@pradip1995/segment-login-template 0.4.1 → 0.4.2

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.2",
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
  )
@@ -9,9 +9,15 @@ export default function AccountGuestOrders({
9
9
  orders,
10
10
  countryCode,
11
11
  }: {
12
- orders: HttpTypes.StoreOrder[]
12
+ orders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
13
13
  countryCode: string
14
14
  }) {
15
+ const safeOrders: HttpTypes.StoreOrder[] = Array.isArray(orders)
16
+ ? orders
17
+ : Array.isArray((orders as any)?.orders)
18
+ ? (orders as any).orders
19
+ : []
20
+
15
21
  async function handleSignOut() {
16
22
  await logoutGuest()
17
23
  window.location.href = `/${countryCode}/account`
@@ -29,13 +35,13 @@ export default function AccountGuestOrders({
29
35
  </button>
30
36
  </div>
31
37
 
32
- {orders.length === 0 ? (
38
+ {safeOrders.length === 0 ? (
33
39
  <div className="card-surface rounded-2xl p-8 text-center text-sm text-muted">
34
40
  No guest orders found for this session.
35
41
  </div>
36
42
  ) : (
37
43
  <ul className="space-y-3">
38
- {orders.map((order) => (
44
+ {safeOrders.map((order) => (
39
45
  <li key={order.id}>
40
46
  <LocalizedLink
41
47
  href={`/orders/${order.id}`}
@@ -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
- }
@@ -2,10 +2,11 @@
2
2
 
3
3
  import { useState } from "react"
4
4
  import {
5
- createPaymentDetail,
6
- deletePaymentDetail,
7
- makeDefaultPaymentDetail,
8
- } from "@pradip1995/commerce-core/data/payment-details"
5
+ addPaymentMethodAction,
6
+ deletePaymentMethodAction,
7
+ makeDefaultPaymentMethodAction,
8
+ } from "./payment-methods-actions"
9
+ import { acct } from "./account-theme"
9
10
 
10
11
  type PaymentDetail = {
11
12
  id: string
@@ -14,6 +15,19 @@ type PaymentDetail = {
14
15
  is_default?: boolean
15
16
  }
16
17
 
18
+ const emptyForm = {
19
+ upi_id: "",
20
+ account_holder_name: "",
21
+ bank_name: "",
22
+ account_number: "",
23
+ ifsc: "",
24
+ branch_name: "",
25
+ card_holder_name: "",
26
+ card_number: "",
27
+ expiry_date: "",
28
+ cvv: "",
29
+ }
30
+
17
31
  export default function AccountPaymentMethods({
18
32
  paymentDetails,
19
33
  }: {
@@ -23,92 +37,118 @@ export default function AccountPaymentMethods({
23
37
  const [type, setType] = useState<"upi" | "bank" | "card">("upi")
24
38
  const [pending, setPending] = useState(false)
25
39
  const [error, setError] = useState<string | null>(null)
26
- const [form, setForm] = useState({
27
- upi_id: "",
28
- account_holder: "",
29
- account_number: "",
30
- ifsc: "",
31
- card_last4: "",
32
- })
40
+ const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
41
+ const [form, setForm] = useState(emptyForm)
33
42
 
34
43
  async function handleAdd(e: React.FormEvent) {
35
44
  e.preventDefault()
36
45
  setPending(true)
37
46
  setError(null)
38
- try {
39
- let detail_json: Record<string, string> = {}
40
- if (type === "upi") {
41
- detail_json = { upi_id: form.upi_id.trim() }
42
- } else if (type === "bank") {
43
- detail_json = {
44
- account_holder: form.account_holder.trim(),
45
- account_number: form.account_number.trim(),
46
- ifsc: form.ifsc.trim(),
47
- }
48
- } else {
49
- detail_json = { card_last4: form.card_last4.trim() }
47
+ setFieldErrors({})
48
+
49
+ let detail_json: Record<string, string> = {}
50
+ if (type === "upi") {
51
+ detail_json = { upi_id: form.upi_id.trim() }
52
+ } else if (type === "bank") {
53
+ detail_json = {
54
+ account_holder_name: form.account_holder_name.trim(),
55
+ bank_name: form.bank_name.trim(),
56
+ account_number: form.account_number.trim(),
57
+ ifsc: form.ifsc.trim(),
58
+ }
59
+ if (form.branch_name.trim()) {
60
+ detail_json.branch_name = form.branch_name.trim()
50
61
  }
51
- await createPaymentDetail(type, detail_json)
52
- window.location.reload()
53
- } catch (err) {
54
- setError(err instanceof Error ? err.message : "Failed to add payment method")
55
- } finally {
62
+ } else {
63
+ detail_json = {
64
+ card_holder_name: form.card_holder_name.trim(),
65
+ card_number: form.card_number.trim(),
66
+ expiry_date: form.expiry_date.trim(),
67
+ cvv: form.cvv.trim(),
68
+ }
69
+ }
70
+
71
+ const result = await addPaymentMethodAction(type, detail_json)
72
+ if (!result.success) {
73
+ setError(result.error)
74
+ setFieldErrors(result.field_errors || {})
56
75
  setPending(false)
76
+ return
57
77
  }
78
+ window.location.reload()
58
79
  }
59
80
 
60
81
  async function handleDefault(id: string) {
61
82
  setPending(true)
62
- try {
63
- await makeDefaultPaymentDetail(id)
64
- window.location.reload()
65
- } finally {
83
+ setError(null)
84
+ setFieldErrors({})
85
+ const result = await makeDefaultPaymentMethodAction(id)
86
+ if (!result.success) {
87
+ setError(result.error)
66
88
  setPending(false)
89
+ return
67
90
  }
91
+ window.location.reload()
68
92
  }
69
93
 
70
94
  async function handleDelete(id: string) {
71
95
  if (!confirm("Remove this payment method?")) return
72
96
  setPending(true)
73
- try {
74
- await deletePaymentDetail(id)
75
- window.location.reload()
76
- } finally {
97
+ setError(null)
98
+ setFieldErrors({})
99
+ const result = await deletePaymentMethodAction(id)
100
+ if (!result.success) {
101
+ setError(result.error)
77
102
  setPending(false)
103
+ return
78
104
  }
105
+ window.location.reload()
79
106
  }
80
107
 
81
108
  function labelFor(detail: PaymentDetail) {
82
109
  const json = detail.detail_json ?? {}
83
110
  if (detail.type === "upi") return `UPI · ${json.upi_id || "—"}`
84
- if (detail.type === "bank")
85
- return `Bank · ${json.account_holder || "—"} (${json.account_number?.slice(-4) || "****"})`
86
- return `Card · **** ${json.card_last4 || "****"}`
111
+ if (detail.type === "bank") {
112
+ const holder = json.account_holder_name || json.account_holder || ""
113
+ const last4 = json.account_number?.slice(-4) || "****"
114
+ const bank = json.bank_name ? ` · ${json.bank_name}` : ""
115
+ return `Bank · ${holder}${bank} (····${last4})`
116
+ }
117
+ const holder = json.card_holder_name || "—"
118
+ const last4 =
119
+ json.card_number?.replace(/\s+/g, "").slice(-4) ||
120
+ json.card_last4 ||
121
+ "****"
122
+ return `Card · ${holder} (····${last4})`
87
123
  }
88
124
 
89
125
  return (
90
126
  <div className="space-y-6">
91
127
  <div className="flex flex-wrap items-end justify-between gap-4">
92
128
  <div>
93
- <h2 className="section-heading text-xl mb-1">Payment methods</h2>
94
- <p className="text-sm text-muted">Refund destinations for returns and exchanges.</p>
129
+ <h2 className={acct.title}>Payment methods</h2>
130
+ <p className={`text-sm ${acct.muted}`}>Refund destinations for returns and exchanges.</p>
95
131
  </div>
96
- <button type="button" onClick={() => setShowForm((v) => !v)} className="btn-primary text-sm">
132
+ <button type="button" onClick={() => setShowForm((v) => !v)} className={acct.btnPrimary}>
97
133
  {showForm ? "Cancel" : "Add method"}
98
134
  </button>
99
135
  </div>
100
136
 
101
137
  {showForm && (
102
- <form onSubmit={handleAdd} className="card-surface rounded-2xl p-6 space-y-4">
138
+ <form onSubmit={handleAdd} className={`${acct.card} p-6 space-y-4`}>
103
139
  <div className="flex flex-wrap gap-2">
104
140
  {(["upi", "bank", "card"] as const).map((t) => (
105
141
  <button
106
142
  key={t}
107
143
  type="button"
108
- onClick={() => setType(t)}
109
- className={`px-3 py-1.5 text-xs font-medium rounded-full border ${
144
+ onClick={() => {
145
+ setType(t)
146
+ setError(null)
147
+ setFieldErrors({})
148
+ }}
149
+ className={`px-3 py-1.5 text-xs font-medium rounded-none border ${
110
150
  type === t
111
- ? "border-brand-accent text-brand-accent bg-brand-accent/5"
151
+ ? "border-brand-accent text-brand-accent bg-brand-accent-muted"
112
152
  : "border-cart-border text-muted"
113
153
  }`}
114
154
  >
@@ -121,46 +161,87 @@ export default function AccountPaymentMethods({
121
161
  <Field
122
162
  label="UPI ID"
123
163
  value={form.upi_id}
164
+ error={fieldErrors.upi_id}
124
165
  onChange={(v) => setForm((f) => ({ ...f, upi_id: v }))}
125
166
  />
126
167
  )}
168
+
127
169
  {type === "bank" && (
128
170
  <>
129
171
  <Field
130
- label="Account holder"
131
- value={form.account_holder}
132
- onChange={(v) => setForm((f) => ({ ...f, account_holder: v }))}
172
+ label="Account holder name"
173
+ value={form.account_holder_name}
174
+ error={fieldErrors.account_holder_name}
175
+ onChange={(v) => setForm((f) => ({ ...f, account_holder_name: v }))}
176
+ />
177
+ <Field
178
+ label="Bank name"
179
+ value={form.bank_name}
180
+ error={fieldErrors.bank_name}
181
+ onChange={(v) => setForm((f) => ({ ...f, bank_name: v }))}
133
182
  />
134
183
  <Field
135
184
  label="Account number"
136
185
  value={form.account_number}
186
+ error={fieldErrors.account_number}
137
187
  onChange={(v) => setForm((f) => ({ ...f, account_number: v }))}
138
188
  />
139
189
  <Field
140
190
  label="IFSC"
141
191
  value={form.ifsc}
192
+ error={fieldErrors.ifsc}
142
193
  onChange={(v) => setForm((f) => ({ ...f, ifsc: v }))}
143
194
  />
195
+ <Field
196
+ label="Branch name (optional)"
197
+ value={form.branch_name}
198
+ required={false}
199
+ error={fieldErrors.branch_name}
200
+ onChange={(v) => setForm((f) => ({ ...f, branch_name: v }))}
201
+ />
144
202
  </>
145
203
  )}
204
+
146
205
  {type === "card" && (
147
- <Field
148
- label="Last 4 digits"
149
- value={form.card_last4}
150
- onChange={(v) => setForm((f) => ({ ...f, card_last4: v }))}
151
- />
206
+ <>
207
+ <Field
208
+ label="Card holder name"
209
+ value={form.card_holder_name}
210
+ error={fieldErrors.card_holder_name}
211
+ onChange={(v) => setForm((f) => ({ ...f, card_holder_name: v }))}
212
+ />
213
+ <Field
214
+ label="Card number"
215
+ value={form.card_number}
216
+ error={fieldErrors.card_number}
217
+ onChange={(v) => setForm((f) => ({ ...f, card_number: v }))}
218
+ />
219
+ <Field
220
+ label="Expiry (MM/YY)"
221
+ value={form.expiry_date}
222
+ error={fieldErrors.expiry_date}
223
+ placeholder="08/28"
224
+ onChange={(v) => setForm((f) => ({ ...f, expiry_date: v }))}
225
+ />
226
+ <Field
227
+ label="CVV"
228
+ value={form.cvv}
229
+ error={fieldErrors.cvv}
230
+ onChange={(v) => setForm((f) => ({ ...f, cvv: v }))}
231
+ />
232
+ </>
152
233
  )}
153
234
 
154
- {error && <p className="text-sm text-brand-sale">{error}</p>}
155
-
156
- <button type="submit" disabled={pending} className="btn-primary text-sm disabled:opacity-60">
235
+ <button type="submit" disabled={pending} className={acct.btnPrimary}>
157
236
  {pending ? "Saving…" : "Save payment method"}
158
237
  </button>
159
238
  </form>
160
239
  )}
161
240
 
241
+ {error && <p className="text-sm text-red-600">{error}</p>}
242
+
162
243
  {paymentDetails.length === 0 ? (
163
- <div className="card-surface rounded-2xl p-8 text-center text-sm text-muted">
244
+ <div className={`${acct.cardMuted} p-8 text-center text-sm ${acct.muted}`}>
164
245
  No refund payment methods saved.
165
246
  </div>
166
247
  ) : (
@@ -168,7 +249,7 @@ export default function AccountPaymentMethods({
168
249
  {paymentDetails.map((detail) => (
169
250
  <li
170
251
  key={detail.id}
171
- className="card-surface rounded-2xl p-5 flex flex-wrap justify-between gap-4 items-center"
252
+ className={`${acct.card} p-5 flex flex-wrap justify-between gap-4 items-center`}
172
253
  >
173
254
  <div>
174
255
  <p className="font-medium text-heading text-sm">{labelFor(detail)}</p>
@@ -191,7 +272,7 @@ export default function AccountPaymentMethods({
191
272
  type="button"
192
273
  onClick={() => handleDelete(detail.id)}
193
274
  disabled={pending}
194
- className="text-brand-sale hover:underline disabled:opacity-60"
275
+ className="text-red-600 hover:underline disabled:opacity-60"
195
276
  >
196
277
  Remove
197
278
  </button>
@@ -208,22 +289,33 @@ function Field({
208
289
  label,
209
290
  value,
210
291
  onChange,
292
+ error,
293
+ required = true,
294
+ placeholder,
211
295
  }: {
212
296
  label: string
213
297
  value: string
214
298
  onChange: (value: string) => void
299
+ error?: string
300
+ required?: boolean
301
+ placeholder?: string
215
302
  }) {
216
303
  return (
217
304
  <label className="block">
218
- <span className="text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-1.5 block">
305
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted mb-1.5 block">
219
306
  {label}
220
307
  </span>
221
308
  <input
222
309
  value={value}
223
310
  onChange={(e) => onChange(e.target.value)}
224
- required
225
- className="w-full border border-cart-border rounded px-3 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
311
+ required={required}
312
+ placeholder={placeholder}
313
+ aria-invalid={Boolean(error)}
314
+ className={`w-full border rounded-none px-3 py-2.5 text-sm bg-surface text-heading focus:outline-none focus:border-brand-accent ${
315
+ error ? "border-red-500" : "border-cart-border"
316
+ }`}
226
317
  />
318
+ {error && <span className="mt-1 block text-xs text-red-600">{error}</span>}
227
319
  </label>
228
320
  )
229
321
  }
@@ -9,6 +9,7 @@ import {
9
9
  } from "./auth-server"
10
10
  import type { OtpVerificationComponent } from "./otp-component"
11
11
  import OtpVerificationModal from "./otp-verification-modal"
12
+ import { acct } from "./account-theme"
12
13
 
13
14
  export default function AccountProfile({
14
15
  customer,
@@ -94,28 +95,28 @@ export default function AccountProfile({
94
95
  return (
95
96
  <div className="space-y-8">
96
97
  <div>
97
- <h2 className="section-heading text-xl mb-1">Account details</h2>
98
- <p className="text-sm text-muted">Update your name and contact information.</p>
98
+ <h2 className={acct.title}>Account details</h2>
99
+ <p className={`text-sm ${acct.muted}`}>Update your name and contact information.</p>
99
100
  </div>
100
101
 
101
- <form action={formAction} className="card-surface rounded-2xl p-6 space-y-4">
102
+ <form action={formAction} className={`${acct.card} p-6 space-y-4`}>
102
103
  <Field label="Full name" name="full_name" defaultValue={fullName} />
103
104
  <div>
104
- <label className="block text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-1.5">
105
+ <label className="block text-xs font-semibold uppercase tracking-wider text-muted mb-1.5">
105
106
  Email
106
107
  </label>
107
108
  <div className="flex flex-wrap items-center gap-3">
108
109
  <input
109
110
  value={customer.email}
110
111
  readOnly
111
- className="flex-1 min-w-[200px] border border-cart-border rounded px-3 py-2.5 text-sm bg-surface-muted text-muted"
112
+ className="flex-1 min-w-[200px] border border-cart-border rounded-none px-3 py-2.5 text-sm bg-surface-muted text-muted"
112
113
  />
113
114
  {!emailVerified && (
114
115
  <button
115
116
  type="button"
116
117
  onClick={() => sendVerifyOtp("email_verification")}
117
118
  disabled={otpPending}
118
- className="btn-outline text-xs shrink-0"
119
+ className={`${acct.btnOutline} !py-2 !px-3`}
119
120
  >
120
121
  Verify email
121
122
  </button>
@@ -133,7 +134,7 @@ export default function AccountProfile({
133
134
  type="button"
134
135
  onClick={() => sendVerifyOtp("phone_verification")}
135
136
  disabled={otpPending}
136
- className="btn-outline text-xs"
137
+ className={`${acct.btnOutline} !py-2 !px-3`}
137
138
  >
138
139
  Verify phone
139
140
  </button>
@@ -145,12 +146,12 @@ export default function AccountProfile({
145
146
  </div>
146
147
 
147
148
  {(state.error || message) && (
148
- <p className={`text-sm ${state.ok || message ? "text-green-600" : "text-brand-sale"}`}>
149
+ <p className={`text-sm ${state.ok || message ? "text-green-600" : "text-red-600"}`}>
149
150
  {message || state.error}
150
151
  </p>
151
152
  )}
152
153
 
153
- <button type="submit" disabled={pending} className="btn-primary disabled:opacity-60">
154
+ <button type="submit" disabled={pending} className={acct.btnPrimary}>
154
155
  {pending ? "Saving…" : "Save changes"}
155
156
  </button>
156
157
  </form>
@@ -184,14 +185,14 @@ function Field({
184
185
  }) {
185
186
  return (
186
187
  <label className="block">
187
- <span className="text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-1.5 block">
188
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted mb-1.5 block">
188
189
  {label}
189
190
  </span>
190
191
  <input
191
192
  name={name}
192
193
  type={type}
193
194
  defaultValue={defaultValue}
194
- className="w-full border border-cart-border rounded px-3 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
195
+ 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"
195
196
  />
196
197
  </label>
197
198
  )
@@ -0,0 +1,28 @@
1
+ /** Account / order visual tokens — maps to segment-tokens CSS variables */
2
+ export const acct = {
3
+ pageBg: "bg-page-bg",
4
+ card: "bg-surface border border-cart-border rounded-none shadow-card",
5
+ cardMuted: "bg-surface-muted border border-cart-border rounded-none",
6
+ title:
7
+ "font-heading text-2xl sm:text-3xl font-extrabold tracking-tight text-heading",
8
+ sectionTitle:
9
+ "text-base sm:text-lg font-bold uppercase tracking-wider text-heading",
10
+ subsection: "text-sm font-semibold uppercase tracking-wider text-muted",
11
+ accent: "text-brand-accent",
12
+ muted: "text-muted",
13
+ heading: "text-heading",
14
+ body: "text-sm text-body",
15
+ border: "border-cart-border",
16
+ btnPrimary:
17
+ "inline-flex items-center justify-center bg-brand-primary hover:bg-brand-accent text-inverse rounded-none px-6 py-3 font-bold text-xs uppercase tracking-widest transition-all disabled:opacity-50",
18
+ btnAccent:
19
+ "inline-flex items-center justify-center bg-brand-accent hover:bg-brand-accent-hover text-inverse rounded-none px-6 py-3 font-bold text-xs uppercase tracking-widest transition-all disabled:opacity-50",
20
+ btnOutline:
21
+ "inline-flex items-center justify-center border border-cart-border text-brand-accent rounded-none px-6 py-3 font-bold text-xs uppercase tracking-widest hover:bg-surface-muted transition-all disabled:opacity-50",
22
+ input:
23
+ "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",
24
+ label:
25
+ "block text-xs font-semibold uppercase tracking-wider text-muted mb-1.5",
26
+ badge:
27
+ "inline-flex items-center px-3 py-1 text-xs font-semibold border rounded-none",
28
+ } as const
@@ -0,0 +1,214 @@
1
+ "use server"
2
+
3
+ import { getAuthHeaders } from "@pradip1995/commerce-core/data/cookies"
4
+
5
+ type PaymentMethodType = "upi" | "bank" | "card"
6
+
7
+ type ActionResult =
8
+ | { success: true }
9
+ | { success: false; error: string; field_errors?: Record<string, string> }
10
+
11
+ function getBaseUrl() {
12
+ return process.env.MEDUSA_BACKEND_URL || "http://localhost:9000"
13
+ }
14
+
15
+ function getPublishableKey() {
16
+ return process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || ""
17
+ }
18
+
19
+ function trimAll(detail_json: Record<string, string>): Record<string, string> {
20
+ const out: Record<string, string> = {}
21
+ for (const [key, value] of Object.entries(detail_json)) {
22
+ if (typeof value !== "string") continue
23
+ const trimmed = value.trim()
24
+ if (!trimmed) continue
25
+ out[key] = key === "ifsc" ? trimmed.toUpperCase() : trimmed
26
+ }
27
+ return out
28
+ }
29
+
30
+ function validatePaymentDetail(
31
+ type: PaymentMethodType,
32
+ detail_json: Record<string, string>
33
+ ): Record<string, string> {
34
+ const field_errors: Record<string, string> = {}
35
+
36
+ if (type === "upi") {
37
+ const upi = detail_json.upi_id || ""
38
+ if (!upi) {
39
+ field_errors.upi_id = "UPI ID is required"
40
+ } else if (!/^[\w.\-]{2,256}@[a-zA-Z]{2,64}$/.test(upi)) {
41
+ field_errors.upi_id = "Enter a valid UPI ID (e.g. name@upi)"
42
+ }
43
+ }
44
+
45
+ if (type === "bank") {
46
+ const holder = detail_json.account_holder_name || ""
47
+ const bank = detail_json.bank_name || ""
48
+ const account = detail_json.account_number || ""
49
+ const ifsc = detail_json.ifsc || ""
50
+
51
+ if (!holder) {
52
+ field_errors.account_holder_name = "Account holder name is required"
53
+ }
54
+ if (!bank) {
55
+ field_errors.bank_name = "Bank name is required"
56
+ }
57
+ if (!account) {
58
+ field_errors.account_number = "Account number is required"
59
+ } else if (!/^\d{9,18}$/.test(account)) {
60
+ field_errors.account_number = "Account number must be 9–18 digits"
61
+ }
62
+ if (!ifsc) {
63
+ field_errors.ifsc = "IFSC is required"
64
+ } else if (!/^[A-Z]{4}0[A-Z0-9]{6}$/.test(ifsc)) {
65
+ field_errors.ifsc =
66
+ "Enter a valid IFSC (e.g. HDFC0001234) — 4 letters, 0, then 6 characters"
67
+ }
68
+ }
69
+
70
+ if (type === "card") {
71
+ const holder = detail_json.card_holder_name || ""
72
+ const number = detail_json.card_number || ""
73
+ const expiry = detail_json.expiry_date || ""
74
+ const cvv = detail_json.cvv || ""
75
+
76
+ if (!holder) {
77
+ field_errors.card_holder_name = "Card holder name is required"
78
+ }
79
+ if (!number) {
80
+ field_errors.card_number = "Card number is required"
81
+ } else if (!/^\d{13,19}$/.test(number.replace(/\s+/g, ""))) {
82
+ field_errors.card_number = "Enter a valid card number (13–19 digits)"
83
+ }
84
+ if (!expiry) {
85
+ field_errors.expiry_date = "Expiry date is required"
86
+ } else if (!/^(0[1-9]|1[0-2])\/\d{2}$/.test(expiry)) {
87
+ field_errors.expiry_date = "Use MM/YY format (e.g. 08/28)"
88
+ }
89
+ if (!cvv) {
90
+ field_errors.cvv = "CVV is required"
91
+ } else if (!/^\d{3,4}$/.test(cvv)) {
92
+ field_errors.cvv = "CVV must be 3 or 4 digits"
93
+ }
94
+ }
95
+
96
+ return field_errors
97
+ }
98
+
99
+ function firstFieldError(field_errors: Record<string, string>): string {
100
+ return Object.values(field_errors)[0] || "Invalid payment details"
101
+ }
102
+
103
+ function parseBackendError(body: unknown, status: number): string {
104
+ if (!body || typeof body !== "object") {
105
+ return status === 400
106
+ ? "Invalid payment details. Check the entered values."
107
+ : `Request failed (${status})`
108
+ }
109
+
110
+ const data = body as {
111
+ message?: unknown
112
+ errors?: Array<{ message?: string; path?: string[]; field?: string }>
113
+ }
114
+
115
+ if (Array.isArray(data.errors) && data.errors.length > 0) {
116
+ const details = data.errors
117
+ .map((item) => {
118
+ const path = item.path?.join(".") || item.field
119
+ const msg = item.message?.trim()
120
+ if (path && msg) return `${path}: ${msg}`
121
+ return msg
122
+ })
123
+ .filter((msg): msg is string => Boolean(msg))
124
+ if (details.length) return details.join("; ")
125
+ }
126
+
127
+ if (typeof data.message === "string" && data.message.trim()) {
128
+ const msg = data.message.trim()
129
+ if (/^invalid request( data)?\.?$/i.test(msg)) {
130
+ return "Invalid payment details. Check required fields and try again."
131
+ }
132
+ return msg
133
+ }
134
+
135
+ return `Request failed (${status})`
136
+ }
137
+
138
+ async function paymentDetailsFetch(
139
+ path: string,
140
+ init: { method: string; body?: Record<string, unknown> }
141
+ ): Promise<ActionResult> {
142
+ const authHeaders = await getAuthHeaders()
143
+ if (!("authorization" in authHeaders)) {
144
+ return { success: false, error: "Please sign in to manage payment methods" }
145
+ }
146
+
147
+ const response = await fetch(`${getBaseUrl()}${path}`, {
148
+ method: init.method,
149
+ headers: {
150
+ Accept: "application/json",
151
+ "Content-Type": "application/json",
152
+ "x-publishable-api-key": getPublishableKey(),
153
+ ...authHeaders,
154
+ },
155
+ body: init.body ? JSON.stringify(init.body) : undefined,
156
+ cache: "no-store",
157
+ })
158
+
159
+ if (response.ok) {
160
+ return { success: true }
161
+ }
162
+
163
+ const body = await response.json().catch(() => null)
164
+ return {
165
+ success: false,
166
+ error: parseBackendError(body, response.status),
167
+ }
168
+ }
169
+
170
+ export async function addPaymentMethodAction(
171
+ type: PaymentMethodType,
172
+ detail_json: Record<string, string>
173
+ ): Promise<ActionResult> {
174
+ const normalized = trimAll(detail_json)
175
+ if (normalized.card_number) {
176
+ normalized.card_number = normalized.card_number.replace(/\s+/g, "")
177
+ }
178
+
179
+ const field_errors = validatePaymentDetail(type, normalized)
180
+ if (Object.keys(field_errors).length > 0) {
181
+ return {
182
+ success: false,
183
+ error: firstFieldError(field_errors),
184
+ field_errors,
185
+ }
186
+ }
187
+
188
+ return paymentDetailsFetch("/store/payment-details", {
189
+ method: "POST",
190
+ body: { type, detail_json: normalized },
191
+ })
192
+ }
193
+
194
+ export async function makeDefaultPaymentMethodAction(
195
+ id: string
196
+ ): Promise<ActionResult> {
197
+ if (!id) {
198
+ return { success: false, error: "Payment method id is required" }
199
+ }
200
+ return paymentDetailsFetch(`/store/payment-details/${id}/make-default`, {
201
+ method: "POST",
202
+ })
203
+ }
204
+
205
+ export async function deletePaymentMethodAction(
206
+ id: string
207
+ ): Promise<ActionResult> {
208
+ if (!id) {
209
+ return { success: false, error: "Payment method id is required" }
210
+ }
211
+ return paymentDetailsFetch(`/store/payment-details/${id}`, {
212
+ method: "DELETE",
213
+ })
214
+ }
package/src/segment.tsx CHANGED
@@ -18,9 +18,9 @@ type AccountProps = {
18
18
  countryCode?: string
19
19
  path?: string
20
20
  customer?: HttpTypes.StoreCustomer | null
21
- orders?: HttpTypes.StoreOrder[]
21
+ orders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
22
22
  paymentDetails?: Record<string, unknown>[]
23
- guestOrders?: HttpTypes.StoreOrder[]
23
+ guestOrders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
24
24
  title?: string
25
25
  subtitle?: string
26
26
  otpComponent?: OtpVerificationComponent