feat: three nuxt frontends, demo seed, rounding + money-exponent + rate-cast fixes, archived specs
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Task: build apps/admin (platform console) — READ openspec/changes/frontend-apps/agent-conventions.md FIRST and follow it exactly. Your app = @vmall/admin, port 3002. Allowed roles: platform_admin.
|
||||
|
||||
Replace the placeholder pages. app.vue (sidebar layout) exists — keep it.
|
||||
|
||||
## Pages to implement (apps/admin/pages/)
|
||||
|
||||
1. `login.vue` — per conventions; only platform_admin may proceed. (Seed account: admin@vmall.local / admin1234 — display this hint on the login page in a muted line for the dev MVP.)
|
||||
2. `index.vue` (auth) — dashboard: stat cards (total users, shops, orders, enabled currencies) from `$api.admin.listUsers()`, `$api.admin.listShops()`, `$api.admin.listOrders()`, `$api.admin.listCurrencies()`.
|
||||
3. `users.vue` (auth) — `$api.admin.listUsers(page)` table: email, display_name, role badge, assigned shop (resolve shop_id → shop name via `$api.admin.listShops()`, localized), created. Role editor per row: role select (platform_admin/shop_owner/shop_staff/customer) + shop select shown ONLY for shop roles + save button → `$api.admin.setUserRole(id, role, shopId|null)`; on validation error (e.g. shop role without shop → 400) show message. Paging.
|
||||
4. `shops.vue` (auth) — `$api.admin.listShops()` table: localized name, slug, status badge, created; actions suspend/activate → `$api.admin.setShopStatus`. Create-shop card: name EN, name ZH, slug → `$api.admin.createShop({en, zh}, slug)`; refetch; show conflict error (duplicate slug → 409).
|
||||
5. `orders.vue` (auth) — `$api.admin.listOrders(page)`: table (order_no, shop (resolve id→name), user_id short, total formatted in order.currency, status badge, created). Paging.
|
||||
6. `currencies.vue` (auth) — `$api.admin.listCurrencies()` table: code, localized name, symbol, exponent, rate_to_base, base badge, enabled badge. Per-row rate editor (number input + save → `$api.admin.setRate(code, rate)`). New-currency card: code (3 letters), name EN + ZH, symbol, exponent (0–6), rate_to_base, enabled checkbox → `$api.admin.upsertCurrency({...})` with CurrencyUpsertBody (rate_to_base is a STRING). Toggle enabled via upsert with the row's other fields preserved.
|
||||
|
||||
## Middleware
|
||||
middleware/auth.ts per conventions; apply to everything except /login.
|
||||
|
||||
## Verify
|
||||
`pnpm --filter @vmall/admin build` MUST pass. Fix type errors properly (no `any`, no @ts-ignore).
|
||||
|
||||
## Report back
|
||||
Pages built, deviations, contract gaps found.
|
||||
@@ -0,0 +1,27 @@
|
||||
# VMall frontend conventions (READ FIRST — applies to every app agent)
|
||||
|
||||
Repo: /Users/chengdzhang/github/jamyun/vmall (pnpm workspace). The Rust API is COMPLETE and tested; do not touch apps/api, packages/shared, openspec, or any app other than yours.
|
||||
|
||||
## Your app
|
||||
- Nuxt 3 + pinia + @nuxtjs/i18n. Deps installed. Verify with `pnpm --filter @vmall/<your-app> build` at the end (MUST pass). Do not run dev servers, do not run other apps' builds, do not run cargo.
|
||||
- The API may not be running while you work — code against the contract, prove with `nuxt build` (type safety + compile). NEVER mock the API.
|
||||
|
||||
## Contract (packages/shared/src)
|
||||
- `useNuxtApp().$api` is a typed `ApiClient` (see packages/shared/src/api.ts for every method + request body types). Provided by plugins/api.ts (already wired, reads baseUrl from runtimeConfig public.apiBase, token from localStorage `vmall.token`).
|
||||
- Types in packages/shared/src/types.ts (User, Product, Sku, Cart, Order, Shipment, Invoice, Currency, Paged, etc.). Money = integer minor units + ISO currency code.
|
||||
- `t(localizedText, locale)` picks the display string from a JSONB {"en","zh"} map. `formatMoney(amountMinor, currency, exponent, locale)` formats minor units.
|
||||
- All UI strings via $t with keys from @vmall/shared/locales (en + zh exist). If you need a key that doesn't exist, add it to YOUR app's locales-extra.ts (enExtra/zhExtra, same nested shape) — NEVER edit packages/shared.
|
||||
- Shared stylesheet `@vmall/shared/ui.css` is loaded: use its classes (.card, .btn .primary .sm .danger, .table, .badge .green/.blue/.orange/.red, .field, .grid.products, .product-card, .page-title, .page-head, .form-narrow, .muted, .row, .between, .mt, .mb, .error-text). Add app-scoped CSS only in a <style> block when needed.
|
||||
|
||||
## Session & guards
|
||||
- stores/session.ts: `useSessionStore()` — hydrate() on mounted (app.vue already does), setAuth({token,user}), logout(), getters.isLoggedIn, state.user (role, display_name).
|
||||
- Route guard convention: create `middleware/auth.ts` with defineNuxtRouteMiddleware that (client-side) reads localStorage `vmall.token` + `vmall.user` (JSON) and redirects to /login when absent or when user.role is not in the app's allowed roles. Apply with `definePageMeta({ middleware: "auth" })` on every protected page. The /login page itself is public.
|
||||
- Login page pattern: form → `$api.login(email, password)` → on success check `user.role` against the app's allowed roles (wrong role → show error, do not setAuth) → `session.setAuth(...)` → navigateTo("/"). Show API error message on failure.
|
||||
|
||||
## Behavior rules
|
||||
- All pages render in BOTH en and zh — switcher already in app.vue; never hardcode user-facing strings.
|
||||
- All list pages handle empty state ($t('common.empty')) and API errors (show err.message in .error-text).
|
||||
- After every mutation, re-fetch the affected list/detail from the API (no local-only state faking).
|
||||
- Dates: `new Date(x).toLocaleString(locale === 'zh' ? 'zh-CN' : 'en-US')`.
|
||||
- Money inputs: enter major units (e.g. 12.99), convert to minor via exponent (SKU currencies are exp-2 except JPY exp-0; SKU editor may restrict currency to USD/CNY/EUR which are all exp-2).
|
||||
- Status badges: map statuses to .badge colors (green=positive terminal, blue=active/in-progress, orange=pending, red=cancelled/rejected).
|
||||
@@ -0,0 +1,24 @@
|
||||
# Task: build apps/mall (customer storefront) — READ openspec/changes/frontend-apps/agent-conventions.md FIRST and follow it exactly. Your app = @vmall/mall, port 3000. Allowed roles: customer.
|
||||
|
||||
Replace the placeholder pages with the real storefront. app.vue (topnav with locale + currency switchers) already exists — keep it; you may refine but not regress it. Currency selection comes from `usePrefs().currency` (cookie) and is fed by the switcher in app.vue.
|
||||
|
||||
## Pages to implement (apps/mall/pages/)
|
||||
|
||||
1. `index.vue` — catalog home. Category filter (select, from `$api.listCategories()`), search input (q), paging (prev/next). Product grid (.grid.products): card with first image (or placeholder div), localized name via `t(name, locale)`, price = lowest active-SKU price, displayed in the SELECTED currency.
|
||||
Price display helper (make `composables/usePrice.ts`): given amount_minor + sku currency, if it equals selected currency use as-is, else `$api.convert(...)`; cache results in a reactive Map keyed `amount:from:to` to avoid duplicate calls; format with `formatMoney(converted, selected, exponentOfSelected, locale)`. Load currency list once (store it in the composable via useState) for exponents.
|
||||
2. `products/[id].vue` — detail. `$api.getProduct(route.params.id)`: images (main + thumbs), localized name/description, SKU picker (radio list: sku_code + attributes JSON rendered as key: value pairs + stock), qty input (1..stock), converted price, add-to-cart button. Not logged in → redirect /login (preserve intent not required). Success → navigate to /cart.
|
||||
3. `cart.vue` (auth) — `$api.getCart()`: line items (localized name, sku_code, unit price converted, qty editor with update on change `$api.updateCartItem`, remove `$api.removeCartItem`), line totals, subtotal (sum of converted lines), checkout button → /checkout. Empty state.
|
||||
4. `checkout.vue` (auth) — shipping address form (recipient/phone/country/region/city/line1/postal_code, all validated non-empty except region/postal optional-but-shown), order preview (lines + subtotal in selected currency), place order → `$api.checkout(address, currency)` → success page state: show created order numbers + link to /orders.
|
||||
5. `orders/index.vue` (auth) — `$api.listMyOrders(page)`: table (order_no, created, item count, total formatted in order.currency, status badge), link to detail. Paging.
|
||||
6. `orders/[id].vue` (auth) — `$api.getOrder(id)`: items table, address card, status badge, total. Actions by status: pending_payment → Pay (`$api.payOrder`) and Cancel (`$api.cancelOrder`). Shipments section: from `$api.listMyShipments()` filtered by order_id — carrier/tracking/status, confirm-delivery button when shipped (`$api.confirmDelivered`). Invoice section: request form (kind select personal/company, title, tax_no shown when company) → `$api.requestInvoice`; show existing invoice for this order from `$api.listMyInvoices()`.
|
||||
7. `invoices.vue` (auth) — `$api.listMyInvoices()`: table (invoice_no or —, order_no, title, kind, amount formatted, status badge, issued_at).
|
||||
8. `login.vue`, `register.vue` — .form-narrow cards. Register: `$api.register(email, password, displayName)` then setAuth and go home. Link between the two.
|
||||
|
||||
## Middleware
|
||||
middleware/auth.ts per conventions; apply to cart, checkout, orders/*, invoices.
|
||||
|
||||
## Verify
|
||||
`pnpm --filter @vmall/mall build` MUST pass. Fix all type errors properly (no `any`, no @ts-ignore).
|
||||
|
||||
## Report back
|
||||
Pages built, any deviations, anything missing from the API client contract (e.g. a pay endpoint) — list precisely.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Task: build apps/shop-admin (merchant console) — READ openspec/changes/frontend-apps/agent-conventions.md FIRST and follow it exactly. Your app = @vmall/shop-admin, port 3001. Allowed roles: shop_owner, shop_staff.
|
||||
|
||||
Replace the placeholder pages. app.vue (sidebar layout) exists — keep it.
|
||||
|
||||
## Pages to implement (apps/shop-admin/pages/)
|
||||
|
||||
1. `login.vue` — per conventions; only shop_owner/shop_staff may proceed.
|
||||
2. `index.vue` (auth) — dashboard: stat cards (total products, published count, orders by status counts, pending invoice requests) computed from `$api.shop.listMyProducts({ per_page: 100 })`, `$api.shop.listOrders({ per_page: 100 })`, `$api.shop.listInvoices()`. Also show shop profile from `$api.shop.getMyShop()` (localized name, slug, status).
|
||||
3. `products/index.vue` (auth) — table: localized name, slug, SKU count, status badge, created; status filter select (all/draft/published/unpublished); actions: edit link, publish (`$api.shop.publish`) / unpublish (`$api.shop.unpublish`) buttons with immediate refetch; "New product" button → products/new. Paging if total > per_page.
|
||||
4. `products/new.vue` + `products/[id].vue` (auth) — product form (shared component `components/ProductForm.vue`): slug, name EN + ZH, description EN + ZH (textareas), category select (`$api.listCategories()`, optional), images textarea (one URL per line → string[]). Save: create → `$api.shop.createProduct`, edit → `$api.shop.updateProduct`; body type ProductUpsertBody.
|
||||
On the EDIT page additionally: SKU manager — table of existing SKUs (sku_code, price formatted, currency, stock, active) + form to add/update a SKU (`$api.shop.upsertSku(productId, body)` with SkuUpsertBody): sku_code, price in MAJOR units converted to minor ×100 (currencies USD/CNY/EUR all exponent 2; offer a select of those three), stock integer, active checkbox. After upsert, refetch the product via `$api.shop.getProduct(id)` (exists in the client).
|
||||
Validation: slug required (lowercase alnum + dash), name EN required; show API errors (e.g. publish without priced SKU → 400) via .error-text.
|
||||
5. `orders/index.vue` (auth) — `$api.shop.listOrders({ status, page })`: status filter, table (order_no, created, items count, total in order.currency formatted, status badge), row → orders/[id].
|
||||
6. `orders/[id].vue` (auth) — order items, address, status. Create-shipment card (when status paid or fulfilling): carrier + tracking_no inputs + one qty input per order item (default = unshipped remainder = item.qty − sum of that item's qty across this order's shipments from `$api.shop.listShipments()` filtered by order_id); submit → `$api.shop.createShipment(orderId, carrier, trackingNo, items)` with only qty>0 lines. Shipments of this order: table with status + "Mark shipped" button when pending (`$api.shop.markShipped`). Refetch after every action.
|
||||
7. `shipments.vue` (auth) — `$api.shop.listShipments()`: full table (shipment_no, order_no, carrier, tracking_no, status badge, created) + mark-shipped action.
|
||||
8. `invoices.vue` (auth) — `$api.shop.listInvoices()`: table (invoice_no or —, order_no, title, kind, tax_no, amount formatted, status badge) + "Issue" button when requested (`$api.shop.issueInvoice`).
|
||||
|
||||
## Middleware
|
||||
middleware/auth.ts per conventions; apply to everything except /login.
|
||||
|
||||
## Verify
|
||||
`pnpm --filter @vmall/shop-admin build` MUST pass. Fix type errors properly (no `any`, no @ts-ignore).
|
||||
|
||||
## Report back
|
||||
Pages built, deviations, contract gaps found.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Proposal: frontend-apps
|
||||
|
||||
## Why
|
||||
The API needs its three user-facing surfaces: customer storefront (mall), merchant console (shop-admin), and platform console (admin) — all Nuxt 3, all bilingual (en/zh), with the mall offering multi-currency display.
|
||||
|
||||
## What changes
|
||||
- Shared package @vmall/shared: API contract types, typed API client, en/zh locales, base stylesheet.
|
||||
- mall (port 3000): product browsing w/ search+category, product detail, cart, checkout, orders, shipments, invoices, login/register, currency switcher (display conversion).
|
||||
- shop-admin (3001): dashboard, product CRUD + publish/unpublish + SKU editing, order list, shipment creation/ship, invoice issue.
|
||||
- admin (3002): user role assignment, shop creation/suspension, order oversight, currency management (rates).
|
||||
|
||||
## Non-goals
|
||||
- SSR SEO tuning, design-system polish beyond the shared stylesheet, e2e test suites (smoke-verified in browser).
|
||||
|
||||
## Capabilities
|
||||
- `frontend-mall`: shopper experience.
|
||||
- `frontend-shop-admin`: merchant experience.
|
||||
- `frontend-admin`: platform operator experience.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Spec delta: frontend-admin
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Platform user and shop management
|
||||
Platform admins SHALL assign user roles (with shop scope), create shops, and suspend/activate shops. Suspended shops' products MUST NOT be purchasable (enforced by API, reflected in UI).
|
||||
|
||||
#### Scenario: assign shop owner
|
||||
- **WHEN** an admin assigns role shop_owner with a shop to a user
|
||||
- **THEN** that user can log into shop-admin and manage that shop
|
||||
|
||||
### Requirement: Currency management
|
||||
Platform admins SHALL view currencies and update exchange rates; new rates affect subsequent conversions.
|
||||
|
||||
#### Scenario: rate update
|
||||
- **WHEN** an admin updates CNY rate_to_base
|
||||
- **THEN** the mall conversion endpoint returns amounts computed with the new rate
|
||||
@@ -0,0 +1,24 @@
|
||||
# Spec delta: frontend-mall
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Localized storefront
|
||||
The mall SHALL render UI strings and catalog content in en or zh from one switcher, defaulting to en.
|
||||
|
||||
#### Scenario: switch to Chinese
|
||||
- **WHEN** a shopper switches locale to zh
|
||||
- **THEN** navigation, buttons and product names render in Chinese without reload errors
|
||||
|
||||
### Requirement: Multi-currency display
|
||||
The mall SHALL offer a currency switcher (enabled currencies from the API) converting SKU prices for display; checkout uses the selected currency.
|
||||
|
||||
#### Scenario: switch currency
|
||||
- **WHEN** a shopper switches from USD to JPY on a product priced $10.00
|
||||
- **THEN** the displayed price reflects the API conversion rate (integer minor units)
|
||||
|
||||
### Requirement: Shopping flow
|
||||
A shopper SHALL be able to browse, view detail, add to cart, checkout with a shipping address, pay (mock), track shipments, confirm delivery, and request an invoice — all against the live API.
|
||||
|
||||
#### Scenario: end-to-end purchase
|
||||
- **WHEN** a registered shopper completes checkout on a non-empty cart
|
||||
- **THEN** orders appear under Orders and the cart is empty
|
||||
@@ -0,0 +1,17 @@
|
||||
# Spec delta: frontend-shop-admin
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Merchant product management
|
||||
Shop users SHALL manage only their own shop's products: create/edit bilingual content, manage SKUs, publish/unpublish with immediate effect on the storefront.
|
||||
|
||||
#### Scenario: publish visible in mall
|
||||
- **WHEN** a merchant publishes a product in shop-admin
|
||||
- **THEN** it appears in the mall product list for the matching locale
|
||||
|
||||
### Requirement: Merchant fulfillment
|
||||
Shop users SHALL see incoming orders, create shipments, mark them shipped, and issue requested invoices.
|
||||
|
||||
#### Scenario: ship an order
|
||||
- **WHEN** a merchant creates a shipment for a paid order and marks it shipped
|
||||
- **THEN** the shopper sees the shipment with tracking info
|
||||
@@ -0,0 +1,22 @@
|
||||
# Tasks: frontend-apps
|
||||
|
||||
## 1. Shared package
|
||||
- [x] @vmall/shared: types, API client, locales (en/zh), ui.css
|
||||
|
||||
## 2. Scaffolds
|
||||
- [x] Three Nuxt apps: config, i18n, pinia session store, api plugin, layouts
|
||||
|
||||
## 3. mall
|
||||
- [x] Home: product grid + search + category filter + paging
|
||||
- [x] Product detail with SKU picker, localized content, converted price
|
||||
- [x] Cart page, checkout page (address form), orders list/detail, shipments, invoices, login/register
|
||||
|
||||
## 4. shop-admin
|
||||
- [x] Login guard, dashboard, products list/new/edit (i18n fields, SKUs), publish/unpublish
|
||||
- [x] Orders, shipment creation + mark shipped, invoices list + issue
|
||||
|
||||
## 5. admin
|
||||
- [x] Login guard, users (role assignment), shops (create/suspend), orders, currencies (rate edit)
|
||||
|
||||
## 6. Verification
|
||||
- [x] All three apps build; browser smoke: register→shop create→product publish→purchase→ship→invoice in en + zh
|
||||
Reference in New Issue
Block a user