Skip to content

feat(web): add canonical private routes#346

Merged
onerandomdevv merged 2 commits into
devfrom
feat/web-canonical-private-routes
Jun 1, 2026
Merged

feat(web): add canonical private routes#346
onerandomdevv merged 2 commits into
devfrom
feat/web-canonical-private-routes

Conversation

@onerandomdevv
Copy link
Copy Markdown
Collaborator

@onerandomdevv onerandomdevv commented Jun 1, 2026

What does this PR do?

Adds the canonical pre-launch private route foundation for Twizrr web. Private shopper routes move away from /buyer/* into short app routes such as /home, /cart, /orders, /wishlist, /saved, /messages, and /settings/*; /u/:username is added for public user profiles; route helpers/policy and middleware are updated so protected routes redirect through login while removed legacy physical routes now fall through to the normal 404 policy.

Type of change

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

Area affected

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

How to test this

  1. From apps/web, run pnpm.cmd run lint, npx.cmd tsc --noEmit, and pnpm.cmd run build.
  2. Verify the build route table includes /home, /cart, /orders, /orders/[id], /orders/confirmed/[orderId], /wishlist, /saved, /messages, /notifications, /settings/profile, /settings/account, /settings/store/profile, /settings/store/account, and /u/[username].
  3. Verify old physical route pages such as /buyer/profile, /account/profile, /store/my-store, and /store/settings are no longer present as app routes and should fall through to the normal 404 behavior.

Expected result: canonical routes compile/build successfully, protected routes are governed by the new route policy/middleware, and old pre-launch route styles are not kept as compatibility redirects.

Pre-commit checklist

  • Backend lint/type/build pass when backend is affected
  • Web lint/type/build pass when web 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 non-MVP legacy features reintroduced
  • All money values are BigInt kobo, never float
  • Paystack webhook changes verify HMAC before processing
  • Database migrations are Prisma migrations, not db push
  • Database migrations are backward-compatible or risk is documented

Screenshots

N/A. This PR is route foundation/navigation wiring only; visible UI polish and screenshots are intentionally left for later UX cleanup PRs.

Notes for reviewer

  • No backend, env, Prisma, or event/audit files are included.
  • Route helpers were added in apps/web/src/lib/route-policy.ts and canonical path helpers in apps/web/src/lib/routes.ts.
  • Middleware now uses the route policy for protected route handling and auth-page redirects.
  • Navigation/link targets were updated to canonical private routes.
  • /u/:username is public and owner-aware at a lightweight route-foundation level.
  • Old /buyer/* physical route pages were removed instead of keeping redirects because Twizrr has not launched yet.
  • apps/web/AGENTS.md was updated to remove stale /@chiastyles and /@[handle] examples in favor of /stores/chiastyles and /stores/[handle].
  • Dev-server smoke was attempted in the isolated worktree, but local PowerShell job startup timed out; build output verified the route table instead.
  • The commit hook required generating local Prisma/shared artifacts in the isolated worktree so the repo-level backend typecheck could run; those generated artifacts were not committed.

Validation completed:

  • pnpm.cmd run lint: passed
  • npx.cmd tsc --noEmit: passed
  • pnpm.cmd run build: passed
  • pnpm.cmd --filter @twizrr/shared run build: passed locally for the pre-commit hook prerequisite
  • npx.cmd prisma generate --schema=prisma/schema.prisma: passed locally for the pre-commit hook prerequisite
  • pnpm.cmd --filter @twizrr/backend exec tsc --noEmit: passed via pre-commit hook
  • git diff --cached --check: passed before commit

Summary by CodeRabbit

  • New Features

    • Added Account and Profile settings pages
    • Added Messages section (chats coming soon)
    • Added Notifications center
    • Added public user profile pages
  • Improvements

    • Simplified authenticated navigation routes for easier access to cart, orders, wishlist, and saved items
    • Reorganized settings experience with dedicated account and profile management pages
    • Consolidated store and shopper settings into dedicated workspaces

@codesandbox
Copy link
Copy Markdown

codesandbox Bot commented Jun 1, 2026

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Warning

Review limit reached

@onerandomdevv, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 19 minutes and 42 seconds. Learn how PR review limits work.

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

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 09b75e37-92e9-4cb5-88d4-9f5e50b95e6a

📥 Commits

Reviewing files that changed from the base of the PR and between 2fe55b8 and e79b7a2.

📒 Files selected for processing (13)
  • apps/web/src/app/(app)/cart/page.tsx
  • apps/web/src/app/(app)/checkout/[productId]/page.tsx
  • apps/web/src/app/(app)/home/page.tsx
  • apps/web/src/app/(app)/messages/page.tsx
  • apps/web/src/app/(app)/notifications/page.tsx
  • apps/web/src/app/(app)/orders/[id]/page.tsx
  • apps/web/src/app/(public)/u/[username]/page.tsx
  • apps/web/src/components/feed/FeedCard.tsx
  • apps/web/src/components/feed/QuickBuySheet.tsx
  • apps/web/src/components/layout/ProfileMenuDrawer.tsx
  • apps/web/src/components/layout/navigation.ts
  • apps/web/src/hooks/useOwnerStore.ts
  • apps/web/src/lib/routes.ts
📝 Walkthrough

Walkthrough

This PR reorganizes the web app's shopper routing from /buyer/* patterns to top-level authenticated routes (/home, /cart, /orders, etc.), adds new settings routes, introduces hooks for loading user/store data, updates navigation infrastructure to derive menu items dynamically, and refactors middleware to use centralized route-policy utilities.

Changes

Shopper Route Restructuring

Layer / File(s) Summary
Documentation and configuration updates
apps/web/AGENTS.md, apps/web/WEB_DESIGN.md, apps/web/next.config.mjs
Updated specification docs to reflect new route structure (/home, /cart, /orders, /wishlist, /saved, /messages instead of /buyer/*; /stores/[handle] instead of @[handle]); removed rewrites() and redirects() from Next.js config; updated middleware redirect targets to /home.
Route policy and URL helpers
apps/web/src/lib/route-policy.ts, apps/web/src/lib/routes.ts
New route-policy.ts module defines protected/auth/workspace route detection and redirect URL builders; updated routes.ts with normalized handle validation and canonical /stores/{handle} / /u/{username} href generation with fallbacks.
New authenticated page routes
apps/web/src/app/(app)/
Created top-level shopper pages: home, cart, checkout/[productId], orders/[id], orders/confirmed/[orderId], saved, wishlist, messages, notifications; added shared (app) layout wrapping pages in ShopperShell.
Settings page routes
apps/web/src/app/(settings)/settings/
Created new settings routes: /account, /profile, /store/account, /store/profile with metadata and shell wrappers; added index redirects from /settings/account and /settings/store/account.
Public user profile page
apps/web/src/app/(public)/u/[username]/page.tsx
New public-facing async server component that fetches user profile via API, formats display/stats, and renders error/not-found states; no caching on fetch.
Profile and store data hooks
apps/web/src/hooks/useOwnProfile.ts, apps/web/src/hooks/useOwnerStore.ts
New client-side hooks: useOwnProfile loads current user profile on mount; useOwnerStore fetches store with 8s timeout and caches result for sync initial state; both prevent state updates after unmount.
Navigation item generators and AppShell props
apps/web/src/components/layout/navigation.ts, apps/web/src/components/layout/AppShell.tsx
Replaced static nav item exports with getStoreNavItems() and getShoppingNavItems() generators; expanded AppShellProps to include displayName, profilePhotoUrl, storeHandle (all nullable); fixed root styling instead of mode-dependent colors.
Layout component updates for new navigation
apps/web/src/components/layout/LeftNav.tsx, apps/web/src/components/layout/BottomNav.tsx
Updated LeftNav/BottomNav to call nav generators with store/profile inputs; LeftNav now handles hasStoreProfile: null (skeleton), true (manage), false (create); both set aria-label and use isActiveItem for active styling.
Drawer component route updates
apps/web/src/components/layout/ProfileMenuDrawer.tsx, apps/web/src/components/layout/StoreMenuDrawer.tsx
Updated shopping drawer to link to /orders, /cart, /wishlist, /saved; updated account drawer to link to /settings/* paths; store drawer derives store profile link from getPublicStoreHref(); both drawers now use conditional links for store creation/management.
ShopperShell and cart/checkout components
apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx, apps/web/src/app/(shopper)/buyer/cart/CartCheckoutSheet.tsx, apps/web/src/app/(shopper)/buyer/cart/CartClient.tsx, apps/web/src/app/(shopper)/buyer/checkout/[productId]/CheckoutClient.tsx
ShopperShell now fetches profile/store data via hooks and detects /checkout and /orders/confirmed as standalone routes; cart/checkout components updated to use /orders/confirmed/{orderId} and getPublicStoreHref() for store links.
Order detail and confirmation components
apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx, apps/web/src/app/(shopper)/buyer/orders/confirmed/[orderId]/OrderConfirmedClient.tsx, apps/web/src/components/order/OrderCard.tsx
Updated back/tracking links from /buyer/orders to /orders; store links now use getPublicStoreHref(); order card routes to new /orders/{id} path.
Feed card and product detail updates
apps/web/src/components/feed/FeedCard.tsx, apps/web/src/components/feed/QuickBuySheet.tsx, apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx
Updated feed cards to remove @ prefix from handles, use getPublicStoreHref(), and adjust avatar border radius; QuickBuySheet and ProductDetailClient use /checkout/{productId} instead of /buyer/checkout/{productId} and round store icons to rounded-xl.
Middleware route-policy integration
apps/web/src/middleware.ts
Refactored to use isProtectedRoute(), isAuthPageRoute(), getLoginRedirectPath() from route-policy; removed hardcoded /buyer/* prefix matching and /@handle rewrite; changed authenticated-user auth-page redirect from /explore to /home.
Removed legacy pages
apps/web/src/app/(shopper)/buyer/measurements/page.tsx, apps/web/src/app/(shopper)/buyer/profile/page.tsx, apps/web/src/app/(shopper)/buyer/settings/page.tsx, apps/web/src/app/(store)/store/my-store/MyStoreClient.tsx, apps/web/src/app/(store)/store/my-store/page.tsx, apps/web/src/app/(store)/store/settings/page.tsx
Removed old route entries for measurements, profile, settings under /buyer/* and store settings under /store/; MyStoreClient component that provided copy-link and store preview is deleted.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • coded-devs/twizrr#293: Adds ShopperShell layout wrapper; main PR extends it with new route detection and profile/store data fetching.
  • coded-devs/twizrr#326: Introduces /buyer/measurements, /buyer/profile, /buyer/settings pages that the main PR removes and relocates to new settings routes.
  • coded-devs/twizrr#294: Adds new (auth) route group pages (/login, /register, etc.); main PR's middleware updates integrate with the new auth-page detection logic.

Poem

A rabbit hops through routes with glee,
From /buyer/* to home and spree,
New paths spring forth: /cart, /orders, clean,
Settings bloom where none have been,
The maze of pages restructured fine,
Now navigation's by design! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.51% 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 clearly and concisely describes the main change: adding canonical private routes for the web app, moving away from /buyer/* to shorter routes like /home, /cart, /orders, etc.
Description check ✅ Passed The PR description follows the template with all major sections completed: What does this PR do, Type of change, Area affected, How to test, Pre-commit checklist, Screenshots, and Notes for reviewer. All required information is present and detailed.
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-canonical-private-routes

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: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/src/middleware.ts (1)

19-43: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve redirects for legacy @handle store URLs.

This middleware no longer carries the old /@handle/stores/:handle normalization, so existing public store links now fall through to 404. The PR explicitly drops old private /buyer/* paths, but breaking previously shareable public profile URLs is a much rougher migration. Adding a small redirect here would keep old links working while still making /stores/[handle] canonical.

🤖 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/middleware.ts` around lines 19 - 43, Add a small redirect for
legacy public profile URLs by detecting paths of the form "/@handle" early in
middleware (in function middleware) before the protected/auth checks; extract
the handle from request.nextUrl.pathname (e.g., strip the leading "@"),
construct a new URL with pathname `/stores/:handle` while preserving
request.nextUrl.search/query, and return NextResponse.redirect(newUrl). Keep
existing helpers (hasSession, isProtectedRoute, isAuthPageRoute,
getLoginRedirectPath) and do not reintroduce dropped private "/buyer/*"
behavior.
apps/web/AGENTS.md (1)

58-95: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the new public/profile and settings routes here.

This tree still omits the new /u/[username], /notifications, and /(settings)/settings/* paths, so the canonical App Router map is already stale inside the same PR. Based on learnings: The App Router structure uses (public), (auth), (app), and (store) route groups with distinct layouts — do not flatten this hierarchy.

🤖 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/AGENTS.md` around lines 58 - 95, Update the App Router tree to
include the missing routes without flattening route-group hierarchy: add the
public profile route entry /u/[username] under the appropriate group (likely
(public) or (app) depending on auth requirements), add /notifications under the
(app) authenticated pages, and add the settings group as /(settings)/settings/*
(with its own layout entry) rather than inlining settings pages; reference the
route keys shown in the diff (e.g., page.tsx files for p/[code],
stores/[handle], home/page.tsx) when inserting the new entries so the tree
matches the actual filesystem layout and preserves (public), (auth), (app), and
(store) groups.
🧹 Nitpick comments (3)
apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx (1)

392-402: ⚡ Quick win

Use the image radius token for the store logo.

These containers now use rounded-xl (12px), which is the card radius. The repo token for images is 20px, so this drifts from the web design system.

As per coding guidelines "Use border-radius tokens: 6px for small elements, 12px for cards, 16px for modals, 20px for images".

🤖 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/`(public)/p/[code]/ProductDetailClient.tsx around lines 392
- 402, In ProductDetailClient (the store logo container) replace the card radius
class "rounded-xl" with the image-radius token (20px) on both the Image wrapper
and the fallback container so the logo uses the image border-radius;
specifically update the divs around Image and the fallback div that currently
use "rounded-xl" to use the image token (e.g. "rounded-[20px]" or your project's
image radius utility) so both branches match the design system.
apps/web/src/components/layout/AppShell.tsx (1)

52-67: ⚡ Quick win

Move these new shell dimensions behind design tokens.

This hunk adds several arbitrary pixel values (72px, 240px, 924px, 960px, 600px) directly in TSX. Please swap them to CSS-variable-backed tokens so the shell stays aligned with the shared layout system.

As per coding guidelines, apps/web/src/**/*.{tsx,css}: Use CSS variables (design tokens) only for colors, spacing, and typography — never hardcode hex values or fixed pixels.

🤖 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/components/layout/AppShell.tsx` around lines 52 - 67, The JSX in
AppShell.tsx hardcodes layout pixel values in className strings (e.g., "pt-14",
"md:ml-[72px]", "lg:ml-[240px]", "xl:max-w-[924px]", "max-w-[960px]",
"max-w-[600px]") — replace those literal pixel sizes with CSS-variable-backed
design tokens and reference them in the Tailwind/className expressions (keep the
conditional branches using showRightPanel, isStoreMode, centerMaxWidth intact);
add or use existing CSS vars like --shell-pt, --shell-md-ml, --shell-lg-ml,
--shell-xl-maxw, --shell-default-maxw and update the stylesheet to define those
tokens so the component uses var(--shell-...) instead of hardcoded px values.
apps/web/src/app/(public)/u/[username]/page.tsx (1)

23-24: ⚡ Quick win

Route this through the shared API client instead of raw fetch.

This page reimplements base URL handling and response-envelope parsing locally, which can drift from the rest of the app’s API behavior. Prefer lib/api.ts here so the page inherits the shared baseURL and { success, data } unwrapping contract. As per coding guidelines, "Use axios API client instance from lib/api.ts with baseURL and response interceptor to unwrap { success, data } envelope".

Also applies to: 148-177

🤖 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/`(public)/u/[username]/page.tsx around lines 23 - 24,
Replace the ad-hoc API_BASE and raw fetch calls in this page component with the
shared axios API client from lib/api.ts: import the client (e.g., `api`) and
call `await api.get(...)`/`api.post(...)` for the same endpoints used in this
file (remove the manual API_BASE variable and any URL concatenation), then read
the unwrapped payload from `response.data.data` (the shared `{ success, data }`
envelope) instead of manually parsing responses; apply this change to every
fetch in this file (including the blocks around lines 148-177) so the page uses
the centralized baseURL and response interceptor behavior.
🤖 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/AGENTS.md`:
- Around line 587-600: Replace the hardcoded protected-route checks in the
middleware (the explicit pathname.startsWith('/home') || ... block) with the
shared route policy exported from route-policy.ts: import and call the exported
helper (e.g., isProtectedRoute or protectedPrefixes) to determine if a pathname
is protected, and use that result in the session check that performs
NextResponse.redirect; also update the example to reference the centralized
publicRoutes/authRoutes constants (or use exported helpers) instead of
duplicating lists so the middleware covers /checkout, /wishlist, /saved,
/messages, /notifications, /settings/* and stays in sync.

In `@apps/web/src/app/`(app)/cart/page.tsx:
- Line 5: The page metadata title in page.tsx uses a hyphen ("Your cart -
twizrr") which is inconsistent with the home page's pipe separator; update the
metadata title value to use the pipe separator ("Your cart | twizrr") so all
page titles follow the same format by editing the title entry in the page's
metadata definition (the "title" field in apps/web/src/app/(app)/cart/page.tsx).

In `@apps/web/src/app/`(app)/checkout/[productId]/page.tsx:
- Line 5: Update the page metadata title string to use the same separator as the
rest of the site—replace the hyphen in the current title literal title:
"Checkout - twizrr" with a pipe so it matches the pattern used elsewhere (e.g.,
"Checkout | twizrr"); locate and modify the title: "Checkout - twizrr" entry in
the checkout page metadata to use title: "Checkout | twizrr".

In `@apps/web/src/app/`(app)/home/page.tsx:
- Around line 14-15: The h1 heading uses the restricted Syne font class; replace
the class "font-syne" with "font-cabinet" on the <h1 className="font-syne
text-xl font-bold text-[var(--color-espresso)]"> element so the Home page
follows the guideline (use font-cabinet for UI/body text and reserve font-syne
for landing/404/500 hero headlines).

In `@apps/web/src/app/`(app)/messages/page.tsx:
- Line 5: The page metadata title uses an inconsistent separator ("Chats -
twizrr"); update the metadata title in apps/web/src/app/(app)/messages/page.tsx
(the title string in the exported metadata object or variable) to use the pipe
separator to match other pages (e.g., "Chats | twizrr"), ensuring consistency
across the site.

In `@apps/web/src/app/`(app)/notifications/page.tsx:
- Line 5: The metadata title in page.tsx uses a hyphen separator ("title:
\"Notifications - twizrr\"") which is inconsistent with other pages that use a
pipe; update the title metadata value to use the pipe separator (e.g., change
the string to "Notifications | twizrr") so the metadata key title in this file
matches the site's standard punctuation for page titles.

In `@apps/web/src/app/`(app)/orders/[id]/page.tsx:
- Line 5: The page metadata title uses a hyphen separator ("Order details -
twizrr"); update the exported metadata title (the title property in page.tsx) to
use the pipe separator to match the site's convention (e.g., "Order details |
twizrr"), ensuring consistency with other pages like the home page that use the
pipe.

In `@apps/web/src/app/`(public)/u/[username]/page.tsx:
- Around line 56-57: Change the heading elements that currently use the Syne
font to Cabinet: replace the className token "font-syne" with "font-cabinet" on
the h1 at the "We could not load this profile" JSX element and the other heading
JSX block around lines referenced (the h1/h2 group at the 91-93 region); ensure
you only change the font class (leave other classes like "text-2xl",
"font-bold", and color variables intact) so the headings follow the UI/body font
guideline.
- Around line 28-47: The branch that handles missing user profiles currently
returns JSX (profile.status === "not-found") causing a soft-404; change it to
call Next's notFound() to return a real 404 (replace the JSX return in the
profile.status === "not-found" branch with notFound()). Also ensure typography
rules: use font-syne only for landing page and 404/500 hero headlines—keep the
404 title (where "Profile not found" is rendered) in font-syne but revert the
profile display name and other generic text to font-cabinet (adjust className
usage around the title and display name). Finally, stop using
process.env.NEXT_PUBLIC_API_URL + manual fetch in this page and switch to the
shared axios client exported from lib/api.ts (replace raw fetch calls and
parsing with the client functions or axios instance used elsewhere in the repo,
e.g., import and use the client from apps/web/src/lib/api.ts in the data-loading
logic such as the function that currently builds requests around lines 148-186).

In `@apps/web/src/components/feed/FeedCard.tsx`:
- Around line 327-360: The card currently wraps the entire content (variable
content) in a Link when store.handle exists which results in an <a> containing a
native <button> from the Button component; update FeedCard.tsx so
getPublicStoreHref(store.handle) Link only wraps the non-interactive part (the
article content excluding the Follow Button) and render the Button (from
Button.tsx) as a sibling outside that Link so you no longer nest interactive
elements; ensure layout and spacing remain identical (keep the same wrapper
classes on the article and the Button rendered after it) and preserve the
disabled state and getPublicStoreHref/store.handle checks.

In `@apps/web/src/components/feed/QuickBuySheet.tsx`:
- Around line 23-25: handleProceedToCheckout closes the sheet then navigates to
checkout using only product.id which drops the required product code and
triggers CheckoutClient's "missing product reference" bail-out; update the
navigation in handleProceedToCheckout to include the product code (e.g.,
router.push(`/checkout/${product.id}?code=${product.code}`) or the appropriate
productCode property) so the checkout route receives productCode, ensuring
CheckoutClient can proceed, while keeping the onOpenChange(false) call intact.

In `@apps/web/src/components/layout/ProfileMenuDrawer.tsx`:
- Around line 157-174: Two DrawerItem entries point to the same href
"/settings/profile" (the items with labels "Profile" and "Public profile
settings"); update the "Public profile settings" DrawerItem (the one using icon
User and onClose={close}) so it no longer duplicates the "Profile" link — either
remove that DrawerItem or change its href to the correct public profile route
(e.g., the current user's public route such as `/u/${username}` or a dedicated
settings page like `/settings/public-profile`) so each drawer entry targets a
unique destination.

In `@apps/web/src/hooks/useOwnerStore.ts`:
- Around line 21-35: The helper fetchOwnerStoreWithTimeout currently resolves
the timeout branch to null which callers cache as "none"; instead make the
timeout reject (e.g., throw a distinct Timeout Error) so the race will throw
rather than return null and callers won't memoize a false-negative. Locate
fetchOwnerStoreWithTimeout and change the timeout promise from resolve(null) to
reject(new Error('OwnerStoreTimeout') or similar), keep clearing timeoutId in
the finally block, and ensure callers handle the thrown timeout error (or let it
bubble) rather than treating null as "no store".
- Around line 41-66: The refresh function and the load() inside the useEffect
call fetchOwnerStoreWithTimeout() without error handling, so a rejection leaves
cachedState as "loading" and UI stuck; wrap each await
fetchOwnerStoreWithTimeout() in a try/catch (both in refresh and in the load()
closure), and on catch set cachedStore = null, cachedState = "none" (and call
setStore(null) and setState(cachedState)); preserve the existing active check in
load() before applying state and ensure the catch branches also respect active
before calling setStore/setState so rejected fetches never leave the hook in a
perpetual "loading" state.

In `@apps/web/src/lib/routes.ts`:
- Around line 15-17: getPublicUserHref currently returns "/settings/profile"
when normalizePublicHandle(username) yields no value, which risks redirecting
public requests to a protected/private route; change getPublicUserHref to return
a safe public fallback (e.g., null or a public route like "/") instead of
"/settings/profile" so anonymous users aren't bounced to login and authenticated
users don't land on a private page—locate getPublicUserHref and its call to
normalizePublicHandle(username) and replace the protected fallback with null or
another explicit public path.

---

Outside diff comments:
In `@apps/web/AGENTS.md`:
- Around line 58-95: Update the App Router tree to include the missing routes
without flattening route-group hierarchy: add the public profile route entry
/u/[username] under the appropriate group (likely (public) or (app) depending on
auth requirements), add /notifications under the (app) authenticated pages, and
add the settings group as /(settings)/settings/* (with its own layout entry)
rather than inlining settings pages; reference the route keys shown in the diff
(e.g., page.tsx files for p/[code], stores/[handle], home/page.tsx) when
inserting the new entries so the tree matches the actual filesystem layout and
preserves (public), (auth), (app), and (store) groups.

In `@apps/web/src/middleware.ts`:
- Around line 19-43: Add a small redirect for legacy public profile URLs by
detecting paths of the form "/@handle" early in middleware (in function
middleware) before the protected/auth checks; extract the handle from
request.nextUrl.pathname (e.g., strip the leading "@"), construct a new URL with
pathname `/stores/:handle` while preserving request.nextUrl.search/query, and
return NextResponse.redirect(newUrl). Keep existing helpers (hasSession,
isProtectedRoute, isAuthPageRoute, getLoginRedirectPath) and do not reintroduce
dropped private "/buyer/*" behavior.

---

Nitpick comments:
In `@apps/web/src/app/`(public)/p/[code]/ProductDetailClient.tsx:
- Around line 392-402: In ProductDetailClient (the store logo container) replace
the card radius class "rounded-xl" with the image-radius token (20px) on both
the Image wrapper and the fallback container so the logo uses the image
border-radius; specifically update the divs around Image and the fallback div
that currently use "rounded-xl" to use the image token (e.g. "rounded-[20px]" or
your project's image radius utility) so both branches match the design system.

In `@apps/web/src/app/`(public)/u/[username]/page.tsx:
- Around line 23-24: Replace the ad-hoc API_BASE and raw fetch calls in this
page component with the shared axios API client from lib/api.ts: import the
client (e.g., `api`) and call `await api.get(...)`/`api.post(...)` for the same
endpoints used in this file (remove the manual API_BASE variable and any URL
concatenation), then read the unwrapped payload from `response.data.data` (the
shared `{ success, data }` envelope) instead of manually parsing responses;
apply this change to every fetch in this file (including the blocks around lines
148-177) so the page uses the centralized baseURL and response interceptor
behavior.

In `@apps/web/src/components/layout/AppShell.tsx`:
- Around line 52-67: The JSX in AppShell.tsx hardcodes layout pixel values in
className strings (e.g., "pt-14", "md:ml-[72px]", "lg:ml-[240px]",
"xl:max-w-[924px]", "max-w-[960px]", "max-w-[600px]") — replace those literal
pixel sizes with CSS-variable-backed design tokens and reference them in the
Tailwind/className expressions (keep the conditional branches using
showRightPanel, isStoreMode, centerMaxWidth intact); add or use existing CSS
vars like --shell-pt, --shell-md-ml, --shell-lg-ml, --shell-xl-maxw,
--shell-default-maxw and update the stylesheet to define those tokens so the
component uses var(--shell-...) instead of hardcoded px values.
🪄 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: 752530e4-07ad-4c86-b255-b8bed432268c

📥 Commits

Reviewing files that changed from the base of the PR and between f06a772 and 2fe55b8.

📒 Files selected for processing (50)
  • apps/web/AGENTS.md
  • apps/web/WEB_DESIGN.md
  • apps/web/next.config.mjs
  • apps/web/src/app/(app)/cart/page.tsx
  • apps/web/src/app/(app)/checkout/[productId]/page.tsx
  • apps/web/src/app/(app)/home/page.tsx
  • apps/web/src/app/(app)/layout.tsx
  • apps/web/src/app/(app)/messages/page.tsx
  • apps/web/src/app/(app)/notifications/page.tsx
  • apps/web/src/app/(app)/orders/[id]/page.tsx
  • apps/web/src/app/(app)/orders/confirmed/[orderId]/page.tsx
  • apps/web/src/app/(app)/orders/page.tsx
  • apps/web/src/app/(app)/saved/page.tsx
  • apps/web/src/app/(app)/wishlist/page.tsx
  • apps/web/src/app/(public)/p/[code]/ProductDetailClient.tsx
  • apps/web/src/app/(public)/u/[username]/page.tsx
  • apps/web/src/app/(settings)/settings/account/page.tsx
  • apps/web/src/app/(settings)/settings/page.tsx
  • apps/web/src/app/(settings)/settings/profile/page.tsx
  • apps/web/src/app/(settings)/settings/store/account/page.tsx
  • apps/web/src/app/(settings)/settings/store/page.tsx
  • apps/web/src/app/(settings)/settings/store/profile/page.tsx
  • apps/web/src/app/(shopper)/buyer/_components/ShopperShell.tsx
  • apps/web/src/app/(shopper)/buyer/cart/CartCheckoutSheet.tsx
  • apps/web/src/app/(shopper)/buyer/cart/CartClient.tsx
  • apps/web/src/app/(shopper)/buyer/checkout/[productId]/CheckoutClient.tsx
  • apps/web/src/app/(shopper)/buyer/measurements/page.tsx
  • apps/web/src/app/(shopper)/buyer/orders/[id]/OrderDetailClient.tsx
  • apps/web/src/app/(shopper)/buyer/orders/confirmed/[orderId]/OrderConfirmedClient.tsx
  • apps/web/src/app/(shopper)/buyer/profile/page.tsx
  • apps/web/src/app/(shopper)/buyer/settings/page.tsx
  • apps/web/src/app/(store)/store/my-store/MyStoreClient.tsx
  • apps/web/src/app/(store)/store/my-store/page.tsx
  • apps/web/src/app/(store)/store/settings/page.tsx
  • apps/web/src/components/feed/FeedCard.tsx
  • apps/web/src/components/feed/QuickBuySheet.tsx
  • apps/web/src/components/layout/AppShell.tsx
  • apps/web/src/components/layout/BottomNav.tsx
  • apps/web/src/components/layout/LeftNav.tsx
  • apps/web/src/components/layout/ProfileMenuDrawer.tsx
  • apps/web/src/components/layout/StoreMenuDrawer.tsx
  • apps/web/src/components/layout/navigation.ts
  • apps/web/src/components/order/OrderCard.tsx
  • apps/web/src/components/settings/AccountSettingsContent.tsx
  • apps/web/src/hooks/useOwnProfile.ts
  • apps/web/src/hooks/useOwnerStore.ts
  • apps/web/src/lib/route-policy.ts
  • apps/web/src/lib/routes.ts
  • apps/web/src/lib/wishlist.ts
  • apps/web/src/middleware.ts
💤 Files with no reviewable changes (7)
  • apps/web/src/app/(shopper)/buyer/measurements/page.tsx
  • apps/web/src/app/(shopper)/buyer/profile/page.tsx
  • apps/web/src/app/(store)/store/settings/page.tsx
  • apps/web/src/app/(store)/store/my-store/MyStoreClient.tsx
  • apps/web/src/app/(store)/store/my-store/page.tsx
  • apps/web/src/app/(shopper)/buyer/settings/page.tsx
  • apps/web/next.config.mjs

Comment thread apps/web/AGENTS.md
Comment on lines +587 to 600
const publicRoutes = ['/', '/explore', '/stores', '/u/', '/p/', '/c/', '/about', '/terms', '/privacy', '/help']
const authRoutes = ['/login', '/register', '/forgot-password', '/verify-email']

// Public route — allow through:
if (publicRoutes.some(r => pathname.startsWith(r))) return NextResponse.next()

// Auth page + already logged in — redirect to feed:
// Auth page + already logged in — redirect to home:
if (authRoutes.some(r => pathname.startsWith(r)) && session) {
return NextResponse.redirect(new URL('/buyer/feed', request.url))
return NextResponse.redirect(new URL('/home', request.url))
}

// Protected route + no session — redirect to login:
if (!session && (pathname.startsWith('/buyer/') || pathname.startsWith('/store/'))) {
if (!session && (pathname.startsWith('/home') || pathname.startsWith('/cart') || pathname.startsWith('/orders') || pathname.startsWith('/store/'))) {
return NextResponse.redirect(new URL(`/login?redirect=${pathname}`, request.url))
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

Update the middleware example to match the shared route policy.

The protected-route snippet still only covers /home, /cart, /orders, and /store/, so anyone copying it will miss /checkout, /wishlist, /saved, /messages, /notifications, and /settings/*. At this point it would be safer to reference apps/web/src/lib/route-policy.ts directly instead of duplicating prefix lists here.

🤖 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/AGENTS.md` around lines 587 - 600, Replace the hardcoded
protected-route checks in the middleware (the explicit
pathname.startsWith('/home') || ... block) with the shared route policy exported
from route-policy.ts: import and call the exported helper (e.g.,
isProtectedRoute or protectedPrefixes) to determine if a pathname is protected,
and use that result in the session check that performs NextResponse.redirect;
also update the example to reference the centralized publicRoutes/authRoutes
constants (or use exported helpers) instead of duplicating lists so the
middleware covers /checkout, /wishlist, /saved, /messages, /notifications,
/settings/* and stays in sync.

Comment thread apps/web/src/app/(app)/cart/page.tsx Outdated
Comment thread apps/web/src/app/(app)/checkout/[productId]/page.tsx Outdated
Comment thread apps/web/src/app/(app)/home/page.tsx Outdated
Comment thread apps/web/src/app/(app)/messages/page.tsx Outdated
Comment thread apps/web/src/components/feed/QuickBuySheet.tsx Outdated
Comment thread apps/web/src/components/layout/ProfileMenuDrawer.tsx Outdated
Comment thread apps/web/src/hooks/useOwnerStore.ts
Comment thread apps/web/src/hooks/useOwnerStore.ts Outdated
Comment thread apps/web/src/lib/routes.ts
@onerandomdevv onerandomdevv merged commit ea53b62 into dev Jun 1, 2026
8 checks passed
@onerandomdevv onerandomdevv deleted the feat/web-canonical-private-routes branch June 1, 2026 23:52
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.

1 participant