@cimplify/cli 0.7.10 → 0.7.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-SMWNH54I.mjs → add-3CEDUXNO.mjs} +1 -1
- package/dist/chunk-DAE3YSKU.mjs +6151 -0
- package/dist/{chunk-DTKRN5WC.mjs → chunk-XB4MMPXC.mjs} +2 -2
- package/dist/{chunk-AGJ3GDM5.mjs → chunk-ZFCWAYK2.mjs} +1 -1
- package/dist/dispatcher.mjs +9 -9
- package/dist/{doctor-DD2HRC43.mjs → doctor-A3YU5YOX.mjs} +2 -2
- package/dist/{explain-H46SCM2M.mjs → explain-WZQTCB3C.mjs} +1 -1
- package/dist/{introspect-YKAWUZ6D.mjs → introspect-2GJIQ6CP.mjs} +2 -2
- package/dist/{list-6FF2UPH2.mjs → list-F3LBI7HF.mjs} +1 -1
- package/dist/{update-STXI3CS4.mjs → update-PINBW3NG.mjs} +1 -1
- package/package.json +2 -2
- package/templates/storefront-auto/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-auto/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-auto/app/account/orders/page.tsx +84 -15
- package/templates/storefront-auto/app/account/page.tsx +39 -16
- package/templates/storefront-auto/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-auto/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-auto/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-auto/bun.lock +8 -2
- package/templates/storefront-auto/lib/brand.ts +1 -1
- package/templates/storefront-auto/lib/cimplify-server.ts +54 -0
- package/templates/storefront-auto/package.json +1 -1
- package/templates/storefront-bakery/AGENTS.md +4 -4
- package/templates/storefront-bakery/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-bakery/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-bakery/app/account/orders/page.tsx +84 -15
- package/templates/storefront-bakery/app/account/page.tsx +39 -16
- package/templates/storefront-bakery/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-bakery/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-bakery/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-bakery/bun.lock +8 -2
- package/templates/storefront-bakery/lib/brand.ts +1 -1
- package/templates/storefront-bakery/lib/cimplify-server.ts +54 -0
- package/templates/storefront-bakery/package.json +1 -1
- package/templates/storefront-fashion/AGENTS.md +4 -4
- package/templates/storefront-fashion/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-fashion/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-fashion/app/account/orders/page.tsx +84 -15
- package/templates/storefront-fashion/app/account/page.tsx +39 -16
- package/templates/storefront-fashion/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-fashion/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-fashion/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-fashion/bun.lock +8 -2
- package/templates/storefront-fashion/lib/brand.ts +1 -1
- package/templates/storefront-fashion/lib/cimplify-server.ts +54 -0
- package/templates/storefront-fashion/package.json +1 -1
- package/templates/storefront-grocery/AGENTS.md +4 -4
- package/templates/storefront-grocery/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-grocery/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-grocery/app/account/orders/page.tsx +84 -15
- package/templates/storefront-grocery/app/account/page.tsx +39 -16
- package/templates/storefront-grocery/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-grocery/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-grocery/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-grocery/bun.lock +8 -2
- package/templates/storefront-grocery/lib/brand.ts +1 -1
- package/templates/storefront-grocery/lib/cimplify-server.ts +54 -0
- package/templates/storefront-grocery/package.json +1 -1
- package/templates/storefront-pharmacy/AGENTS.md +4 -4
- package/templates/storefront-pharmacy/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-pharmacy/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-pharmacy/app/account/orders/page.tsx +84 -15
- package/templates/storefront-pharmacy/app/account/page.tsx +39 -16
- package/templates/storefront-pharmacy/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-pharmacy/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-pharmacy/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-pharmacy/bun.lock +8 -2
- package/templates/storefront-pharmacy/lib/brand.ts +1 -1
- package/templates/storefront-pharmacy/lib/cimplify-server.ts +54 -0
- package/templates/storefront-pharmacy/package.json +1 -1
- package/templates/storefront-restaurant/AGENTS.md +4 -4
- package/templates/storefront-restaurant/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-restaurant/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-restaurant/app/account/orders/page.tsx +84 -15
- package/templates/storefront-restaurant/app/account/page.tsx +39 -16
- package/templates/storefront-restaurant/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-restaurant/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-restaurant/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-restaurant/bun.lock +8 -2
- package/templates/storefront-restaurant/lib/brand.ts +1 -1
- package/templates/storefront-restaurant/lib/cimplify-server.ts +54 -0
- package/templates/storefront-restaurant/package.json +1 -1
- package/templates/storefront-retail/AGENTS.md +4 -4
- package/templates/storefront-retail/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-retail/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-retail/app/account/orders/page.tsx +84 -15
- package/templates/storefront-retail/app/account/page.tsx +39 -16
- package/templates/storefront-retail/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-retail/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-retail/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-retail/bun.lock +8 -2
- package/templates/storefront-retail/lib/brand.ts +1 -1
- package/templates/storefront-retail/lib/cimplify-server.ts +54 -0
- package/templates/storefront-retail/package.json +1 -1
- package/templates/storefront-services/AGENTS.md +4 -4
- package/templates/storefront-services/app/account/addresses/actions.ts +56 -0
- package/templates/storefront-services/app/account/addresses/page.tsx +178 -15
- package/templates/storefront-services/app/account/orders/page.tsx +84 -15
- package/templates/storefront-services/app/account/page.tsx +39 -16
- package/templates/storefront-services/app/account/wallets/actions.ts +52 -0
- package/templates/storefront-services/app/account/wallets/page.tsx +185 -0
- package/templates/storefront-services/app/sitemap-page/page.tsx +1 -1
- package/templates/storefront-services/bun.lock +8 -2
- package/templates/storefront-services/lib/brand.ts +1 -1
- package/templates/storefront-services/lib/cimplify-server.ts +54 -0
- package/templates/storefront-services/package.json +1 -1
- package/dist/chunk-YFLBC6BL.mjs +0 -6151
- package/templates/storefront-auto/app/account/settings/page.tsx +0 -21
- package/templates/storefront-auto/components/account-iframe.tsx +0 -19
- package/templates/storefront-bakery/app/account/settings/page.tsx +0 -21
- package/templates/storefront-bakery/components/account-iframe.tsx +0 -19
- package/templates/storefront-fashion/app/account/settings/page.tsx +0 -21
- package/templates/storefront-fashion/components/account-iframe.tsx +0 -19
- package/templates/storefront-grocery/app/account/settings/page.tsx +0 -21
- package/templates/storefront-grocery/components/account-iframe.tsx +0 -19
- package/templates/storefront-pharmacy/app/account/settings/page.tsx +0 -21
- package/templates/storefront-pharmacy/components/account-iframe.tsx +0 -19
- package/templates/storefront-restaurant/app/account/settings/page.tsx +0 -21
- package/templates/storefront-restaurant/components/account-iframe.tsx +0 -19
- package/templates/storefront-retail/app/account/settings/page.tsx +0 -21
- package/templates/storefront-retail/components/account-iframe.tsx +0 -19
- package/templates/storefront-services/app/account/settings/page.tsx +0 -21
- package/templates/storefront-services/components/account-iframe.tsx +0 -19
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccountContent,
|
|
3
|
+
AccountHero,
|
|
4
|
+
AccountNav,
|
|
5
|
+
AccountShell,
|
|
6
|
+
AccountSidebar,
|
|
7
|
+
AccountSignedOutPrompt,
|
|
8
|
+
Button,
|
|
9
|
+
EmptyState,
|
|
10
|
+
Section,
|
|
11
|
+
} from "@cimplify/sdk/react";
|
|
12
|
+
import type { CustomerMobileMoney } from "@cimplify/sdk";
|
|
13
|
+
import { MOBILE_MONEY_PROVIDER } from "@cimplify/sdk";
|
|
14
|
+
import { brand } from "@/lib/brand";
|
|
15
|
+
import { ACCOUNT_NAV, apiFetch, serverAccessToken, serverSession } from "@/lib/cimplify-server";
|
|
16
|
+
import {
|
|
17
|
+
createWalletAction,
|
|
18
|
+
deleteWalletAction,
|
|
19
|
+
setDefaultWalletAction,
|
|
20
|
+
} from "./actions";
|
|
21
|
+
|
|
22
|
+
const CLIENT_ID = process.env.NEXT_PUBLIC_CIMPLIFY_CLIENT_ID ?? "";
|
|
23
|
+
const REDIRECT_URI = process.env.CIMPLIFY_REDIRECT_URI ?? "";
|
|
24
|
+
|
|
25
|
+
const PROVIDER_LABEL: Record<string, string> = {
|
|
26
|
+
[MOBILE_MONEY_PROVIDER.MTN]: "MTN",
|
|
27
|
+
[MOBILE_MONEY_PROVIDER.VODAFONE]: "Vodafone",
|
|
28
|
+
[MOBILE_MONEY_PROVIDER.TELECEL]: "Telecel",
|
|
29
|
+
[MOBILE_MONEY_PROVIDER.AIRTEL]: "Airtel",
|
|
30
|
+
[MOBILE_MONEY_PROVIDER.AIRTELTIGO]: "AirtelTigo",
|
|
31
|
+
[MOBILE_MONEY_PROVIDER.MPESA]: "M-Pesa",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function maskedPhone(phone: string): string {
|
|
35
|
+
const digits = phone.replace(/\D/g, "");
|
|
36
|
+
if (digits.length < 4) return phone;
|
|
37
|
+
return `${phone.slice(0, 4)} ••• ${digits.slice(-3)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function WalletCard({ wallet }: { wallet: CustomerMobileMoney }) {
|
|
41
|
+
const isDefault = wallet.is_default === true;
|
|
42
|
+
const providerLabel = PROVIDER_LABEL[wallet.provider] ?? wallet.provider;
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex items-start justify-between gap-4 rounded-[14px] border border-border bg-card p-5">
|
|
45
|
+
<div className="min-w-0">
|
|
46
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
47
|
+
<span className="text-[14px] font-medium text-foreground">
|
|
48
|
+
{wallet.label || providerLabel}
|
|
49
|
+
</span>
|
|
50
|
+
{isDefault && (
|
|
51
|
+
<span className="text-[10px] font-medium tracking-[0.08em] uppercase text-foreground/60 bg-foreground/5 px-2 py-0.5 rounded-full">
|
|
52
|
+
Default
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
<p className="text-[13px] text-foreground/70 leading-relaxed">
|
|
57
|
+
{providerLabel} · {maskedPhone(wallet.phone_number)}
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
61
|
+
{!isDefault && (
|
|
62
|
+
<form action={setDefaultWalletAction}>
|
|
63
|
+
<input type="hidden" name="mobile_money_id" value={wallet.id} />
|
|
64
|
+
<button
|
|
65
|
+
type="submit"
|
|
66
|
+
className="text-[12px] font-medium text-foreground/70 hover:text-foreground px-2 py-1"
|
|
67
|
+
>
|
|
68
|
+
Make default
|
|
69
|
+
</button>
|
|
70
|
+
</form>
|
|
71
|
+
)}
|
|
72
|
+
<form action={deleteWalletAction}>
|
|
73
|
+
<input type="hidden" name="mobile_money_id" value={wallet.id} />
|
|
74
|
+
<button
|
|
75
|
+
type="submit"
|
|
76
|
+
className="text-[12px] font-medium text-destructive/80 hover:text-destructive px-2 py-1"
|
|
77
|
+
>
|
|
78
|
+
Remove
|
|
79
|
+
</button>
|
|
80
|
+
</form>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AddWalletForm() {
|
|
87
|
+
return (
|
|
88
|
+
<form
|
|
89
|
+
action={createWalletAction}
|
|
90
|
+
className="rounded-[14px] border border-dashed border-border bg-card p-5 space-y-3"
|
|
91
|
+
>
|
|
92
|
+
<h3 className="text-[13px] font-semibold tracking-[0.02em] text-foreground">
|
|
93
|
+
Add a mobile money wallet
|
|
94
|
+
</h3>
|
|
95
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
96
|
+
<select
|
|
97
|
+
name="provider"
|
|
98
|
+
required
|
|
99
|
+
defaultValue=""
|
|
100
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
101
|
+
>
|
|
102
|
+
<option value="" disabled>
|
|
103
|
+
Provider
|
|
104
|
+
</option>
|
|
105
|
+
{Object.entries(PROVIDER_LABEL).map(([value, label]) => (
|
|
106
|
+
<option key={value} value={value}>
|
|
107
|
+
{label}
|
|
108
|
+
</option>
|
|
109
|
+
))}
|
|
110
|
+
</select>
|
|
111
|
+
<input
|
|
112
|
+
name="phone_number"
|
|
113
|
+
required
|
|
114
|
+
type="tel"
|
|
115
|
+
inputMode="tel"
|
|
116
|
+
autoComplete="tel"
|
|
117
|
+
placeholder="+233 24 400 0000"
|
|
118
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<input
|
|
122
|
+
name="label"
|
|
123
|
+
placeholder="Label (Personal, Business — optional)"
|
|
124
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px] w-full"
|
|
125
|
+
/>
|
|
126
|
+
<div className="flex justify-end">
|
|
127
|
+
<Button type="submit" variant="primary">
|
|
128
|
+
Save wallet
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default async function WalletsPage() {
|
|
136
|
+
const session = await serverSession();
|
|
137
|
+
if (!session) {
|
|
138
|
+
return (
|
|
139
|
+
<AccountSignedOutPrompt
|
|
140
|
+
clientId={CLIENT_ID}
|
|
141
|
+
redirectUri={REDIRECT_URI}
|
|
142
|
+
eyebrow={brand.account.loginEyebrow}
|
|
143
|
+
title={brand.account.loginTitle}
|
|
144
|
+
description={brand.account.loginSubtitle}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const bearer = await serverAccessToken();
|
|
150
|
+
const wallets =
|
|
151
|
+
(await apiFetch<CustomerMobileMoney[]>("/v1/link/mobile-money", { bearer })) ?? [];
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<AccountShell
|
|
155
|
+
sidebar={
|
|
156
|
+
<AccountSidebar>
|
|
157
|
+
<AccountNav items={ACCOUNT_NAV} activeHref="/account/wallets" />
|
|
158
|
+
</AccountSidebar>
|
|
159
|
+
}
|
|
160
|
+
>
|
|
161
|
+
<AccountHero
|
|
162
|
+
eyebrow="Wallets"
|
|
163
|
+
title="How would you like to pay?"
|
|
164
|
+
subtitle="Saved wallets work across every Cimplify shop you use."
|
|
165
|
+
/>
|
|
166
|
+
<AccountContent>
|
|
167
|
+
<Section title="Your wallets" meta={`${wallets.length} saved`}>
|
|
168
|
+
<div className="space-y-3">
|
|
169
|
+
{wallets.length === 0 ? (
|
|
170
|
+
<EmptyState
|
|
171
|
+
title="No wallets yet"
|
|
172
|
+
description="Add a mobile money wallet to skip the number entry next checkout."
|
|
173
|
+
/>
|
|
174
|
+
) : (
|
|
175
|
+
wallets.map((wallet) => <WalletCard key={wallet.id} wallet={wallet} />)
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</Section>
|
|
179
|
+
<Section title="Add another">
|
|
180
|
+
<AddWalletForm />
|
|
181
|
+
</Section>
|
|
182
|
+
</AccountContent>
|
|
183
|
+
</AccountShell>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -55,7 +55,7 @@ const STATIC_LINKS: { title: string; links: { href: string; label: string }[] }[
|
|
|
55
55
|
{ href: "/account", label: "Account" },
|
|
56
56
|
{ href: "/account/orders", label: "Orders" },
|
|
57
57
|
{ href: "/account/addresses", label: "Addresses" },
|
|
58
|
-
{ href: "/account/
|
|
58
|
+
{ href: "/account/wallets", label: "Wallets" },
|
|
59
59
|
{ href: "/login", label: "Sign in" },
|
|
60
60
|
{ href: "/signup", label: "Create account" },
|
|
61
61
|
{ href: "/track-order", label: "Track an order" },
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "__STOREFRONT_NAME__",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@cimplify/sdk": "^0.
|
|
8
|
+
"@cimplify/sdk": "^0.64.1",
|
|
9
9
|
"next": "^16.2.6",
|
|
10
10
|
"react": "^19.0.0",
|
|
11
11
|
"react-dom": "^19.0.0",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
"@base-ui/utils": ["@base-ui/utils@0.2.9", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.11", "reselect": "^5.1.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-x/PDDCYzoqPpjrdyb3VcyylTI2IjUXEtYDGi5foh7KsnmNJIIaVwA2GLgDH1dps1GgXiJbA60hM+AyuTfQzIvw=="],
|
|
33
33
|
|
|
34
|
-
"@cimplify/sdk": ["@cimplify/sdk@0.
|
|
34
|
+
"@cimplify/sdk": ["@cimplify/sdk@0.64.1", "", { "dependencies": { "@base-ui/react": "^1.4.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", "jose": "^6.2.3", "libphonenumber-js": "1.12.41", "react-day-picker": "^9.14.0", "tailwind-merge": "^3.5.0", "xss": "^1.0.15", "zod": "^4.4.3" }, "peerDependencies": { "@paystack/inline-js": "^2.22.8", "msw": ">=2.0.0", "react": ">=17.0.0", "vitest": ">=2.0.0" }, "optionalPeers": ["@paystack/inline-js", "msw", "react", "vitest"], "bin": { "cimplify-mock": "dist/mock/cli.mjs" } }, "sha512-qY3ngAxAyeRwHR5jJZ7eeeNKgvayZlVUuNglp779bJiwtwL3cZ8Ux4L+k3SgRzgAxtKz9YU2mmVBZxEu14frUw=="],
|
|
35
35
|
|
|
36
36
|
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
|
37
37
|
|
|
@@ -313,6 +313,8 @@
|
|
|
313
313
|
|
|
314
314
|
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
|
315
315
|
|
|
316
|
+
"cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="],
|
|
317
|
+
|
|
316
318
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
317
319
|
|
|
318
320
|
"date-fns": ["date-fns@4.2.1", "", {}, "sha512-37RhSdxaG1suen6VDCza6rNrQfooyQh57HFVPwQGEq2QWliVLzPQZ8Oa017weOu+HZCnzI7N3Pf/wyoBKfEqrA=="],
|
|
@@ -349,6 +351,8 @@
|
|
|
349
351
|
|
|
350
352
|
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
|
351
353
|
|
|
354
|
+
"jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
|
|
355
|
+
|
|
352
356
|
"libphonenumber-js": ["libphonenumber-js@1.12.41", "", {}, "sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA=="],
|
|
353
357
|
|
|
354
358
|
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
|
@@ -467,6 +471,8 @@
|
|
|
467
471
|
|
|
468
472
|
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
|
469
473
|
|
|
474
|
+
"xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
|
|
475
|
+
|
|
470
476
|
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
|
471
477
|
|
|
472
478
|
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
|
@@ -636,7 +636,7 @@ export const brand: Brand = {
|
|
|
636
636
|
{ label: "Sign in", href: "/login" },
|
|
637
637
|
{ label: "Create account", href: "/signup" },
|
|
638
638
|
{ label: "Your orders", href: "/account/orders" },
|
|
639
|
-
{ label: "
|
|
639
|
+
{ label: "Wallets", href: "/account/wallets" },
|
|
640
640
|
],
|
|
641
641
|
},
|
|
642
642
|
{
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cookies } from "next/headers";
|
|
2
|
+
import {
|
|
3
|
+
getAccessTokenFromCookieHeader,
|
|
4
|
+
getSessionFromCookieHeader,
|
|
5
|
+
} from "@cimplify/sdk/server";
|
|
6
|
+
import type { NavItem } from "@cimplify/sdk/react";
|
|
7
|
+
|
|
8
|
+
export const ACCOUNT_NAV: NavItem[] = [
|
|
9
|
+
{ label: "Dashboard", href: "/account" },
|
|
10
|
+
{ label: "Orders", href: "/account/orders" },
|
|
11
|
+
{ label: "Addresses", href: "/account/addresses" },
|
|
12
|
+
{ label: "Wallets", href: "/account/wallets" },
|
|
13
|
+
{ label: "Cimplify identity", href: "https://link.cimplify.io", external: true },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const CLIENT_ID = process.env.CIMPLIFY_CLIENT_ID ?? "";
|
|
17
|
+
const ISSUER = (process.env.CIMPLIFY_ISSUER ?? "https://api.cimplify.io").replace(/\/$/, "");
|
|
18
|
+
|
|
19
|
+
const OIDC_CONFIG = { clientId: CLIENT_ID, issuer: ISSUER };
|
|
20
|
+
|
|
21
|
+
async function cookieHeader(): Promise<string> {
|
|
22
|
+
const store = await cookies();
|
|
23
|
+
return store.getAll().map((c) => `${c.name}=${c.value}`).join("; ");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function serverSession() {
|
|
27
|
+
return getSessionFromCookieHeader(OIDC_CONFIG, await cookieHeader());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function serverAccessToken(): Promise<string | null> {
|
|
31
|
+
return getAccessTokenFromCookieHeader(OIDC_CONFIG, await cookieHeader());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Envelope<T> {
|
|
35
|
+
data: T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function apiFetch<T>(
|
|
39
|
+
path: string,
|
|
40
|
+
init: RequestInit & { bearer: string | null },
|
|
41
|
+
): Promise<T | null> {
|
|
42
|
+
if (!init.bearer) return null;
|
|
43
|
+
const { bearer, ...rest } = init;
|
|
44
|
+
const headers = new Headers(rest.headers);
|
|
45
|
+
headers.set("Authorization", `Bearer ${bearer}`);
|
|
46
|
+
headers.set("Accept", "application/json");
|
|
47
|
+
if (rest.body && !headers.has("Content-Type")) {
|
|
48
|
+
headers.set("Content-Type", "application/json");
|
|
49
|
+
}
|
|
50
|
+
const res = await fetch(`${ISSUER}${path}`, { ...rest, headers, cache: "no-store" });
|
|
51
|
+
if (!res.ok) return null;
|
|
52
|
+
const envelope = (await res.json()) as Envelope<T>;
|
|
53
|
+
return envelope.data;
|
|
54
|
+
}
|
|
@@ -32,10 +32,10 @@ app/
|
|
|
32
32
|
|
|
33
33
|
cart/page.tsx, checkout/page.tsx, orders/[id]/page.tsx
|
|
34
34
|
|
|
35
|
-
account/page.tsx <
|
|
36
|
-
account/orders/page.tsx <
|
|
37
|
-
account/addresses/page.tsx
|
|
38
|
-
account/
|
|
35
|
+
account/page.tsx <AccountDashboardPage /> (native, Server Component)
|
|
36
|
+
account/orders/page.tsx <AccountOrdersPage /> (native)
|
|
37
|
+
account/addresses/page.tsx Native list + Server Actions for CRUD
|
|
38
|
+
account/wallets/page.tsx Native list + Server Actions for CRUD
|
|
39
39
|
login/page.tsx, signup/page.tsx redirects → /account
|
|
40
40
|
|
|
41
41
|
contact/page.tsx, track-order/page.tsx (find a booking)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { revalidatePath } from "next/cache";
|
|
4
|
+
import { apiFetch, serverAccessToken } from "@/lib/cimplify-server";
|
|
5
|
+
import type { CustomerAddress } from "@cimplify/sdk";
|
|
6
|
+
|
|
7
|
+
const ACCOUNT_ADDRESSES = "/account/addresses";
|
|
8
|
+
|
|
9
|
+
function required(form: FormData, key: string): string {
|
|
10
|
+
const value = String(form.get(key) ?? "").trim();
|
|
11
|
+
if (!value) throw new Error(`Missing required field: ${key}`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function optional(form: FormData, key: string): string | undefined {
|
|
16
|
+
const value = String(form.get(key) ?? "").trim();
|
|
17
|
+
return value || undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function createAddressAction(form: FormData): Promise<void> {
|
|
21
|
+
const bearer = await serverAccessToken();
|
|
22
|
+
await apiFetch<CustomerAddress>("/v1/link/addresses", {
|
|
23
|
+
bearer,
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
label: optional(form, "label"),
|
|
27
|
+
street_address: required(form, "street_address"),
|
|
28
|
+
apartment: optional(form, "apartment"),
|
|
29
|
+
city: required(form, "city"),
|
|
30
|
+
region: optional(form, "region"),
|
|
31
|
+
postal_code: optional(form, "postal_code"),
|
|
32
|
+
country: optional(form, "country"),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
revalidatePath(ACCOUNT_ADDRESSES);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function deleteAddressAction(form: FormData): Promise<void> {
|
|
39
|
+
const bearer = await serverAccessToken();
|
|
40
|
+
const id = required(form, "address_id");
|
|
41
|
+
await apiFetch<unknown>(`/v1/link/addresses/${encodeURIComponent(id)}`, {
|
|
42
|
+
bearer,
|
|
43
|
+
method: "DELETE",
|
|
44
|
+
});
|
|
45
|
+
revalidatePath(ACCOUNT_ADDRESSES);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function setDefaultAddressAction(form: FormData): Promise<void> {
|
|
49
|
+
const bearer = await serverAccessToken();
|
|
50
|
+
const id = required(form, "address_id");
|
|
51
|
+
await apiFetch<unknown>(`/v1/link/addresses/${encodeURIComponent(id)}/default`, {
|
|
52
|
+
bearer,
|
|
53
|
+
method: "POST",
|
|
54
|
+
});
|
|
55
|
+
revalidatePath(ACCOUNT_ADDRESSES);
|
|
56
|
+
}
|
|
@@ -1,21 +1,184 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
AccountContent,
|
|
3
|
+
AccountHero,
|
|
4
|
+
AccountNav,
|
|
5
|
+
AccountShell,
|
|
6
|
+
AccountSidebar,
|
|
7
|
+
AccountSignedOutPrompt,
|
|
8
|
+
Button,
|
|
9
|
+
EmptyState,
|
|
10
|
+
Section,
|
|
11
|
+
} from "@cimplify/sdk/react";
|
|
12
|
+
import type { CustomerAddress } from "@cimplify/sdk";
|
|
3
13
|
import { brand } from "@/lib/brand";
|
|
14
|
+
import { ACCOUNT_NAV, apiFetch, serverAccessToken, serverSession } from "@/lib/cimplify-server";
|
|
15
|
+
import {
|
|
16
|
+
createAddressAction,
|
|
17
|
+
deleteAddressAction,
|
|
18
|
+
setDefaultAddressAction,
|
|
19
|
+
} from "./actions";
|
|
4
20
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
const CLIENT_ID = process.env.NEXT_PUBLIC_CIMPLIFY_CLIENT_ID ?? "";
|
|
22
|
+
const REDIRECT_URI = process.env.CIMPLIFY_REDIRECT_URI ?? "";
|
|
23
|
+
|
|
24
|
+
function singleLine(address: CustomerAddress): string {
|
|
25
|
+
return [
|
|
26
|
+
address.street_address,
|
|
27
|
+
address.apartment,
|
|
28
|
+
address.city,
|
|
29
|
+
address.region,
|
|
30
|
+
address.postal_code,
|
|
31
|
+
address.country,
|
|
32
|
+
]
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.join(", ");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AddressCard({ address }: { address: CustomerAddress }) {
|
|
38
|
+
const isDefault = address.is_default === true;
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex items-start justify-between gap-4 rounded-[14px] border border-border bg-card p-5">
|
|
41
|
+
<div className="min-w-0">
|
|
42
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
43
|
+
<span className="text-[14px] font-medium text-foreground">{address.label || "Address"}</span>
|
|
44
|
+
{isDefault && (
|
|
45
|
+
<span className="text-[10px] font-medium tracking-[0.08em] uppercase text-foreground/60 bg-foreground/5 px-2 py-0.5 rounded-full">
|
|
46
|
+
Default
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
<p className="text-[13px] text-foreground/70 leading-relaxed truncate">
|
|
51
|
+
{singleLine(address)}
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
55
|
+
{!isDefault && (
|
|
56
|
+
<form action={setDefaultAddressAction}>
|
|
57
|
+
<input type="hidden" name="address_id" value={address.id} />
|
|
58
|
+
<button
|
|
59
|
+
type="submit"
|
|
60
|
+
className="text-[12px] font-medium text-foreground/70 hover:text-foreground px-2 py-1"
|
|
61
|
+
>
|
|
62
|
+
Make default
|
|
63
|
+
</button>
|
|
64
|
+
</form>
|
|
65
|
+
)}
|
|
66
|
+
<form action={deleteAddressAction}>
|
|
67
|
+
<input type="hidden" name="address_id" value={address.id} />
|
|
68
|
+
<button
|
|
69
|
+
type="submit"
|
|
70
|
+
className="text-[12px] font-medium text-destructive/80 hover:text-destructive px-2 py-1"
|
|
71
|
+
>
|
|
72
|
+
Remove
|
|
73
|
+
</button>
|
|
74
|
+
</form>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function AddAddressForm() {
|
|
81
|
+
return (
|
|
82
|
+
<form
|
|
83
|
+
action={createAddressAction}
|
|
84
|
+
className="rounded-[14px] border border-dashed border-border bg-card p-5 space-y-3"
|
|
85
|
+
>
|
|
86
|
+
<h3 className="text-[13px] font-semibold tracking-[0.02em] text-foreground">Add an address</h3>
|
|
87
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
88
|
+
<input
|
|
89
|
+
name="label"
|
|
90
|
+
placeholder="Label (Home, Work)"
|
|
91
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
92
|
+
/>
|
|
93
|
+
<input
|
|
94
|
+
name="street_address"
|
|
95
|
+
required
|
|
96
|
+
placeholder="Street address"
|
|
97
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
98
|
+
/>
|
|
99
|
+
<input
|
|
100
|
+
name="apartment"
|
|
101
|
+
placeholder="Apartment / unit (optional)"
|
|
102
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
103
|
+
/>
|
|
104
|
+
<input
|
|
105
|
+
name="city"
|
|
106
|
+
required
|
|
107
|
+
placeholder="City"
|
|
108
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
109
|
+
/>
|
|
110
|
+
<input
|
|
111
|
+
name="region"
|
|
112
|
+
placeholder="Region / state"
|
|
113
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
114
|
+
/>
|
|
115
|
+
<input
|
|
116
|
+
name="postal_code"
|
|
117
|
+
placeholder="Postal code"
|
|
118
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px]"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<input
|
|
122
|
+
name="country"
|
|
123
|
+
placeholder="Country"
|
|
124
|
+
className="rounded-[10px] border border-border bg-background px-3 h-10 text-[13px] w-full"
|
|
125
|
+
/>
|
|
126
|
+
<div className="flex justify-end">
|
|
127
|
+
<Button type="submit" variant="primary">
|
|
128
|
+
Save address
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default async function AddressesPage() {
|
|
136
|
+
const session = await serverSession();
|
|
137
|
+
if (!session) {
|
|
138
|
+
return (
|
|
139
|
+
<AccountSignedOutPrompt
|
|
140
|
+
clientId={CLIENT_ID}
|
|
141
|
+
redirectUri={REDIRECT_URI}
|
|
142
|
+
eyebrow={brand.account.loginEyebrow}
|
|
143
|
+
title={brand.account.loginTitle}
|
|
144
|
+
description={brand.account.loginSubtitle}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const bearer = await serverAccessToken();
|
|
150
|
+
const addresses = (await apiFetch<CustomerAddress[]>("/v1/link/addresses", { bearer })) ?? [];
|
|
8
151
|
|
|
9
|
-
export default function AddressesPage() {
|
|
10
152
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
153
|
+
<AccountShell
|
|
154
|
+
sidebar={
|
|
155
|
+
<AccountSidebar>
|
|
156
|
+
<AccountNav items={ACCOUNT_NAV} activeHref="/account/addresses" />
|
|
157
|
+
</AccountSidebar>
|
|
158
|
+
}
|
|
159
|
+
>
|
|
160
|
+
<AccountHero
|
|
161
|
+
eyebrow="Saved addresses"
|
|
162
|
+
title="Where should we deliver?"
|
|
163
|
+
subtitle="Saved addresses sync across every Cimplify shop you use."
|
|
164
|
+
/>
|
|
165
|
+
<AccountContent>
|
|
166
|
+
<Section title="Your addresses" meta={`${addresses.length} saved`}>
|
|
167
|
+
<div className="space-y-3">
|
|
168
|
+
{addresses.length === 0 ? (
|
|
169
|
+
<EmptyState
|
|
170
|
+
title="No addresses yet"
|
|
171
|
+
description="Add a delivery address to check out faster next time."
|
|
172
|
+
/>
|
|
173
|
+
) : (
|
|
174
|
+
addresses.map((address) => <AddressCard key={address.id} address={address} />)
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
</Section>
|
|
178
|
+
<Section title="Add another">
|
|
179
|
+
<AddAddressForm />
|
|
180
|
+
</Section>
|
|
181
|
+
</AccountContent>
|
|
182
|
+
</AccountShell>
|
|
20
183
|
);
|
|
21
184
|
}
|