@cimplify/sdk 0.41.0 → 0.42.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/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/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/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/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/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/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/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,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
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Guest order tracker. Routes /orders/<id> with the entered id; the
|
|
8
|
+
* post-checkout confirmation page already lives at /orders/[id] so this
|
|
9
|
+
* just sends the visitor there. Replace the redirect with a real lookup
|
|
10
|
+
* (e.g. Cimplify orders API + email-match guard) for production.
|
|
11
|
+
*/
|
|
12
|
+
export function TrackOrderForm() {
|
|
13
|
+
const router = useRouter();
|
|
14
|
+
const [orderId, setOrderId] = useState("");
|
|
15
|
+
const [email, setEmail] = useState("");
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<form
|
|
19
|
+
onSubmit={(e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
if (!orderId.trim()) return;
|
|
22
|
+
router.push(`/orders/${encodeURIComponent(orderId.trim())}`);
|
|
23
|
+
}}
|
|
24
|
+
className="space-y-4 rounded-2xl border border-border bg-card p-6"
|
|
25
|
+
>
|
|
26
|
+
<div>
|
|
27
|
+
<label
|
|
28
|
+
htmlFor="orderId"
|
|
29
|
+
className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5"
|
|
30
|
+
>
|
|
31
|
+
Order number
|
|
32
|
+
</label>
|
|
33
|
+
<input
|
|
34
|
+
id="orderId"
|
|
35
|
+
name="orderId"
|
|
36
|
+
required
|
|
37
|
+
value={orderId}
|
|
38
|
+
onChange={(e) => setOrderId(e.target.value)}
|
|
39
|
+
placeholder="ord_abc123…"
|
|
40
|
+
className="w-full px-4 py-3 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<label
|
|
45
|
+
htmlFor="email"
|
|
46
|
+
className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5"
|
|
47
|
+
>
|
|
48
|
+
Order email
|
|
49
|
+
</label>
|
|
50
|
+
<input
|
|
51
|
+
id="email"
|
|
52
|
+
name="email"
|
|
53
|
+
type="email"
|
|
54
|
+
required
|
|
55
|
+
value={email}
|
|
56
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
57
|
+
placeholder="you@email.com"
|
|
58
|
+
className="w-full px-4 py-3 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
<button
|
|
62
|
+
type="submit"
|
|
63
|
+
className="w-full inline-flex items-center justify-center px-5 py-3 rounded-md bg-primary text-primary-foreground font-semibold text-sm hover:bg-primary/90 transition-colors"
|
|
64
|
+
>
|
|
65
|
+
Track order
|
|
66
|
+
</button>
|
|
67
|
+
</form>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { CimplifyAccount } from "@cimplify/sdk/react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cimplify Account portal — iframe-mounted UI hosted by Cimplify Link.
|
|
7
|
+
* Handles sign-in, sign-up, OTP, addresses, payment methods, sessions,
|
|
8
|
+
* and order history. The iframe owns auth state; we just choose which
|
|
9
|
+
* `section` to land on.
|
|
10
|
+
*/
|
|
11
|
+
export function AccountIframe({ section }: { section?: string }) {
|
|
12
|
+
return <CimplifyAccount section={section} />;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { BrandPolicySection } from "@/lib/brand";
|
|
2
|
+
|
|
3
|
+
interface PolicyShape {
|
|
4
|
+
eyebrow: string;
|
|
5
|
+
title: string;
|
|
6
|
+
lastUpdated?: string;
|
|
7
|
+
sections: BrandPolicySection[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Shared layout for shipping / returns / accessibility / terms / privacy.
|
|
12
|
+
* Reads a `{ eyebrow, title, lastUpdated, sections[] }` block from brand.
|
|
13
|
+
*/
|
|
14
|
+
export function PolicyPage({ policy }: { policy: PolicyShape }) {
|
|
15
|
+
return (
|
|
16
|
+
<article className="max-w-3xl mx-auto px-8 py-16 prose prose-lg max-w-none">
|
|
17
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2 not-prose">
|
|
18
|
+
{policy.eyebrow}
|
|
19
|
+
</p>
|
|
20
|
+
<h1 className="text-[clamp(2.25rem,5vw,3.5rem)] font-semibold mb-2 -tracking-[0.02em]">
|
|
21
|
+
{policy.title}
|
|
22
|
+
</h1>
|
|
23
|
+
{policy.lastUpdated && (
|
|
24
|
+
<p className="text-sm text-muted-foreground not-prose mb-10">
|
|
25
|
+
Last updated: {policy.lastUpdated}
|
|
26
|
+
</p>
|
|
27
|
+
)}
|
|
28
|
+
<section className="space-y-5 leading-relaxed text-foreground/90">
|
|
29
|
+
{policy.sections.map((s) => (
|
|
30
|
+
<div key={s.heading}>
|
|
31
|
+
<h2 className="text-2xl font-semibold mt-0">{s.heading}</h2>
|
|
32
|
+
{typeof s.body === "string" ? (
|
|
33
|
+
<p>{s.body}</p>
|
|
34
|
+
) : (
|
|
35
|
+
<>
|
|
36
|
+
<p>{s.body.intro}</p>
|
|
37
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
38
|
+
{s.body.bullets.map((b) => (
|
|
39
|
+
<li key={b}>{b}</li>
|
|
40
|
+
))}
|
|
41
|
+
</ul>
|
|
42
|
+
</>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
))}
|
|
46
|
+
</section>
|
|
47
|
+
</article>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -62,6 +62,20 @@ export interface Brand {
|
|
|
62
62
|
};
|
|
63
63
|
terms: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
|
|
64
64
|
privacy: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
|
|
65
|
+
shipping: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
|
|
66
|
+
returns: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
|
|
67
|
+
accessibility: { eyebrow: string; title: string; lastUpdated: string; sections: BrandPolicySection[] };
|
|
68
|
+
account: {
|
|
69
|
+
loginEyebrow: string; loginTitle: string; loginSubtitle: string;
|
|
70
|
+
signupEyebrow: string; signupTitle: string; signupSubtitle: string;
|
|
71
|
+
accountEyebrow: string; accountTitle: string;
|
|
72
|
+
};
|
|
73
|
+
contactPage: {
|
|
74
|
+
eyebrow: string; title: string; body: string;
|
|
75
|
+
reasons: string[];
|
|
76
|
+
directLines: { label: string; value: string; href: string }[];
|
|
77
|
+
};
|
|
78
|
+
trackOrder: { eyebrow: string; title: string; body: string };
|
|
65
79
|
footer: {
|
|
66
80
|
blurb: string; sitemap: BrandSitemapSection[];
|
|
67
81
|
poweredBy?: { label: string; href: string };
|
|
@@ -365,6 +379,74 @@ export const brand: Brand = {
|
|
|
365
379
|
],
|
|
366
380
|
},
|
|
367
381
|
|
|
382
|
+
shipping: {
|
|
383
|
+
eyebrow: "Shipping",
|
|
384
|
+
title: "Shipping",
|
|
385
|
+
lastUpdated: "1 May 2026",
|
|
386
|
+
sections: [
|
|
387
|
+
{ heading: "Worldwide via DHL Express", body: "2–4 business days from order. Free over $80 (or local equivalent)." },
|
|
388
|
+
{ heading: "Customs & duties", body: "Charged on delivery in some countries — DHL contacts you. Prepay for orders over $300; email help@studiofrx.test before placing the order." },
|
|
389
|
+
{ heading: "Studio pickup", body: "Free, ready in 30 minutes. Tue–Sat 11am–8pm at Osu Oxford Street, Accra." },
|
|
390
|
+
{ heading: "Tracking", body: "DHL tracking link sent by email and SMS within an hour of dispatch." },
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
returns: {
|
|
395
|
+
eyebrow: "Returns",
|
|
396
|
+
title: "Returns & Exchanges",
|
|
397
|
+
lastUpdated: "1 May 2026",
|
|
398
|
+
sections: [
|
|
399
|
+
{ heading: "30 days, on us (US/UK/EU)", body: "Free return labels prepaid by us. Item must be unworn with original tags." },
|
|
400
|
+
{ heading: "Worldwide", body: "Return shipping is on you, but we'll refund or exchange immediately on receipt." },
|
|
401
|
+
{ heading: "Final sale", body: "Underwear, swim, custom pieces, and anything marked final-sale on the product page is non-returnable." },
|
|
402
|
+
{ heading: "How to start", body: "Email help@studiofrx.test with your order number. We'll send a label or instructions within 24 hours." },
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
|
|
406
|
+
accessibility: {
|
|
407
|
+
eyebrow: "Accessibility",
|
|
408
|
+
title: "Accessibility Statement",
|
|
409
|
+
lastUpdated: "1 May 2026",
|
|
410
|
+
sections: [
|
|
411
|
+
{ heading: "Our commitment", body: "We aim for WCAG 2.1 AA on this site, tested on each release." },
|
|
412
|
+
{ heading: "What we've done", body: { intro: "Specifically, we've:", bullets: [
|
|
413
|
+
"Maintained a minimum 4.5:1 contrast on body text.",
|
|
414
|
+
"Ensured every interactive element is keyboard-reachable.",
|
|
415
|
+
"Provided alt text on every image, including lookbook editorials.",
|
|
416
|
+
"Respected `prefers-reduced-motion` for the homepage editorial transitions.",
|
|
417
|
+
] } },
|
|
418
|
+
{ heading: "Reporting issues", body: "Email accessibility@studiofrx.test. We respond within 5 business days." },
|
|
419
|
+
],
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
account: {
|
|
423
|
+
loginEyebrow: "Welcome back",
|
|
424
|
+
loginTitle: "Sign in to FRX",
|
|
425
|
+
loginSubtitle: "Track orders, manage your collective access, and view past drops.",
|
|
426
|
+
signupEyebrow: "Welcome",
|
|
427
|
+
signupTitle: "Create your account",
|
|
428
|
+
signupSubtitle: "First-look at the next drop, faster checkout, and your size remembered.",
|
|
429
|
+
accountEyebrow: "Your account",
|
|
430
|
+
accountTitle: "Welcome back",
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
contactPage: {
|
|
434
|
+
eyebrow: "Contact",
|
|
435
|
+
title: "Talk to the studio.",
|
|
436
|
+
body: "Order question, sizing help, collab pitch — message us and we'll reply within a business day.",
|
|
437
|
+
reasons: ["General question", "An order issue", "Sizing help", "Returns / exchanges", "Press / collabs", "Wholesale"],
|
|
438
|
+
directLines: [
|
|
439
|
+
{ label: "Email", value: "help@studiofrx.test", href: "mailto:help@studiofrx.test" },
|
|
440
|
+
{ label: "Phone", value: "+233 244 000 000", href: "tel:+233244000000" },
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
trackOrder: {
|
|
445
|
+
eyebrow: "Track an order",
|
|
446
|
+
title: "Where's my drop?",
|
|
447
|
+
body: "Enter your order number and the email used at checkout. We'll show DHL's latest scan and the live ETA.",
|
|
448
|
+
},
|
|
449
|
+
|
|
368
450
|
footer: {
|
|
369
451
|
blurb:
|
|
370
452
|
"Limited-run streetwear from Accra. Heavyweight cotton, hand-screened, numbered. Each drop is a closed run.",
|
|
@@ -392,10 +474,21 @@ export const brand: Brand = {
|
|
|
392
474
|
{
|
|
393
475
|
title: "Help",
|
|
394
476
|
links: [
|
|
395
|
-
{ label: "Contact", href: "
|
|
396
|
-
{ label: "
|
|
397
|
-
{ label: "
|
|
398
|
-
{ label: "
|
|
477
|
+
{ label: "Contact", href: "/contact" },
|
|
478
|
+
{ label: "Track an order", href: "/track-order" },
|
|
479
|
+
{ label: "Size guide", href: "/size-guide" },
|
|
480
|
+
{ label: "Shipping", href: "/shipping" },
|
|
481
|
+
{ label: "Returns", href: "/returns" },
|
|
482
|
+
{ label: "FAQ", href: "/faq" },
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
title: "Account",
|
|
487
|
+
links: [
|
|
488
|
+
{ label: "Sign in", href: "/login" },
|
|
489
|
+
{ label: "Create account", href: "/signup" },
|
|
490
|
+
{ label: "Your orders", href: "/account/orders" },
|
|
491
|
+
{ label: "Settings", href: "/account/settings" },
|
|
399
492
|
],
|
|
400
493
|
},
|
|
401
494
|
{
|
|
@@ -403,6 +496,8 @@ export const brand: Brand = {
|
|
|
403
496
|
links: [
|
|
404
497
|
{ label: "Terms of Service", href: "/terms" },
|
|
405
498
|
{ label: "Privacy Policy", href: "/privacy" },
|
|
499
|
+
{ label: "Accessibility", href: "/accessibility" },
|
|
500
|
+
{ label: "Sitemap", href: "/sitemap-page" },
|
|
406
501
|
],
|
|
407
502
|
},
|
|
408
503
|
],
|
|
@@ -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.accessibility.title} — ${brand.name}`,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function AccessibilityPage() {
|
|
10
|
+
return <PolicyPage policy={brand.accessibility} />;
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { AccountIframe } from "@/components/account-iframe";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Addresses — ${brand.name}`,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function AddressesPage() {
|
|
10
|
+
return (
|
|
11
|
+
<article className="max-w-5xl mx-auto px-6 sm:px-8 py-12">
|
|
12
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
13
|
+
Account
|
|
14
|
+
</p>
|
|
15
|
+
<h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
|
|
16
|
+
Your addresses
|
|
17
|
+
</h1>
|
|
18
|
+
<AccountIframe section="addresses" />
|
|
19
|
+
</article>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { AccountIframe } from "@/components/account-iframe";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Orders — ${brand.name}`,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function OrdersPage() {
|
|
10
|
+
return (
|
|
11
|
+
<article className="max-w-5xl mx-auto px-6 sm:px-8 py-12">
|
|
12
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
13
|
+
Account
|
|
14
|
+
</p>
|
|
15
|
+
<h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
|
|
16
|
+
Your orders
|
|
17
|
+
</h1>
|
|
18
|
+
<AccountIframe section="orders" />
|
|
19
|
+
</article>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { AccountIframe } from "@/components/account-iframe";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Account — ${brand.name}`,
|
|
7
|
+
description: brand.account.signupSubtitle,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function AccountPage() {
|
|
11
|
+
return (
|
|
12
|
+
<article className="max-w-5xl mx-auto px-6 sm:px-8 py-12">
|
|
13
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
14
|
+
{brand.account.accountEyebrow}
|
|
15
|
+
</p>
|
|
16
|
+
<h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
|
|
17
|
+
{brand.account.accountTitle}
|
|
18
|
+
</h1>
|
|
19
|
+
<AccountIframe />
|
|
20
|
+
</article>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { AccountIframe } from "@/components/account-iframe";
|
|
3
|
+
import { brand } from "@/lib/brand";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: `Settings — ${brand.name}`,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function SettingsPage() {
|
|
10
|
+
return (
|
|
11
|
+
<article className="max-w-5xl mx-auto px-6 sm:px-8 py-12">
|
|
12
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
13
|
+
Account
|
|
14
|
+
</p>
|
|
15
|
+
<h1 className="font-serif text-[clamp(2rem,4vw,2.75rem)] font-semibold mb-8 -tracking-[0.02em]">
|
|
16
|
+
Settings
|
|
17
|
+
</h1>
|
|
18
|
+
<AccountIframe section="settings" />
|
|
19
|
+
</article>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Local-only contact form. On submit, fakes a successful response so the
|
|
7
|
+
* template ships a usable interaction. Wire to a real handler (Server
|
|
8
|
+
* Action posting to your CRM, support inbox, or Cimplify Support API)
|
|
9
|
+
* before going to production.
|
|
10
|
+
*/
|
|
11
|
+
export function ContactForm({ reasons }: { reasons: string[] }) {
|
|
12
|
+
const [submitted, setSubmitted] = useState(false);
|
|
13
|
+
const [reason, setReason] = useState(reasons[0] ?? "");
|
|
14
|
+
|
|
15
|
+
if (submitted) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="rounded-2xl border border-border bg-card p-8">
|
|
18
|
+
<p className="text-[11px] font-semibold uppercase tracking-[0.16em] text-primary mb-2">
|
|
19
|
+
✓ Sent
|
|
20
|
+
</p>
|
|
21
|
+
<h2 className="font-serif text-2xl font-semibold mb-2">Thanks — we'll be in touch.</h2>
|
|
22
|
+
<p className="text-muted-foreground">
|
|
23
|
+
We respond within one business day. Check your spam folder if you don't see a
|
|
24
|
+
reply by tomorrow.
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<form
|
|
32
|
+
onSubmit={(e) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
setSubmitted(true);
|
|
35
|
+
}}
|
|
36
|
+
className="space-y-5"
|
|
37
|
+
>
|
|
38
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
39
|
+
<Field label="Your name" name="name" required />
|
|
40
|
+
<Field label="Email" name="email" type="email" required />
|
|
41
|
+
</div>
|
|
42
|
+
<div>
|
|
43
|
+
<label className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5">
|
|
44
|
+
What's it about?
|
|
45
|
+
</label>
|
|
46
|
+
<select
|
|
47
|
+
name="reason"
|
|
48
|
+
value={reason}
|
|
49
|
+
onChange={(e) => setReason(e.target.value)}
|
|
50
|
+
className="w-full px-4 py-2.5 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm"
|
|
51
|
+
>
|
|
52
|
+
{reasons.map((r) => (
|
|
53
|
+
<option key={r} value={r}>
|
|
54
|
+
{r}
|
|
55
|
+
</option>
|
|
56
|
+
))}
|
|
57
|
+
</select>
|
|
58
|
+
</div>
|
|
59
|
+
<Field label="Order number (optional)" name="order" />
|
|
60
|
+
<div>
|
|
61
|
+
<label className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5">
|
|
62
|
+
Message
|
|
63
|
+
</label>
|
|
64
|
+
<textarea
|
|
65
|
+
name="message"
|
|
66
|
+
required
|
|
67
|
+
rows={6}
|
|
68
|
+
className="w-full px-4 py-2.5 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm resize-y"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
<button
|
|
72
|
+
type="submit"
|
|
73
|
+
className="inline-flex items-center justify-center px-5 py-3 rounded-md bg-primary text-primary-foreground font-semibold text-sm hover:bg-primary/90 transition-colors"
|
|
74
|
+
>
|
|
75
|
+
Send message
|
|
76
|
+
</button>
|
|
77
|
+
</form>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function Field({
|
|
82
|
+
label,
|
|
83
|
+
name,
|
|
84
|
+
type = "text",
|
|
85
|
+
required,
|
|
86
|
+
}: {
|
|
87
|
+
label: string;
|
|
88
|
+
name: string;
|
|
89
|
+
type?: string;
|
|
90
|
+
required?: boolean;
|
|
91
|
+
}) {
|
|
92
|
+
return (
|
|
93
|
+
<div>
|
|
94
|
+
<label
|
|
95
|
+
htmlFor={name}
|
|
96
|
+
className="text-xs font-semibold uppercase tracking-wider text-muted-foreground block mb-1.5"
|
|
97
|
+
>
|
|
98
|
+
{label}
|
|
99
|
+
</label>
|
|
100
|
+
<input
|
|
101
|
+
id={name}
|
|
102
|
+
name={name}
|
|
103
|
+
type={type}
|
|
104
|
+
required={required}
|
|
105
|
+
className="w-full px-4 py-2.5 rounded-md bg-background border border-border focus:border-primary focus:ring-2 focus:ring-primary/20 outline-none text-sm"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|