@cimplify/cli 0.6.8 → 0.6.10
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/dist/{add-U4S5DBPN.mjs → add-GDHA7MKM.mjs} +1 -1
- package/dist/{chunk-AS2SIMRN.mjs → chunk-5L6LJE6I.mjs} +1 -1
- package/dist/{chunk-EALN6SAN.mjs → chunk-EKJ6T66O.mjs} +25 -25
- package/dist/{chunk-KPEMFKKN.mjs → chunk-ZTKQOLAC.mjs} +1 -1
- package/dist/dispatcher.mjs +9 -9
- package/dist/{doctor-Y6XJWZVX.mjs → doctor-SSSYBYCL.mjs} +2 -2
- package/dist/{explain-WDIQSCUY.mjs → explain-VG7XUP62.mjs} +1 -1
- package/dist/{introspect-3EMAYETR.mjs → introspect-HYJ6VI3U.mjs} +2 -2
- package/dist/{list-KO2QX42Q.mjs → list-NQP4SU5K.mjs} +1 -1
- package/dist/{update-J6ZDMCOB.mjs → update-HHRCPKSU.mjs} +1 -1
- package/package.json +1 -1
- package/templates/storefront-auto/app/layout.tsx +3 -1
- package/templates/storefront-auto/components/product-modal.tsx +104 -0
- package/templates/storefront-auto/lib/site-url.ts +3 -26
- package/templates/storefront-auto/lib/site.config.ts +7 -0
- package/templates/storefront-auto/next.config.ts +7 -0
- package/templates/storefront-bakery/app/layout.tsx +3 -1
- package/templates/storefront-bakery/lib/site-url.ts +3 -26
- package/templates/storefront-bakery/lib/site.config.ts +7 -0
- package/templates/storefront-bakery/next.config.ts +7 -0
- package/templates/storefront-fashion/app/layout.tsx +3 -1
- package/templates/storefront-fashion/components/product-modal.tsx +104 -0
- package/templates/storefront-fashion/lib/site-url.ts +3 -26
- package/templates/storefront-fashion/lib/site.config.ts +7 -0
- package/templates/storefront-fashion/next.config.ts +7 -0
- package/templates/storefront-grocery/app/layout.tsx +3 -1
- package/templates/storefront-grocery/lib/site-url.ts +3 -26
- package/templates/storefront-grocery/lib/site.config.ts +7 -0
- package/templates/storefront-grocery/next.config.ts +7 -0
- package/templates/storefront-pharmacy/app/layout.tsx +3 -1
- package/templates/storefront-pharmacy/components/product-modal.tsx +104 -0
- package/templates/storefront-pharmacy/lib/site-url.ts +3 -26
- package/templates/storefront-pharmacy/lib/site.config.ts +7 -0
- package/templates/storefront-pharmacy/next.config.ts +7 -0
- package/templates/storefront-restaurant/app/layout.tsx +3 -1
- package/templates/storefront-restaurant/app/reservations/reservations-client.tsx +1 -1
- package/templates/storefront-restaurant/lib/site-url.ts +3 -26
- package/templates/storefront-restaurant/lib/site.config.ts +7 -0
- package/templates/storefront-restaurant/next.config.ts +7 -0
- package/templates/storefront-retail/app/layout.tsx +3 -1
- package/templates/storefront-retail/components/product-modal.tsx +104 -0
- package/templates/storefront-retail/lib/site-url.ts +3 -26
- package/templates/storefront-retail/lib/site.config.ts +7 -0
- package/templates/storefront-retail/next.config.ts +7 -0
- package/templates/storefront-services/app/book/book-client.tsx +2 -1
- package/templates/storefront-services/app/layout.tsx +3 -1
- package/templates/storefront-services/lib/site-url.ts +3 -26
- package/templates/storefront-services/lib/site.config.ts +7 -0
- package/templates/storefront-services/next.config.ts +7 -0
- package/templates/storefront-auto/app/login/page.tsx +0 -17
- package/templates/storefront-auto/app/signup/page.tsx +0 -17
- package/templates/storefront-bakery/app/login/page.tsx +0 -17
- package/templates/storefront-bakery/app/signup/page.tsx +0 -17
- package/templates/storefront-fashion/app/login/page.tsx +0 -17
- package/templates/storefront-fashion/app/signup/page.tsx +0 -17
- package/templates/storefront-grocery/app/login/page.tsx +0 -17
- package/templates/storefront-grocery/app/signup/page.tsx +0 -17
- package/templates/storefront-pharmacy/app/login/page.tsx +0 -17
- package/templates/storefront-pharmacy/app/signup/page.tsx +0 -17
- package/templates/storefront-restaurant/app/login/page.tsx +0 -17
- package/templates/storefront-restaurant/app/signup/page.tsx +0 -17
- package/templates/storefront-retail/app/login/page.tsx +0 -17
- package/templates/storefront-retail/app/signup/page.tsx +0 -17
- package/templates/storefront-services/app/login/page.tsx +0 -17
- package/templates/storefront-services/app/signup/page.tsx +0 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { gitDetectRoot, gitCurrentBranch, gitCurrentSha, gitStatusPorcelain } from './chunk-K5464A3L.mjs';
|
|
3
3
|
import { parseEnvFile } from './chunk-DBZ3UOQ2.mjs';
|
|
4
|
-
import { package_default } from './chunk-
|
|
4
|
+
import { package_default } from './chunk-5L6LJE6I.mjs';
|
|
5
5
|
import { parseArgs } from './chunk-C4M3DXKC.mjs';
|
|
6
6
|
import { readAuthOrNull, readProjectLinkOrNull, readProjectState } from './chunk-UBAI443T.mjs';
|
|
7
7
|
import { bold, dim, yellow, green, info, result, red } from './chunk-E2T2SBP5.mjs';
|
package/dist/dispatcher.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { TEMPLATES } from './chunk-
|
|
3
|
-
import { package_default } from './chunk-
|
|
2
|
+
import { TEMPLATES } from './chunk-EKJ6T66O.mjs';
|
|
3
|
+
import { package_default } from './chunk-5L6LJE6I.mjs';
|
|
4
4
|
|
|
5
5
|
// src/dispatcher.ts
|
|
6
6
|
var VERSION = package_default.version ?? "unknown";
|
|
@@ -137,15 +137,15 @@ var COMMANDS = {
|
|
|
137
137
|
logs: () => import('./logs-YNN2PQ24.mjs'),
|
|
138
138
|
status: () => import('./status-JSYXM5RT.mjs'),
|
|
139
139
|
dev: () => import('./dev-ONW2S77K.mjs'),
|
|
140
|
-
introspect: () => import('./introspect-
|
|
141
|
-
doctor: () => import('./doctor-
|
|
142
|
-
explain: () => import('./explain-
|
|
140
|
+
introspect: () => import('./introspect-HYJ6VI3U.mjs'),
|
|
141
|
+
doctor: () => import('./doctor-SSSYBYCL.mjs'),
|
|
142
|
+
explain: () => import('./explain-VG7XUP62.mjs'),
|
|
143
143
|
assets: () => import('./assets-EBEMMENZ.mjs'),
|
|
144
144
|
repo: () => import('./repo-WOBWKEAO.mjs'),
|
|
145
|
-
list: () => import('./list-
|
|
146
|
-
add: () => import('./add-
|
|
147
|
-
update: () => import('./update-
|
|
148
|
-
upgrade: () => import('./update-
|
|
145
|
+
list: () => import('./list-NQP4SU5K.mjs'),
|
|
146
|
+
add: () => import('./add-GDHA7MKM.mjs'),
|
|
147
|
+
update: () => import('./update-HHRCPKSU.mjs'),
|
|
148
|
+
upgrade: () => import('./update-HHRCPKSU.mjs'),
|
|
149
149
|
"auth-step-up": () => import('./auth-step-up-BIUYQJP6.mjs')
|
|
150
150
|
};
|
|
151
151
|
var COMMAND_PREFIXES = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { gatherIntrospection } from './chunk-
|
|
2
|
+
import { gatherIntrospection } from './chunk-ZTKQOLAC.mjs';
|
|
3
3
|
import './chunk-K5464A3L.mjs';
|
|
4
4
|
import './chunk-DBZ3UOQ2.mjs';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-5L6LJE6I.mjs';
|
|
6
6
|
import { parseArgs, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
7
7
|
import { ApiClient } from './chunk-MAOO6ZZ5.mjs';
|
|
8
8
|
import { readAuthOrNull } from './chunk-UBAI443T.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { package_default } from './chunk-
|
|
2
|
+
import { package_default } from './chunk-5L6LJE6I.mjs';
|
|
3
3
|
import { parseArgs } from './chunk-C4M3DXKC.mjs';
|
|
4
4
|
import { bold, dim, info, result, CliError, CLI_ERROR_CODE } from './chunk-E2T2SBP5.mjs';
|
|
5
5
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { run as default, extractMockSeed, gatherIntrospection, renderIntrospection } from './chunk-
|
|
2
|
+
export { run as default, extractMockSeed, gatherIntrospection, renderIntrospection } from './chunk-ZTKQOLAC.mjs';
|
|
3
3
|
import './chunk-K5464A3L.mjs';
|
|
4
4
|
import './chunk-DBZ3UOQ2.mjs';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-5L6LJE6I.mjs';
|
|
6
6
|
import './chunk-C4M3DXKC.mjs';
|
|
7
7
|
import './chunk-UBAI443T.mjs';
|
|
8
8
|
import './chunk-E2T2SBP5.mjs';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { REGISTRY_INDEX } from './chunk-
|
|
2
|
+
import { REGISTRY_INDEX } from './chunk-EKJ6T66O.mjs';
|
|
3
3
|
import { parseArgs, flagBool } from './chunk-C4M3DXKC.mjs';
|
|
4
4
|
import { CliError, CLI_ERROR_CODE, info, bold, dim, green, result } from './chunk-E2T2SBP5.mjs';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { package_default } from './chunk-
|
|
2
|
+
import { package_default } from './chunk-5L6LJE6I.mjs';
|
|
3
3
|
import { promptYesNo } from './chunk-ITAFAORS.mjs';
|
|
4
4
|
import { parseArgs, flagBool, flagString } from './chunk-C4M3DXKC.mjs';
|
|
5
5
|
import { success, bold, info, dim, result, failure, CliError, CLI_ERROR_CODE, step, isJsonMode } from './chunk-E2T2SBP5.mjs';
|
package/package.json
CHANGED
|
@@ -53,7 +53,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
53
53
|
</Suspense>
|
|
54
54
|
<Providers>
|
|
55
55
|
<Header />
|
|
56
|
-
<main className="flex-1 pb-12 w-full">
|
|
56
|
+
<main className="flex-1 pb-12 w-full">
|
|
57
|
+
<Suspense fallback={null}>{children}</Suspense>
|
|
58
|
+
</main>
|
|
57
59
|
<Footer />
|
|
58
60
|
<Suspense fallback={null}>
|
|
59
61
|
<ProductModal />
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
6
|
+
import { ProductSheet, useProduct, useCart } from "@cimplify/sdk/react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* URL-driven product modal. Reads `?product=<slug>` and renders the SDK's
|
|
10
|
+
* `<ProductSheet/>` — vertical layout with image-on-top, then header, then
|
|
11
|
+
* the variant/add-on/composite/bundle customizer. Closing the modal clears
|
|
12
|
+
* the search param. Deep-linkable and survives reloads.
|
|
13
|
+
*/
|
|
14
|
+
export function ProductModal() {
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const pathname = usePathname();
|
|
17
|
+
const searchParams = useSearchParams();
|
|
18
|
+
const slug = searchParams?.get("product") ?? null;
|
|
19
|
+
|
|
20
|
+
const { product } = useProduct(slug ?? "", { enabled: Boolean(slug) });
|
|
21
|
+
const { addItem } = useCart();
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!slug) return;
|
|
25
|
+
const original = document.body.style.overflow;
|
|
26
|
+
document.body.style.overflow = "hidden";
|
|
27
|
+
return () => {
|
|
28
|
+
document.body.style.overflow = original;
|
|
29
|
+
};
|
|
30
|
+
}, [slug]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!slug) return;
|
|
34
|
+
const onKey = (e: KeyboardEvent) => {
|
|
35
|
+
if (e.key === "Escape") close();
|
|
36
|
+
};
|
|
37
|
+
window.addEventListener("keydown", onKey);
|
|
38
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
39
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
+
}, [slug]);
|
|
41
|
+
|
|
42
|
+
if (!slug) return null;
|
|
43
|
+
|
|
44
|
+
function close() {
|
|
45
|
+
const next = new URLSearchParams(searchParams?.toString() ?? "");
|
|
46
|
+
next.delete("product");
|
|
47
|
+
const qs = next.toString();
|
|
48
|
+
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
role="dialog"
|
|
54
|
+
aria-modal="true"
|
|
55
|
+
onClick={close}
|
|
56
|
+
className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center sm:p-6 bg-foreground/50 backdrop-blur-sm"
|
|
57
|
+
>
|
|
58
|
+
<div
|
|
59
|
+
onClick={(e) => e.stopPropagation()}
|
|
60
|
+
className="relative w-full sm:max-w-lg max-h-[92vh] overflow-auto bg-card sm:rounded-3xl rounded-t-3xl shadow-2xl"
|
|
61
|
+
>
|
|
62
|
+
<button
|
|
63
|
+
onClick={close}
|
|
64
|
+
aria-label="Close product details"
|
|
65
|
+
className="absolute top-4 right-4 z-10 w-9 h-9 rounded-full bg-card border border-border text-sm cursor-pointer transition-colors hover:bg-muted"
|
|
66
|
+
>
|
|
67
|
+
✕
|
|
68
|
+
</button>
|
|
69
|
+
{product ? (
|
|
70
|
+
<ProductSheet
|
|
71
|
+
product={product}
|
|
72
|
+
onClose={close}
|
|
73
|
+
onAddToCart={async (p, qty, options) => {
|
|
74
|
+
await addItem(p, qty, options);
|
|
75
|
+
close();
|
|
76
|
+
}}
|
|
77
|
+
renderImage={({ src, alt, className }) => (
|
|
78
|
+
<Image
|
|
79
|
+
src={src}
|
|
80
|
+
alt={alt}
|
|
81
|
+
width={1200}
|
|
82
|
+
height={900}
|
|
83
|
+
className={className}
|
|
84
|
+
style={{ width: "100%", height: "auto", objectFit: "cover" }}
|
|
85
|
+
priority
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
classNames={{
|
|
89
|
+
root: "p-6 sm:p-8 gap-4",
|
|
90
|
+
image: "rounded-2xl overflow-hidden -mx-6 sm:-mx-8 -mt-6 sm:-mt-8 mb-2",
|
|
91
|
+
header: "flex items-baseline justify-between gap-4",
|
|
92
|
+
name: "font-serif text-2xl font-semibold m-0",
|
|
93
|
+
price: "text-lg font-semibold text-primary",
|
|
94
|
+
description: "text-sm text-muted-foreground leading-relaxed",
|
|
95
|
+
customizer: "pt-2",
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
) : (
|
|
99
|
+
<div className="p-8 text-center text-muted-foreground">Loading…</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SITE_URL } from "./site.config";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Canonical absolute URL for this storefront, derived at request time.
|
|
5
|
-
*
|
|
6
|
-
* Resolution order:
|
|
7
|
-
* 1. `NEXT_PUBLIC_SITE_URL` — set when you need a fixed canonical
|
|
8
|
-
* (multiple domains, www-vs-apex preference, etc.).
|
|
9
|
-
* 2. The request `Host` header — works automatically on every deploy,
|
|
10
|
-
* including preview URLs and custom domains.
|
|
11
|
-
* 3. `https://example.com` — only ever returned during prerender of
|
|
12
|
-
* a page that has no live request (rare in App Router).
|
|
13
|
-
*/
|
|
3
|
+
/** Canonical absolute URL for this storefront. */
|
|
14
4
|
export async function getSiteUrl(): Promise<string> {
|
|
15
|
-
|
|
16
|
-
if (explicit) return explicit;
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const h = await headers();
|
|
20
|
-
const host = h.get("host");
|
|
21
|
-
if (host) {
|
|
22
|
-
const isLocal = host.startsWith("localhost") || host.startsWith("127.");
|
|
23
|
-
return `${isLocal ? "http" : "https"}://${host}`;
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
// `headers()` is unavailable in some build contexts — fall through.
|
|
27
|
-
}
|
|
28
|
-
return "https://example.com";
|
|
5
|
+
return SITE_URL;
|
|
29
6
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical absolute URL. The platform overwrites this at build time with the
|
|
3
|
+
* storefront's primary domain; the default only applies to local builds. It's
|
|
4
|
+
* per-deploy config, not per-request, so it stays a static constant — keeping
|
|
5
|
+
* the root layout prerenderable under `cacheComponents`.
|
|
6
|
+
*/
|
|
7
|
+
export const SITE_URL = "https://example.com";
|
|
@@ -24,6 +24,13 @@ const nextConfig: NextConfig = {
|
|
|
24
24
|
// `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
|
|
25
25
|
// dynamic Suspense boundaries fill in when ready.
|
|
26
26
|
cacheComponents: true,
|
|
27
|
+
async redirects() {
|
|
28
|
+
// Config-level so cacheComponents needn't prerender a redirect()-only page.
|
|
29
|
+
return [
|
|
30
|
+
{ source: "/login", destination: "/account", permanent: false },
|
|
31
|
+
{ source: "/signup", destination: "/account", permanent: false },
|
|
32
|
+
];
|
|
33
|
+
},
|
|
27
34
|
async rewrites() {
|
|
28
35
|
return [
|
|
29
36
|
{ source: "/api/v1/:path*", destination: `${STOREFRONT_URL}/api/v1/:path*` },
|
|
@@ -53,7 +53,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
53
53
|
</Suspense>
|
|
54
54
|
<Providers>
|
|
55
55
|
<Header />
|
|
56
|
-
<main className="flex-1 pb-12 w-full">
|
|
56
|
+
<main className="flex-1 pb-12 w-full">
|
|
57
|
+
<Suspense fallback={null}>{children}</Suspense>
|
|
58
|
+
</main>
|
|
57
59
|
<Footer />
|
|
58
60
|
<Suspense fallback={null}>
|
|
59
61
|
<ProductModal />
|
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SITE_URL } from "./site.config";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Canonical absolute URL for this storefront, derived at request time.
|
|
5
|
-
*
|
|
6
|
-
* Resolution order:
|
|
7
|
-
* 1. `NEXT_PUBLIC_SITE_URL` — set when you need a fixed canonical
|
|
8
|
-
* (multiple domains, www-vs-apex preference, etc.).
|
|
9
|
-
* 2. The request `Host` header — works automatically on every deploy,
|
|
10
|
-
* including preview URLs and custom domains.
|
|
11
|
-
* 3. `https://example.com` — only ever returned during prerender of
|
|
12
|
-
* a page that has no live request (rare in App Router).
|
|
13
|
-
*/
|
|
3
|
+
/** Canonical absolute URL for this storefront. */
|
|
14
4
|
export async function getSiteUrl(): Promise<string> {
|
|
15
|
-
|
|
16
|
-
if (explicit) return explicit;
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const h = await headers();
|
|
20
|
-
const host = h.get("host");
|
|
21
|
-
if (host) {
|
|
22
|
-
const isLocal = host.startsWith("localhost") || host.startsWith("127.");
|
|
23
|
-
return `${isLocal ? "http" : "https"}://${host}`;
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
// `headers()` is unavailable in some build contexts — fall through.
|
|
27
|
-
}
|
|
28
|
-
return "https://example.com";
|
|
5
|
+
return SITE_URL;
|
|
29
6
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical absolute URL. The platform overwrites this at build time with the
|
|
3
|
+
* storefront's primary domain; the default only applies to local builds. It's
|
|
4
|
+
* per-deploy config, not per-request, so it stays a static constant — keeping
|
|
5
|
+
* the root layout prerenderable under `cacheComponents`.
|
|
6
|
+
*/
|
|
7
|
+
export const SITE_URL = "https://example.com";
|
|
@@ -24,6 +24,13 @@ const nextConfig: NextConfig = {
|
|
|
24
24
|
// `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
|
|
25
25
|
// dynamic Suspense boundaries fill in when ready.
|
|
26
26
|
cacheComponents: true,
|
|
27
|
+
async redirects() {
|
|
28
|
+
// Config-level so cacheComponents needn't prerender a redirect()-only page.
|
|
29
|
+
return [
|
|
30
|
+
{ source: "/login", destination: "/account", permanent: false },
|
|
31
|
+
{ source: "/signup", destination: "/account", permanent: false },
|
|
32
|
+
];
|
|
33
|
+
},
|
|
27
34
|
async rewrites() {
|
|
28
35
|
return [
|
|
29
36
|
{ source: "/api/v1/:path*", destination: `${STOREFRONT_URL}/api/v1/:path*` },
|
|
@@ -54,7 +54,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
54
54
|
</Suspense>
|
|
55
55
|
<Providers>
|
|
56
56
|
<Header />
|
|
57
|
-
<main className="flex-1 pb-12 w-full">
|
|
57
|
+
<main className="flex-1 pb-12 w-full">
|
|
58
|
+
<Suspense fallback={null}>{children}</Suspense>
|
|
59
|
+
</main>
|
|
58
60
|
<Footer />
|
|
59
61
|
<Suspense fallback={null}>
|
|
60
62
|
<ProductModal />
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
6
|
+
import { ProductSheet, useProduct, useCart } from "@cimplify/sdk/react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* URL-driven product modal. Reads `?product=<slug>` and renders the SDK's
|
|
10
|
+
* `<ProductSheet/>` — vertical layout with image-on-top, then header, then
|
|
11
|
+
* the variant/add-on/composite/bundle customizer. Closing the modal clears
|
|
12
|
+
* the search param. Deep-linkable and survives reloads.
|
|
13
|
+
*/
|
|
14
|
+
export function ProductModal() {
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
const pathname = usePathname();
|
|
17
|
+
const searchParams = useSearchParams();
|
|
18
|
+
const slug = searchParams?.get("product") ?? null;
|
|
19
|
+
|
|
20
|
+
const { product } = useProduct(slug ?? "", { enabled: Boolean(slug) });
|
|
21
|
+
const { addItem } = useCart();
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!slug) return;
|
|
25
|
+
const original = document.body.style.overflow;
|
|
26
|
+
document.body.style.overflow = "hidden";
|
|
27
|
+
return () => {
|
|
28
|
+
document.body.style.overflow = original;
|
|
29
|
+
};
|
|
30
|
+
}, [slug]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!slug) return;
|
|
34
|
+
const onKey = (e: KeyboardEvent) => {
|
|
35
|
+
if (e.key === "Escape") close();
|
|
36
|
+
};
|
|
37
|
+
window.addEventListener("keydown", onKey);
|
|
38
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
39
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
+
}, [slug]);
|
|
41
|
+
|
|
42
|
+
if (!slug) return null;
|
|
43
|
+
|
|
44
|
+
function close() {
|
|
45
|
+
const next = new URLSearchParams(searchParams?.toString() ?? "");
|
|
46
|
+
next.delete("product");
|
|
47
|
+
const qs = next.toString();
|
|
48
|
+
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
role="dialog"
|
|
54
|
+
aria-modal="true"
|
|
55
|
+
onClick={close}
|
|
56
|
+
className="fixed inset-0 z-[100] flex items-end sm:items-center justify-center sm:p-6 bg-foreground/50 backdrop-blur-sm"
|
|
57
|
+
>
|
|
58
|
+
<div
|
|
59
|
+
onClick={(e) => e.stopPropagation()}
|
|
60
|
+
className="relative w-full sm:max-w-lg max-h-[92vh] overflow-auto bg-card sm:rounded-3xl rounded-t-3xl shadow-2xl"
|
|
61
|
+
>
|
|
62
|
+
<button
|
|
63
|
+
onClick={close}
|
|
64
|
+
aria-label="Close product details"
|
|
65
|
+
className="absolute top-4 right-4 z-10 w-9 h-9 rounded-full bg-card border border-border text-sm cursor-pointer transition-colors hover:bg-muted"
|
|
66
|
+
>
|
|
67
|
+
✕
|
|
68
|
+
</button>
|
|
69
|
+
{product ? (
|
|
70
|
+
<ProductSheet
|
|
71
|
+
product={product}
|
|
72
|
+
onClose={close}
|
|
73
|
+
onAddToCart={async (p, qty, options) => {
|
|
74
|
+
await addItem(p, qty, options);
|
|
75
|
+
close();
|
|
76
|
+
}}
|
|
77
|
+
renderImage={({ src, alt, className }) => (
|
|
78
|
+
<Image
|
|
79
|
+
src={src}
|
|
80
|
+
alt={alt}
|
|
81
|
+
width={1200}
|
|
82
|
+
height={900}
|
|
83
|
+
className={className}
|
|
84
|
+
style={{ width: "100%", height: "auto", objectFit: "cover" }}
|
|
85
|
+
priority
|
|
86
|
+
/>
|
|
87
|
+
)}
|
|
88
|
+
classNames={{
|
|
89
|
+
root: "p-6 sm:p-8 gap-4",
|
|
90
|
+
image: "rounded-2xl overflow-hidden -mx-6 sm:-mx-8 -mt-6 sm:-mt-8 mb-2",
|
|
91
|
+
header: "flex items-baseline justify-between gap-4",
|
|
92
|
+
name: "font-serif text-2xl font-semibold m-0",
|
|
93
|
+
price: "text-lg font-semibold text-primary",
|
|
94
|
+
description: "text-sm text-muted-foreground leading-relaxed",
|
|
95
|
+
customizer: "pt-2",
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
) : (
|
|
99
|
+
<div className="p-8 text-center text-muted-foreground">Loading…</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SITE_URL } from "./site.config";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Canonical absolute URL for this storefront, derived at request time.
|
|
5
|
-
*
|
|
6
|
-
* Resolution order:
|
|
7
|
-
* 1. `NEXT_PUBLIC_SITE_URL` — set when you need a fixed canonical
|
|
8
|
-
* (multiple domains, www-vs-apex preference, etc.).
|
|
9
|
-
* 2. The request `Host` header — works automatically on every deploy,
|
|
10
|
-
* including preview URLs and custom domains.
|
|
11
|
-
* 3. `https://example.com` — only ever returned during prerender of
|
|
12
|
-
* a page that has no live request (rare in App Router).
|
|
13
|
-
*/
|
|
3
|
+
/** Canonical absolute URL for this storefront. */
|
|
14
4
|
export async function getSiteUrl(): Promise<string> {
|
|
15
|
-
|
|
16
|
-
if (explicit) return explicit;
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const h = await headers();
|
|
20
|
-
const host = h.get("host");
|
|
21
|
-
if (host) {
|
|
22
|
-
const isLocal = host.startsWith("localhost") || host.startsWith("127.");
|
|
23
|
-
return `${isLocal ? "http" : "https"}://${host}`;
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
// `headers()` is unavailable in some build contexts — fall through.
|
|
27
|
-
}
|
|
28
|
-
return "https://example.com";
|
|
5
|
+
return SITE_URL;
|
|
29
6
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical absolute URL. The platform overwrites this at build time with the
|
|
3
|
+
* storefront's primary domain; the default only applies to local builds. It's
|
|
4
|
+
* per-deploy config, not per-request, so it stays a static constant — keeping
|
|
5
|
+
* the root layout prerenderable under `cacheComponents`.
|
|
6
|
+
*/
|
|
7
|
+
export const SITE_URL = "https://example.com";
|
|
@@ -24,6 +24,13 @@ const nextConfig: NextConfig = {
|
|
|
24
24
|
// `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
|
|
25
25
|
// dynamic Suspense boundaries fill in when ready.
|
|
26
26
|
cacheComponents: true,
|
|
27
|
+
async redirects() {
|
|
28
|
+
// Config-level so cacheComponents needn't prerender a redirect()-only page.
|
|
29
|
+
return [
|
|
30
|
+
{ source: "/login", destination: "/account", permanent: false },
|
|
31
|
+
{ source: "/signup", destination: "/account", permanent: false },
|
|
32
|
+
];
|
|
33
|
+
},
|
|
27
34
|
async rewrites() {
|
|
28
35
|
return [
|
|
29
36
|
{ source: "/api/v1/:path*", destination: `${STOREFRONT_URL}/api/v1/:path*` },
|
|
@@ -47,7 +47,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
47
47
|
</Suspense>
|
|
48
48
|
<Providers>
|
|
49
49
|
<Header />
|
|
50
|
-
<main className="flex-1 pb-12 w-full">
|
|
50
|
+
<main className="flex-1 pb-12 w-full">
|
|
51
|
+
<Suspense fallback={null}>{children}</Suspense>
|
|
52
|
+
</main>
|
|
51
53
|
<Footer />
|
|
52
54
|
<Suspense fallback={null}>
|
|
53
55
|
<ProductModal />
|
|
@@ -1,29 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SITE_URL } from "./site.config";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Canonical absolute URL for this storefront, derived at request time.
|
|
5
|
-
*
|
|
6
|
-
* Resolution order:
|
|
7
|
-
* 1. `NEXT_PUBLIC_SITE_URL` — set when you need a fixed canonical
|
|
8
|
-
* (multiple domains, www-vs-apex preference, etc.).
|
|
9
|
-
* 2. The request `Host` header — works automatically on every deploy,
|
|
10
|
-
* including preview URLs and custom domains.
|
|
11
|
-
* 3. `https://example.com` — only ever returned during prerender of
|
|
12
|
-
* a page that has no live request (rare in App Router).
|
|
13
|
-
*/
|
|
3
|
+
/** Canonical absolute URL for this storefront. */
|
|
14
4
|
export async function getSiteUrl(): Promise<string> {
|
|
15
|
-
|
|
16
|
-
if (explicit) return explicit;
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const h = await headers();
|
|
20
|
-
const host = h.get("host");
|
|
21
|
-
if (host) {
|
|
22
|
-
const isLocal = host.startsWith("localhost") || host.startsWith("127.");
|
|
23
|
-
return `${isLocal ? "http" : "https"}://${host}`;
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
// `headers()` is unavailable in some build contexts — fall through.
|
|
27
|
-
}
|
|
28
|
-
return "https://example.com";
|
|
5
|
+
return SITE_URL;
|
|
29
6
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical absolute URL. The platform overwrites this at build time with the
|
|
3
|
+
* storefront's primary domain; the default only applies to local builds. It's
|
|
4
|
+
* per-deploy config, not per-request, so it stays a static constant — keeping
|
|
5
|
+
* the root layout prerenderable under `cacheComponents`.
|
|
6
|
+
*/
|
|
7
|
+
export const SITE_URL = "https://example.com";
|
|
@@ -24,6 +24,13 @@ const nextConfig: NextConfig = {
|
|
|
24
24
|
// `cacheTag` / `cacheLife` for SSR caching. Cached chrome streams first,
|
|
25
25
|
// dynamic Suspense boundaries fill in when ready.
|
|
26
26
|
cacheComponents: true,
|
|
27
|
+
async redirects() {
|
|
28
|
+
// Config-level so cacheComponents needn't prerender a redirect()-only page.
|
|
29
|
+
return [
|
|
30
|
+
{ source: "/login", destination: "/account", permanent: false },
|
|
31
|
+
{ source: "/signup", destination: "/account", permanent: false },
|
|
32
|
+
];
|
|
33
|
+
},
|
|
27
34
|
async rewrites() {
|
|
28
35
|
return [
|
|
29
36
|
{ source: "/api/v1/:path*", destination: `${STOREFRONT_URL}/api/v1/:path*` },
|
|
@@ -53,7 +53,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
53
53
|
</Suspense>
|
|
54
54
|
<Providers>
|
|
55
55
|
<Header />
|
|
56
|
-
<main className="flex-1 pb-12 w-full">
|
|
56
|
+
<main className="flex-1 pb-12 w-full">
|
|
57
|
+
<Suspense fallback={null}>{children}</Suspense>
|
|
58
|
+
</main>
|
|
57
59
|
<Footer />
|
|
58
60
|
<Suspense fallback={null}>
|
|
59
61
|
<ProductModal />
|