@zorgo/next 1.1.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cli.js +344 -1
  2. package/dist/cli.js.map +1 -0
  3. package/dist/cli.mjs +344 -1
  4. package/dist/cli.mjs.map +1 -0
  5. package/dist/components.d.mts +315 -87
  6. package/dist/components.d.ts +315 -87
  7. package/dist/components.js +1216 -820
  8. package/dist/components.js.map +1 -1
  9. package/dist/components.mjs +1195 -832
  10. package/dist/components.mjs.map +1 -1
  11. package/dist/images.d.mts +36 -0
  12. package/dist/images.d.ts +36 -0
  13. package/dist/images.js +62 -0
  14. package/dist/images.js.map +1 -0
  15. package/dist/images.mjs +32 -0
  16. package/dist/images.mjs.map +1 -0
  17. package/dist/index.d.mts +175 -6
  18. package/dist/index.d.ts +175 -6
  19. package/dist/index.js +162 -1
  20. package/dist/index.js.map +1 -0
  21. package/dist/index.mjs +162 -1
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/item-page.d.mts +151 -0
  24. package/dist/item-page.d.ts +151 -0
  25. package/dist/item-page.js +166 -0
  26. package/dist/item-page.js.map +1 -0
  27. package/dist/item-page.mjs +129 -0
  28. package/dist/item-page.mjs.map +1 -0
  29. package/dist/seo.d.mts +31 -1
  30. package/dist/seo.d.ts +31 -1
  31. package/dist/server.js +541 -1
  32. package/dist/server.js.map +1 -0
  33. package/dist/server.mjs +504 -1
  34. package/dist/server.mjs.map +1 -0
  35. package/dist/slugs.js.map +1 -1
  36. package/dist/slugs.mjs.map +1 -1
  37. package/dist/tree/app/components/ClientItemForm.tsx +7 -0
  38. package/dist/tree/app/components/Header.tsx +3 -0
  39. package/dist/tree/app/components/ModalRegistry.tsx +3 -2
  40. package/dist/tree/app/components/index.ts +2 -1
  41. package/dist/tree/app/globals.css.gen.ts +1 -0
  42. package/dist/tree/app/menu/[slug]/page.tsx +16 -48
  43. package/dist/tree/gitignore +1 -0
  44. package/dist/tree/image-loader.ts +4 -0
  45. package/dist/tree/next.config.ts +12 -0
  46. package/package.json +11 -1
  47. package/dist/cli.d.mts +0 -9
  48. package/dist/cli.d.ts +0 -9
  49. package/dist/tree/app/menu/[slug]/ItemCustomizationClient.tsx +0 -18
package/dist/index.d.ts CHANGED
@@ -2,8 +2,45 @@ import * as _stripe_stripe_js from '@stripe/stripe-js';
2
2
  import { StripeElementsOptions } from '@stripe/stripe-js';
3
3
  import * as React from 'react';
4
4
  import React__default, { RefObject } from 'react';
5
- import * as react_jsx_runtime from 'react/jsx-runtime';
6
5
  import { z } from 'zod';
6
+ import * as react_jsx_runtime from 'react/jsx-runtime';
7
+
8
+ /**
9
+ * How the section picker draws itself. Closed set — each value needs a renderer,
10
+ * so adding one is a code change (and an `alter type portion_geometry add value`).
11
+ *
12
+ * Section order is fixed per geometry, and consecutive indices must be spatially
13
+ * adjacent so any divisor of the division count partitions cleanly (sections 1–3
14
+ * of 6 are exactly section 1 of 2). Without that, switching a customer from
15
+ * sixths to halves has no defined answer.
16
+ *
17
+ * - `radial` — contiguous arcs from 12 o'clock, counter-clockwise
18
+ * - `grid` — column-major: down the left column, then the next column
19
+ * - `linear` — one end to the other
20
+ * - `count` — equal shares of a countable quantity, no geometry
21
+ */
22
+ declare const PortionGeometrySchema: z.ZodEnum<{
23
+ radial: "radial";
24
+ grid: "grid";
25
+ linear: "linear";
26
+ count: "count";
27
+ }>;
28
+ type PortionGeometry = z.infer<typeof PortionGeometrySchema>;
29
+ /** A row of `portion_schemes` — a franchise-owned, franchise-named menu entity. */
30
+ declare const PortionSchemeSchema: z.ZodObject<{
31
+ id: z.ZodNumber;
32
+ franchise_id: z.ZodNumber;
33
+ geometry: z.ZodEnum<{
34
+ radial: "radial";
35
+ grid: "grid";
36
+ linear: "linear";
37
+ count: "count";
38
+ }>;
39
+ name: z.ZodString;
40
+ min_divisions: z.ZodDefault<z.ZodNumber>;
41
+ max_divisions: z.ZodNumber;
42
+ }, z.core.$strip>;
43
+ type PortionScheme = z.infer<typeof PortionSchemeSchema>;
7
44
 
8
45
  type LocationStatus = "active" | "permanently_closed";
9
46
  interface Location {
@@ -53,16 +90,27 @@ interface ItemVariant {
53
90
  components?: Component[];
54
91
  base_item_id?: number;
55
92
  rules?: Rule[];
93
+ portion_scheme_id?: number | null;
94
+ portion_scheme?: PortionScheme | null;
56
95
  display_price?: number;
57
96
  active_promo_id?: number;
58
97
  }
98
+ interface BaseItemImage {
99
+ image_id: number;
100
+ base_item_id: number;
101
+ alias: string;
102
+ raw_path?: string | null;
103
+ processed_path?: string | null;
104
+ sort_index?: number;
105
+ local_key?: string | null;
106
+ }
59
107
  interface MenuItem {
60
108
  base_item_id: number;
61
109
  category_id: number;
62
110
  franchise_id: number;
63
111
  name: string;
64
112
  per_person?: boolean | null;
65
- image_path?: string | null;
113
+ images?: BaseItemImage[];
66
114
  variants: ItemVariant[];
67
115
  rules: Rule[];
68
116
  description?: string | null;
@@ -100,6 +148,7 @@ interface Rule {
100
148
  min_selection: number;
101
149
  max_selection?: number;
102
150
  included_quantity: number;
151
+ is_portionable?: boolean;
103
152
  items?: RuleItem[];
104
153
  components?: RuleComponent[];
105
154
  }
@@ -130,14 +179,25 @@ interface CustomizationOption {
130
179
  selected: boolean;
131
180
  disabled: boolean;
132
181
  toggle: () => void;
182
+ /** Section this option currently targets; null when editing the whole item. */
183
+ portion?: string | null;
184
+ /** Section indices this option already covers, for shading the picker. */
185
+ coveredSections?: number[];
186
+ /** Already applied to the whole item, so individual sections are moot. */
187
+ coveredWhole?: boolean;
133
188
  }
134
189
  interface CustomizationRule extends Rule {
135
190
  rule_id: number;
136
191
  max: number;
192
+ /** Weighted, not a row count: two halves of one topping count as 1. */
137
193
  selectedCount: number;
138
194
  satisfied: boolean;
195
+ /** "Choose 1. Select up to 5. 1 included" — the rule's limits in words. */
196
+ subtext: string;
139
197
  message: () => string | null;
140
198
  options: CustomizationOption[];
199
+ /** This rule can be applied per-section under the item's scheme. */
200
+ portionable?: boolean;
141
201
  }
142
202
  interface OrderItem {
143
203
  item_id: number;
@@ -155,6 +215,7 @@ interface OrderItem {
155
215
  components?: OrderComponent[];
156
216
  selections?: RuleSelection[];
157
217
  comments?: string;
218
+ portion_scheme?: PortionScheme | null;
158
219
  status?: "removed" | "modified";
159
220
  applied_promo_id?: number;
160
221
  discount_amount?: number;
@@ -163,6 +224,7 @@ interface OrderComponent extends Component {
163
224
  quantity_servings: number;
164
225
  upcharge?: number;
165
226
  rule_id?: number;
227
+ portion?: string | null;
166
228
  }
167
229
  interface RuleSelection {
168
230
  rule_id: number;
@@ -177,6 +239,7 @@ interface Selection {
177
239
  serving_price?: number;
178
240
  name: string;
179
241
  status?: "removed" | "modified" | "added";
242
+ portion?: string | null;
180
243
  component_type_id?: number;
181
244
  serving_weight?: number;
182
245
  measurement_type?: "weight" | "count";
@@ -383,7 +446,6 @@ declare function useMenu(): {
383
446
  items: MenuItem[];
384
447
  allItems: MenuItem[];
385
448
  categories: Category[];
386
- getItemImageUrl: (imagePath?: string | null) => string | null;
387
449
  };
388
450
  /**
389
451
  * Seeds a store with prebuilt data (see the app's prebuild script) during the
@@ -432,11 +494,27 @@ declare function useItemCustomization(baseItemId: number, seed?: OrderItem, edit
432
494
  unmetRule: CustomizationRule | null;
433
495
  canSubmit: boolean;
434
496
  submit: () => OrderItem | null;
497
+ portionScheme: {
498
+ id: number;
499
+ franchise_id: number;
500
+ geometry: "radial" | "grid" | "linear" | "count";
501
+ name: string;
502
+ min_divisions: number;
503
+ max_divisions: number;
504
+ } | null;
505
+ placeOption: (ruleId: number, baseKey: string, portion: string | null, max?: number) => void;
506
+ clearOption: (ruleId: number, baseKey: string) => void;
507
+ recutSelections: (next: number) => void;
435
508
  quantity: number;
436
- setQuantity: React.Dispatch<React.SetStateAction<number>>;
509
+ setQuantity: (next: number | ((current: number) => number)) => void;
437
510
  comments: string;
438
511
  setComments: React.Dispatch<React.SetStateAction<string>>;
439
- setComponentServings: (componentId: number, qty: number) => void;
512
+ /**
513
+ * Set a component's servings for the whole item, or for one section when
514
+ * `portion` is given. 0 removes it — "no cheese", or "no cheese on the
515
+ * left half".
516
+ */
517
+ setComponentServings: (componentId: number, qty: number, portion?: string | null) => void;
440
518
  price: number;
441
519
  salePrice: number | undefined;
442
520
  isEditMode: boolean;
@@ -1105,4 +1183,95 @@ declare function useFormValues(): Record<string, unknown>;
1105
1183
 
1106
1184
  declare function useFormValidity(): boolean;
1107
1185
 
1108
- export { type Category, CheckboxField, ClickableField, ConditionalRenderer, type Customer, type CustomizationOption, type CustomizationRule, DateField, DateTime, DateTimeRange, DisclaimerText, DiscreteNumberField, DynamicFormShell, EmailField, FieldError, FieldLabel, GuestCountField, Header, IncrementField, type ItemVariant, type Location, LocationField, type Menu, type MenuItem, NameFields, type Order, type OrderComponent, type OrderItem, PhoneField, Platforms, PerPersonSummary as PricePreview, type Promotion, type RecommendationRequest, type RecommendationResult, type RecommendationStrategy, type Rule, type RuleComponent, type RuleItem, type RuleSelection, SectionLabel, type Selection, SliderField, SubmitButton, TextField, type TimeConstraints, TimeField, setZorgoBoot, setZorgoPathnameHook, useCart, useCateringConfig, useCustomer, useField, useFormStatus, useFormValidity, useFormValues, useHydrateMenu, useHydrateStore, useItemCustomization, useLocation, useMenu, useModal, usePayment, usePromotions, useRecommendations, useRegisterField };
1186
+ declare function parsePortion(portion: string): {
1187
+ index: number;
1188
+ divisions: number;
1189
+ };
1190
+ /** A section's share of the whole. No portion = the whole item = 1. */
1191
+ declare function portionWeight(portion?: string | null): number;
1192
+ /**
1193
+ * Selection keys carry their portion as a suffix: `component:123@1/2`. A bare
1194
+ * key is the whole item, which is what every pre-portion key already is — so
1195
+ * existing state parses unchanged.
1196
+ */
1197
+ declare function portionKey(baseKey: string, portion?: string | null): string;
1198
+ declare function parsePortionKey(key: string): {
1199
+ baseKey: string;
1200
+ portion: string | null;
1201
+ };
1202
+ /**
1203
+ * A section's share of a whole-item upcharge, in cents. Rounds up, so covering
1204
+ * every section can cost a cent or two more than the whole — which is why a
1205
+ * selection covering everything collapses to a single portion-less row instead.
1206
+ */
1207
+ declare function splitUpcharge(cents: number, divisions: number): number;
1208
+ /** Every division count a scheme allows, for the picker's cut selector. */
1209
+ declare function divisionsFor(scheme: Pick<PortionScheme, "min_divisions" | "max_divisions">): number[];
1210
+ /**
1211
+ * The one place a section index becomes words. The picker, the cart line, the
1212
+ * receipt and the KDS ticket all call this on the same scheme, so the index is
1213
+ * the only token the UI and the data share.
1214
+ */
1215
+ declare function portionLabel(portion: string | null | undefined, scheme: Pick<PortionScheme, "geometry">): string;
1216
+ /**
1217
+ * Expand per-section overrides on one keyed entity into one row per section —
1218
+ * how "no cheese on the left half" becomes a 0-serving row for that section and
1219
+ * a normal row for the rest. No overrides, or the same value on every section,
1220
+ * is a single whole-item row (§3 collapse).
1221
+ */
1222
+ declare function portionRows(baseKey: string, values: Record<string, number>, fallback: number): {
1223
+ portion: string | null;
1224
+ value: number;
1225
+ }[];
1226
+ /** Weighted count of a rule's selections: two halves of one topping count as 1. */
1227
+ declare function selectionWeight(keys: string[]): number;
1228
+ /**
1229
+ * Spend `included_quantity` on selection keys in pick order. A topping is a
1230
+ * topping: it takes one included slot no matter how little of the pizza it covers,
1231
+ * so putting pepperoni on one half never leaves half a free slot behind to
1232
+ * discount everything else — and its rows share that one slot, so two halves of
1233
+ * pepperoni consume one included topping, not two. With no portions this is
1234
+ * exactly the old positional rule (`position < included ? 0 : basePrice`).
1235
+ */
1236
+ declare function weightedUpcharges(keys: string[], basePriceOf: (baseKey: string) => number, included: number): Map<string, number>;
1237
+ /**
1238
+ * Re-express a portion under a different cut, for when the customer switches
1239
+ * from halves to quarters mid-order. Returns null when neither count divides the
1240
+ * other (halves → thirds), meaning the selection can't be carried over.
1241
+ */
1242
+ declare function remapPortion(portion: string, nextDivisions: number): string[] | null;
1243
+ /**
1244
+ * Positional section wording, geometry-free — deliberately not `portionLabel`.
1245
+ * Derived words ("Left", "Top left") only exist for a few geometry/division
1246
+ * pairs and need the scheme; a receipt has to say something for every cut.
1247
+ */
1248
+ declare function sectionLabel(portion?: string | null): string;
1249
+ /** A text glyph for a section, or null when unicode has no character for it. */
1250
+ declare function portionGlyph(portion: string | null | undefined, geometry?: PortionGeometry | null): string | null;
1251
+ interface PortionGroup {
1252
+ portion: string | null;
1253
+ label: string;
1254
+ glyph: string | null;
1255
+ /** The item's rules, each carrying only the selections that land on this section. */
1256
+ selections: RuleSelection[];
1257
+ removals: OrderComponent[];
1258
+ /**
1259
+ * Components present on this section. Whole-item components are the base
1260
+ * recipe — implied by the item's name — so only portioned ones land here,
1261
+ * where "pepperoni on the left" is the whole point of the line.
1262
+ */
1263
+ additions: OrderComponent[];
1264
+ }
1265
+ /**
1266
+ * One group per covered section: the whole-item group first, then sections in
1267
+ * index order, rules keeping their original order inside each. An item with no
1268
+ * portions anywhere yields exactly one whole-item group, so a surface can render
1269
+ * this unconditionally and show the heading only when `portion_scheme` is set.
1270
+ *
1271
+ * It groups and nothing else — every selection it is handed comes back out. What
1272
+ * counts as worth printing is the surface's call, not this function's.
1273
+ */
1274
+ declare function groupItemPortions(item: Pick<OrderItem, "selections" | "components" | "portion_scheme">): PortionGroup[];
1275
+ declare function radialSectionPath(index: number, divisions: number, r?: number): string;
1276
+
1277
+ export { type Category, CheckboxField, ClickableField, ConditionalRenderer, type Customer, type CustomizationOption, type CustomizationRule, DateField, DateTime, DateTimeRange, DisclaimerText, DiscreteNumberField, DynamicFormShell, EmailField, FieldError, FieldLabel, GuestCountField, Header, IncrementField, type ItemVariant, type Location, LocationField, type Menu, type MenuItem, NameFields, type Order, type OrderComponent, type OrderItem, PhoneField, Platforms, type PortionGroup, PerPersonSummary as PricePreview, type Promotion, type RecommendationRequest, type RecommendationResult, type RecommendationStrategy, type Rule, type RuleComponent, type RuleItem, type RuleSelection, SectionLabel, type Selection, SliderField, SubmitButton, TextField, type TimeConstraints, TimeField, divisionsFor, groupItemPortions, parsePortion, parsePortionKey, portionGlyph, portionKey, portionLabel, portionRows, portionWeight, radialSectionPath, remapPortion, sectionLabel, selectionWeight, setZorgoBoot, setZorgoPathnameHook, splitUpcharge, useCart, useCateringConfig, useCustomer, useField, useFormStatus, useFormValidity, useFormValues, useHydrateMenu, useHydrateStore, useItemCustomization, useLocation, useMenu, useModal, usePayment, usePromotions, useRecommendations, useRegisterField, weightedUpcharges };