170 lines
4.8 KiB
TypeScript
170 lines
4.8 KiB
TypeScript
export interface PrintfulMeta {
|
||
sku: number;
|
||
sizes?: string[];
|
||
colors?: { id: string; name: string; hex: string }[];
|
||
}
|
||
|
||
export interface ProductTemplate {
|
||
id: string;
|
||
name: string;
|
||
description: string;
|
||
// Print area dimensions in mm
|
||
printArea: { widthMm: number; heightMm: number };
|
||
// Output DPI
|
||
dpi: number;
|
||
// Bleed in mm
|
||
bleedMm: number;
|
||
// What artifact spec fields to use
|
||
productType: string;
|
||
substrates: string[];
|
||
requiredCapabilities: string[];
|
||
finish: string;
|
||
// Printful product metadata (optional — cosmolocal-only products omit this)
|
||
printful?: PrintfulMeta;
|
||
// Computed pixel dimensions (at DPI)
|
||
get widthPx(): number;
|
||
get heightPx(): number;
|
||
}
|
||
|
||
function makeTemplate(opts: Omit<ProductTemplate, "widthPx" | "heightPx">): ProductTemplate {
|
||
return {
|
||
...opts,
|
||
get widthPx() {
|
||
return Math.round(((this.printArea.widthMm + this.bleedMm * 2) / 25.4) * this.dpi);
|
||
},
|
||
get heightPx() {
|
||
return Math.round(((this.printArea.heightMm + this.bleedMm * 2) / 25.4) * this.dpi);
|
||
},
|
||
};
|
||
}
|
||
|
||
export const PRODUCTS: Record<string, ProductTemplate> = {
|
||
sticker: makeTemplate({
|
||
id: "sticker",
|
||
name: "Sticker Sheet",
|
||
description: "A4 sheet of die-cut vinyl stickers",
|
||
printArea: { widthMm: 210, heightMm: 297 },
|
||
dpi: 300,
|
||
bleedMm: 2,
|
||
productType: "sticker-sheet",
|
||
substrates: ["vinyl-matte", "vinyl-gloss", "sticker-paper-matte"],
|
||
requiredCapabilities: ["vinyl-cut"],
|
||
finish: "matte",
|
||
printful: { sku: 358 },
|
||
}),
|
||
poster: makeTemplate({
|
||
id: "poster",
|
||
name: "Poster (A3)",
|
||
description: "A3 art print / poster",
|
||
printArea: { widthMm: 297, heightMm: 420 },
|
||
dpi: 300,
|
||
bleedMm: 3,
|
||
productType: "poster",
|
||
substrates: ["paper-160gsm-cover", "paper-100gsm"],
|
||
requiredCapabilities: ["inkjet-print"],
|
||
finish: "matte",
|
||
}),
|
||
tee: makeTemplate({
|
||
id: "tee",
|
||
name: "T-Shirt",
|
||
description: "Front print on cotton tee (12x16 inch print area)",
|
||
printArea: { widthMm: 305, heightMm: 406 },
|
||
dpi: 300,
|
||
bleedMm: 0,
|
||
productType: "tee",
|
||
substrates: ["cotton-standard", "cotton-organic"],
|
||
requiredCapabilities: ["dtg-print"],
|
||
finish: "none",
|
||
printful: {
|
||
sku: 71,
|
||
sizes: ["S", "M", "L", "XL", "2XL", "3XL"],
|
||
colors: [
|
||
{ id: "black", name: "Black", hex: "#0a0a0a" },
|
||
{ id: "white", name: "White", hex: "#ffffff" },
|
||
{ id: "forest_green", name: "Forest Green", hex: "#2d4a3e" },
|
||
{ id: "heather_charcoal", name: "Heather Charcoal", hex: "#4a4a4a" },
|
||
{ id: "maroon", name: "Maroon", hex: "#5a2d2d" },
|
||
],
|
||
},
|
||
}),
|
||
hoodie: makeTemplate({
|
||
id: "hoodie",
|
||
name: "Hoodie",
|
||
description: "Front print on pullover hoodie (14x16 inch print area)",
|
||
printArea: { widthMm: 356, heightMm: 406 },
|
||
dpi: 300,
|
||
bleedMm: 0,
|
||
productType: "hoodie",
|
||
substrates: ["cotton-polyester-blend", "cotton-organic"],
|
||
requiredCapabilities: ["dtg-print"],
|
||
finish: "none",
|
||
printful: {
|
||
sku: 146,
|
||
sizes: ["S", "M", "L", "XL", "2XL"],
|
||
colors: [
|
||
{ id: "black", name: "Black", hex: "#0a0a0a" },
|
||
{ id: "dark_grey_heather", name: "Dark Grey Heather", hex: "#3a3a3a" },
|
||
],
|
||
},
|
||
}),
|
||
};
|
||
|
||
export function getProduct(id: string): ProductTemplate | undefined {
|
||
return PRODUCTS[id];
|
||
}
|
||
|
||
// ── POD product configurations ──
|
||
// Maps design product types to POD provider SKUs and pricing
|
||
|
||
export interface PodProductConfig {
|
||
type: string;
|
||
provider: "printful" | "prodigi";
|
||
sku: string;
|
||
variants: string[];
|
||
retailPrice: number;
|
||
}
|
||
|
||
/** Default POD product configs applied to each design */
|
||
export const POD_PRODUCTS: Record<string, PodProductConfig[]> = {
|
||
sticker: [
|
||
{ type: "sticker", provider: "prodigi", sku: "GLOBAL-STI-KIS-3X3", variants: ["matte", "gloss"], retailPrice: 3.50 },
|
||
],
|
||
poster: [
|
||
{ type: "print", provider: "prodigi", sku: "GLOBAL-FAP-A4", variants: ["matte"], retailPrice: 12.99 },
|
||
],
|
||
tee: [
|
||
{ type: "shirt", provider: "printful", sku: "71", variants: ["S", "M", "L", "XL", "2XL"], retailPrice: 29.99 },
|
||
],
|
||
hoodie: [
|
||
{ type: "hoodie", provider: "printful", sku: "146", variants: ["S", "M", "L", "XL", "2XL"], retailPrice: 39.99 },
|
||
],
|
||
};
|
||
|
||
/**
|
||
* Get the POD product configs for a product type.
|
||
* Returns an array of purchasable SKU configurations.
|
||
*/
|
||
export function getPodProducts(productType: string): PodProductConfig[] {
|
||
return POD_PRODUCTS[productType] || [];
|
||
}
|
||
|
||
/**
|
||
* Flatten all designs into purchasable storefront products.
|
||
* Each design × product config = one storefront listing.
|
||
*/
|
||
export interface StorefrontProduct {
|
||
slug: string;
|
||
designSlug: string;
|
||
name: string;
|
||
description: string;
|
||
category: string;
|
||
productType: string;
|
||
imageUrl: string;
|
||
basePrice: number;
|
||
variants: string[];
|
||
provider: string;
|
||
sku: string;
|
||
isActive: boolean;
|
||
}
|
||
|