@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.
@@ -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,79 @@
1
+ /** Logged-in account 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
29
+
30
+ /** Auth / login visual tokens — maps to segment-tokens CSS variables (theme-safe) */
31
+ export const auth = {
32
+ viewport:
33
+ "w-full flex flex-col lg:flex-row overflow-hidden min-h-[calc(100svh-6.25rem)] lg:h-[calc(100svh-6.25rem)] lg:max-h-[calc(100svh-6.25rem)] bg-page-bg",
34
+ formColumn: "relative flex flex-1 flex-col min-h-0 w-full lg:w-1/2 bg-page-bg",
35
+ formWash:
36
+ "pointer-events-none absolute inset-y-0 -left-20 w-40 bg-gradient-to-r from-transparent to-page-bg hidden lg:block z-[1]",
37
+ formGlow:
38
+ "pointer-events-none absolute inset-0 bg-gradient-to-l from-brand-primary/[0.03] via-transparent to-transparent hidden lg:block",
39
+ formInner:
40
+ "relative z-[2] flex flex-1 flex-col justify-center min-h-0 overflow-y-auto overscroll-contain px-5 sm:px-10 md:px-12 lg:px-14 xl:px-20 py-5 sm:py-6 lg:py-8",
41
+ formCard:
42
+ "relative w-full max-w-[440px] shrink-0 border border-cart-border/60 bg-surface shadow-[0_10px_40px_-16px_rgba(0,0,0,0.12)] p-7 sm:p-8",
43
+ desktopHeader: "hidden lg:block mb-6 max-w-[440px] shrink-0",
44
+ title:
45
+ "font-heading text-2xl xl:text-[32px] font-bold text-heading uppercase tracking-[0.08em] leading-tight",
46
+ subtitle: "mt-2 text-[11px] font-medium text-muted uppercase tracking-[0.28em]",
47
+ mobileTitle:
48
+ "font-heading text-base font-bold text-heading uppercase tracking-[0.16em] lg:hidden",
49
+ fieldLabel:
50
+ "text-[10px] font-semibold text-heading/80 uppercase tracking-[0.22em]",
51
+ input:
52
+ "w-full px-0 py-3 text-sm text-heading bg-transparent border-0 border-b-2 border-cart-border rounded-none placeholder:text-muted/50 focus:outline-none focus:border-brand-primary focus:ring-0 transition-colors duration-200",
53
+ btnPrimary:
54
+ "group relative w-full py-3.5 bg-brand-primary text-inverse text-[11px] font-bold uppercase tracking-[0.28em] shadow-[0_8px_28px_-6px_rgba(0,0,0,0.25)] hover:bg-brand-accent hover:shadow-[0_12px_32px_-6px_rgba(0,0,0,0.3)] transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed overflow-hidden active:scale-[0.98]",
55
+ btnShine:
56
+ "before:absolute before:inset-0 before:bg-gradient-to-r before:from-transparent before:via-white/15 before:to-transparent before:-translate-x-full hover:before:translate-x-full before:transition-transform before:duration-700",
57
+ btnOutline:
58
+ "w-full flex items-center justify-center gap-2.5 py-3 border border-brand-primary/15 bg-surface/80 hover:bg-page-bg hover:border-brand-primary/35 shadow-sm transition-all duration-200 disabled:opacity-50",
59
+ guestCta:
60
+ "w-full py-3 border border-brand-primary/20 bg-gradient-to-r from-surface to-page-bg text-heading text-[10px] font-bold uppercase tracking-[0.18em] hover:border-brand-accent hover:shadow-[0_4px_16px_-4px_rgba(0,0,0,0.15)] transition-all duration-300 flex items-center justify-center gap-2 disabled:opacity-50 active:scale-[0.98]",
61
+ tabActive:
62
+ "pb-3 text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] border-b-2 -mb-px border-brand-accent text-brand-accent transition-colors duration-200",
63
+ tabIdle:
64
+ "pb-3 text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] border-b-2 -mb-px border-transparent text-muted hover:text-heading transition-colors duration-200",
65
+ modeActive:
66
+ "flex-1 py-2 text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] rounded-md bg-surface text-heading shadow-sm transition-colors duration-200",
67
+ modeIdle:
68
+ "flex-1 py-2 text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] rounded-md text-muted hover:text-heading transition-colors duration-200",
69
+ chipActive:
70
+ "px-3 py-1.5 text-xs font-medium border border-brand-accent text-brand-accent bg-brand-accent/5 transition-colors duration-200",
71
+ chipIdle:
72
+ "px-3 py-1.5 text-xs font-medium border border-cart-border text-muted hover:text-heading transition-colors duration-200",
73
+ } as const
74
+
75
+ export type BrandPanelConfig = {
76
+ image?: string | null
77
+ eyebrow?: string | null
78
+ headline?: string | null
79
+ }
@@ -2,6 +2,8 @@
2
2
 
3
3
  import { useState } from "react"
4
4
  import { checkEmailRegistered, requestPasswordReset } from "./auth-server"
5
+ import { auth } from "./account-theme"
6
+ import { AuthField, Ornament } from "./login-form"
5
7
 
6
8
  export default function ForgotPasswordForm({ onBack }: { onBack: () => void }) {
7
9
  const [email, setEmail] = useState("")
@@ -42,13 +44,17 @@ export default function ForgotPasswordForm({ onBack }: { onBack: () => void }) {
42
44
 
43
45
  if (success) {
44
46
  return (
45
- <div className="space-y-4 text-center py-4">
46
- <div className="w-14 h-14 mx-auto rounded-full bg-green-100 flex items-center justify-center text-2xl">
47
-
47
+ <div className="space-y-5 text-center py-4">
48
+ <Ornament />
49
+ <div className="w-14 h-14 mx-auto rounded-full bg-brand-accent/10 flex items-center justify-center text-brand-accent">
50
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden>
51
+ <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
52
+ <polyline points="22,6 12,13 2,6" />
53
+ </svg>
48
54
  </div>
49
- <h2 className="section-heading text-lg">Check your email</h2>
55
+ <h2 className={`${auth.title} text-xl`}>Check your email</h2>
50
56
  <p className="text-sm text-muted">{message}</p>
51
- <button type="button" onClick={onBack} className="btn-outline w-full">
57
+ <button type="button" onClick={onBack} className={auth.btnOutline}>
52
58
  Back to sign in
53
59
  </button>
54
60
  </div>
@@ -56,31 +62,43 @@ export default function ForgotPasswordForm({ onBack }: { onBack: () => void }) {
56
62
  }
57
63
 
58
64
  return (
59
- <form onSubmit={handleSubmit} className="space-y-4">
60
- <p className="text-sm text-muted">
61
- Enter your email and we&apos;ll send a link to reset your password.
62
- </p>
63
- <label className="block">
64
- <span className="text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-1.5 block">
65
- Email
66
- </span>
67
- <input
68
- name="email"
69
- type="email"
70
- value={email}
71
- onChange={(e) => setEmail(e.target.value)}
72
- required
73
- className="w-full border border-cart-border rounded px-3 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
74
- />
75
- </label>
76
- {error && <p className="text-sm text-brand-sale">{error}</p>}
77
- <button type="submit" disabled={pending} className="btn-primary w-full disabled:opacity-60">
78
- {pending ? "Sending…" : "Send reset link"}
65
+ <form onSubmit={handleSubmit} className="space-y-5">
66
+ <div className="text-center lg:text-left">
67
+ <Ornament />
68
+ <h2 className={`${auth.title} text-xl lg:text-2xl`}>Reset password</h2>
69
+ <p className="mt-2 text-sm text-muted">
70
+ Enter your email and we&apos;ll send a link to reset your password.
71
+ </p>
72
+ </div>
73
+
74
+ <AuthField
75
+ label="Email"
76
+ name="email"
77
+ type="email"
78
+ value={email}
79
+ onChange={(e) => setEmail(e.target.value)}
80
+ autoComplete="email"
81
+ placeholder="you@example.com"
82
+ />
83
+
84
+ {error && (
85
+ <p className="text-sm text-brand-sale" role="alert">
86
+ {error}
87
+ </p>
88
+ )}
89
+
90
+ <button
91
+ type="submit"
92
+ disabled={pending}
93
+ className={`${auth.btnPrimary} ${auth.btnShine}`}
94
+ >
95
+ <span className="relative z-[1]">{pending ? "Sending…" : "Send reset link"}</span>
79
96
  </button>
97
+
80
98
  <button
81
99
  type="button"
82
100
  onClick={onBack}
83
- className="text-sm text-muted hover:text-brand-accent w-full text-center"
101
+ className="text-sm text-muted hover:text-brand-accent w-full text-center transition-colors"
84
102
  >
85
103
  Back to sign in
86
104
  </button>
@@ -6,9 +6,7 @@ import {
6
6
  GOOGLE_LOGIN_COUNTRY_CODE_KEY,
7
7
  } from "@pradip1995/commerce-auth/util/google-auth-client"
8
8
  import { initiateGoogleAuth } from "./auth-server"
9
-
10
- const BUTTON_CLASS =
11
- "btn-outline w-full flex items-center justify-center gap-2.5 py-3 px-4"
9
+ import { auth } from "./account-theme"
12
10
 
13
11
  export default function GoogleAuthSection({ countryCode }: { countryCode: string }) {
14
12
  const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID?.trim()
@@ -46,9 +44,9 @@ export default function GoogleAuthSection({ countryCode }: { countryCode: string
46
44
  }
47
45
 
48
46
  return (
49
- <div className="mb-6">
47
+ <div>
50
48
  {error && (
51
- <p className="mb-3 text-sm text-red-600" role="alert">
49
+ <p className="mb-3 text-sm text-brand-sale" role="alert">
52
50
  {error}
53
51
  </p>
54
52
  )}
@@ -56,10 +54,10 @@ export default function GoogleAuthSection({ countryCode }: { countryCode: string
56
54
  type="button"
57
55
  onClick={handleClick}
58
56
  disabled={loading}
59
- className={BUTTON_CLASS}
60
- style={{ borderRadius: "30px" }}
57
+ className={auth.btnOutline}
58
+ aria-label="Continue with Google"
61
59
  >
62
- <svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
60
+ <svg width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
63
61
  <path
64
62
  fill="#4285F4"
65
63
  d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
@@ -77,8 +75,8 @@ export default function GoogleAuthSection({ countryCode }: { countryCode: string
77
75
  d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
78
76
  />
79
77
  </svg>
80
- <span className="text-sm text-gray-700 font-bold">
81
- {loading ? "Connecting..." : "Continue with Google"}
78
+ <span className="text-xs text-heading font-medium tracking-wide">
79
+ {loading ? "Redirecting…" : "Continue with Google"}
82
80
  </span>
83
81
  </button>
84
82
  </div>