<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kofi Amo-Antwi</title>
    <description>The latest articles on DEV Community by Kofi Amo-Antwi (@bigboyka).</description>
    <link>https://dev.to/bigboyka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3810762%2F7582d63f-22f7-44b4-8393-466da7dd677a.png</url>
      <title>DEV Community: Kofi Amo-Antwi</title>
      <link>https://dev.to/bigboyka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bigboyka"/>
    <language>en</language>
    <item>
      <title>Building a Trust Layer: Ratings in GusLift (Current State)</title>
      <dc:creator>Kofi Amo-Antwi</dc:creator>
      <pubDate>Tue, 12 May 2026 17:45:28 +0000</pubDate>
      <link>https://dev.to/guslift/building-a-trust-layer-ratings-in-guslift-current-state-d0i</link>
      <guid>https://dev.to/guslift/building-a-trust-layer-ratings-in-guslift-current-state-d0i</guid>
      <description>&lt;p&gt;GusLift is a campus rideshare app for students at our university. After shipping ride history and profile photos, the next obvious gap was trust: before someone gets in a car (or offers a seat), they want a signal that the other person has shown up and behaved well on past trips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data model:&lt;/strong&gt; one row per person, per ride&lt;br&gt;
Ratings live in Supabase in a ratings table keyed by the ride and who is giving the rating. Conceptually each row stores: which ride it was, who rated (from_user_id), who was rated (to_user_id), and an integer score from 1 to 5.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii9o4n3d5ke1vuwsx5o1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fii9o4n3d5ke1vuwsx5o1.png" alt=" " width="538" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the API side, POST /api/ratings validates that payload and upserts on conflict so a user can adjust their score for the same ride without creating duplicates. GET /api/ratings?user_id=… aggregates every rating received by that user and returns a rounded average plus a count, which is what powers the driver’s headline number on the home screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ride history as the glue&lt;/strong&gt;&lt;br&gt;
The ride history endpoint already loads completed trips, riders, drivers, and cars. It now also pulls all ratings for those ride IDs and, for the currently logged-in user, attaches my_rating: the score they already submitted for that trip, if any.&lt;/p&gt;

&lt;p&gt;That single field unlocks two different UX needs:&lt;/p&gt;

&lt;p&gt;Lists can show a small “you rated this 4/5” pill without a second round-trip per row.&lt;br&gt;
Home can ask “do you have any completed rides where you still have not rated?” by scanning history for the first ride with my_rating == null.&lt;br&gt;
So history stays the source of truth for “what happened,” and ratings are a thin join on top.&lt;/p&gt;

&lt;p&gt;Rider experience: celebrate, then ask&lt;br&gt;
On the rider side, the flow is deliberately emotional before it is transactional. When a ride completes, a full-screen “Ride completed” overlay gives a moment of closure. After it dismisses, a bottom toast slides in: driver name, five stars, optional skip, and a link to ride details.&lt;/p&gt;

&lt;p&gt;The toast is time-limited (progress bar + auto-dismiss), posts to POST /api/ratings on star tap, and shows a short thank-you state before sliding away. If the user skipped the prompt earlier, a best-effort check against ride history can still surface the toast for the oldest unrated completed trip so people are not stuck forever.&lt;/p&gt;

&lt;p&gt;Ride detail (from history or from the toast’s “view details” path) also includes an inline star control so a rating can be submitted or changed later, using the same API.&lt;/p&gt;

&lt;p&gt;Driver experience: reputation, not nagging (for now)&lt;br&gt;
Drivers see their aggregate rating and count on the driver home hero when the GET endpoint returns data. History and detail screens can show the driver’s own past rating for a trip where that applies.&lt;/p&gt;

&lt;p&gt;The backend is symmetric (any user can rate any counterparty on a ride), but the mobile prompts are currently centered on riders rating drivers. Reciprocal “rate your rider” flows are a natural follow-up now that POST and history enrichment are in place.&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>database</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building Ride History &amp; Profile Customization in GusLift</title>
      <dc:creator>Kofi Amo-Antwi</dc:creator>
      <pubDate>Tue, 05 May 2026 18:23:34 +0000</pubDate>
      <link>https://dev.to/guslift/building-ride-history-profile-customization-in-guslift-3lc9</link>
      <guid>https://dev.to/guslift/building-ride-history-profile-customization-in-guslift-3lc9</guid>
      <description>&lt;p&gt;Ride History: Tracking Every Completed Trip&lt;br&gt;
The idea behind Ride History is straightforward: after a driver marks a ride as complete, both the rider and the driver should be able to look back at all their past trips. Not just today's rides, but their entire account history.&lt;/p&gt;

&lt;p&gt;On the backend, the API queries Supabase for every ride tied to the user's Google ID, filtered to only completed rides and ordered newest-first:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yadxjybxm6u524mg4df.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9yadxjybxm6u524mg4df.png" alt=" " width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's no date filter and no row limit -- it returns every completed ride the account has ever taken. On the mobile side, each ride renders as a card showing the date, route, pickup time, and the matched rider or driver's name. Users can also dismiss individual entries or clear the whole list, which sets a driver_hidden or rider_hidden flag without actually deleting data.&lt;/p&gt;

&lt;p&gt;One challenge we ran into was understanding the ride lifecycle. Rides are created with status: "accepted" when a match is confirmed through our real-time matching worker. They only flip to "completed" when the driver explicitly taps "Complete ride" on their dashboard. This means the history screen is intentionally limited to trips both parties actually finished -- not just ones that were matched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw14oxm66sk13nab5n8py.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw14oxm66sk13nab5n8py.png" alt=" " width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Profile Image Picker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwxqwk4zbgxmmumidd44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwxqwk4zbgxmmumidd44.png" alt=" " width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also built a reusable PhotoPicker component that lets both riders and drivers set a profile photo during setup. It uses Expo's ImagePicker API to request gallery permissions, open the device photo library, and crop to a 1:1 square:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjxwk6cgent38mpwivz3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjxwk6cgent38mpwivz3.png" alt=" " width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selected image is sent to the backend as part of a FormData payload during registration, where it gets uploaded to Supabase Storage and the public URL is saved to the user's profile. This was trickier than expected because React Native and web handle file blobs differently -- on web we can pass the raw File object, but on native we pass a { uri, name, type } descriptor that fetch knows how to stream as multipart form data.&lt;/p&gt;

&lt;p&gt;The profile photo then shows up across the app: in the rider's home screen header, in the driver's rider list, and anywhere a user's identity is displayed.&lt;/p&gt;

&lt;p&gt;Author: Kofi Amo-Antwi&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>mobile</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
