@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/login-form.tsx
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState, useState } from "react"
|
|
4
4
|
import { login } from "@pradip1995/commerce-core/client/actions/customer"
|
|
5
|
-
import { sendAuthOtp,
|
|
5
|
+
import { sendAuthOtp, verifyAuthOtpAndLogin } from "./auth-server"
|
|
6
6
|
import GoogleAuthSection from "./google-auth-section"
|
|
7
7
|
import OtpVerificationModal from "./otp-verification-modal"
|
|
8
8
|
import type { OtpVerificationComponent } from "./otp-component"
|
|
9
|
+
import { auth } from "./account-theme"
|
|
9
10
|
|
|
10
11
|
type LoginMode = "password" | "otp"
|
|
11
12
|
|
|
@@ -14,28 +15,31 @@ export default function LoginForm({
|
|
|
14
15
|
OtpComponent = OtpVerificationModal,
|
|
15
16
|
onForgot,
|
|
16
17
|
onRegister,
|
|
18
|
+
onTrackGuest,
|
|
17
19
|
}: {
|
|
18
20
|
countryCode: string
|
|
19
21
|
OtpComponent?: OtpVerificationComponent
|
|
20
22
|
onForgot: () => void
|
|
21
23
|
onRegister: () => void
|
|
24
|
+
onTrackGuest?: () => void
|
|
22
25
|
}) {
|
|
23
26
|
const [mode, setMode] = useState<LoginMode>("password")
|
|
24
27
|
|
|
25
28
|
return (
|
|
26
|
-
<div className="space-y-6">
|
|
29
|
+
<div className="w-full flex flex-col space-y-6" data-testid="login-page">
|
|
30
|
+
<div className="text-center lg:hidden">
|
|
31
|
+
<Ornament />
|
|
32
|
+
<h2 className={auth.mobileTitle}>Sign in</h2>
|
|
33
|
+
<p className="mt-2 text-[10px] text-muted uppercase tracking-[0.24em]">
|
|
34
|
+
Access your account
|
|
35
|
+
</p>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
27
38
|
<GoogleAuthSection countryCode={countryCode} />
|
|
28
39
|
|
|
29
|
-
<
|
|
30
|
-
<div className="absolute inset-0 flex items-center">
|
|
31
|
-
<div className="w-full border-t border-cart-border" />
|
|
32
|
-
</div>
|
|
33
|
-
<div className="relative flex justify-center text-xs uppercase tracking-[var(--letter-spacing-nav)]">
|
|
34
|
-
<span className="bg-page-bg px-3 text-muted">Or continue with</span>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
40
|
+
<AuthDivider label="Or continue with" />
|
|
37
41
|
|
|
38
|
-
<div className="flex gap-2 p-1 bg-surface-muted
|
|
42
|
+
<div className="flex gap-2 p-1 bg-surface-muted/80">
|
|
39
43
|
<ModeButton active={mode === "password"} onClick={() => setMode("password")}>
|
|
40
44
|
Password
|
|
41
45
|
</ModeButton>
|
|
@@ -52,10 +56,28 @@ export default function LoginForm({
|
|
|
52
56
|
|
|
53
57
|
<p className="text-sm text-center text-muted">
|
|
54
58
|
New here?{" "}
|
|
55
|
-
<button
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={onRegister}
|
|
62
|
+
className="text-heading font-semibold hover:text-brand-accent transition-colors underline underline-offset-2"
|
|
63
|
+
>
|
|
56
64
|
Create an account
|
|
57
65
|
</button>
|
|
58
66
|
</p>
|
|
67
|
+
|
|
68
|
+
{onTrackGuest && (
|
|
69
|
+
<div className="pt-4 border-t border-brand-primary/10">
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
onClick={onTrackGuest}
|
|
73
|
+
className={auth.guestCta}
|
|
74
|
+
data-testid="track-guest-order-button"
|
|
75
|
+
>
|
|
76
|
+
<GuestPackageIcon />
|
|
77
|
+
Track guest order
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
59
81
|
</div>
|
|
60
82
|
)
|
|
61
83
|
}
|
|
@@ -70,13 +92,7 @@ function ModeButton({
|
|
|
70
92
|
children: React.ReactNode
|
|
71
93
|
}) {
|
|
72
94
|
return (
|
|
73
|
-
<button
|
|
74
|
-
type="button"
|
|
75
|
-
onClick={onClick}
|
|
76
|
-
className={`flex-1 py-2 text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] rounded-md transition-colors ${
|
|
77
|
-
active ? "bg-page-bg text-heading shadow-sm" : "text-muted hover:text-heading"
|
|
78
|
-
}`}
|
|
79
|
-
>
|
|
95
|
+
<button type="button" onClick={onClick} className={active ? auth.modeActive : auth.modeIdle}>
|
|
80
96
|
{children}
|
|
81
97
|
</button>
|
|
82
98
|
)
|
|
@@ -90,33 +106,72 @@ function PasswordLoginForm({
|
|
|
90
106
|
onForgot: () => void
|
|
91
107
|
}) {
|
|
92
108
|
const [state, formAction, pending] = useActionState(login, null)
|
|
109
|
+
const [showPassword, setShowPassword] = useState(false)
|
|
93
110
|
|
|
94
111
|
return (
|
|
95
|
-
<form action={formAction} className="space-y-
|
|
112
|
+
<form action={formAction} className="space-y-5">
|
|
96
113
|
<input type="hidden" name="country_code" value={countryCode} />
|
|
97
|
-
|
|
98
|
-
<
|
|
114
|
+
|
|
115
|
+
<AuthField
|
|
116
|
+
label="Email or phone"
|
|
117
|
+
name="email_or_phone"
|
|
118
|
+
type="text"
|
|
119
|
+
autoComplete="username"
|
|
120
|
+
placeholder="Enter email or phone"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<div>
|
|
124
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
125
|
+
<span className={auth.fieldLabel}>Password</span>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={onForgot}
|
|
129
|
+
className={`${auth.fieldLabel} text-brand-accent hover:text-heading transition-colors`}
|
|
130
|
+
>
|
|
131
|
+
Forgot?
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="relative">
|
|
135
|
+
<input
|
|
136
|
+
name="password"
|
|
137
|
+
type={showPassword ? "text" : "password"}
|
|
138
|
+
autoComplete="current-password"
|
|
139
|
+
required
|
|
140
|
+
placeholder="Enter your password"
|
|
141
|
+
className={`${auth.input} pr-10`}
|
|
142
|
+
data-testid="password-input"
|
|
143
|
+
/>
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
onClick={() => setShowPassword((v) => !v)}
|
|
147
|
+
className="absolute inset-y-0 right-0 flex items-center text-muted hover:text-heading transition-colors"
|
|
148
|
+
aria-label={showPassword ? "Hide password" : "Show password"}
|
|
149
|
+
>
|
|
150
|
+
{showPassword ? <EyeOffIcon /> : <EyeIcon />}
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
99
155
|
{state && typeof state === "string" && state !== "ACCOUNT_DELETION_PENDING" && (
|
|
100
|
-
<p className="text-sm text-brand-sale">
|
|
156
|
+
<p className="text-sm text-brand-sale" role="alert">
|
|
101
157
|
{state === "Email not verified." || state.includes("not verified")
|
|
102
158
|
? "Your email is not verified yet. Complete OTP verification during registration, or use OTP sign-in below."
|
|
103
159
|
: state}
|
|
104
160
|
</p>
|
|
105
161
|
)}
|
|
106
162
|
{state === "ACCOUNT_DELETION_PENDING" && (
|
|
107
|
-
<p className="text-sm text-brand-sale">
|
|
163
|
+
<p className="text-sm text-brand-sale" role="alert">
|
|
108
164
|
This account has a pending deletion request. Check your email to cancel it.
|
|
109
165
|
</p>
|
|
110
166
|
)}
|
|
111
|
-
|
|
112
|
-
{pending ? "Signing in…" : "Sign in"}
|
|
113
|
-
</button>
|
|
167
|
+
|
|
114
168
|
<button
|
|
115
|
-
type="
|
|
116
|
-
|
|
117
|
-
className=
|
|
169
|
+
type="submit"
|
|
170
|
+
disabled={pending}
|
|
171
|
+
className={`${auth.btnPrimary} ${auth.btnShine}`}
|
|
172
|
+
data-testid="sign-in-button"
|
|
118
173
|
>
|
|
119
|
-
|
|
174
|
+
<span className="relative z-[1]">{pending ? "Signing in…" : "Sign in"}</span>
|
|
120
175
|
</button>
|
|
121
176
|
</form>
|
|
122
177
|
)
|
|
@@ -129,7 +184,7 @@ function OtpLoginForm({
|
|
|
129
184
|
countryCode: string
|
|
130
185
|
OtpComponent: OtpVerificationComponent
|
|
131
186
|
}) {
|
|
132
|
-
const [authMethod, setAuthMethod] = useState<"email_auth" | "phone_auth"
|
|
187
|
+
const [authMethod, setAuthMethod] = useState<"email_auth" | "phone_auth">("email_auth")
|
|
133
188
|
const [identifier, setIdentifier] = useState("")
|
|
134
189
|
const [otpToken, setOtpToken] = useState<string | null>(null)
|
|
135
190
|
const [otp, setOtp] = useState("")
|
|
@@ -146,17 +201,6 @@ function OtpLoginForm({
|
|
|
146
201
|
setError(null)
|
|
147
202
|
|
|
148
203
|
try {
|
|
149
|
-
if (authMethod === "guest") {
|
|
150
|
-
const res = await sendOTP(identifier)
|
|
151
|
-
if (!res.success && (res as { error?: string }).error) {
|
|
152
|
-
throw new Error((res as { error?: string }).error)
|
|
153
|
-
}
|
|
154
|
-
setOtpToken("guest")
|
|
155
|
-
setOtp("")
|
|
156
|
-
setShowOtpModal(true)
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
|
|
160
204
|
const res = await sendAuthOtp(
|
|
161
205
|
authMethod === "email_auth"
|
|
162
206
|
? { email: identifier.trim(), type: "email_auth" }
|
|
@@ -183,15 +227,6 @@ function OtpLoginForm({
|
|
|
183
227
|
setError(null)
|
|
184
228
|
|
|
185
229
|
try {
|
|
186
|
-
if (authMethod === "guest") {
|
|
187
|
-
const res = await sendOTP(identifier)
|
|
188
|
-
if (!res.success && (res as { error?: string }).error) {
|
|
189
|
-
throw new Error((res as { error?: string }).error)
|
|
190
|
-
}
|
|
191
|
-
setOtp("")
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
|
|
195
230
|
const res = await sendAuthOtp(
|
|
196
231
|
authMethod === "email_auth"
|
|
197
232
|
? { email: identifier.trim(), type: "email_auth" }
|
|
@@ -218,30 +253,20 @@ function OtpLoginForm({
|
|
|
218
253
|
setError(null)
|
|
219
254
|
|
|
220
255
|
try {
|
|
221
|
-
if (authMethod === "guest") {
|
|
222
|
-
const res = await verifyOTP(identifier, otpToken || "", otp)
|
|
223
|
-
if (!res.success) {
|
|
224
|
-
throw new Error((res as { error?: string }).error || "Invalid code")
|
|
225
|
-
}
|
|
226
|
-
const token = (res as { token?: string }).token
|
|
227
|
-
if (token) {
|
|
228
|
-
document.cookie = `_medusa_guest_token=${token}; path=/; max-age=86400; SameSite=Lax`
|
|
229
|
-
}
|
|
230
|
-
window.location.href = `/${countryCode}/account/guest-orders`
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
|
|
234
256
|
if (isNewUser && (!firstName.trim() || !lastName.trim())) {
|
|
235
257
|
throw new Error("Please enter your first and last name to finish registration")
|
|
236
258
|
}
|
|
237
259
|
|
|
238
|
-
await verifyAuthOtpAndLogin({
|
|
260
|
+
const result = await verifyAuthOtpAndLogin({
|
|
239
261
|
token: otpToken || "",
|
|
240
262
|
code: otp,
|
|
241
263
|
countryCode,
|
|
242
264
|
first_name: isNewUser ? firstName.trim() : undefined,
|
|
243
265
|
last_name: isNewUser ? lastName.trim() : undefined,
|
|
244
266
|
})
|
|
267
|
+
if (result && !result.success) {
|
|
268
|
+
throw new Error(result.error || "Verification failed")
|
|
269
|
+
}
|
|
245
270
|
} catch (err) {
|
|
246
271
|
if (isNextRedirect(err)) throw err
|
|
247
272
|
setError(err instanceof Error ? err.message : "Verification failed")
|
|
@@ -249,13 +274,6 @@ function OtpLoginForm({
|
|
|
249
274
|
}
|
|
250
275
|
}
|
|
251
276
|
|
|
252
|
-
const otpTitle =
|
|
253
|
-
authMethod === "guest"
|
|
254
|
-
? "Verify guest order access"
|
|
255
|
-
: authMethod === "phone_auth"
|
|
256
|
-
? "Verify your phone"
|
|
257
|
-
: "Verify your email"
|
|
258
|
-
|
|
259
277
|
return (
|
|
260
278
|
<>
|
|
261
279
|
<form onSubmit={handleSendOtp} className="space-y-4">
|
|
@@ -266,41 +284,45 @@ function OtpLoginForm({
|
|
|
266
284
|
<Chip active={authMethod === "phone_auth"} onClick={() => setAuthMethod("phone_auth")}>
|
|
267
285
|
Phone OTP
|
|
268
286
|
</Chip>
|
|
269
|
-
<Chip active={authMethod === "guest"} onClick={() => setAuthMethod("guest")}>
|
|
270
|
-
Guest orders
|
|
271
|
-
</Chip>
|
|
272
287
|
</div>
|
|
273
288
|
|
|
274
|
-
<
|
|
289
|
+
<AuthField
|
|
275
290
|
label={authMethod === "phone_auth" ? "Phone" : "Email"}
|
|
276
291
|
name="identifier"
|
|
277
292
|
type={authMethod === "phone_auth" ? "tel" : "email"}
|
|
278
293
|
value={identifier}
|
|
279
294
|
onChange={(e) => setIdentifier(e.target.value)}
|
|
280
295
|
autoComplete={authMethod === "phone_auth" ? "tel" : "email"}
|
|
296
|
+
placeholder={
|
|
297
|
+
authMethod === "phone_auth" ? "Enter your phone" : "Enter your email"
|
|
298
|
+
}
|
|
281
299
|
/>
|
|
282
300
|
|
|
283
301
|
<p className="text-xs text-muted">
|
|
284
|
-
|
|
285
|
-
? "We will send a one-time code to view your guest orders."
|
|
286
|
-
: "We will send a one-time code to sign in or create your account."}
|
|
302
|
+
We will send a one-time code to sign in or create your account.
|
|
287
303
|
</p>
|
|
288
304
|
|
|
289
|
-
{error && !showOtpModal &&
|
|
305
|
+
{error && !showOtpModal && (
|
|
306
|
+
<p className="text-sm text-brand-sale" role="alert">
|
|
307
|
+
{error}
|
|
308
|
+
</p>
|
|
309
|
+
)}
|
|
290
310
|
|
|
291
311
|
<button
|
|
292
312
|
type="submit"
|
|
293
313
|
disabled={pending || !identifier.trim()}
|
|
294
|
-
className=
|
|
314
|
+
className={`${auth.btnPrimary} ${auth.btnShine}`}
|
|
295
315
|
>
|
|
296
|
-
|
|
316
|
+
<span className="relative z-[1]">
|
|
317
|
+
{pending ? "Sending…" : "Send verification code"}
|
|
318
|
+
</span>
|
|
297
319
|
</button>
|
|
298
320
|
</form>
|
|
299
321
|
|
|
300
322
|
<OtpComponent
|
|
301
323
|
open={showOtpModal}
|
|
302
324
|
preventClose
|
|
303
|
-
title={
|
|
325
|
+
title={authMethod === "phone_auth" ? "Verify your phone" : "Verify your email"}
|
|
304
326
|
description={`Enter the 6-digit code sent to ${identifier || "your contact"}.`}
|
|
305
327
|
otp={otp}
|
|
306
328
|
onOtpChange={setOtp}
|
|
@@ -310,32 +332,22 @@ function OtpLoginForm({
|
|
|
310
332
|
onVerify={handleVerifyOtp}
|
|
311
333
|
onResend={handleResendOtp}
|
|
312
334
|
extraContent={
|
|
313
|
-
isNewUser
|
|
335
|
+
isNewUser ? (
|
|
314
336
|
<div className="grid grid-cols-2 gap-3 mb-6">
|
|
315
|
-
<
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
Last name
|
|
330
|
-
</span>
|
|
331
|
-
<input
|
|
332
|
-
type="text"
|
|
333
|
-
value={lastName}
|
|
334
|
-
onChange={(e) => setLastName(e.target.value)}
|
|
335
|
-
required
|
|
336
|
-
className="w-full border border-cart-border rounded px-3 py-2.5 text-sm bg-surface focus:outline-none focus:border-brand-accent"
|
|
337
|
-
/>
|
|
338
|
-
</label>
|
|
337
|
+
<AuthField
|
|
338
|
+
label="First name"
|
|
339
|
+
name="first_name"
|
|
340
|
+
value={firstName}
|
|
341
|
+
onChange={(e) => setFirstName(e.target.value)}
|
|
342
|
+
required
|
|
343
|
+
/>
|
|
344
|
+
<AuthField
|
|
345
|
+
label="Last name"
|
|
346
|
+
name="last_name"
|
|
347
|
+
value={lastName}
|
|
348
|
+
onChange={(e) => setLastName(e.target.value)}
|
|
349
|
+
required
|
|
350
|
+
/>
|
|
339
351
|
</div>
|
|
340
352
|
) : undefined
|
|
341
353
|
}
|
|
@@ -344,68 +356,120 @@ function OtpLoginForm({
|
|
|
344
356
|
)
|
|
345
357
|
}
|
|
346
358
|
|
|
347
|
-
function
|
|
348
|
-
return (
|
|
349
|
-
typeof err === "object" &&
|
|
350
|
-
err !== null &&
|
|
351
|
-
"digest" in err &&
|
|
352
|
-
String((err as { digest?: string }).digest || "").includes("NEXT_REDIRECT")
|
|
353
|
-
)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function Chip({
|
|
357
|
-
active,
|
|
358
|
-
onClick,
|
|
359
|
-
children,
|
|
360
|
-
}: {
|
|
361
|
-
active: boolean
|
|
362
|
-
onClick: () => void
|
|
363
|
-
children: React.ReactNode
|
|
364
|
-
}) {
|
|
359
|
+
export function AuthDivider({ label }: { label: string }) {
|
|
365
360
|
return (
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
</button>
|
|
361
|
+
<div className="relative">
|
|
362
|
+
<div className="absolute inset-0 flex items-center">
|
|
363
|
+
<div className="w-full border-t border-brand-primary/10" />
|
|
364
|
+
</div>
|
|
365
|
+
<div className="relative flex justify-center items-center gap-2 text-[10px] font-semibold uppercase tracking-[0.28em]">
|
|
366
|
+
<span className="px-2 bg-surface text-brand-accent">◆</span>
|
|
367
|
+
<span className="px-1 bg-surface text-muted">{label}</span>
|
|
368
|
+
<span className="px-2 bg-surface text-brand-accent">◆</span>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
377
371
|
)
|
|
378
372
|
}
|
|
379
373
|
|
|
380
|
-
function
|
|
374
|
+
export function AuthField({
|
|
381
375
|
label,
|
|
382
376
|
name,
|
|
383
|
-
type,
|
|
377
|
+
type = "text",
|
|
384
378
|
autoComplete,
|
|
385
379
|
value,
|
|
386
380
|
onChange,
|
|
381
|
+
placeholder,
|
|
382
|
+
required = true,
|
|
387
383
|
}: {
|
|
388
384
|
label: string
|
|
389
|
-
name
|
|
390
|
-
type
|
|
385
|
+
name?: string
|
|
386
|
+
type?: string
|
|
391
387
|
autoComplete?: string
|
|
392
388
|
value?: string
|
|
393
389
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
390
|
+
placeholder?: string
|
|
391
|
+
required?: boolean
|
|
394
392
|
}) {
|
|
395
393
|
return (
|
|
396
394
|
<label className="block">
|
|
397
|
-
<span className=
|
|
398
|
-
{label}
|
|
399
|
-
</span>
|
|
395
|
+
<span className={`${auth.fieldLabel} mb-1.5 block`}>{label}</span>
|
|
400
396
|
<input
|
|
401
397
|
name={name}
|
|
402
398
|
type={type}
|
|
403
399
|
autoComplete={autoComplete}
|
|
404
400
|
value={value}
|
|
405
401
|
onChange={onChange}
|
|
406
|
-
|
|
407
|
-
|
|
402
|
+
placeholder={placeholder}
|
|
403
|
+
required={required}
|
|
404
|
+
className={auth.input}
|
|
408
405
|
/>
|
|
409
406
|
</label>
|
|
410
407
|
)
|
|
411
408
|
}
|
|
409
|
+
|
|
410
|
+
export function Ornament() {
|
|
411
|
+
return (
|
|
412
|
+
<div className="flex items-center justify-center gap-2 mb-3" aria-hidden>
|
|
413
|
+
<span className="h-px w-6 bg-brand-accent/70" />
|
|
414
|
+
<span className="text-brand-accent text-[7px] leading-none">◆</span>
|
|
415
|
+
<span className="h-px w-6 bg-brand-accent/70" />
|
|
416
|
+
</div>
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function Chip({
|
|
421
|
+
active,
|
|
422
|
+
onClick,
|
|
423
|
+
children,
|
|
424
|
+
}: {
|
|
425
|
+
active: boolean
|
|
426
|
+
onClick: () => void
|
|
427
|
+
children: React.ReactNode
|
|
428
|
+
}) {
|
|
429
|
+
return (
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
onClick={onClick}
|
|
433
|
+
className={active ? auth.chipActive : auth.chipIdle}
|
|
434
|
+
>
|
|
435
|
+
{children}
|
|
436
|
+
</button>
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function isNextRedirect(err: unknown) {
|
|
441
|
+
return (
|
|
442
|
+
typeof err === "object" &&
|
|
443
|
+
err !== null &&
|
|
444
|
+
"digest" in err &&
|
|
445
|
+
String((err as { digest?: string }).digest || "").includes("NEXT_REDIRECT")
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function GuestPackageIcon() {
|
|
450
|
+
return (
|
|
451
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden>
|
|
452
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" strokeLinecap="round" strokeLinejoin="round" />
|
|
453
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96" strokeLinecap="round" strokeLinejoin="round" />
|
|
454
|
+
<line x1="12" y1="22.08" x2="12" y2="12" strokeLinecap="round" />
|
|
455
|
+
</svg>
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function EyeIcon() {
|
|
460
|
+
return (
|
|
461
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden>
|
|
462
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
463
|
+
<circle cx="12" cy="12" r="3" />
|
|
464
|
+
</svg>
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function EyeOffIcon() {
|
|
469
|
+
return (
|
|
470
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden>
|
|
471
|
+
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
|
|
472
|
+
<line x1="1" y1="1" x2="23" y2="23" />
|
|
473
|
+
</svg>
|
|
474
|
+
)
|
|
475
|
+
}
|