feat(mall): serve home marketing content from the API
Wave 4 of replacing the fixed-data mock adapter, and the first capability the
mall never had a backend for: the home page's banners, promo tiles, quick links
and floor advert art move out of local arrays.
- four explicit tables (`banners`, `promos`, `quick_links`, `floor_adverts`)
rather than one JSONB payload table, so Postgres enforces each shape
- a migration seeds them from the assets the page already rendered, so the flip
is visually a no-op. Destinations are real routes now: the mock's promo links
pointed at dangling `?category=c1` ids and its first banner used `sort=sales`,
which the catalog API rejects
- `GET /api/content/home` is public and returns the four active, ordered lists,
always including a key so a page can render a missing block
- `GET /api/admin/content` and `PUT /api/admin/content/{kind}` let a platform
admin read everything and replace one kind transactionally, with positions
reindexed from the submitted order and a rejected list changing nothing
- the mall's fixed-data adapter learns `getHomeContent`, and a `content` domain
joins the per-domain switch so the rollback path still renders the page
Verified: 23 backend tests green including six new content tests; all three
frontends build; the home page renders the same four blocks as before, an admin
reorder and deactivation change the rendered carousel, and the fixed-data
rollback renders every block with the backend stopped.
OpenSpec change: openspec/changes/replace-mock-api-wave-4
This commit is contained in:
@@ -9,6 +9,7 @@ import type {
|
||||
AuthTokens,
|
||||
Cart,
|
||||
CartItem,
|
||||
HomeContent,
|
||||
Invoice,
|
||||
InvoiceKind,
|
||||
Order,
|
||||
@@ -18,8 +19,11 @@ import type {
|
||||
} from "@vmall/shared";
|
||||
import {
|
||||
BASE_CURRENCY,
|
||||
MOCK_BANNERS,
|
||||
MOCK_CATEGORIES,
|
||||
MOCK_CURRENCIES,
|
||||
MOCK_PROMOS,
|
||||
MOCK_QUICK_LINKS,
|
||||
MOCK_STORES,
|
||||
MOCK_USER,
|
||||
defaultAddress,
|
||||
@@ -314,6 +318,40 @@ export function createMockApi(): ApiClient {
|
||||
|
||||
listMyInvoices: () => Promise.resolve(state.invoices.map((i) => ({ ...i }))),
|
||||
|
||||
// Mirror of the seeded storefront-content rows, so the home page renders
|
||||
// identically when every domain is configured to fixed data.
|
||||
getHomeContent: (): Promise<HomeContent> =>
|
||||
Promise.resolve({
|
||||
banners: MOCK_BANNERS.map((b, i) => ({
|
||||
id: `bn${i + 1}`,
|
||||
image: b.image,
|
||||
url: b.url,
|
||||
position: i,
|
||||
active: true,
|
||||
})),
|
||||
promos: MOCK_PROMOS.map((p, i) => ({
|
||||
id: `pr${i + 1}`,
|
||||
image: p.image,
|
||||
url: p.url,
|
||||
position: i,
|
||||
active: true,
|
||||
})),
|
||||
quick_links: MOCK_QUICK_LINKS.map((q, i) => ({
|
||||
id: `ql${i + 1}`,
|
||||
label: q.label,
|
||||
url: q.url,
|
||||
glyph: q.glyph,
|
||||
position: i,
|
||||
active: true,
|
||||
})),
|
||||
floor_adverts: Array.from({ length: 6 }, (_, i) => ({
|
||||
id: `fa${i + 1}`,
|
||||
image: `/mock/floor-adv-${i + 1}.svg`,
|
||||
position: i,
|
||||
active: true,
|
||||
})),
|
||||
}),
|
||||
|
||||
shop: {
|
||||
getMyShop: () => unsupported(),
|
||||
listMyProducts: () => unsupported(),
|
||||
@@ -341,6 +379,8 @@ export function createMockApi(): ApiClient {
|
||||
listCurrencies: () => unsupported(),
|
||||
upsertCurrency: () => unsupported(),
|
||||
setRate: () => unsupported(),
|
||||
getContent: () => unsupported(),
|
||||
replaceContent: () => unsupported(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user