@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pradip1995/segment-login-template",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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="section-heading text-xl mb-1">Saved addresses</h2>
27
- <p className="text-sm text-muted">Manage shipping and billing addresses.</p>
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="btn-primary text-sm">
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="card-surface rounded-2xl p-8 text-center text-sm text-muted">
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="card-surface rounded-2xl p-5">
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="btn-outline text-xs"
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="card-surface rounded-2xl p-5">
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-brand-sale">{String(state.error)}</p>
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="btn-primary text-sm disabled:opacity-60">
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="btn-outline text-sm">
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-brand-sale hover:underline disabled:opacity-60"
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-[var(--letter-spacing-nav)] text-muted mb-1.5 block">
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: HttpTypes.StoreOrder[]
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
- <h2 className="section-heading text-xl mb-1">Guest orders</h2>
25
- <p className="text-sm text-muted">Orders linked to your verified email.</p>
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
- {orders.length === 0 ? (
33
- <div className="card-surface rounded-2xl p-8 text-center text-sm text-muted">
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
- {orders.map((order) => (
71
+ {safeOrders.map((order) => (
39
72
  <li key={order.id}>
40
73
  <LocalizedLink
41
74
  href={`/orders/${order.id}`}
42
- className="card-surface rounded-2xl p-5 flex items-center justify-between gap-4 hover:border-brand-accent transition-colors block"
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">Order #{order.display_id}</p>
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>
@@ -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: HttpTypes.StoreOrder[]
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 orders
18
- return orders.filter((order) => {
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
- return id.includes(q) || status.toLowerCase().includes(q)
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
- }, [orders, query])
52
+ }, [safeOrders, query])
24
53
 
25
54
  return (
26
55
  <div className="space-y-6">
27
- <div>
28
- <h2 className="section-heading text-xl mb-1">Order history</h2>
29
- <p className="text-sm text-muted">View and track your past orders.</p>
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="w-full border border-cart-border rounded-lg px-4 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
68
+ className={acct.input}
38
69
  />
39
70
 
40
71
  {filtered.length === 0 ? (
41
- <div className="card-surface rounded-2xl p-8 text-center">
42
- <p className="text-muted text-sm mb-4">
43
- {orders.length === 0 ? "No orders yet." : "No orders match your search."}
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
- {orders.length === 0 && (
46
- <LocalizedLink href="/store" className="btn-primary inline-block text-sm">
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-3">
53
- {filtered.map((order) => (
54
- <li key={order.id}>
55
- <LocalizedLink
56
- href={`/orders/${order.id}`}
57
- className="card-surface rounded-2xl p-5 flex flex-wrap items-center justify-between gap-4 hover:border-brand-accent transition-colors block"
58
- >
59
- <div>
60
- <p className="font-semibold text-heading">Order #{order.display_id}</p>
61
- <p className="text-xs text-muted mt-1 capitalize">
62
- {order.status?.replace(/_/g, " ")}
63
- {order.created_at && (
64
- <> · {new Date(order.created_at).toLocaleDateString()}</>
65
- )}
66
- </p>
67
- <p className="text-xs text-muted mt-1">
68
- {order.items?.length ?? 0} item{(order.items?.length ?? 0) === 1 ? "" : "s"}
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
- </div>
71
- <p className="text-sm font-semibold text-brand-accent shrink-0">
72
- {formatPrice(order.total, order.currency_code)}
73
- </p>
74
- </LocalizedLink>
75
- </li>
76
- ))}
125
+ </LocalizedLink>
126
+ </li>
127
+ )
128
+ })}
77
129
  </ul>
78
130
  )}
79
131
  </div>
@@ -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: HttpTypes.StoreOrder[]
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 className="space-y-8">
23
- <div>
24
- <p className="text-sm text-muted mb-1">Welcome back</p>
25
- <h2 className="section-heading text-xl">Hello, {customer.first_name || name}</h2>
26
- <p className="text-sm text-muted mt-1">Signed in as {customer.email}</p>
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="grid grid-cols-1 sm:grid-cols-2 gap-4">
30
- <StatCard
31
- title="Profile"
32
- value={`${completion}%`}
33
- subtitle="Completed"
34
- href="/account/profile"
35
- />
36
- <StatCard
37
- title="Addresses"
38
- value={String(addressCount)}
39
- subtitle="Saved"
40
- href="/account/addresses"
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
- <div>
45
- <div className="flex items-center justify-between gap-4 mb-4">
46
- <h3 className="text-sm font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-heading">
47
- Recent orders
48
- </h3>
49
- {orders.length > 0 && (
50
- <LocalizedLink href="/account/orders" className="text-xs text-brand-accent hover:underline">
51
- View all
52
- </LocalizedLink>
53
- )}
54
- </div>
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
- {orders.length === 0 ? (
57
- <div className="card-surface rounded-2xl p-8 text-center">
58
- <p className="text-muted text-sm mb-4">No orders yet.</p>
59
- <LocalizedLink href="/store" className="btn-primary inline-block text-sm">
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="space-y-3">
65
- {orders.slice(0, 5).map((order) => (
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
- href={`/orders/${order.id}`}
69
- className="card-surface rounded-2xl p-4 flex items-center justify-between gap-4 hover:border-brand-accent transition-colors block"
70
- >
71
- <div>
72
- <p className="font-medium text-heading text-sm">Order #{order.display_id}</p>
73
- <p className="text-xs text-muted mt-1 capitalize">
74
- {order.status?.replace(/_/g, " ")}
75
- </p>
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
- }