v0.6: template-manager owns the product catalog
The host no longer carries a runtime product catalog. products.js shrinks to a legacy single-product stub; the canonical catalog lives in template-manager's productsStore, seeded ONCE on first boot from the new seedProducts.js (after that the store under templates/ is authoritative and the seed is ignored — intentional, so restarts never rewrite an admin-managed catalog). phoneModels.js and templateCategories.js are the supporting constant sets, and docs/template-schema.md documents the v4 schema. templates/ is the admin-managed store itself (_products_index.json + products/<id>/product.json, JSON only) — committed so the configured catalog (sizes, pricing, options built up in the admin UI) survives a fresh checkout rather than regressing to the seed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,89 +1,69 @@
|
||||
/**
|
||||
* Product type configuration for the Pawfectly Yours editor.
|
||||
* Legacy single-product stub.
|
||||
*
|
||||
* The frontend rendering is parameterized by these objects so that the same
|
||||
* editor code can drive shirts, magnets, earrings, stickers, and so on. Only
|
||||
* code reads this — there is intentionally no UI for picking the active
|
||||
* product.
|
||||
* v0.6 — the host no longer owns a runtime product catalog. The
|
||||
* canonical catalog lives in template-manager (productsStore) and is
|
||||
* seeded once from src/constants/seedProducts.js.
|
||||
*
|
||||
* Coordinate systems
|
||||
* ──────────────────
|
||||
* Every product has TWO coordinate systems:
|
||||
* What's left in this file
|
||||
* ────────────────────────
|
||||
* The legacy single-product editor flow at `/` (apparel-designer's
|
||||
* App.jsx) calls `getActiveProduct()` and reads a small set of
|
||||
* shape-only fields off the result: `canvasSize`, `printZone`,
|
||||
* `productSvg`, `background`. We keep a minimal stub here so that
|
||||
* flow continues booting without re-architecting it for the v0.6
|
||||
* data shape.
|
||||
*
|
||||
* 1. **Design coordinates** (`canvasSize`) — the integer space in which
|
||||
* individual elements (images, text, stickers) live. Element x/y/width/
|
||||
* height are in these units. The server export pipeline scales from
|
||||
* `canvasSize` to the final 4500×4500 print resolution.
|
||||
*
|
||||
* 2. **Product SVG coordinates** (`productSvg.width / .height`) — the
|
||||
* viewBox of the product mockup that's drawn behind the design canvas.
|
||||
* The `printZone` field positions the design canvas inside the product
|
||||
* mockup, in these units.
|
||||
*
|
||||
* Keeping `canvasSize` at 300 (the historical value) means existing templates
|
||||
* and the server-side export math do not need to change. The visual fit
|
||||
* (canvas overlapping the print zone of the product) is achieved by CSS
|
||||
* scaling the rendered Konva Stage at runtime.
|
||||
* Migration intent
|
||||
* ────────────────
|
||||
* The legacy `/` flow is slated for retirement; the customer-facing
|
||||
* surface is `/customize?template=…` which reads the template's
|
||||
* embedded variant. When the legacy flow is removed, this file goes
|
||||
* with it.
|
||||
*/
|
||||
|
||||
export const PRODUCT_TYPES = {
|
||||
shirt: {
|
||||
id: 'shirt',
|
||||
label: 'T-Shirt',
|
||||
canvasSize: 300,
|
||||
// Where on the product mockup the design canvas should overlay, in
|
||||
// product SVG coordinates. Sized roughly to a real DTG print zone for
|
||||
// a youth-medium tee.
|
||||
//
|
||||
// Note: the product SVG's viewBox is offset (starts at 40, 50) to crop
|
||||
// empty margin around the shirt outline. printZone coords are in that
|
||||
// same viewBox space — i.e. (90, 130) is what the chest landed at
|
||||
// after the crop, not the path's raw (130, 180).
|
||||
printZone: { x: 90, y: 130, width: 220, height: 220 },
|
||||
productSvg: {
|
||||
// Component name (looked up by DesignCanvas) and the dimensions of its
|
||||
// viewBox. Must match the actual SVG.
|
||||
component: 'shirt',
|
||||
width: 400,
|
||||
height: 470,
|
||||
},
|
||||
background: {
|
||||
// 'color' = user can pick from SHIRT_COLORS
|
||||
// 'fixed' = use background.color as a constant fill
|
||||
mode: 'color',
|
||||
defaultColorId: 'white',
|
||||
},
|
||||
},
|
||||
|
||||
// Reference shapes for future products. None of these are wired into the
|
||||
// UI yet, but the DesignCanvas/ProductSVG plumbing is ready to handle them.
|
||||
//
|
||||
// magnet: {
|
||||
// id: 'magnet',
|
||||
// label: 'Magnet',
|
||||
// canvasSize: 300,
|
||||
// printZone: { x: 30, y: 30, width: 240, height: 240 },
|
||||
// productSvg: { component: 'magnet', width: 300, height: 300 },
|
||||
// background: { mode: 'fixed', color: '#f4f1ee' },
|
||||
// },
|
||||
// earring: { ... },
|
||||
// sticker: { ... },
|
||||
// Bare shirt shape with just enough fields for the legacy
|
||||
// getActiveProduct() consumers in App.jsx + the editor's
|
||||
// legacy single-product code paths.
|
||||
const LEGACY_SHIRT_STUB = {
|
||||
id: 'shirt',
|
||||
label: 'T-Shirt',
|
||||
canvasSize: 300,
|
||||
printZone: { x: 90, y: 130, width: 220, height: 220 },
|
||||
productSvg: { component: 'shirt', width: 400, height: 470 },
|
||||
background: { mode: 'color', defaultColorId: 'white' },
|
||||
// No variants — v0.6 catalog lives in template-manager.
|
||||
variants: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Which product the editor is currently configured for. Change this constant
|
||||
* to point at a different `PRODUCT_TYPES` entry. There's intentionally no UI
|
||||
* exposure — switching products is a code change.
|
||||
* Return the active product for the legacy single-product editor.
|
||||
* Always returns the shirt stub in v0.6.
|
||||
*/
|
||||
export function getActiveProduct() {
|
||||
return LEGACY_SHIRT_STUB;
|
||||
}
|
||||
|
||||
// ── Deprecated exports ────────────────────────────────────────
|
||||
//
|
||||
// These names are referenced in places that haven't been swept yet.
|
||||
// They resolve to the legacy stub or to undefined so any straggling
|
||||
// importer doesn't crash on module load. Will be removed in a
|
||||
// follow-up cleanup pass.
|
||||
|
||||
/** @deprecated Use seedProducts.DEFAULT_PRODUCT_ID. */
|
||||
export const DEFAULT_PRODUCT_TYPE_ID = 'shirt';
|
||||
|
||||
/** @deprecated Use seedProducts.DEFAULT_SIZE_ID. */
|
||||
export const DEFAULT_VARIANT_ID = 'shirt-default';
|
||||
|
||||
/** @deprecated Use seedProducts. */
|
||||
export const ACTIVE_PRODUCT_ID = 'shirt';
|
||||
|
||||
export function getActiveProduct() {
|
||||
const product = PRODUCT_TYPES[ACTIVE_PRODUCT_ID];
|
||||
if (!product) {
|
||||
throw new Error(
|
||||
`Unknown ACTIVE_PRODUCT_ID="${ACTIVE_PRODUCT_ID}". `
|
||||
+ `Valid ids: ${Object.keys(PRODUCT_TYPES).join(', ')}`
|
||||
);
|
||||
}
|
||||
return product;
|
||||
/** @deprecated Use seedProducts; map productId/sizeId via the products API. */
|
||||
export const PRODUCT_TYPES = { shirt: LEGACY_SHIRT_STUB };
|
||||
|
||||
/** @deprecated Use the resolved variant from a template GET response. */
|
||||
export function getVariant(_productTypeId, _variantId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user