import type { LucideIcon } from 'lucide-react'; import { Boxes, Handshake, Shapes } from 'lucide-react'; /** * The two sections whose rows are drawn through a template — collections, and gallery pieces. * * ── Why one shape ── * * They are the same object. A title, an address, some words, a carousel, and a table of properties; * the sections differ in what they GROUP, not in how one is edited. Copying the panel would be three * hundred lines duplicated for an icon and a noun, and the copies would drift the first time one of * them got a fix. * * The site draws them through one set of blocks for exactly the same reason — see * `src/apps/web/components/subject.ts` in the website. * * ── What a section still owns ── * * Its endpoint, the word it puts in the URL, and its SPEC FIELDS. That last one is the real * difference: a collection ends with a delivery time; a piece ends with the sitemap's NOTAS*, * production time and shipping conditions. Both are just rows in a form, and naming them here is what * keeps the form from having to know which section it is drawing. */ /** What every subject row has. Snake case: it is the wire, not our shape. */ export type SubjectRecord = { id: number; /** The carousel holding its media, or null while nobody has chosen one. */ carousel_id: number | null; title: string; slug: string | null; description: string | null; sort_order: number; photo_count: number; /** The row's own lead, or null meaning "the first item of its carousel". */ cover_url: string | null; /** How that picture is framed, `x,y,w,h` in percentages. Null is centred. */ cover_crop: string | null; /** * The shape the template frames a lead into, or null for the picture's own. * * Read off the page, not the row — one decision for the whole section — and sent with the row so * this panel can draw a cropping frame of the right proportions without knowing how a template is * put together. */ cover_shape: number | null; }; export type Collection = SubjectRecord & { descriptive_text: string | null; measurement: string | null; material: string | null; colors: string | null; delivery_time: string | null; }; export type Collab = SubjectRecord & { /** * The client's word for the title, carried alongside `title`. * * The column really is `project_name` — see the site's collabs schema — and the wire publishes both * so this panel can go on reading `title` like every other section. Writes accept either. */ project_name: string; }; export type Piece = SubjectRecord & { descriptive_text: string | null; measurement: string | null; material: string | null; colors: string | null; production_notes: string | null; shipping_notes: string | null; }; export type SubjectPhoto = { path: string; name: string }; export type SubjectSection = { /** The query key the selection lives under — `?collection=3`. See docs/navigation-audit.md. */ param: string; /** Where the rows are, relative to the plugin's mount. */ endpoint: string; /** The site path they live at, for the address hint under the slug field. */ base: string; icon: LucideIcon; /** Lower case, mid-sentence: "Choose a collection, or make one." */ noun: string; /** Key, label, placeholder — in the order the client's sitemap lists them. */ specs: [Extract, string, string][]; /** Which of those are long enough to want a textarea. */ long?: Extract[]; }; export const COLLECTIONS: SubjectSection = { param: 'collection', endpoint: '/fofos-admin/collections', base: '/collections', icon: Boxes, noun: 'collection', specs: [ ['descriptive_text', 'Descriptive text', 'What this collection is'], ['measurement', 'Measurement', '45cm x 45cm'], ['material', 'Raw material', 'Acrylic yarn'], ['colors', 'Colours', ''], ['delivery_time', 'Delivery time', '4-6 weeks'], ], long: ['descriptive_text'], }; export const COLLABS: SubjectSection = { param: 'collab', endpoint: '/fofos-admin/collabs', base: '/collabs', icon: Handshake, noun: 'collab', /* No specs, and that is the difference. A collab is a PROJECT rather than an object — there is no measurement, no raw material, no colours to list. The form is a name, an address and a description, which is all the sitemap gives it, and the template carries no specs block to draw. */ specs: [], }; export const PIECES: SubjectSection = { param: 'piece', endpoint: '/fofos-admin/pieces', base: '/gallery', icon: Shapes, noun: 'piece', /* The last two are the sitemap's NOTAS* — conditions of buying the object rather than properties of it. Two more fields here, because that is honestly all the difference amounts to; the template is where they are drawn apart. */ specs: [ ['descriptive_text', 'Descriptive text', 'What this piece is'], ['measurement', 'Measurement', '45cm x 45cm'], ['material', 'Raw material', 'Acrylic yarn'], ['colors', 'Colours', ''], ['production_notes', 'Production time', 'Made to order, 4-6 weeks'], ['shipping_notes', 'Shipping', 'Ships worldwide, tracked'], ], long: ['descriptive_text'], };