@zorgo/next 1.0.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.
- package/README.md +19 -0
- package/dist/cli.d.mts +5 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +2 -0
- package/dist/cli.mjs +2 -0
- package/dist/components.d.mts +337 -0
- package/dist/components.d.ts +337 -0
- package/dist/components.js +1996 -0
- package/dist/components.js.map +1 -0
- package/dist/components.mjs +1962 -0
- package/dist/components.mjs.map +1 -0
- package/dist/index.d.mts +305 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.js +2 -0
- package/dist/index.mjs +2 -0
- package/dist/seo.d.mts +30 -0
- package/dist/seo.d.ts +30 -0
- package/dist/seo.js +82 -0
- package/dist/seo.js.map +1 -0
- package/dist/seo.mjs +56 -0
- package/dist/seo.mjs.map +1 -0
- package/dist/slugs.d.mts +1 -0
- package/dist/slugs.d.ts +1 -0
- package/dist/slugs.js +47 -0
- package/dist/slugs.js.map +1 -0
- package/dist/slugs.mjs +19 -0
- package/dist/slugs.mjs.map +1 -0
- package/dist/tree/.env.gen.ts +27 -0
- package/dist/tree/README.md +0 -0
- package/dist/tree/app/api/zorgo-token/route.ts +11 -0
- package/dist/tree/app/apple-icon.tsx.gen.ts +54 -0
- package/dist/tree/app/checkout/page.tsx +10 -0
- package/dist/tree/app/components/Footer.tsx +50 -0
- package/dist/tree/app/components/Header.tsx +128 -0
- package/dist/tree/app/components/ModalHost.tsx +56 -0
- package/dist/tree/app/components/ModalRegistry.tsx +113 -0
- package/dist/tree/app/components/TestCard.tsx +35 -0
- package/dist/tree/app/components/index.ts +3 -0
- package/dist/tree/app/error.tsx +10 -0
- package/dist/tree/app/global-error.tsx +12 -0
- package/dist/tree/app/globals.css.gen.ts +123 -0
- package/dist/tree/app/icon.tsx.gen.ts +87 -0
- package/dist/tree/app/layout.tsx.gen.ts +55 -0
- package/dist/tree/app/locations/page.tsx +30 -0
- package/dist/tree/app/manifest.ts.gen.ts +39 -0
- package/dist/tree/app/menu/[slug]/ItemCustomizationClient.tsx +18 -0
- package/dist/tree/app/menu/[slug]/page.tsx +78 -0
- package/dist/tree/app/menu/page.tsx +36 -0
- package/dist/tree/app/not-found.tsx +8 -0
- package/dist/tree/app/opengraph-image.tsx.gen.ts +82 -0
- package/dist/tree/app/page.tsx +19 -0
- package/dist/tree/app/privacy/page.tsx.gen.ts +15 -0
- package/dist/tree/app/robots.ts +9 -0
- package/dist/tree/app/sitemap.ts +26 -0
- package/dist/tree/app/terms/page.tsx.gen.ts +16 -0
- package/dist/tree/gitignore +44 -0
- package/dist/tree/lib/zorgoSiteToken.ts +20 -0
- package/dist/tree/next.config.ts +14 -0
- package/dist/tree/open-next.config.ts +4 -0
- package/dist/tree/package.json.gen.ts +24 -0
- package/dist/tree/postcss.config.mjs +7 -0
- package/dist/tree/scripts/prebuild.ts +448 -0
- package/dist/tree/tsconfig.json +35 -0
- package/package.json +72 -0
package/dist/seo.d.mts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Menu } from '@zorgo/universal/utils/menuUtils';
|
|
2
|
+
import { MenuItem } from '@zorgo/universal/interfaces';
|
|
3
|
+
|
|
4
|
+
/** Franchise-level facts for the Restaurant node. Everything but `name` is
|
|
5
|
+
* optional so the schema degrades gracefully when a field is missing. */
|
|
6
|
+
interface RestaurantInfo {
|
|
7
|
+
name: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
servesCuisine?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds a standalone schema.org JSON-LD object for a single menu item, for
|
|
15
|
+
* embedding in that item's `/menu/[slug]` page `<head>`. Reuses `menuItemNode`
|
|
16
|
+
* (the same node shape used inside the Restaurant menu) and adds the
|
|
17
|
+
* `@context` a top-level node needs. Pure — safe to run at build time.
|
|
18
|
+
*/
|
|
19
|
+
declare function buildMenuItemJsonLd(item: MenuItem): {
|
|
20
|
+
"@context": string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Builds a schema.org Restaurant JSON-LD object from a prebuilt Menu and
|
|
24
|
+
* franchise info. Categories become MenuSections; the items in each become
|
|
25
|
+
* MenuItems. Pure — same inputs, same output — so it can run at prebuild time
|
|
26
|
+
* and be embedded straight into the site's <head>.
|
|
27
|
+
*/
|
|
28
|
+
declare function buildRestaurantJsonLd(menu: Menu, info: RestaurantInfo): Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
export { type RestaurantInfo, buildMenuItemJsonLd, buildRestaurantJsonLd };
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Menu } from '@zorgo/universal/utils/menuUtils';
|
|
2
|
+
import { MenuItem } from '@zorgo/universal/interfaces';
|
|
3
|
+
|
|
4
|
+
/** Franchise-level facts for the Restaurant node. Everything but `name` is
|
|
5
|
+
* optional so the schema degrades gracefully when a field is missing. */
|
|
6
|
+
interface RestaurantInfo {
|
|
7
|
+
name: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
image?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
servesCuisine?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds a standalone schema.org JSON-LD object for a single menu item, for
|
|
15
|
+
* embedding in that item's `/menu/[slug]` page `<head>`. Reuses `menuItemNode`
|
|
16
|
+
* (the same node shape used inside the Restaurant menu) and adds the
|
|
17
|
+
* `@context` a top-level node needs. Pure — safe to run at build time.
|
|
18
|
+
*/
|
|
19
|
+
declare function buildMenuItemJsonLd(item: MenuItem): {
|
|
20
|
+
"@context": string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Builds a schema.org Restaurant JSON-LD object from a prebuilt Menu and
|
|
24
|
+
* franchise info. Categories become MenuSections; the items in each become
|
|
25
|
+
* MenuItems. Pure — same inputs, same output — so it can run at prebuild time
|
|
26
|
+
* and be embedded straight into the site's <head>.
|
|
27
|
+
*/
|
|
28
|
+
declare function buildRestaurantJsonLd(menu: Menu, info: RestaurantInfo): Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
export { type RestaurantInfo, buildMenuItemJsonLd, buildRestaurantJsonLd };
|
package/dist/seo.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/seo.ts
|
|
21
|
+
var seo_exports = {};
|
|
22
|
+
__export(seo_exports, {
|
|
23
|
+
buildMenuItemJsonLd: () => buildMenuItemJsonLd,
|
|
24
|
+
buildRestaurantJsonLd: () => buildRestaurantJsonLd
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(seo_exports);
|
|
27
|
+
function priceString(cents) {
|
|
28
|
+
return (cents / 100).toFixed(2);
|
|
29
|
+
}
|
|
30
|
+
function variantOffer(variant) {
|
|
31
|
+
return {
|
|
32
|
+
"@type": "Offer",
|
|
33
|
+
price: priceString(variant.display_price ?? variant.price),
|
|
34
|
+
priceCurrency: "USD"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function menuItemNode(item) {
|
|
38
|
+
const node = {
|
|
39
|
+
"@type": "MenuItem",
|
|
40
|
+
name: item.name
|
|
41
|
+
};
|
|
42
|
+
if (item.description) node.description = item.description;
|
|
43
|
+
const offers = item.variants.map(variantOffer);
|
|
44
|
+
if (offers.length === 1) node.offers = offers[0];
|
|
45
|
+
else if (offers.length > 1) node.offers = offers;
|
|
46
|
+
return node;
|
|
47
|
+
}
|
|
48
|
+
function buildMenuItemJsonLd(item) {
|
|
49
|
+
return { "@context": "https://schema.org", ...menuItemNode(item) };
|
|
50
|
+
}
|
|
51
|
+
function buildRestaurantJsonLd(menu, info) {
|
|
52
|
+
const hasMenuSection = menu.categories.map((category) => {
|
|
53
|
+
const section = {
|
|
54
|
+
"@type": "MenuSection",
|
|
55
|
+
name: category.name
|
|
56
|
+
};
|
|
57
|
+
const items = menu.items.filter((item) => item.category_id === category.category_id).map(menuItemNode);
|
|
58
|
+
if (items.length) section.hasMenuItem = items;
|
|
59
|
+
return section;
|
|
60
|
+
});
|
|
61
|
+
const restaurant = {
|
|
62
|
+
"@context": "https://schema.org",
|
|
63
|
+
"@type": "Restaurant",
|
|
64
|
+
name: info.name
|
|
65
|
+
};
|
|
66
|
+
if (info.url) restaurant.url = info.url;
|
|
67
|
+
if (info.image) restaurant.image = info.image;
|
|
68
|
+
if (info.description) restaurant.description = info.description;
|
|
69
|
+
if (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;
|
|
70
|
+
restaurant.hasMenu = {
|
|
71
|
+
"@type": "Menu",
|
|
72
|
+
name: "Menu",
|
|
73
|
+
hasMenuSection
|
|
74
|
+
};
|
|
75
|
+
return restaurant;
|
|
76
|
+
}
|
|
77
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
78
|
+
0 && (module.exports = {
|
|
79
|
+
buildMenuItemJsonLd,
|
|
80
|
+
buildRestaurantJsonLd
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=seo.js.map
|
package/dist/seo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo.ts"],"sourcesContent":["import type { Menu } from \"@zorgo/universal/utils/menuUtils\";\nimport type { ItemVariant, MenuItem } from \"@zorgo/universal/interfaces\";\n\n/** Franchise-level facts for the Restaurant node. Everything but `name` is\n * optional so the schema degrades gracefully when a field is missing. */\nexport interface RestaurantInfo {\n\tname: string;\n\turl?: string;\n\timage?: string;\n\tdescription?: string;\n\tservesCuisine?: string[];\n}\n\n// cents -> \"5.49\" (schema.org Offer.price is a string in major currency units)\nfunction priceString(cents: number): string {\n\treturn (cents / 100).toFixed(2);\n}\n\nfunction variantOffer(variant: ItemVariant) {\n\treturn {\n\t\t\"@type\": \"Offer\",\n\t\tprice: priceString(variant.display_price ?? variant.price),\n\t\tpriceCurrency: \"USD\",\n\t};\n}\n\nfunction menuItemNode(item: MenuItem) {\n\tconst node: Record<string, unknown> = {\n\t\t\"@type\": \"MenuItem\",\n\t\tname: item.name,\n\t};\n\tif (item.description) node.description = item.description;\n\t// one Offer for a single price, an array when the item has size/variant prices\n\tconst offers = item.variants.map(variantOffer);\n\tif (offers.length === 1) node.offers = offers[0];\n\telse if (offers.length > 1) node.offers = offers;\n\treturn node;\n}\n\n/**\n * Builds a standalone schema.org JSON-LD object for a single menu item, for\n * embedding in that item's `/menu/[slug]` page `<head>`. Reuses `menuItemNode`\n * (the same node shape used inside the Restaurant menu) and adds the\n * `@context` a top-level node needs. Pure — safe to run at build time.\n */\nexport function buildMenuItemJsonLd(item: MenuItem) {\n\treturn { \"@context\": \"https://schema.org\", ...menuItemNode(item) };\n}\n\n/**\n * Builds a schema.org Restaurant JSON-LD object from a prebuilt Menu and\n * franchise info. Categories become MenuSections; the items in each become\n * MenuItems. Pure — same inputs, same output — so it can run at prebuild time\n * and be embedded straight into the site's <head>.\n */\nexport function buildRestaurantJsonLd(menu: Menu, info: RestaurantInfo) {\n\tconst hasMenuSection = menu.categories.map((category) => {\n\t\tconst section: Record<string, unknown> = {\n\t\t\t\"@type\": \"MenuSection\",\n\t\t\tname: category.name,\n\t\t};\n\t\tconst items = menu.items\n\t\t\t.filter((item) => item.category_id === category.category_id)\n\t\t\t.map(menuItemNode);\n\t\tif (items.length) section.hasMenuItem = items;\n\t\treturn section;\n\t});\n\n\tconst restaurant: Record<string, unknown> = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"Restaurant\",\n\t\tname: info.name,\n\t};\n\tif (info.url) restaurant.url = info.url;\n\tif (info.image) restaurant.image = info.image;\n\tif (info.description) restaurant.description = info.description;\n\tif (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;\n\trestaurant.hasMenu = {\n\t\t\"@type\": \"Menu\",\n\t\tname: \"Menu\",\n\t\thasMenuSection,\n\t};\n\n\treturn restaurant;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,SAAS,YAAY,OAAuB;AAC3C,UAAQ,QAAQ,KAAK,QAAQ,CAAC;AAC/B;AAEA,SAAS,aAAa,SAAsB;AAC3C,SAAO;AAAA,IACN,SAAS;AAAA,IACT,OAAO,YAAY,QAAQ,iBAAiB,QAAQ,KAAK;AAAA,IACzD,eAAe;AAAA,EAChB;AACD;AAEA,SAAS,aAAa,MAAgB;AACrC,QAAM,OAAgC;AAAA,IACrC,SAAS;AAAA,IACT,MAAM,KAAK;AAAA,EACZ;AACA,MAAI,KAAK,YAAa,MAAK,cAAc,KAAK;AAE9C,QAAM,SAAS,KAAK,SAAS,IAAI,YAAY;AAC7C,MAAI,OAAO,WAAW,EAAG,MAAK,SAAS,OAAO,CAAC;AAAA,WACtC,OAAO,SAAS,EAAG,MAAK,SAAS;AAC1C,SAAO;AACR;AAQO,SAAS,oBAAoB,MAAgB;AACnD,SAAO,EAAE,YAAY,sBAAsB,GAAG,aAAa,IAAI,EAAE;AAClE;AAQO,SAAS,sBAAsB,MAAY,MAAsB;AACvE,QAAM,iBAAiB,KAAK,WAAW,IAAI,CAAC,aAAa;AACxD,UAAM,UAAmC;AAAA,MACxC,SAAS;AAAA,MACT,MAAM,SAAS;AAAA,IAChB;AACA,UAAM,QAAQ,KAAK,MACjB,OAAO,CAAC,SAAS,KAAK,gBAAgB,SAAS,WAAW,EAC1D,IAAI,YAAY;AAClB,QAAI,MAAM,OAAQ,SAAQ,cAAc;AACxC,WAAO;AAAA,EACR,CAAC;AAED,QAAM,aAAsC;AAAA,IAC3C,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAM,KAAK;AAAA,EACZ;AACA,MAAI,KAAK,IAAK,YAAW,MAAM,KAAK;AACpC,MAAI,KAAK,MAAO,YAAW,QAAQ,KAAK;AACxC,MAAI,KAAK,YAAa,YAAW,cAAc,KAAK;AACpD,MAAI,KAAK,eAAe,OAAQ,YAAW,gBAAgB,KAAK;AAChE,aAAW,UAAU;AAAA,IACpB,SAAS;AAAA,IACT,MAAM;AAAA,IACN;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
|
package/dist/seo.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/seo.ts
|
|
2
|
+
function priceString(cents) {
|
|
3
|
+
return (cents / 100).toFixed(2);
|
|
4
|
+
}
|
|
5
|
+
function variantOffer(variant) {
|
|
6
|
+
return {
|
|
7
|
+
"@type": "Offer",
|
|
8
|
+
price: priceString(variant.display_price ?? variant.price),
|
|
9
|
+
priceCurrency: "USD"
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function menuItemNode(item) {
|
|
13
|
+
const node = {
|
|
14
|
+
"@type": "MenuItem",
|
|
15
|
+
name: item.name
|
|
16
|
+
};
|
|
17
|
+
if (item.description) node.description = item.description;
|
|
18
|
+
const offers = item.variants.map(variantOffer);
|
|
19
|
+
if (offers.length === 1) node.offers = offers[0];
|
|
20
|
+
else if (offers.length > 1) node.offers = offers;
|
|
21
|
+
return node;
|
|
22
|
+
}
|
|
23
|
+
function buildMenuItemJsonLd(item) {
|
|
24
|
+
return { "@context": "https://schema.org", ...menuItemNode(item) };
|
|
25
|
+
}
|
|
26
|
+
function buildRestaurantJsonLd(menu, info) {
|
|
27
|
+
const hasMenuSection = menu.categories.map((category) => {
|
|
28
|
+
const section = {
|
|
29
|
+
"@type": "MenuSection",
|
|
30
|
+
name: category.name
|
|
31
|
+
};
|
|
32
|
+
const items = menu.items.filter((item) => item.category_id === category.category_id).map(menuItemNode);
|
|
33
|
+
if (items.length) section.hasMenuItem = items;
|
|
34
|
+
return section;
|
|
35
|
+
});
|
|
36
|
+
const restaurant = {
|
|
37
|
+
"@context": "https://schema.org",
|
|
38
|
+
"@type": "Restaurant",
|
|
39
|
+
name: info.name
|
|
40
|
+
};
|
|
41
|
+
if (info.url) restaurant.url = info.url;
|
|
42
|
+
if (info.image) restaurant.image = info.image;
|
|
43
|
+
if (info.description) restaurant.description = info.description;
|
|
44
|
+
if (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;
|
|
45
|
+
restaurant.hasMenu = {
|
|
46
|
+
"@type": "Menu",
|
|
47
|
+
name: "Menu",
|
|
48
|
+
hasMenuSection
|
|
49
|
+
};
|
|
50
|
+
return restaurant;
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
buildMenuItemJsonLd,
|
|
54
|
+
buildRestaurantJsonLd
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=seo.mjs.map
|
package/dist/seo.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo.ts"],"sourcesContent":["import type { Menu } from \"@zorgo/universal/utils/menuUtils\";\nimport type { ItemVariant, MenuItem } from \"@zorgo/universal/interfaces\";\n\n/** Franchise-level facts for the Restaurant node. Everything but `name` is\n * optional so the schema degrades gracefully when a field is missing. */\nexport interface RestaurantInfo {\n\tname: string;\n\turl?: string;\n\timage?: string;\n\tdescription?: string;\n\tservesCuisine?: string[];\n}\n\n// cents -> \"5.49\" (schema.org Offer.price is a string in major currency units)\nfunction priceString(cents: number): string {\n\treturn (cents / 100).toFixed(2);\n}\n\nfunction variantOffer(variant: ItemVariant) {\n\treturn {\n\t\t\"@type\": \"Offer\",\n\t\tprice: priceString(variant.display_price ?? variant.price),\n\t\tpriceCurrency: \"USD\",\n\t};\n}\n\nfunction menuItemNode(item: MenuItem) {\n\tconst node: Record<string, unknown> = {\n\t\t\"@type\": \"MenuItem\",\n\t\tname: item.name,\n\t};\n\tif (item.description) node.description = item.description;\n\t// one Offer for a single price, an array when the item has size/variant prices\n\tconst offers = item.variants.map(variantOffer);\n\tif (offers.length === 1) node.offers = offers[0];\n\telse if (offers.length > 1) node.offers = offers;\n\treturn node;\n}\n\n/**\n * Builds a standalone schema.org JSON-LD object for a single menu item, for\n * embedding in that item's `/menu/[slug]` page `<head>`. Reuses `menuItemNode`\n * (the same node shape used inside the Restaurant menu) and adds the\n * `@context` a top-level node needs. Pure — safe to run at build time.\n */\nexport function buildMenuItemJsonLd(item: MenuItem) {\n\treturn { \"@context\": \"https://schema.org\", ...menuItemNode(item) };\n}\n\n/**\n * Builds a schema.org Restaurant JSON-LD object from a prebuilt Menu and\n * franchise info. Categories become MenuSections; the items in each become\n * MenuItems. Pure — same inputs, same output — so it can run at prebuild time\n * and be embedded straight into the site's <head>.\n */\nexport function buildRestaurantJsonLd(menu: Menu, info: RestaurantInfo) {\n\tconst hasMenuSection = menu.categories.map((category) => {\n\t\tconst section: Record<string, unknown> = {\n\t\t\t\"@type\": \"MenuSection\",\n\t\t\tname: category.name,\n\t\t};\n\t\tconst items = menu.items\n\t\t\t.filter((item) => item.category_id === category.category_id)\n\t\t\t.map(menuItemNode);\n\t\tif (items.length) section.hasMenuItem = items;\n\t\treturn section;\n\t});\n\n\tconst restaurant: Record<string, unknown> = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"Restaurant\",\n\t\tname: info.name,\n\t};\n\tif (info.url) restaurant.url = info.url;\n\tif (info.image) restaurant.image = info.image;\n\tif (info.description) restaurant.description = info.description;\n\tif (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;\n\trestaurant.hasMenu = {\n\t\t\"@type\": \"Menu\",\n\t\tname: \"Menu\",\n\t\thasMenuSection,\n\t};\n\n\treturn restaurant;\n}\n"],"mappings":";AAcA,SAAS,YAAY,OAAuB;AAC3C,UAAQ,QAAQ,KAAK,QAAQ,CAAC;AAC/B;AAEA,SAAS,aAAa,SAAsB;AAC3C,SAAO;AAAA,IACN,SAAS;AAAA,IACT,OAAO,YAAY,QAAQ,iBAAiB,QAAQ,KAAK;AAAA,IACzD,eAAe;AAAA,EAChB;AACD;AAEA,SAAS,aAAa,MAAgB;AACrC,QAAM,OAAgC;AAAA,IACrC,SAAS;AAAA,IACT,MAAM,KAAK;AAAA,EACZ;AACA,MAAI,KAAK,YAAa,MAAK,cAAc,KAAK;AAE9C,QAAM,SAAS,KAAK,SAAS,IAAI,YAAY;AAC7C,MAAI,OAAO,WAAW,EAAG,MAAK,SAAS,OAAO,CAAC;AAAA,WACtC,OAAO,SAAS,EAAG,MAAK,SAAS;AAC1C,SAAO;AACR;AAQO,SAAS,oBAAoB,MAAgB;AACnD,SAAO,EAAE,YAAY,sBAAsB,GAAG,aAAa,IAAI,EAAE;AAClE;AAQO,SAAS,sBAAsB,MAAY,MAAsB;AACvE,QAAM,iBAAiB,KAAK,WAAW,IAAI,CAAC,aAAa;AACxD,UAAM,UAAmC;AAAA,MACxC,SAAS;AAAA,MACT,MAAM,SAAS;AAAA,IAChB;AACA,UAAM,QAAQ,KAAK,MACjB,OAAO,CAAC,SAAS,KAAK,gBAAgB,SAAS,WAAW,EAC1D,IAAI,YAAY;AAClB,QAAI,MAAM,OAAQ,SAAQ,cAAc;AACxC,WAAO;AAAA,EACR,CAAC;AAED,QAAM,aAAsC;AAAA,IAC3C,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,MAAM,KAAK;AAAA,EACZ;AACA,MAAI,KAAK,IAAK,YAAW,MAAM,KAAK;AACpC,MAAI,KAAK,MAAO,YAAW,QAAQ,KAAK;AACxC,MAAI,KAAK,YAAa,YAAW,cAAc,KAAK;AACpD,MAAI,KAAK,eAAe,OAAQ,YAAW,gBAAgB,KAAK;AAChE,aAAW,UAAU;AAAA,IACpB,SAAS;AAAA,IACT,MAAM;AAAA,IACN;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
|
package/dist/slugs.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { baseItemIdFromSlug, menuItemSlug } from '@zorgo/universal/utils/menuSlugs';
|
package/dist/slugs.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { baseItemIdFromSlug, menuItemSlug } from '@zorgo/universal/utils/menuSlugs';
|
package/dist/slugs.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/slugs.ts
|
|
21
|
+
var slugs_exports = {};
|
|
22
|
+
__export(slugs_exports, {
|
|
23
|
+
baseItemIdFromSlug: () => baseItemIdFromSlug,
|
|
24
|
+
menuItemSlug: () => menuItemSlug
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(slugs_exports);
|
|
27
|
+
|
|
28
|
+
// ../zorgo/universal/utils/menuSlugs.ts
|
|
29
|
+
function menuItemNameToSlug(name) {
|
|
30
|
+
return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").trim();
|
|
31
|
+
}
|
|
32
|
+
function menuItemSlug(name, baseItemId) {
|
|
33
|
+
return `${menuItemNameToSlug(name)}-${baseItemId}`;
|
|
34
|
+
}
|
|
35
|
+
function baseItemIdFromSlug(slug) {
|
|
36
|
+
const match = slug.match(/-(\d+)$/);
|
|
37
|
+
if (!match) {
|
|
38
|
+
throw new Error(`No base_item_id found in slug: ${slug}`);
|
|
39
|
+
}
|
|
40
|
+
return Number(match[1]);
|
|
41
|
+
}
|
|
42
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
43
|
+
0 && (module.exports = {
|
|
44
|
+
baseItemIdFromSlug,
|
|
45
|
+
menuItemSlug
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=slugs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/slugs.ts","../../zorgo/universal/utils/menuSlugs.ts"],"sourcesContent":["export { menuItemSlug, baseItemIdFromSlug } from \"@zorgo/universal/utils/menuSlugs\";\n","import { supabase } from \"./supabase\";\n\nexport interface MenuSlugParam {\n\tslug: string;\n}\n\nexport interface BaseMenuSlugRecord {\n\tbaseItemId: number;\n\tname: string;\n\tslug: string;\n\tdescription: string | null;\n\timagePath: string | null;\n}\n\nfunction requireApiKey(apiKey?: string): string {\n\tconst resolvedApiKey = apiKey ?? process.env.ZORGO_API_KEY;\n\tif (!resolvedApiKey) {\n\t\tthrow new Error(\"ZORGO_API_KEY is not set\");\n\t}\n\treturn resolvedApiKey;\n}\n\nexport function menuItemNameToSlug(name: string): string {\n\treturn name\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9\\s-]/g, \"\")\n\t\t.replace(/\\s+/g, \"-\")\n\t\t.trim();\n}\n\n/** Builds the `/menu/[slug]` route segment for an item, e.g. \"chicken-burrito-42\". */\nexport function menuItemSlug(name: string, baseItemId: number): string {\n\treturn `${menuItemNameToSlug(name)}-${baseItemId}`;\n}\n\n/** Parses the trailing numeric base_item_id off a `menuItemSlug` result. */\nexport function baseItemIdFromSlug(slug: string): number {\n\tconst match = slug.match(/-(\\d+)$/);\n\tif (!match) {\n\t\tthrow new Error(`No base_item_id found in slug: ${slug}`);\n\t}\n\treturn Number(match[1]);\n}\n\nexport function buildSlugParam(name: string): MenuSlugParam {\n\treturn { slug: menuItemNameToSlug(name) };\n}\n\nexport function buildSlugParamsFromNames(names: string[]): MenuSlugParam[] {\n\treturn names.map(buildSlugParam);\n}\n\nfunction assertUniqueSlugs(items: Array<{ name: string; slug: string }>): void {\n\tconst duplicates = new Map<string, string[]>();\n\n\tfor (const item of items) {\n\t\tconst matches = duplicates.get(item.slug) ?? [];\n\t\tmatches.push(item.name);\n\t\tduplicates.set(item.slug, matches);\n\t}\n\n\tconst conflicts = Array.from(duplicates.entries()).filter(\n\t\t([, names]) => names.length > 1,\n\t);\n\tif (conflicts.length === 0) return;\n\n\tconst message = conflicts\n\t\t.map(([slug, names]) => `\"${slug}\" => ${names.join(\", \")}`)\n\t\t.join(\"; \");\n\tthrow new Error(`Duplicate menu slugs detected: ${message}`);\n}\n\nasync function getFranchiseIdFromApiKey(apiKey?: string): Promise<number> {\n\tconst keyValue = requireApiKey(apiKey);\n\tconst { data, error } = await supabase\n\t\t.from(\"api_keys\")\n\t\t.select(\"franchise_id\")\n\t\t.eq(\"key_value\", keyValue)\n\t\t.eq(\"is_active\", true)\n\t\t.single();\n\n\tif (error || !data?.franchise_id) {\n\t\tthrow new Error(\"Trouble finding franchise for API key\");\n\t}\n\n\treturn data.franchise_id;\n}\n\nexport async function getBaseMenuSlugRecordsByApiKey(\n\tapiKey?: string,\n\toptions: { assertUniqueSlugs?: boolean } = {},\n): Promise<BaseMenuSlugRecord[]> {\n\tconst { assertUniqueSlugs: shouldAssertUniqueSlugs = true } = options;\n\tconst franchiseId = await getFranchiseIdFromApiKey(apiKey);\n\tconst { data, error } = await supabase\n\t\t.from(\"base_items\")\n\t\t.select(\"base_item_id, name, description, image_path\")\n\t\t.eq(\"franchise_id\", franchiseId)\n\t\t.order(\"base_item_id\", { ascending: true });\n\n\tif (error || !data) {\n\t\tthrow new Error(\"Trouble loading base menu items for franchise\");\n\t}\n\n\tconst records = data\n\t\t.filter((item) => typeof item.name === \"string\" && item.name.length > 0)\n\t\t.map((item) => ({\n\t\t\tbaseItemId: item.base_item_id,\n\t\t\tname: item.name,\n\t\t\tslug: menuItemNameToSlug(item.name),\n\t\t\tdescription: item.description ?? null,\n\t\t\timagePath: item.image_path ?? null,\n\t\t}));\n\n\tif (shouldAssertUniqueSlugs) {\n\t\tassertUniqueSlugs(records);\n\t}\n\treturn records;\n}\n\nexport async function getBaseMenuSlugParamsByApiKey(\n\tapiKey?: string,\n): Promise<MenuSlugParam[]> {\n\tconst records = await getBaseMenuSlugRecordsByApiKey(apiKey);\n\treturn records.map(({ slug }) => ({ slug }));\n}\n\nexport async function getBaseMenuSlugRecordBySlug(\n\tslug: string,\n\tapiKey?: string,\n): Promise<BaseMenuSlugRecord | null> {\n\tconst records = await getBaseMenuSlugRecordsByApiKey(apiKey);\n\treturn records.find((record) => record.slug === slug) ?? null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACsBO,SAAS,mBAAmB,MAAsB;AACxD,SAAO,KACL,YAAY,EACZ,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACR;AAGO,SAAS,aAAa,MAAc,YAA4B;AACtE,SAAO,GAAG,mBAAmB,IAAI,CAAC,IAAI,UAAU;AACjD;AAGO,SAAS,mBAAmB,MAAsB;AACxD,QAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,MAAI,CAAC,OAAO;AACX,UAAM,IAAI,MAAM,kCAAkC,IAAI,EAAE;AAAA,EACzD;AACA,SAAO,OAAO,MAAM,CAAC,CAAC;AACvB;","names":[]}
|
package/dist/slugs.mjs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// ../zorgo/universal/utils/menuSlugs.ts
|
|
2
|
+
function menuItemNameToSlug(name) {
|
|
3
|
+
return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").trim();
|
|
4
|
+
}
|
|
5
|
+
function menuItemSlug(name, baseItemId) {
|
|
6
|
+
return `${menuItemNameToSlug(name)}-${baseItemId}`;
|
|
7
|
+
}
|
|
8
|
+
function baseItemIdFromSlug(slug) {
|
|
9
|
+
const match = slug.match(/-(\d+)$/);
|
|
10
|
+
if (!match) {
|
|
11
|
+
throw new Error(`No base_item_id found in slug: ${slug}`);
|
|
12
|
+
}
|
|
13
|
+
return Number(match[1]);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
baseItemIdFromSlug,
|
|
17
|
+
menuItemSlug
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=slugs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../zorgo/universal/utils/menuSlugs.ts"],"sourcesContent":["import { supabase } from \"./supabase\";\n\nexport interface MenuSlugParam {\n\tslug: string;\n}\n\nexport interface BaseMenuSlugRecord {\n\tbaseItemId: number;\n\tname: string;\n\tslug: string;\n\tdescription: string | null;\n\timagePath: string | null;\n}\n\nfunction requireApiKey(apiKey?: string): string {\n\tconst resolvedApiKey = apiKey ?? process.env.ZORGO_API_KEY;\n\tif (!resolvedApiKey) {\n\t\tthrow new Error(\"ZORGO_API_KEY is not set\");\n\t}\n\treturn resolvedApiKey;\n}\n\nexport function menuItemNameToSlug(name: string): string {\n\treturn name\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9\\s-]/g, \"\")\n\t\t.replace(/\\s+/g, \"-\")\n\t\t.trim();\n}\n\n/** Builds the `/menu/[slug]` route segment for an item, e.g. \"chicken-burrito-42\". */\nexport function menuItemSlug(name: string, baseItemId: number): string {\n\treturn `${menuItemNameToSlug(name)}-${baseItemId}`;\n}\n\n/** Parses the trailing numeric base_item_id off a `menuItemSlug` result. */\nexport function baseItemIdFromSlug(slug: string): number {\n\tconst match = slug.match(/-(\\d+)$/);\n\tif (!match) {\n\t\tthrow new Error(`No base_item_id found in slug: ${slug}`);\n\t}\n\treturn Number(match[1]);\n}\n\nexport function buildSlugParam(name: string): MenuSlugParam {\n\treturn { slug: menuItemNameToSlug(name) };\n}\n\nexport function buildSlugParamsFromNames(names: string[]): MenuSlugParam[] {\n\treturn names.map(buildSlugParam);\n}\n\nfunction assertUniqueSlugs(items: Array<{ name: string; slug: string }>): void {\n\tconst duplicates = new Map<string, string[]>();\n\n\tfor (const item of items) {\n\t\tconst matches = duplicates.get(item.slug) ?? [];\n\t\tmatches.push(item.name);\n\t\tduplicates.set(item.slug, matches);\n\t}\n\n\tconst conflicts = Array.from(duplicates.entries()).filter(\n\t\t([, names]) => names.length > 1,\n\t);\n\tif (conflicts.length === 0) return;\n\n\tconst message = conflicts\n\t\t.map(([slug, names]) => `\"${slug}\" => ${names.join(\", \")}`)\n\t\t.join(\"; \");\n\tthrow new Error(`Duplicate menu slugs detected: ${message}`);\n}\n\nasync function getFranchiseIdFromApiKey(apiKey?: string): Promise<number> {\n\tconst keyValue = requireApiKey(apiKey);\n\tconst { data, error } = await supabase\n\t\t.from(\"api_keys\")\n\t\t.select(\"franchise_id\")\n\t\t.eq(\"key_value\", keyValue)\n\t\t.eq(\"is_active\", true)\n\t\t.single();\n\n\tif (error || !data?.franchise_id) {\n\t\tthrow new Error(\"Trouble finding franchise for API key\");\n\t}\n\n\treturn data.franchise_id;\n}\n\nexport async function getBaseMenuSlugRecordsByApiKey(\n\tapiKey?: string,\n\toptions: { assertUniqueSlugs?: boolean } = {},\n): Promise<BaseMenuSlugRecord[]> {\n\tconst { assertUniqueSlugs: shouldAssertUniqueSlugs = true } = options;\n\tconst franchiseId = await getFranchiseIdFromApiKey(apiKey);\n\tconst { data, error } = await supabase\n\t\t.from(\"base_items\")\n\t\t.select(\"base_item_id, name, description, image_path\")\n\t\t.eq(\"franchise_id\", franchiseId)\n\t\t.order(\"base_item_id\", { ascending: true });\n\n\tif (error || !data) {\n\t\tthrow new Error(\"Trouble loading base menu items for franchise\");\n\t}\n\n\tconst records = data\n\t\t.filter((item) => typeof item.name === \"string\" && item.name.length > 0)\n\t\t.map((item) => ({\n\t\t\tbaseItemId: item.base_item_id,\n\t\t\tname: item.name,\n\t\t\tslug: menuItemNameToSlug(item.name),\n\t\t\tdescription: item.description ?? null,\n\t\t\timagePath: item.image_path ?? null,\n\t\t}));\n\n\tif (shouldAssertUniqueSlugs) {\n\t\tassertUniqueSlugs(records);\n\t}\n\treturn records;\n}\n\nexport async function getBaseMenuSlugParamsByApiKey(\n\tapiKey?: string,\n): Promise<MenuSlugParam[]> {\n\tconst records = await getBaseMenuSlugRecordsByApiKey(apiKey);\n\treturn records.map(({ slug }) => ({ slug }));\n}\n\nexport async function getBaseMenuSlugRecordBySlug(\n\tslug: string,\n\tapiKey?: string,\n): Promise<BaseMenuSlugRecord | null> {\n\tconst records = await getBaseMenuSlugRecordsByApiKey(apiKey);\n\treturn records.find((record) => record.slug === slug) ?? null;\n}\n"],"mappings":";AAsBO,SAAS,mBAAmB,MAAsB;AACxD,SAAO,KACL,YAAY,EACZ,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACR;AAGO,SAAS,aAAa,MAAc,YAA4B;AACtE,SAAO,GAAG,mBAAmB,IAAI,CAAC,IAAI,UAAU;AACjD;AAGO,SAAS,mBAAmB,MAAsB;AACxD,QAAM,QAAQ,KAAK,MAAM,SAAS;AAClC,MAAI,CAAC,OAAO;AACX,UAAM,IAAI,MAAM,kCAAkC,IAAI,EAAE;AAAA,EACzD;AACA,SAAO,OAAO,MAAM,CAAC,CAAC;AACvB;","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SUPABASE_URL } from "@zorgo/universal/constants";
|
|
2
|
+
import type { ScaffoldOptions } from "../../types";
|
|
3
|
+
|
|
4
|
+
// .env is the single source of truth for this site. Everything that varies per
|
|
5
|
+
// project (which API environment, the site URL, the worker name) lives here;
|
|
6
|
+
// scripts/prebuild.ts reads these back to regenerate wrangler.toml on every
|
|
7
|
+
// build, so wrangler.toml is a build artifact — edit .env, not it.
|
|
8
|
+
export default function generateEnvFile(opts: ScaffoldOptions): string {
|
|
9
|
+
const lines = [
|
|
10
|
+
// which Zorgo API this site talks to (an Environments enum value). read by
|
|
11
|
+
// @zorgo constants (client), scripts/prebuild.ts, and lib/zorgoSiteToken.ts.
|
|
12
|
+
`NEXT_PUBLIC_API_BASE_URL=${opts.apiBaseUrl}`,
|
|
13
|
+
// the site's own origin — read by sitemap.ts, robots.ts, layout, stores.
|
|
14
|
+
`NEXT_PUBLIC_SITE_URL=${opts.projectUrl}`,
|
|
15
|
+
`NEXT_PUBLIC_BASE_URL=${opts.projectUrl}`,
|
|
16
|
+
`NEXT_PUBLIC_SUPABASE_URL=${SUPABASE_URL}`,
|
|
17
|
+
// worker name for the generated wrangler.toml.
|
|
18
|
+
`ZORGO_PROJECT_NAME=${opts.projectName}`,
|
|
19
|
+
];
|
|
20
|
+
if (opts.stripePublishableKey) {
|
|
21
|
+
lines.push(`NEXT_PUBLIC_STRIPE_PUBLIC_KEY=${opts.stripePublishableKey}`);
|
|
22
|
+
}
|
|
23
|
+
if (opts.websiteSecret) {
|
|
24
|
+
lines.push(`ZORGO_SITE_SECRET=${opts.websiteSecret}`);
|
|
25
|
+
}
|
|
26
|
+
return lines.join("\n") + "\n";
|
|
27
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { fetchSiteToken } from "../../../lib/zorgoSiteToken";
|
|
4
|
+
|
|
5
|
+
export async function GET() {
|
|
6
|
+
const token = await fetchSiteToken();
|
|
7
|
+
if (!token) {
|
|
8
|
+
return NextResponse.json({ error: "Missing or invalid ZORGO_SITE_SECRET env var" }, { status: 500 });
|
|
9
|
+
}
|
|
10
|
+
return NextResponse.json({ token });
|
|
11
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ScaffoldOptions } from "../../../types";
|
|
2
|
+
|
|
3
|
+
// Mirrors icon.tsx.gen.ts but at the 180x180 Apple touch-icon size iOS uses for
|
|
4
|
+
// "Add to Home Screen". Kept as its own file because Next.js maps the
|
|
5
|
+
// `apple-icon` convention to <link rel="apple-touch-icon"> separately from `icon`.
|
|
6
|
+
export default function generateAppleIcon(opts: ScaffoldOptions): string {
|
|
7
|
+
const letter = opts.projectName.charAt(0).toUpperCase();
|
|
8
|
+
|
|
9
|
+
return `import { ImageResponse } from "next/og";
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
|
|
13
|
+
export const size = { width: 180, height: 180 };
|
|
14
|
+
export const contentType = "image/png";
|
|
15
|
+
|
|
16
|
+
// Brand palette prebuild drops at public/styles/styles.json. Neutral fallback if
|
|
17
|
+
// it's missing so the icon still renders.
|
|
18
|
+
function loadTheme() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(
|
|
21
|
+
readFileSync(join(process.cwd(), "public/styles/styles.json"), "utf8"),
|
|
22
|
+
);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function AppleIcon() {
|
|
29
|
+
const theme = loadTheme();
|
|
30
|
+
const background = theme?.primary ?? "#171717";
|
|
31
|
+
const foreground = theme?.primaryForeground ?? "#ffffff";
|
|
32
|
+
return new ImageResponse(
|
|
33
|
+
(
|
|
34
|
+
<div
|
|
35
|
+
style={{
|
|
36
|
+
width: "100%",
|
|
37
|
+
height: "100%",
|
|
38
|
+
display: "flex",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
justifyContent: "center",
|
|
41
|
+
background,
|
|
42
|
+
color: foreground,
|
|
43
|
+
fontSize: 112,
|
|
44
|
+
fontWeight: 700,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
${letter}
|
|
48
|
+
</div>
|
|
49
|
+
),
|
|
50
|
+
size,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
|
|
3
|
+
export default function Footer() {
|
|
4
|
+
const pages = [
|
|
5
|
+
{
|
|
6
|
+
name: "Home",
|
|
7
|
+
href: "/",
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: "Locations",
|
|
11
|
+
href: "/locations",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "Menu",
|
|
15
|
+
href: "/menu",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "Privacy Policy",
|
|
19
|
+
href: "/privacy",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "Terms of Service",
|
|
23
|
+
href: "/terms",
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
return (
|
|
27
|
+
<footer className="w-full p-6 bg-background-recessed flex flex-col gap-4">
|
|
28
|
+
<div className="flex flex-col md:flex-row md:items-center gap-4">
|
|
29
|
+
{pages.map((page) => (
|
|
30
|
+
<Link
|
|
31
|
+
href={page.href}
|
|
32
|
+
key={page.name}
|
|
33
|
+
className="text-md opacity-50 hover:opacity-100 transition-opacity duration-300 font-semibold"
|
|
34
|
+
>
|
|
35
|
+
{page.name}
|
|
36
|
+
</Link>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
<p className="text-md">
|
|
40
|
+
<span className="opacity-75">Made with ❤️ by </span>
|
|
41
|
+
<Link
|
|
42
|
+
href="https://zorgotech.com"
|
|
43
|
+
className="opacity-75 hover:opacity-100 transition-opacity duration-300 font-semibold"
|
|
44
|
+
>
|
|
45
|
+
Zorgo
|
|
46
|
+
</Link>
|
|
47
|
+
</p>
|
|
48
|
+
</footer>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import Image from "next/image";
|
|
4
|
+
import { useLocation, useCart } from "@zorgo/next";
|
|
5
|
+
import { ShoppingCart, MapPin } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
const pages = [
|
|
8
|
+
{
|
|
9
|
+
name: "Home",
|
|
10
|
+
href: "/",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "Locations",
|
|
14
|
+
href: "/locations",
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export default function Header() {
|
|
19
|
+
const { currentLocation } = useLocation();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="p-4 pr-12 pl-6 w-full bg-background-elevated flex flex-row justify-between items-center sticky top-0 z-50 text-foreground">
|
|
23
|
+
<Logo />
|
|
24
|
+
|
|
25
|
+
<div className="flex flex-row items-center justify-center gap-6 md:gap-12 h-full">
|
|
26
|
+
<Link
|
|
27
|
+
href="/locations"
|
|
28
|
+
className="flex items-center gap-2 hover:opacity-50 transition-opacity duration-300"
|
|
29
|
+
>
|
|
30
|
+
<MapPin className="w-8 h-8" strokeWidth={2} />
|
|
31
|
+
<p className="text-xl font-semibold hidden md:block">
|
|
32
|
+
{currentLocation?.name ?? "Find a Location"}
|
|
33
|
+
</p>
|
|
34
|
+
</Link>
|
|
35
|
+
<Link href="/menu">
|
|
36
|
+
<div className="bg-primary text-primary-foreground text-xl font-bold p-3 md:px-8 rounded-lg hover:opacity-50 transition-opacity duration-300">
|
|
37
|
+
Order
|
|
38
|
+
</div>
|
|
39
|
+
</Link>
|
|
40
|
+
<CartButton />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function NavLinks() {
|
|
47
|
+
return (
|
|
48
|
+
<div className=" h-full flex items-center justify-center">
|
|
49
|
+
<div className="flex-1">
|
|
50
|
+
{pages.map((page) => (
|
|
51
|
+
<Link href={page.href} key={page.name} className="p-4">
|
|
52
|
+
{page.name}
|
|
53
|
+
</Link>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function LocationDropdown() {
|
|
61
|
+
const { availableLocations, currentLocation, setLocation } = useLocation();
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<select
|
|
65
|
+
className="text-xl font-semibold"
|
|
66
|
+
value={currentLocation?.location_id ?? ""}
|
|
67
|
+
onChange={(e) => {
|
|
68
|
+
const next = availableLocations.find(
|
|
69
|
+
(location) =>
|
|
70
|
+
location.location_id === parseInt(e.target.value),
|
|
71
|
+
);
|
|
72
|
+
if (next) setLocation(next);
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<option value="" disabled>
|
|
76
|
+
Select a location
|
|
77
|
+
</option>
|
|
78
|
+
{availableLocations.map((location) => (
|
|
79
|
+
<option
|
|
80
|
+
key={location.location_id}
|
|
81
|
+
value={location.location_id.toString()}
|
|
82
|
+
>
|
|
83
|
+
{location.name}
|
|
84
|
+
</option>
|
|
85
|
+
))}
|
|
86
|
+
</select>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Logo() {
|
|
91
|
+
return (
|
|
92
|
+
<Link
|
|
93
|
+
href="/"
|
|
94
|
+
className="hover:opacity-50 transition-opacity duration-300"
|
|
95
|
+
>
|
|
96
|
+
{/* bg-primary shows through until logo.png (written by scripts/prebuild.ts) paints */}
|
|
97
|
+
<div className="bg-primary w-16 h-16 rounded-lg overflow-hidden">
|
|
98
|
+
<Image
|
|
99
|
+
src="/images/logo.png"
|
|
100
|
+
alt="Home"
|
|
101
|
+
width={64}
|
|
102
|
+
height={64}
|
|
103
|
+
className="w-full h-full object-cover"
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
</Link>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function CartButton() {
|
|
111
|
+
const { items } = useCart();
|
|
112
|
+
const itemCount = items.length;
|
|
113
|
+
return (
|
|
114
|
+
<Link
|
|
115
|
+
href="/checkout"
|
|
116
|
+
className="relative cursor-pointer hover:opacity-50 transition-opacity duration-300"
|
|
117
|
+
>
|
|
118
|
+
<ShoppingCart className="w-8 h-8" strokeWidth={2} />
|
|
119
|
+
<span
|
|
120
|
+
className={`absolute -top-1.5 -right-2 min-w-5 h-5 px-1 rounded-full flex items-center justify-center text-xs font-semibold text-primary-foreground ${
|
|
121
|
+
itemCount > 0 ? "bg-primary" : "bg-background"
|
|
122
|
+
}`}
|
|
123
|
+
>
|
|
124
|
+
{itemCount}
|
|
125
|
+
</span>
|
|
126
|
+
</Link>
|
|
127
|
+
);
|
|
128
|
+
}
|