restaurant-kit 0.1.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 (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +488 -0
  3. package/dist/RestaurantSiteRoutes.js +23 -0
  4. package/dist/admin/AdminActionMenu.js +29 -0
  5. package/dist/admin/AdminAuthPanel.js +53 -0
  6. package/dist/admin/AdminBackupsDialog.js +54 -0
  7. package/dist/admin/AdminDraftProvider.js +10 -0
  8. package/dist/admin/AdminGroupsPanel.js +68 -0
  9. package/dist/admin/AdminItemDrawer.js +264 -0
  10. package/dist/admin/AdminMenuCard.js +63 -0
  11. package/dist/admin/AdminMenuEditor.module.css.js +209 -0
  12. package/dist/admin/AdminMenuGroupTabs.js +43 -0
  13. package/dist/admin/AdminMenuIconPicker.js +39 -0
  14. package/dist/admin/AdminRoutes.js +13 -0
  15. package/dist/admin/AdminSectionEditor.js +104 -0
  16. package/dist/admin/AdminTestimonialsPanel.js +214 -0
  17. package/dist/admin/AdminTestimonialsPanel.module.css.js +29 -0
  18. package/dist/admin/adminDraftContext.js +5 -0
  19. package/dist/admin/adminEditorUtils.js +47 -0
  20. package/dist/admin/adminMenuCommands.js +83 -0
  21. package/dist/admin/useAdminDraft.js +14 -0
  22. package/dist/admin/useAdminMenuDraft.js +226 -0
  23. package/dist/cart/CartDrawer.js +87 -0
  24. package/dist/cart/CartDrawer.module.css.js +47 -0
  25. package/dist/cart/CartProvider.js +31 -0
  26. package/dist/cart/cartContext.js +5 -0
  27. package/dist/cart/cartOrder.js +34 -0
  28. package/dist/cart/cartReducer.js +57 -0
  29. package/dist/cart/useCart.js +10 -0
  30. package/dist/components/Brand.js +16 -0
  31. package/dist/components/Brand.module.css.js +15 -0
  32. package/dist/components/Footer.js +19 -0
  33. package/dist/components/Footer.module.css.js +11 -0
  34. package/dist/components/Header.js +34 -0
  35. package/dist/components/Header.module.css.js +19 -0
  36. package/dist/components/Hero.js +23 -0
  37. package/dist/components/Hero.module.css.js +15 -0
  38. package/dist/components/InstagramIcon.js +27 -0
  39. package/dist/components/LocationsSection.js +34 -0
  40. package/dist/components/LocationsSection.module.css.js +17 -0
  41. package/dist/components/Marquee.js +10 -0
  42. package/dist/components/Marquee.module.css.js +9 -0
  43. package/dist/components/OrderIcon.js +11 -0
  44. package/dist/components/RatingSection.js +32 -0
  45. package/dist/components/RatingSection.module.css.js +19 -0
  46. package/dist/components/TestimonialsSection.js +73 -0
  47. package/dist/components/TestimonialsSection.module.css.js +37 -0
  48. package/dist/components/WhatsAppIcon.js +13 -0
  49. package/dist/config/RestaurantKitProvider.js +26 -0
  50. package/dist/config/siteConfigContext.js +23 -0
  51. package/dist/config/useOrdering.js +12 -0
  52. package/dist/index.js +92 -0
  53. package/dist/menu/localMenuApi.js +43 -0
  54. package/dist/menu/menuApi.js +121 -0
  55. package/dist/menu/menuDraftStorage.js +35 -0
  56. package/dist/menu/menuIcons.js +24 -0
  57. package/dist/menu/menuMutations.js +99 -0
  58. package/dist/menu/menuOrdering.js +94 -0
  59. package/dist/menu/menuSchema.js +4 -0
  60. package/dist/menu/priceFormat.js +39 -0
  61. package/dist/menu/useMenuData.js +38 -0
  62. package/dist/menu/validateMenu.js +63 -0
  63. package/dist/menu-browser/MenuBrowser.js +54 -0
  64. package/dist/menu-browser/MenuBrowser.module.css.js +153 -0
  65. package/dist/menu-browser/MenuCard.js +106 -0
  66. package/dist/menu-browser/MenuCartDrawer.js +18 -0
  67. package/dist/menu-browser/MenuCategoryBlock.js +45 -0
  68. package/dist/menu-browser/MenuItemSheet.js +132 -0
  69. package/dist/menu-browser/MenuLightbox.js +33 -0
  70. package/dist/menu-browser/MenuRow.js +43 -0
  71. package/dist/menu-browser/MenuSection.js +10 -0
  72. package/dist/menu-browser/menuItemDisplay.js +19 -0
  73. package/dist/menu-browser/useMediaQuery.js +19 -0
  74. package/dist/menu-browser/useMenuBrowserState.js +82 -0
  75. package/dist/menu-browser/useParkedSheet.js +32 -0
  76. package/dist/pages/AdminMenuEditor.js +291 -0
  77. package/dist/pages/AdminPreview.js +43 -0
  78. package/dist/pages/AdminPreview.module.css.js +9 -0
  79. package/dist/pages/MenuSite.js +40 -0
  80. package/dist/restaurant-kit.css +1 -0
  81. package/dist/testimonials/downscaleImage.js +14 -0
  82. package/dist/testimonials/testimonialMutations.js +52 -0
  83. package/dist/types/RestaurantSiteRoutes.d.ts +11 -0
  84. package/dist/types/admin/AdminActionMenu.d.ts +20 -0
  85. package/dist/types/admin/AdminAuthPanel.d.ts +13 -0
  86. package/dist/types/admin/AdminBackupsDialog.d.ts +9 -0
  87. package/dist/types/admin/AdminDraftProvider.d.ts +12 -0
  88. package/dist/types/admin/AdminGroupsPanel.d.ts +14 -0
  89. package/dist/types/admin/AdminItemDrawer.d.ts +16 -0
  90. package/dist/types/admin/AdminMenuCard.d.ts +10 -0
  91. package/dist/types/admin/AdminMenuGroupTabs.d.ts +11 -0
  92. package/dist/types/admin/AdminMenuIconPicker.d.ts +8 -0
  93. package/dist/types/admin/AdminRoutes.d.ts +5 -0
  94. package/dist/types/admin/AdminSectionEditor.d.ts +20 -0
  95. package/dist/types/admin/AdminTestimonialsPanel.d.ts +7 -0
  96. package/dist/types/admin/adminDraftContext.d.ts +2 -0
  97. package/dist/types/admin/adminEditorUtils.d.ts +22 -0
  98. package/dist/types/admin/adminMenuCommands.d.ts +73 -0
  99. package/dist/types/admin/useAdminDraft.d.ts +5 -0
  100. package/dist/types/admin/useAdminMenuDraft.d.ts +41 -0
  101. package/dist/types/cart/CartDrawer.d.ts +7 -0
  102. package/dist/types/cart/CartProvider.d.ts +4 -0
  103. package/dist/types/cart/cartContext.d.ts +2 -0
  104. package/dist/types/cart/cartOrder.d.ts +9 -0
  105. package/dist/types/cart/cartReducer.d.ts +36 -0
  106. package/dist/types/cart/cartTypes.d.ts +37 -0
  107. package/dist/types/cart/useCart.d.ts +1 -0
  108. package/dist/types/components/Brand.d.ts +5 -0
  109. package/dist/types/components/Footer.d.ts +1 -0
  110. package/dist/types/components/Header.d.ts +1 -0
  111. package/dist/types/components/Hero.d.ts +1 -0
  112. package/dist/types/components/InstagramIcon.d.ts +6 -0
  113. package/dist/types/components/LocationsSection.d.ts +1 -0
  114. package/dist/types/components/Marquee.d.ts +1 -0
  115. package/dist/types/components/OrderIcon.d.ts +4 -0
  116. package/dist/types/components/RatingSection.d.ts +1 -0
  117. package/dist/types/components/TestimonialsSection.d.ts +2 -0
  118. package/dist/types/components/WhatsAppIcon.d.ts +5 -0
  119. package/dist/types/config/RestaurantKitProvider.d.ts +24 -0
  120. package/dist/types/config/siteConfigContext.d.ts +28 -0
  121. package/dist/types/config/siteTypes.d.ts +50 -0
  122. package/dist/types/config/useOrdering.d.ts +16 -0
  123. package/dist/types/index.d.ts +38 -0
  124. package/dist/types/menu/localMenuApi.d.ts +9 -0
  125. package/dist/types/menu/menuApi.d.ts +68 -0
  126. package/dist/types/menu/menuDraftStorage.d.ts +5 -0
  127. package/dist/types/menu/menuIcons.d.ts +7 -0
  128. package/dist/types/menu/menuMutations.d.ts +13 -0
  129. package/dist/types/menu/menuOrdering.d.ts +38 -0
  130. package/dist/types/menu/menuSchema.d.ts +68 -0
  131. package/dist/types/menu/priceFormat.d.ts +8 -0
  132. package/dist/types/menu/useMenuData.d.ts +12 -0
  133. package/dist/types/menu/validateMenu.d.ts +16 -0
  134. package/dist/types/menu-browser/MenuBrowser.d.ts +6 -0
  135. package/dist/types/menu-browser/MenuCard.d.ts +7 -0
  136. package/dist/types/menu-browser/MenuCartDrawer.d.ts +5 -0
  137. package/dist/types/menu-browser/MenuCategoryBlock.d.ts +11 -0
  138. package/dist/types/menu-browser/MenuItemSheet.d.ts +10 -0
  139. package/dist/types/menu-browser/MenuLightbox.d.ts +9 -0
  140. package/dist/types/menu-browser/MenuRow.d.ts +7 -0
  141. package/dist/types/menu-browser/MenuSection.d.ts +1 -0
  142. package/dist/types/menu-browser/menuItemDisplay.d.ts +5 -0
  143. package/dist/types/menu-browser/useMediaQuery.d.ts +1 -0
  144. package/dist/types/menu-browser/useMenuBrowserState.d.ts +17 -0
  145. package/dist/types/menu-browser/useParkedSheet.d.ts +13 -0
  146. package/dist/types/pages/AdminMenuEditor.d.ts +1 -0
  147. package/dist/types/pages/AdminPreview.d.ts +1 -0
  148. package/dist/types/pages/MenuSite.d.ts +6 -0
  149. package/dist/types/testimonials/downscaleImage.d.ts +6 -0
  150. package/dist/types/testimonials/testimonialMutations.d.ts +9 -0
  151. package/dist/types/utils.d.ts +1 -0
  152. package/dist/types/worker/auth.d.ts +97 -0
  153. package/dist/types/worker/http.d.ts +17 -0
  154. package/dist/types/worker/index.d.ts +16 -0
  155. package/dist/types/worker/menuStorage.d.ts +94 -0
  156. package/dist/utils.js +6 -0
  157. package/dist/worker/auth.js +183 -0
  158. package/dist/worker/http.js +65 -0
  159. package/dist/worker/menuStorage.js +95 -0
  160. package/dist/worker.js +131 -0
  161. package/package.json +105 -0
  162. package/scripts/bootstrap-site.mjs +125 -0
  163. package/scripts/hash-admin-password.mjs +11 -0
  164. package/scripts/lib/password.mjs +72 -0
@@ -0,0 +1,226 @@
1
+ import { useRef as R, useState as c, useCallback as r, useEffect as W } from "react";
2
+ import { toast as a } from "sonner";
3
+ import { useRestaurantKit as ae } from "../config/siteConfigContext.js";
4
+ import { updateMenuItem as ie } from "../menu/menuMutations.js";
5
+ import { isLikelyAuthError as oe, getApiErrorMessage as d } from "./adminEditorUtils.js";
6
+ import { applyAdminMenuCommand as ue } from "./adminMenuCommands.js";
7
+ function ce(i, l) {
8
+ switch (l.type) {
9
+ case "load": {
10
+ const g = i.revision + 1;
11
+ return { menu: l.menu, revision: g, savedRevision: g };
12
+ }
13
+ case "set":
14
+ return { ...i, menu: l.menu, revision: i.revision + 1 };
15
+ case "saved":
16
+ return { ...i, savedRevision: Math.max(i.savedRevision, l.revision) };
17
+ }
18
+ }
19
+ function j(i) {
20
+ return i.revision !== i.savedRevision;
21
+ }
22
+ function $(i) {
23
+ return { ...i, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
24
+ }
25
+ function he(i = {}) {
26
+ const l = ae(), { api: g, initialMenu: q = l.seedMenu, enabled: h = !0 } = i, n = g ?? l.menuApi, A = g ? !1 : l.isLocalApi, f = R({ menu: q, revision: 0, savedRevision: 0 }), [C, z] = c(f.current), m = r((e) => {
27
+ f.current = ce(f.current, e), z(f.current);
28
+ }, []), D = R(null), [k, E] = c(h), [K, L] = c(!1), [T, U] = c(!1), [F, v] = c(""), [G, y] = c(null), [H, P] = c(!1), M = r(() => {
29
+ n.getAdminSession().then(y).catch(() => y(null));
30
+ }, [n]), u = r(() => {
31
+ E(!0), v(""), n.getDraft().then(({ menu: e, etag: t }) => {
32
+ D.current = t, m({ type: "load", menu: e }), M();
33
+ }).catch((e) => {
34
+ if (oe(e)) {
35
+ v(d(e)), M();
36
+ return;
37
+ }
38
+ a.error("Could not load remote draft", {
39
+ description: d(e)
40
+ });
41
+ }).finally(() => {
42
+ E(!1);
43
+ });
44
+ }, [m, n, M]);
45
+ W(() => {
46
+ h && u();
47
+ }, [h, u]);
48
+ const b = j(C);
49
+ W(() => {
50
+ if (!h || !b || typeof window > "u") return;
51
+ const e = (t) => {
52
+ t.preventDefault();
53
+ };
54
+ return window.addEventListener("beforeunload", e), () => window.removeEventListener("beforeunload", e);
55
+ }, [h, b]);
56
+ const J = r(() => {
57
+ window.location.assign(n.getAdminSignInUrl(window.location.href));
58
+ }, [n]), N = r(
59
+ async (e) => {
60
+ P(!0);
61
+ try {
62
+ const t = await n.signInWithPassword(e);
63
+ y(t), v(""), u();
64
+ } catch (t) {
65
+ v(d(t));
66
+ } finally {
67
+ P(!1);
68
+ }
69
+ },
70
+ [u, n]
71
+ ), Q = r(async () => {
72
+ try {
73
+ await n.signOut();
74
+ } catch (e) {
75
+ a.error("Could not sign out", {
76
+ description: d(e)
77
+ });
78
+ return;
79
+ }
80
+ y((e) => e && { ...e, authenticated: !1, user: null }), v("Signed out. Sign in again to edit the remote menu."), a.success("Signed out");
81
+ }, [n]), S = r(
82
+ (e) => {
83
+ const t = f.current.menu, s = e(t);
84
+ s !== t && m({ type: "set", menu: $(s) });
85
+ },
86
+ [m]
87
+ ), x = R(Promise.resolve()), w = r(() => {
88
+ const { menu: e, revision: t } = f.current, s = x.current.then(async () => {
89
+ const { etag: o } = await n.saveDraftMenu(e, { ifMatch: D.current });
90
+ D.current = o, m({ type: "saved", revision: t });
91
+ });
92
+ return x.current = s.catch(() => {
93
+ }), s;
94
+ }, [m, n]), V = r(async () => {
95
+ L(!0);
96
+ try {
97
+ await w(), a.success("Draft saved", { description: A ? "The local draft menu has been updated." : "The remote draft menu has been updated." });
98
+ } catch (e) {
99
+ a.error("Could not save draft", {
100
+ description: d(e)
101
+ });
102
+ } finally {
103
+ L(!1);
104
+ }
105
+ }, [A, w]), X = r(async () => {
106
+ U(!0);
107
+ try {
108
+ j(f.current) && await w(), await n.publishDraftMenu(), a.success("Draft published", { description: "The public menu now uses this draft." });
109
+ } catch (e) {
110
+ a.error("Could not publish draft", {
111
+ description: d(e)
112
+ });
113
+ } finally {
114
+ U(!1);
115
+ }
116
+ }, [n, w]), Y = r(() => {
117
+ u(), a.success("Unsaved changes discarded");
118
+ }, [u]), [O, B] = c(!1), Z = r(
119
+ async (e) => {
120
+ B(!0);
121
+ try {
122
+ await n.restoreDraft(e), u(), a.success(e.kind === "published" ? "Draft restored from the published menu" : "Draft restored from backup", {
123
+ description: "Review it, then publish to make it live."
124
+ });
125
+ } catch (t) {
126
+ a.error("Could not restore the draft", {
127
+ description: d(t)
128
+ });
129
+ } finally {
130
+ B(!1);
131
+ }
132
+ },
133
+ [u, n]
134
+ ), _ = r(() => n.listMenuBackups(), [n]), ee = r(
135
+ (e) => {
136
+ const t = f.current.menu, s = ue(t, e);
137
+ return s.status === "rejected" ? s.reason : (s.menu !== t && m({ type: "set", menu: $(s.menu) }), null);
138
+ },
139
+ [m]
140
+ ), [te, ne] = c(/* @__PURE__ */ new Set()), p = r((e, t) => {
141
+ ne((s) => {
142
+ const o = new Set(s);
143
+ return t ? o.add(e) : o.delete(e), o;
144
+ });
145
+ }, []), I = r(
146
+ (e, t) => {
147
+ S((s) => ie(s, e, t));
148
+ },
149
+ [S]
150
+ ), se = r(
151
+ async (e, t) => {
152
+ if (!t.type.startsWith("image/")) {
153
+ a.error("Choose an image file");
154
+ return;
155
+ }
156
+ const s = e.imageKey;
157
+ p(e.id, !0);
158
+ try {
159
+ const o = await n.uploadMenuImage(t);
160
+ I(e.id, {
161
+ image: o.url,
162
+ imageKey: o.key
163
+ }), a.success("Image uploaded", { description: `${e.title} now uses the new image.` }), s && n.deleteMenuImage(s).catch(() => {
164
+ a.warning("Old image was replaced but not deleted", {
165
+ description: "The menu item uses the new image. The old R2 object can be cleaned up later."
166
+ });
167
+ });
168
+ } catch (o) {
169
+ a.error("Could not upload image", {
170
+ description: d(o)
171
+ });
172
+ } finally {
173
+ p(e.id, !1);
174
+ }
175
+ },
176
+ [n, p, I]
177
+ ), re = r(
178
+ async (e) => {
179
+ const t = e.imageKey;
180
+ if (I(e.id, { image: "", imageKey: void 0 }), !t) {
181
+ a.success("Image removed", { description: "The original static asset was only cleared from this item." });
182
+ return;
183
+ }
184
+ p(e.id, !0);
185
+ try {
186
+ await n.deleteMenuImage(t), a.success("Image removed");
187
+ } catch (s) {
188
+ a.warning("Image removed from item, but R2 cleanup failed", {
189
+ description: d(s)
190
+ });
191
+ } finally {
192
+ p(e.id, !1);
193
+ }
194
+ },
195
+ [n, p, I]
196
+ );
197
+ return {
198
+ draftMenu: C.menu,
199
+ hasUnsavedChanges: b,
200
+ isLoadingDraft: k,
201
+ isSavingDraft: K,
202
+ isPublishingDraft: T,
203
+ isBusy: k || K || T || O,
204
+ authErrorMessage: F,
205
+ authSession: G,
206
+ isSigningIn: H,
207
+ loadRemoteDraft: u,
208
+ signIn: J,
209
+ signInWithPassword: N,
210
+ signOut: Q,
211
+ saveDraft: V,
212
+ publishDraft: X,
213
+ discardChanges: Y,
214
+ restoreDraft: Z,
215
+ listBackups: _,
216
+ isRestoring: O,
217
+ applyMenuMutation: S,
218
+ applyCommand: ee,
219
+ uploadingItemIds: te,
220
+ uploadItemImage: se,
221
+ removeItemImage: re
222
+ };
223
+ }
224
+ export {
225
+ he as useAdminMenuDraft
226
+ };
@@ -0,0 +1,87 @@
1
+ import { jsxs as t, Fragment as m, jsx as e } from "react/jsx-runtime";
2
+ import { ShoppingBag as k, X as B, Minus as I, Plus as w } from "lucide-react";
3
+ import { useMemo as A } from "react";
4
+ import { WhatsAppIcon as $ } from "../components/WhatsAppIcon.js";
5
+ import { useSiteConfig as x } from "../config/siteConfigContext.js";
6
+ import { buildWhatsAppOrderUrl as O, formatVariationName as W } from "./cartOrder.js";
7
+ import { useCart as j } from "./useCart.js";
8
+ import r from "./CartDrawer.module.css.js";
9
+ function L({ onEditItem: d } = {}) {
10
+ const { items: o, itemCount: l, isOpen: n, orderNote: i, addItem: p, decreaseItem: u, removeItem: N, setItemNote: b, setOrderNote: v, openCart: f, closeCart: c, clearCart: C } = j(), { whatsappNumber: s, orderGreeting: h } = x(), g = A(() => O(o, i, { whatsappNumber: s, orderGreeting: h }), [o, h, i, s]);
11
+ return /* @__PURE__ */ t(m, { children: [
12
+ l ? /* @__PURE__ */ t("button", { className: r.cartBar, type: "button", onClick: f, "aria-expanded": n, children: [
13
+ /* @__PURE__ */ e("span", { className: r.cartBarIcon, children: /* @__PURE__ */ e(k, { "aria-hidden": "true" }) }),
14
+ /* @__PURE__ */ t("span", { children: [
15
+ l,
16
+ " item",
17
+ l === 1 ? "" : "s"
18
+ ] }),
19
+ /* @__PURE__ */ e("strong", { children: "Review order" })
20
+ ] }) : null,
21
+ /* @__PURE__ */ e("div", { className: `${r.backdrop} ${n ? r.openBackdrop : ""}`, onClick: c }),
22
+ /* @__PURE__ */ t("aside", { className: `${r.drawer} ${n ? r.openDrawer : ""}`, "aria-label": "Order cart", "aria-hidden": !n, inert: !n, children: [
23
+ /* @__PURE__ */ t("header", { className: r.header, children: [
24
+ /* @__PURE__ */ t("div", { children: [
25
+ /* @__PURE__ */ e("p", { children: "Order draft" }),
26
+ /* @__PURE__ */ e("h2", { children: "Your cart" })
27
+ ] }),
28
+ /* @__PURE__ */ e("button", { className: r.iconButton, type: "button", onClick: c, "aria-label": "Close cart", children: /* @__PURE__ */ e(B, { "aria-hidden": "true" }) })
29
+ ] }),
30
+ o.length ? /* @__PURE__ */ t(m, { children: [
31
+ /* @__PURE__ */ e("div", { className: r.items, children: o.map((a) => /* @__PURE__ */ t("article", { className: r.item, children: [
32
+ /* @__PURE__ */ t("div", { children: [
33
+ /* @__PURE__ */ e("h3", { children: a.itemName }),
34
+ a.variationName ? /* @__PURE__ */ e("p", { children: W(a.variationName) }) : null,
35
+ a.removedIngredients?.length ? /* @__PURE__ */ t("p", { children: [
36
+ "No: ",
37
+ a.removedIngredients.join(", ")
38
+ ] }) : null
39
+ ] }),
40
+ /* @__PURE__ */ t("div", { className: r.quantityControls, children: [
41
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => u(a.key), "aria-label": `Remove one ${a.itemName}`, children: /* @__PURE__ */ e(I, { "aria-hidden": "true" }) }),
42
+ /* @__PURE__ */ e("span", { children: a.quantity }),
43
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => p(a), "aria-label": `Add one ${a.itemName}`, children: /* @__PURE__ */ e(w, { "aria-hidden": "true" }) })
44
+ ] }),
45
+ /* @__PURE__ */ e(
46
+ "input",
47
+ {
48
+ className: r.noteInput,
49
+ "aria-label": `Note for ${a.itemName}`,
50
+ value: a.note ?? "",
51
+ onChange: (y) => b(a.key, y.target.value),
52
+ placeholder: "Note for the kitchen (optional) — e.g. no pickles"
53
+ }
54
+ ),
55
+ /* @__PURE__ */ t("div", { className: r.itemActions, children: [
56
+ d ? /* @__PURE__ */ e("button", { className: r.removeButton, type: "button", onClick: () => d(a), children: "Edit" }) : null,
57
+ /* @__PURE__ */ e("button", { className: r.removeButton, type: "button", onClick: () => N(a.key), children: "Remove" })
58
+ ] })
59
+ ] }, a.key)) }),
60
+ /* @__PURE__ */ t("footer", { className: r.footer, children: [
61
+ /* @__PURE__ */ e("label", { className: r.orderNoteLabel, htmlFor: "cart-order-note", children: "Order note" }),
62
+ /* @__PURE__ */ e(
63
+ "textarea",
64
+ {
65
+ className: r.orderNote,
66
+ id: "cart-order-note",
67
+ value: i,
68
+ onChange: (a) => v(a.target.value),
69
+ placeholder: "Anything about the whole order — delivery instructions, extra sauce…"
70
+ }
71
+ ),
72
+ /* @__PURE__ */ e("button", { className: r.clearButton, type: "button", onClick: C, children: "Clear cart" }),
73
+ /* @__PURE__ */ t("a", { className: r.whatsappButton, href: g, target: "_blank", rel: "noreferrer", children: [
74
+ "Send order on WhatsApp",
75
+ /* @__PURE__ */ e($, { className: r.whatsappIcon })
76
+ ] })
77
+ ] })
78
+ ] }) : /* @__PURE__ */ t("div", { className: r.empty, children: [
79
+ /* @__PURE__ */ e("h3", { children: "No items yet." }),
80
+ /* @__PURE__ */ e("p", { children: "Add something from the menu to draft a WhatsApp order." })
81
+ ] })
82
+ ] })
83
+ ] });
84
+ }
85
+ export {
86
+ L as CartDrawer
87
+ };
@@ -0,0 +1,47 @@
1
+ const t = "_cartBar_li06l_1", o = "_cartBarIcon_li06l_21", n = "_backdrop_li06l_39", e = "_openBackdrop_li06l_49", _ = "_drawer_li06l_54", r = "_openDrawer_li06l_74", l = "_header_li06l_78", a = "_iconButton_li06l_100", c = "_items_li06l_118", s = "_item_li06l_118", i = "_quantityControls_li06l_151", p = "_itemActions_li06l_161", B = "_removeButton_li06l_188", d = "_footer_li06l_200", u = "_clearButton_li06l_205", m = "_whatsappButton_li06l_219", w = "_whatsappIcon_li06l_232", h = "_empty_li06l_238", I = "_noteInput_li06l_285", y = "_orderNote_li06l_286", b = "_orderNoteLabel_li06l_309", k = {
2
+ cartBar: t,
3
+ cartBarIcon: o,
4
+ backdrop: n,
5
+ openBackdrop: e,
6
+ drawer: _,
7
+ openDrawer: r,
8
+ header: l,
9
+ iconButton: a,
10
+ items: c,
11
+ item: s,
12
+ quantityControls: i,
13
+ itemActions: p,
14
+ removeButton: B,
15
+ footer: d,
16
+ clearButton: u,
17
+ whatsappButton: m,
18
+ whatsappIcon: w,
19
+ empty: h,
20
+ noteInput: I,
21
+ orderNote: y,
22
+ orderNoteLabel: b
23
+ };
24
+ export {
25
+ n as backdrop,
26
+ t as cartBar,
27
+ o as cartBarIcon,
28
+ u as clearButton,
29
+ k as default,
30
+ _ as drawer,
31
+ h as empty,
32
+ d as footer,
33
+ l as header,
34
+ a as iconButton,
35
+ s as item,
36
+ p as itemActions,
37
+ c as items,
38
+ I as noteInput,
39
+ e as openBackdrop,
40
+ r as openDrawer,
41
+ y as orderNote,
42
+ b as orderNoteLabel,
43
+ i as quantityControls,
44
+ B as removeButton,
45
+ m as whatsappButton,
46
+ w as whatsappIcon
47
+ };
@@ -0,0 +1,31 @@
1
+ import { jsx as c } from "react/jsx-runtime";
2
+ import { useReducer as y, useMemo as p } from "react";
3
+ import { CartContext as u } from "./cartContext.js";
4
+ import { cartSelectionKey as s } from "./cartOrder.js";
5
+ import { cartReducer as d, initialCartState as l } from "./cartReducer.js";
6
+ function x({ children: n }) {
7
+ const [o, r] = y(d, l), i = p(() => {
8
+ const m = (e) => {
9
+ const t = s(e);
10
+ return o.items.find((a) => a.key === t)?.quantity ?? 0;
11
+ };
12
+ return {
13
+ ...o,
14
+ itemCount: o.items.reduce((e, t) => e + t.quantity, 0),
15
+ addItem: (e, t) => r({ type: "add", selection: e, quantity: t?.quantity, note: t?.note }),
16
+ replaceItem: (e, t, a) => r({ type: "replace", key: e, selection: t, quantity: a?.quantity, note: a?.note }),
17
+ decreaseItem: (e) => r({ type: "decrease", key: e }),
18
+ removeItem: (e) => r({ type: "remove", key: e }),
19
+ getQuantity: m,
20
+ setItemNote: (e, t) => r({ type: "set-note", key: e, note: t }),
21
+ setOrderNote: (e) => r({ type: "set-order-note", note: e }),
22
+ openCart: () => r({ type: "open" }),
23
+ closeCart: () => r({ type: "close" }),
24
+ clearCart: () => r({ type: "clear" })
25
+ };
26
+ }, [o]);
27
+ return /* @__PURE__ */ c(u.Provider, { value: i, children: n });
28
+ }
29
+ export {
30
+ x as CartProvider
31
+ };
@@ -0,0 +1,5 @@
1
+ import { createContext as t } from "react";
2
+ const e = t(null);
3
+ export {
4
+ e as CartContext
5
+ };
@@ -0,0 +1,34 @@
1
+ function u(e) {
2
+ const t = (e.removedIngredients ?? []).map((r) => r.trim().toLowerCase()).filter(Boolean).sort().join(",");
3
+ return [e.itemId, e.variationId ?? "default", ...t ? [`no=${t}`] : []].join(":");
4
+ }
5
+ function a(e) {
6
+ return e ? e.charAt(0).toUpperCase() + e.slice(1) : "";
7
+ }
8
+ function s(e) {
9
+ const t = a(e.variationName), r = (e.removedIngredients ?? []).filter((i) => i.trim()), n = e.note?.trim(), o = [`${e.quantity}x ${e.itemName}${t ? ` - ${t}` : ""}`];
10
+ return r.length && o.push(` No: ${r.join(", ")}`), n && o.push(` Note: ${n}`), o;
11
+ }
12
+ function d(e, t, r) {
13
+ const n = t.trim();
14
+ return [
15
+ r.orderGreeting,
16
+ "",
17
+ ...e.flatMap(s),
18
+ ...n ? ["", `Order note: ${n}`] : [],
19
+ "",
20
+ "",
21
+ "Name:",
22
+ "Address:"
23
+ ].join(`
24
+ `);
25
+ }
26
+ function m(e, t, r) {
27
+ return `https://wa.me/${r.whatsappNumber}?text=${encodeURIComponent(d(e, t, r))}`;
28
+ }
29
+ export {
30
+ d as buildWhatsAppMessage,
31
+ m as buildWhatsAppOrderUrl,
32
+ u as cartSelectionKey,
33
+ a as formatVariationName
34
+ };
@@ -0,0 +1,57 @@
1
+ import { cartSelectionKey as d } from "./cartOrder.js";
2
+ const l = { items: [], isOpen: !1, orderNote: "" };
3
+ function k(e, n) {
4
+ switch (n.type) {
5
+ case "add": {
6
+ const r = d(n.selection), o = Math.max(1, Math.round(n.quantity ?? 1)), i = n.note?.trim() ? n.note : void 0;
7
+ return e.items.find((s) => s.key === r) ? {
8
+ ...e,
9
+ items: e.items.map((s) => s.key === r ? { ...s, quantity: s.quantity + o, note: i ?? s.note } : s)
10
+ } : {
11
+ ...e,
12
+ items: [...e.items, { ...n.selection, key: r, quantity: o, ...i ? { note: i } : {} }]
13
+ };
14
+ }
15
+ case "replace": {
16
+ const r = e.items.findIndex((t) => t.key === n.key);
17
+ if (r === -1) return e;
18
+ const o = d(n.selection), i = Math.max(1, Math.round(n.quantity ?? 1)), u = n.note?.trim() ? n.note : void 0, s = n.note === void 0;
19
+ if (e.items.some((t, y) => t.key === o && y !== r))
20
+ return {
21
+ ...e,
22
+ items: e.items.filter((t, y) => y !== r).map((t) => t.key === o ? { ...t, quantity: t.quantity + i, note: s ? t.note : u } : t)
23
+ };
24
+ const c = [...e.items];
25
+ return c[r] = { ...n.selection, key: o, quantity: i, ...u ? { note: u } : {} }, { ...e, items: c };
26
+ }
27
+ case "decrease":
28
+ return {
29
+ ...e,
30
+ items: e.items.flatMap((r) => r.key !== n.key ? [r] : r.quantity <= 1 ? [] : [{ ...r, quantity: r.quantity - 1 }])
31
+ };
32
+ case "remove":
33
+ return {
34
+ ...e,
35
+ items: e.items.filter((r) => r.key !== n.key)
36
+ };
37
+ case "set-note":
38
+ return {
39
+ ...e,
40
+ items: e.items.map((r) => r.key === n.key ? { ...r, note: n.note } : r)
41
+ };
42
+ case "set-order-note":
43
+ return { ...e, orderNote: n.note };
44
+ case "open":
45
+ return { ...e, isOpen: !0 };
46
+ case "close":
47
+ return { ...e, isOpen: !1 };
48
+ case "clear":
49
+ return { ...e, items: [], isOpen: !1, orderNote: "" };
50
+ default:
51
+ return e;
52
+ }
53
+ }
54
+ export {
55
+ k as cartReducer,
56
+ l as initialCartState
57
+ };
@@ -0,0 +1,10 @@
1
+ import { useContext as r } from "react";
2
+ import { CartContext as e } from "./cartContext.js";
3
+ function i() {
4
+ const t = r(e);
5
+ if (!t) throw new Error("useCart must be used inside CartProvider");
6
+ return t;
7
+ }
8
+ export {
9
+ i as useCart
10
+ };
@@ -0,0 +1,16 @@
1
+ import { jsxs as s, jsx as r } from "react/jsx-runtime";
2
+ import a from "./Brand.module.css.js";
3
+ import { useSiteConfig as i } from "../config/siteConfigContext.js";
4
+ function c({ className: n }) {
5
+ const e = i();
6
+ return /* @__PURE__ */ s("a", { className: `${a.brand} ${n ?? ""}`, href: "#top", "aria-label": `${e.brandName} home`, children: [
7
+ /* @__PURE__ */ r("img", { className: a.logo, src: e.logoSrc, alt: "", "aria-hidden": "true" }),
8
+ /* @__PURE__ */ s("span", { className: a.copy, children: [
9
+ /* @__PURE__ */ r("span", { className: a.name, children: e.brandName }),
10
+ /* @__PURE__ */ r("span", { className: a.line, children: e.tagline })
11
+ ] })
12
+ ] });
13
+ }
14
+ export {
15
+ c as Brand
16
+ };
@@ -0,0 +1,15 @@
1
+ const o = "_brand_18lzz_1", n = "_logo_18lzz_8", _ = "_copy_18lzz_17", l = "_name_18lzz_22", z = "_line_18lzz_30", c = {
2
+ brand: o,
3
+ logo: n,
4
+ copy: _,
5
+ name: l,
6
+ line: z
7
+ };
8
+ export {
9
+ o as brand,
10
+ _ as copy,
11
+ c as default,
12
+ z as line,
13
+ n as logo,
14
+ l as name
15
+ };
@@ -0,0 +1,19 @@
1
+ import { jsxs as r, jsx as t } from "react/jsx-runtime";
2
+ import e from "./Footer.module.css.js";
3
+ import { useSiteConfig as s } from "../config/siteConfigContext.js";
4
+ function a() {
5
+ const o = s();
6
+ return /* @__PURE__ */ r("footer", { className: e.footer, children: [
7
+ /* @__PURE__ */ r("p", { className: e.legal, children: [
8
+ "© ",
9
+ (/* @__PURE__ */ new Date()).getFullYear(),
10
+ " ",
11
+ o.brandName,
12
+ ". All rights reserved."
13
+ ] }),
14
+ /* @__PURE__ */ t("p", { className: e.note, children: o.footerNote })
15
+ ] });
16
+ }
17
+ export {
18
+ a as Footer
19
+ };
@@ -0,0 +1,11 @@
1
+ const o = "_footer_km57l_1", t = "_legal_km57l_13", e = "_note_km57l_14", l = {
2
+ footer: o,
3
+ legal: t,
4
+ note: e
5
+ };
6
+ export {
7
+ l as default,
8
+ o as footer,
9
+ t as legal,
10
+ e as note
11
+ };
@@ -0,0 +1,34 @@
1
+ import { jsxs as i, jsx as r } from "react/jsx-runtime";
2
+ import { useState as s, useEffect as h } from "react";
3
+ import { Brand as p } from "./Brand.js";
4
+ import { OrderIcon as w } from "./OrderIcon.js";
5
+ import e from "./Header.module.css.js";
6
+ import { useSiteConfig as g } from "../config/siteConfigContext.js";
7
+ import { useOrdering as v } from "../config/useOrdering.js";
8
+ function $() {
9
+ const a = g(), t = v(), [l, c] = s(!1), [d, m] = s(!1);
10
+ return h(() => {
11
+ const n = () => {
12
+ const f = document.querySelector("#menu"), u = window.matchMedia("(max-width: 980px)").matches, o = f?.getBoundingClientRect();
13
+ c(window.scrollY > 24), m(!!(o && o.top <= (u ? 12 : 84) && o.bottom > 180));
14
+ };
15
+ return n(), window.addEventListener("scroll", n, { passive: !0 }), window.addEventListener("resize", n), () => {
16
+ window.removeEventListener("scroll", n), window.removeEventListener("resize", n);
17
+ };
18
+ }, []), /* @__PURE__ */ i("header", { className: `${e.header} ${l ? e.scrolled : ""} ${d ? e.menuHidden : ""}`, children: [
19
+ /* @__PURE__ */ r(p, {}),
20
+ /* @__PURE__ */ i("nav", { className: e.nav, "aria-label": "Primary navigation", children: [
21
+ /* @__PURE__ */ r("a", { href: "#menu", children: "Menu" }),
22
+ /* @__PURE__ */ r("a", { href: "#restaurant", children: "Restaurant" }),
23
+ /* @__PURE__ */ r("a", { href: "#locations", children: "Location" }),
24
+ /* @__PURE__ */ r("a", { href: a.instagramUrl, target: "_blank", rel: "noreferrer", children: "Instagram" })
25
+ ] }),
26
+ t.href ? /* @__PURE__ */ i("a", { className: `${e.pill} ${t.isWhatsApp ? "" : e.externalPill}`, href: t.href, target: "_blank", rel: "noreferrer", children: [
27
+ t.shortLabel,
28
+ /* @__PURE__ */ r(w, { className: e.pillIcon })
29
+ ] }) : null
30
+ ] });
31
+ }
32
+ export {
33
+ $ as Header
34
+ };
@@ -0,0 +1,19 @@
1
+ const l = "_header_1o00h_1", e = "_scrolled_1o00h_19", n = "_nav_1o00h_26", o = "_pill_1o00h_42", _ = "_externalPill_1o00h_62", c = "_pillIcon_1o00h_68", s = "_menuHidden_1o00h_75", t = {
2
+ header: l,
3
+ scrolled: e,
4
+ nav: n,
5
+ pill: o,
6
+ externalPill: _,
7
+ pillIcon: c,
8
+ menuHidden: s
9
+ };
10
+ export {
11
+ t as default,
12
+ _ as externalPill,
13
+ l as header,
14
+ s as menuHidden,
15
+ n as nav,
16
+ o as pill,
17
+ c as pillIcon,
18
+ e as scrolled
19
+ };
@@ -0,0 +1,23 @@
1
+ import { jsx as e, jsxs as o } from "react/jsx-runtime";
2
+ import { OrderIcon as t } from "./OrderIcon.js";
3
+ import r from "./Hero.module.css.js";
4
+ import { useSiteConfig as l } from "../config/siteConfigContext.js";
5
+ import { useOrdering as s } from "../config/useOrdering.js";
6
+ function b() {
7
+ const i = l(), n = s();
8
+ return /* @__PURE__ */ e("section", { className: r.hero, "aria-labelledby": "hero-title", children: /* @__PURE__ */ o("div", { className: r.copy, children: [
9
+ /* @__PURE__ */ e("p", { className: "micro-label", children: i.heroEyebrow }),
10
+ /* @__PURE__ */ o("h1", { id: "hero-title", className: r.title, children: [
11
+ /* @__PURE__ */ e("strong", { children: i.brandName }),
12
+ /* @__PURE__ */ e("span", { children: i.tagline })
13
+ ] }),
14
+ /* @__PURE__ */ e("p", { className: r.subline, children: i.heroSubline }),
15
+ /* @__PURE__ */ e("div", { className: r.actions, children: n.href ? /* @__PURE__ */ o("a", { className: `button ${n.isWhatsApp ? "button-whatsapp" : "button-order"}`, href: n.href, target: "_blank", rel: "noreferrer", children: [
16
+ n.label,
17
+ /* @__PURE__ */ e(t, { className: "button-icon" })
18
+ ] }) : /* @__PURE__ */ e("a", { className: "button button-fill", href: "#menu", children: "View menu" }) })
19
+ ] }) });
20
+ }
21
+ export {
22
+ b as Hero
23
+ };
@@ -0,0 +1,15 @@
1
+ const o = "_hero_1fwou_1", t = "_copy_1fwou_33", s = "_title_1fwou_40", _ = "_subline_1fwou_64", c = "_actions_1fwou_72", e = {
2
+ hero: o,
3
+ copy: t,
4
+ title: s,
5
+ subline: _,
6
+ actions: c
7
+ };
8
+ export {
9
+ c as actions,
10
+ t as copy,
11
+ e as default,
12
+ o as hero,
13
+ _ as subline,
14
+ s as title
15
+ };