Shop-Storefront/docs/PRD.md

341 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Shop-Storefront — Current State Document
> **Generated:** 2026-03-27
> **Role:** Brownfield analysis by Senior Staff Engineer / Technical Lead
> **Branch at time of analysis:** `namds/refactor-base-layout`
---
## Table of Contents
1. [Phase 1 — Discovery & Scaffolding](#phase-1--discovery--scaffolding)
2. [Phase 2 — Codebase Triage](#phase-2--codebase-triage)
3. [Phase 3 — Documentation](#phase-3--documentation)
- [High-Level System Overview](#1-high-level-system-overview)
- [Architecture Diagram](#2-architecture-diagram)
- [Local Setup Guide](#3-local-setup-guide)
- [Key Domain Entities](#4-key-domain-entities)
4. [Phase 4 — Maintenance Strategy](#phase-4--maintenance-strategy)
- [Immediate Risks](#1-immediate-risks)
- [Refactoring Opportunities](#2-refactoring-opportunities-low-hanging-fruit)
- [Testing Strategy](#3-testing-strategy)
---
## Phase 1 — Discovery & Scaffolding
### Tech Stack
| Layer | Technology |
| ----------------------- | --------------------------------------------------------- |
| **Framework** | Next.js 15 (App Router, Turbopack) |
| **Language** | TypeScript 5.3 |
| **UI Runtime** | React 19 RC (`19.0.0-rc-66855b96-20241106`) |
| **Styling** | Tailwind CSS 3 + `@medusajs/ui` + Radix UI |
| **Commerce Backend** | Medusa V2 (`@medusajs/js-sdk`) |
| **Payments** | Stripe (`@stripe/react-stripe-js`), PayPal |
| **Package Manager** | Yarn 3 (Berry) |
| **Database (indirect)** | `pg` listed as dependency — Medusa owns the DB connection |
### Entry Points & Critical Files
| File | Role |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `src/middleware.ts` | **True entry point.** Intercepts every request, resolves country code from Vercel geo-header or URL, and redirects to `/{countryCode}/...` |
| `src/app/layout.tsx` | Root Next.js layout (HTML shell, global styles) |
| `src/app/[countryCode]/(main)/layout.tsx` | **Primary layout shell.** Loads the JSON design config, fetches cart + customer, and hands everything to `DynamicLayoutRenderer` |
| `src/vibentec/configloader.ts` | Reads active design JSON from `config/` at request time |
| `src/vibentec/devJsonFileNames.ts` | Hardcoded switch that selects which tenant/design JSON is active |
| `src/lib/config.ts` | Instantiates the Medusa JS SDK singleton |
| `src/lib/data/` | All server-side data fetching (cart, customer, products, orders, regions, etc.) |
---
## Phase 2 — Codebase Triage
### Architectural Pattern
This is a **modular monolith storefront** using Next.js App Router with a custom **JSON-driven dynamic layout layer** on top. There are two distinct halves:
1. **Standard Medusa Storefront** — The base is a Medusa V2 Next.js starter. It follows a clear `modules/``templates/``components/` hierarchy, with server-side data fetching in `lib/data/` feeding React Server Components.
2. **Vibentec UI Builder** — A bespoke system in `src/vibentec/` that allows the entire page shell (header, nav, footer, banners) to be defined declaratively in JSON config files (`/config/`). At runtime, `DynamicLayoutRenderer` walks the JSON node tree and maps keys to React components via `componentMap`. Multiple tenant/brand configs exist: `3bear`, `drsquatch`, `vibentec`, `mds-starter`, `medusa-starter`, `playground`.
### Core Domain
`src/lib/data/` is the data layer. `src/modules/` holds all business-domain UI (account, cart, checkout, products, orders, collections, store). The custom differentiator is `src/vibentec/` — this is Vibentec's intellectual property layered on top of the OSS Medusa starter.
### Technical Debt & Health Assessment
| Severity | Issue |
| ---------- | ----------------------------------------------------------------------------------------------------------------- |
| **High** | `REVALIDATE_SECRET=supersecret` is committed in `.env` — must be rotated before production |
| **High** | A real publishable API key (`pk_65b8a...`) is hardcoded in the committed `.env` file |
| **Medium** | React 19 **RC** (release candidate) is pinned — not a stable release |
| **Medium** | Active tenant config is hardcoded in `src/vibentec/devJsonFileNames.ts` — switching brands requires a code change |
| **Medium** | `LayoutContext` uses `any` for `customer` and `cart` — kills type safety at the renderer boundary |
| **Medium** | `pg` is listed as a direct dependency but no direct DB usage exists in this storefront |
| **Low** | Zero test files found anywhere in the project |
| **Low** | `@types/react-instantsearch-dom` in devDeps but no instantsearch code exists — unused dependency |
---
## Phase 3 — Documentation
### 1. High-Level System Overview
This is a **multi-tenant e-commerce storefront** built on Next.js 15 (App Router), serving as the customer-facing frontend for a Medusa V2 headless commerce backend. The application handles the full retail customer journey: browsing products by category or collection, managing a cart, checking out with Stripe or PayPal, and managing account details and order history. All routes are prefixed with a country code (e.g., `/us/`, `/de/`), which is resolved at the edge by middleware that consults Medusa's region API and Vercel's geo-IP headers to deliver region-appropriate pricing and shipping rules.
The most significant custom layer is the **Vibentec UI Builder** (`src/vibentec/`). Rather than hard-coding the page shell (header, nav, banner bar, footer), the entire layout is declared as a JSON component tree stored in `config/*.design.json` files. At request time, the primary layout server component reads the active JSON file, passes it to `DynamicLayoutRenderer`, which walks the tree and resolves component names to actual React components via `componentMap`. This makes the full layout of the storefront configurable without touching React code — a white-label capability designed to serve multiple brands from a single codebase.
Data flows unidirectionally from the Medusa backend through server-side `"use server"` functions in `src/lib/data/`, which use the Medusa JS SDK with Next.js `force-cache` and tag-based revalidation. There is no client-side global state management (no Redux, Zustand, or React Query). The only React Context used is a minimal `ModalContext` scoped to the modal component. All authentication state is conveyed via cookies and forwarded as HTTP headers to Medusa.
---
### 2. Architecture Diagram
```mermaid
graph TD
subgraph "Browser"
Browser["Customer Browser"]
end
subgraph "Next.js 15 App (Port 8000)"
Middleware["src/middleware.ts\n(Edge Runtime)\nGeo-IP → Country Code\nRegion redirect"]
subgraph "App Router"
RootLayout["app/layout.tsx\nRoot HTML shell"]
MainLayout["app/[countryCode]/(main)/layout.tsx\nLoads design JSON\nFetches cart + customer\nDynamicLayoutRenderer"]
CheckoutLayout["app/[countryCode]/(checkout)/layout.tsx\nCheckout shell"]
subgraph "Pages"
Home["/ (main) page.tsx"]
Store["/store page.tsx"]
Product["/products/[handle]"]
Cart["/cart"]
Checkout["/checkout"]
Account["/account (Parallel Routes)"]
Order["/order/[id]"]
Collections["/collections/[handle]"]
Categories["/categories/[...category]"]
end
end
subgraph "Vibentec UI Builder (src/vibentec/)"
ConfigLoader["configloader.ts\nReads config/*.design.json"]
Renderer["renderer.tsx\nDynamicLayoutRenderer"]
ComponentMap["component-map.tsx\ncomponentMap registry"]
end
subgraph "Modules (src/modules/)"
Layout["layout/\nHeader, Nav, Footer,\nCart Button, Side Menu,\nMega Menu, Country Select"]
Products["products/\nGallery, Actions, Price,\nPreview, Tabs, Related"]
CartMod["cart/\nItems, Summary, Preview"]
CheckoutMod["checkout/\nAddresses, Payment,\nShipping, Review"]
AccountMod["account/\nProfile, Orders,\nAddresses, Login"]
OrderMod["order/\nConfirmation, Details,\nTransfer"]
end
subgraph "Data Layer (src/lib/data/)"
DataFunctions["cart.ts · products.ts\ncustomer.ts · orders.ts\ncategories.ts · collections.ts\nregions.ts · payment.ts\nfulfillment.ts · cookies.ts"]
end
subgraph "Config Files (config/)"
DesignJSON["nam.vibentec.design.json\nnam.3bear.design.json\nnam.drsquatch.design.json\nste.medusa-starter.design.json\n(+ others)"]
end
end
subgraph "External Services"
MedusaBackend["Medusa V2 Backend\n(localhost:9000)\nProducts, Orders, Carts,\nCustomers, Regions,\nPayment Sessions"]
Stripe["Stripe\nPayment Processing"]
PayPal["PayPal\nPayment Processing"]
S3["AWS S3\nProduct Images"]
end
Browser -->|"HTTP Request"| Middleware
Middleware -->|"Resolves region\nSets _medusa_cache_id cookie"| RootLayout
RootLayout --> MainLayout
MainLayout --> ConfigLoader
ConfigLoader -->|"Reads active .design.json"| DesignJSON
MainLayout --> Renderer
Renderer --> ComponentMap
ComponentMap --> Layout
MainLayout -->|"props.children"| Pages
Pages --> Products
Pages --> CartMod
Pages --> CheckoutMod
Pages --> AccountMod
Pages --> OrderMod
DataFunctions -->|"Medusa JS SDK\nforce-cache + tag revalidation"| MedusaBackend
CheckoutMod -->|"Stripe Elements"| Stripe
CheckoutMod -->|"PayPal SDK"| PayPal
MedusaBackend -->|"Image URLs"| S3
Products & CartMod & CheckoutMod & AccountMod & OrderMod --> DataFunctions
```
---
### 3. Local Setup Guide
**Prerequisites:** Node.js ≥ 20, Yarn 3 (Berry), a running Medusa V2 backend with at least one Region configured.
```bash
# 1. Clone and install
git clone <repo-url>
cd Shop-Storefront
yarn install
# 2. Configure environment
cp .env .env.local
# Edit .env.local and set:
# MEDUSA_BACKEND_URL=http://localhost:9000
# NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=<your key from Medusa Admin>
# NEXT_PUBLIC_BASE_URL=http://localhost:8000
# NEXT_PUBLIC_DEFAULT_REGION=us
# REVALIDATE_SECRET=<generate a strong random string — never use "supersecret">
# 3. Select the active brand design (defaults to namVibentec)
# Edit src/vibentec/devJsonFileNames.ts:
# const fileName = jsonFileNames.namVibentec; ← change to desired brand key
# 4. Run development server (Turbopack, port 8000)
yarn dev
# 5. Open browser — middleware will redirect to /{DEFAULT_REGION}/
open http://localhost:8000
```
**Build for production:**
```bash
yarn build
yarn start
```
> `next.config.js` calls `checkEnvVariables()` at startup. Missing required env vars will cause a descriptive build failure.
---
### 4. Key Domain Entities
Inferred from `@medusajs/types` (`HttpTypes`) as used by the data layer, plus local types in `src/types/global.ts`.
| Entity | Key Fields | Source |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **StoreProduct** | `id`, `title`, `handle`, `thumbnail`, `variants[]`, `tags[]`, `metadata`, `collection`, `categories[]` | `lib/data/products.ts` |
| **StoreProductVariant** | `id`, `calculated_price`, `inventory_quantity`, `options[]` | Fetched with `*variants.calculated_price` field selector |
| **StoreCart** | `id`, `region_id`, `items[]`, `shipping_address`, `billing_address`, `email`, `promotions[]`, `shipping_methods[]`, `payment_collection` | `lib/data/cart.ts` |
| **StoreLineItem** | `id`, `variant_id`, `quantity`, `total`, `product`, `variant`, `thumbnail`, `metadata` | Part of StoreCart |
| **StoreRegion** | `id`, `countries[]` (each with `iso_2`) | `middleware.ts`, `lib/data/regions.ts` |
| **StoreCustomer** | `id`, `email`, `first_name`, `last_name`, `addresses[]`, `phone` | `lib/data/customer.ts` |
| **StoreOrder** | `id`, `status`, `items[]`, `shipping_address`, `payment_collections[]`, `fulfillments[]` | `lib/data/orders.ts` |
| **StoreCartShippingOption** | `id`, `name`, `amount`, `provider_id` | `lib/data/cart.ts``listCartOptions()` |
| **FeaturedProduct** _(local)_ | `id`, `title`, `handle`, `thumbnail` | `src/types/global.ts` |
| **VariantPrice** _(local)_ | `calculated_price`, `original_price`, `currency_code`, `price_type`, `percentage_diff` | `src/types/global.ts` |
| **StoreFreeShippingPrice** _(local)_ | extends `StorePrice` + `target_reached`, `target_remaining`, `remaining_percentage` | `src/types/global.ts` |
| **LayoutComponentNode** _(local)_ | `{ [ComponentName]: { config?, children? } }` | `src/vibentec/component-map.tsx` |
---
## Phase 4 — Maintenance Strategy
### 1. Immediate Risks
| Severity | Risk | Location | Action Required |
| ------------ | ---------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Critical** | Secrets committed to repository | `.env` | Rotate `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY` immediately. Regenerate `REVALIDATE_SECRET`. Use `.env.local` (already `.gitignore`'d) for real values. |
| **Critical** | Silent bug: missing `await` | `src/lib/data/cart.ts:339` | `const cartId = getCartId()` is missing `await`. `cartId` resolves to a `Promise` (truthy), so the guard `if (!cartId)` never fires. `setAddresses` silently fails to validate cart existence on cold sessions. Fix: `const cartId = await getCartId()` |
| **High** | Empty stub functions silently do nothing | `src/lib/data/cart.ts:279318` | `applyGiftCard`, `removeDiscount`, `removeGiftCard` are exported but completely empty. Any UI that calls them will silently succeed with no effect. Either implement for Medusa V2 or remove the exports. |
| **High** | TypeScript & ESLint errors suppressed at build | `next.config.js:2427` | `ignoreBuildErrors: true` and `ignoreDuringBuilds: true` ship broken types silently. Re-enable both before production and resolve resulting errors. |
| **Medium** | React 19 RC pinned | `package.json` | Pre-release runtime in production. Migrate to stable React 19 once available, or document the risk explicitly. |
---
### 2. Refactoring Opportunities (Low-Hanging Fruit)
Listed in priority order — highest impact for lowest effort first.
**1. Externalize active brand config to an environment variable**
- File: `src/vibentec/devJsonFileNames.ts`
- Change the hardcoded `const fileName = jsonFileNames.namVibentec` to read from `process.env.VIBENTEC_DESIGN_CONFIG`.
- Impact: Enables true multi-tenant deployment (different brands on different deployments) without any code changes. Currently requires a code commit to switch brands.
**2. Type the `LayoutContext` properly**
- File: `src/vibentec/component-map.tsx:3438`
- Replace `customer: any` and `cart: any` with `HttpTypes.StoreCustomer | null` and `HttpTypes.StoreCart | null`.
- Impact: Propagates type safety through the entire renderer pipeline, catching prop mismatches at compile time.
**3. Remove dead dependencies**
- `pg` and `@types/pg` — no direct DB access exists in this storefront.
- `@types/react-instantsearch-dom` — no instantsearch code exists.
- Run: `yarn remove pg @types/pg @types/react-instantsearch-dom`
- Impact: Cleaner dependency graph, faster installs, less confusion for new developers.
**4. Fix hardcoded `data-mode="light"`**
- File: `src/app/layout.tsx:11`
- If dark mode is planned: drive this from a cookie or `prefers-color-scheme`. If not: add a comment documenting the intentional lock to light mode.
**5. Delete commented-out V1 cart code**
- File: `src/lib/data/cart.ts:279318`
- The commented blocks are Medusa V1 patterns. They will never be un-commented as-is. Remove them to reduce noise and cognitive load.
---
### 3. Testing Strategy
Given zero existing tests and the RSC-heavy architecture, the recommended approach is **outside-in, starting at the data boundaries**.
**Step 1 — Integration tests for the data layer** (`src/lib/data/`)
These are pure server functions with no React involved — the easiest place to start. Use **Vitest** with a real (or Docker-composed) Medusa backend in test mode.
```
Priority order: cart.ts → customer.ts → products.ts → regions.ts
```
Start with `cart.ts` — it has the most mutations, the most business logic, and already has a confirmed `await` bug. Writing a test for `setAddresses` would have caught that immediately.
**Step 2 — Component tests for the Vibentec renderer** (`src/vibentec/`)
The `DynamicLayoutRenderer` and `componentMap` are the highest-risk custom code with no safety net. Use **React Testing Library** with `@testing-library/react` (RSC-compatible via Next.js jest transform).
Write snapshot tests that pass a known JSON fixture through the full renderer pipeline. This creates a regression guard before any design JSON changes.
```typescript
// Example fixture test shape
it("renders a Header with a Banner child from JSON", () => {
const nodes = [
{ Header: { children: [{ Banner: { config: { variant: "nav" } } }] } },
]
const { container } = render(
<DynamicLayoutRenderer nodes={nodes} context={mockContext} />
)
expect(container).toMatchSnapshot()
})
```
**Step 3 — E2E tests for critical user journeys** using **Playwright**
Two journeys that must never break:
1. `Browse Store → Product Detail → Add to Cart → Checkout → Order Confirmed`
2. `Login → Account Dashboard → View Order History`
**Step 4 — CI enforcement**
The `.github/` directory currently only contains an issue template. Add a GitHub Actions workflow that runs on every PR:
```yaml
- yarn lint # re-enable ignoreDuringBuilds: false first
- tsc --noEmit # re-enable ignoreBuildErrors: false first
- vitest run # data layer integration tests
- playwright test # critical path E2E
```
No PR merges to `main` without all four passing.