feat: Next.js 16 frontend — full customer shop and admin panel - #11
Merged
Conversation
…PI layer - Initialised shadcn/ui (Tailwind v4 CSS-variable mode); installed button, input, label, card, badge, form, select, textarea, dialog, sheet, dropdown-menu, table, skeleton, sonner, navigation-menu, separator, avatar, tabs, and pagination - Added TanStack Query v5, react-hook-form, Zod, Stripe JS, lucide-react, server-only - Updated next.config.ts: reactCompiler, remotePatterns (GCS + localhost), turbopack root to silence workspace-root warning - Created TypeScript types: auth, product (with images[]), order, payment - Created Zod validation schemas: auth (login/register), product, address - Created lib/api/server.ts — server-only fetcher using await cookies() (Next.js 16) - Created lib/api/client.ts — browser-side wrapper with 401 auto-refresh - Created lib/query-keys.ts — centralised TanStack Query key factory - Created lib/providers/query-provider.tsx — QueryClientProvider with devtools - Updated app/layout.tsx — wrapped with QueryProvider + Toaster (sonner) - Added app/not-found.tsx — friendly 404 page using buttonVariants - Updated .gitignore to exclude GCP service-account JSON files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js 16 requires this devDependency when reactCompiler is enabled in next.config.ts — without it the dev server fails to start. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Route Handlers (BFF cookie relay): - app/api/auth/login — POST: calls backend, sets HttpOnly access_token (15m) + refresh_token (7d) both with Path=/ so proxy.ts can read the refresh cookie - app/api/auth/register — POST: same cookie pattern as login - app/api/auth/logout — POST: best-effort backend logout, clears both cookies - app/api/auth/refresh — POST: exchanges refresh_token, issues new pair - app/api/auth/me — GET: proxies /users/me with the httpOnly access_token proxy.ts (Next.js 16 — renamed from middleware.ts): - Redirects logged-in users away from /login and /register - Guards /cart, /checkout, /orders, /profile, /admin with access_token check - Falls back to token refresh via /api/auth/refresh when only refresh_token exists - Decodes JWT payload to enforce admin-only access to /admin routes Auth pages — luxury editorial dark design: - app/(auth)/layout.tsx: split panel, Cormorant Garamond, warm-gold accent, subtle grid texture, radial glow, staggered reveal animations - login/page.tsx + login-form.tsx: react-hook-form + loginSchema, inline error state, gold focus ring via CSS variable override, callbackUrl redirect - register/page.tsx + register-form.tsx: full_name, email, phone (optional), password, confirm_password with cross-field Zod refinement lib/hooks/use-auth.ts: useCurrentUser() + useLogout() with TanStack Query globals.css: added auron-fade-up keyframes + gold color tokens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Layout: - app/(shop)/layout.tsx: shop shell wrapping Navbar + Footer around all shop pages - Navbar: sticky glass-blur header, Cormorant Garamond logo with gold dot, debounced search (NavbarSearch client island in Suspense), cart icon, user dropdown with avatar initials and role-aware menu (admin link shown for admin role) - Footer: dark-panel, three link columns, gold top-line gradient, client component for hover effects - Promoted Cormorant Garamond to root layout so both auth and shop share the font Landing page (app/(shop)/page.tsx): - Animated ticker band (CSS-only, seamless loop, gold dots as separators) - Asymmetric editorial hero: oversized stacked serif text left, dark geometric panel right with concentric gold circles and "A" monogram watermark - Featured products grid (8 newest, SSR with 5-min revalidation) - CategoryGrid client component with gold-border hover on category cards Product catalog: - /products: server-rendered listing with ProductFilters sidebar (category, sort, price range) and ProductSearch (300ms debounce, updates URL), both wrapped in Suspense; searchParams awaited as Promise (Next.js 16) - /products/[id]: product detail with image gallery strip, stock badge, breadcrumb, add-to-cart button (disabled when out-of-stock, wired in next phase) - loading.tsx files for both routes (skeleton states) Components: - ProductCard: 4:5 aspect image, slide-up on hover, fallback "A" monogram - ProductGrid: responsive auto-fill grid + page-number pagination - ProductGridSkeleton: matching skeleton layout - ProductFilters + ProductSearch: client components, URL-param driven - CategoryGrid: client component extracted to avoid server/client boundary issues API layer: - lib/api/products.ts: getProducts, getProduct, getCategories (server-only, force-cache with 5-min revalidation) - lib/hooks/use-products.ts: useProducts, useProduct, useCategories (client hooks) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Cart: add/update/remove items, qty stepper, real-time total, CartCount badge in navbar - Product detail: Add to Cart client component wired with quantity selector and toast feedback - Cart page: item list with inline controls, order summary panel with shipping threshold logic, empty state - BFF route handlers for cart and orders (read access_token httpOnly cookie, forward to gateway with Bearer auth) - Order history list: SSR via serverFetch, status badges, order rows with hover state - Order detail: SSR, item breakdown table, shipping address, cancel button (client component, available on pending/confirmed/processing) - TypeScript types for Cart, CartItem, Order, OrderItem Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ling - Checkout page: shipping form (shipping_name + shipping_address), cart summary sidebar, places order via POST /api/orders on submit - Pay page (/checkout/[orderId]/pay): polls GET /api/payments/order/:orderId every 2 s until client_secret arrives (Kafka async gap between order creation and Stripe PaymentIntent setup), then mounts Stripe Elements; calls stripe.confirmPayment() with redirect:'if_required' to handle 3DS without full redirect for card payments - Success page: confirmed/processing states, order reference, links to order detail and shop - PaymentForm component: PaymentElement with custom Stripe appearance matching the editorial theme (flat theme, gold accent #C9A96E, Geist font) - BFF route /api/payments/order/[orderId] forwards to gateway with httpOnly access_token - usePaymentByOrder hook: polling via TanStack Query refetchInterval, stops once client_secret is non-null Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… management
Bug fixes:
- /api/auth/me BFF now unwraps the { success, data } envelope and maps backend `name` to frontend `full_name`; useCurrentUser reads the mapped object directly instead of guessing the shape
- Address type corrected to match backend AddressResponse: uses `postal_code` (not `zip_code`), optional fields typed as `string | null`, removed `user_id`/`updated_at` which the backend doesn't include
New BFF routes:
- PUT /api/profile — maps full_name→name outbound and name→full_name inbound
- GET/POST /api/addresses, PUT/DELETE /api/addresses/[id] — proxy to /users/me/addresses with httpOnly cookie auth
Profile page (/profile):
- Two-column layout: circular monogram avatar sidebar + form content area
- Account details form: name and email with save-only-if-dirty guard
- Password change form: new password + confirm, validated with Zod refine
- Addresses section: card grid with inline edit expand, delete with confirm, add-new inline form, default badge with gold star
- All mutations use toast feedback; form validation via Zod v4 + react-hook-form
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin layout and sidebar: - `app/admin/layout.tsx` — Server Component that decodes the JWT, redirects non-admins to / and unauthenticated users to /login (defence-in-depth; proxy.ts already guards /admin at the edge) - `components/admin/admin-sidebar.tsx` — sticky dark sidebar with gold active-link indicator; usePathname for exact/prefix matching Dashboard (app/admin/page.tsx): - Stat cards: total products and category count - "Recent Products" table (last 6, newest first) with status badges Products CRUD: - `/admin/products` — SSR paginated table, Pencil edit link, DeleteProductButton (client island that calls DELETE BFF and calls router.refresh()) - `/admin/products/new` — server page fetches categories, passes as props to ProductForm client component (avoids client-side category fetch) - `/admin/products/[id]/edit` — server page fetches product + categories, pre-populates ProductForm - `components/admin/product-form.tsx` — react-hook-form + Zod; fixed the z.coerce.number() Resolver type incompatibility by switching to z.number() with valueAsNumber: true on the input registration Categories: - `/admin/categories` — fully client-side; useCategories fetches from public endpoint; useCreateCategory POSTs via BFF; list auto-refreshes via invalidateQueries Inventory: - `/admin/inventory` — SSR fetches product list, passes to InventoryTable client component; each InventoryRow uses useInventory() per-product query (lazy stock fetch, deduped/cached by TanStack Query) and useUpdateInventory mutation to call PUT /api/admin/inventory/:productId BFF routes (all read httpOnly access_token cookie): - POST /api/admin/products — create product - PUT/DELETE /api/admin/products/[id] — update/delete product - POST /api/admin/categories — create category - GET/PUT /api/admin/inventory/[productId] — read and set stock Fixed lib/validations/product.ts: - Added image_url optional field - Changed is_active from z.boolean().optional().default(true) → z.boolean() (default now lives in useForm defaultValues; avoids Resolver type mismatch) - Changed price from z.coerce.number() → z.number() with valueAsNumber: true registration (same Zod v4 + react-hook-form Resolver incompatibility fix) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete frontend implementation for Auron built on Next.js 16, React 19, TanStack Query v5, and Stripe.
proxy.tsroute guard (Next.js 16 convention), token refresh on protected routesclient_secretevery 2s while Kafka delivers the payment intent), success pageArchitecture notes
access_tokencookie and forwardAuthorization: Bearerto the gateway. The browser never touches the cookie.proxy.ts(notmiddleware.ts),await params/await searchParams,await cookies()everywhere.loadStripesingleton outside component,<Elements>provider,stripe.confirmPayment({ redirect: 'if_required' }). Stripe appearance uses hex equivalents since the iframe doesn't support oklch.z.boolean().default()andz.coerce.number()cause Resolver type incompatibilities; fixed throughout by usingz.boolean()/z.number()withdefaultValuesandvalueAsNumber: true.Test plan
/profileaccessible/profileredirects to/login/admin→ redirected to//productswith JS disabled → SSR content visible4242 4242 4242 4242→ success page/ordersshows completed order/admindashboard shows product count/products🤖 Generated with Claude Code