Skip to content

(SP: 1) [Cart] adding route for user orders to cart page#337

Merged
ViktorSvertoka merged 21 commits intodevelopfrom
lso/feat/shop
Feb 17, 2026
Merged

(SP: 1) [Cart] adding route for user orders to cart page#337
ViktorSvertoka merged 21 commits intodevelopfrom
lso/feat/shop

Conversation

@liudmylasovetovs
Copy link
Collaborator

@liudmylasovetovs liudmylasovetovs commented Feb 17, 2026

Description

This PR adds a lightweight “My Orders” entry point to the Shop Cart page and wires it to the existing Shop Orders API, while fixing the initial render flicker on refresh by using the platform’s global loader.


Related Issue

Issue: #<issue_number>


Changes

  • Added a CartPage “My Orders” summary card (order count + latest order quick link) and placed it under the cart items list for better layout consistency.
  • Integrated the orders summary fetch from GET /api/shop/orders with safe public-page behavior (silently ignores 401/403 when user is not authenticated).
  • Eliminated the “empty cart” flash on refresh by gating initial cart render behind the global animated Loader until client/cart state is ready (single, consistent first paint).
  • Kept styling consistent with existing Shop/UI utility classes and platform conventions (links, borders, muted surfaces, focus styles).

Database Changes (if applicable)

  • Schema migration required
  • Seed data updated
  • Breaking changes to existing queries
  • Transaction-safe migration
  • Migration tested locally on Neon

How Has This Been Tested?

  • Tested locally
  • Verified in development environment
  • Checked responsive layout (if UI-related)
  • Tested accessibility (keyboard / screen reader)

Screenshots (if applicable)

N/A (minor UI placement + loading behavior)


Checklist

Before submitting

  • Code has been self-reviewed
  • No TypeScript or console errors
  • Code follows project conventions
  • Scope is limited to this feature/fix
  • No unrelated refactors included
  • English used in code, commits, and docs
  • New dependencies discussed with team
  • Database migration tested locally (if applicable)
  • GitHub Projects card moved to In Review

Reviewers

Summary by CodeRabbit

  • New Features

    • Orders summary card in the cart showing total orders, link to Orders page, and quick access to the latest order.
  • UX Improvements

    • Cart delays rendering until client is ready, shows loading spinner or count badge, and handles unavailable/unauthorized order data gracefully.
    • Cart persistence scoped per user so cart state follows signed-in users.
  • Chores

    • Added orders API endpoint, DB index and migration; updated tooling and translations for loading states.

…wnership test and pass all pre-prod invariants
@netlify
Copy link

netlify bot commented Feb 17, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 349929c
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/69940a7de277f400080a3742
😎 Deploy Preview https://deploy-preview-337--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel
Copy link
Contributor

vercel bot commented Feb 17, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devlovers-net Ready Ready Preview, Comment Feb 17, 2026 6:29am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Adds a server API to return authenticated user orders, a client-side orders summary and Orders card on the cart page, per-user cart storage and CartProvider owner scoping, a DB index and migration for orders(user_id, created_at), a drizzle-kit devDependency bump, and related translation strings.

Changes

Cohort / File(s) Summary
Cart Page Orders Integration
frontend/app/[locale]/shop/cart/CartPageClient.tsx
Adds client-side orders fetch with AbortController (2500ms), OrdersSummaryState, isOrdersLoading/isClientReady, robust JSON shape handling, and renders an Orders card (link, badge/loader, latest order link) using shop.orders translations.
Orders API Endpoint
frontend/app/api/shop/orders/route.ts
New GET handler + export const dynamic = 'force-dynamic'; enforces auth via getCurrentUser(), queries orders joined with orderItems, aggregates primary item label and itemCount, serializes rows, returns no-store JSON, and emits structured logs with duration.
Cart owner scoping / persistence
frontend/components/header/AppChrome.tsx, frontend/components/shop/CartProvider.tsx, frontend/lib/cart.ts
Adds userIdcartOwnerId prop threading: CartProvider becomes owner-aware, storage keys are per-owner, legacy migration helper added, signatures updated for getStored/persist/rehydrate/clear, and ownerId passed through sync/rehydration/error flows.
Layout change
frontend/app/[locale]/layout.tsx
Passes userId (string
DB schema, migration & journal
frontend/db/schema/shop.ts, frontend/drizzle/0014_curvy_ironclad.sql, frontend/drizzle/meta/_journal.json
Adds index idx_orders_user_id_created_at on (userId, createdAt) with SQL migration and journal entry; minor formatting tweak in a type alias.
Tooling
frontend/package.json
Bumps devDependency drizzle-kit from ^0.18.1 to ^0.31.9.
Translations
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
Adds shop.cart.loading localized strings (en/pl/uk) used by cart/orders loading UI.

Sequence Diagram

sequenceDiagram
    participant User
    participant CartClient as Cart Page Client
    participant OrdersAPI as Orders API (/api/shop/orders)
    participant DB as Database

    User->>CartClient: Open cart page
    CartClient->>CartClient: set isClientReady, isOrdersLoading
    CartClient->>OrdersAPI: GET /api/shop/orders (AbortController, 2500ms)
    OrdersAPI->>OrdersAPI: getCurrentUser() (auth)
    alt unauthenticated
        OrdersAPI-->>CartClient: 401/403 (no-store)
        CartClient->>CartClient: clear ordersSummary, stop loading
    else authenticated
        OrdersAPI->>DB: query orders JOIN orderItems (aggregate label, count)
        DB-->>OrdersAPI: rows
        OrdersAPI-->>CartClient: 200 JSON (orders summary)
        CartClient->>CartClient: update ordersSummary, stop loading
        CartClient->>User: render Orders card (count, link)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ViktorSvertoka
  • AM1007

Poem

🐇 I hopped into the cart to peep and cheer,
Found orders, counts, and links all near,
Per-owner stash and a tiny API song,
A badge that counts and a loader strong—
Hooray, the cart hums happy all year!

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a user orders route/feature to the cart page. It is specific, clear, and directly reflects the primary objective of the PR.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch lso/feat/shop

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

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e694a46ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

) : null}
</div>
) : null;
if (!isClientReady || !hasLoadedOrdersSummary) {

Choose a reason for hiding this comment

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

P1 Badge Decouple cart render from orders-summary fetch

CartPageClient blocks all cart content behind the loader until hasLoadedOrdersSummary becomes true, but that flag is only flipped after the /api/shop/orders request settles (including timeout/abort in the effect above). In slow/error cases, this adds up to a 2.5s delay to viewing cart items and starting checkout for a non-essential summary call, which regresses the primary cart flow; render the cart independently and load the orders card asynchronously.

Useful? React with 👍 / 👎.

try {
const user = await getCurrentUser();
if (!user) {
logWarn('public_orders_list_unauthorized', {

Choose a reason for hiding this comment

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

P2 Badge Avoid warning on expected anonymous orders requests

This route logs public_orders_list_unauthorized as a warning for every unauthenticated request, but the cart page now calls /api/shop/orders on mount and explicitly treats 401/403 as normal public behavior. That means routine anonymous cart traffic will emit warning-level logs, creating alert noise and obscuring real failures; this path should be downgraded (or skipped) for expected anonymous access.

Useful? React with 👍 / 👎.

Copy link
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: 3

🧹 Nitpick comments (5)
frontend/app/api/shop/orders/route.ts (1)

46-59: Consider reducing log level for expected unauthenticated requests.

The cart page is public, so unauthenticated hits to this endpoint are expected and frequent. logWarn at Line 49 may generate significant noise in production logs. A debug or info level (or skipping the log entirely) would be more appropriate for a non-anomalous code path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/api/shop/orders/route.ts` around lines 46 - 59, The review
points out that using logWarn for expected unauthenticated accesses floods
production logs; update the handler in route.ts so when getCurrentUser() returns
null it uses a lower-severity logging call (e.g., logDebug or logInfo) or omits
logging entirely instead of logWarn, keeping the same metadata (baseMeta, code:
'UNAUTHORIZED', durationMs) and still returning the existing noStoreJson
response; locate the block that calls getCurrentUser() and replace the logWarn
invocation accordingly.
frontend/app/[locale]/shop/cart/CartPageClient.tsx (4)

133-183: AbortController not aborted on unmount — fetch continues in background.

The cleanup function sets cancelled = true to prevent state updates, but the AbortController is scoped inside loadOrdersSummary and never aborted on unmount. If the user navigates away quickly, the request stays in flight.

Suggested fix
  useEffect(() => {
    let cancelled = false;
+   const controller = new AbortController();

    async function loadOrdersSummary() {
      setIsOrdersLoading(true);

-     const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 2500);

      try {
        const res = await fetch('/api/shop/orders', {
          method: 'GET',
          headers: { Accept: 'application/json' },
          cache: 'no-store',
          signal: controller.signal,
        });
        // ... rest unchanged
      } finally {
        clearTimeout(timeoutId);
        if (!cancelled) {
          setIsOrdersLoading(false);
          setHasLoadedOrdersSummary(true);
        }
      }
    }

    void loadOrdersSummary();

    return () => {
      cancelled = true;
+     controller.abort();
    };
  }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx around lines 133 - 183,
The fetch's AbortController is created inside loadOrdersSummary and never
aborted on component unmount, allowing the request to continue; refactor so the
controller is accessible in the outer scope (e.g., a ref or variable captured by
the effect) and call controller.abort() in the cleanup returned by the
useEffect, while preserving the cancelled flag and existing finally logic in
loadOrdersSummary; ensure the fetch uses controller.signal and that aborting
doesn't attempt state updates (keep the cancelled checks in
setOrdersSummary/setIsOrdersLoading/setHasLoadedOrdersSummary).

318-327: Spinner inside ordersCard is dead code.

ordersCard is only rendered when ordersSummary is non-null (Line 311). ordersSummary is set in the same async execution frame as setIsOrdersLoading(false) (Lines 165 → 172), so React batches them. By the time ordersCard renders, isOrdersLoading is already false; the <Loader2> branch is unreachable.

Either remove the spinner or, if you want a loading state for the orders card, render the card shell before ordersSummary is available and show the spinner there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx around lines 318 - 327,
The Loader2 branch inside the ordersCard is dead because ordersCard only mounts
when ordersSummary is non-null and state updates are batched; remove the
unreachable spinner branch or change the render logic so the card shell mounts
before ordersSummary is set and shows the spinner while isOrdersLoading is true.
Locate the JSX that renders ordersCard (references: ordersCard, ordersSummary,
isOrdersLoading, setIsOrdersLoading) and either a) simplify the conditional to
always render the badge with ordersSummary.count (removing Loader2), or b) move
the parent conditional so the card renders when isOrdersLoading is true and show
Loader2 until ordersSummary is populated. Ensure you update any aria-live
attributes accordingly.

303-309: Hoist constant class-name strings outside the component.

ORDERS_LINK, ORDERS_COUNT_BADGE, and ORDERS_CARD are static cn(…) calls recomputed on every render. All similar constants in this file (SHOP_PRODUCT_LINK, SHOP_STEPPER_BTN, SHOP_HERO_CTA) are already defined at module scope. Move these to match the existing pattern.

Suggested diff
 const SHOP_HERO_CTA = cn(
   ...
 );
+
+const ORDERS_LINK = cn(SHOP_LINK_BASE, SHOP_LINK_MD, SHOP_FOCUS);
+
+const ORDERS_COUNT_BADGE = cn(
+  'border-border bg-muted/40 text-foreground inline-flex items-center rounded-full border px-2.5 py-1 text-xs font-semibold tabular-nums'
+);
+
+const ORDERS_CARD = cn('border-border rounded-md border p-4');

 // ... inside the component, remove the three local declarations
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx around lines 303 - 309,
ORDERS_LINK, ORDERS_COUNT_BADGE and ORDERS_CARD are being recomputed inside the
CartPageClient component; hoist them to module scope like the existing
SHOP_PRODUCT_LINK, SHOP_STEPPER_BTN and SHOP_HERO_CTA constants so they are
computed once at import time. Locate the cn(...) definitions (symbols:
ORDERS_LINK, ORDERS_COUNT_BADGE, ORDERS_CARD) in CartPageClient.tsx and move
their const declarations to the top of the file alongside the other SHOP_*
constants, leaving the component to reference those module-scoped constants
instead.

150-151: Early return on 401/403 skips setting ordersSummary — confirm this is intentional for the loading gate.

When the user is unauthenticated, the function returns early at Line 151 without calling setOrdersSummary, so ordersSummary stays null and ordersCard is null. The finally block still fires and sets hasLoadedOrdersSummary = true, which correctly unblocks the gate. This path works, but it's fragile: any new early-return added later without a finally companion would leave the page stuck on the loader forever.

Consider restructuring so the gate doesn't depend on this fetch at all (per the major issue above).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx around lines 150 - 151,
The early return on res.status 401/403 skips setting ordersSummary which can
make the UI fragile; change that branch to explicitly setOrdersSummary to an
empty/explicit "no orders" value (e.g. [] or a well-formed empty OrdersSummary)
before returning so ordersCard consumers get a known state, and keep the
existing finally that sets hasLoadedOrdersSummary = true; locate the check
around res.status === 401 || res.status === 403 in the fetch handler and update
it to call setOrdersSummary(...) (not just return) so ordersSummary/ordersCard
are deterministic even for unauthenticated responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx:
- Line 356: Replace the hardcoded "Loading…" string in the CartPageClient
component with a translation key (e.g., t('loading')): locate the JSX that
renders "Loading…" (in CartPageClient) and change it to use the translator
function t('loading'); if t is not in scope, import and call the project's i18n
hook (e.g., useTranslation or useLocaleTranslate) at the top of CartPageClient
and use its t function so the fallback/loading text is localized.
- Around line 346-361: The full-page loading gate that checks isClientReady &&
hasLoadedOrdersSummary should be removed so the cart UI renders immediately;
instead let ordersCard (which already handles ordersSummary === null by
returning null) manage its own loading/skeleton state. Keep any minimal gating
for isClientReady only if needed to avoid hydration flash (handle it separately
from the orders fetch), and ensure hasLoadedOrdersSummary does not block
rendering of cart items, checkout controls, or empty-state UI; move any
orders-summary-specific spinner into the ordersCard area so slow/401 responses
don’t show a full-page spinner.

In `@frontend/app/api/shop/orders/route.ts`:
- Around line 35-101: Add a composite index on orders.userId and
orders.createdAt to speed up the GET query that filters by userId and orders by
createdAt; in the table definition for orders in frontend/db/schema/shop.ts
(where orders and the existing orders_sweep_claim_expires_idx are declared) add
an index definition equivalent to idx_orders_user_id_created_at on (user_id,
created_at DESC) so the db can use it for the WHERE eq(orders.userId, user.id)
and ORDER BY desc(orders.createdAt) used in the GET handler.

---

Nitpick comments:
In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx:
- Around line 133-183: The fetch's AbortController is created inside
loadOrdersSummary and never aborted on component unmount, allowing the request
to continue; refactor so the controller is accessible in the outer scope (e.g.,
a ref or variable captured by the effect) and call controller.abort() in the
cleanup returned by the useEffect, while preserving the cancelled flag and
existing finally logic in loadOrdersSummary; ensure the fetch uses
controller.signal and that aborting doesn't attempt state updates (keep the
cancelled checks in
setOrdersSummary/setIsOrdersLoading/setHasLoadedOrdersSummary).
- Around line 318-327: The Loader2 branch inside the ordersCard is dead because
ordersCard only mounts when ordersSummary is non-null and state updates are
batched; remove the unreachable spinner branch or change the render logic so the
card shell mounts before ordersSummary is set and shows the spinner while
isOrdersLoading is true. Locate the JSX that renders ordersCard (references:
ordersCard, ordersSummary, isOrdersLoading, setIsOrdersLoading) and either a)
simplify the conditional to always render the badge with ordersSummary.count
(removing Loader2), or b) move the parent conditional so the card renders when
isOrdersLoading is true and show Loader2 until ordersSummary is populated.
Ensure you update any aria-live attributes accordingly.
- Around line 303-309: ORDERS_LINK, ORDERS_COUNT_BADGE and ORDERS_CARD are being
recomputed inside the CartPageClient component; hoist them to module scope like
the existing SHOP_PRODUCT_LINK, SHOP_STEPPER_BTN and SHOP_HERO_CTA constants so
they are computed once at import time. Locate the cn(...) definitions (symbols:
ORDERS_LINK, ORDERS_COUNT_BADGE, ORDERS_CARD) in CartPageClient.tsx and move
their const declarations to the top of the file alongside the other SHOP_*
constants, leaving the component to reference those module-scoped constants
instead.
- Around line 150-151: The early return on res.status 401/403 skips setting
ordersSummary which can make the UI fragile; change that branch to explicitly
setOrdersSummary to an empty/explicit "no orders" value (e.g. [] or a
well-formed empty OrdersSummary) before returning so ordersCard consumers get a
known state, and keep the existing finally that sets hasLoadedOrdersSummary =
true; locate the check around res.status === 401 || res.status === 403 in the
fetch handler and update it to call setOrdersSummary(...) (not just return) so
ordersSummary/ordersCard are deterministic even for unauthenticated responses.

In `@frontend/app/api/shop/orders/route.ts`:
- Around line 46-59: The review points out that using logWarn for expected
unauthenticated accesses floods production logs; update the handler in route.ts
so when getCurrentUser() returns null it uses a lower-severity logging call
(e.g., logDebug or logInfo) or omits logging entirely instead of logWarn,
keeping the same metadata (baseMeta, code: 'UNAUTHORIZED', durationMs) and still
returning the existing noStoreJson response; locate the block that calls
getCurrentUser() and replace the logWarn invocation accordingly.

Copy link
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx:
- Around line 157-162: The current 401/403 handling sets ordersSummary to a
truthy object which causes the Orders card to render for unauthenticated users;
update the handler in CartPageClient.tsx (the fetch response branch using
res.status === 401 || res.status === 403) to call setOrdersSummary(null) instead
of setOrdersSummary({ count: 0, latestOrderId: null }) while preserving the
existing cancelled guard, so ordersSummary stays null and the ordersCard will
not render for unauthenticated visitors.

In `@frontend/app/api/shop/orders/route.ts`:
- Around line 61-101: The query in route.ts currently limits results to 50 and
the client reads the badge count from orders.length, so users with >50 orders
see "50"; modify the DB select to include a totalCount (e.g., add sql`count(*)
OVER()` as totalCount to the select projection that builds rows) or run a
separate count query and attach totalCount to the response object returned from
the handler that sets rows, and then update the client (CartPageClient.tsx) to
read data.totalCount (or response.totalCount) instead of orders.length; locate
the select that builds rows (variable rows using select with primaryItemLabel
and itemCount) and add the totalCount field there or add a paired count query in
the same route handler.

Copy link
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.

🧹 Nitpick comments (5)
frontend/app/[locale]/shop/cart/CartPageClient.tsx (2)

368-376: Hardcoded "Loading…" was removed — but the loader still lacks accessible labeling.

The full-page <Loader> has no text or aria-label for screen readers. Consider adding a visually-hidden status announcement so assistive technology users know the page is loading.

Suggested improvement
       <div className="flex flex-col items-center justify-center gap-4">
         <Loader size={160} className="opacity-90" />
+        <span className="sr-only" role="status" aria-live="polite">
+          {t('loading')}
+        </span>
       </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx around lines 368 - 376,
The Loader component inside the early return when !isClientReady lacks
accessible labeling; add an offscreen/live status so screen readers announce
loading (e.g., a visually-hidden text "Loading…" or role="status" with
aria-live="polite") alongside the <Loader /> in CartPageClient.tsx to provide an
accessible loading announcement for assistive tech users.

164-165: Silent discard on non-OK / malformed responses could hide server issues.

Lines 164–165 silently bail on any non-OK status (other than 401/403) or unparseable body. This is fine for the UI, but there's no logging or telemetry, making it harder to diagnose issues like repeated 500s from the orders endpoint.

Consider at minimum a console.warn for unexpected status codes in development, or integrating with whatever client-side error reporting the platform uses.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx around lines 164 - 165,
The fetch handling in CartPageClient.tsx currently bails silently when responses
are non-OK or the body is unparseable; update the logic around the res/data
checks to surface unexpected server issues by emitting a warning or reporting
telemetry (e.g., console.warn in development or calling the existing client-side
error reporter) whenever res.ok is false (excluding 401/403) or when data is
null/typeof !== 'object'; include res.status, res.statusText and the parsed/raw
body (or the parse error) in the log/report to aid debugging and keep the
early-return behavior for the UI intact (refer to the res and data variables in
the current code block).
frontend/app/api/shop/orders/route.ts (3)

94-94: itemCount lacks a SQL type annotation.

sql\count(${orderItems.id})`has no generic type parameter, so Drizzle infers it asunknown. This is safely handled by toCount` on Line 116, but adding the annotation improves IDE support and self-documentation.

Suggested fix
-        itemCount: sql`count(${orderItems.id})`,
+        itemCount: sql<string>`count(${orderItems.id})`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/api/shop/orders/route.ts` at line 94, The selected field
itemCount uses sql`count(${orderItems.id})` without a generic type so Drizzle
infers unknown; add an explicit generic type to the sql template (e.g., number
or bigint as appropriate for your DB) for sql`count(${orderItems.id})` so IDEs
and typings work correctly and remain consistent with the subsequent toCount
usage (see itemCount and the call to toCount).

60-65: Separate count query is fine, but totalCount type from sql<number> may mislead.

PostgreSQL count(*) returns bigint, which Drizzle typically delivers as a string. The sql<number> generic is only a TypeScript hint and doesn't coerce the runtime value. This works correctly here only because toCount handles string | bigint | number — but a future reader might trust the <number> annotation and skip toCount.

Consider annotating as sql<string> to match the actual runtime type, keeping toCount as the safe coercion layer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/api/shop/orders/route.ts` around lines 60 - 65, The count query
annotation is misleading: change the generic on sql from number to string so the
expression using sql in totalCountRows better reflects PostgreSQL returning
bigint as a string (e.g., replace sql<number>`count(*)` with
sql<string>`count(*)`), keep using the toCount(...) call when deriving
totalCount from totalCountRows[0]?.value to perform the safe coercion of
string|bigint|number into a numeric count.

35-134: No success-path log emitted — consider adding one for observability parity.

Unauthorized access and failures are both logged, but a successful orders fetch produces no log entry. For request tracing and latency monitoring, a brief success log would maintain parity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/api/shop/orders/route.ts` around lines 35 - 134, The GET handler
emits logs for unauthorized and error paths but not on success; add a success
log using the same baseMeta and startedAtMs so traces and latency are recorded.
After preparing the response (before returning noStoreJson on success) call
logInfo (e.g., logInfo('public_orders_list_success', { ...baseMeta, code: 'OK',
durationMs: Date.now() - startedAtMs, totalCount })) to include duration and
totalCount (or orders.length) for observability parity with the existing
logError/logInfo calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx:
- Around line 157-162: The 401/403 branch correctly sets ordersSummary to null
to prevent the orders card rendering for unauthenticated users, so no code
change is required; confirm the conditional in CartPageClient.tsx that checks
res.status === 401 || res.status === 403 and the early return after
setOrdersSummary(null) is preserved, and ensure the surrounding variables
referenced (ordersSummary state setter setOrdersSummary and the cancelled flag)
remain intact and tested to avoid regressions.

---

Nitpick comments:
In `@frontend/app/`[locale]/shop/cart/CartPageClient.tsx:
- Around line 368-376: The Loader component inside the early return when
!isClientReady lacks accessible labeling; add an offscreen/live status so screen
readers announce loading (e.g., a visually-hidden text "Loading…" or
role="status" with aria-live="polite") alongside the <Loader /> in
CartPageClient.tsx to provide an accessible loading announcement for assistive
tech users.
- Around line 164-165: The fetch handling in CartPageClient.tsx currently bails
silently when responses are non-OK or the body is unparseable; update the logic
around the res/data checks to surface unexpected server issues by emitting a
warning or reporting telemetry (e.g., console.warn in development or calling the
existing client-side error reporter) whenever res.ok is false (excluding
401/403) or when data is null/typeof !== 'object'; include res.status,
res.statusText and the parsed/raw body (or the parse error) in the log/report to
aid debugging and keep the early-return behavior for the UI intact (refer to the
res and data variables in the current code block).

In `@frontend/app/api/shop/orders/route.ts`:
- Line 94: The selected field itemCount uses sql`count(${orderItems.id})`
without a generic type so Drizzle infers unknown; add an explicit generic type
to the sql template (e.g., number or bigint as appropriate for your DB) for
sql`count(${orderItems.id})` so IDEs and typings work correctly and remain
consistent with the subsequent toCount usage (see itemCount and the call to
toCount).
- Around line 60-65: The count query annotation is misleading: change the
generic on sql from number to string so the expression using sql in
totalCountRows better reflects PostgreSQL returning bigint as a string (e.g.,
replace sql<number>`count(*)` with sql<string>`count(*)`), keep using the
toCount(...) call when deriving totalCount from totalCountRows[0]?.value to
perform the safe coercion of string|bigint|number into a numeric count.
- Around line 35-134: The GET handler emits logs for unauthorized and error
paths but not on success; add a success log using the same baseMeta and
startedAtMs so traces and latency are recorded. After preparing the response
(before returning noStoreJson on success) call logInfo (e.g.,
logInfo('public_orders_list_success', { ...baseMeta, code: 'OK', durationMs:
Date.now() - startedAtMs, totalCount })) to include duration and totalCount (or
orders.length) for observability parity with the existing logError/logInfo
calls.

…obank logging safety invariant, namespace localStorage cart by user and reset on auth change
Copy link
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/components/shop/CartProvider.tsx`:
- Around line 149-157: The didHydrate boolean guard in CartProvider (didHydrate
ref used in the useEffect that calls getStoredCartItems and syncCartWithServer)
creates an implicit dependency on the parent remounting via a key tied to
cartOwnerId; change the guard to track the last-hydrated owner instead so
re-initialization occurs when cartOwnerId changes: replace didHydrate with a ref
like lastHydratedOwnerRef, compare lastHydratedOwnerRef.current to cartOwnerId
at the start of the effect, bail if equal, otherwise set
lastHydratedOwnerRef.current = cartOwnerId and proceed to load stored items and
call syncCartWithServer; update the useEffect deps to include cartOwnerId and
syncCartWithServer.

@ViktorSvertoka ViktorSvertoka merged commit 38bebb2 into develop Feb 17, 2026
11 checks passed
@ViktorSvertoka ViktorSvertoka deleted the lso/feat/shop branch February 17, 2026 07:26
@coderabbitai coderabbitai bot mentioned this pull request Feb 26, 2026
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