@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
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { loadEnvConfig } from "@next/env";
|
|
4
|
+
import { Menu } from "@zorgo/next";
|
|
5
|
+
import { buildRestaurantJsonLd } from "@zorgo/next/seo";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { fetchSiteToken } from "../lib/zorgoSiteToken";
|
|
8
|
+
|
|
9
|
+
loadEnvConfig(process.cwd());
|
|
10
|
+
|
|
11
|
+
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:3000";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
todo list:
|
|
15
|
+
1. bring color functions out into their own utility file
|
|
16
|
+
2. clean up this file and move utilities elsewhere
|
|
17
|
+
3. get rid of all references to the supabaseUrl. should be pulled thru by whichever environment is specified
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
function writeWranglerToml() {
|
|
21
|
+
const name = process.env.ZORGO_PROJECT_NAME ?? "zorgo-site";
|
|
22
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "";
|
|
23
|
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
|
|
24
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? siteUrl;
|
|
25
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
|
26
|
+
|
|
27
|
+
// custom_domain routes need a bare hostname; skip them if the site URL isn't
|
|
28
|
+
// a parseable absolute URL (e.g. a local/placeholder value).
|
|
29
|
+
let host: string | undefined;
|
|
30
|
+
try {
|
|
31
|
+
host = new URL(siteUrl).hostname;
|
|
32
|
+
} catch {
|
|
33
|
+
host = undefined;
|
|
34
|
+
}
|
|
35
|
+
const routes = host
|
|
36
|
+
? `
|
|
37
|
+
[[routes]]
|
|
38
|
+
pattern = "${host}"
|
|
39
|
+
custom_domain = true
|
|
40
|
+
|
|
41
|
+
[[routes]]
|
|
42
|
+
pattern = "www.${host}"
|
|
43
|
+
custom_domain = true
|
|
44
|
+
`
|
|
45
|
+
: "";
|
|
46
|
+
|
|
47
|
+
const toml = `# AUTO-GENERATED by scripts/prebuild.ts from .env — do not edit.
|
|
48
|
+
name = "${name}"
|
|
49
|
+
main = ".open-next/worker.js"
|
|
50
|
+
compatibility_date = "2024-09-23"
|
|
51
|
+
compatibility_flags = ["nodejs_compat"]
|
|
52
|
+
|
|
53
|
+
[assets]
|
|
54
|
+
directory = ".open-next/assets"
|
|
55
|
+
binding = "ASSETS"
|
|
56
|
+
|
|
57
|
+
[vars]
|
|
58
|
+
NEXT_PUBLIC_SUPABASE_URL = "${supabaseUrl}"
|
|
59
|
+
NEXT_PUBLIC_API_BASE_URL = "${apiBaseUrl}"
|
|
60
|
+
NEXT_PUBLIC_SITE_URL = "${siteUrl}"
|
|
61
|
+
NEXT_PUBLIC_BASE_URL = "${baseUrl}"
|
|
62
|
+
|
|
63
|
+
[observability]
|
|
64
|
+
enabled = true
|
|
65
|
+
|
|
66
|
+
[observability.logs]
|
|
67
|
+
enabled = true
|
|
68
|
+
invocation_logs = true
|
|
69
|
+
${routes}`;
|
|
70
|
+
|
|
71
|
+
writeFileSync(path.join(process.cwd(), "wrangler.toml"), toml);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface FetchArgs {
|
|
75
|
+
franchiseId: number;
|
|
76
|
+
token: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const BrandingSchema = z
|
|
80
|
+
.object({
|
|
81
|
+
backgroundBase: z.string().default("#F5F5F5"),
|
|
82
|
+
primary: z.string().default("#3264FF"),
|
|
83
|
+
accent: z.string().default("#9696FF"),
|
|
84
|
+
fontFamily: z.string().default("Inter"), // Google Font family name
|
|
85
|
+
})
|
|
86
|
+
.prefault({});
|
|
87
|
+
type Branding = z.infer<typeof BrandingSchema>;
|
|
88
|
+
|
|
89
|
+
// schemas
|
|
90
|
+
const FranchiseSchema = z
|
|
91
|
+
.object({
|
|
92
|
+
name: z.string().default(process.env.ZORGO_PROJECT_NAME ?? "Restaurant"),
|
|
93
|
+
site_url: z.string().optional(),
|
|
94
|
+
})
|
|
95
|
+
.prefault({});
|
|
96
|
+
|
|
97
|
+
const HoursSchema = z.array(z.unknown()).prefault([]);
|
|
98
|
+
|
|
99
|
+
const MenuSchema = z.object({
|
|
100
|
+
items: z.array(z.unknown()).default([]),
|
|
101
|
+
categories: z.array(z.unknown()).default([]),
|
|
102
|
+
components: z.array(z.unknown()).default([]),
|
|
103
|
+
rules: z.array(z.unknown()).default([]),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async function fetchData<T>(
|
|
108
|
+
endpoint: string,
|
|
109
|
+
token: string,
|
|
110
|
+
schema: z.ZodType<T>,
|
|
111
|
+
): Promise<T> {
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
114
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
115
|
+
});
|
|
116
|
+
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
|
|
117
|
+
const body = await res.json();
|
|
118
|
+
return schema.parse(body?.data ?? body);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(`prebuild: ${endpoint} failed, using defaults:`, err);
|
|
121
|
+
return schema.parse(undefined);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// style stuff
|
|
126
|
+
const FONT_ON_LIGHT = "#0a0a0a";
|
|
127
|
+
const FONT_ON_DARK = "#f5f5f5";
|
|
128
|
+
/** Neutral channel delta toward white (elevated) / black (recessed). */
|
|
129
|
+
const BACKGROUND_SURFACE_DELTA = 15;
|
|
130
|
+
/**
|
|
131
|
+
* Max warm/cool bias via R↔B trade (full strength on light bases).
|
|
132
|
+
* Scaled down by luminance so darker colors shift less.
|
|
133
|
+
*/
|
|
134
|
+
const BACKGROUND_TEMPERATURE_BIAS = 8;
|
|
135
|
+
|
|
136
|
+
function parseHexRgb(
|
|
137
|
+
background: string,
|
|
138
|
+
): { r: number; g: number; b: number } | null {
|
|
139
|
+
const hex = background.trim().replace(/^#/, "");
|
|
140
|
+
const full =
|
|
141
|
+
hex.length === 3
|
|
142
|
+
? hex
|
|
143
|
+
.split("")
|
|
144
|
+
.map((c) => c + c)
|
|
145
|
+
.join("")
|
|
146
|
+
: hex;
|
|
147
|
+
if (full.length !== 6 || Number.isNaN(Number.parseInt(full, 16))) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
r: Number.parseInt(full.slice(0, 2), 16),
|
|
152
|
+
g: Number.parseInt(full.slice(2, 4), 16),
|
|
153
|
+
b: Number.parseInt(full.slice(4, 6), 16),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function toHex({ r, g, b }: { r: number; g: number; b: number }): string {
|
|
158
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function relativeLuminance({
|
|
162
|
+
r,
|
|
163
|
+
g,
|
|
164
|
+
b,
|
|
165
|
+
}: {
|
|
166
|
+
r: number;
|
|
167
|
+
g: number;
|
|
168
|
+
b: number;
|
|
169
|
+
}): number {
|
|
170
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Temperature bias scaled by luminance; max on light bases, near-zero on dark. */
|
|
174
|
+
function temperatureBiasFor({
|
|
175
|
+
r,
|
|
176
|
+
g,
|
|
177
|
+
b,
|
|
178
|
+
}: {
|
|
179
|
+
r: number;
|
|
180
|
+
g: number;
|
|
181
|
+
b: number;
|
|
182
|
+
}): number {
|
|
183
|
+
return Math.round(
|
|
184
|
+
BACKGROUND_TEMPERATURE_BIAS * relativeLuminance({ r, g, b }),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Off-black on light backgrounds, off-white on dark. */
|
|
189
|
+
function deriveFontColor(background: string): string {
|
|
190
|
+
const rgb = parseHexRgb(background);
|
|
191
|
+
if (!rgb) return FONT_ON_LIGHT;
|
|
192
|
+
return relativeLuminance(rgb) > 0.5 ? FONT_ON_LIGHT : FONT_ON_DARK;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Lighter, slightly warmer surface (cards, raised panels) from backgroundBase. */
|
|
196
|
+
function deriveElevatedBackground(background: string): string {
|
|
197
|
+
const rgb = parseHexRgb(background);
|
|
198
|
+
if (!rgb) return background;
|
|
199
|
+
const bias = temperatureBiasFor(rgb);
|
|
200
|
+
const warm = Math.round(bias / 4);
|
|
201
|
+
return toHex({
|
|
202
|
+
r: Math.min(255, rgb.r + BACKGROUND_SURFACE_DELTA + warm),
|
|
203
|
+
g: Math.min(255, rgb.g + BACKGROUND_SURFACE_DELTA),
|
|
204
|
+
b: Math.min(255, rgb.b + BACKGROUND_SURFACE_DELTA - bias),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Darker, slightly cooler surface (wells, inset areas) from backgroundBase. */
|
|
209
|
+
function deriveRecessedBackground(background: string): string {
|
|
210
|
+
const rgb = parseHexRgb(background);
|
|
211
|
+
if (!rgb) return background;
|
|
212
|
+
const bias = temperatureBiasFor(rgb);
|
|
213
|
+
const cool = Math.round(bias / 4);
|
|
214
|
+
return toHex({
|
|
215
|
+
r: Math.max(0, rgb.r - BACKGROUND_SURFACE_DELTA - cool),
|
|
216
|
+
g: Math.max(0, rgb.g - BACKGROUND_SURFACE_DELTA),
|
|
217
|
+
b: Math.max(0, rgb.b - BACKGROUND_SURFACE_DELTA + bias),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// main prebuild function
|
|
222
|
+
async function prebuild() {
|
|
223
|
+
console.log("running prebuild script...");
|
|
224
|
+
|
|
225
|
+
// regenerate deploy config from .env before any network work
|
|
226
|
+
writeWranglerToml();
|
|
227
|
+
|
|
228
|
+
const token = await fetchSiteToken();
|
|
229
|
+
if (!token) throw new Error("Missing or invalid ZORGO_SITE_SECRET env var");
|
|
230
|
+
|
|
231
|
+
const payload = JSON.parse(
|
|
232
|
+
Buffer.from(token.split(".")[1], "base64url").toString(),
|
|
233
|
+
);
|
|
234
|
+
const franchiseId = payload.franchise_ids[0];
|
|
235
|
+
|
|
236
|
+
// menu + franchise feed the JSON-LD; a menu failure must break the build
|
|
237
|
+
// loudly (it's imported for SSR), not silently ship a missing/empty menu.ts.
|
|
238
|
+
const [menuData] = await Promise.all([
|
|
239
|
+
menu({ franchiseId, token }),
|
|
240
|
+
logo({ franchiseId, token }).catch((err) =>
|
|
241
|
+
console.error("logo failed:", err),
|
|
242
|
+
),
|
|
243
|
+
hours({ franchiseId, token }).catch((err) =>
|
|
244
|
+
console.error("hours failed:", err),
|
|
245
|
+
),
|
|
246
|
+
styles({ franchiseId, token }).catch((err) =>
|
|
247
|
+
console.error("styles failed:", err),
|
|
248
|
+
),
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
await jsonLd({ franchiseId, token }, menuData).catch((err) =>
|
|
252
|
+
console.error("jsonLd failed:", err),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
console.log("prebuild script completed");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// fetches
|
|
259
|
+
async function logo({ franchiseId, token }: FetchArgs) {
|
|
260
|
+
const logoResponse = await fetch(
|
|
261
|
+
`${API_BASE_URL}/franchise-info/logo/${franchiseId}`,
|
|
262
|
+
{
|
|
263
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// 404 (no logo) returns a JSON body — don't persist it as logo.png, or the
|
|
268
|
+
// icon/OG image would render an error blob instead of falling back.
|
|
269
|
+
if (!logoResponse.ok) return;
|
|
270
|
+
|
|
271
|
+
const arrayBuffer = await logoResponse.arrayBuffer();
|
|
272
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
273
|
+
|
|
274
|
+
const outDir = path.join(process.cwd(), `public/images/`);
|
|
275
|
+
|
|
276
|
+
mkdirSync(outDir, { recursive: true });
|
|
277
|
+
|
|
278
|
+
writeFileSync(path.join(outDir, "logo.png"), buffer);
|
|
279
|
+
writeFileSync(path.join(outDir, "logo.jpg"), buffer);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function menu({ franchiseId, token }: FetchArgs): Promise<Menu> {
|
|
283
|
+
const menuResponse = await fetch(
|
|
284
|
+
`${API_BASE_URL}/franchise-info/menu/${franchiseId}`,
|
|
285
|
+
{
|
|
286
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (!menuResponse.ok) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`menu fetch failed ${menuResponse.status}: ${await menuResponse.text()}`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const body = await menuResponse.json();
|
|
297
|
+
const menu = MenuSchema.parse(body?.data ?? {}) as Menu;
|
|
298
|
+
|
|
299
|
+
// A menu is core content: an empty one is a broken storefront, so keep
|
|
300
|
+
// failing the build loudly rather than shipping an empty menu.ts.
|
|
301
|
+
if (menu.items.length === 0) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`menu response has no items: ${JSON.stringify(body).slice(0, 200)}`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const outDir = path.join(process.cwd(), `lib/generated/`);
|
|
308
|
+
mkdirSync(outDir, { recursive: true });
|
|
309
|
+
|
|
310
|
+
// typed module so consumers get Menu-checked imports instead of an
|
|
311
|
+
// untyped runtime fetch of menu.json.
|
|
312
|
+
writeFileSync(
|
|
313
|
+
path.join(outDir, "menu.ts"),
|
|
314
|
+
`// AUTO-GENERATED by scripts/prebuild.ts — do not edit.\n` +
|
|
315
|
+
`import type { Menu } from "@zorgo/next";\n\n` +
|
|
316
|
+
`export const menu: Menu = ${JSON.stringify(menu, null, 2)};\n`,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
return menu;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// franchise row (name, site_url) -> Restaurant JSON-LD, written as a typed
|
|
323
|
+
// module the layout imports and drops into <head>.
|
|
324
|
+
async function jsonLd({ franchiseId, token }: FetchArgs, menu: Menu) {
|
|
325
|
+
const franchise = await fetchData(
|
|
326
|
+
`/franchise-info/franchise/${franchiseId}`,
|
|
327
|
+
token,
|
|
328
|
+
FranchiseSchema,
|
|
329
|
+
);
|
|
330
|
+
const url = franchise.site_url;
|
|
331
|
+
|
|
332
|
+
const data = buildRestaurantJsonLd(menu, {
|
|
333
|
+
name: franchise.name,
|
|
334
|
+
url,
|
|
335
|
+
image: url ? `${url}/images/logo.png` : undefined,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const outDir = path.join(process.cwd(), `lib/generated/`);
|
|
339
|
+
mkdirSync(outDir, { recursive: true });
|
|
340
|
+
writeFileSync(
|
|
341
|
+
path.join(outDir, "jsonld.ts"),
|
|
342
|
+
`// AUTO-GENERATED by scripts/prebuild.ts — do not edit.\n` +
|
|
343
|
+
`export const restaurantJsonLd = ${JSON.stringify(data, null, 2)};\n`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async function hours({ franchiseId, token }: FetchArgs) {
|
|
348
|
+
await fetchData(`/franchise-info/hours/${franchiseId}`, token, HoursSchema);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function styles({ franchiseId, token }: FetchArgs) {
|
|
352
|
+
const brand = await fetchData(
|
|
353
|
+
`/franchise-info/styles/${franchiseId}`,
|
|
354
|
+
token,
|
|
355
|
+
BrandingSchema,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Same derived palette globals.css uses, persisted as JSON so the build-time
|
|
359
|
+
// icon/OG/manifest routes stay on brand (they readFileSync this).
|
|
360
|
+
const branding = {
|
|
361
|
+
...brand,
|
|
362
|
+
backgroundElevated: deriveElevatedBackground(brand.backgroundBase),
|
|
363
|
+
backgroundRecessed: deriveRecessedBackground(brand.backgroundBase),
|
|
364
|
+
foreground: deriveFontColor(brand.backgroundBase),
|
|
365
|
+
primaryForeground: deriveFontColor(brand.primary),
|
|
366
|
+
accentForeground: deriveFontColor(brand.accent),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const stylesDir = path.join(process.cwd(), `public/styles/`);
|
|
370
|
+
mkdirSync(stylesDir, { recursive: true });
|
|
371
|
+
writeFileSync(
|
|
372
|
+
path.join(stylesDir, "styles.json"),
|
|
373
|
+
JSON.stringify(branding, null, 2),
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
writeFileSync(
|
|
377
|
+
path.join(process.cwd(), "app", "globals.css"),
|
|
378
|
+
buildGlobalsCss(brand),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// css write
|
|
383
|
+
function buildGlobalsCss(branding: Branding): string {
|
|
384
|
+
const fontFamilyParam = branding.fontFamily.replace(/ /g, "+");
|
|
385
|
+
const foreground = deriveFontColor(branding.backgroundBase);
|
|
386
|
+
const primaryForeground = deriveFontColor(branding.primary);
|
|
387
|
+
const accentForeground = deriveFontColor(branding.accent);
|
|
388
|
+
const backgroundElevated = deriveElevatedBackground(
|
|
389
|
+
branding.backgroundBase,
|
|
390
|
+
);
|
|
391
|
+
const backgroundRecessed = deriveRecessedBackground(
|
|
392
|
+
branding.backgroundBase,
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
return `
|
|
396
|
+
@import url("https://fonts.googleapis.com/css2?family=${fontFamilyParam}:wght@400;700&display=swap");
|
|
397
|
+
@import "tailwindcss";
|
|
398
|
+
/* Tailwind v4 skips node_modules; @zorgo/next ships un-obfuscated components so
|
|
399
|
+
their class names are scannable here. */
|
|
400
|
+
@source "../node_modules/@zorgo/next/dist/components.mjs";
|
|
401
|
+
|
|
402
|
+
:root {
|
|
403
|
+
--background: ${branding.backgroundBase};
|
|
404
|
+
--background-elevated: ${backgroundElevated};
|
|
405
|
+
--background-recessed: ${backgroundRecessed};
|
|
406
|
+
--foreground: ${foreground};
|
|
407
|
+
--primary: ${branding.primary};
|
|
408
|
+
--primary-foreground: ${primaryForeground};
|
|
409
|
+
--accent: ${branding.accent};
|
|
410
|
+
--accent-foreground: ${accentForeground};
|
|
411
|
+
--font-sans: "${branding.fontFamily}", sans-serif;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
@theme inline {
|
|
415
|
+
--color-background: var(--background);
|
|
416
|
+
--color-background-elevated: var(--background-elevated);
|
|
417
|
+
--color-background-recessed: var(--background-recessed);
|
|
418
|
+
--color-foreground: var(--foreground);
|
|
419
|
+
--color-primary: var(--primary);
|
|
420
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
421
|
+
--color-accent: var(--accent);
|
|
422
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
423
|
+
--font-sans: var(--font-sans);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@layer base {
|
|
427
|
+
button,
|
|
428
|
+
[type="button"],
|
|
429
|
+
[type="submit"],
|
|
430
|
+
[type="reset"] {
|
|
431
|
+
cursor: pointer;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
button:disabled,
|
|
435
|
+
[type="button"]:disabled,
|
|
436
|
+
[type="submit"]:disabled,
|
|
437
|
+
[type="reset"]:disabled {
|
|
438
|
+
cursor: not-allowed;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
prebuild().catch((err: Error) => {
|
|
446
|
+
console.error(err);
|
|
447
|
+
process.exit(1);
|
|
448
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./src/*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
35
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zorgo/next",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"bin": {
|
|
7
|
+
"zorgo": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"sideEffects": [
|
|
10
|
+
"./dist/index.mjs",
|
|
11
|
+
"./dist/index.js",
|
|
12
|
+
"./dist/components.mjs",
|
|
13
|
+
"./dist/components.js",
|
|
14
|
+
"./src/index.ts"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"name": "@zorgo/next",
|
|
21
|
+
"registry": "https://registry.npmjs.org/",
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"react": "*",
|
|
26
|
+
"react-dom": "*",
|
|
27
|
+
"next": "^14.x.x || ^15.x.x || ^16.x.x",
|
|
28
|
+
"lucide-react": "*",
|
|
29
|
+
"@stripe/react-stripe-js": "*"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": "./dist/index.mjs",
|
|
34
|
+
"require": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./components": {
|
|
38
|
+
"import": "./dist/components.mjs",
|
|
39
|
+
"require": "./dist/components.js",
|
|
40
|
+
"types": "./dist/components.d.ts"
|
|
41
|
+
},
|
|
42
|
+
"./slugs": {
|
|
43
|
+
"import": "./dist/slugs.mjs",
|
|
44
|
+
"require": "./dist/slugs.js",
|
|
45
|
+
"types": "./dist/slugs.d.ts"
|
|
46
|
+
},
|
|
47
|
+
"./seo": {
|
|
48
|
+
"import": "./dist/seo.mjs",
|
|
49
|
+
"require": "./dist/seo.js",
|
|
50
|
+
"types": "./dist/seo.d.ts"
|
|
51
|
+
},
|
|
52
|
+
"./cli": {
|
|
53
|
+
"import": "./dist/cli.mjs",
|
|
54
|
+
"require": "./dist/cli.js",
|
|
55
|
+
"types": "./dist/cli.d.ts"
|
|
56
|
+
},
|
|
57
|
+
"./package.json": "./package.json"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"immer": "^10.0.0",
|
|
61
|
+
"prompts": "^2.4.2",
|
|
62
|
+
"zustand": "^4.x.x"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsup && tsx scripts/obfuscate.ts",
|
|
66
|
+
"release": "node scripts/publish.mjs",
|
|
67
|
+
"dev": "echo \"No build step. Consumers transpile TS sources.\"",
|
|
68
|
+
"clean": "rm -rf dist",
|
|
69
|
+
"generate:test": "tsup && tsx --env-file=.env scripts/test-scaffold.ts",
|
|
70
|
+
"env:init": "tsx scripts/init-env.ts"
|
|
71
|
+
}
|
|
72
|
+
}
|