@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.
- package/package.json +1 -1
- package/src/account-addresses.tsx +14 -13
- package/src/account-guest-orders.tsx +47 -12
- package/src/account-orders.tsx +90 -38
- package/src/account-overview.tsx +96 -74
- package/src/account-payment-methods.tsx +155 -63
- package/src/account-profile.tsx +12 -11
- package/src/account-theme.ts +79 -0
- 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 +96 -0
- package/src/login-form.tsx +207 -146
- package/src/payment-methods-actions.ts +214 -0
- package/src/register-form.tsx +77 -57
- package/src/segment.tsx +144 -50
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,19 +253,6 @@ 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
|
}
|
|
@@ -249,13 +271,6 @@ function OtpLoginForm({
|
|
|
249
271
|
}
|
|
250
272
|
}
|
|
251
273
|
|
|
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
274
|
return (
|
|
260
275
|
<>
|
|
261
276
|
<form onSubmit={handleSendOtp} className="space-y-4">
|
|
@@ -266,41 +281,45 @@ function OtpLoginForm({
|
|
|
266
281
|
<Chip active={authMethod === "phone_auth"} onClick={() => setAuthMethod("phone_auth")}>
|
|
267
282
|
Phone OTP
|
|
268
283
|
</Chip>
|
|
269
|
-
<Chip active={authMethod === "guest"} onClick={() => setAuthMethod("guest")}>
|
|
270
|
-
Guest orders
|
|
271
|
-
</Chip>
|
|
272
284
|
</div>
|
|
273
285
|
|
|
274
|
-
<
|
|
286
|
+
<AuthField
|
|
275
287
|
label={authMethod === "phone_auth" ? "Phone" : "Email"}
|
|
276
288
|
name="identifier"
|
|
277
289
|
type={authMethod === "phone_auth" ? "tel" : "email"}
|
|
278
290
|
value={identifier}
|
|
279
291
|
onChange={(e) => setIdentifier(e.target.value)}
|
|
280
292
|
autoComplete={authMethod === "phone_auth" ? "tel" : "email"}
|
|
293
|
+
placeholder={
|
|
294
|
+
authMethod === "phone_auth" ? "Enter your phone" : "Enter your email"
|
|
295
|
+
}
|
|
281
296
|
/>
|
|
282
297
|
|
|
283
298
|
<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."}
|
|
299
|
+
We will send a one-time code to sign in or create your account.
|
|
287
300
|
</p>
|
|
288
301
|
|
|
289
|
-
{error && !showOtpModal &&
|
|
302
|
+
{error && !showOtpModal && (
|
|
303
|
+
<p className="text-sm text-brand-sale" role="alert">
|
|
304
|
+
{error}
|
|
305
|
+
</p>
|
|
306
|
+
)}
|
|
290
307
|
|
|
291
308
|
<button
|
|
292
309
|
type="submit"
|
|
293
310
|
disabled={pending || !identifier.trim()}
|
|
294
|
-
className=
|
|
311
|
+
className={`${auth.btnPrimary} ${auth.btnShine}`}
|
|
295
312
|
>
|
|
296
|
-
|
|
313
|
+
<span className="relative z-[1]">
|
|
314
|
+
{pending ? "Sending…" : "Send verification code"}
|
|
315
|
+
</span>
|
|
297
316
|
</button>
|
|
298
317
|
</form>
|
|
299
318
|
|
|
300
319
|
<OtpComponent
|
|
301
320
|
open={showOtpModal}
|
|
302
321
|
preventClose
|
|
303
|
-
title={
|
|
322
|
+
title={authMethod === "phone_auth" ? "Verify your phone" : "Verify your email"}
|
|
304
323
|
description={`Enter the 6-digit code sent to ${identifier || "your contact"}.`}
|
|
305
324
|
otp={otp}
|
|
306
325
|
onOtpChange={setOtp}
|
|
@@ -310,32 +329,22 @@ function OtpLoginForm({
|
|
|
310
329
|
onVerify={handleVerifyOtp}
|
|
311
330
|
onResend={handleResendOtp}
|
|
312
331
|
extraContent={
|
|
313
|
-
isNewUser
|
|
332
|
+
isNewUser ? (
|
|
314
333
|
<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>
|
|
334
|
+
<AuthField
|
|
335
|
+
label="First name"
|
|
336
|
+
name="first_name"
|
|
337
|
+
value={firstName}
|
|
338
|
+
onChange={(e) => setFirstName(e.target.value)}
|
|
339
|
+
required
|
|
340
|
+
/>
|
|
341
|
+
<AuthField
|
|
342
|
+
label="Last name"
|
|
343
|
+
name="last_name"
|
|
344
|
+
value={lastName}
|
|
345
|
+
onChange={(e) => setLastName(e.target.value)}
|
|
346
|
+
required
|
|
347
|
+
/>
|
|
339
348
|
</div>
|
|
340
349
|
) : undefined
|
|
341
350
|
}
|
|
@@ -344,68 +353,120 @@ function OtpLoginForm({
|
|
|
344
353
|
)
|
|
345
354
|
}
|
|
346
355
|
|
|
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
|
-
}) {
|
|
356
|
+
export function AuthDivider({ label }: { label: string }) {
|
|
365
357
|
return (
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
</button>
|
|
358
|
+
<div className="relative">
|
|
359
|
+
<div className="absolute inset-0 flex items-center">
|
|
360
|
+
<div className="w-full border-t border-brand-primary/10" />
|
|
361
|
+
</div>
|
|
362
|
+
<div className="relative flex justify-center items-center gap-2 text-[10px] font-semibold uppercase tracking-[0.28em]">
|
|
363
|
+
<span className="px-2 bg-surface text-brand-accent">◆</span>
|
|
364
|
+
<span className="px-1 bg-surface text-muted">{label}</span>
|
|
365
|
+
<span className="px-2 bg-surface text-brand-accent">◆</span>
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
377
368
|
)
|
|
378
369
|
}
|
|
379
370
|
|
|
380
|
-
function
|
|
371
|
+
export function AuthField({
|
|
381
372
|
label,
|
|
382
373
|
name,
|
|
383
|
-
type,
|
|
374
|
+
type = "text",
|
|
384
375
|
autoComplete,
|
|
385
376
|
value,
|
|
386
377
|
onChange,
|
|
378
|
+
placeholder,
|
|
379
|
+
required = true,
|
|
387
380
|
}: {
|
|
388
381
|
label: string
|
|
389
|
-
name
|
|
390
|
-
type
|
|
382
|
+
name?: string
|
|
383
|
+
type?: string
|
|
391
384
|
autoComplete?: string
|
|
392
385
|
value?: string
|
|
393
386
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
387
|
+
placeholder?: string
|
|
388
|
+
required?: boolean
|
|
394
389
|
}) {
|
|
395
390
|
return (
|
|
396
391
|
<label className="block">
|
|
397
|
-
<span className=
|
|
398
|
-
{label}
|
|
399
|
-
</span>
|
|
392
|
+
<span className={`${auth.fieldLabel} mb-1.5 block`}>{label}</span>
|
|
400
393
|
<input
|
|
401
394
|
name={name}
|
|
402
395
|
type={type}
|
|
403
396
|
autoComplete={autoComplete}
|
|
404
397
|
value={value}
|
|
405
398
|
onChange={onChange}
|
|
406
|
-
|
|
407
|
-
|
|
399
|
+
placeholder={placeholder}
|
|
400
|
+
required={required}
|
|
401
|
+
className={auth.input}
|
|
408
402
|
/>
|
|
409
403
|
</label>
|
|
410
404
|
)
|
|
411
405
|
}
|
|
406
|
+
|
|
407
|
+
export function Ornament() {
|
|
408
|
+
return (
|
|
409
|
+
<div className="flex items-center justify-center gap-2 mb-3" aria-hidden>
|
|
410
|
+
<span className="h-px w-6 bg-brand-accent/70" />
|
|
411
|
+
<span className="text-brand-accent text-[7px] leading-none">◆</span>
|
|
412
|
+
<span className="h-px w-6 bg-brand-accent/70" />
|
|
413
|
+
</div>
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function Chip({
|
|
418
|
+
active,
|
|
419
|
+
onClick,
|
|
420
|
+
children,
|
|
421
|
+
}: {
|
|
422
|
+
active: boolean
|
|
423
|
+
onClick: () => void
|
|
424
|
+
children: React.ReactNode
|
|
425
|
+
}) {
|
|
426
|
+
return (
|
|
427
|
+
<button
|
|
428
|
+
type="button"
|
|
429
|
+
onClick={onClick}
|
|
430
|
+
className={active ? auth.chipActive : auth.chipIdle}
|
|
431
|
+
>
|
|
432
|
+
{children}
|
|
433
|
+
</button>
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function isNextRedirect(err: unknown) {
|
|
438
|
+
return (
|
|
439
|
+
typeof err === "object" &&
|
|
440
|
+
err !== null &&
|
|
441
|
+
"digest" in err &&
|
|
442
|
+
String((err as { digest?: string }).digest || "").includes("NEXT_REDIRECT")
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function GuestPackageIcon() {
|
|
447
|
+
return (
|
|
448
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" aria-hidden>
|
|
449
|
+
<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" />
|
|
450
|
+
<polyline points="3.27 6.96 12 12.01 20.73 6.96" strokeLinecap="round" strokeLinejoin="round" />
|
|
451
|
+
<line x1="12" y1="22.08" x2="12" y2="12" strokeLinecap="round" />
|
|
452
|
+
</svg>
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function EyeIcon() {
|
|
457
|
+
return (
|
|
458
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden>
|
|
459
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
460
|
+
<circle cx="12" cy="12" r="3" />
|
|
461
|
+
</svg>
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function EyeOffIcon() {
|
|
466
|
+
return (
|
|
467
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden>
|
|
468
|
+
<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" />
|
|
469
|
+
<line x1="1" y1="1" x2="23" y2="23" />
|
|
470
|
+
</svg>
|
|
471
|
+
)
|
|
472
|
+
}
|