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,52 @@
1
+ function u(t) {
2
+ return Number.isFinite(t) ? Math.min(5, Math.max(1, Math.round(t))) : 5;
3
+ }
4
+ function o(t) {
5
+ return [...t.testimonials ?? []].sort((i, r) => i.order - r.order);
6
+ }
7
+ function f(t) {
8
+ return o(t).filter((i) => i.isVisible && i.text.trim());
9
+ }
10
+ function s(t, i) {
11
+ return { ...t, testimonials: i.map((r, n) => r.order === n ? r : { ...r, order: n }) };
12
+ }
13
+ function c() {
14
+ return {
15
+ id: `testimonial-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 7)}`,
16
+ author: "",
17
+ rating: 5,
18
+ text: "",
19
+ order: Number.MAX_SAFE_INTEGER,
20
+ isVisible: !0
21
+ };
22
+ }
23
+ function l(t, i) {
24
+ return s(t, [...o(t), i]);
25
+ }
26
+ function m(t, i, r) {
27
+ if (!t.testimonials?.some((e) => e.id === i)) return t;
28
+ const n = o(t).map(
29
+ (e) => e.id === i ? { ...e, ...r, ...r.rating === void 0 ? {} : { rating: u(r.rating) } } : e
30
+ );
31
+ return s(t, n);
32
+ }
33
+ function g(t, i) {
34
+ return t.testimonials?.some((r) => r.id === i) ? s(
35
+ t,
36
+ o(t).filter((r) => r.id !== i)
37
+ ) : t;
38
+ }
39
+ function x(t, i, r) {
40
+ const n = [...o(t)], e = n.findIndex((d) => d.id === i), a = e + r;
41
+ return e < 0 || a < 0 || a >= n.length ? t : ([n[e], n[a]] = [n[a], n[e]], s(t, n));
42
+ }
43
+ export {
44
+ l as addTestimonial,
45
+ u as clampRating,
46
+ c as createTestimonialDraft,
47
+ g as deleteTestimonial,
48
+ x as moveTestimonial,
49
+ o as sortedTestimonials,
50
+ m as updateTestimonial,
51
+ f as visibleTestimonials
52
+ };
@@ -0,0 +1,11 @@
1
+ import type { RestaurantKitApiOptions } from "./config/siteConfigContext";
2
+ import type { RestaurantSiteConfig } from "./config/siteTypes";
3
+ import type { DynamicMenu } from "./menu/menuSchema";
4
+ type RestaurantSiteProps = {
5
+ readonly config: RestaurantSiteConfig;
6
+ readonly seedMenu: DynamicMenu;
7
+ /** Pass your Vite env here: `{ baseUrl: import.meta.env.VITE_MENU_API_BASE_URL, publicMenuUrl: import.meta.env.VITE_MENU_API_URL }`. */
8
+ readonly api?: RestaurantKitApiOptions;
9
+ };
10
+ export declare function RestaurantSite({ config, seedMenu, api }: RestaurantSiteProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from "react";
2
+ type AdminActionMenuProps = {
3
+ children: ReactNode;
4
+ label: string;
5
+ };
6
+ export declare function AdminActionMenu({ children, label }: AdminActionMenuProps): import("react").JSX.Element;
7
+ type AdminActionItemProps = {
8
+ children: ReactNode;
9
+ disabled?: boolean;
10
+ isDanger?: boolean;
11
+ onSelect: () => void;
12
+ };
13
+ export declare function AdminActionItem({ children, disabled, isDanger, onSelect }: AdminActionItemProps): import("react").JSX.Element;
14
+ export declare function AdminActionSeparator(): import("react").JSX.Element;
15
+ type AdminActionSubMenuProps = {
16
+ children: ReactNode;
17
+ label: ReactNode;
18
+ };
19
+ export declare function AdminActionSubMenu({ children, label }: AdminActionSubMenuProps): import("react").JSX.Element;
20
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { AdminAuthStrategy } from "../menu/menuApi";
2
+ type AdminAuthPanelProps = {
3
+ /** null while the API has not reported a strategy (unreachable, or still loading). */
4
+ strategy: AdminAuthStrategy | null;
5
+ isLoadingDraft: boolean;
6
+ isSigningIn: boolean;
7
+ message: string;
8
+ onRetry: () => void;
9
+ onSignInWithAccess: () => void;
10
+ onSignInWithPassword: (password: string) => void;
11
+ };
12
+ export declare function AdminAuthPanel({ strategy, isLoadingDraft, isSigningIn, message, onRetry, onSignInWithAccess, onSignInWithPassword }: AdminAuthPanelProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { MenuBackup } from "../menu/menuApi";
2
+ type AdminBackupsDialogProps = {
3
+ listBackups: () => Promise<readonly MenuBackup[]>;
4
+ onClose: () => void;
5
+ onRestore: (key: string) => void;
6
+ };
7
+ /** Lists the backups written on each publish; restoring one overwrites the draft, not the live menu. */
8
+ export declare function AdminBackupsDialog({ listBackups, onClose, onRestore }: AdminBackupsDialogProps): import("react").JSX.Element;
9
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import { type UseAdminMenuDraftOptions } from "./useAdminMenuDraft";
3
+ type AdminDraftProviderProps = {
4
+ readonly children: ReactNode;
5
+ readonly options?: UseAdminMenuDraftOptions;
6
+ };
7
+ /**
8
+ * Owns the admin draft above the editor and preview routes, so navigating between them keeps
9
+ * unsaved edits in memory instead of remounting the editor and reloading the remote draft.
10
+ */
11
+ export declare function AdminDraftProvider({ children, options }: AdminDraftProviderProps): import("react").JSX.Element;
12
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { MenuGroup } from "../menu/menuSchema";
2
+ export type AdminGroupRow = {
3
+ readonly group: MenuGroup;
4
+ readonly itemCount: number;
5
+ readonly hasSections: boolean;
6
+ };
7
+ type AdminGroupsPanelProps = {
8
+ groups: readonly AdminGroupRow[];
9
+ onAddGroup: () => void;
10
+ onDeleteGroup: (groupId: string) => void;
11
+ onUpdateGroup: (groupId: string, patch: Partial<MenuGroup>) => void;
12
+ };
13
+ export declare function AdminGroupsPanel({ groups, onAddGroup, onDeleteGroup, onUpdateGroup }: AdminGroupsPanelProps): import("react").JSX.Element;
14
+ export {};
@@ -0,0 +1,16 @@
1
+ import "react-image-crop/dist/ReactCrop.css";
2
+ import type { MenuItem, MenuSize } from "../menu/menuSchema";
3
+ type AdminItemDrawerProps = {
4
+ isUploadingImage: boolean;
5
+ item: MenuItem;
6
+ onAddSize: () => void;
7
+ onClose: () => void;
8
+ onDelete: () => void;
9
+ onRemoveImage: () => void;
10
+ onRemoveSize: (sizeId: string) => void;
11
+ onUpdate: (patch: Partial<MenuItem>) => void;
12
+ onUpdateSize: (sizeId: string, patch: Partial<MenuSize>) => void;
13
+ onUploadImage: (file: File) => void;
14
+ };
15
+ export declare function AdminItemDrawer({ isUploadingImage, item, onAddSize, onClose, onDelete, onRemoveImage, onRemoveSize, onUpdate, onUpdateSize, onUploadImage }: AdminItemDrawerProps): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { MenuItem } from "../menu/menuSchema";
2
+ type AdminMenuCardProps = {
3
+ index: number;
4
+ isReordering: boolean;
5
+ isUploadingImage: boolean;
6
+ item: MenuItem;
7
+ onEdit: () => void;
8
+ };
9
+ export declare function AdminMenuCard({ index, isReordering, isUploadingImage, item, onEdit }: AdminMenuCardProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { MenuGroup } from "../menu/menuSchema";
2
+ type AdminMenuGroupTabsProps = {
3
+ activeGroupId: string;
4
+ editingGroupId: string;
5
+ groups: readonly MenuGroup[];
6
+ onSelectGroup: (groupId: string) => void;
7
+ onSetEditingGroupId: (groupId: string) => void;
8
+ onUpdateGroup: (groupId: string, patch: Partial<MenuGroup>) => void;
9
+ };
10
+ export declare function AdminMenuGroupTabs({ activeGroupId, editingGroupId, groups, onSelectGroup, onSetEditingGroupId, onUpdateGroup }: AdminMenuGroupTabsProps): import("react").JSX.Element;
11
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { MenuIcon } from "../menu/menuSchema";
2
+ type AdminMenuIconPickerProps = {
3
+ readonly label: string;
4
+ readonly value: MenuIcon;
5
+ readonly onChange: (icon: MenuIcon) => void;
6
+ };
7
+ export declare function AdminMenuIconPicker({ label, value, onChange }: AdminMenuIconPickerProps): import("react").JSX.Element;
8
+ export {};
@@ -0,0 +1,5 @@
1
+ import { AdminMenuEditor } from "../pages/AdminMenuEditor";
2
+ import { AdminPreview } from "../pages/AdminPreview";
3
+ /** Everything under `/admin`, in one module so `RestaurantSite` can load it on demand. */
4
+ export declare function AdminLayout(): import("react").JSX.Element;
5
+ export { AdminMenuEditor, AdminPreview };
@@ -0,0 +1,20 @@
1
+ import type { MenuCategory, MenuGroup, MenuItem } from "../menu/menuSchema";
2
+ type AdminSectionEditorProps = {
3
+ category: MenuCategory;
4
+ groupLabel: string;
5
+ groups: readonly MenuGroup[];
6
+ isFirstSection: boolean;
7
+ isLastSection: boolean;
8
+ isReordering: boolean;
9
+ items: readonly MenuItem[];
10
+ uploadingItemIds: ReadonlySet<string>;
11
+ onAddItem: () => void;
12
+ onDeleteSection: () => void;
13
+ onEditItemDetails: (itemId: string) => void;
14
+ onFocusSection: () => void;
15
+ onMoveSection: (direction: -1 | 1) => void;
16
+ onToggleReorderItems: () => void;
17
+ onUpdateCategory: (patch: Partial<MenuCategory>) => void;
18
+ };
19
+ export declare function AdminSectionEditor({ category, groupLabel, groups, isFirstSection, isLastSection, isReordering, items, uploadingItemIds, onAddItem, onDeleteSection, onEditItemDetails, onFocusSection, onMoveSection, onToggleReorderItems, onUpdateCategory, }: AdminSectionEditorProps): import("react").JSX.Element;
20
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { DynamicMenu } from "../menu/menuSchema";
2
+ type AdminTestimonialsPanelProps = {
3
+ readonly menu: DynamicMenu;
4
+ readonly applyMenuMutation: (mutateMenu: (menu: DynamicMenu) => DynamicMenu) => void;
5
+ };
6
+ export declare function AdminTestimonialsPanel({ menu, applyMenuMutation }: AdminTestimonialsPanelProps): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { AdminMenuDraft } from "./useAdminMenuDraft";
2
+ export declare const AdminDraftContext: import("react").Context<AdminMenuDraft | null>;
@@ -0,0 +1,22 @@
1
+ import type { MenuItem } from "../menu/menuSchema";
2
+ export { formatPrice } from "../menu/priceFormat";
3
+ export { imageSrc as imageSource } from "../menu-browser/menuItemDisplay";
4
+ export type SortableData = {
5
+ kind: "group";
6
+ id: string;
7
+ } | {
8
+ kind: "item";
9
+ id: string;
10
+ categoryId: string;
11
+ };
12
+ export type SortableEntity = {
13
+ readonly id?: string | number;
14
+ readonly data?: unknown;
15
+ readonly index?: number;
16
+ } | null;
17
+ export declare function slugify(value: string): string;
18
+ export declare function uniqueId(prefix: string, label: string): string;
19
+ export declare function emptyItem(categoryId: string): MenuItem;
20
+ export declare function getApiErrorMessage(error: unknown): string;
21
+ export declare function isLikelyAuthError(error: unknown): boolean;
22
+ export declare function sortableDataFromEntity(entity: SortableEntity): SortableData | null;
@@ -0,0 +1,73 @@
1
+ import type { DynamicMenu, MenuCategory, MenuGroup, MenuItem, MenuSize } from "../menu/menuSchema";
2
+ export type AdminMenuCommand = {
3
+ readonly type: "add-group";
4
+ readonly group: MenuGroup;
5
+ } | {
6
+ readonly type: "update-group";
7
+ readonly groupId: string;
8
+ readonly patch: Partial<MenuGroup>;
9
+ } | {
10
+ readonly type: "delete-group";
11
+ readonly groupId: string;
12
+ } | {
13
+ readonly type: "move-group";
14
+ readonly groupId: string;
15
+ readonly direction: -1 | 1;
16
+ } | {
17
+ readonly type: "reorder-groups";
18
+ readonly orderedGroupIds: readonly string[];
19
+ } | {
20
+ readonly type: "add-section";
21
+ readonly category: MenuCategory;
22
+ } | {
23
+ readonly type: "update-section";
24
+ readonly categoryId: string;
25
+ readonly patch: Partial<MenuCategory>;
26
+ } | {
27
+ readonly type: "delete-section";
28
+ readonly categoryId: string;
29
+ } | {
30
+ readonly type: "move-section";
31
+ readonly categoryId: string;
32
+ readonly direction: -1 | 1;
33
+ } | {
34
+ readonly type: "add-item";
35
+ readonly item: MenuItem;
36
+ } | {
37
+ readonly type: "update-item";
38
+ readonly itemId: string;
39
+ readonly patch: Partial<MenuItem>;
40
+ } | {
41
+ readonly type: "delete-item";
42
+ readonly itemId: string;
43
+ } | {
44
+ readonly type: "move-item-before";
45
+ readonly sourceItemId: string;
46
+ readonly targetItemId: string;
47
+ } | {
48
+ readonly type: "add-size";
49
+ readonly itemId: string;
50
+ readonly size: MenuSize;
51
+ } | {
52
+ readonly type: "update-size";
53
+ readonly itemId: string;
54
+ readonly sizeId: string;
55
+ readonly patch: Partial<MenuSize>;
56
+ } | {
57
+ readonly type: "delete-size";
58
+ readonly itemId: string;
59
+ readonly sizeId: string;
60
+ };
61
+ export type AdminMenuCommandRejection = "pinned-group" | "group-has-sections" | "section-has-items";
62
+ export type AdminMenuCommandResult = {
63
+ readonly status: "applied";
64
+ readonly menu: DynamicMenu;
65
+ } | {
66
+ readonly status: "rejected";
67
+ readonly reason: AdminMenuCommandRejection;
68
+ };
69
+ export declare function createMenuGroupDraft(menu: DynamicMenu): MenuGroup;
70
+ export declare function createMenuCategoryDraft(menu: DynamicMenu, groupId: string): MenuCategory;
71
+ export declare function createMenuSizeDraft(item: MenuItem): MenuSize;
72
+ export declare function getAdminMenuCommandRejection(menu: DynamicMenu, command: AdminMenuCommand): AdminMenuCommandRejection | null;
73
+ export declare function applyAdminMenuCommand(menu: DynamicMenu, command: AdminMenuCommand): AdminMenuCommandResult;
@@ -0,0 +1,5 @@
1
+ import { type AdminMenuDraft, type UseAdminMenuDraftOptions } from "./useAdminMenuDraft";
2
+ /** The draft from the nearest provider, or null when no provider is mounted. */
3
+ export declare function useAdminDraftContext(): AdminMenuDraft | null;
4
+ /** Prefers a provider-owned draft; falls back to a local one so the editor works standalone. */
5
+ export declare function useAdminDraft(options?: UseAdminMenuDraftOptions): AdminMenuDraft;
@@ -0,0 +1,41 @@
1
+ import type { AdminSession, MenuApiClient, MenuBackup, RestoreDraftSource } from "../menu/menuApi";
2
+ import type { DynamicMenu, MenuItem } from "../menu/menuSchema";
3
+ import { type AdminMenuCommand, type AdminMenuCommandRejection } from "./adminMenuCommands";
4
+ export type UseAdminMenuDraftOptions = {
5
+ readonly api?: MenuApiClient;
6
+ readonly initialMenu?: DynamicMenu;
7
+ /** When false the hook is inert: no remote load, no beforeunload guard. Used when a provider above already owns the draft. */
8
+ readonly enabled?: boolean;
9
+ };
10
+ export type AdminMenuDraft = {
11
+ readonly draftMenu: DynamicMenu;
12
+ readonly hasUnsavedChanges: boolean;
13
+ readonly isLoadingDraft: boolean;
14
+ readonly isSavingDraft: boolean;
15
+ readonly isPublishingDraft: boolean;
16
+ readonly isBusy: boolean;
17
+ readonly authErrorMessage: string;
18
+ /** null until the API has answered /api/admin/session (or when it is unreachable). */
19
+ readonly authSession: AdminSession | null;
20
+ readonly isSigningIn: boolean;
21
+ readonly loadRemoteDraft: () => void;
22
+ /** Cloudflare Access strategy: full-page redirect through the Access login. */
23
+ readonly signIn: () => void;
24
+ /** Password strategy: exchanges the password for a session cookie, then reloads the draft. */
25
+ readonly signInWithPassword: (password: string) => Promise<void>;
26
+ readonly signOut: () => Promise<void>;
27
+ readonly saveDraft: () => Promise<void>;
28
+ readonly publishDraft: () => Promise<void>;
29
+ /** Drops unsaved edits by reloading the remote draft. */
30
+ readonly discardChanges: () => void;
31
+ /** Overwrites the remote draft with the published menu or a backup, then reloads it. */
32
+ readonly restoreDraft: (source: RestoreDraftSource) => Promise<void>;
33
+ readonly listBackups: () => Promise<readonly MenuBackup[]>;
34
+ readonly isRestoring: boolean;
35
+ readonly applyMenuMutation: (mutateMenu: (menu: DynamicMenu) => DynamicMenu) => void;
36
+ readonly applyCommand: (command: AdminMenuCommand) => AdminMenuCommandRejection | null;
37
+ readonly uploadingItemIds: ReadonlySet<string>;
38
+ readonly uploadItemImage: (item: MenuItem, file: File) => Promise<void>;
39
+ readonly removeItemImage: (item: MenuItem) => Promise<void>;
40
+ };
41
+ export declare function useAdminMenuDraft(options?: UseAdminMenuDraftOptions): AdminMenuDraft;
@@ -0,0 +1,7 @@
1
+ import type { CartItem } from "./cartTypes";
2
+ type CartDrawerProps = {
3
+ /** When provided, each line gets an Edit action that hands the line back for editing. */
4
+ onEditItem?: (item: CartItem) => void;
5
+ };
6
+ export declare function CartDrawer({ onEditItem }?: CartDrawerProps): import("react").JSX.Element;
7
+ export {};
@@ -0,0 +1,4 @@
1
+ import { type ReactNode } from "react";
2
+ export declare function CartProvider({ children }: {
3
+ children: ReactNode;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ import type { CartContextValue } from "./cartTypes";
2
+ export declare const CartContext: import("react").Context<CartContextValue | null>;
@@ -0,0 +1,9 @@
1
+ import type { CartItem, CartSelection } from "./cartTypes";
2
+ export type CartOrderConfig = {
3
+ readonly whatsappNumber: string;
4
+ readonly orderGreeting: string;
5
+ };
6
+ export declare function cartSelectionKey(selection: CartSelection): string;
7
+ export declare function formatVariationName(name?: string): string;
8
+ export declare function buildWhatsAppMessage(items: readonly CartItem[], orderNote: string, config: CartOrderConfig): string;
9
+ export declare function buildWhatsAppOrderUrl(items: readonly CartItem[], orderNote: string, config: CartOrderConfig): string;
@@ -0,0 +1,36 @@
1
+ import type { CartSelection, CartState } from "./cartTypes";
2
+ export type CartAction = {
3
+ type: "add";
4
+ selection: CartSelection;
5
+ quantity?: number;
6
+ note?: string;
7
+ }
8
+ /** `note` omitted keeps the line's note; `null` or a blank string clears it. */
9
+ | {
10
+ type: "replace";
11
+ key: string;
12
+ selection: CartSelection;
13
+ quantity?: number;
14
+ note?: string | null;
15
+ } | {
16
+ type: "decrease";
17
+ key: string;
18
+ } | {
19
+ type: "remove";
20
+ key: string;
21
+ } | {
22
+ type: "set-note";
23
+ key: string;
24
+ note: string;
25
+ } | {
26
+ type: "set-order-note";
27
+ note: string;
28
+ } | {
29
+ type: "open";
30
+ } | {
31
+ type: "close";
32
+ } | {
33
+ type: "clear";
34
+ };
35
+ export declare const initialCartState: CartState;
36
+ export declare function cartReducer(state: CartState, action: CartAction): CartState;
@@ -0,0 +1,37 @@
1
+ export type CartSelection = {
2
+ readonly itemId: string;
3
+ readonly itemName: string;
4
+ readonly variationId?: string;
5
+ readonly variationName?: string;
6
+ readonly removedIngredients?: readonly string[];
7
+ };
8
+ export type CartItem = CartSelection & {
9
+ readonly key: string;
10
+ readonly quantity: number;
11
+ readonly note?: string;
12
+ };
13
+ export type CartState = {
14
+ readonly items: readonly CartItem[];
15
+ readonly isOpen: boolean;
16
+ readonly orderNote: string;
17
+ };
18
+ export type CartContextValue = CartState & {
19
+ readonly itemCount: number;
20
+ readonly addItem: (selection: CartSelection, options?: {
21
+ quantity?: number;
22
+ note?: string;
23
+ }) => void;
24
+ /** `note: null` (or "") clears the note when the edit merges into an existing line; omitting it keeps the line's note. */
25
+ readonly replaceItem: (key: string, selection: CartSelection, options?: {
26
+ quantity?: number;
27
+ note?: string | null;
28
+ }) => void;
29
+ readonly decreaseItem: (key: string) => void;
30
+ readonly removeItem: (key: string) => void;
31
+ readonly getQuantity: (selection: CartSelection) => number;
32
+ readonly setItemNote: (key: string, note: string) => void;
33
+ readonly setOrderNote: (note: string) => void;
34
+ readonly openCart: () => void;
35
+ readonly closeCart: () => void;
36
+ readonly clearCart: () => void;
37
+ };
@@ -0,0 +1 @@
1
+ export declare function useCart(): import("./cartTypes").CartContextValue;
@@ -0,0 +1,5 @@
1
+ type BrandProps = {
2
+ className?: string;
3
+ };
4
+ export declare function Brand({ className }: BrandProps): import("react").JSX.Element;
5
+ export {};
@@ -0,0 +1 @@
1
+ export declare function Footer(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function Header(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function Hero(): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ type InstagramIconProps = {
2
+ className?: string;
3
+ };
4
+ /** Outline glyph matching lucide's stroke style; lucide 1.x no longer ships brand icons. */
5
+ export declare function InstagramIcon({ className }: InstagramIconProps): import("react").JSX.Element;
6
+ export {};
@@ -0,0 +1 @@
1
+ export declare function LocationsSection(): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function Marquee(): import("react").JSX.Element;
@@ -0,0 +1,4 @@
1
+ /** Icon for an order button: WhatsApp glyph, the ordering app's logo, or a plain outbound arrow. */
2
+ export declare function OrderIcon({ className }: {
3
+ readonly className?: string;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export declare function RatingSection(): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ /** Owner-curated customer reviews. Renders nothing until the menu has a visible testimonial. */
2
+ export declare function TestimonialsSection(): import("react").JSX.Element | null;
@@ -0,0 +1,5 @@
1
+ type WhatsAppIconProps = {
2
+ className?: string;
3
+ };
4
+ export declare function WhatsAppIcon({ className }: WhatsAppIconProps): import("react").JSX.Element;
5
+ export {};
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from "react";
2
+ import { type MenuApiClient } from "../menu/menuApi";
3
+ import type { DynamicMenu } from "../menu/menuSchema";
4
+ import { type RestaurantKitApiOptions } from "./siteConfigContext";
5
+ import type { RestaurantSiteConfig } from "./siteTypes";
6
+ type RestaurantKitProviderProps = {
7
+ readonly config: RestaurantSiteConfig;
8
+ readonly seedMenu: DynamicMenu;
9
+ /**
10
+ * API endpoints, typically fed from the site's own Vite env:
11
+ * `{ baseUrl: import.meta.env.VITE_MENU_API_BASE_URL, publicMenuUrl: import.meta.env.VITE_MENU_API_URL }`.
12
+ */
13
+ readonly api?: RestaurantKitApiOptions;
14
+ /** Override the API client entirely (tests, custom transports). */
15
+ readonly menuApi?: MenuApiClient;
16
+ readonly children: ReactNode;
17
+ };
18
+ /**
19
+ * Single source of restaurant identity. Everything that needs the config, seed menu, API
20
+ * client, or draft key reads it from here — there is no module-scope fallback, so two
21
+ * providers on one page can never bleed into each other.
22
+ */
23
+ export declare function RestaurantKitProvider({ config, seedMenu, api, menuApi, children }: RestaurantKitProviderProps): import("react").JSX.Element;
24
+ export {};
@@ -0,0 +1,28 @@
1
+ import type { MenuApiClient } from "../menu/menuApi";
2
+ import type { DynamicMenu } from "../menu/menuSchema";
3
+ import type { RestaurantSiteConfig } from "./siteTypes";
4
+ export type RestaurantKitApiOptions = {
5
+ /** Origin of the menu Worker for admin calls. Empty = same origin. */
6
+ readonly baseUrl?: string;
7
+ /** URL of the published menu JSON. Defaults to `<baseUrl>/api/menu`. */
8
+ readonly publicMenuUrl?: string;
9
+ /** "auto" (default): browser-local adapter on localhost unless `baseUrl` is set. */
10
+ readonly mode?: "auto" | "local" | "remote";
11
+ };
12
+ export type RestaurantKitValue = {
13
+ readonly config: RestaurantSiteConfig;
14
+ readonly seedMenu: DynamicMenu;
15
+ /** Remote client, or the browser-local adapter (see `isLocalApi`). Built once per provider. */
16
+ readonly menuApi: MenuApiClient;
17
+ /** True when drafts live in this browser's localStorage instead of the Worker. */
18
+ readonly isLocalApi: boolean;
19
+ /** Where the public site fetches the published menu from. */
20
+ readonly publicMenuUrl: string;
21
+ /** localStorage key for the local-dev draft, namespaced by restaurant id. */
22
+ readonly draftStorageKey: string;
23
+ };
24
+ export declare const SiteConfigContext: import("react").Context<RestaurantKitValue | null>;
25
+ export declare function useRestaurantKit(): RestaurantKitValue;
26
+ export declare function useSiteConfig(): RestaurantSiteConfig;
27
+ export declare function useSeedMenu(): DynamicMenu;
28
+ export declare function useMenuApi(): MenuApiClient;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * How customers order.
3
+ * - `whatsapp` (default): cart + "Add to cart" on every item, order sent as a WhatsApp message.
4
+ * - `external`: cart off. The menu is browse-only and every order button links to `url`
5
+ * (Toters, Talabat, the restaurant's own app…).
6
+ */
7
+ export type OrderingConfig = {
8
+ readonly mode: "whatsapp";
9
+ } | {
10
+ readonly mode: "external";
11
+ readonly url: string;
12
+ /** Full button text, e.g. "Order on Toters". */
13
+ readonly label: string;
14
+ /** Header pill text. Defaults to "Order". */
15
+ readonly shortLabel?: string;
16
+ /** Logo of the ordering app, shown inside the buttons. Falls back to an arrow. */
17
+ readonly iconSrc?: string;
18
+ };
19
+ export type RestaurantSiteConfig = {
20
+ readonly restaurantId: string;
21
+ readonly brandName: string;
22
+ readonly tagline: string;
23
+ readonly locationLabel: string;
24
+ readonly address: string;
25
+ readonly phoneDisplay: string;
26
+ readonly phoneHref: string;
27
+ /** International form, no `+`. Empty string hides every WhatsApp link. */
28
+ readonly whatsappNumber: string;
29
+ readonly instagramHandle: string;
30
+ readonly instagramUrl: string;
31
+ readonly mapsUrl: string;
32
+ readonly logoSrc: string;
33
+ readonly rating: string;
34
+ readonly ratingLabel: string;
35
+ readonly reviewCount: string;
36
+ readonly cuisineSummary: string;
37
+ readonly localBadge: string;
38
+ readonly orderGreeting: string;
39
+ readonly highlights: readonly string[];
40
+ readonly heroEyebrow: string;
41
+ readonly heroSubline: string;
42
+ readonly ratingHeadline: string;
43
+ readonly ratingCopy: string;
44
+ readonly footerNote: string;
45
+ /** Optional copy for the Testimonials section (shown only when the menu has visible testimonials). */
46
+ readonly testimonialsEyebrow?: string;
47
+ readonly testimonialsHeadline?: string;
48
+ /** Optional. Omit for the default WhatsApp cart. */
49
+ readonly ordering?: OrderingConfig;
50
+ };