@cimplify/cli 0.6.9 → 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.
Files changed (65) hide show
  1. package/dist/{add-U4S5DBPN.mjs → add-GDHA7MKM.mjs} +1 -1
  2. package/dist/{chunk-CMF2X3SI.mjs → chunk-5L6LJE6I.mjs} +1 -1
  3. package/dist/{chunk-EALN6SAN.mjs → chunk-EKJ6T66O.mjs} +25 -25
  4. package/dist/{chunk-4HDXZJMR.mjs → chunk-ZTKQOLAC.mjs} +1 -1
  5. package/dist/dispatcher.mjs +9 -9
  6. package/dist/{doctor-BTMEATD4.mjs → doctor-SSSYBYCL.mjs} +2 -2
  7. package/dist/{explain-JH4TKGTP.mjs → explain-VG7XUP62.mjs} +1 -1
  8. package/dist/{introspect-DOZCE52F.mjs → introspect-HYJ6VI3U.mjs} +2 -2
  9. package/dist/{list-KO2QX42Q.mjs → list-NQP4SU5K.mjs} +1 -1
  10. package/dist/{update-O2UR6PAW.mjs → update-HHRCPKSU.mjs} +1 -1
  11. package/package.json +1 -1
  12. package/templates/storefront-auto/app/layout.tsx +3 -1
  13. package/templates/storefront-auto/components/product-modal.tsx +104 -0
  14. package/templates/storefront-auto/lib/site-url.ts +3 -26
  15. package/templates/storefront-auto/lib/site.config.ts +7 -0
  16. package/templates/storefront-auto/next.config.ts +7 -0
  17. package/templates/storefront-bakery/app/layout.tsx +3 -1
  18. package/templates/storefront-bakery/lib/site-url.ts +3 -26
  19. package/templates/storefront-bakery/lib/site.config.ts +7 -0
  20. package/templates/storefront-bakery/next.config.ts +7 -0
  21. package/templates/storefront-fashion/app/layout.tsx +3 -1
  22. package/templates/storefront-fashion/components/product-modal.tsx +104 -0
  23. package/templates/storefront-fashion/lib/site-url.ts +3 -26
  24. package/templates/storefront-fashion/lib/site.config.ts +7 -0
  25. package/templates/storefront-fashion/next.config.ts +7 -0
  26. package/templates/storefront-grocery/app/layout.tsx +3 -1
  27. package/templates/storefront-grocery/lib/site-url.ts +3 -26
  28. package/templates/storefront-grocery/lib/site.config.ts +7 -0
  29. package/templates/storefront-grocery/next.config.ts +7 -0
  30. package/templates/storefront-pharmacy/app/layout.tsx +3 -1
  31. package/templates/storefront-pharmacy/components/product-modal.tsx +104 -0
  32. package/templates/storefront-pharmacy/lib/site-url.ts +3 -26
  33. package/templates/storefront-pharmacy/lib/site.config.ts +7 -0
  34. package/templates/storefront-pharmacy/next.config.ts +7 -0
  35. package/templates/storefront-restaurant/app/layout.tsx +3 -1
  36. package/templates/storefront-restaurant/app/reservations/reservations-client.tsx +1 -1
  37. package/templates/storefront-restaurant/lib/site-url.ts +3 -26
  38. package/templates/storefront-restaurant/lib/site.config.ts +7 -0
  39. package/templates/storefront-restaurant/next.config.ts +7 -0
  40. package/templates/storefront-retail/app/layout.tsx +3 -1
  41. package/templates/storefront-retail/components/product-modal.tsx +104 -0
  42. package/templates/storefront-retail/lib/site-url.ts +3 -26
  43. package/templates/storefront-retail/lib/site.config.ts +7 -0
  44. package/templates/storefront-retail/next.config.ts +7 -0
  45. package/templates/storefront-services/app/book/book-client.tsx +2 -1
  46. package/templates/storefront-services/app/layout.tsx +3 -1
  47. package/templates/storefront-services/lib/site-url.ts +3 -26
  48. package/templates/storefront-services/lib/site.config.ts +7 -0
  49. package/templates/storefront-services/next.config.ts +7 -0
  50. package/templates/storefront-auto/app/login/page.tsx +0 -17
  51. package/templates/storefront-auto/app/signup/page.tsx +0 -17
  52. package/templates/storefront-bakery/app/login/page.tsx +0 -17
  53. package/templates/storefront-bakery/app/signup/page.tsx +0 -17
  54. package/templates/storefront-fashion/app/login/page.tsx +0 -17
  55. package/templates/storefront-fashion/app/signup/page.tsx +0 -17
  56. package/templates/storefront-grocery/app/login/page.tsx +0 -17
  57. package/templates/storefront-grocery/app/signup/page.tsx +0 -17
  58. package/templates/storefront-pharmacy/app/login/page.tsx +0 -17
  59. package/templates/storefront-pharmacy/app/signup/page.tsx +0 -17
  60. package/templates/storefront-restaurant/app/login/page.tsx +0 -17
  61. package/templates/storefront-restaurant/app/signup/page.tsx +0 -17
  62. package/templates/storefront-retail/app/login/page.tsx +0 -17
  63. package/templates/storefront-retail/app/signup/page.tsx +0 -17
  64. package/templates/storefront-services/app/login/page.tsx +0 -17
  65. package/templates/storefront-services/app/signup/page.tsx +0 -17
@@ -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 { headers } from "next/headers";
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
- const explicit = process.env.NEXT_PUBLIC_SITE_URL?.trim();
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">{children}</main>
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 />
@@ -59,7 +59,7 @@ export function ReservationsClient({ options }: { options: Product[] }) {
59
59
  try {
60
60
  const when = new Date(selectedSlotKey).toLocaleString();
61
61
  const note = `Party of ${partySize} · ${when}${notes ? ` · ${notes}` : ""}`;
62
- await addItem(selectedOption, 1, { notes: note });
62
+ await addItem(selectedOption, 1, { specialInstructions: note });
63
63
  router.push("/checkout");
64
64
  } catch {
65
65
  setSubmitting(false);
@@ -1,29 +1,6 @@
1
- import { headers } from "next/headers";
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
- const explicit = process.env.NEXT_PUBLIC_SITE_URL?.trim();
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">{children}</main>
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 { headers } from "next/headers";
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
- const explicit = process.env.NEXT_PUBLIC_SITE_URL?.trim();
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*` },
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
5
5
  import type { Product } from "@cimplify/sdk";
6
6
  import type { AvailableSlot } from "@cimplify/sdk";
7
7
  import { useCart, DateSlotPicker } from "@cimplify/sdk/react";
8
+ import { brand } from "@/lib/brand";
8
9
 
9
10
  /**
10
11
  * Booking flow:
@@ -82,7 +83,7 @@ export function BookClient({ treatments }: { treatments: Product[] }) {
82
83
  <p className="font-semibold text-sm m-0 truncate">{t.name}</p>
83
84
  <p className="text-xs text-muted-foreground m-0">
84
85
  {t.duration_minutes ? `${t.duration_minutes} min · ` : ""}
85
- {t.currency ?? "GHS"} {t.default_price}
86
+ {brand.currency} {t.default_price}
86
87
  </p>
87
88
  </div>
88
89
  {active && (
@@ -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">{children}</main>
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 { headers } from "next/headers";
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
- const explicit = process.env.NEXT_PUBLIC_SITE_URL?.trim();
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*` },
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Sign in — ${brand.name}`,
7
- description: brand.account.loginSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link (the iframe in `<CimplifyAccount>`) handles sign-in
12
- * automatically when no session exists. We just bounce /login to /account
13
- * so consumers landing here get the right UI without a duplicate form.
14
- */
15
- export default function LoginPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Create an account — ${brand.name}`,
7
- description: brand.account.signupSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link handles sign-up inside the `<CimplifyAccount>` iframe.
12
- * We bounce /signup to /account; the iframe shows the create-account UI
13
- * for visitors with no session.
14
- */
15
- export default function SignupPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Sign in — ${brand.name}`,
7
- description: brand.account.loginSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link (the iframe in `<CimplifyAccount>`) handles sign-in
12
- * automatically when no session exists. We just bounce /login to /account
13
- * so consumers landing here get the right UI without a duplicate form.
14
- */
15
- export default function LoginPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Create an account — ${brand.name}`,
7
- description: brand.account.signupSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link handles sign-up inside the `<CimplifyAccount>` iframe.
12
- * We bounce /signup to /account; the iframe shows the create-account UI
13
- * for visitors with no session.
14
- */
15
- export default function SignupPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Sign in — ${brand.name}`,
7
- description: brand.account.loginSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link (the iframe in `<CimplifyAccount>`) handles sign-in
12
- * automatically when no session exists. We just bounce /login to /account
13
- * so consumers landing here get the right UI without a duplicate form.
14
- */
15
- export default function LoginPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Create an account — ${brand.name}`,
7
- description: brand.account.signupSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link handles sign-up inside the `<CimplifyAccount>` iframe.
12
- * We bounce /signup to /account; the iframe shows the create-account UI
13
- * for visitors with no session.
14
- */
15
- export default function SignupPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Sign in — ${brand.name}`,
7
- description: brand.account.loginSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link (the iframe in `<CimplifyAccount>`) handles sign-in
12
- * automatically when no session exists. We just bounce /login to /account
13
- * so consumers landing here get the right UI without a duplicate form.
14
- */
15
- export default function LoginPage(): never {
16
- redirect("/account");
17
- }
@@ -1,17 +0,0 @@
1
- import type { Metadata } from "next";
2
- import { redirect } from "next/navigation";
3
- import { brand } from "@/lib/brand";
4
-
5
- export const metadata: Metadata = {
6
- title: `Create an account — ${brand.name}`,
7
- description: brand.account.signupSubtitle,
8
- };
9
-
10
- /**
11
- * Cimplify Link handles sign-up inside the `<CimplifyAccount>` iframe.
12
- * We bounce /signup to /account; the iframe shows the create-account UI
13
- * for visitors with no session.
14
- */
15
- export default function SignupPage(): never {
16
- redirect("/account");
17
- }