@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/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={paymentDetails as Parameters<typeof AccountPaymentMethods>[0]["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 <AccountOverview customer={customer} orders={orders} countryCode={countryCode} />
128
+ return (
129
+ <AccountOverview customer={customer} orders={orders} countryCode={countryCode} />
130
+ )
70
131
  }
71
132
  }
72
133
 
73
- const [view, setView] = useState<"login" | "register" | "forgot">(
74
- path === "register" ? "register" : path === "forgot-password" ? "forgot" : "login"
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
- <div className="w-full max-w-md mx-auto">
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>
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
- {view === "login" && (
98
- <LoginForm
99
- countryCode={countryCode}
100
- OtpComponent={OtpComponent}
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")}
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={`pb-3 text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] border-b-2 -mb-px transition-colors ${
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>