@pradip1995/segment-login-template 0.2.5 → 0.4.0

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/src/segment.tsx CHANGED
@@ -1,16 +1,29 @@
1
1
  "use client"
2
2
 
3
- import { useActionState, useState } from "react"
4
- import { login, requestPasswordReset, signout, signup } from "@pradip1995/commerce-core/client/actions/customer"
5
- import LocalizedLink from "@pradip1995/segment-primitives/localized-link"
6
- import { formatPrice } from "@pradip1995/segment-primitives/format-price"
3
+ import { useState } from "react"
7
4
  import type { HttpTypes } from "@medusajs/types"
5
+ import LoginForm from "./login-form"
6
+ import RegisterForm from "./register-form"
7
+ import ForgotPasswordForm from "./forgot-password-form"
8
+ import AccountOverview from "./account-overview"
9
+ import AccountProfile from "./account-profile"
10
+ import AccountAddresses from "./account-addresses"
11
+ import AccountOrders from "./account-orders"
12
+ import AccountPaymentMethods from "./account-payment-methods"
13
+ import AccountGuestOrders from "./account-guest-orders"
14
+ import { resolveOtpComponent, type OtpVerificationComponent } from "./otp-component"
15
+ import { accountSectionPath } from "./account-utils"
8
16
 
9
17
  type AccountProps = {
10
18
  countryCode?: string
11
19
  path?: string
12
20
  customer?: HttpTypes.StoreCustomer | null
13
21
  orders?: HttpTypes.StoreOrder[]
22
+ paymentDetails?: Record<string, unknown>[]
23
+ guestOrders?: HttpTypes.StoreOrder[]
24
+ title?: string
25
+ subtitle?: string
26
+ otpComponent?: OtpVerificationComponent
14
27
  }
15
28
 
16
29
  export default function LoginTemplate({
@@ -18,33 +31,84 @@ export default function LoginTemplate({
18
31
  path = "",
19
32
  customer,
20
33
  orders = [],
34
+ paymentDetails = [],
35
+ guestOrders = [],
36
+ title,
37
+ subtitle,
38
+ otpComponent,
21
39
  }: AccountProps) {
22
- const [view, setView] = useState<"login" | "register" | "forgot">(
23
- path === "register" ? "register" : path === "forgot-password" ? "forgot" : "login"
24
- )
40
+ const OtpComponent = resolveOtpComponent(otpComponent)
41
+ const section = accountSectionPath(path)
42
+
43
+ if (section === "guest-orders") {
44
+ return <AccountGuestOrders orders={guestOrders} countryCode={countryCode} />
45
+ }
25
46
 
26
47
  if (customer && customer.id !== "pending_deletion") {
27
- return <AccountOverview customer={customer} orders={orders} countryCode={countryCode} />
48
+ switch (section) {
49
+ case "profile":
50
+ return (
51
+ <AccountProfile
52
+ customer={customer}
53
+ countryCode={countryCode}
54
+ OtpComponent={OtpComponent}
55
+ />
56
+ )
57
+ case "addresses":
58
+ return <AccountAddresses customer={customer} countryCode={countryCode} />
59
+ case "orders":
60
+ return <AccountOrders orders={orders} />
61
+ case "payment-methods":
62
+ return (
63
+ <AccountPaymentMethods
64
+ paymentDetails={paymentDetails as Parameters<typeof AccountPaymentMethods>[0]["paymentDetails"]}
65
+ />
66
+ )
67
+ case "overview":
68
+ default:
69
+ return <AccountOverview customer={customer} orders={orders} countryCode={countryCode} />
70
+ }
28
71
  }
29
72
 
73
+ const [view, setView] = useState<"login" | "register" | "forgot">(
74
+ path === "register" ? "register" : path === "forgot-password" ? "forgot" : "login"
75
+ )
76
+
30
77
  return (
31
78
  <div className="w-full max-w-md mx-auto">
32
- <div className="flex gap-6 mb-8 border-b border-cart-border">
33
- <TabButton active={view === "login"} onClick={() => setView("login")}>
34
- Sign in
35
- </TabButton>
36
- <TabButton active={view === "register"} onClick={() => setView("register")}>
37
- Register
38
- </TabButton>
39
- </div>
79
+ {(title || subtitle) && view !== "forgot" && (
80
+ <div className="mb-8 text-center">
81
+ {title && <h1 className="section-heading text-2xl mb-2">{title}</h1>}
82
+ {subtitle && <p className="text-sm text-muted">{subtitle}</p>}
83
+ </div>
84
+ )}
85
+
86
+ {view !== "forgot" && (
87
+ <div className="flex gap-6 mb-8 border-b border-cart-border">
88
+ <TabButton active={view === "login"} onClick={() => setView("login")}>
89
+ Sign in
90
+ </TabButton>
91
+ <TabButton active={view === "register"} onClick={() => setView("register")}>
92
+ Register
93
+ </TabButton>
94
+ </div>
95
+ )}
40
96
 
41
97
  {view === "login" && (
42
98
  <LoginForm
43
99
  countryCode={countryCode}
100
+ OtpComponent={OtpComponent}
44
101
  onForgot={() => setView("forgot")}
102
+ onRegister={() => setView("register")}
103
+ />
104
+ )}
105
+ {view === "register" && (
106
+ <RegisterForm
107
+ countryCode={countryCode}
108
+ OtpComponent={OtpComponent}
109
+ onLogin={() => setView("login")}
45
110
  />
46
111
  )}
47
- {view === "register" && <RegisterForm countryCode={countryCode} />}
48
112
  {view === "forgot" && <ForgotPasswordForm onBack={() => setView("login")} />}
49
113
  </div>
50
114
  )
@@ -73,186 +137,3 @@ function TabButton({
73
137
  </button>
74
138
  )
75
139
  }
76
-
77
- function LoginForm({
78
- countryCode,
79
- onForgot,
80
- }: {
81
- countryCode: string
82
- onForgot: () => void
83
- }) {
84
- const [state, formAction, pending] = useActionState(login, null)
85
-
86
- return (
87
- <form action={formAction} className="space-y-4">
88
- <input type="hidden" name="countryCode" value={countryCode} />
89
- <Field label="Email or phone" name="email_or_phone" type="text" autoComplete="username" />
90
- <Field label="Password" name="password" type="password" autoComplete="current-password" />
91
- {state && typeof state === "string" && (
92
- <p className="text-sm text-brand-sale">{state}</p>
93
- )}
94
- <button type="submit" disabled={pending} className="btn-primary w-full disabled:opacity-60">
95
- {pending ? "Signing in…" : "Sign in"}
96
- </button>
97
- <button type="button" onClick={onForgot} className="text-sm text-muted hover:text-brand-accent w-full text-center">
98
- Forgot password?
99
- </button>
100
- </form>
101
- )
102
- }
103
-
104
- function RegisterForm({ countryCode }: { countryCode: string }) {
105
- const [state, formAction, pending] = useActionState(signup, null)
106
-
107
- return (
108
- <form action={formAction} className="space-y-4">
109
- <input type="hidden" name="countryCode" value={countryCode} />
110
- <div className="grid grid-cols-2 gap-3">
111
- <Field label="First name" name="first_name" type="text" autoComplete="given-name" />
112
- <Field label="Last name" name="last_name" type="text" autoComplete="family-name" />
113
- </div>
114
- <Field label="Email" name="email" type="email" autoComplete="email" />
115
- <Field label="Phone" name="phone" type="tel" autoComplete="tel" />
116
- <Field label="Password" name="password" type="password" autoComplete="new-password" />
117
- {state && typeof state === "string" && (
118
- <p className="text-sm text-brand-sale">{state}</p>
119
- )}
120
- <button type="submit" disabled={pending} className="btn-primary w-full disabled:opacity-60">
121
- {pending ? "Creating account…" : "Create account"}
122
- </button>
123
- </form>
124
- )
125
- }
126
-
127
- function ForgotPasswordForm({ onBack }: { onBack: () => void }) {
128
- const [email, setEmail] = useState("")
129
- const [message, setMessage] = useState<string | null>(null)
130
- const [pending, setPending] = useState(false)
131
-
132
- async function handleSubmit(e: React.FormEvent) {
133
- e.preventDefault()
134
- setPending(true)
135
- setMessage(null)
136
- const result = await requestPasswordReset(email)
137
- setMessage(result.success ? result.message : result.error || "Something went wrong")
138
- setPending(false)
139
- }
140
-
141
- return (
142
- <form onSubmit={handleSubmit} className="space-y-4">
143
- <p className="text-sm text-muted">Enter your email and we&apos;ll send a reset link.</p>
144
- <Field
145
- label="Email"
146
- name="email"
147
- type="email"
148
- value={email}
149
- onChange={(e) => setEmail(e.target.value)}
150
- />
151
- {message && <p className="text-sm text-body">{message}</p>}
152
- <button type="submit" disabled={pending} className="btn-primary w-full disabled:opacity-60">
153
- {pending ? "Sending…" : "Send reset link"}
154
- </button>
155
- <button type="button" onClick={onBack} className="text-sm text-muted hover:text-brand-accent w-full text-center">
156
- Back to sign in
157
- </button>
158
- </form>
159
- )
160
- }
161
-
162
- function AccountOverview({
163
- customer,
164
- orders,
165
- countryCode,
166
- }: {
167
- customer: HttpTypes.StoreCustomer
168
- orders: HttpTypes.StoreOrder[]
169
- countryCode: string
170
- }) {
171
- const name = [customer.first_name, customer.last_name].filter(Boolean).join(" ") || customer.email
172
-
173
- return (
174
- <div className="space-y-8">
175
- <div className="card-surface rounded-lg p-6">
176
- <p className="text-xs uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-2">My account</p>
177
- <h1 className="section-heading text-xl mb-1">{name}</h1>
178
- <p className="text-sm text-muted">{customer.email}</p>
179
- {customer.phone && <p className="text-sm text-muted mt-1">{customer.phone}</p>}
180
- <form action={signout.bind(null, countryCode)} className="mt-6">
181
- <button type="submit" className="btn-outline text-xs">
182
- Sign out
183
- </button>
184
- </form>
185
- </div>
186
-
187
- <div>
188
- <h2 className="text-sm font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-heading mb-4">
189
- Recent orders
190
- </h2>
191
- {orders.length === 0 ? (
192
- <div className="card-surface rounded-lg p-8 text-center">
193
- <p className="text-muted text-sm mb-4">No orders yet.</p>
194
- <LocalizedLink href="/store" className="btn-primary inline-block">
195
- Start shopping
196
- </LocalizedLink>
197
- </div>
198
- ) : (
199
- <ul className="space-y-3">
200
- {orders.map((order) => (
201
- <li key={order.id}>
202
- <LocalizedLink
203
- href={`/orders/${order.id}`}
204
- className="card-surface rounded-lg p-4 flex items-center justify-between gap-4 hover:border-brand-accent transition-colors block"
205
- >
206
- <div>
207
- <p className="font-medium text-heading text-sm">
208
- Order #{order.display_id}
209
- </p>
210
- <p className="text-xs text-muted mt-1 capitalize">
211
- {order.status?.replace(/_/g, " ")}
212
- </p>
213
- </div>
214
- <p className="text-sm font-semibold text-brand-accent shrink-0">
215
- {formatPrice(order.total, order.currency_code)}
216
- </p>
217
- </LocalizedLink>
218
- </li>
219
- ))}
220
- </ul>
221
- )}
222
- </div>
223
- </div>
224
- )
225
- }
226
-
227
- function Field({
228
- label,
229
- name,
230
- type,
231
- autoComplete,
232
- value,
233
- onChange,
234
- }: {
235
- label: string
236
- name: string
237
- type: string
238
- autoComplete?: string
239
- value?: string
240
- onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
241
- }) {
242
- return (
243
- <label className="block">
244
- <span className="text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-muted mb-1.5 block">
245
- {label}
246
- </span>
247
- <input
248
- name={name}
249
- type={type}
250
- autoComplete={autoComplete}
251
- value={value}
252
- onChange={onChange}
253
- required
254
- className="w-full border border-cart-border rounded px-3 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
255
- />
256
- </label>
257
- )
258
- }