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,27 @@
1
+ import { jsxs as t, jsx as r } from "react/jsx-runtime";
2
+ function i({ className: e }) {
3
+ return /* @__PURE__ */ t(
4
+ "svg",
5
+ {
6
+ className: e,
7
+ width: "24",
8
+ height: "24",
9
+ viewBox: "0 0 24 24",
10
+ fill: "none",
11
+ stroke: "currentColor",
12
+ strokeWidth: "2",
13
+ strokeLinecap: "round",
14
+ strokeLinejoin: "round",
15
+ "aria-hidden": "true",
16
+ focusable: "false",
17
+ children: [
18
+ /* @__PURE__ */ r("rect", { width: "20", height: "20", x: "2", y: "2", rx: "5", ry: "5" }),
19
+ /* @__PURE__ */ r("path", { d: "M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z" }),
20
+ /* @__PURE__ */ r("line", { x1: "17.5", x2: "17.51", y1: "6.5", y2: "6.5" })
21
+ ]
22
+ }
23
+ );
24
+ }
25
+ export {
26
+ i as InstagramIcon
27
+ };
@@ -0,0 +1,34 @@
1
+ import { jsx as e, jsxs as l } from "react/jsx-runtime";
2
+ import { InstagramIcon as n } from "./InstagramIcon.js";
3
+ import r from "./LocationsSection.module.css.js";
4
+ import { useSiteConfig as i } from "../config/siteConfigContext.js";
5
+ function h() {
6
+ const a = i();
7
+ return /* @__PURE__ */ e("section", { className: `section ${r.locations}`, id: "locations", "aria-labelledby": "locations-title", children: /* @__PURE__ */ l("div", { className: "section-inner", children: [
8
+ /* @__PURE__ */ e("p", { className: "micro-label", children: "Location" }),
9
+ /* @__PURE__ */ e("h2", { id: "locations-title", children: "Come Find Us!" }),
10
+ /* @__PURE__ */ l("div", { className: r.row, children: [
11
+ /* @__PURE__ */ l("a", { className: r.card, href: a.mapsUrl, target: "_blank", rel: "noreferrer", children: [
12
+ /* @__PURE__ */ e("h3", { children: a.locationLabel }),
13
+ /* @__PURE__ */ e("p", { children: a.address }),
14
+ /* @__PURE__ */ e("strong", { children: "Open Maps" })
15
+ ] }),
16
+ /* @__PURE__ */ l("article", { className: r.card, children: [
17
+ /* @__PURE__ */ e("span", { children: "TEL" }),
18
+ /* @__PURE__ */ e("h3", { children: a.whatsappNumber ? "Call or WhatsApp" : "Call us" }),
19
+ /* @__PURE__ */ l("div", { className: r.linkStack, children: [
20
+ /* @__PURE__ */ e("a", { href: a.phoneHref, children: a.phoneDisplay }),
21
+ a.whatsappNumber ? /* @__PURE__ */ e("a", { href: `https://wa.me/${a.whatsappNumber}`, target: "_blank", rel: "noreferrer", children: "Message on WhatsApp" }) : null
22
+ ] })
23
+ ] }),
24
+ /* @__PURE__ */ l("article", { className: `${r.card} ${r.accent}`, children: [
25
+ /* @__PURE__ */ e(n, { className: r.socialIcon }),
26
+ /* @__PURE__ */ e("h3", { children: a.instagramHandle }),
27
+ /* @__PURE__ */ e("a", { href: a.instagramUrl, target: "_blank", rel: "noreferrer", children: "Open Instagram" })
28
+ ] })
29
+ ] })
30
+ ] }) });
31
+ }
32
+ export {
33
+ h as LocationsSection
34
+ };
@@ -0,0 +1,17 @@
1
+ const c = "_locations_106nk_1", n = "_row_106nk_14", o = "_card_106nk_23", t = "_socialIcon_106nk_57", _ = "_linkStack_106nk_100", a = "_accent_106nk_105", s = {
2
+ locations: c,
3
+ row: n,
4
+ card: o,
5
+ socialIcon: t,
6
+ linkStack: _,
7
+ accent: a
8
+ };
9
+ export {
10
+ a as accent,
11
+ o as card,
12
+ s as default,
13
+ _ as linkStack,
14
+ c as locations,
15
+ n as row,
16
+ t as socialIcon
17
+ };
@@ -0,0 +1,10 @@
1
+ import { jsx as e } from "react/jsx-runtime";
2
+ import s from "./Marquee.module.css.js";
3
+ import { useSiteConfig as t } from "../config/siteConfigContext.js";
4
+ function c() {
5
+ const { highlights: r } = t();
6
+ return /* @__PURE__ */ e("section", { className: s.marquee, "aria-label": "Menu highlights", children: /* @__PURE__ */ e("div", { className: s.track, children: [...r, ...r].map((i, a) => /* @__PURE__ */ e("span", { children: i }, `${i}-${a}`)) }) });
7
+ }
8
+ export {
9
+ c as Marquee
10
+ };
@@ -0,0 +1,9 @@
1
+ const a = "_marquee_zbaww_1", e = "_track_zbaww_8", t = {
2
+ marquee: a,
3
+ track: e
4
+ };
5
+ export {
6
+ t as default,
7
+ a as marquee,
8
+ e as track
9
+ };
@@ -0,0 +1,11 @@
1
+ import { jsx as o } from "react/jsx-runtime";
2
+ import { ArrowUpRight as t } from "lucide-react";
3
+ import { useOrdering as n } from "../config/useOrdering.js";
4
+ import { WhatsAppIcon as e } from "./WhatsAppIcon.js";
5
+ function m({ className: r }) {
6
+ const i = n();
7
+ return i.isWhatsApp ? /* @__PURE__ */ o(e, { className: r }) : i.iconSrc ? /* @__PURE__ */ o("img", { className: r, src: i.iconSrc, alt: "", "aria-hidden": "true" }) : /* @__PURE__ */ o(t, { className: r, "aria-hidden": "true" });
8
+ }
9
+ export {
10
+ m as OrderIcon
11
+ };
@@ -0,0 +1,32 @@
1
+ import { jsx as a, jsxs as r } from "react/jsx-runtime";
2
+ import i from "./RatingSection.module.css.js";
3
+ import { useSiteConfig as l } from "../config/siteConfigContext.js";
4
+ const n = 5;
5
+ function c(e) {
6
+ const t = Number(e), s = Number.isFinite(t) ? Math.min(n, Math.max(0, Math.round(t))) : n;
7
+ return "★".repeat(s) + "☆".repeat(n - s);
8
+ }
9
+ function h() {
10
+ const e = l();
11
+ return /* @__PURE__ */ a("section", { className: `section ${i.restaurant}`, id: "restaurant", "aria-labelledby": "restaurant-title", children: /* @__PURE__ */ r("div", { className: `section-inner ${i.grid}`, children: [
12
+ /* @__PURE__ */ r("div", { children: [
13
+ /* @__PURE__ */ a("p", { className: "micro-label", children: "Customer rated" }),
14
+ /* @__PURE__ */ a("h2", { id: "restaurant-title", children: e.ratingHeadline })
15
+ ] }),
16
+ /* @__PURE__ */ r("div", { className: i.panel, "aria-label": "Customer rating", children: [
17
+ /* @__PURE__ */ r("div", { className: i.score, children: [
18
+ /* @__PURE__ */ a("span", { children: e.rating }),
19
+ /* @__PURE__ */ a("div", { className: i.stars, "aria-label": e.ratingLabel, children: c(e.rating) })
20
+ ] }),
21
+ /* @__PURE__ */ a("p", { className: i.copy, children: e.ratingCopy }),
22
+ /* @__PURE__ */ r("div", { className: i.meta, children: [
23
+ /* @__PURE__ */ a("span", { children: e.reviewCount }),
24
+ /* @__PURE__ */ a("span", { children: e.cuisineSummary }),
25
+ /* @__PURE__ */ a("span", { children: e.localBadge })
26
+ ] })
27
+ ] })
28
+ ] }) });
29
+ }
30
+ export {
31
+ h as RatingSection
32
+ };
@@ -0,0 +1,19 @@
1
+ const t = "_restaurant_fztj2_1", s = "_grid_fztj2_7", _ = "_panel_fztj2_14", a = "_score_fztj2_27", r = "_stars_fztj2_41", e = "_copy_fztj2_49", o = "_meta_fztj2_56", c = {
2
+ restaurant: t,
3
+ grid: s,
4
+ panel: _,
5
+ score: a,
6
+ stars: r,
7
+ copy: e,
8
+ meta: o
9
+ };
10
+ export {
11
+ e as copy,
12
+ c as default,
13
+ s as grid,
14
+ o as meta,
15
+ _ as panel,
16
+ t as restaurant,
17
+ a as score,
18
+ r as stars
19
+ };
@@ -0,0 +1,73 @@
1
+ import { jsxs as i, jsx as e } from "react/jsx-runtime";
2
+ import { ArrowLeft as p, ArrowRight as w, ArrowUpRight as v } from "lucide-react";
3
+ import { useRef as N, useState as y, useCallback as k, useEffect as C } from "react";
4
+ import { useSiteConfig as S } from "../config/siteConfigContext.js";
5
+ import { imageSrc as x } from "../menu-browser/menuItemDisplay.js";
6
+ import { useMenuData as $ } from "../menu/useMenuData.js";
7
+ import { visibleTestimonials as R, clampRating as b } from "../testimonials/testimonialMutations.js";
8
+ import a from "./TestimonialsSection.module.css.js";
9
+ const f = 5;
10
+ function L({ testimonial: r }) {
11
+ const s = b(r.rating), n = r.author.trim() || "Google reviewer";
12
+ return /* @__PURE__ */ i("li", { className: `${a.card} ${r.image ? "" : a.textOnly}`, children: [
13
+ r.image ? /* @__PURE__ */ e("div", { className: a.media, children: /* @__PURE__ */ e("img", { src: x(r.image), alt: `Photo shared by ${n}`, loading: "lazy" }) }) : null,
14
+ /* @__PURE__ */ i("figure", { className: a.body, children: [
15
+ /* @__PURE__ */ i("div", { className: a.stars, role: "img", "aria-label": `${s} out of ${f} stars`, children: [
16
+ "★".repeat(s),
17
+ /* @__PURE__ */ e("span", { "aria-hidden": "true", children: "★".repeat(f - s) })
18
+ ] }),
19
+ /* @__PURE__ */ e("blockquote", { className: a.quote, children: r.text.trim() }),
20
+ /* @__PURE__ */ i("figcaption", { className: a.byline, children: [
21
+ /* @__PURE__ */ e("span", { className: a.author, children: n }),
22
+ r.sourceUrl ? /* @__PURE__ */ i("a", { href: r.sourceUrl, target: "_blank", rel: "noreferrer", children: [
23
+ "Read on Google",
24
+ /* @__PURE__ */ e(v, { "aria-hidden": "true" })
25
+ ] }) : null
26
+ ] })
27
+ ] })
28
+ ] });
29
+ }
30
+ function B() {
31
+ const r = S(), { menu: s } = $(), n = R(s), u = N(null), [m, g] = y({ back: !1, forward: !1 }), o = k(() => {
32
+ const t = u.current;
33
+ if (!t) return;
34
+ const l = t.scrollLeft > 4, c = t.scrollLeft + t.clientWidth < t.scrollWidth - 4;
35
+ g((d) => d.back === l && d.forward === c ? d : { back: l, forward: c });
36
+ }, []);
37
+ C(() => (o(), window.addEventListener("resize", o), () => window.removeEventListener("resize", o)), [o, n.length]);
38
+ const h = (t) => {
39
+ const l = u.current, c = l?.querySelector("li");
40
+ if (!l || !c) return;
41
+ const d = Number.parseFloat(getComputedStyle(l).columnGap) || 0;
42
+ l.scrollBy({ left: t * (c.getBoundingClientRect().width + d), behavior: "smooth" });
43
+ };
44
+ return n.length ? (
45
+ // Takes over the rating section's slot and anchor (`#restaurant`, linked from the header nav).
46
+ /* @__PURE__ */ i("section", { className: `section ${a.testimonials}`, id: "restaurant", "aria-labelledby": "reviews-title", children: [
47
+ /* @__PURE__ */ i("div", { className: `section-inner ${a.heading}`, children: [
48
+ /* @__PURE__ */ i("div", { children: [
49
+ r.testimonialsEyebrow ? /* @__PURE__ */ e("p", { className: "micro-label", children: r.testimonialsEyebrow }) : null,
50
+ /* @__PURE__ */ e("h2", { id: "reviews-title", children: r.testimonialsHeadline ?? "Our customers' cameras don't lie." })
51
+ ] }),
52
+ /* @__PURE__ */ i("div", { className: a.score, "aria-label": "Customer rating", children: [
53
+ /* @__PURE__ */ e("span", { className: a.scoreValue, children: r.rating }),
54
+ /* @__PURE__ */ i("div", { children: [
55
+ /* @__PURE__ */ e("div", { className: a.scoreStars, role: "img", "aria-label": r.ratingLabel, children: "★".repeat(b(Number(r.rating))) }),
56
+ /* @__PURE__ */ e("p", { children: r.reviewCount })
57
+ ] })
58
+ ] })
59
+ ] }),
60
+ /* @__PURE__ */ i("div", { className: `section-inner ${a.subhead}`, children: [
61
+ /* @__PURE__ */ e("p", { children: r.ratingCopy }),
62
+ m.back || m.forward ? /* @__PURE__ */ i("div", { className: a.arrows, children: [
63
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => h(-1), disabled: !m.back, "aria-label": "Previous reviews", children: /* @__PURE__ */ e(p, { "aria-hidden": "true" }) }),
64
+ /* @__PURE__ */ e("button", { type: "button", onClick: () => h(1), disabled: !m.forward, "aria-label": "Next reviews", children: /* @__PURE__ */ e(w, { "aria-hidden": "true" }) })
65
+ ] }) : null
66
+ ] }),
67
+ /* @__PURE__ */ e("ul", { className: a.track, "aria-label": "Customer reviews", tabIndex: 0, ref: u, onScroll: o, children: n.map((t) => /* @__PURE__ */ e(L, { testimonial: t }, t.id)) })
68
+ ] })
69
+ ) : null;
70
+ }
71
+ export {
72
+ B as TestimonialsSection
73
+ };
@@ -0,0 +1,37 @@
1
+ const s = "_testimonials_6f34y_1", _ = "_heading_6f34y_13", t = "_score_6f34y_27", o = "_scoreValue_6f34y_40", e = "_scoreStars_6f34y_47", a = "_subhead_6f34y_63", c = "_arrows_6f34y_79", r = "_track_6f34y_114", n = "_card_6f34y_134", y = "_media_6f34y_148", f = "_body_6f34y_159", d = "_stars_6f34y_168", i = "_quote_6f34y_179", l = "_textOnly_6f34y_192", u = "_byline_6f34y_203", b = "_author_6f34y_214", h = {
2
+ testimonials: s,
3
+ heading: _,
4
+ score: t,
5
+ scoreValue: o,
6
+ scoreStars: e,
7
+ subhead: a,
8
+ arrows: c,
9
+ track: r,
10
+ card: n,
11
+ media: y,
12
+ body: f,
13
+ stars: d,
14
+ quote: i,
15
+ textOnly: l,
16
+ byline: u,
17
+ author: b
18
+ };
19
+ export {
20
+ c as arrows,
21
+ b as author,
22
+ f as body,
23
+ u as byline,
24
+ n as card,
25
+ h as default,
26
+ _ as heading,
27
+ y as media,
28
+ i as quote,
29
+ t as score,
30
+ e as scoreStars,
31
+ o as scoreValue,
32
+ d as stars,
33
+ a as subhead,
34
+ s as testimonials,
35
+ l as textOnly,
36
+ r as track
37
+ };
@@ -0,0 +1,13 @@
1
+ import { jsx as a } from "react/jsx-runtime";
2
+ function e({ className: r }) {
3
+ return /* @__PURE__ */ a("svg", { className: r, viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ a(
4
+ "path",
5
+ {
6
+ fill: "currentColor",
7
+ d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 0 0-3.48-8.413Z"
8
+ }
9
+ ) });
10
+ }
11
+ export {
12
+ e as WhatsAppIcon
13
+ };
@@ -0,0 +1,26 @@
1
+ import { jsx as p } from "react/jsx-runtime";
2
+ import { useMemo as s } from "react";
3
+ import { shouldUseLocalMenuApi as f, createLocalMenuApiClient as U } from "../menu/localMenuApi.js";
4
+ import { createMenuApiClient as d } from "../menu/menuApi.js";
5
+ import { menuDraftStorageKey as A } from "../menu/menuDraftStorage.js";
6
+ import { SiteConfigContext as C } from "./siteConfigContext.js";
7
+ function M(r, t) {
8
+ return r ? `${r.replace(/\/+$/, "")}/${t.replace(/^\/+/, "")}` : t;
9
+ }
10
+ function $({ config: r, seedMenu: t, api: e, menuApi: a, children: l }) {
11
+ const o = e?.baseUrl?.trim() ?? "", i = e?.publicMenuUrl?.trim() || M(o, "/api/menu"), n = e?.mode ?? "auto", c = s(() => {
12
+ const u = A(r.restaurantId), m = n === "local" || n === "auto" && f(o);
13
+ return {
14
+ config: r,
15
+ seedMenu: t,
16
+ draftStorageKey: u,
17
+ publicMenuUrl: i,
18
+ isLocalApi: m,
19
+ menuApi: a ?? (m ? U(t, u) : d({ apiBaseUrl: o, publicMenuUrl: i }))
20
+ };
21
+ }, [o, r, a, n, i, t]);
22
+ return /* @__PURE__ */ p(C.Provider, { value: c, children: l });
23
+ }
24
+ export {
25
+ $ as RestaurantKitProvider
26
+ };
@@ -0,0 +1,23 @@
1
+ import { useContext as n, createContext as u } from "react";
2
+ const r = u(null);
3
+ function e() {
4
+ const t = n(r);
5
+ if (!t) throw new Error("restaurant-kit hooks must be used inside <RestaurantKitProvider> (or <RestaurantSite>).");
6
+ return t;
7
+ }
8
+ function i() {
9
+ return e().config;
10
+ }
11
+ function s() {
12
+ return e().seedMenu;
13
+ }
14
+ function a() {
15
+ return e().menuApi;
16
+ }
17
+ export {
18
+ r as SiteConfigContext,
19
+ a as useMenuApi,
20
+ e as useRestaurantKit,
21
+ s as useSeedMenu,
22
+ i as useSiteConfig
23
+ };
@@ -0,0 +1,12 @@
1
+ import { useContext as o } from "react";
2
+ import { SiteConfigContext as n } from "./siteConfigContext.js";
3
+ function i() {
4
+ const e = o(n)?.config, r = e?.ordering;
5
+ if (r?.mode === "external")
6
+ return { cartEnabled: !1, isWhatsApp: !1, href: r.url, label: r.label, shortLabel: r.shortLabel ?? "Order", iconSrc: r.iconSrc };
7
+ const t = e?.whatsappNumber ?? "";
8
+ return { cartEnabled: !0, isWhatsApp: !0, href: t ? `https://wa.me/${t}` : "", label: "Order on WhatsApp", shortLabel: "Order" };
9
+ }
10
+ export {
11
+ i as useOrdering
12
+ };
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ /* empty css */
2
+ import { RestaurantSite as t } from "./RestaurantSiteRoutes.js";
3
+ import { RestaurantKitProvider as m } from "./config/RestaurantKitProvider.js";
4
+ import { useMenuApi as p, useRestaurantKit as i, useSeedMenu as f, useSiteConfig as u } from "./config/siteConfigContext.js";
5
+ import { useOrdering as s } from "./config/useOrdering.js";
6
+ import { MenuSite as M } from "./pages/MenuSite.js";
7
+ import { AdminMenuEditor as l } from "./pages/AdminMenuEditor.js";
8
+ import { AdminPreview as D } from "./pages/AdminPreview.js";
9
+ import { Brand as C } from "./components/Brand.js";
10
+ import { Footer as g } from "./components/Footer.js";
11
+ import { Header as v } from "./components/Header.js";
12
+ import { Hero as h } from "./components/Hero.js";
13
+ import { LocationsSection as K } from "./components/LocationsSection.js";
14
+ import { Marquee as L } from "./components/Marquee.js";
15
+ import { RatingSection as V } from "./components/RatingSection.js";
16
+ import { TestimonialsSection as b } from "./components/TestimonialsSection.js";
17
+ import { InstagramIcon as E } from "./components/InstagramIcon.js";
18
+ import { WhatsAppIcon as H } from "./components/WhatsAppIcon.js";
19
+ import { MenuBrowser as U } from "./menu-browser/MenuBrowser.js";
20
+ import { MenuSection as q } from "./menu-browser/MenuSection.js";
21
+ import { MenuCartDrawer as k } from "./menu-browser/MenuCartDrawer.js";
22
+ import { CartProvider as G } from "./cart/CartProvider.js";
23
+ import { useCart as Q } from "./cart/useCart.js";
24
+ import { buildWhatsAppMessage as Y, buildWhatsAppOrderUrl as Z, cartSelectionKey as _, formatVariationName as $ } from "./cart/cartOrder.js";
25
+ import { MenuDataProvider as er, useMenuData as or } from "./menu/useMenuData.js";
26
+ import { MenuApiError as nr, createMenuApiClient as mr } from "./menu/menuApi.js";
27
+ import { createLocalMenuApiClient as pr, shouldUseLocalMenuApi as ir } from "./menu/localMenuApi.js";
28
+ import { clearMenuDraft as ur, menuDraftStorageKey as xr, readMenuDraft as sr, writeMenuDraft as dr } from "./menu/menuDraftStorage.js";
29
+ import { menuIconOptions as cr, menuIcons as lr } from "./menu/menuIcons.js";
30
+ import { formatPrice as Dr, formatPriceTotal as Sr, priceFromInput as Cr, priceInputValue as Ir } from "./menu/priceFormat.js";
31
+ import { menuIconIds as Pr } from "./menu/menuSchema.js";
32
+ import { assertValidMenu as Rr, validateMenu as hr } from "./menu/validateMenu.js";
33
+ import { useAdminMenuDraft as Kr } from "./admin/useAdminMenuDraft.js";
34
+ import { AdminDraftProvider as Lr } from "./admin/AdminDraftProvider.js";
35
+ import { useAdminDraft as Vr, useAdminDraftContext as Wr } from "./admin/useAdminDraft.js";
36
+ import { applyAdminMenuCommand as Br, getAdminMenuCommandRejection as Er } from "./admin/adminMenuCommands.js";
37
+ export {
38
+ Lr as AdminDraftProvider,
39
+ l as AdminMenuEditor,
40
+ D as AdminPreview,
41
+ C as Brand,
42
+ G as CartProvider,
43
+ g as Footer,
44
+ v as Header,
45
+ h as Hero,
46
+ E as InstagramIcon,
47
+ K as LocationsSection,
48
+ L as Marquee,
49
+ nr as MenuApiError,
50
+ U as MenuBrowser,
51
+ k as MenuCartDrawer,
52
+ er as MenuDataProvider,
53
+ q as MenuSection,
54
+ M as MenuSite,
55
+ V as RatingSection,
56
+ m as RestaurantKitProvider,
57
+ t as RestaurantSite,
58
+ b as TestimonialsSection,
59
+ H as WhatsAppIcon,
60
+ Br as applyAdminMenuCommand,
61
+ Rr as assertValidMenu,
62
+ Y as buildWhatsAppMessage,
63
+ Z as buildWhatsAppOrderUrl,
64
+ _ as cartSelectionKey,
65
+ ur as clearMenuDraft,
66
+ pr as createLocalMenuApiClient,
67
+ mr as createMenuApiClient,
68
+ Dr as formatPrice,
69
+ Sr as formatPriceTotal,
70
+ $ as formatVariationName,
71
+ Er as getAdminMenuCommandRejection,
72
+ xr as menuDraftStorageKey,
73
+ Pr as menuIconIds,
74
+ cr as menuIconOptions,
75
+ lr as menuIcons,
76
+ Cr as priceFromInput,
77
+ Ir as priceInputValue,
78
+ sr as readMenuDraft,
79
+ ir as shouldUseLocalMenuApi,
80
+ Vr as useAdminDraft,
81
+ Wr as useAdminDraftContext,
82
+ Kr as useAdminMenuDraft,
83
+ Q as useCart,
84
+ p as useMenuApi,
85
+ or as useMenuData,
86
+ s as useOrdering,
87
+ i as useRestaurantKit,
88
+ f as useSeedMenu,
89
+ u as useSiteConfig,
90
+ hr as validateMenu,
91
+ dr as writeMenuDraft
92
+ };
@@ -0,0 +1,43 @@
1
+ import { writeMenuDraft as o, readMenuDraft as r } from "./menuDraftStorage.js";
2
+ function u(e) {
3
+ return new Promise((n, a) => {
4
+ const t = new FileReader();
5
+ t.onload = () => {
6
+ typeof t.result == "string" ? n(t.result) : a(new Error("Could not read image file."));
7
+ }, t.onerror = () => a(new Error("Could not read image file.")), t.readAsDataURL(e);
8
+ });
9
+ }
10
+ const s = { authenticated: !0, strategy: "local", user: "local" };
11
+ function i(e, n) {
12
+ return {
13
+ getAdminSession: async () => s,
14
+ signInWithPassword: async () => s,
15
+ signOut: async () => {
16
+ },
17
+ getPublishedMenu: async () => r(n) ?? e,
18
+ getDraftMenu: async () => r(n) ?? e,
19
+ getDraft: async () => ({ menu: r(n) ?? e, etag: null }),
20
+ getAdminSignInUrl: () => "/admin",
21
+ saveDraftMenu: async (a) => (o(a, n), { etag: null }),
22
+ publishDraftMenu: async () => ({ ok: !0, backupKey: null }),
23
+ listMenuBackups: async () => [],
24
+ // Local dev has no publish history; "published" means the bundled seed.
25
+ restoreDraft: async () => (o(e, n), { etag: null }),
26
+ uploadMenuImage: async (a) => {
27
+ const t = await u(a);
28
+ return {
29
+ key: `local/images/${Date.now()}-${a.name}`,
30
+ url: t
31
+ };
32
+ },
33
+ deleteMenuImage: async () => {
34
+ }
35
+ };
36
+ }
37
+ function c(e = "") {
38
+ return typeof window > "u" || e.trim() ? !1 : window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
39
+ }
40
+ export {
41
+ i as createLocalMenuApiClient,
42
+ c as shouldUseLocalMenuApi
43
+ };
@@ -0,0 +1,121 @@
1
+ class d extends Error {
2
+ status;
3
+ constructor(t, n) {
4
+ super(t), this.name = "MenuApiError", this.status = n;
5
+ }
6
+ }
7
+ function f(e = {}) {
8
+ const t = e.fetcher ?? fetch, n = e.apiBaseUrl ?? "", s = e.publicMenuUrl ?? p(n, "/api/menu");
9
+ return {
10
+ getPublishedMenu: () => o(t, s),
11
+ getDraftMenu: () => o(t, i(n, "/api/admin/menu/draft")),
12
+ getDraft: async () => {
13
+ const { body: a, response: r } = await c(t, i(n, "/api/admin/menu/draft"));
14
+ return { menu: a, etag: r.headers.get("etag") };
15
+ },
16
+ getAdminSignInUrl: (a) => {
17
+ const r = i(n, "/api/admin/sign-in");
18
+ return a ? `${r}?returnTo=${encodeURIComponent(a)}` : r;
19
+ },
20
+ getAdminSession: () => o(t, i(n, "/api/admin/session")),
21
+ signInWithPassword: async (a) => {
22
+ const r = await o(t, i(n, "/api/admin/login"), {
23
+ method: "POST",
24
+ headers: {
25
+ "content-type": "application/json"
26
+ },
27
+ body: JSON.stringify({ password: a })
28
+ });
29
+ return { authenticated: !0, strategy: r.strategy, user: r.user };
30
+ },
31
+ signOut: async () => {
32
+ await o(t, i(n, "/api/admin/logout"), {
33
+ method: "POST"
34
+ });
35
+ },
36
+ saveDraftMenu: async (a, r = {}) => {
37
+ const { body: u, response: h } = await c(t, i(n, "/api/admin/menu/draft"), {
38
+ method: "PUT",
39
+ headers: {
40
+ "content-type": "application/json",
41
+ ...r.ifMatch ? { "if-match": r.ifMatch } : {}
42
+ },
43
+ body: JSON.stringify(a)
44
+ });
45
+ return { etag: u.etag ?? h.headers.get("etag") };
46
+ },
47
+ publishDraftMenu: () => o(t, i(n, "/api/admin/menu/publish"), {
48
+ method: "POST"
49
+ }),
50
+ listMenuBackups: async () => {
51
+ const { backups: a } = await o(t, i(n, "/api/admin/menu/backups"));
52
+ return a;
53
+ },
54
+ restoreDraft: async (a) => {
55
+ const { body: r } = await c(t, i(n, "/api/admin/menu/restore"), {
56
+ method: "POST",
57
+ headers: {
58
+ "content-type": "application/json"
59
+ },
60
+ body: JSON.stringify(a.kind === "published" ? { source: "published" } : { source: "backup", key: a.key })
61
+ });
62
+ return { etag: r.etag ?? null };
63
+ },
64
+ uploadMenuImage: (a) => {
65
+ const r = new FormData();
66
+ return r.set("file", a), o(t, i(n, "/api/admin/images"), {
67
+ method: "POST",
68
+ body: r
69
+ }).then((u) => ({
70
+ ...u,
71
+ url: m(u.url, n)
72
+ }));
73
+ },
74
+ deleteMenuImage: async (a) => {
75
+ await o(t, `${i(n, "/api/admin/images")}?key=${encodeURIComponent(a)}`, {
76
+ method: "DELETE"
77
+ });
78
+ }
79
+ };
80
+ }
81
+ function i(e, t) {
82
+ return p(e, t);
83
+ }
84
+ function p(e, t) {
85
+ return e ? `${e.replace(/\/+$/, "")}/${t.replace(/^\/+/, "")}` : t;
86
+ }
87
+ function m(e, t) {
88
+ return !e || e.startsWith("http://") || e.startsWith("https://") ? e : p(t, e);
89
+ }
90
+ async function o(e, t, n) {
91
+ return (await c(e, t, n)).body;
92
+ }
93
+ async function c(e, t, n) {
94
+ let s;
95
+ try {
96
+ s = await e(t, {
97
+ credentials: "include",
98
+ ...n
99
+ });
100
+ } catch {
101
+ throw new d("Could not reach the menu API. Check your connection, then retry.", 0);
102
+ }
103
+ const r = (s.headers.get("content-type") ?? "").includes("application/json");
104
+ if (!s.ok) {
105
+ const u = r ? g(await s.json()) : l(s);
106
+ throw new d(u, s.status);
107
+ }
108
+ if (!r)
109
+ throw new d("Expected a JSON response. The /api route is not reaching the menu Worker, or a sign-in page intercepted it.", s.status);
110
+ return { body: await s.json(), response: s };
111
+ }
112
+ function g(e) {
113
+ return e && typeof e == "object" && "error" in e && typeof e.error == "string" ? e.error : "Menu API request failed.";
114
+ }
115
+ function l(e) {
116
+ return e.status === 401 || e.status === 403 ? "You are not authorized to edit this menu." : e.status === 412 ? "The draft was changed elsewhere. Reload the editor to get the latest version." : e.status === 429 ? "Too many attempts. Wait a minute and try again." : `Menu API request failed with status ${e.status}.`;
117
+ }
118
+ export {
119
+ d as MenuApiError,
120
+ f as createMenuApiClient
121
+ };
@@ -0,0 +1,35 @@
1
+ function n(t) {
2
+ return `${t}:menu-draft:v1`;
3
+ }
4
+ function r() {
5
+ return typeof window < "u" && typeof window.localStorage < "u";
6
+ }
7
+ function o(t) {
8
+ if (!r()) return null;
9
+ try {
10
+ const e = window.localStorage.getItem(t);
11
+ return e ? JSON.parse(e) : null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ function a(t, e) {
17
+ if (r())
18
+ try {
19
+ window.localStorage.setItem(e, JSON.stringify(t));
20
+ } catch {
21
+ }
22
+ }
23
+ function u(t) {
24
+ if (r())
25
+ try {
26
+ window.localStorage.removeItem(t);
27
+ } catch {
28
+ }
29
+ }
30
+ export {
31
+ u as clearMenuDraft,
32
+ n as menuDraftStorageKey,
33
+ o as readMenuDraft,
34
+ a as writeMenuDraft
35
+ };