Skip to content

feat(web): add order tracking timeline#322

Merged
onerandomdevv merged 4 commits into
devfrom
feat/web-order-tracking
May 23, 2026
Merged

feat(web): add order tracking timeline#322
onerandomdevv merged 4 commits into
devfrom
feat/web-order-tracking

Conversation

@SAHEED2010
Copy link
Copy Markdown
Collaborator

@SAHEED2010 SAHEED2010 commented May 23, 2026

What does this PR do?

Implements W-10 — Order Tracking + Vertical Timeline. This adds the shopper order list and order detail pages, including filter tabs, safe order cards, escrow banner, vertical order timeline, OTP confirmation input, and status-specific actions.

Task

W-10 — Order Tracking + Vertical Timeline

What changed

  • Added /buyer/orders
  • Added /buyer/orders/[id]
  • Added order list with filter tabs:
    • All
    • Processing
    • Dispatched
    • Delivered
    • Returns
  • Added order detail page with:
    • escrow banner
    • product summary
    • delivery/payment cards
    • vertical 5-step timeline
    • status-specific notices/actions
  • Added OTP input component for delivery confirmation
  • Added placeholder dispute dialog
  • Extended apps/web/src/lib/orders.ts with shopper-safe order helpers
  • Added a small backend read-only include extension for shopper order display data

Files changed

  • apps/web/src/app/(shopper)/buyer/orders/page.tsx
  • apps/web/src/app/(shopper)/buyer/orders/OrdersListClient.tsx
  • apps/web/src/app/(shopper)/buyer/orders/[id]/page.tsx
  • apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx
  • apps/web/src/components/order/OrderTimeline.tsx
  • apps/web/src/components/order/DeliveryCodeInput.tsx
  • apps/web/src/components/order/OrderCard.tsx
  • apps/web/src/components/order/EscrowBanner.tsx
  • apps/web/src/components/order/RaiseDisputeDialog.tsx
  • apps/web/src/lib/orders.ts
  • apps/backend/src/modules/order/order.service.ts

Backend endpoints verified

  • GET /orders/me
  • GET /orders/:id
  • POST /orders/:id/cancel
  • POST /orders/:id/confirm-delivery
  • GET /orders/:id/tracking
  • POST /orders/:id/disputes exists but is not called in this PR

Backend change note

This PR includes one read-only backend change in:

apps/backend/src/modules/order/order.service.ts

The order detail/list Prisma includes were extended with safe shopper display fields only:

  • product name
  • product image URL
  • product code
  • store name
  • store handle
  • business name
  • verification tier

No schema changes were added.

No DTO changes were added.

No order lifecycle, status transition, payment, payout, ledger, refund, stock, or dropship logic was changed.

No unsafe fields were added to the select.

Behavior implemented

Order list

  • /buyer/orders renders shopper order cards
  • Filter tabs work for:
    • All
    • Processing
    • Dispatched
    • Delivered
    • Returns
  • Each order card links to /buyer/orders/[id]
  • Empty states and loading skeletons are included

Order detail

  • /buyer/orders/[id] renders:
    • order code
    • product summary
    • escrow banner
    • delivery/payment cards
    • timeline
    • status-specific action area

Timeline

Timeline steps:

  • Order Placed
  • Processing
  • Dispatched
  • Delivered
  • Completed

Timestamps are derived from backend order events where available.

Fallbacks are limited to:

  • createdAt for Order Placed
  • dispatchedAt for Dispatched

Missing times render as unavailable instead of being fabricated.

Status actions

  • PENDING_PAYMENT: cancel order action
  • DISPATCHED / IN_TRANSIT: OTP input and Confirm I Received It action
  • DELIVERED: dispute placeholder and review coming soon
  • COMPLETED: buy again link when product code is available, review coming soon
  • CANCELLED, DISPUTE, REFUND_PENDING: safe read-only notices

Dispute CTA

This PR does not implement full dispute filing UI.

The Raise Dispute CTA opens a placeholder dialog with approved copy:

“Dispute filing is coming soon. You'll be able to raise a dispute from this order page during the dispute window.”

No POST /orders/:id/disputes call is made.

Dropship safety

The frontend uses a strict shopper order whitelist and never spreads raw order responses.

The UI never renders:

  • orderType
  • storeType
  • physicalStoreId
  • linkedOrderId
  • sourcedProductId
  • dropshipperCostKobo
  • digitalMarginKobo
  • buyerId
  • raw storeId
  • fulfillment order data

The normalizer refuses rows with non-null dropship internals or orderType === "DROPSHIP_FULFILLMENT" as defense in depth.

No frontend payout-split computation was added.

Verification

Web:

  • pnpm run lint — PASS
  • npx tsc --noEmit — PASS
  • pnpm run build — PASS

Backend:

  • npx prisma generate — PASS
  • npx tsc --noEmit — PASS
  • pnpm run lint — PASS

Build confirms these routes are registered:

  • /buyer/orders
  • /buyer/orders/[id]

Type of change

  • New feature
  • Bug fix
  • Refactor / cleanup
  • Database migration included
  • Chore / maintenance
  • Documentation

Area affected

  • Web
  • Backend
  • WhatsApp
  • Shared package
  • Database / Prisma
  • GitHub / CI / infrastructure

How to test this

  1. Log in as a shopper.
  2. Complete a Buy Now checkout.
  3. From the order confirmed screen, click Track Order.
  4. Confirm /buyer/orders/[id] loads.
  5. Confirm the order timeline and escrow banner render.
  6. Navigate to /buyer/orders.
  7. Confirm the order appears in the correct filter tab.
  8. For dispatched orders, enter OTP and confirm delivery.
  9. Confirm no dropship/internal fields appear in the UI.

Expected result:

  • Shopper can view order list and order detail.
  • Timeline reflects current order status.
  • OTP confirmation calls the verified backend endpoint.
  • Dropship/private fields are not exposed.
  • W-09b remains unchanged.

Pre-commit checklist

  • Web lint/type/build pass when web is affected
  • Backend type/lint pass when backend is affected
  • Shared package build passes when shared is affected
  • No console.log left in production code
  • No secrets or .env files committed
  • No new any types added
  • No emojis added
  • Tailwind/design tokens used
  • No Prisma migration added
  • No payment/payout/ledger/order lifecycle logic changed

Screenshots

Required for UI changes. Add mobile and desktop screenshots of:

  • /buyer/orders
  • /buyer/orders/[id]

Notes for reviewer

This PR intentionally does not implement W-11 Cart, W-16 Store Orders, full dispute filing UI, review flow, logistics booking UI, payment changes, payout changes, or backend order lifecycle changes.

Summary by CodeRabbit

Release Notes

  • New Features
    • Buyer order management: View your orders with status-based filtering and search
    • Order details page with complete information, status timeline, and escrow tracking
    • Delivery confirmation using a 6-digit verification code
    • Ability to cancel orders and initiate disputes
    • Order status updates and tracking information display

Review Change Stack

Implements W-10. Shoppers can now view all their orders at /buyer/orders
and drill into a single order at /buyer/orders/[id] with a vertical
timeline stepper and status-driven actions.

Routes
- /buyer/orders                 → list with filter tabs (All / Processing /
                                  Dispatched / Delivered / Returns)
- /buyer/orders/[id]            → detail view with escrow banner, product
                                  summary, timeline, delivery + payment
                                  cards, and per-status action area
- /buyer/orders/confirmed/[id]  → existing W-09b route, untouched (its
                                  Track Order CTA already points here)

Action surface (verified against current backend contracts)
- PENDING_PAYMENT   → [Cancel order] → POST /orders/:id/cancel (buyer
                      cancel is rejected after PAID; CTA is hidden for
                      every other status)
- DISPATCHED/IN_TRANSIT → 6-digit OTP input (the backend stores only the
                      bcrypt hash and never returns the plaintext; the
                      buyer enters the code from their notification) +
                      [Confirm I Received It] → POST /orders/:id/confirm-delivery
- DELIVERED         → [Raise a dispute] text link → placeholder dialog
                      (W-10 does not call POST /orders/:id/disputes;
                      full filing UI is out of scope) + dispute-window
                      countdown only when backend disputeWindowEndsAt is
                      in the future
- COMPLETED         → [Buy again] → /p/{productCode} when product code is
                      available, otherwise hidden; same dispute placeholder
- CANCELLED / DISPUTE / REFUND_PENDING → read-only notices

New components (apps/web/src/components/order/)
- OrderTimeline.tsx       — 5-step vertical stepper. Timestamps read
                            from order.events; only two narrow fallbacks
                            (createdAt for "Order Placed", dispatchedAt
                            for "Dispatched"); missing times render as "—"
                            rather than being fabricated.
- DeliveryCodeInput.tsx   — 6 individual digit inputs with auto-advance,
                            backspace step-back, arrow navigation, and
                            paste support (≥44px touch targets).
- OrderCard.tsx           — list-row card with order code, time-ago,
                            product thumbnail/name, store name, status
                            badge, and escrow status line.
- EscrowBanner.tsx        — active / completed / muted tones, driven by
                            status + payoutStatus.
- RaiseDisputeDialog.tsx  — placeholder dialog only; copy: "Dispute
                            filing is coming soon. You'll be able to
                            raise a dispute from this order page during
                            the dispute window."

Library
- apps/web/src/lib/orders.ts expanded:
  * ShopperOrder shape v2: adds product, store, dispatchedAt,
    disputeWindowEndsAt, payoutStatus, deliveryMethod, events.
  * Status enum reconciled with backend (PREPARING, IN_TRANSIT, DISPUTE,
    REFUND_PENDING).
  * fetchMyOrders, cancelOrder, confirmDelivery, fetchOrderTracking
    added. All requests use the existing api helper.
  * Defensive guard kept and extended: refuses to surface any row that
    leaks dropshipperCostKobo / digitalMarginKobo / linkedOrderId /
    sourcedProductId, or that has orderType === DROPSHIP_FULFILLMENT.

Backend (single read-only Prisma include extension — no schema, DTO, or
lifecycle changes)
- apps/backend/src/modules/order/order.service.ts:
  * getById include now also selects product { name, imageUrl, productCode }
    and storeProfile { storeName, storeHandle, businessName, verificationTier }.
  * listByBuyer include extended the same way (replaces the previous
    storeProfile.businessName-only select).
- stripDropshipPrivateFields still runs unchanged.
- Only public-safe fields are joined: physical store identity, bank
  details, Paystack recipient fields, and dropship internals remain
  invisible to shoppers.

Dropship safety
- Frontend never reads or computes orderType, storeType, physicalStoreId,
  linkedOrderId, sourcedProductId, dropshipperCostKobo, digitalMarginKobo,
  buyerId, raw storeId (except for internal routing), or fulfillment
  order data.
- For DROPSHIP_CUSTOMER orders the joined product is the physical
  product the buyer purchased (safe to show); the joined store is the
  digital store (the public seller — safe to show). Physical store stays
  invisible.

W-09b confirmation screen unchanged.
@SAHEED2010 SAHEED2010 requested a review from onerandomdevv as a code owner May 23, 2026 03:09
@codesandbox
Copy link
Copy Markdown

codesandbox Bot commented May 23, 2026

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@onerandomdevv, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 56 minutes and 49 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 80a0308b-f315-46a5-9daa-79c7a01ef1a4

📥 Commits

Reviewing files that changed from the base of the PR and between 9631727 and 13dafbf.

📒 Files selected for processing (3)
  • apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx
  • apps/web/src/components/order/DeliveryCodeInput.tsx
  • apps/web/src/components/order/OrderTimeline.tsx
📝 Walkthrough

Walkthrough

This PR adds a complete buyer orders experience: backend service queries are narrowed to required fields, order types and client API are defined with new status states and payout/delivery tracking, reusable display components render order cards/timelines/escrow/OTP input, and server pages deliver order listing with status filtering and detailed order view with tracking events and delivery confirmation.

Changes

Buyer Orders Experience

Layer / File(s) Summary
Backend order data narrowing
apps/backend/src/modules/order/order.service.ts
OrderService.getById and listByBuyer explicitly select specific product identity fields and a richer storeProfile subset instead of broader defaults.
Order types and client API
apps/web/src/lib/orders.ts
ShopperOrderStatus enum updated to include PREPARING, DISPUTE, REFUND_PENDING. ShopperOrder interface extended with deliveryMethod, dispatchedAt, disputeWindowEndsAt, payoutStatus, and nested product/store/events. Normalization refactored with status coercion, defensive dropship filtering, and new public APIs: fetchMyOrders, cancelOrder, confirmDelivery, fetchOrderTracking.
Order display components
apps/web/src/components/order/OrderCard.tsx, apps/web/src/components/order/OrderTimeline.tsx, apps/web/src/components/order/EscrowBanner.tsx, apps/web/src/components/order/DeliveryCodeInput.tsx, apps/web/src/components/order/RaiseDisputeDialog.tsx
OrderCard shows order summary with status badge and escrow text. OrderTimeline renders fixed-step progress with computed reached index and timestamp fallbacks. EscrowBanner displays escrow/payout status with tone-driven styling. DeliveryCodeInput provides 6-digit OTP input with focus management and paste distribution. RaiseDisputeDialog shows placeholder dispute UI.
Order listing page with tab filtering
apps/web/src/app/(shopper)/buyer/orders/OrdersListClient.tsx, apps/web/src/app/(shopper)/buyer/orders/page.tsx
OrdersListClient fetches buyer orders, filters by tab-selected status, and renders tab bar with OrderCard list, skeleton loader, empty state with per-tab hints, and error UI. OrdersPage wraps OrdersListClient with metadata.
Order detail page with tracking and confirmation
apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx, apps/web/src/app/(shopper)/buyer/orders/[id]/page.tsx
OrderDetailPage extracts route param and renders OrderDetailClient. OrderDetailClient fetches order and tracking events separately, manages OTP/dialog state, and renders full detail: header, escrow banner, status-specific notices, timeline, delivery/payment cards, action area for delivery confirmation (OTP validation + confirmDelivery), cancellation (window.confirm guard + cancelOrder), and dispute dialog. Includes Notice helper, paymentStatusLabel, skeleton and error UI with navigation back to list.

Sequence Diagram(s)

sequenceDiagram
  participant Backend
  participant fetchMyOrders
  participant normalizeOrder
  participant ShopperUI
  Backend->>fetchMyOrders: raw order array or paginated response
  fetchMyOrders->>normalizeOrder: raw RawShopperOrder
  normalizeOrder->>normalizeOrder: validate status, coerce fields, filter dropship
  normalizeOrder->>normalizeOrder: normalize joined product/store/events
  normalizeOrder->>ShopperUI: typed ShopperOrder
  ShopperUI->>Backend: confirmDelivery/cancelOrder/fetchOrderTracking
Loading
sequenceDiagram
  participant Shopper
  participant OrderDetailClient
  participant API
  participant UI
  Shopper->>OrderDetailClient: load /buyer/orders/{id}
  OrderDetailClient->>API: fetchOrderById(orderId)
  OrderDetailClient->>API: fetchOrderTracking(orderId)
  API->>OrderDetailClient: ShopperOrder + tracking events
  OrderDetailClient->>UI: render header, timeline, escrow
  Shopper->>UI: enter OTP and click Confirm
  UI->>OrderDetailClient: handleConfirm(otp)
  OrderDetailClient->>API: confirmDelivery(orderId, otp)
  API->>OrderDetailClient: success/error
  OrderDetailClient->>UI: toast, reload
  Shopper->>UI: click Raise Dispute
  UI->>OrderDetailClient: open RaiseDisputeDialog
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • coded-devs/twizrr#316: Refines shopper-facing OrderService field selection in the same getById/listByBuyer code paths where PR #316 added dropship privacy filtering.
  • coded-devs/twizrr#312: This PR's confirmDelivery and order management flows are built to consume the backend's OTP-based delivery confirmation endpoint and lifecycle changes from PR #312.
  • coded-devs/twizrr#107: Wires shopper order tracking display via fetchOrderTracking, which relies on the v3 tracking endpoints and service methods added in PR #107.

Suggested reviewers

  • onerandomdevv

Poem

🐰 A curious rabbit hops through orders new,
With tabs and timelines and escrow on view.
From confirmation codes to timeline steps clear,
The shopper's journey is now bright and dear. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.28% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(web): add order tracking timeline' accurately reflects the main change: adding order tracking UI with a vertical timeline component, though it does not capture the full scope of order list and detail pages.
Description check ✅ Passed The PR description is comprehensive and follows the template structure with all required sections: What does this PR do, Type of change, Area affected, How to test, Pre-commit checklist, Screenshots, and Notes for reviewer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-order-tracking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/app/`(shopper)/buyer/orders/[id]/OrderDetailClient.tsx:
- Around line 173-176: The code assigns a user-facing string directly from
err.message (in OrderDetailClient where the variable message is built around
lines referenced and similarly at the other occurrence), which can leak internal
details; change this to always set a generic, user-friendly message (e.g., "We
couldn't verify that code. Double-check and try again.") for display and move
the raw error details into a separate log or telemetry call (console.error or
your monitoring function) so internals are recorded but not shown to shoppers;
update both places that build message (the block using typeof (err as {
message?: string })?.message and the similar block at the other location) to
follow this pattern.
- Around line 84-87: The loadTracking function can throw from fetchOrderTracking
and currently has no error handling; wrap the await fetchOrderTracking(orderId)
call in a try/catch, log or surface the error appropriately (e.g., console.error
or a UI error state) and only call setTracking(events) when not cancelled and
events were retrieved; keep the existing cancelled check and ensure the catch
block avoids unhandled promise rejections (and optionally set an error state or
no-op when cancelled).
- Around line 158-162: Replace the ad-hoc otp length checks with a Zod-driven
validation: define a Zod schema (e.g., otpSchema = z.string().length(6)) and
validate otp inside handleConfirm using otpSchema.safeParse(otp) instead of
otp.length !== 6; if validation fails, call setConfirmError with a suitable
message from the schema result, otherwise proceed with confirmation logic. Also
derive the confirm button disabled prop from the same schema result (e.g.,
!otpSchema.safeParse(otp).success) so both the runtime check in handleConfirm
and the UI disabled state use the identical Zod rule; update references to otp,
handleConfirm, and setConfirmError accordingly.
- Around line 45-79: Replace the ad-hoc useEffect/fetches in
OrderDetailClient.tsx with TanStack Query: create useQuery calls for fetching
the order and tracking using clear queryKeys (e.g., ['order', orderId] and
['order', orderId, 'tracking']) and remove the local fetch logic in
reload/useEffect; wrap fetchOrderTracking in a try/catch inside its query or in
an async function used by useQuery to avoid unhandled rejections; validate OTP
using a Zod schema (e.g., otpSchema.parse/ safeParse) instead of inline
otp.length !== 6; and update handleConfirm/handleCancel to map backend errors to
user-safe messages (don’t forward err.message) or log the raw error internally
while returning a generic error string to the UI.

In `@apps/web/src/app/`(shopper)/buyer/orders/[id]/page.tsx:
- Around line 9-17: The page types params incorrectly as a Promise and awaits
it; update OrderDetailPageProps to use params: { id: string } (synchronous) and
remove the await in OrderDetailPage so it directly reads const { id } = params
and returns <OrderDetailClient orderId={id} />; adjust the interface and the
OrderDetailPage function signature accordingly.

In `@apps/web/src/app/`(shopper)/buyer/orders/OrdersListClient.tsx:
- Around line 4-13: The component is using manual fetch state via useEffect
instead of TanStack Query; replace the bespoke loading/error logic (the
useEffect and local state around fetchMyOrders in OrdersListClient.tsx) with a
useQuery call that references the centralized query key queryKeys.orders.mine,
returning data typed as ShopperOrder[] and status as needed (useQuery for
fetchMyOrders). Remove local loading/error state, wire the query's
isLoading/isError/data into the existing UI that renders Skeleton,
AlertCircle/Package placeholders and OrderCard, and ensure any cache
invalidation or refetch elsewhere uses queryKeys.orders.mine for consistency.

In `@apps/web/src/components/order/DeliveryCodeInput.tsx`:
- Around line 40-45: The code currently uses arr.join("").trim() which collapses
empty slots and shifts digits when editing mid-code; replace any use of .trim()
(e.g., in writeDigit and the other callbacks that build the code string) with
arr.join("") so the resulting value preserves empty placeholders and positions,
and ensure onChange receives the fixed-length positional string (or empty-string
placeholders) rather than a trimmed, collapsed string.

In `@apps/web/src/components/order/OrderTimeline.tsx`:
- Around line 67-70: The switch hardcodes CANCELLED/DISPUTE/REFUND_PENDING to
return step index 1 which misrepresents different terminal paths; instead
compute the freeze step by inspecting the order's events or lifecycle metadata
(e.g., use the latest event type/timestamp or an existing mapping helper) and
return the corresponding step index. Locate the switch handling those statuses
in OrderTimeline (the case "CANCELLED"/"DISPUTE"/"REFUND_PENDING" block) and
replace the fixed return 1 with a call to a function that derives the correct
step (e.g., getStepIndexFromEvents(events) or mapLifecycleToStep(lifecycle)) so
the frozen timeline reflects where the terminal action actually occurred. Ensure
the logic falls back to a sensible default if no event evidence exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ee2e3d6a-85c5-46cc-b243-52da14e0611d

📥 Commits

Reviewing files that changed from the base of the PR and between 76f4797 and 9631727.

📒 Files selected for processing (11)
  • apps/backend/src/modules/order/order.service.ts
  • apps/web/src/app/(shopper)/buyer/orders/OrdersListClient.tsx
  • apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx
  • apps/web/src/app/(shopper)/buyer/orders/[id]/page.tsx
  • apps/web/src/app/(shopper)/buyer/orders/page.tsx
  • apps/web/src/components/order/DeliveryCodeInput.tsx
  • apps/web/src/components/order/EscrowBanner.tsx
  • apps/web/src/components/order/OrderCard.tsx
  • apps/web/src/components/order/OrderTimeline.tsx
  • apps/web/src/components/order/RaiseDisputeDialog.tsx
  • apps/web/src/lib/orders.ts

Comment on lines +45 to +79
const reload = useCallback(async () => {
try {
const next = await fetchOrderById(orderId);
if (!next) {
setState("error");
return;
}
setOrder(next);
setState("ready");
} catch {
setState("error");
}
}, [orderId]);

useEffect(() => {
let cancelled = false;
async function load() {
try {
const fetched = await fetchOrderById(orderId);
if (cancelled) return;
if (!fetched) {
setState("error");
return;
}
setOrder(fetched);
setState("ready");
} catch {
if (!cancelled) setState("error");
}
}
load();
return () => {
cancelled = true;
};
}, [orderId]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify this component is not using TanStack Query hooks and inspect existing order query key patterns.
rg -n --type=tsx 'useQuery|useMutation|useSuspenseQuery|queryClient|invalidateQueries' -- "apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"
rg -n --type=ts 'queryKeys|orders|fetchOrderById|fetchOrderTracking' -- "apps/web/src/lib/orders.ts"

Repository: coded-devs/twizrr

Length of output: 548


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"

echo "== Search for TanStack Query hooks in target component =="
rg -n "useQuery|useMutation|useSuspenseQuery|useQueryClient|invalidateQueries|queryClient|startTransition\(" "$FILE" || true

echo "== Show first 220 lines with numbers (for context/OTP validation) =="
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n

echo "== Inspect apps/web/src/lib/orders.ts for query key patterns =="
LIB="apps/web/src/lib/orders.ts"
wc -l "$LIB" || true
sed -n '1,260p' "$LIB" | cat -n

echo "== Search for centralized order queryKeys usage across apps/web/src =="
rg -n "queryKeys|ordersQuery|orderQuery|getOrders|orderDetails" apps/web/src --glob='*.ts' --glob='*.tsx' || true

Repository: coded-devs/twizrr

Length of output: 17561


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"

echo "== Search for TanStack Query hooks in target component =="
rg -n "useQuery|useMutation|useSuspenseQuery|useQueryClient|invalidateQueries|queryClient|startTransition\(" "$FILE" || true

echo "== Show first 220 lines with numbers (for context/OTP validation) =="
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n

echo "== Inspect apps/web/src/lib/orders.ts for query key patterns =="
LIB="apps/web/src/lib/orders.ts"
wc -l "$LIB" || true
sed -n '1,260p' "$LIB" | cat -n

echo "== Search for centralized order queryKeys usage across apps/web/src =="
rg -n "queryKeys|ordersQuery|orderQuery|getOrders|orderDetails" apps/web/src --glob='*.ts' --glob='*.tsx' || true

Repository: coded-devs/twizrr

Length of output: 17561


Ensure order/tracking data uses TanStack Query (queryKeys) instead of useEffect data fetching

OrderDetailClient.tsx loads the order and tracking via useEffect calling fetchOrderById / fetchOrderTracking and local component state (state, order, tracking) rather than using TanStack Query hooks with centralized queryKeys.

Additional gaps in the same component:

  • fetchOrderTracking(orderId) in the tracking useEffect has no error guard (try/catch), so failures can cause an unhandled rejection.
  • OTP validation is done inline via otp.length !== 6 (no Zod schema).
  • Error handling in handleConfirm/handleCancel forwards backend err.message when present, which may leak backend details instead of using a safer, user-generic message.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(shopper)/buyer/orders/[id]/OrderDetailClient.tsx around
lines 45 - 79, Replace the ad-hoc useEffect/fetches in OrderDetailClient.tsx
with TanStack Query: create useQuery calls for fetching the order and tracking
using clear queryKeys (e.g., ['order', orderId] and ['order', orderId,
'tracking']) and remove the local fetch logic in reload/useEffect; wrap
fetchOrderTracking in a try/catch inside its query or in an async function used
by useQuery to avoid unhandled rejections; validate OTP using a Zod schema
(e.g., otpSchema.parse/ safeParse) instead of inline otp.length !== 6; and
update handleConfirm/handleCancel to map backend errors to user-safe messages
(don’t forward err.message) or log the raw error internally while returning a
generic error string to the UI.

Comment thread apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx
Comment on lines +158 to +162
async function handleConfirm() {
if (otp.length !== 6) {
setConfirmError("Enter all 6 digits of your delivery code.");
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether this file already uses Zod/RHF validation primitives.
rg -n --type=tsx 'zod|z\.object|safeParse|react-hook-form|useForm|resolver' -- "apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"

Repository: coded-devs/twizrr

Length of output: 89


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"

echo "== Lines around handleConfirm =="
nl -ba "$FILE" | sed -n '130,220p'

echo
echo "== Zod / RHF usage in this file =="
rg -n --hidden --no-ignore -S \
  'zod|z\.object|safeParse|react-hook-form|useForm|resolver' \
  "$FILE" || true

Repository: coded-devs/twizrr

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"

echo "== Lines 130-220 =="
awk 'NR>=130 && NR<=220 {printf "%d\t%s\n", NR, $0}' "$FILE"

echo
echo "== Zod / RHF usage in this file =="
rg -n --hidden --no-ignore -S \
  'zod|z\.object|safeParse|react-hook-form|useForm|resolver' \
  "$FILE" || true

Repository: coded-devs/twizrr

Length of output: 3602


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx"
echo "== Lines 380-450 =="
awk 'NR>=380 && NR<=450 {printf "%d\t%s\n", NR, $0}' "$FILE"

Repository: coded-devs/twizrr

Length of output: 3110


Replace inline OTP length checks with Zod schema validation
OrderDetailClient.tsx validates the 6-digit delivery code via otp.length !== 6 inside handleConfirm (158-162) and separately disables the confirm button via disabled={otp.length !== 6} (428-434), with no Zod/RHF validation primitives used in this component. Use a single Zod schema for the OTP (e.g., z.string().length(6)) and drive both the error message and the disabled state from the schema result.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(shopper)/buyer/orders/[id]/OrderDetailClient.tsx around
lines 158 - 162, Replace the ad-hoc otp length checks with a Zod-driven
validation: define a Zod schema (e.g., otpSchema = z.string().length(6)) and
validate otp inside handleConfirm using otpSchema.safeParse(otp) instead of
otp.length !== 6; if validation fails, call setConfirmError with a suitable
message from the schema result, otherwise proceed with confirmation logic. Also
derive the confirm button disabled prop from the same schema result (e.g.,
!otpSchema.safeParse(otp).success) so both the runtime check in handleConfirm
and the UI disabled state use the identical Zod rule; update references to otp,
handleConfirm, and setConfirmError accordingly.

Comment thread apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx Outdated
Comment on lines +9 to +17
interface OrderDetailPageProps {
params: Promise<{ id: string }>;
}

export default async function OrderDetailPage({
params,
}: OrderDetailPageProps) {
const { id } = await params;
return <OrderDetailClient orderId={id} />;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) Confirm installed Next.js version in this repo.
fd -a package.json | xargs -I{} sh -c 'echo "--- {} ---"; jq -r ".dependencies.next // .devDependencies.next // empty" "{}"'

# 2) Find async Promise-typed route params usage in App Router pages.
rg -n --type=ts --type=tsx 'params\s*:\s*Promise<\{[^}]*\}>' -- "apps/web/src/app"

Repository: coded-devs/twizrr

Length of output: 314


Align params typing with Next.js 14 App Router expectations.

This repo is on Next.js ^14.2.35 (apps/web/package.json), but the page types params as Promise<{ id: string }> and awaits it. Use synchronous params: { id: string } and remove the await.

Proposed fix
 interface OrderDetailPageProps {
-  params: Promise<{ id: string }>;
+  params: { id: string };
 }
 
 export default async function OrderDetailPage({
   params,
 }: OrderDetailPageProps) {
-  const { id } = await params;
+  const { id } = params;
   return <OrderDetailClient orderId={id} />;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(shopper)/buyer/orders/[id]/page.tsx around lines 9 - 17,
The page types params incorrectly as a Promise and awaits it; update
OrderDetailPageProps to use params: { id: string } (synchronous) and remove the
await in OrderDetailPage so it directly reads const { id } = params and returns
<OrderDetailClient orderId={id} />; adjust the interface and the OrderDetailPage
function signature accordingly.

Comment on lines +4 to +13
import { useEffect, useMemo, useState } from "react";
import { AlertCircle, Package } from "lucide-react";
import { Skeleton } from "@/components/ui/Skeleton";
import { OrderCard } from "@/components/order/OrderCard";
import { cn } from "@/lib/utils";
import {
fetchMyOrders,
type ShopperOrder,
type ShopperOrderStatus,
} from "@/lib/orders";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Replace manual fetch state with TanStack Query + centralized query key.

Line 66 implements bespoke loading/error lifecycle with useEffect, which diverges from the project’s required server-state pattern. Please move this to React Query (useQuery) with a shared queryKeys.orders.mine key for consistent caching, retries, and invalidation across buyer-order flows.

As per coding guidelines, "Use React Query (TanStack Query v5) for server state management with centralized queryKeys".

Also applies to: 62-82

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/`(shopper)/buyer/orders/OrdersListClient.tsx around lines 4
- 13, The component is using manual fetch state via useEffect instead of
TanStack Query; replace the bespoke loading/error logic (the useEffect and local
state around fetchMyOrders in OrdersListClient.tsx) with a useQuery call that
references the centralized query key queryKeys.orders.mine, returning data typed
as ShopperOrder[] and status as needed (useQuery for fetchMyOrders). Remove
local loading/error state, wire the query's isLoading/isError/data into the
existing UI that renders Skeleton, AlertCircle/Package placeholders and
OrderCard, and ensure any cache invalidation or refetch elsewhere uses
queryKeys.orders.mine for consistency.

Comment thread apps/web/src/components/order/DeliveryCodeInput.tsx
Comment thread apps/web/src/components/order/OrderTimeline.tsx Outdated
- DeliveryCodeInput: stop collapsing positional slots when the user fills
  a non-final box first. The wire-format string is now always 6 chars
  with " " for empty slots; positions are preserved. OrderDetailClient
  extracts the digit-only payload (cleanOtp via /\D/g) for validation
  and submission, so a 6-char value with placeholder spaces no longer
  passes the length check.
- OrderTimeline: terminal-state (CANCELLED / DISPUTE / REFUND_PENDING)
  freeze step is now derived from the order's event history rather than
  hardcoded to step 1. A cancel-at-PENDING_PAYMENT freezes at "Order
  Placed"; a dispute raised after dispatch freezes at "Dispatched"; etc.
- OrderDetailClient: confirm-delivery and cancel error paths no longer
  forward backend err.message to the UI. Both surface generic copy and
  the OTP helper text above the input already gives the buyer context
  for failed attempts.
- OrderDetailClient: tracking effect now has an explicit try/catch as
  defense in depth. fetchOrderTracking already swallows errors and
  returns [], but this guards against future regressions.

Intentionally NOT actioned:
- Migrating order/tracking data loading to TanStack Query (PR #322
  comments 1, 6) and migrating OTP validation to Zod + RHF (comment 3):
  the apps/web codebase has no centralized queryKeys module and no
  @hookform/resolvers dependency. Same posture as PR #317 — these are
  cross-cutting refactors that need their own task to establish the
  patterns.
- Switching `params: Promise<{ id: string }>` to a synchronous shape
  (comment 5): W-09a and W-09b already use the async-params pattern
  successfully on Next 14.2.x; reverting would diverge from established
  routes. The Promise form is also forward-compatible with Next 15
  where async params become required.
@onerandomdevv onerandomdevv merged commit b9d2b65 into dev May 23, 2026
8 checks passed
@SAHEED2010 SAHEED2010 deleted the feat/web-order-tracking branch May 23, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants