@pradip1995/segment-login-template 0.4.2 → 0.5.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 +10 -7
- package/src/account-guest-orders.tsx +41 -12
- package/src/account-payment-methods.tsx +7 -0
- package/src/account-theme.ts +64 -1
- package/src/auth-server.ts +23 -7
- package/src/forgot-password-form.tsx +44 -26
- package/src/google-auth-section.tsx +8 -10
- package/src/guest-order-modal.tsx +224 -0
- package/src/login-brand-panel.tsx +158 -0
- package/src/login-form.tsx +211 -147
- package/src/otp-unified-form.tsx +340 -0
- package/src/payment-methods-actions.ts +16 -4
- package/src/register-form.tsx +77 -57
- package/src/segment.tsx +181 -48
package/src/segment.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { useState } from "react"
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
import { useSearchParams } from "next/navigation"
|
|
4
5
|
import type { HttpTypes } from "@medusajs/types"
|
|
5
6
|
import LoginForm from "./login-form"
|
|
6
7
|
import RegisterForm from "./register-form"
|
|
@@ -11,8 +12,13 @@ import AccountAddresses from "./account-addresses"
|
|
|
11
12
|
import AccountOrders from "./account-orders"
|
|
12
13
|
import AccountPaymentMethods from "./account-payment-methods"
|
|
13
14
|
import AccountGuestOrders from "./account-guest-orders"
|
|
15
|
+
import LoginBrandPanel from "./login-brand-panel"
|
|
16
|
+
import GuestOrderModal from "./guest-order-modal"
|
|
17
|
+
import OtpUnifiedForm from "./otp-unified-form"
|
|
14
18
|
import { resolveOtpComponent, type OtpVerificationComponent } from "./otp-component"
|
|
15
19
|
import { accountSectionPath } from "./account-utils"
|
|
20
|
+
import { auth, type BrandPanelConfig } from "./account-theme"
|
|
21
|
+
import { listGuestOrders } from "@pradip1995/commerce-core/data/guest"
|
|
16
22
|
|
|
17
23
|
type AccountProps = {
|
|
18
24
|
countryCode?: string
|
|
@@ -23,7 +29,16 @@ type AccountProps = {
|
|
|
23
29
|
guestOrders?: HttpTypes.StoreOrder[] | { orders?: HttpTypes.StoreOrder[] } | null
|
|
24
30
|
title?: string
|
|
25
31
|
subtitle?: string
|
|
32
|
+
registerTitle?: string
|
|
33
|
+
registerSubtitle?: string
|
|
34
|
+
brandPanel?: BrandPanelConfig | null
|
|
35
|
+
guestTrackLabel?: string
|
|
26
36
|
otpComponent?: OtpVerificationComponent
|
|
37
|
+
/**
|
|
38
|
+
* `otp` — Sahsha single-screen (Google + Mobile/Email OTP, signup in verify step).
|
|
39
|
+
* `full` — password + OTP tabs with separate register form.
|
|
40
|
+
*/
|
|
41
|
+
authMode?: "otp" | "full"
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
export default function LoginTemplate({
|
|
@@ -33,12 +48,54 @@ export default function LoginTemplate({
|
|
|
33
48
|
orders = [],
|
|
34
49
|
paymentDetails = [],
|
|
35
50
|
guestOrders = [],
|
|
36
|
-
title,
|
|
37
|
-
subtitle,
|
|
51
|
+
title = "Sign in",
|
|
52
|
+
subtitle = "Access your exclusive collection",
|
|
53
|
+
registerTitle = "Create account",
|
|
54
|
+
registerSubtitle = "Join our exclusive collection",
|
|
55
|
+
brandPanel,
|
|
56
|
+
guestTrackLabel = "Track guest order",
|
|
38
57
|
otpComponent,
|
|
58
|
+
authMode = "otp",
|
|
39
59
|
}: AccountProps) {
|
|
40
60
|
const OtpComponent = resolveOtpComponent(otpComponent)
|
|
41
61
|
const section = accountSectionPath(path)
|
|
62
|
+
const searchParams = useSearchParams()
|
|
63
|
+
|
|
64
|
+
const [view, setView] = useState<"login" | "register" | "forgot">(
|
|
65
|
+
path === "register" ? "register" : path === "forgot-password" ? "forgot" : "login"
|
|
66
|
+
)
|
|
67
|
+
const [showGuestModal, setShowGuestModal] = useState(false)
|
|
68
|
+
const [checkingGuest, setCheckingGuest] = useState(false)
|
|
69
|
+
const [guestEmail, setGuestEmail] = useState("")
|
|
70
|
+
const [guestAutoStart, setGuestAutoStart] = useState(false)
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const guestView = searchParams.get("view")
|
|
74
|
+
const email = searchParams.get("email")
|
|
75
|
+
if (guestView === "guest") {
|
|
76
|
+
setGuestEmail(email && email !== "undefined" ? email : "")
|
|
77
|
+
setGuestAutoStart(Boolean(email && email !== "undefined"))
|
|
78
|
+
setShowGuestModal(true)
|
|
79
|
+
}
|
|
80
|
+
}, [searchParams])
|
|
81
|
+
|
|
82
|
+
async function handleTrackGuestOrder() {
|
|
83
|
+
// Guest JWT is httpOnly (_medusa_guest_jwt); probe via server action (empty token → cookie).
|
|
84
|
+
setCheckingGuest(true)
|
|
85
|
+
try {
|
|
86
|
+
await listGuestOrders("")
|
|
87
|
+
window.location.href = `/${countryCode}/account/guest-orders`
|
|
88
|
+
return
|
|
89
|
+
} catch {
|
|
90
|
+
// No valid guest session — open OTP modal
|
|
91
|
+
} finally {
|
|
92
|
+
setCheckingGuest(false)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setGuestEmail(searchParams.get("email") || "")
|
|
96
|
+
setGuestAutoStart(false)
|
|
97
|
+
setShowGuestModal(true)
|
|
98
|
+
}
|
|
42
99
|
|
|
43
100
|
if (section === "guest-orders") {
|
|
44
101
|
return <AccountGuestOrders orders={guestOrders} countryCode={countryCode} />
|
|
@@ -61,56 +118,136 @@ export default function LoginTemplate({
|
|
|
61
118
|
case "payment-methods":
|
|
62
119
|
return (
|
|
63
120
|
<AccountPaymentMethods
|
|
64
|
-
paymentDetails={
|
|
121
|
+
paymentDetails={
|
|
122
|
+
paymentDetails as Parameters<typeof AccountPaymentMethods>[0]["paymentDetails"]
|
|
123
|
+
}
|
|
65
124
|
/>
|
|
66
125
|
)
|
|
67
126
|
case "overview":
|
|
68
127
|
default:
|
|
69
|
-
return
|
|
128
|
+
return (
|
|
129
|
+
<AccountOverview customer={customer} orders={orders} countryCode={countryCode} />
|
|
130
|
+
)
|
|
70
131
|
}
|
|
71
132
|
}
|
|
72
133
|
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
134
|
+
const isOtpMode = authMode === "otp"
|
|
135
|
+
const isSignIn = view === "login"
|
|
136
|
+
const isRegister = view === "register"
|
|
137
|
+
const heading = isOtpMode
|
|
138
|
+
? title
|
|
139
|
+
: isSignIn
|
|
140
|
+
? title
|
|
141
|
+
: isRegister
|
|
142
|
+
? registerTitle
|
|
143
|
+
: "Reset password"
|
|
144
|
+
const subheading = isOtpMode
|
|
145
|
+
? subtitle
|
|
146
|
+
: isSignIn
|
|
147
|
+
? subtitle
|
|
148
|
+
: isRegister
|
|
149
|
+
? registerSubtitle
|
|
150
|
+
: undefined
|
|
76
151
|
|
|
77
152
|
return (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
153
|
+
<>
|
|
154
|
+
<div data-login-viewport className={auth.viewport}>
|
|
155
|
+
<LoginBrandPanel panel={brandPanel} variant="mobile" />
|
|
156
|
+
<LoginBrandPanel panel={brandPanel} variant="desktop" />
|
|
157
|
+
|
|
158
|
+
<div className={auth.formColumn}>
|
|
159
|
+
<div className={auth.formWash} aria-hidden />
|
|
160
|
+
<div className={auth.formGlow} aria-hidden />
|
|
161
|
+
|
|
162
|
+
<div className={auth.formInner}>
|
|
163
|
+
{(isOtpMode || isSignIn || isRegister) && view !== "forgot" && (
|
|
164
|
+
<header className={auth.desktopHeader}>
|
|
165
|
+
<div className="flex items-center gap-3 mb-3">
|
|
166
|
+
<span className="h-px flex-1 max-w-[48px] bg-gradient-to-r from-brand-accent to-brand-primary" />
|
|
167
|
+
<span className="text-brand-accent text-[8px] leading-none">◆</span>
|
|
168
|
+
<span className="h-px flex-1 max-w-[48px] bg-gradient-to-l from-brand-accent to-brand-primary" />
|
|
169
|
+
</div>
|
|
170
|
+
<h1 className={auth.title}>{heading}</h1>
|
|
171
|
+
{subheading && <p className={auth.subtitle}>{subheading}</p>}
|
|
172
|
+
</header>
|
|
173
|
+
)}
|
|
174
|
+
|
|
175
|
+
<div className={auth.formCard}>
|
|
176
|
+
{isOtpMode ? (
|
|
177
|
+
view === "forgot" ? (
|
|
178
|
+
<ForgotPasswordForm onBack={() => setView("login")} />
|
|
179
|
+
) : (
|
|
180
|
+
<OtpUnifiedForm
|
|
181
|
+
countryCode={countryCode}
|
|
182
|
+
onTrackGuest={() => {
|
|
183
|
+
if (checkingGuest) return
|
|
184
|
+
void handleTrackGuestOrder()
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
)
|
|
188
|
+
) : (
|
|
189
|
+
<>
|
|
190
|
+
{view !== "forgot" && (
|
|
191
|
+
<div className="flex gap-6 mb-8 border-b border-cart-border">
|
|
192
|
+
<TabButton active={view === "login"} onClick={() => setView("login")}>
|
|
193
|
+
Sign in
|
|
194
|
+
</TabButton>
|
|
195
|
+
<TabButton
|
|
196
|
+
active={view === "register"}
|
|
197
|
+
onClick={() => setView("register")}
|
|
198
|
+
>
|
|
199
|
+
Register
|
|
200
|
+
</TabButton>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{view === "login" && (
|
|
205
|
+
<LoginForm
|
|
206
|
+
countryCode={countryCode}
|
|
207
|
+
OtpComponent={OtpComponent}
|
|
208
|
+
onForgot={() => setView("forgot")}
|
|
209
|
+
onRegister={() => setView("register")}
|
|
210
|
+
onTrackGuest={() => {
|
|
211
|
+
if (checkingGuest) return
|
|
212
|
+
void handleTrackGuestOrder()
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
{view === "register" && (
|
|
217
|
+
<RegisterForm
|
|
218
|
+
countryCode={countryCode}
|
|
219
|
+
OtpComponent={OtpComponent}
|
|
220
|
+
onLogin={() => setView("login")}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
223
|
+
{view === "forgot" && (
|
|
224
|
+
<ForgotPasswordForm onBack={() => setView("login")} />
|
|
225
|
+
)}
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
{checkingGuest && (
|
|
231
|
+
<p className="mt-4 text-xs text-muted uppercase tracking-[0.2em]">
|
|
232
|
+
{guestTrackLabel}…
|
|
233
|
+
</p>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
94
236
|
</div>
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
onLogin={() => setView("login")}
|
|
110
|
-
/>
|
|
111
|
-
)}
|
|
112
|
-
{view === "forgot" && <ForgotPasswordForm onBack={() => setView("login")} />}
|
|
113
|
-
</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<GuestOrderModal
|
|
240
|
+
open={showGuestModal}
|
|
241
|
+
onClose={() => {
|
|
242
|
+
setShowGuestModal(false)
|
|
243
|
+
setGuestAutoStart(false)
|
|
244
|
+
}}
|
|
245
|
+
countryCode={countryCode}
|
|
246
|
+
initialEmail={guestEmail}
|
|
247
|
+
autoStart={guestAutoStart}
|
|
248
|
+
OtpComponent={OtpComponent}
|
|
249
|
+
/>
|
|
250
|
+
</>
|
|
114
251
|
)
|
|
115
252
|
}
|
|
116
253
|
|
|
@@ -127,11 +264,7 @@ function TabButton({
|
|
|
127
264
|
<button
|
|
128
265
|
type="button"
|
|
129
266
|
onClick={onClick}
|
|
130
|
-
className={
|
|
131
|
-
active
|
|
132
|
-
? "border-brand-accent text-brand-accent"
|
|
133
|
-
: "border-transparent text-muted hover:text-heading"
|
|
134
|
-
}`}
|
|
267
|
+
className={active ? auth.tabActive : auth.tabIdle}
|
|
135
268
|
>
|
|
136
269
|
{children}
|
|
137
270
|
</button>
|