@cimplify/sdk 0.41.0 → 0.42.1
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/cli/commands/deploy.mjs +561 -0
- package/dist/cli/commands/dev.mjs +434 -0
- package/dist/cli/commands/domains.mjs +479 -0
- package/dist/cli/commands/env.mjs +610 -0
- package/dist/cli/commands/link.mjs +325 -0
- package/dist/cli/commands/login.mjs +515 -0
- package/dist/cli/commands/logout.mjs +54 -0
- package/dist/cli/commands/logs.mjs +358 -0
- package/dist/cli/commands/projects.mjs +327 -0
- package/dist/cli/commands/rollback.mjs +477 -0
- package/dist/cli/commands/status.mjs +380 -0
- package/dist/cli/commands/unlink.mjs +58 -0
- package/dist/cli/commands/whoami.mjs +234 -0
- package/dist/dispatcher.mjs +91 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/templates/storefront-bakery/AGENTS.md +61 -10
- package/templates/storefront-bakery/app/accessibility/page.tsx +11 -0
- package/templates/storefront-bakery/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-bakery/app/account/orders/page.tsx +21 -0
- package/templates/storefront-bakery/app/account/page.tsx +22 -0
- package/templates/storefront-bakery/app/account/settings/page.tsx +21 -0
- package/templates/storefront-bakery/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-bakery/app/contact/page.tsx +54 -0
- package/templates/storefront-bakery/app/login/page.tsx +17 -0
- package/templates/storefront-bakery/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-bakery/app/returns/page.tsx +11 -0
- package/templates/storefront-bakery/app/search/page.tsx +38 -0
- package/templates/storefront-bakery/app/search/search-client.tsx +7 -0
- package/templates/storefront-bakery/app/shipping/page.tsx +16 -0
- package/templates/storefront-bakery/app/signup/page.tsx +17 -0
- package/templates/storefront-bakery/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-bakery/app/track-order/page.tsx +24 -0
- package/templates/storefront-bakery/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-bakery/components/account-iframe.tsx +13 -0
- package/templates/storefront-bakery/components/policy-page.tsx +49 -0
- package/templates/storefront-bakery/lib/brand.ts +119 -4
- package/templates/storefront-fashion/AGENTS.md +80 -26
- package/templates/storefront-fashion/app/accessibility/page.tsx +11 -0
- package/templates/storefront-fashion/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-fashion/app/account/orders/page.tsx +21 -0
- package/templates/storefront-fashion/app/account/page.tsx +22 -0
- package/templates/storefront-fashion/app/account/settings/page.tsx +21 -0
- package/templates/storefront-fashion/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-fashion/app/contact/page.tsx +54 -0
- package/templates/storefront-fashion/app/login/page.tsx +17 -0
- package/templates/storefront-fashion/app/lookbook/page.tsx +132 -0
- package/templates/storefront-fashion/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-fashion/app/returns/page.tsx +11 -0
- package/templates/storefront-fashion/app/search/page.tsx +38 -0
- package/templates/storefront-fashion/app/search/search-client.tsx +7 -0
- package/templates/storefront-fashion/app/shipping/page.tsx +16 -0
- package/templates/storefront-fashion/app/signup/page.tsx +17 -0
- package/templates/storefront-fashion/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-fashion/app/size-guide/page.tsx +155 -0
- package/templates/storefront-fashion/app/track-order/page.tsx +24 -0
- package/templates/storefront-fashion/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-fashion/components/account-iframe.tsx +13 -0
- package/templates/storefront-fashion/components/policy-page.tsx +49 -0
- package/templates/storefront-fashion/lib/brand.ts +99 -4
- package/templates/storefront-grocery/AGENTS.md +56 -11
- package/templates/storefront-grocery/app/accessibility/page.tsx +11 -0
- package/templates/storefront-grocery/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-grocery/app/account/orders/page.tsx +21 -0
- package/templates/storefront-grocery/app/account/page.tsx +22 -0
- package/templates/storefront-grocery/app/account/settings/page.tsx +21 -0
- package/templates/storefront-grocery/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-grocery/app/contact/page.tsx +54 -0
- package/templates/storefront-grocery/app/login/page.tsx +17 -0
- package/templates/storefront-grocery/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-grocery/app/returns/page.tsx +11 -0
- package/templates/storefront-grocery/app/search/page.tsx +38 -0
- package/templates/storefront-grocery/app/search/search-client.tsx +7 -0
- package/templates/storefront-grocery/app/shipping/page.tsx +16 -0
- package/templates/storefront-grocery/app/signup/page.tsx +17 -0
- package/templates/storefront-grocery/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-grocery/app/track-order/page.tsx +24 -0
- package/templates/storefront-grocery/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-grocery/components/account-iframe.tsx +13 -0
- package/templates/storefront-grocery/components/policy-page.tsx +49 -0
- package/templates/storefront-grocery/lib/brand.ts +92 -3
- package/templates/storefront-restaurant/AGENTS.md +64 -13
- package/templates/storefront-restaurant/app/accessibility/page.tsx +11 -0
- package/templates/storefront-restaurant/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-restaurant/app/account/orders/page.tsx +21 -0
- package/templates/storefront-restaurant/app/account/page.tsx +22 -0
- package/templates/storefront-restaurant/app/account/settings/page.tsx +21 -0
- package/templates/storefront-restaurant/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-restaurant/app/contact/page.tsx +54 -0
- package/templates/storefront-restaurant/app/login/page.tsx +17 -0
- package/templates/storefront-restaurant/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-restaurant/app/reservations/page.tsx +66 -0
- package/templates/storefront-restaurant/app/reservations/reservations-client.tsx +234 -0
- package/templates/storefront-restaurant/app/returns/page.tsx +11 -0
- package/templates/storefront-restaurant/app/search/page.tsx +38 -0
- package/templates/storefront-restaurant/app/search/search-client.tsx +7 -0
- package/templates/storefront-restaurant/app/shipping/page.tsx +16 -0
- package/templates/storefront-restaurant/app/signup/page.tsx +17 -0
- package/templates/storefront-restaurant/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-restaurant/app/track-order/page.tsx +24 -0
- package/templates/storefront-restaurant/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-restaurant/components/account-iframe.tsx +13 -0
- package/templates/storefront-restaurant/components/policy-page.tsx +49 -0
- package/templates/storefront-restaurant/lib/brand.ts +91 -3
- package/templates/storefront-retail/AGENTS.md +65 -24
- package/templates/storefront-retail/app/accessibility/page.tsx +11 -0
- package/templates/storefront-retail/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-retail/app/account/orders/page.tsx +21 -0
- package/templates/storefront-retail/app/account/page.tsx +22 -0
- package/templates/storefront-retail/app/account/settings/page.tsx +21 -0
- package/templates/storefront-retail/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-retail/app/contact/page.tsx +54 -0
- package/templates/storefront-retail/app/login/page.tsx +17 -0
- package/templates/storefront-retail/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-retail/app/returns/page.tsx +11 -0
- package/templates/storefront-retail/app/search/page.tsx +38 -0
- package/templates/storefront-retail/app/search/search-client.tsx +7 -0
- package/templates/storefront-retail/app/shipping/page.tsx +16 -0
- package/templates/storefront-retail/app/signup/page.tsx +17 -0
- package/templates/storefront-retail/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-retail/app/track-order/page.tsx +24 -0
- package/templates/storefront-retail/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-retail/components/account-iframe.tsx +13 -0
- package/templates/storefront-retail/components/policy-page.tsx +49 -0
- package/templates/storefront-retail/lib/brand.ts +105 -9
- package/templates/storefront-services/AGENTS.md +64 -16
- package/templates/storefront-services/app/accessibility/page.tsx +11 -0
- package/templates/storefront-services/app/account/addresses/page.tsx +21 -0
- package/templates/storefront-services/app/account/orders/page.tsx +21 -0
- package/templates/storefront-services/app/account/page.tsx +22 -0
- package/templates/storefront-services/app/account/settings/page.tsx +21 -0
- package/templates/storefront-services/app/book/book-client.tsx +195 -0
- package/templates/storefront-services/app/book/page.tsx +65 -0
- package/templates/storefront-services/app/contact/contact-form.tsx +109 -0
- package/templates/storefront-services/app/contact/page.tsx +54 -0
- package/templates/storefront-services/app/login/page.tsx +17 -0
- package/templates/storefront-services/app/opensearch.xml/route.ts +37 -0
- package/templates/storefront-services/app/returns/page.tsx +11 -0
- package/templates/storefront-services/app/search/page.tsx +38 -0
- package/templates/storefront-services/app/search/search-client.tsx +7 -0
- package/templates/storefront-services/app/shipping/page.tsx +16 -0
- package/templates/storefront-services/app/signup/page.tsx +17 -0
- package/templates/storefront-services/app/sitemap-page/page.tsx +167 -0
- package/templates/storefront-services/app/track-order/page.tsx +24 -0
- package/templates/storefront-services/app/track-order/track-order-form.tsx +69 -0
- package/templates/storefront-services/components/account-iframe.tsx +13 -0
- package/templates/storefront-services/components/policy-page.tsx +49 -0
- package/templates/storefront-services/lib/brand.ts +93 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Image from "next/image";
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { brand } from "@/lib/brand";
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: `Lookbook — ${brand.name}`,
|
|
8
|
+
description: "Editorial photography from each Studio FRX drop.",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
interface LookbookEntry {
|
|
12
|
+
drop: string;
|
|
13
|
+
title: string;
|
|
14
|
+
hero: string;
|
|
15
|
+
tiles: string[];
|
|
16
|
+
byline: string;
|
|
17
|
+
date: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Each entry is a drop; tiles are the editorial shots. Replace the URLs
|
|
21
|
+
// with your own asset bucket / CDN before going live.
|
|
22
|
+
const ENTRIES: LookbookEntry[] = [
|
|
23
|
+
{
|
|
24
|
+
drop: "Drop 04",
|
|
25
|
+
title: "Built for now.",
|
|
26
|
+
date: "Spring 2026",
|
|
27
|
+
byline: "Photographed by Selasi Adjei in Jamestown, Accra.",
|
|
28
|
+
hero: "https://images.unsplash.com/photo-1490481651871-ab68de25d43d?w=1800&h=1100&fit=crop&auto=format&q=85",
|
|
29
|
+
tiles: [
|
|
30
|
+
"https://images.unsplash.com/photo-1483985988355-763728e1935b?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
31
|
+
"https://images.unsplash.com/photo-1521572163474-6864f9cf17ab?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
32
|
+
"https://images.unsplash.com/photo-1485518882345-15568b007407?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
drop: "Drop 03",
|
|
37
|
+
title: "Heavyweight, hand-screened.",
|
|
38
|
+
date: "Winter 2025",
|
|
39
|
+
byline: "Shot at the Tema dye works.",
|
|
40
|
+
hero: "https://images.unsplash.com/photo-1467043153537-a4fba2cd39ef?w=1800&h=1100&fit=crop&auto=format&q=85",
|
|
41
|
+
tiles: [
|
|
42
|
+
"https://images.unsplash.com/photo-1576566588028-4147f3842f27?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
43
|
+
"https://images.unsplash.com/photo-1542838686-37da4a9fd1b3?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
44
|
+
"https://images.unsplash.com/photo-1496747611176-843222e1e57c?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
drop: "Drop 02",
|
|
49
|
+
title: "From the studio floor.",
|
|
50
|
+
date: "Autumn 2025",
|
|
51
|
+
byline: "Studio days, Osu.",
|
|
52
|
+
hero: "https://images.unsplash.com/photo-1525507119028-ed4c629a60a3?w=1800&h=1100&fit=crop&auto=format&q=85",
|
|
53
|
+
tiles: [
|
|
54
|
+
"https://images.unsplash.com/photo-1512436991641-6745cdb1723f?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
55
|
+
"https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
56
|
+
"https://images.unsplash.com/photo-1490114538077-0a7f8cb49891?w=900&h=1200&fit=crop&auto=format&q=85",
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export default function LookbookPage() {
|
|
62
|
+
return (
|
|
63
|
+
<article>
|
|
64
|
+
<section className="border-b border-border">
|
|
65
|
+
<div className="max-w-7xl mx-auto px-6 sm:px-10 py-14">
|
|
66
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.2em] text-primary mb-3">
|
|
67
|
+
Lookbook
|
|
68
|
+
</p>
|
|
69
|
+
<h1 className="font-display text-[clamp(3rem,9vw,7rem)] uppercase mb-4 -tracking-[0.04em] leading-[0.92]">
|
|
70
|
+
The drops, in full.
|
|
71
|
+
</h1>
|
|
72
|
+
<p className="text-base text-muted-foreground max-w-2xl leading-relaxed">
|
|
73
|
+
Each drop is photographed in the room it's built in. Editorials below;
|
|
74
|
+
full collection in the shop.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
|
|
79
|
+
{ENTRIES.map((entry, i) => (
|
|
80
|
+
<section key={entry.drop} className="border-b border-border">
|
|
81
|
+
<div className="max-w-7xl mx-auto px-6 sm:px-10 py-16">
|
|
82
|
+
<div className="flex items-end justify-between gap-6 mb-8">
|
|
83
|
+
<div>
|
|
84
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.2em] text-muted-foreground mb-2">
|
|
85
|
+
{entry.drop} · {entry.date}
|
|
86
|
+
</p>
|
|
87
|
+
<h2 className="font-display text-[clamp(2rem,5vw,3.5rem)] uppercase m-0 -tracking-[0.04em] leading-[0.95]">
|
|
88
|
+
{entry.title}
|
|
89
|
+
</h2>
|
|
90
|
+
<p className="text-sm text-muted-foreground mt-2">{entry.byline}</p>
|
|
91
|
+
</div>
|
|
92
|
+
<Link
|
|
93
|
+
href="/shop"
|
|
94
|
+
className="hidden sm:inline-flex items-center gap-2 text-sm font-semibold uppercase tracking-wider text-foreground hover:text-primary transition-colors whitespace-nowrap"
|
|
95
|
+
>
|
|
96
|
+
Shop {entry.drop} →
|
|
97
|
+
</Link>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="relative w-full aspect-[16/9] rounded-2xl overflow-hidden bg-muted mb-3">
|
|
101
|
+
<Image
|
|
102
|
+
src={entry.hero}
|
|
103
|
+
alt={`${entry.drop} hero — ${entry.title}`}
|
|
104
|
+
fill
|
|
105
|
+
sizes="(min-width: 1280px) 1280px, 100vw"
|
|
106
|
+
className="object-cover"
|
|
107
|
+
priority={i === 0}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
|
112
|
+
{entry.tiles.map((src, ti) => (
|
|
113
|
+
<div
|
|
114
|
+
key={src}
|
|
115
|
+
className="relative aspect-[3/4] rounded-2xl overflow-hidden bg-muted"
|
|
116
|
+
>
|
|
117
|
+
<Image
|
|
118
|
+
src={src}
|
|
119
|
+
alt={`${entry.drop} editorial ${ti + 1}`}
|
|
120
|
+
fill
|
|
121
|
+
sizes="(min-width: 768px) 33vw, 100vw"
|
|
122
|
+
className="object-cover"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
))}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
))}
|
|
130
|
+
</article>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { brand } from "@/lib/brand";
|
|
2
|
+
|
|
3
|
+
const SITE_URL =
|
|
4
|
+
process.env.NEXT_PUBLIC_SITE_URL?.trim() || "https://example.com";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OpenSearch description document — lets browsers add this site to the
|
|
8
|
+
* address bar's search engine list. When users press Tab after typing the
|
|
9
|
+
* domain, they get an inline search box that hits /search?q=...
|
|
10
|
+
*/
|
|
11
|
+
export async function GET(): Promise<Response> {
|
|
12
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
13
|
+
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
|
14
|
+
<ShortName>${escapeXml(brand.shortName)}</ShortName>
|
|
15
|
+
<Description>Search ${escapeXml(brand.name)}</Description>
|
|
16
|
+
<InputEncoding>UTF-8</InputEncoding>
|
|
17
|
+
<Url type="text/html" method="get" template="${SITE_URL}/search?q={searchTerms}" />
|
|
18
|
+
<Url type="application/opensearchdescription+xml" rel="self" template="${SITE_URL}/opensearch.xml" />
|
|
19
|
+
<moz:SearchForm>${SITE_URL}/search</moz:SearchForm>
|
|
20
|
+
</OpenSearchDescription>
|
|
21
|
+
`;
|
|
22
|
+
return new Response(xml, {
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/opensearchdescription+xml; charset=utf-8",
|
|
25
|
+
"Cache-Control": "public, max-age=86400",
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function escapeXml(s: string): string {
|
|
31
|
+
return s
|
|
32
|
+
.replace(/&/g, "&")
|
|
33
|
+
.replace(/</g, "<")
|
|
34
|
+
.replace(/>/g, ">")
|
|
35
|
+
.replace(/"/g, """)
|
|
36
|
+
.replace(/'/g, "'");
|
|
37
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
import { PolicyPage } from "@/components/policy-page";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `${brand.returns.title} — ${brand.name}`,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function ReturnsPage() {
|
|
10
|
+
return <PolicyPage policy={brand.returns} />;
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { SearchClient } from "./search-client";
|
|
4
|
+
import { brand } from "@/lib/brand";
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: `Search — ${brand.name}`,
|
|
8
|
+
description: `Search ${brand.name} — products, collections, categories.`,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function SearchPage() {
|
|
12
|
+
return (
|
|
13
|
+
<article className="max-w-7xl mx-auto px-6 sm:px-8 py-10">
|
|
14
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
15
|
+
Search
|
|
16
|
+
</p>
|
|
17
|
+
<h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
|
|
18
|
+
Find anything.
|
|
19
|
+
</h1>
|
|
20
|
+
<Suspense fallback={<SearchSkeleton />}>
|
|
21
|
+
<SearchClient />
|
|
22
|
+
</Suspense>
|
|
23
|
+
</article>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function SearchSkeleton() {
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
<div className="h-12 w-full max-w-xl bg-muted rounded animate-pulse mb-8" />
|
|
31
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-4">
|
|
32
|
+
{Array.from({ length: 8 }).map((_, i) => (
|
|
33
|
+
<div key={i} className="aspect-[4/3] bg-muted rounded-2xl animate-pulse" />
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
import { PolicyPage } from "@/components/policy-page";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `${brand.shipping.title} — ${brand.name}`,
|
|
7
|
+
description: brand.shipping.sections[0]?.body
|
|
8
|
+
? typeof brand.shipping.sections[0].body === "string"
|
|
9
|
+
? brand.shipping.sections[0].body
|
|
10
|
+
: brand.shipping.sections[0].body.intro
|
|
11
|
+
: undefined,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function ShippingPage() {
|
|
15
|
+
return <PolicyPage policy={brand.shipping} />;
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import { cacheTag, cacheLife } from "next/cache";
|
|
4
|
+
import { getServerClient, tags, type Product } from "@cimplify/sdk/server";
|
|
5
|
+
import { brand } from "@/lib/brand";
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: `Sitemap — ${brand.name}`,
|
|
9
|
+
description: "A human-readable index of every page on this site.",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface SitemapData {
|
|
13
|
+
products: { slug: string; name: string }[];
|
|
14
|
+
categories: { slug: string; name: string }[];
|
|
15
|
+
collections: { slug: string; name: string }[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getSitemap(): Promise<SitemapData> {
|
|
19
|
+
"use cache";
|
|
20
|
+
cacheTag(tags.products(), tags.categories(), tags.collections());
|
|
21
|
+
cacheLife("hours");
|
|
22
|
+
|
|
23
|
+
const client = getServerClient();
|
|
24
|
+
const [pRes, cRes, colRes] = await Promise.all([
|
|
25
|
+
client.catalogue.getProducts({ limit: 500 }),
|
|
26
|
+
client.catalogue.getCategories(),
|
|
27
|
+
client.catalogue.getCollections(),
|
|
28
|
+
]);
|
|
29
|
+
return {
|
|
30
|
+
products: (pRes.ok ? pRes.value.items : []).map((p: Product) => ({
|
|
31
|
+
slug: p.slug ?? p.id,
|
|
32
|
+
name: p.name,
|
|
33
|
+
})),
|
|
34
|
+
categories: (cRes.ok ? cRes.value : []).map((c) => ({ slug: c.slug, name: c.name })),
|
|
35
|
+
collections: (colRes.ok ? colRes.value : []).map((c) => ({ slug: c.slug, name: c.name })),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const STATIC_LINKS: { title: string; links: { href: string; label: string }[] }[] = [
|
|
40
|
+
{
|
|
41
|
+
title: "Browse",
|
|
42
|
+
links: [
|
|
43
|
+
{ href: "/", label: "Home" },
|
|
44
|
+
{ href: "/shop", label: "Shop" },
|
|
45
|
+
{ href: "/search", label: "Search" },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: "Account",
|
|
50
|
+
links: [
|
|
51
|
+
{ href: "/account", label: "Account" },
|
|
52
|
+
{ href: "/account/orders", label: "Orders" },
|
|
53
|
+
{ href: "/account/addresses", label: "Addresses" },
|
|
54
|
+
{ href: "/account/settings", label: "Settings" },
|
|
55
|
+
{ href: "/login", label: "Sign in" },
|
|
56
|
+
{ href: "/signup", label: "Create account" },
|
|
57
|
+
{ href: "/track-order", label: "Track an order" },
|
|
58
|
+
{ href: "/cart", label: "Cart" },
|
|
59
|
+
{ href: "/checkout", label: "Checkout" },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: "About",
|
|
64
|
+
links: [
|
|
65
|
+
{ href: "/about", label: "About" },
|
|
66
|
+
{ href: "/faq", label: "FAQ" },
|
|
67
|
+
{ href: "/contact", label: "Contact" },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: "Policies",
|
|
72
|
+
links: [
|
|
73
|
+
{ href: "/shipping", label: "Shipping" },
|
|
74
|
+
{ href: "/returns", label: "Returns" },
|
|
75
|
+
{ href: "/accessibility", label: "Accessibility" },
|
|
76
|
+
{ href: "/terms", label: "Terms of Service" },
|
|
77
|
+
{ href: "/privacy", label: "Privacy Policy" },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
title: "Machine-readable",
|
|
82
|
+
links: [
|
|
83
|
+
{ href: "/sitemap.xml", label: "sitemap.xml (search engines)" },
|
|
84
|
+
{ href: "/llms.txt", label: "llms.txt (LLM agents)" },
|
|
85
|
+
{ href: "/robots.txt", label: "robots.txt" },
|
|
86
|
+
{ href: "/opensearch.xml", label: "opensearch.xml (browser search)" },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
export default async function SitemapHtmlPage() {
|
|
92
|
+
const { products, categories, collections } = await getSitemap();
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<article className="max-w-5xl mx-auto px-6 sm:px-8 py-16">
|
|
96
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
97
|
+
Sitemap
|
|
98
|
+
</p>
|
|
99
|
+
<h1 className="text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-2 -tracking-[0.02em]">
|
|
100
|
+
Every page, in one place.
|
|
101
|
+
</h1>
|
|
102
|
+
<p className="text-muted-foreground mb-12">
|
|
103
|
+
For search engines, see <Link href="/sitemap.xml" className="text-primary hover:underline">/sitemap.xml</Link>.
|
|
104
|
+
For LLM agents, see <Link href="/llms.txt" className="text-primary hover:underline">/llms.txt</Link>.
|
|
105
|
+
</p>
|
|
106
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
|
107
|
+
{STATIC_LINKS.map((s) => (
|
|
108
|
+
<Section key={s.title} title={s.title}>
|
|
109
|
+
{s.links.map((l) => (
|
|
110
|
+
<li key={l.href}>
|
|
111
|
+
<Link href={l.href} className="hover:text-primary transition-colors">
|
|
112
|
+
{l.label}
|
|
113
|
+
</Link>
|
|
114
|
+
</li>
|
|
115
|
+
))}
|
|
116
|
+
</Section>
|
|
117
|
+
))}
|
|
118
|
+
{categories.length > 0 && (
|
|
119
|
+
<Section title="Categories">
|
|
120
|
+
{categories.map((c) => (
|
|
121
|
+
<li key={c.slug}>
|
|
122
|
+
<Link href={`/categories/${c.slug}`} className="hover:text-primary transition-colors">
|
|
123
|
+
{c.name}
|
|
124
|
+
</Link>
|
|
125
|
+
</li>
|
|
126
|
+
))}
|
|
127
|
+
</Section>
|
|
128
|
+
)}
|
|
129
|
+
{collections.length > 0 && (
|
|
130
|
+
<Section title="Collections">
|
|
131
|
+
{collections.map((c) => (
|
|
132
|
+
<li key={c.slug}>
|
|
133
|
+
<Link href={`/collections/${c.slug}`} className="hover:text-primary transition-colors">
|
|
134
|
+
{c.name}
|
|
135
|
+
</Link>
|
|
136
|
+
</li>
|
|
137
|
+
))}
|
|
138
|
+
</Section>
|
|
139
|
+
)}
|
|
140
|
+
{products.length > 0 && (
|
|
141
|
+
<Section title={`Products (${products.length})`}>
|
|
142
|
+
{products.map((p) => (
|
|
143
|
+
<li key={p.slug}>
|
|
144
|
+
<Link href={`/shop?product=${encodeURIComponent(p.slug)}`} className="hover:text-primary transition-colors">
|
|
145
|
+
{p.name}
|
|
146
|
+
</Link>
|
|
147
|
+
</li>
|
|
148
|
+
))}
|
|
149
|
+
</Section>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
</article>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
|
157
|
+
return (
|
|
158
|
+
<div>
|
|
159
|
+
<p className="font-semibold text-[12px] uppercase tracking-[0.12em] text-foreground mb-3">
|
|
160
|
+
{title}
|
|
161
|
+
</p>
|
|
162
|
+
<ul className="space-y-2 m-0 p-0 list-none text-sm text-muted-foreground">
|
|
163
|
+
{children}
|
|
164
|
+
</ul>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { brand } from "@/lib/brand";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: `Size Guide — ${brand.name}`,
|
|
6
|
+
description: "Chest, length, and shoulder measurements for every Studio FRX piece.",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
interface SizeRow {
|
|
10
|
+
size: string;
|
|
11
|
+
chest: string;
|
|
12
|
+
length: string;
|
|
13
|
+
shoulder: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TOPS_CHART: SizeRow[] = [
|
|
17
|
+
{ size: "XS", chest: "104 cm / 41 in", length: "68 cm / 26.8 in", shoulder: "50 cm / 19.7 in" },
|
|
18
|
+
{ size: "S", chest: "110 cm / 43.3 in", length: "70 cm / 27.5 in", shoulder: "52 cm / 20.5 in" },
|
|
19
|
+
{ size: "M", chest: "116 cm / 45.7 in", length: "72 cm / 28.3 in", shoulder: "54 cm / 21.3 in" },
|
|
20
|
+
{ size: "L", chest: "122 cm / 48 in", length: "74 cm / 29.1 in", shoulder: "56 cm / 22 in" },
|
|
21
|
+
{ size: "XL", chest: "128 cm / 50.4 in", length: "76 cm / 29.9 in", shoulder: "58 cm / 22.8 in" },
|
|
22
|
+
{ size: "2XL", chest: "134 cm / 52.7 in", length: "78 cm / 30.7 in", shoulder: "60 cm / 23.6 in" },
|
|
23
|
+
{ size: "3XL", chest: "140 cm / 55.1 in", length: "80 cm / 31.5 in", shoulder: "62 cm / 24.4 in" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const BOTTOMS_CHART: { size: string; waist: string; hip: string; inseam: string }[] = [
|
|
27
|
+
{ size: "XS", waist: "72 cm / 28 in", hip: "92 cm / 36 in", inseam: "78 cm / 30.7 in" },
|
|
28
|
+
{ size: "S", waist: "76 cm / 30 in", hip: "96 cm / 38 in", inseam: "78 cm / 30.7 in" },
|
|
29
|
+
{ size: "M", waist: "82 cm / 32 in", hip: "102 cm / 40 in", inseam: "80 cm / 31.5 in" },
|
|
30
|
+
{ size: "L", waist: "88 cm / 35 in", hip: "108 cm / 42.5 in", inseam: "80 cm / 31.5 in" },
|
|
31
|
+
{ size: "XL", waist: "94 cm / 37 in", hip: "114 cm / 45 in", inseam: "82 cm / 32.3 in" },
|
|
32
|
+
{ size: "2XL", waist: "102 cm / 40 in", hip: "122 cm / 48 in", inseam: "82 cm / 32.3 in" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export default function SizeGuidePage() {
|
|
36
|
+
return (
|
|
37
|
+
<article className="max-w-4xl mx-auto px-6 sm:px-8 py-16">
|
|
38
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.2em] text-primary mb-2">
|
|
39
|
+
Size guide
|
|
40
|
+
</p>
|
|
41
|
+
<h1 className="font-display text-[clamp(2.5rem,7vw,5rem)] uppercase mb-4 -tracking-[0.04em] leading-[0.95]">
|
|
42
|
+
Find your fit.
|
|
43
|
+
</h1>
|
|
44
|
+
<p className="text-base text-muted-foreground leading-relaxed max-w-2xl mb-12">
|
|
45
|
+
Our pieces are cut for an oversized, dropped-shoulder fit. Order true to size for
|
|
46
|
+
the intended fit; size down for a regular cut. All measurements are flat-laid
|
|
47
|
+
garment measurements.
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<section className="mb-14">
|
|
51
|
+
<h2 className="text-2xl font-bold mb-2 -tracking-[0.025em]">Tops & outerwear</h2>
|
|
52
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
53
|
+
Hoodies, tees, sweats, jackets. Chest measured pit-to-pit, doubled. Length
|
|
54
|
+
measured from highest shoulder seam to hem.
|
|
55
|
+
</p>
|
|
56
|
+
<SizeTable
|
|
57
|
+
headings={["Size", "Chest", "Length", "Shoulder"]}
|
|
58
|
+
rows={TOPS_CHART.map((r) => [r.size, r.chest, r.length, r.shoulder])}
|
|
59
|
+
/>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section className="mb-14">
|
|
63
|
+
<h2 className="text-2xl font-bold mb-2 -tracking-[0.025em]">Bottoms</h2>
|
|
64
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
65
|
+
Trousers, shorts, sweats. Waist measured flat across the top of the
|
|
66
|
+
waistband, doubled.
|
|
67
|
+
</p>
|
|
68
|
+
<SizeTable
|
|
69
|
+
headings={["Size", "Waist", "Hip", "Inseam"]}
|
|
70
|
+
rows={BOTTOMS_CHART.map((r) => [r.size, r.waist, r.hip, r.inseam])}
|
|
71
|
+
/>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
<section className="rounded-2xl bg-foreground text-background p-8">
|
|
75
|
+
<h2 className="text-xl font-bold mb-3 -tracking-[0.025em]">How to measure yourself</h2>
|
|
76
|
+
<ul className="space-y-3 text-background/85 leading-relaxed">
|
|
77
|
+
<li>
|
|
78
|
+
<strong className="text-background">Chest</strong> — Wrap a soft tape around
|
|
79
|
+
the fullest part of your chest, under the arms, keeping the tape level.
|
|
80
|
+
</li>
|
|
81
|
+
<li>
|
|
82
|
+
<strong className="text-background">Waist</strong> — Around the narrowest part
|
|
83
|
+
of your waist, just above the navel.
|
|
84
|
+
</li>
|
|
85
|
+
<li>
|
|
86
|
+
<strong className="text-background">Hip</strong> — Around the fullest part of
|
|
87
|
+
your hips, feet together.
|
|
88
|
+
</li>
|
|
89
|
+
<li>
|
|
90
|
+
<strong className="text-background">Inseam</strong> — From the very top of the
|
|
91
|
+
inside of your leg down to the floor (no shoes).
|
|
92
|
+
</li>
|
|
93
|
+
</ul>
|
|
94
|
+
</section>
|
|
95
|
+
|
|
96
|
+
<p className="mt-12 text-sm text-muted-foreground">
|
|
97
|
+
Still unsure?{" "}
|
|
98
|
+
<a
|
|
99
|
+
href={`mailto:${brand.faq.contactEmail}`}
|
|
100
|
+
className="text-primary font-semibold hover:underline"
|
|
101
|
+
>
|
|
102
|
+
Email us your usual size in another brand
|
|
103
|
+
</a>{" "}
|
|
104
|
+
and we'll recommend a fit.
|
|
105
|
+
</p>
|
|
106
|
+
</article>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function SizeTable({
|
|
111
|
+
headings,
|
|
112
|
+
rows,
|
|
113
|
+
}: {
|
|
114
|
+
headings: string[];
|
|
115
|
+
rows: string[][];
|
|
116
|
+
}) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="overflow-x-auto rounded-2xl border border-border">
|
|
119
|
+
<table className="w-full text-sm">
|
|
120
|
+
<thead className="bg-muted">
|
|
121
|
+
<tr>
|
|
122
|
+
{headings.map((h) => (
|
|
123
|
+
<th
|
|
124
|
+
key={h}
|
|
125
|
+
className="text-left font-semibold uppercase tracking-wider text-[11px] text-muted-foreground px-4 py-3"
|
|
126
|
+
>
|
|
127
|
+
{h}
|
|
128
|
+
</th>
|
|
129
|
+
))}
|
|
130
|
+
</tr>
|
|
131
|
+
</thead>
|
|
132
|
+
<tbody>
|
|
133
|
+
{rows.map((r, i) => (
|
|
134
|
+
<tr
|
|
135
|
+
key={r[0]}
|
|
136
|
+
className={i % 2 === 0 ? "bg-background" : "bg-muted/30"}
|
|
137
|
+
>
|
|
138
|
+
{r.map((cell, j) => (
|
|
139
|
+
<td
|
|
140
|
+
key={j}
|
|
141
|
+
className={[
|
|
142
|
+
"px-4 py-3 tabular-nums",
|
|
143
|
+
j === 0 ? "font-semibold text-foreground" : "text-foreground/80",
|
|
144
|
+
].join(" ")}
|
|
145
|
+
>
|
|
146
|
+
{cell}
|
|
147
|
+
</td>
|
|
148
|
+
))}
|
|
149
|
+
</tr>
|
|
150
|
+
))}
|
|
151
|
+
</tbody>
|
|
152
|
+
</table>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { TrackOrderForm } from "./track-order-form";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Track an order — ${brand.name}`,
|
|
7
|
+
description: brand.trackOrder.body,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function TrackOrderPage() {
|
|
11
|
+
const t = brand.trackOrder;
|
|
12
|
+
return (
|
|
13
|
+
<article className="max-w-2xl mx-auto px-6 sm:px-8 py-16">
|
|
14
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
15
|
+
{t.eyebrow}
|
|
16
|
+
</p>
|
|
17
|
+
<h1 className="font-serif text-[clamp(2rem,5vw,3rem)] font-semibold mb-4 -tracking-[0.02em]">
|
|
18
|
+
{t.title}
|
|
19
|
+
</h1>
|
|
20
|
+
<p className="text-muted-foreground leading-relaxed mb-8">{t.body}</p>
|
|
21
|
+
<TrackOrderForm />
|
|
22
|
+
</article>
|
|
23
|
+
);
|
|
24
|
+
}
|