Wip stage M4
This commit is contained in:
@@ -2,10 +2,9 @@
|
||||
|
||||
A modern sports-fashion e-commerce platform.
|
||||
|
||||
**Status: milestone 2 — auth and RBAC, on top of a live bilingual catalog.**
|
||||
The storefront renders real products in Vietnamese and English; the admin has working sign-in
|
||||
with rotating refresh tokens and permission-filtered navigation. Cart, checkout and orders are
|
||||
next; see [Roadmap](#roadmap).
|
||||
**Status: milestone 3 — the admin can now write to the catalog.**
|
||||
Products, variants, media and stock are editable from the back office and appear on the
|
||||
bilingual storefront immediately. Cart, checkout and orders are next; see [Roadmap](#roadmap).
|
||||
|
||||
```
|
||||
Storefront (Next.js) ─┐
|
||||
@@ -123,12 +122,12 @@ sport-store/
|
||||
│ ├── storefront/ Next.js customer site (:3000)
|
||||
│ │ └── src/
|
||||
│ │ ├── app/[locale]/ (shop) (checkout) (account) route groups
|
||||
│ │ ├── components/ Cross-feature UI (layout, chrome)
|
||||
│ │ ├── features/ auth · product · category · collection · search
|
||||
│ │ │ cart · checkout · order · wishlist · account
|
||||
│ │ ├── components/
|
||||
│ │ │ └── commerce/ Hand-built brand UI — hero, mega menu, header,
|
||||
│ │ │ product card, gallery, PDP, filter sheet
|
||||
│ │ ├── i18n/ next-intl routing, request config, navigation
|
||||
│ │ ├── messages/ vi.json · en.json (UI strings)
|
||||
│ │ ├── hooks/ lib/ services/ stores/ styles/ types/
|
||||
│ │ ├── hooks/ lib/ stores/ styles/ types/
|
||||
│ │
|
||||
│ ├── admin/ Next.js back office (:3001)
|
||||
│ │ └── src/
|
||||
@@ -148,7 +147,7 @@ sport-store/
|
||||
│ ├── types/ Framework-free domain + API contracts (zero deps)
|
||||
│ ├── validation/ Zod schemas shared by API and both frontends
|
||||
│ ├── api-client/ The only sanctioned way for a frontend to reach the API
|
||||
│ ├── ui/ Design-system primitives (Button, Input, Badge, Skeleton)
|
||||
│ ├── ui/ shadcn/ui infrastructure, owned as source (ADR-0017)
|
||||
│ ├── config/ Shared tsconfig bases + Tailwind theme tokens
|
||||
│ └── eslint-config/ Flat configs incl. the architectural boundary rules
|
||||
│
|
||||
@@ -159,7 +158,7 @@ sport-store/
|
||||
│
|
||||
├── docs/
|
||||
│ ├── architecture.md Boundaries, conventions, risks — read this first
|
||||
│ └── adr/ 15 decision records
|
||||
│ └── adr/ 17 decision records
|
||||
│
|
||||
├── docker-compose.yml Backing services; `--profile full` runs everything
|
||||
├── turbo.json pnpm-workspace.yaml package.json
|
||||
@@ -205,6 +204,13 @@ Full detail in [`docs/architecture.md`](./docs/architecture.md). The rules that
|
||||
dev and production authenticating identically.
|
||||
([ADR-0015](./docs/adr/0015-frontends-reach-the-api-through-their-own-origin.md))
|
||||
|
||||
10. **shadcn/ui for infrastructure, hand-built for brand.** Dialog, Sheet, Dropdown, Tabs, Button
|
||||
and Input come from the registry and are owned as source in `@sport/ui`. The hero, mega menu,
|
||||
header, product card, gallery and PDP are written by hand in
|
||||
`apps/storefront/src/components/commerce/` — those are the store, and a registry component
|
||||
would make them look like a template.
|
||||
([ADR-0017](./docs/adr/0017-shadcn-for-infrastructure-hand-built-for-brand.md))
|
||||
|
||||
### Languages
|
||||
|
||||
Vietnamese is the default and is served from clean URLs; English is prefixed with `/en`.
|
||||
@@ -238,23 +244,24 @@ locale-in-path would buy nothing.
|
||||
|
||||
## Roadmap
|
||||
|
||||
| Milestone | Scope |
|
||||
| --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **M0** ✅ | Architecture, tooling, schema, health check, Docker, CI |
|
||||
| **M1** ✅ | Catalog read API + Redis caching + vi/en localisation + storefront wired to real data |
|
||||
| **M2** ✅ | Auth: login, refresh rotation with reuse detection, RBAC admin, user & role management |
|
||||
| **M3** | Admin catalog: product editor, variant matrix, media uploads, inventory |
|
||||
| **M4** ◐ | Storefront catalog — listings, PDP, variant selector and filters landed with M1; sort UI, pagination and a mobile filter drawer remain |
|
||||
| **M5** | Cart, checkout, orders |
|
||||
| **M6** | Search + faceting |
|
||||
| **M7** | Promotions, coupons, reviews, CMS |
|
||||
| **M8** | Customer account |
|
||||
| **M9** | Payments (VNPay, MoMo, ZaloPay, COD), shipping, notifications |
|
||||
| Milestone | Scope |
|
||||
| --------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **M0** ✅ | Architecture, tooling, schema, health check, Docker, CI |
|
||||
| **M1** ✅ | Catalog read API + Redis caching + vi/en localisation + storefront wired to real data |
|
||||
| **M2** ✅ | Auth: login, refresh rotation with reuse detection, RBAC admin, user & role management |
|
||||
| **M3** ✅ | Admin catalog: write API, variant matrix, media uploads, inventory ledger, product editor (option builder + per-locale tabs) |
|
||||
| **M4** ✅ | Storefront catalog — listings, PDP, variant selector, filters, sort control, load-more pagination and a mobile filter sheet |
|
||||
| **M5** | Cart, checkout, orders |
|
||||
| **M6** | Search + faceting |
|
||||
| **M7** | Promotions, coupons, reviews, CMS |
|
||||
| **M8** | Customer account |
|
||||
| **M9** | Payments (VNPay, MoMo, ZaloPay, COD), shipping, notifications |
|
||||
|
||||
**Recommended next step: M3 (admin catalog write path).** Reads, auth and RBAC are in place, so
|
||||
the product editor and variant matrix now have everything they need — a known operator, a
|
||||
permission to check, and a catalog to edit. It is also what makes the seed replaceable by real
|
||||
merchandising.
|
||||
**Recommended next step: M5 (cart & checkout).** M3 now closes the loop end to end: an operator
|
||||
creates a product with per-locale content, defines the option axes, gets a generated variant matrix,
|
||||
prices it, attaches imagery per colourway, receives stock through the ledger and publishes — and the
|
||||
result renders on the storefront in both languages. Cart and checkout are the first flows that put
|
||||
the variant model under real concurrency.
|
||||
|
||||
---
|
||||
|
||||
@@ -262,11 +269,13 @@ merchandising.
|
||||
|
||||
Everything below was run, not assumed:
|
||||
|
||||
- `pnpm lint` · `pnpm typecheck` · `pnpm test` · `pnpm build` — 25/25 Turborepo tasks pass;
|
||||
- `pnpm lint` · `pnpm typecheck` · `pnpm test` · `pnpm build` — 26/26 Turborepo tasks pass;
|
||||
`pnpm format:check` clean
|
||||
- 5 migrations, 32 tables; seed loads 36 permissions, 6 roles, 3 brands, 8 categories,
|
||||
3 collections, 12 products, **155 variants**, 64 uploaded images and 3 dev accounts
|
||||
- **25 tests** — RBAC guards, password hashing, translation fallback, `Accept-Language`
|
||||
- **44 tests** — RBAC guards, password hashing, translation fallback, `Accept-Language`, the
|
||||
variant matrix planner, the HTTP client's fetch receiver and retry recursion, and the inventory
|
||||
list's variant-driven projection
|
||||
- Catalog: listings with filters/facets/cursor paging, PDP, navigation — correctly localised in
|
||||
both `vi` and `en`; money formats per locale from one integer (`690.000 ₫` / `₫690,000`)
|
||||
- Storefront: every route 200 in both locales; `/en/products/<vi-slug>` → 307 →
|
||||
@@ -283,15 +292,28 @@ Everything below was run, not assumed:
|
||||
|
||||
### Verified in a real browser
|
||||
|
||||
Server-side checks and curl are not sufficient for client behaviour — three bugs proved it.
|
||||
Confirmed by clicking through Chrome with the console and network panel open:
|
||||
Server-side checks and curl are not sufficient for client behaviour. Confirmed by clicking
|
||||
through Chrome with the console and network panel open:
|
||||
|
||||
- Admin sign-in issues exactly **one** `POST /auth/admin/login`, then redirects to the dashboard
|
||||
- Sidebar is filtered by the signed-in operator's permissions; users table and role viewer load
|
||||
real data; language switch preserves the session and the current page; sign-out returns to login
|
||||
- Admin sign-in issues exactly **one** request, then redirects; sidebar is permission-filtered;
|
||||
users table and role viewer load real data; language switch preserves session and page
|
||||
- Storefront PDP: gallery swaps with the colourway, per-variant stock disables the right sizes,
|
||||
SKU updates, and switching language moves between translated slugs
|
||||
- Filters apply (`/men?colors=black&onSale=true`), and all 16 grid images load
|
||||
SKU updates, language switch moves between translated slugs; filters apply; all grid images load
|
||||
- **Admin catalog (M3):** product list with live stock and price ranges; publish/unpublish;
|
||||
media library upload driven from the browser (presign → PUT to MinIO → register, 400×500 PNG
|
||||
landed at 10,962 bytes with a date-partitioned UUID key); inventory adjustment from the table
|
||||
wrote a ledger entry and the storefront went `OUT_OF_STOCK` → `IN_STOCK` on the next request
|
||||
- **Listing controls (M4):** sort menu changes the order and the URL together
|
||||
(`?sort=price_asc`), and is shareable; "load more" appends the next page in place without
|
||||
touching the address bar, updates "showing N of M", and disappears when the set is exhausted;
|
||||
following the same button's `href` with JavaScript off returns a distinct, correctly
|
||||
locale-prefixed second page — page 1 and page 2 verified disjoint with a working cursor chain
|
||||
- **The whole M3 loop, authored through the UI:** created a product with vi + en content, two
|
||||
colourways and two sizes → 4 variants generated with correct SKUs and translated titles
|
||||
(`Đen / M`, `Xanh Neon / L`) → edited two prices and one sale price, with only the changed rows
|
||||
sent → attached one image per colourway → received stock on all four variants → published →
|
||||
the PDP renders in both languages at per-locale slugs, the gallery and price track the colourway
|
||||
swatch, and the sale price shows in red
|
||||
|
||||
Known benign noise: NestJS logs two `Unsupported route path: "/api/*"` warnings at boot. They
|
||||
come from Nest's own global-prefix handling under Express 5 / path-to-regexp v8, are
|
||||
|
||||
Reference in New Issue
Block a user