@pradip1995/segment-login-template 0.4.1 → 0.4.3
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/package.json +1 -1
- package/src/account-addresses.tsx +14 -13
- package/src/account-guest-orders.tsx +47 -12
- package/src/account-orders.tsx +90 -38
- package/src/account-overview.tsx +96 -74
- package/src/account-payment-methods.tsx +155 -63
- package/src/account-profile.tsx +12 -11
- package/src/account-theme.ts +79 -0
- package/src/forgot-password-form.tsx +44 -26
- package/src/google-auth-section.tsx +8 -10
- package/src/guest-order-modal.tsx +224 -0
- package/src/login-brand-panel.tsx +96 -0
- package/src/login-form.tsx +207 -146
- package/src/payment-methods-actions.ts +214 -0
- package/src/register-form.tsx +77 -57
- package/src/segment.tsx +144 -50
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
deleteCustomerAddress,
|
|
8
8
|
updateCustomerAddress,
|
|
9
9
|
} from "@pradip1995/commerce-core/data/customer"
|
|
10
|
+
import { acct } from "./account-theme"
|
|
10
11
|
|
|
11
12
|
export default function AccountAddresses({
|
|
12
13
|
customer,
|
|
@@ -23,23 +24,23 @@ export default function AccountAddresses({
|
|
|
23
24
|
<div className="space-y-8">
|
|
24
25
|
<div className="flex flex-wrap items-end justify-between gap-4">
|
|
25
26
|
<div>
|
|
26
|
-
<h2 className=
|
|
27
|
-
<p className=
|
|
27
|
+
<h2 className={acct.title}>Saved addresses</h2>
|
|
28
|
+
<p className={`text-sm ${acct.muted}`}>Manage shipping and billing addresses.</p>
|
|
28
29
|
</div>
|
|
29
|
-
<button type="button" onClick={() => setShowAdd(true)} className=
|
|
30
|
+
<button type="button" onClick={() => setShowAdd(true)} className={acct.btnPrimary}>
|
|
30
31
|
Add address
|
|
31
32
|
</button>
|
|
32
33
|
</div>
|
|
33
34
|
|
|
34
35
|
{addresses.length === 0 && !showAdd && (
|
|
35
|
-
<div className=
|
|
36
|
+
<div className={`${acct.cardMuted} p-8 text-center text-sm ${acct.muted}`}>
|
|
36
37
|
No saved addresses yet.
|
|
37
38
|
</div>
|
|
38
39
|
)}
|
|
39
40
|
|
|
40
41
|
<ul className="space-y-4">
|
|
41
42
|
{addresses.map((address) => (
|
|
42
|
-
<li key={address.id} className=
|
|
43
|
+
<li key={address.id} className={`${acct.card} p-5`}>
|
|
43
44
|
{editingId === address.id ? (
|
|
44
45
|
<AddressForm
|
|
45
46
|
countryCode={countryCode}
|
|
@@ -75,7 +76,7 @@ export default function AccountAddresses({
|
|
|
75
76
|
<button
|
|
76
77
|
type="button"
|
|
77
78
|
onClick={() => setEditingId(address.id!)}
|
|
78
|
-
className=
|
|
79
|
+
className={`${acct.btnOutline} !py-2 !px-3`}
|
|
79
80
|
>
|
|
80
81
|
Edit
|
|
81
82
|
</button>
|
|
@@ -88,7 +89,7 @@ export default function AccountAddresses({
|
|
|
88
89
|
</ul>
|
|
89
90
|
|
|
90
91
|
{showAdd && (
|
|
91
|
-
<div className=
|
|
92
|
+
<div className={`${acct.card} p-5`}>
|
|
92
93
|
<h3 className="text-sm font-semibold text-heading mb-4">New address</h3>
|
|
93
94
|
<AddressForm
|
|
94
95
|
countryCode={countryCode}
|
|
@@ -146,13 +147,13 @@ function AddressForm({
|
|
|
146
147
|
/>
|
|
147
148
|
<Field label="Phone" name="phone" type="tel" defaultValue={address?.phone || ""} />
|
|
148
149
|
{state?.error && (
|
|
149
|
-
<p className="sm:col-span-2 text-sm text-
|
|
150
|
+
<p className="sm:col-span-2 text-sm text-red-600">{String(state.error)}</p>
|
|
150
151
|
)}
|
|
151
152
|
<div className="sm:col-span-2 flex gap-3">
|
|
152
|
-
<button type="submit" disabled={pending} className=
|
|
153
|
+
<button type="submit" disabled={pending} className={acct.btnPrimary}>
|
|
153
154
|
{pending ? "Saving…" : "Save address"}
|
|
154
155
|
</button>
|
|
155
|
-
<button type="button" onClick={onCancel} className=
|
|
156
|
+
<button type="button" onClick={onCancel} className={acct.btnOutline}>
|
|
156
157
|
Cancel
|
|
157
158
|
</button>
|
|
158
159
|
</div>
|
|
@@ -179,7 +180,7 @@ function DeleteAddressButton({ addressId }: { addressId: string }) {
|
|
|
179
180
|
type="button"
|
|
180
181
|
onClick={handleDelete}
|
|
181
182
|
disabled={pending}
|
|
182
|
-
className="text-xs text-
|
|
183
|
+
className="text-xs text-red-600 hover:underline disabled:opacity-60"
|
|
183
184
|
>
|
|
184
185
|
{pending ? "Deleting…" : "Delete"}
|
|
185
186
|
</button>
|
|
@@ -201,7 +202,7 @@ function Field({
|
|
|
201
202
|
}) {
|
|
202
203
|
return (
|
|
203
204
|
<label className={`block ${className}`}>
|
|
204
|
-
<span className="text-xs font-semibold uppercase tracking-
|
|
205
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-muted mb-1.5 block">
|
|
205
206
|
{label}
|
|
206
207
|
</span>
|
|
207
208
|
<input
|
|
@@ -209,7 +210,7 @@ function Field({
|
|
|
209
210
|
type={type}
|
|
210
211
|
defaultValue={defaultValue}
|
|
211
212
|
required={name !== "address_2"}
|
|
212
|
-
className="w-full border border-cart-border rounded px-3 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
|
|
213
|
+
className="w-full border border-cart-border rounded-none px-3 py-2.5 text-sm bg-surface text-heading focus:outline-none focus:border-brand-accent"
|
|
213
214
|
/>
|
|
214
215
|
</label>
|
|
215
216
|
)
|
|
@@ -4,45 +4,80 @@ import type { HttpTypes } from "@medusajs/types"
|
|
|
4
4
|
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
5
5
|
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
6
6
|
import { logoutGuest } from "@pradip1995/commerce-core/data/guest"
|
|
7
|
+
import { auth } from "./account-theme"
|
|
7
8
|
|
|
8
9
|
export default function AccountGuestOrders({
|
|
9
10
|
orders,
|
|
10
11
|
countryCode,
|
|
11
12
|
}: {
|
|
12
|
-
orders
|
|
13
|
+
orders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
|
|
13
14
|
countryCode: string
|
|
14
15
|
}) {
|
|
16
|
+
const safeOrders: HttpTypes.StoreOrder[] = Array.isArray(orders)
|
|
17
|
+
? orders
|
|
18
|
+
: Array.isArray((orders as { orders?: HttpTypes.StoreOrder[] })?.orders)
|
|
19
|
+
? ((orders as { orders: HttpTypes.StoreOrder[] }).orders)
|
|
20
|
+
: []
|
|
21
|
+
|
|
15
22
|
async function handleSignOut() {
|
|
16
23
|
await logoutGuest()
|
|
17
24
|
window.location.href = `/${countryCode}/account`
|
|
18
25
|
}
|
|
19
26
|
|
|
27
|
+
async function handleCheckDifferent() {
|
|
28
|
+
await logoutGuest()
|
|
29
|
+
window.location.href = `/${countryCode}/account?view=guest`
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
return (
|
|
21
33
|
<div className="space-y-6">
|
|
22
34
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
23
35
|
<div>
|
|
24
|
-
<
|
|
25
|
-
|
|
36
|
+
<div className="flex items-center gap-2 mb-2" aria-hidden>
|
|
37
|
+
<span className="h-px w-6 bg-brand-accent/70" />
|
|
38
|
+
<span className="text-brand-accent text-[7px]">◆</span>
|
|
39
|
+
<span className="h-px w-6 bg-brand-accent/70" />
|
|
40
|
+
</div>
|
|
41
|
+
<h2 className="font-heading text-2xl font-bold text-heading uppercase tracking-[0.08em] mb-1">
|
|
42
|
+
Guest orders
|
|
43
|
+
</h2>
|
|
44
|
+
<p className="text-sm text-muted">
|
|
45
|
+
Track orders linked to your verified email.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="flex flex-wrap gap-2">
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
onClick={handleCheckDifferent}
|
|
52
|
+
className="btn-outline text-xs"
|
|
53
|
+
>
|
|
54
|
+
Check different email
|
|
55
|
+
</button>
|
|
56
|
+
<button type="button" onClick={handleSignOut} className="btn-outline text-xs">
|
|
57
|
+
End guest session
|
|
58
|
+
</button>
|
|
26
59
|
</div>
|
|
27
|
-
<button type="button" onClick={handleSignOut} className="btn-outline text-xs">
|
|
28
|
-
End guest session
|
|
29
|
-
</button>
|
|
30
60
|
</div>
|
|
31
61
|
|
|
32
|
-
{
|
|
33
|
-
<div className="
|
|
34
|
-
No guest orders found for this session
|
|
62
|
+
{safeOrders.length === 0 ? (
|
|
63
|
+
<div className="border border-cart-border bg-surface p-8 text-center">
|
|
64
|
+
<p className="text-sm text-muted mb-4">No guest orders found for this session.</p>
|
|
65
|
+
<LocalizedLink href="/store" className={`${auth.btnPrimary} ${auth.btnShine} inline-flex`}>
|
|
66
|
+
<span className="relative z-[1]">Continue shopping</span>
|
|
67
|
+
</LocalizedLink>
|
|
35
68
|
</div>
|
|
36
69
|
) : (
|
|
37
70
|
<ul className="space-y-3">
|
|
38
|
-
{
|
|
71
|
+
{safeOrders.map((order) => (
|
|
39
72
|
<li key={order.id}>
|
|
40
73
|
<LocalizedLink
|
|
41
74
|
href={`/orders/${order.id}`}
|
|
42
|
-
className="
|
|
75
|
+
className="border border-cart-border bg-surface p-5 flex items-center justify-between gap-4 hover:border-brand-accent transition-colors duration-200 block"
|
|
43
76
|
>
|
|
44
77
|
<div>
|
|
45
|
-
<p className="font-semibold text-heading text-sm">
|
|
78
|
+
<p className="font-semibold text-heading text-sm">
|
|
79
|
+
Order #{order.display_id}
|
|
80
|
+
</p>
|
|
46
81
|
<p className="text-xs text-muted mt-1 capitalize">
|
|
47
82
|
{order.status?.replace(/_/g, " ")}
|
|
48
83
|
</p>
|
package/src/account-orders.tsx
CHANGED
|
@@ -4,29 +4,60 @@ import { useMemo, useState } from "react"
|
|
|
4
4
|
import type { HttpTypes } from "@medusajs/types"
|
|
5
5
|
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
6
6
|
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
7
|
+
import { acct } from "./account-theme"
|
|
8
|
+
|
|
9
|
+
function statusStyle(status?: string, fulfillment?: string) {
|
|
10
|
+
const s = (fulfillment || status || "").toLowerCase()
|
|
11
|
+
if (s.includes("cancel")) {
|
|
12
|
+
return "bg-[#FFF1F2] text-[#BE123C] border-[#BE123C]/20"
|
|
13
|
+
}
|
|
14
|
+
if (s === "delivered" || s === "complete" || s === "completed") {
|
|
15
|
+
return "bg-[#E6F4F0] text-[#008A5D] border-[#008A5D]/20"
|
|
16
|
+
}
|
|
17
|
+
if (s.includes("ship")) {
|
|
18
|
+
return "bg-[#EBF5FF] text-[#0068D2] border-[#0068D2]/20"
|
|
19
|
+
}
|
|
20
|
+
if (s.includes("fulfill")) {
|
|
21
|
+
return "bg-surface-muted text-brand-accent border-brand-accent"
|
|
22
|
+
}
|
|
23
|
+
return "bg-surface-muted text-muted border-cart-border"
|
|
24
|
+
}
|
|
7
25
|
|
|
8
26
|
export default function AccountOrders({
|
|
9
27
|
orders,
|
|
10
28
|
}: {
|
|
11
|
-
orders
|
|
29
|
+
orders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
|
|
12
30
|
}) {
|
|
13
31
|
const [query, setQuery] = useState("")
|
|
14
32
|
|
|
33
|
+
const safeOrders: HttpTypes.StoreOrder[] = useMemo(() => {
|
|
34
|
+
if (Array.isArray(orders)) return orders
|
|
35
|
+
if (Array.isArray((orders as any)?.orders)) return (orders as any).orders
|
|
36
|
+
return []
|
|
37
|
+
}, [orders])
|
|
38
|
+
|
|
15
39
|
const filtered = useMemo(() => {
|
|
16
40
|
const q = query.trim().toLowerCase()
|
|
17
|
-
if (!q) return
|
|
18
|
-
return
|
|
41
|
+
if (!q) return safeOrders
|
|
42
|
+
return safeOrders.filter((order) => {
|
|
19
43
|
const id = String(order.display_id ?? order.id)
|
|
20
44
|
const status = String(order.status ?? "").replace(/_/g, " ")
|
|
21
|
-
|
|
45
|
+
const fulfillment = String(order.fulfillment_status ?? "").replace(/_/g, " ")
|
|
46
|
+
return (
|
|
47
|
+
id.includes(q) ||
|
|
48
|
+
status.toLowerCase().includes(q) ||
|
|
49
|
+
fulfillment.toLowerCase().includes(q)
|
|
50
|
+
)
|
|
22
51
|
})
|
|
23
|
-
}, [
|
|
52
|
+
}, [safeOrders, query])
|
|
24
53
|
|
|
25
54
|
return (
|
|
26
55
|
<div className="space-y-6">
|
|
27
|
-
<div>
|
|
28
|
-
<h2 className=
|
|
29
|
-
<p className=
|
|
56
|
+
<div className="pb-6 border-b border-cart-border">
|
|
57
|
+
<h2 className={acct.title}>Order history</h2>
|
|
58
|
+
<p className={`text-sm ${acct.muted} mt-2`}>
|
|
59
|
+
View and track your past orders.
|
|
60
|
+
</p>
|
|
30
61
|
</div>
|
|
31
62
|
|
|
32
63
|
<input
|
|
@@ -34,46 +65,67 @@ export default function AccountOrders({
|
|
|
34
65
|
value={query}
|
|
35
66
|
onChange={(e) => setQuery(e.target.value)}
|
|
36
67
|
placeholder="Search by order number or status"
|
|
37
|
-
className=
|
|
68
|
+
className={acct.input}
|
|
38
69
|
/>
|
|
39
70
|
|
|
40
71
|
{filtered.length === 0 ? (
|
|
41
|
-
<div className=
|
|
42
|
-
<p className=
|
|
43
|
-
{
|
|
72
|
+
<div className={`${acct.cardMuted} p-8 text-center`}>
|
|
73
|
+
<p className={`${acct.muted} text-sm mb-4`}>
|
|
74
|
+
{safeOrders.length === 0
|
|
75
|
+
? "No orders yet."
|
|
76
|
+
: "No orders match your search."}
|
|
44
77
|
</p>
|
|
45
|
-
{
|
|
46
|
-
<LocalizedLink href="/store" className=
|
|
78
|
+
{safeOrders.length === 0 && (
|
|
79
|
+
<LocalizedLink href="/store" className={acct.btnPrimary}>
|
|
47
80
|
Start shopping
|
|
48
81
|
</LocalizedLink>
|
|
49
82
|
)}
|
|
50
83
|
</div>
|
|
51
84
|
) : (
|
|
52
|
-
<ul className="space-y-
|
|
53
|
-
{filtered.map((order) =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
<ul className="space-y-4">
|
|
86
|
+
{filtered.map((order) => {
|
|
87
|
+
const label = (
|
|
88
|
+
order.fulfillment_status ||
|
|
89
|
+
order.status ||
|
|
90
|
+
""
|
|
91
|
+
).replace(/_/g, " ")
|
|
92
|
+
return (
|
|
93
|
+
<li key={order.id}>
|
|
94
|
+
<LocalizedLink
|
|
95
|
+
href={`/orders/${order.id}`}
|
|
96
|
+
className={`${acct.card} p-5 flex flex-wrap items-center justify-between gap-4 hover:border-brand-accent transition-colors block`}
|
|
97
|
+
>
|
|
98
|
+
<div className="space-y-2 min-w-0">
|
|
99
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
100
|
+
<p className={`font-bold ${acct.heading}`}>
|
|
101
|
+
Order #{order.display_id}
|
|
102
|
+
</p>
|
|
103
|
+
<span
|
|
104
|
+
className={`${acct.badge} capitalize ${statusStyle(
|
|
105
|
+
order.status,
|
|
106
|
+
order.fulfillment_status
|
|
107
|
+
)}`}
|
|
108
|
+
>
|
|
109
|
+
{label}
|
|
110
|
+
</span>
|
|
111
|
+
</div>
|
|
112
|
+
<p className={`text-xs ${acct.muted}`}>
|
|
113
|
+
{order.created_at &&
|
|
114
|
+
new Date(order.created_at).toLocaleDateString("en-IN", {
|
|
115
|
+
dateStyle: "medium",
|
|
116
|
+
})}
|
|
117
|
+
{" · "}
|
|
118
|
+
{order.items?.length ?? 0} item
|
|
119
|
+
{(order.items?.length ?? 0) === 1 ? "" : "s"}
|
|
120
|
+
</p>
|
|
121
|
+
</div>
|
|
122
|
+
<p className={`text-base font-bold ${acct.accent} shrink-0`}>
|
|
123
|
+
{formatPrice(order.total, order.currency_code)}
|
|
69
124
|
</p>
|
|
70
|
-
</
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
</LocalizedLink>
|
|
75
|
-
</li>
|
|
76
|
-
))}
|
|
125
|
+
</LocalizedLink>
|
|
126
|
+
</li>
|
|
127
|
+
)
|
|
128
|
+
})}
|
|
77
129
|
</ul>
|
|
78
130
|
)}
|
|
79
131
|
</div>
|
package/src/account-overview.tsx
CHANGED
|
@@ -4,108 +4,130 @@ import type { HttpTypes } from "@medusajs/types"
|
|
|
4
4
|
import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
|
|
5
5
|
import { formatPrice } from "@pradip1995/segment-primitives/format-price"
|
|
6
6
|
import { getProfileCompletion } from "./account-utils"
|
|
7
|
+
import { acct } from "./account-theme"
|
|
7
8
|
|
|
8
9
|
export default function AccountOverview({
|
|
9
10
|
customer,
|
|
10
11
|
orders,
|
|
11
|
-
countryCode,
|
|
12
12
|
}: {
|
|
13
13
|
customer: HttpTypes.StoreCustomer
|
|
14
|
-
orders
|
|
14
|
+
orders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
|
|
15
15
|
countryCode: string
|
|
16
16
|
}) {
|
|
17
|
-
const name = [customer.first_name, customer.last_name].filter(Boolean).join(" ") || customer.email
|
|
18
17
|
const completion = getProfileCompletion(customer)
|
|
19
18
|
const addressCount = customer.addresses?.length ?? 0
|
|
20
19
|
|
|
20
|
+
const safeOrders: HttpTypes.StoreOrder[] = Array.isArray(orders)
|
|
21
|
+
? orders
|
|
22
|
+
: Array.isArray((orders as any)?.orders)
|
|
23
|
+
? (orders as any).orders
|
|
24
|
+
: []
|
|
25
|
+
|
|
21
26
|
return (
|
|
22
|
-
<div
|
|
23
|
-
<div>
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
<div>
|
|
28
|
+
<div className="flex justify-between items-start gap-4 mb-6 pb-6 border-b border-cart-border">
|
|
29
|
+
<span className={`font-serif text-2xl font-extrabold ${acct.heading}`}>
|
|
30
|
+
Hello {customer.first_name}
|
|
31
|
+
</span>
|
|
32
|
+
<span className={`text-xs ${acct.muted} text-right shrink-0 max-w-[50%]`}>
|
|
33
|
+
Signed in as:{" "}
|
|
34
|
+
<span className={`font-semibold ${acct.accent} block sm:inline`}>
|
|
35
|
+
{customer.email}
|
|
36
|
+
</span>
|
|
37
|
+
</span>
|
|
27
38
|
</div>
|
|
28
39
|
|
|
29
|
-
<div className="
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
40
|
+
<div className="flex items-start gap-x-12 sm:gap-x-16 mb-8 pb-8 border-b border-cart-border">
|
|
41
|
+
<LocalizedLink href="/account/profile" className="flex flex-col gap-y-2 group">
|
|
42
|
+
<h3 className={acct.subsection}>Profile</h3>
|
|
43
|
+
<div className="flex items-end gap-x-2">
|
|
44
|
+
<span className="text-4xl font-serif font-extrabold text-heading group-hover:text-brand-accent transition-colors">
|
|
45
|
+
{completion}%
|
|
46
|
+
</span>
|
|
47
|
+
<span className={`uppercase text-xs font-medium tracking-wider ${acct.muted} pb-1`}>
|
|
48
|
+
Completed
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
</LocalizedLink>
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
</
|
|
53
|
-
|
|
54
|
-
</
|
|
53
|
+
<LocalizedLink href="/account/addresses" className="flex flex-col gap-y-2 group">
|
|
54
|
+
<h3 className={acct.subsection}>Addresses</h3>
|
|
55
|
+
<div className="flex items-end gap-x-2">
|
|
56
|
+
<span className="text-4xl font-serif font-extrabold text-heading group-hover:text-brand-accent transition-colors">
|
|
57
|
+
{addressCount}
|
|
58
|
+
</span>
|
|
59
|
+
<span className={`uppercase text-xs font-medium tracking-wider ${acct.muted} pb-1`}>
|
|
60
|
+
Saved
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
</LocalizedLink>
|
|
64
|
+
</div>
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
<div className="flex flex-col gap-y-4">
|
|
67
|
+
<h3 className={acct.sectionTitle}>Recent orders</h3>
|
|
68
|
+
{safeOrders.length === 0 ? (
|
|
69
|
+
<div className={`${acct.cardMuted} p-8 text-center`}>
|
|
70
|
+
<p className={`${acct.muted} text-sm mb-4`}>No orders yet.</p>
|
|
71
|
+
<LocalizedLink href="/store" className={acct.btnPrimary}>
|
|
60
72
|
Start shopping
|
|
61
73
|
</LocalizedLink>
|
|
62
74
|
</div>
|
|
63
75
|
) : (
|
|
64
|
-
<ul className="
|
|
65
|
-
{
|
|
76
|
+
<ul className="flex flex-col gap-y-3">
|
|
77
|
+
{safeOrders.slice(0, 5).map((order) => (
|
|
66
78
|
<li key={order.id}>
|
|
67
|
-
<LocalizedLink
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
<LocalizedLink href={`/orders/${order.id}`} className="block group">
|
|
80
|
+
<div
|
|
81
|
+
className={`${acct.cardMuted} flex justify-between items-center p-4 transition-colors group-hover:border-brand-accent group-hover:bg-surface`}
|
|
82
|
+
>
|
|
83
|
+
<div className="grid grid-cols-3 gap-x-4 flex-1 text-sm">
|
|
84
|
+
<div>
|
|
85
|
+
<span className={`font-semibold ${acct.muted} text-xs uppercase tracking-wide block`}>
|
|
86
|
+
Date placed
|
|
87
|
+
</span>
|
|
88
|
+
<span className={acct.heading}>
|
|
89
|
+
{order.created_at
|
|
90
|
+
? new Date(order.created_at).toLocaleDateString("en-IN", {
|
|
91
|
+
dateStyle: "medium",
|
|
92
|
+
})
|
|
93
|
+
: "—"}
|
|
94
|
+
</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div>
|
|
97
|
+
<span className={`font-semibold ${acct.muted} text-xs uppercase tracking-wide block`}>
|
|
98
|
+
Order
|
|
99
|
+
</span>
|
|
100
|
+
<span className={`font-bold ${acct.accent}`}>
|
|
101
|
+
#{order.display_id}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div>
|
|
105
|
+
<span className={`font-semibold ${acct.muted} text-xs uppercase tracking-wide block`}>
|
|
106
|
+
Total
|
|
107
|
+
</span>
|
|
108
|
+
<span className={`font-bold ${acct.heading}`}>
|
|
109
|
+
{formatPrice(order.total, order.currency_code)}
|
|
110
|
+
</span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<span className={`${acct.accent} text-lg font-bold group-hover:translate-x-0.5 transition-transform`}>
|
|
114
|
+
›
|
|
115
|
+
</span>
|
|
76
116
|
</div>
|
|
77
|
-
<p className="text-sm font-semibold text-brand-accent shrink-0">
|
|
78
|
-
{formatPrice(order.total, order.currency_code)}
|
|
79
|
-
</p>
|
|
80
117
|
</LocalizedLink>
|
|
81
118
|
</li>
|
|
82
119
|
))}
|
|
83
120
|
</ul>
|
|
84
121
|
)}
|
|
122
|
+
{safeOrders.length > 0 && (
|
|
123
|
+
<LocalizedLink
|
|
124
|
+
href="/account/orders"
|
|
125
|
+
className={`text-xs font-bold uppercase tracking-widest ${acct.accent} hover:underline mt-2`}
|
|
126
|
+
>
|
|
127
|
+
View all orders
|
|
128
|
+
</LocalizedLink>
|
|
129
|
+
)}
|
|
85
130
|
</div>
|
|
86
131
|
</div>
|
|
87
132
|
)
|
|
88
133
|
}
|
|
89
|
-
|
|
90
|
-
function StatCard({
|
|
91
|
-
title,
|
|
92
|
-
value,
|
|
93
|
-
subtitle,
|
|
94
|
-
href,
|
|
95
|
-
}: {
|
|
96
|
-
title: string
|
|
97
|
-
value: string
|
|
98
|
-
subtitle: string
|
|
99
|
-
href: string
|
|
100
|
-
}) {
|
|
101
|
-
return (
|
|
102
|
-
<LocalizedLink
|
|
103
|
-
href={href}
|
|
104
|
-
className="card-surface rounded-2xl p-5 hover:border-brand-accent transition-colors block"
|
|
105
|
-
>
|
|
106
|
-
<p className="text-xs uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-2">{title}</p>
|
|
107
|
-
<p className="text-3xl font-semibold text-heading leading-none">{value}</p>
|
|
108
|
-
<p className="text-xs text-muted mt-2 uppercase">{subtitle}</p>
|
|
109
|
-
</LocalizedLink>
|
|
110
|
-
)
|
|
111
|
-
}
|