@atzentis/auth-react 0.0.13 → 0.0.14
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/dist/index.d.ts +663 -3
- package/dist/index.js +1435 -6
- package/dist/index.js.map +1 -1
- package/package.json +18 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { AuthClient } from '@atzentis/auth-sdk';
|
|
2
|
-
import { createContext, useState, useCallback, useMemo, useContext } from 'react';
|
|
3
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { AuthClient, AuthError, AuthErrorCode } from '@atzentis/auth-sdk';
|
|
2
|
+
import { createContext, useState, useCallback, useMemo, useContext, useEffect } from 'react';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, Card, DropdownMenuLabel } from '@atzentis/ui-shadcn';
|
|
5
|
+
import { FormSelect, FormInput, OTPInput, FormPasswordInput } from '@atzentis/ui-shared';
|
|
6
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
|
+
import { useForm } from 'react-hook-form';
|
|
8
|
+
import { z } from 'zod';
|
|
4
9
|
|
|
5
10
|
var AuthContext = createContext(null);
|
|
11
|
+
var LocalizationContext = createContext({
|
|
12
|
+
overrides: null,
|
|
13
|
+
localizeErrors: true
|
|
14
|
+
});
|
|
6
15
|
function AuthProvider({
|
|
7
16
|
apiKey,
|
|
8
17
|
baseUrl,
|
|
@@ -15,7 +24,9 @@ function AuthProvider({
|
|
|
15
24
|
onNewDeviceAlert,
|
|
16
25
|
children,
|
|
17
26
|
initialUser,
|
|
18
|
-
storage
|
|
27
|
+
storage,
|
|
28
|
+
localization,
|
|
29
|
+
localizeErrors = true
|
|
19
30
|
}) {
|
|
20
31
|
const [client] = useState(() => {
|
|
21
32
|
const handleSessionExpired = () => {
|
|
@@ -58,7 +69,376 @@ function AuthProvider({
|
|
|
58
69
|
}),
|
|
59
70
|
[client, user, session, isLoading, error, clearError]
|
|
60
71
|
);
|
|
61
|
-
|
|
72
|
+
const localizationValue = useMemo(
|
|
73
|
+
() => ({ overrides: localization ?? null, localizeErrors }),
|
|
74
|
+
[localization, localizeErrors]
|
|
75
|
+
);
|
|
76
|
+
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children: /* @__PURE__ */ jsx(LocalizationContext.Provider, { value: localizationValue, children }) });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/localization/error-codes.ts
|
|
80
|
+
var AUTH_ERROR_CODES = {
|
|
81
|
+
INVALID_CREDENTIALS: "Invalid email or password",
|
|
82
|
+
EMAIL_NOT_VERIFIED: "Email address is not verified",
|
|
83
|
+
TWO_FACTOR_REQUIRED: "Two-factor authentication is required",
|
|
84
|
+
INVALID_PHONE_NUMBER: "Invalid phone number",
|
|
85
|
+
OTP_EXPIRED: "Verification code has expired",
|
|
86
|
+
OTP_INVALID: "Invalid verification code",
|
|
87
|
+
OTP_MAX_ATTEMPTS: "Too many verification attempts",
|
|
88
|
+
PHONE_NUMBER_IN_USE: "Phone number is already in use",
|
|
89
|
+
FRAUD_DETECTED: "Suspicious activity detected",
|
|
90
|
+
USERNAME_TAKEN: "Username is already taken",
|
|
91
|
+
USERNAME_INVALID: "Invalid username",
|
|
92
|
+
USERNAME_RESERVED: "This username is reserved",
|
|
93
|
+
DEVICE_NOT_FOUND: "Device not found",
|
|
94
|
+
LOGIN_EVENT_NOT_FOUND: "Login event not found",
|
|
95
|
+
REAUTH_REQUIRED: "Please sign in again to continue",
|
|
96
|
+
MAX_SESSIONS_EXCEEDED: "Maximum number of sessions exceeded",
|
|
97
|
+
CAPTCHA_REQUIRED: "CAPTCHA verification is required",
|
|
98
|
+
CAPTCHA_INVALID: "CAPTCHA verification failed",
|
|
99
|
+
PASSWORD_BREACHED: "This password has been found in a data breach",
|
|
100
|
+
SESSION_EXPIRED: "Session expired",
|
|
101
|
+
TOKEN_EXPIRED: "Token has expired",
|
|
102
|
+
INVALID_TOKEN: "Invalid token",
|
|
103
|
+
UNAUTHORIZED_ROLE: "You do not have permission to perform this action",
|
|
104
|
+
ORGANIZATION_NOT_FOUND: "Organization not found",
|
|
105
|
+
PERMISSION_DENIED: "Permission denied",
|
|
106
|
+
RATE_LIMITED: "Too many requests. Please try again later.",
|
|
107
|
+
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
108
|
+
TIMEOUT: "Request timed out. Please try again.",
|
|
109
|
+
USER_NOT_FOUND: "User not found",
|
|
110
|
+
USER_ALREADY_EXISTS: "A user with this email already exists",
|
|
111
|
+
INVALID_EMAIL: "Invalid email address",
|
|
112
|
+
WEAK_PASSWORD: "Password is too weak",
|
|
113
|
+
ORG_LIMIT_EXCEEDED: "Organization limit exceeded",
|
|
114
|
+
MEMBER_LIMIT_EXCEEDED: "Member limit exceeded",
|
|
115
|
+
KEY_NOT_FOUND: "API key not found",
|
|
116
|
+
KEY_REVOKED: "API key has been revoked",
|
|
117
|
+
KEY_EXPIRED: "API key has expired"
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// src/localization/auth-localization.ts
|
|
121
|
+
var authLocalization = {
|
|
122
|
+
// --- General / Shared ---
|
|
123
|
+
/** @default "Continue" */
|
|
124
|
+
CONTINUE: "Continue",
|
|
125
|
+
/** @default "Cancel" */
|
|
126
|
+
CANCEL: "Cancel",
|
|
127
|
+
/** @default "Save" */
|
|
128
|
+
SAVE: "Save",
|
|
129
|
+
/** @default "Delete" */
|
|
130
|
+
DELETE: "Delete",
|
|
131
|
+
/** @default "Update" */
|
|
132
|
+
UPDATE: "Update",
|
|
133
|
+
/** @default "Remove" */
|
|
134
|
+
REMOVE: "Remove",
|
|
135
|
+
/** @default "Done" */
|
|
136
|
+
DONE: "Done",
|
|
137
|
+
/** @default "Go back" */
|
|
138
|
+
GO_BACK: "Go back",
|
|
139
|
+
/** @default "Loading..." */
|
|
140
|
+
LOADING: "Loading...",
|
|
141
|
+
/** @default "Load more" */
|
|
142
|
+
LOAD_MORE: "Load more",
|
|
143
|
+
/** @default "Unknown" */
|
|
144
|
+
UNKNOWN: "Unknown",
|
|
145
|
+
/** @default "Request failed" */
|
|
146
|
+
REQUEST_FAILED: "Request failed",
|
|
147
|
+
/** @default "Updated successfully" */
|
|
148
|
+
UPDATED_SUCCESSFULLY: "Updated successfully",
|
|
149
|
+
// --- Sign In (LoginForm) ---
|
|
150
|
+
/** @default "Sign in" */
|
|
151
|
+
SIGN_IN: "Sign in",
|
|
152
|
+
/** @default "Sign in" */
|
|
153
|
+
SIGN_IN_ACTION: "Sign in",
|
|
154
|
+
/** @default "Enter your email below to sign in" */
|
|
155
|
+
SIGN_IN_DESCRIPTION: "Enter your email below to sign in",
|
|
156
|
+
/** @default "Enter your username or email to sign in" */
|
|
157
|
+
SIGN_IN_USERNAME_DESCRIPTION: "Enter your username or email to sign in",
|
|
158
|
+
/** @default "Username or email" */
|
|
159
|
+
SIGN_IN_USERNAME_PLACEHOLDER: "Username or email",
|
|
160
|
+
/** @default "Signing in..." */
|
|
161
|
+
SIGNING_IN: "Signing in...",
|
|
162
|
+
/** @default "m@example.com" */
|
|
163
|
+
EMAIL_PLACEHOLDER: "m@example.com",
|
|
164
|
+
/** @default "Password" */
|
|
165
|
+
PASSWORD_PLACEHOLDER: "Password",
|
|
166
|
+
/** @default "Forgot your password?" */
|
|
167
|
+
FORGOT_PASSWORD_LINK: "Forgot your password?",
|
|
168
|
+
/** @default "Use phone instead" */
|
|
169
|
+
USE_PHONE_INSTEAD: "Use phone instead",
|
|
170
|
+
/** @default "Use email instead" */
|
|
171
|
+
USE_EMAIL_INSTEAD: "Use email instead",
|
|
172
|
+
/** @default "Or continue with" */
|
|
173
|
+
OR_CONTINUE_WITH: "Or continue with",
|
|
174
|
+
/** @default "Sign in with" */
|
|
175
|
+
SIGN_IN_WITH: "Sign in with",
|
|
176
|
+
/** @default "Don't have an account?" */
|
|
177
|
+
DONT_HAVE_AN_ACCOUNT: "Don't have an account?",
|
|
178
|
+
/** @default "Remember me" */
|
|
179
|
+
REMEMBER_ME: "Remember me",
|
|
180
|
+
// --- Sign Up (SignupForm) ---
|
|
181
|
+
/** @default "Sign up" */
|
|
182
|
+
SIGN_UP: "Sign up",
|
|
183
|
+
/** @default "Create account" */
|
|
184
|
+
SIGN_UP_ACTION: "Create account",
|
|
185
|
+
/** @default "Enter your information to create an account" */
|
|
186
|
+
SIGN_UP_DESCRIPTION: "Enter your information to create an account",
|
|
187
|
+
/** @default "Check your email for the verification link." */
|
|
188
|
+
SIGN_UP_EMAIL: "Check your email for the verification link.",
|
|
189
|
+
/** @default "Name" */
|
|
190
|
+
NAME_PLACEHOLDER: "Name",
|
|
191
|
+
/** @default "Please enter your full name." */
|
|
192
|
+
NAME_DESCRIPTION: "Please enter your full name.",
|
|
193
|
+
/** @default "Username" */
|
|
194
|
+
USERNAME_PLACEHOLDER: "Username",
|
|
195
|
+
/** @default "Username" */
|
|
196
|
+
USERNAME: "Username",
|
|
197
|
+
/** @default "Confirm password" */
|
|
198
|
+
CONFIRM_PASSWORD_PLACEHOLDER: "Confirm password",
|
|
199
|
+
/** @default "Already have an account?" */
|
|
200
|
+
ALREADY_HAVE_AN_ACCOUNT: "Already have an account?",
|
|
201
|
+
/** @default "I accept the terms and conditions" */
|
|
202
|
+
ACCEPT_TERMS: "I accept the terms and conditions",
|
|
203
|
+
/** @default "You must accept the terms" */
|
|
204
|
+
ACCEPT_TERMS_ERROR: "You must accept the terms",
|
|
205
|
+
/** @default "By continuing, you agree to the" */
|
|
206
|
+
BY_CONTINUING_YOU_AGREE: "By continuing, you agree to the",
|
|
207
|
+
/** @default "Terms of Service" */
|
|
208
|
+
TERMS_OF_SERVICE: "Terms of Service",
|
|
209
|
+
/** @default "Privacy Policy" */
|
|
210
|
+
PRIVACY_POLICY: "Privacy Policy",
|
|
211
|
+
// --- Phone Login (PhoneLoginForm) ---
|
|
212
|
+
/** @default "Phone number" */
|
|
213
|
+
PHONE_NUMBER_PLACEHOLDER: "Phone number",
|
|
214
|
+
/** @default "Country code" */
|
|
215
|
+
COUNTRY_CODE: "Country code",
|
|
216
|
+
/** @default "Send code" */
|
|
217
|
+
SEND_VERIFICATION_CODE: "Send code",
|
|
218
|
+
/** @default "Sending code..." */
|
|
219
|
+
SENDING_VERIFICATION_CODE: "Sending code...",
|
|
220
|
+
/** @default "One-Time Password" */
|
|
221
|
+
ONE_TIME_PASSWORD: "One-Time Password",
|
|
222
|
+
/** @default "Verification code" */
|
|
223
|
+
VERIFICATION_CODE_LABEL: "Verification code",
|
|
224
|
+
/** @default "Verify" */
|
|
225
|
+
VERIFY: "Verify",
|
|
226
|
+
/** @default "Verifying..." */
|
|
227
|
+
VERIFYING: "Verifying...",
|
|
228
|
+
/** @default "Code expires in" */
|
|
229
|
+
CODE_EXPIRES_IN: "Code expires in",
|
|
230
|
+
/** @default "Resend code" */
|
|
231
|
+
RESEND_CODE: "Resend code",
|
|
232
|
+
/** @default "Sign in with password" */
|
|
233
|
+
USE_PASSWORD_INSTEAD: "Sign in with password",
|
|
234
|
+
// --- OAuth ---
|
|
235
|
+
/** @default "Continue with" */
|
|
236
|
+
CONTINUE_WITH: "Continue with",
|
|
237
|
+
/** @default "Redirecting..." */
|
|
238
|
+
REDIRECTING: "Redirecting...",
|
|
239
|
+
// --- Magic Link ---
|
|
240
|
+
/** @default "Magic Link" */
|
|
241
|
+
MAGIC_LINK: "Magic Link",
|
|
242
|
+
/** @default "Send magic link" */
|
|
243
|
+
MAGIC_LINK_ACTION: "Send magic link",
|
|
244
|
+
/** @default "Enter your email to receive a magic link" */
|
|
245
|
+
MAGIC_LINK_DESCRIPTION: "Enter your email to receive a magic link",
|
|
246
|
+
/** @default "We sent a magic link to" */
|
|
247
|
+
MAGIC_LINK_EMAIL: "We sent a magic link to",
|
|
248
|
+
/** @default "Check your inbox and click the link to sign in." */
|
|
249
|
+
MAGIC_LINK_HINT: "Check your inbox and click the link to sign in.",
|
|
250
|
+
/** @default "Sending..." */
|
|
251
|
+
SENDING: "Sending...",
|
|
252
|
+
/** @default "Resend" */
|
|
253
|
+
RESEND: "Resend",
|
|
254
|
+
/** @default "Resending..." */
|
|
255
|
+
RESENDING: "Resending...",
|
|
256
|
+
// --- Password Management ---
|
|
257
|
+
/** @default "Password" */
|
|
258
|
+
PASSWORD: "Password",
|
|
259
|
+
/** @default "New Password" */
|
|
260
|
+
NEW_PASSWORD: "New Password",
|
|
261
|
+
/** @default "Current Password" */
|
|
262
|
+
CURRENT_PASSWORD: "Current Password",
|
|
263
|
+
/** @default "Change Password" */
|
|
264
|
+
CHANGE_PASSWORD: "Change Password",
|
|
265
|
+
/** @default "Enter your current and new password" */
|
|
266
|
+
CHANGE_PASSWORD_DESCRIPTION: "Enter your current and new password",
|
|
267
|
+
/** @default "Password has been changed" */
|
|
268
|
+
CHANGE_PASSWORD_SUCCESS: "Password has been changed",
|
|
269
|
+
/** @default "Forgot Password" */
|
|
270
|
+
FORGOT_PASSWORD: "Forgot Password",
|
|
271
|
+
/** @default "Send reset link" */
|
|
272
|
+
FORGOT_PASSWORD_ACTION: "Send reset link",
|
|
273
|
+
/** @default "Enter your email to reset your password" */
|
|
274
|
+
FORGOT_PASSWORD_DESCRIPTION: "Enter your email to reset your password",
|
|
275
|
+
/** @default "Check your email for the reset link" */
|
|
276
|
+
FORGOT_PASSWORD_EMAIL: "Check your email for the reset link",
|
|
277
|
+
/** @default "Reset Password" */
|
|
278
|
+
RESET_PASSWORD: "Reset Password",
|
|
279
|
+
/** @default "Save new password" */
|
|
280
|
+
RESET_PASSWORD_ACTION: "Save new password",
|
|
281
|
+
/** @default "Password reset successfully" */
|
|
282
|
+
RESET_PASSWORD_SUCCESS: "Password reset successfully",
|
|
283
|
+
/** @default "Set Password" */
|
|
284
|
+
SET_PASSWORD: "Set Password",
|
|
285
|
+
/** @default "Click the button to receive an email to set your password" */
|
|
286
|
+
SET_PASSWORD_DESCRIPTION: "Click the button to receive an email to set your password",
|
|
287
|
+
// --- Devices & Sessions ---
|
|
288
|
+
/** @default "Current Session" */
|
|
289
|
+
CURRENT_SESSION: "Current Session",
|
|
290
|
+
/** @default "Current device" */
|
|
291
|
+
CURRENT_DEVICE: "Current device",
|
|
292
|
+
/** @default "Sessions" */
|
|
293
|
+
SESSIONS: "Sessions",
|
|
294
|
+
/** @default "Manage your active sessions" */
|
|
295
|
+
SESSIONS_DESCRIPTION: "Manage your active sessions",
|
|
296
|
+
/** @default "Trust" */
|
|
297
|
+
TRUST_DEVICE: "Trust",
|
|
298
|
+
/** @default "Untrust" */
|
|
299
|
+
UNTRUST_DEVICE: "Untrust",
|
|
300
|
+
/** @default "Loading devices..." */
|
|
301
|
+
LOADING_DEVICES: "Loading devices...",
|
|
302
|
+
/** @default "No devices found" */
|
|
303
|
+
NO_DEVICES: "No devices found",
|
|
304
|
+
/** @default "Last used:" */
|
|
305
|
+
LAST_USED: "Last used:",
|
|
306
|
+
/** @default "IP:" */
|
|
307
|
+
IP_LABEL: "IP:",
|
|
308
|
+
/** @default "Security" */
|
|
309
|
+
SECURITY: "Security",
|
|
310
|
+
/** @default "Session not fresh. Please sign in again." */
|
|
311
|
+
SESSION_NOT_FRESH: "Session not fresh. Please sign in again.",
|
|
312
|
+
// --- Login Activity & Risk ---
|
|
313
|
+
/** @default "Login Activity" */
|
|
314
|
+
LOGIN_ACTIVITY: "Login Activity",
|
|
315
|
+
/** @default "Recent login attempts" */
|
|
316
|
+
LOGIN_ACTIVITY_DESCRIPTION: "Recent login attempts",
|
|
317
|
+
/** @default "Low" */
|
|
318
|
+
RISK_LOW: "Low",
|
|
319
|
+
/** @default "Medium" */
|
|
320
|
+
RISK_MEDIUM: "Medium",
|
|
321
|
+
/** @default "High" */
|
|
322
|
+
RISK_HIGH: "High",
|
|
323
|
+
/** @default "This was me" */
|
|
324
|
+
MARK_RECOGNIZED: "This was me",
|
|
325
|
+
/** @default "Not me" */
|
|
326
|
+
REPORT_SUSPICIOUS: "Not me",
|
|
327
|
+
/** @default "Loading activity..." */
|
|
328
|
+
LOADING_ACTIVITY: "Loading activity...",
|
|
329
|
+
/** @default "No login activity found" */
|
|
330
|
+
NO_ACTIVITY: "No login activity found",
|
|
331
|
+
// --- Accounts & Switching ---
|
|
332
|
+
/** @default "Accounts" */
|
|
333
|
+
ACCOUNTS: "Accounts",
|
|
334
|
+
/** @default "Manage signed in accounts" */
|
|
335
|
+
ACCOUNTS_DESCRIPTION: "Manage signed in accounts",
|
|
336
|
+
/** @default "Add account" */
|
|
337
|
+
ADD_ACCOUNT: "Add account",
|
|
338
|
+
/** @default "Switch Account" */
|
|
339
|
+
SWITCH_ACCOUNT: "Switch Account",
|
|
340
|
+
/** @default "Select account" */
|
|
341
|
+
SELECT_ACCOUNT: "Select account",
|
|
342
|
+
/** @default "Personal Account" */
|
|
343
|
+
PERSONAL_ACCOUNT: "Personal Account",
|
|
344
|
+
/** @default "Sign out" */
|
|
345
|
+
SIGN_OUT: "Sign out",
|
|
346
|
+
// --- Organizations ---
|
|
347
|
+
/** @default "Organization" */
|
|
348
|
+
ORGANIZATION: "Organization",
|
|
349
|
+
/** @default "Organizations" */
|
|
350
|
+
ORGANIZATIONS: "Organizations",
|
|
351
|
+
/** @default "Manage your organizations" */
|
|
352
|
+
ORGANIZATIONS_DESCRIPTION: "Manage your organizations",
|
|
353
|
+
/** @default "Select organization" */
|
|
354
|
+
SELECT_ORGANIZATION: "Select organization",
|
|
355
|
+
/** @default "Create organization" */
|
|
356
|
+
CREATE_ORGANIZATION: "Create organization",
|
|
357
|
+
/** @default "Name" */
|
|
358
|
+
ORGANIZATION_NAME: "Name",
|
|
359
|
+
/** @default "Acme Inc." */
|
|
360
|
+
ORGANIZATION_NAME_PLACEHOLDER: "Acme Inc.",
|
|
361
|
+
/** @default "Leave Organization" */
|
|
362
|
+
LEAVE_ORGANIZATION: "Leave Organization",
|
|
363
|
+
/** @default "Delete Organization" */
|
|
364
|
+
DELETE_ORGANIZATION: "Delete Organization",
|
|
365
|
+
/** @default "Members" */
|
|
366
|
+
MEMBERS: "Members",
|
|
367
|
+
/** @default "Invite Member" */
|
|
368
|
+
INVITE_MEMBER: "Invite Member",
|
|
369
|
+
/** @default "Remove Member" */
|
|
370
|
+
REMOVE_MEMBER: "Remove Member",
|
|
371
|
+
/** @default "Role" */
|
|
372
|
+
ROLE: "Role",
|
|
373
|
+
/** @default "Admin" */
|
|
374
|
+
ADMIN: "Admin",
|
|
375
|
+
/** @default "Member" */
|
|
376
|
+
MEMBER: "Member",
|
|
377
|
+
/** @default "Owner" */
|
|
378
|
+
OWNER: "Owner",
|
|
379
|
+
// --- User Menu ---
|
|
380
|
+
/** @default "Settings" */
|
|
381
|
+
SETTINGS: "Settings",
|
|
382
|
+
/** @default "Account" */
|
|
383
|
+
ACCOUNT: "Account",
|
|
384
|
+
/** @default "Avatar" */
|
|
385
|
+
AVATAR: "Avatar",
|
|
386
|
+
/** @default "Click avatar to upload a custom one" */
|
|
387
|
+
AVATAR_DESCRIPTION: "Click avatar to upload a custom one",
|
|
388
|
+
/** @default "Delete Avatar" */
|
|
389
|
+
DELETE_AVATAR: "Delete Avatar",
|
|
390
|
+
/** @default "Upload Avatar" */
|
|
391
|
+
UPLOAD_AVATAR: "Upload Avatar",
|
|
392
|
+
/** @default "Email" */
|
|
393
|
+
EMAIL: "Email",
|
|
394
|
+
/** @default "Name" */
|
|
395
|
+
NAME: "Name",
|
|
396
|
+
// --- Two-Factor ---
|
|
397
|
+
/** @default "Two-Factor" */
|
|
398
|
+
TWO_FACTOR: "Two-Factor",
|
|
399
|
+
/** @default "Two-Factor Authentication" */
|
|
400
|
+
TWO_FACTOR_PROMPT: "Two-Factor Authentication",
|
|
401
|
+
/** @default "Enter your one-time password" */
|
|
402
|
+
TWO_FACTOR_DESCRIPTION: "Enter your one-time password",
|
|
403
|
+
/** @default "Verify code" */
|
|
404
|
+
TWO_FACTOR_ACTION: "Verify code",
|
|
405
|
+
/** @default "Two-factor authentication has been enabled" */
|
|
406
|
+
TWO_FACTOR_ENABLED: "Two-factor authentication has been enabled",
|
|
407
|
+
/** @default "Two-factor authentication has been disabled" */
|
|
408
|
+
TWO_FACTOR_DISABLED: "Two-factor authentication has been disabled",
|
|
409
|
+
/** @default "Enable Two-Factor" */
|
|
410
|
+
ENABLE_TWO_FACTOR: "Enable Two-Factor",
|
|
411
|
+
/** @default "Disable Two-Factor" */
|
|
412
|
+
DISABLE_TWO_FACTOR: "Disable Two-Factor",
|
|
413
|
+
/** @default "Backup Codes" */
|
|
414
|
+
BACKUP_CODES: "Backup Codes",
|
|
415
|
+
/** @default "Save these codes in a secure place" */
|
|
416
|
+
BACKUP_CODES_DESCRIPTION: "Save these codes in a secure place",
|
|
417
|
+
// --- Validation ---
|
|
418
|
+
/** @default "Passwords do not match" */
|
|
419
|
+
PASSWORDS_DO_NOT_MATCH: "Passwords do not match",
|
|
420
|
+
/** @default "Password is required" */
|
|
421
|
+
PASSWORD_REQUIRED: "Password is required",
|
|
422
|
+
/** @default "Email address is required" */
|
|
423
|
+
EMAIL_REQUIRED: "Email address is required",
|
|
424
|
+
/** @default "is required" */
|
|
425
|
+
IS_REQUIRED: "is required",
|
|
426
|
+
/** @default "is invalid" */
|
|
427
|
+
IS_INVALID: "is invalid",
|
|
428
|
+
// --- Error Codes ---
|
|
429
|
+
...AUTH_ERROR_CODES
|
|
430
|
+
};
|
|
431
|
+
function getLocalizedError(error, localization, localizeErrors) {
|
|
432
|
+
if (!localizeErrors) {
|
|
433
|
+
if (error instanceof Error && error.message) return error.message;
|
|
434
|
+
return authLocalization.REQUEST_FAILED;
|
|
435
|
+
}
|
|
436
|
+
if (error instanceof AuthError) {
|
|
437
|
+
const errorCode = error.code;
|
|
438
|
+
if (localization?.[errorCode]) return localization[errorCode];
|
|
439
|
+
}
|
|
440
|
+
if (error instanceof Error && error.message) return error.message;
|
|
441
|
+
return localization?.REQUEST_FAILED ?? authLocalization.REQUEST_FAILED;
|
|
62
442
|
}
|
|
63
443
|
function useAuthContext() {
|
|
64
444
|
const context = useContext(AuthContext);
|
|
@@ -251,6 +631,11 @@ function useAuth() {
|
|
|
251
631
|
clearError
|
|
252
632
|
};
|
|
253
633
|
}
|
|
634
|
+
function useAuthLocalization() {
|
|
635
|
+
const ctx = useContext(LocalizationContext);
|
|
636
|
+
const localization = ctx.overrides ? { ...authLocalization, ...ctx.overrides } : authLocalization;
|
|
637
|
+
return { localization, localizeErrors: ctx.localizeErrors };
|
|
638
|
+
}
|
|
254
639
|
function useDevices() {
|
|
255
640
|
const { client } = useAuthContext();
|
|
256
641
|
const [devices, setDevices] = useState([]);
|
|
@@ -748,6 +1133,1050 @@ function useUser() {
|
|
|
748
1133
|
};
|
|
749
1134
|
}
|
|
750
1135
|
|
|
751
|
-
|
|
1136
|
+
// src/helpers/initials.ts
|
|
1137
|
+
function getInitials(name) {
|
|
1138
|
+
return name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
|
|
1139
|
+
}
|
|
1140
|
+
function AccountSwitcher({
|
|
1141
|
+
showAddAccount = false,
|
|
1142
|
+
onSwitch,
|
|
1143
|
+
onAddAccount,
|
|
1144
|
+
maxAccounts = 5,
|
|
1145
|
+
className
|
|
1146
|
+
}) {
|
|
1147
|
+
const { deviceSessions, switchAccount, isLoading, refresh } = useSessions();
|
|
1148
|
+
const { localization } = useAuthLocalization();
|
|
1149
|
+
useEffect(() => {
|
|
1150
|
+
refresh();
|
|
1151
|
+
}, [refresh]);
|
|
1152
|
+
const handleSwitch = useCallback(
|
|
1153
|
+
async (sessionToken, user) => {
|
|
1154
|
+
await switchAccount(sessionToken);
|
|
1155
|
+
onSwitch?.(user);
|
|
1156
|
+
},
|
|
1157
|
+
[switchAccount, onSwitch]
|
|
1158
|
+
);
|
|
1159
|
+
const visibleSessions = deviceSessions.slice(0, maxAccounts);
|
|
1160
|
+
const activeSession = deviceSessions.find((s) => s.isActive);
|
|
1161
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
1162
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: isLoading, children: activeSession ? activeSession.user.name : localization.SELECT_ACCOUNT }) }),
|
|
1163
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
1164
|
+
visibleSessions.map((session) => /* @__PURE__ */ jsx(
|
|
1165
|
+
DropdownMenuItem,
|
|
1166
|
+
{
|
|
1167
|
+
onClick: () => handleSwitch(session.sessionToken, session.user),
|
|
1168
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1169
|
+
/* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 text-xs font-medium", children: session.user.avatar ? /* @__PURE__ */ jsx("img", { src: session.user.avatar, alt: "", className: "h-8 w-8 rounded-full" }) : getInitials(session.user.name) }),
|
|
1170
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
1171
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: session.user.name }),
|
|
1172
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: session.user.email })
|
|
1173
|
+
] }),
|
|
1174
|
+
session.isActive && /* @__PURE__ */ jsx("span", { className: "ml-auto text-sm", children: "\u2713" })
|
|
1175
|
+
] })
|
|
1176
|
+
},
|
|
1177
|
+
session.sessionToken
|
|
1178
|
+
)),
|
|
1179
|
+
showAddAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1180
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1181
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onAddAccount, children: localization.ADD_ACCOUNT })
|
|
1182
|
+
] })
|
|
1183
|
+
] })
|
|
1184
|
+
] }) });
|
|
1185
|
+
}
|
|
1186
|
+
function SessionGuard({
|
|
1187
|
+
children,
|
|
1188
|
+
fallback,
|
|
1189
|
+
loadingFallback = null,
|
|
1190
|
+
freshRequired = false,
|
|
1191
|
+
freshThreshold = 300,
|
|
1192
|
+
requiredRole,
|
|
1193
|
+
className
|
|
1194
|
+
}) {
|
|
1195
|
+
const { user, session, isAuthenticated, isLoading } = useSession();
|
|
1196
|
+
if (isLoading) {
|
|
1197
|
+
return className ? /* @__PURE__ */ jsx("div", { className, children: loadingFallback }) : /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
|
|
1198
|
+
}
|
|
1199
|
+
if (!isAuthenticated) {
|
|
1200
|
+
return className ? /* @__PURE__ */ jsx("div", { className, children: fallback }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
1201
|
+
}
|
|
1202
|
+
if (freshRequired && session) {
|
|
1203
|
+
const sessionAge = (Date.now() - new Date(session.createdAt).getTime()) / 1e3;
|
|
1204
|
+
if (sessionAge > freshThreshold) {
|
|
1205
|
+
return className ? /* @__PURE__ */ jsx("div", { className, children: fallback }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (requiredRole && user) {
|
|
1209
|
+
const userRole = user.role;
|
|
1210
|
+
if (userRole !== requiredRole) {
|
|
1211
|
+
return className ? /* @__PURE__ */ jsx("div", { className, children: fallback }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return className ? /* @__PURE__ */ jsx("div", { className, children }) : /* @__PURE__ */ jsx(Fragment, { children });
|
|
1215
|
+
}
|
|
1216
|
+
var DEFAULT_DEVICE_ICONS = {
|
|
1217
|
+
mobile: "\u{1F4F1}",
|
|
1218
|
+
desktop: "\u{1F4BB}",
|
|
1219
|
+
tablet: "\u{1F4DF}"
|
|
1220
|
+
};
|
|
1221
|
+
function DeviceList({
|
|
1222
|
+
showTrustToggle = false,
|
|
1223
|
+
showRemoveAction = false,
|
|
1224
|
+
onDeviceTrusted,
|
|
1225
|
+
onDeviceUntrusted,
|
|
1226
|
+
onDeviceRemoved,
|
|
1227
|
+
renderDeviceIcon,
|
|
1228
|
+
className
|
|
1229
|
+
}) {
|
|
1230
|
+
const { devices, isLoading, error, refresh, trustDevice, untrustDevice, removeDevice } = useDevices();
|
|
1231
|
+
const { localization } = useAuthLocalization();
|
|
1232
|
+
useEffect(() => {
|
|
1233
|
+
refresh();
|
|
1234
|
+
}, [refresh]);
|
|
1235
|
+
const handleTrustToggle = useCallback(
|
|
1236
|
+
async (device) => {
|
|
1237
|
+
if (device.isTrusted) {
|
|
1238
|
+
await untrustDevice(device.id);
|
|
1239
|
+
onDeviceUntrusted?.(device);
|
|
1240
|
+
} else {
|
|
1241
|
+
await trustDevice(device.id);
|
|
1242
|
+
onDeviceTrusted?.(device);
|
|
1243
|
+
}
|
|
1244
|
+
},
|
|
1245
|
+
[trustDevice, untrustDevice, onDeviceTrusted, onDeviceUntrusted]
|
|
1246
|
+
);
|
|
1247
|
+
const handleRemove = useCallback(
|
|
1248
|
+
async (device) => {
|
|
1249
|
+
await removeDevice(device.id);
|
|
1250
|
+
onDeviceRemoved?.(device);
|
|
1251
|
+
},
|
|
1252
|
+
[removeDevice, onDeviceRemoved]
|
|
1253
|
+
);
|
|
1254
|
+
if (isLoading && devices.length === 0) {
|
|
1255
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.LOADING_DEVICES }) });
|
|
1256
|
+
}
|
|
1257
|
+
if (error) {
|
|
1258
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-red-600", children: error.message }) });
|
|
1259
|
+
}
|
|
1260
|
+
if (devices.length === 0) {
|
|
1261
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.NO_DEVICES }) });
|
|
1262
|
+
}
|
|
1263
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 space-y-4", children: devices.map((device) => /* @__PURE__ */ jsxs(
|
|
1264
|
+
"div",
|
|
1265
|
+
{
|
|
1266
|
+
className: "flex items-start justify-between gap-4 rounded border p-4",
|
|
1267
|
+
children: [
|
|
1268
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
1269
|
+
/* @__PURE__ */ jsx("span", { className: "text-2xl", children: renderDeviceIcon ? renderDeviceIcon(device) : DEFAULT_DEVICE_ICONS[device.deviceType] ?? "\u{1F4BB}" }),
|
|
1270
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1271
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1272
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: device.name }),
|
|
1273
|
+
device.isCurrent && /* @__PURE__ */ jsx("span", { className: "rounded bg-blue-100 px-2 py-0.5 text-xs text-blue-700", children: localization.CURRENT_DEVICE })
|
|
1274
|
+
] }),
|
|
1275
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
|
|
1276
|
+
device.browser,
|
|
1277
|
+
" \xB7 ",
|
|
1278
|
+
device.os
|
|
1279
|
+
] }),
|
|
1280
|
+
(device.lastCity || device.lastCountry) && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: [device.lastCity, device.lastCountry].filter(Boolean).join(", ") }),
|
|
1281
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-400", children: [
|
|
1282
|
+
localization.LAST_USED,
|
|
1283
|
+
" ",
|
|
1284
|
+
device.lastUsedAt,
|
|
1285
|
+
" \xB7 ",
|
|
1286
|
+
localization.IP_LABEL,
|
|
1287
|
+
" ",
|
|
1288
|
+
device.lastIp
|
|
1289
|
+
] })
|
|
1290
|
+
] })
|
|
1291
|
+
] }),
|
|
1292
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1293
|
+
showTrustToggle && /* @__PURE__ */ jsx(
|
|
1294
|
+
Button,
|
|
1295
|
+
{
|
|
1296
|
+
type: "button",
|
|
1297
|
+
variant: "outline",
|
|
1298
|
+
size: "sm",
|
|
1299
|
+
onClick: () => handleTrustToggle(device),
|
|
1300
|
+
children: device.isTrusted ? localization.UNTRUST_DEVICE : localization.TRUST_DEVICE
|
|
1301
|
+
}
|
|
1302
|
+
),
|
|
1303
|
+
showRemoveAction && /* @__PURE__ */ jsx(
|
|
1304
|
+
Button,
|
|
1305
|
+
{
|
|
1306
|
+
type: "button",
|
|
1307
|
+
variant: "destructive",
|
|
1308
|
+
size: "sm",
|
|
1309
|
+
disabled: device.isCurrent,
|
|
1310
|
+
onClick: () => handleRemove(device),
|
|
1311
|
+
children: localization.REMOVE
|
|
1312
|
+
}
|
|
1313
|
+
)
|
|
1314
|
+
] })
|
|
1315
|
+
]
|
|
1316
|
+
},
|
|
1317
|
+
device.id
|
|
1318
|
+
)) }) });
|
|
1319
|
+
}
|
|
1320
|
+
function LoadingBoundary({
|
|
1321
|
+
children,
|
|
1322
|
+
spinner = /* @__PURE__ */ jsx("div", { children: "Loading..." }),
|
|
1323
|
+
delay = 0,
|
|
1324
|
+
className
|
|
1325
|
+
}) {
|
|
1326
|
+
const { isLoading } = useSession();
|
|
1327
|
+
const [showSpinner, setShowSpinner] = useState(delay === 0 && isLoading);
|
|
1328
|
+
useEffect(() => {
|
|
1329
|
+
if (!isLoading) {
|
|
1330
|
+
setShowSpinner(false);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (delay === 0) {
|
|
1334
|
+
setShowSpinner(true);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
const timer = setTimeout(() => {
|
|
1338
|
+
setShowSpinner(true);
|
|
1339
|
+
}, delay);
|
|
1340
|
+
return () => clearTimeout(timer);
|
|
1341
|
+
}, [isLoading, delay]);
|
|
1342
|
+
if (isLoading && showSpinner) {
|
|
1343
|
+
return className ? /* @__PURE__ */ jsx("div", { className, children: spinner }) : /* @__PURE__ */ jsx(Fragment, { children: spinner });
|
|
1344
|
+
}
|
|
1345
|
+
if (isLoading) {
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1349
|
+
}
|
|
1350
|
+
function SubmitButton({ loading, loadingLabel, actionLabel, className }) {
|
|
1351
|
+
return /* @__PURE__ */ jsx(Button, { type: "submit", disabled: loading, className: className ?? "w-full", children: loading ? loadingLabel : actionLabel });
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// src/helpers/risk-utils.ts
|
|
1355
|
+
function getRiskLevel(score) {
|
|
1356
|
+
if (score < 30) return "low";
|
|
1357
|
+
if (score < 60) return "medium";
|
|
1358
|
+
return "high";
|
|
1359
|
+
}
|
|
1360
|
+
var RISK_COLORS = {
|
|
1361
|
+
low: "text-green-600 bg-green-50",
|
|
1362
|
+
medium: "text-yellow-600 bg-yellow-50",
|
|
1363
|
+
high: "text-red-600 bg-red-50"
|
|
1364
|
+
};
|
|
1365
|
+
function LoginActivityFeed({
|
|
1366
|
+
limit = 20,
|
|
1367
|
+
showRiskBadge = false,
|
|
1368
|
+
showReportActions = false,
|
|
1369
|
+
filterByStatus,
|
|
1370
|
+
onSuspiciousReported,
|
|
1371
|
+
className
|
|
1372
|
+
}) {
|
|
1373
|
+
const {
|
|
1374
|
+
events,
|
|
1375
|
+
hasMore,
|
|
1376
|
+
isLoading,
|
|
1377
|
+
error,
|
|
1378
|
+
refresh,
|
|
1379
|
+
loadMore,
|
|
1380
|
+
filter,
|
|
1381
|
+
markRecognized,
|
|
1382
|
+
reportSuspicious
|
|
1383
|
+
} = useLoginActivity({ limit });
|
|
1384
|
+
const { localization } = useAuthLocalization();
|
|
1385
|
+
useEffect(() => {
|
|
1386
|
+
if (filterByStatus) {
|
|
1387
|
+
filter({ status: filterByStatus, limit });
|
|
1388
|
+
}
|
|
1389
|
+
refresh();
|
|
1390
|
+
}, [refresh, filter, filterByStatus, limit]);
|
|
1391
|
+
const handleMarkRecognized = useCallback(
|
|
1392
|
+
async (event) => {
|
|
1393
|
+
await markRecognized(event.id);
|
|
1394
|
+
},
|
|
1395
|
+
[markRecognized]
|
|
1396
|
+
);
|
|
1397
|
+
const handleReportSuspicious = useCallback(
|
|
1398
|
+
async (event) => {
|
|
1399
|
+
await reportSuspicious(event.id);
|
|
1400
|
+
onSuspiciousReported?.(event);
|
|
1401
|
+
},
|
|
1402
|
+
[reportSuspicious, onSuspiciousReported]
|
|
1403
|
+
);
|
|
1404
|
+
if (isLoading && events.length === 0) {
|
|
1405
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.LOADING_ACTIVITY }) });
|
|
1406
|
+
}
|
|
1407
|
+
if (error) {
|
|
1408
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-red-600", children: error.message }) });
|
|
1409
|
+
}
|
|
1410
|
+
if (events.length === 0) {
|
|
1411
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsx("div", { className: "p-6 text-center text-sm text-gray-500", children: localization.NO_ACTIVITY }) });
|
|
1412
|
+
}
|
|
1413
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
|
|
1414
|
+
events.map((event) => {
|
|
1415
|
+
const riskLevel = getRiskLevel(event.riskScore);
|
|
1416
|
+
return /* @__PURE__ */ jsxs(
|
|
1417
|
+
"div",
|
|
1418
|
+
{
|
|
1419
|
+
className: "flex items-start justify-between gap-4 rounded border p-4",
|
|
1420
|
+
children: [
|
|
1421
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1422
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1423
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: event.deviceName }),
|
|
1424
|
+
/* @__PURE__ */ jsx("span", { className: "rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600", children: event.status }),
|
|
1425
|
+
showRiskBadge && /* @__PURE__ */ jsx("span", { className: `rounded px-2 py-0.5 text-xs ${RISK_COLORS[riskLevel]}`, children: riskLevel })
|
|
1426
|
+
] }),
|
|
1427
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
|
|
1428
|
+
event.method,
|
|
1429
|
+
" \xB7 ",
|
|
1430
|
+
event.ip
|
|
1431
|
+
] }),
|
|
1432
|
+
(event.city || event.country) && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: [event.city, event.country].filter(Boolean).join(", ") }),
|
|
1433
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: event.createdAt })
|
|
1434
|
+
] }),
|
|
1435
|
+
showReportActions && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1436
|
+
/* @__PURE__ */ jsx(
|
|
1437
|
+
Button,
|
|
1438
|
+
{
|
|
1439
|
+
type: "button",
|
|
1440
|
+
variant: "outline",
|
|
1441
|
+
size: "sm",
|
|
1442
|
+
onClick: () => handleMarkRecognized(event),
|
|
1443
|
+
children: localization.MARK_RECOGNIZED
|
|
1444
|
+
}
|
|
1445
|
+
),
|
|
1446
|
+
/* @__PURE__ */ jsx(
|
|
1447
|
+
Button,
|
|
1448
|
+
{
|
|
1449
|
+
type: "button",
|
|
1450
|
+
variant: "destructive",
|
|
1451
|
+
size: "sm",
|
|
1452
|
+
onClick: () => handleReportSuspicious(event),
|
|
1453
|
+
children: localization.REPORT_SUSPICIOUS
|
|
1454
|
+
}
|
|
1455
|
+
)
|
|
1456
|
+
] })
|
|
1457
|
+
]
|
|
1458
|
+
},
|
|
1459
|
+
event.id
|
|
1460
|
+
);
|
|
1461
|
+
}),
|
|
1462
|
+
hasMore && /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", disabled: isLoading, onClick: loadMore, children: isLoading ? localization.LOADING : localization.LOAD_MORE }) })
|
|
1463
|
+
] }) });
|
|
1464
|
+
}
|
|
1465
|
+
function toAuthError(error) {
|
|
1466
|
+
if (error instanceof AuthError) return error;
|
|
1467
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1468
|
+
return new AuthError({
|
|
1469
|
+
code: AuthErrorCode.NetworkError,
|
|
1470
|
+
message,
|
|
1471
|
+
statusCode: 0
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
var credentialLoginSchema = z.object({
|
|
1475
|
+
identity: z.string().min(1, "Email or username is required"),
|
|
1476
|
+
password: z.string().min(1, "Password is required"),
|
|
1477
|
+
captchaToken: z.string().optional()
|
|
1478
|
+
});
|
|
1479
|
+
var phoneLoginSchema = z.object({
|
|
1480
|
+
countryCode: z.string(),
|
|
1481
|
+
phoneNumber: z.string().min(1, "Phone number is required")
|
|
1482
|
+
});
|
|
1483
|
+
var otpVerifySchema = z.object({
|
|
1484
|
+
code: z.string().length(6, "Code must be 6 digits")
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
// src/components/shared/utils.ts
|
|
1488
|
+
function isEmail(value) {
|
|
1489
|
+
return value.includes("@");
|
|
1490
|
+
}
|
|
1491
|
+
var PROVIDER_NAMES = {
|
|
1492
|
+
google: "Google",
|
|
1493
|
+
github: "GitHub",
|
|
1494
|
+
apple: "Apple",
|
|
1495
|
+
microsoft: "Microsoft",
|
|
1496
|
+
atzentis: "Atzentis"
|
|
1497
|
+
};
|
|
1498
|
+
function OAuthButton({
|
|
1499
|
+
provider,
|
|
1500
|
+
redirectUri,
|
|
1501
|
+
state,
|
|
1502
|
+
variant = "outline",
|
|
1503
|
+
size = "md",
|
|
1504
|
+
className
|
|
1505
|
+
}) {
|
|
1506
|
+
const { getOAuthUrl } = useAuth();
|
|
1507
|
+
const { localization } = useAuthLocalization();
|
|
1508
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1509
|
+
const sizeMap = {
|
|
1510
|
+
sm: "sm",
|
|
1511
|
+
md: "default",
|
|
1512
|
+
lg: "lg"
|
|
1513
|
+
};
|
|
1514
|
+
const providerName = PROVIDER_NAMES[provider];
|
|
1515
|
+
const handleClick = useCallback(async () => {
|
|
1516
|
+
setIsLoading(true);
|
|
1517
|
+
try {
|
|
1518
|
+
const url = await getOAuthUrl({ provider, redirectUri, state: state ?? "" });
|
|
1519
|
+
window.location.href = url;
|
|
1520
|
+
} catch {
|
|
1521
|
+
setIsLoading(false);
|
|
1522
|
+
}
|
|
1523
|
+
}, [getOAuthUrl, provider, redirectUri, state]);
|
|
1524
|
+
return /* @__PURE__ */ jsx(
|
|
1525
|
+
Button,
|
|
1526
|
+
{
|
|
1527
|
+
type: "button",
|
|
1528
|
+
variant: variant === "filled" ? "default" : "outline",
|
|
1529
|
+
size: sizeMap[size],
|
|
1530
|
+
disabled: isLoading,
|
|
1531
|
+
onClick: handleClick,
|
|
1532
|
+
"aria-label": `${localization.SIGN_IN_WITH} ${providerName}`,
|
|
1533
|
+
className,
|
|
1534
|
+
children: isLoading ? localization.REDIRECTING : `${localization.CONTINUE_WITH} ${providerName}`
|
|
1535
|
+
}
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
var phoneNumberSchema = z.object({
|
|
1539
|
+
countryCode: z.string(),
|
|
1540
|
+
phoneNumber: z.string().min(1, "Phone number is required")
|
|
1541
|
+
});
|
|
1542
|
+
var otpVerifySchema2 = z.object({
|
|
1543
|
+
code: z.string().length(6, "Code must be 6 digits")
|
|
1544
|
+
});
|
|
1545
|
+
var phonePasswordSchema = z.object({
|
|
1546
|
+
countryCode: z.string(),
|
|
1547
|
+
phoneNumber: z.string().min(1, "Phone number is required"),
|
|
1548
|
+
password: z.string().min(1, "Password is required")
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
// src/components/shared/country-codes.ts
|
|
1552
|
+
var COUNTRY_CODES = [
|
|
1553
|
+
{ code: "+30", country: "GR", name: "Greece", flag: "\u{1F1EC}\u{1F1F7}" },
|
|
1554
|
+
{ code: "+49", country: "DE", name: "Germany", flag: "\u{1F1E9}\u{1F1EA}" },
|
|
1555
|
+
{ code: "+1", country: "US", name: "United States", flag: "\u{1F1FA}\u{1F1F8}" },
|
|
1556
|
+
{ code: "+44", country: "GB", name: "United Kingdom", flag: "\u{1F1EC}\u{1F1E7}" },
|
|
1557
|
+
{ code: "+33", country: "FR", name: "France", flag: "\u{1F1EB}\u{1F1F7}" },
|
|
1558
|
+
{ code: "+39", country: "IT", name: "Italy", flag: "\u{1F1EE}\u{1F1F9}" },
|
|
1559
|
+
{ code: "+34", country: "ES", name: "Spain", flag: "\u{1F1EA}\u{1F1F8}" },
|
|
1560
|
+
{ code: "+31", country: "NL", name: "Netherlands", flag: "\u{1F1F3}\u{1F1F1}" },
|
|
1561
|
+
{ code: "+43", country: "AT", name: "Austria", flag: "\u{1F1E6}\u{1F1F9}" },
|
|
1562
|
+
{ code: "+41", country: "CH", name: "Switzerland", flag: "\u{1F1E8}\u{1F1ED}" },
|
|
1563
|
+
{ code: "+357", country: "CY", name: "Cyprus", flag: "\u{1F1E8}\u{1F1FE}" },
|
|
1564
|
+
{ code: "+90", country: "TR", name: "Turkey", flag: "\u{1F1F9}\u{1F1F7}" }
|
|
1565
|
+
];
|
|
1566
|
+
function PhoneLoginForm({
|
|
1567
|
+
onSuccess,
|
|
1568
|
+
onError,
|
|
1569
|
+
allowedCountryCodes = ["+30", "+49"],
|
|
1570
|
+
showPasswordFallback = false,
|
|
1571
|
+
className
|
|
1572
|
+
}) {
|
|
1573
|
+
const { sendOTP, verify, signIn, isSending, isVerifying, expiresIn } = usePhone();
|
|
1574
|
+
const { localization, localizeErrors } = useAuthLocalization();
|
|
1575
|
+
const [step, setStep] = useState("phone");
|
|
1576
|
+
const [formError, setFormError] = useState(null);
|
|
1577
|
+
const [fullPhoneNumber, setFullPhoneNumber] = useState("");
|
|
1578
|
+
const filteredCountryCodes = useMemo(
|
|
1579
|
+
() => COUNTRY_CODES.filter((c) => allowedCountryCodes.includes(c.code)),
|
|
1580
|
+
[allowedCountryCodes]
|
|
1581
|
+
);
|
|
1582
|
+
const phoneForm = useForm({
|
|
1583
|
+
resolver: zodResolver(phoneNumberSchema),
|
|
1584
|
+
defaultValues: {
|
|
1585
|
+
countryCode: filteredCountryCodes[0]?.code ?? "+30",
|
|
1586
|
+
phoneNumber: ""
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
const otpForm = useForm({
|
|
1590
|
+
resolver: zodResolver(otpVerifySchema2),
|
|
1591
|
+
defaultValues: { code: "" }
|
|
1592
|
+
});
|
|
1593
|
+
const [passwordValue, setPasswordValue] = useState("");
|
|
1594
|
+
const countryCodeOptions = filteredCountryCodes.map((c) => ({
|
|
1595
|
+
value: c.code,
|
|
1596
|
+
label: `${c.flag} ${c.name} (${c.code})`
|
|
1597
|
+
}));
|
|
1598
|
+
const handlePhoneSubmit = useCallback(
|
|
1599
|
+
async (data) => {
|
|
1600
|
+
setFormError(null);
|
|
1601
|
+
const phone = `${data.countryCode}${data.phoneNumber}`;
|
|
1602
|
+
setFullPhoneNumber(phone);
|
|
1603
|
+
try {
|
|
1604
|
+
await sendOTP(phone);
|
|
1605
|
+
setStep("otp");
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
1608
|
+
onError?.(toAuthError(err));
|
|
1609
|
+
}
|
|
1610
|
+
},
|
|
1611
|
+
[sendOTP, onError, localization, localizeErrors]
|
|
1612
|
+
);
|
|
1613
|
+
const handleOtpSubmit = useCallback(
|
|
1614
|
+
async (data) => {
|
|
1615
|
+
setFormError(null);
|
|
1616
|
+
try {
|
|
1617
|
+
const response = await verify(fullPhoneNumber, data.code);
|
|
1618
|
+
onSuccess(response.user);
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
1621
|
+
onError?.(toAuthError(err));
|
|
1622
|
+
}
|
|
1623
|
+
},
|
|
1624
|
+
[verify, fullPhoneNumber, onSuccess, onError, localization, localizeErrors]
|
|
1625
|
+
);
|
|
1626
|
+
const handleOtpComplete = useCallback(
|
|
1627
|
+
(code) => {
|
|
1628
|
+
otpForm.setValue("code", code);
|
|
1629
|
+
otpForm.handleSubmit(handleOtpSubmit)();
|
|
1630
|
+
},
|
|
1631
|
+
[otpForm, handleOtpSubmit]
|
|
1632
|
+
);
|
|
1633
|
+
const handlePasswordSubmit = useCallback(
|
|
1634
|
+
async (e) => {
|
|
1635
|
+
e.preventDefault();
|
|
1636
|
+
setFormError(null);
|
|
1637
|
+
try {
|
|
1638
|
+
const response = await signIn(fullPhoneNumber, passwordValue);
|
|
1639
|
+
onSuccess(response.user);
|
|
1640
|
+
} catch (err) {
|
|
1641
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
1642
|
+
onError?.(toAuthError(err));
|
|
1643
|
+
}
|
|
1644
|
+
},
|
|
1645
|
+
[signIn, fullPhoneNumber, passwordValue, onSuccess, onError, localization, localizeErrors]
|
|
1646
|
+
);
|
|
1647
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
|
|
1648
|
+
step === "phone" && /* @__PURE__ */ jsxs("form", { onSubmit: phoneForm.handleSubmit(handlePhoneSubmit), className: "space-y-4", children: [
|
|
1649
|
+
/* @__PURE__ */ jsx(
|
|
1650
|
+
FormSelect,
|
|
1651
|
+
{
|
|
1652
|
+
label: localization.COUNTRY_CODE,
|
|
1653
|
+
options: countryCodeOptions,
|
|
1654
|
+
value: phoneForm.watch("countryCode"),
|
|
1655
|
+
onValueChange: (value) => phoneForm.setValue("countryCode", value)
|
|
1656
|
+
}
|
|
1657
|
+
),
|
|
1658
|
+
/* @__PURE__ */ jsx(
|
|
1659
|
+
FormInput,
|
|
1660
|
+
{
|
|
1661
|
+
label: localization.PHONE_NUMBER_PLACEHOLDER,
|
|
1662
|
+
id: "phone-number",
|
|
1663
|
+
type: "tel",
|
|
1664
|
+
error: phoneForm.formState.errors.phoneNumber?.message,
|
|
1665
|
+
...phoneForm.register("phoneNumber")
|
|
1666
|
+
}
|
|
1667
|
+
),
|
|
1668
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
|
|
1669
|
+
/* @__PURE__ */ jsx(
|
|
1670
|
+
SubmitButton,
|
|
1671
|
+
{
|
|
1672
|
+
loading: isSending,
|
|
1673
|
+
loadingLabel: localization.SENDING_VERIFICATION_CODE,
|
|
1674
|
+
actionLabel: localization.SEND_VERIFICATION_CODE
|
|
1675
|
+
}
|
|
1676
|
+
)
|
|
1677
|
+
] }),
|
|
1678
|
+
step === "otp" && /* @__PURE__ */ jsxs("form", { onSubmit: otpForm.handleSubmit(handleOtpSubmit), className: "space-y-4", children: [
|
|
1679
|
+
/* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium", children: [
|
|
1680
|
+
localization.VERIFICATION_CODE_LABEL,
|
|
1681
|
+
/* @__PURE__ */ jsx(OTPInput, { length: 6, onComplete: handleOtpComplete, disabled: isVerifying })
|
|
1682
|
+
] }),
|
|
1683
|
+
expiresIn !== null && /* @__PURE__ */ jsxs("p", { className: "text-sm text-gray-500", children: [
|
|
1684
|
+
localization.CODE_EXPIRES_IN,
|
|
1685
|
+
" ",
|
|
1686
|
+
expiresIn,
|
|
1687
|
+
"s"
|
|
1688
|
+
] }),
|
|
1689
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
|
|
1690
|
+
/* @__PURE__ */ jsx(
|
|
1691
|
+
SubmitButton,
|
|
1692
|
+
{
|
|
1693
|
+
loading: isVerifying,
|
|
1694
|
+
loadingLabel: localization.VERIFYING,
|
|
1695
|
+
actionLabel: localization.VERIFY
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
showPasswordFallback && /* @__PURE__ */ jsx(
|
|
1699
|
+
Button,
|
|
1700
|
+
{
|
|
1701
|
+
variant: "link",
|
|
1702
|
+
type: "button",
|
|
1703
|
+
className: "p-0 h-auto text-sm",
|
|
1704
|
+
onClick: () => setStep("password"),
|
|
1705
|
+
children: localization.USE_PASSWORD_INSTEAD
|
|
1706
|
+
}
|
|
1707
|
+
)
|
|
1708
|
+
] }),
|
|
1709
|
+
step === "password" && /* @__PURE__ */ jsxs("form", { onSubmit: handlePasswordSubmit, className: "space-y-4", children: [
|
|
1710
|
+
/* @__PURE__ */ jsx(
|
|
1711
|
+
FormPasswordInput,
|
|
1712
|
+
{
|
|
1713
|
+
label: localization.PASSWORD,
|
|
1714
|
+
id: "phone-password",
|
|
1715
|
+
value: passwordValue,
|
|
1716
|
+
onChange: (e) => setPasswordValue(e.target.value)
|
|
1717
|
+
}
|
|
1718
|
+
),
|
|
1719
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
|
|
1720
|
+
/* @__PURE__ */ jsx(
|
|
1721
|
+
SubmitButton,
|
|
1722
|
+
{
|
|
1723
|
+
loading: isVerifying,
|
|
1724
|
+
loadingLabel: localization.SIGNING_IN,
|
|
1725
|
+
actionLabel: localization.SIGN_IN
|
|
1726
|
+
}
|
|
1727
|
+
)
|
|
1728
|
+
] })
|
|
1729
|
+
] }) });
|
|
1730
|
+
}
|
|
1731
|
+
function LoginForm({
|
|
1732
|
+
onSuccess,
|
|
1733
|
+
onError,
|
|
1734
|
+
enablePhone = false,
|
|
1735
|
+
enableForgotPassword = false,
|
|
1736
|
+
onForgotPasswordClick,
|
|
1737
|
+
className,
|
|
1738
|
+
...oauthProps
|
|
1739
|
+
}) {
|
|
1740
|
+
const oauthProviders = "oauthProviders" in oauthProps ? oauthProps.oauthProviders : void 0;
|
|
1741
|
+
const oauthRedirectUri = "oauthRedirectUri" in oauthProps ? oauthProps.oauthRedirectUri : "";
|
|
1742
|
+
const { login, loginWithUsername } = useAuth();
|
|
1743
|
+
const { localization, localizeErrors } = useAuthLocalization();
|
|
1744
|
+
const [mode, setMode] = useState("credential");
|
|
1745
|
+
const [formError, setFormError] = useState(null);
|
|
1746
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
1747
|
+
const {
|
|
1748
|
+
register,
|
|
1749
|
+
handleSubmit,
|
|
1750
|
+
watch,
|
|
1751
|
+
formState: { errors }
|
|
1752
|
+
} = useForm({
|
|
1753
|
+
resolver: zodResolver(credentialLoginSchema),
|
|
1754
|
+
defaultValues: { identity: "", password: "" }
|
|
1755
|
+
});
|
|
1756
|
+
const identityValue = watch("identity");
|
|
1757
|
+
const onSubmit = useCallback(
|
|
1758
|
+
async (data) => {
|
|
1759
|
+
setFormError(null);
|
|
1760
|
+
setIsSubmitting(true);
|
|
1761
|
+
try {
|
|
1762
|
+
if (isEmail(data.identity)) {
|
|
1763
|
+
const response = await login({
|
|
1764
|
+
email: data.identity,
|
|
1765
|
+
password: data.password,
|
|
1766
|
+
captchaToken: data.captchaToken
|
|
1767
|
+
});
|
|
1768
|
+
onSuccess(response.user);
|
|
1769
|
+
} else {
|
|
1770
|
+
const response = await loginWithUsername({
|
|
1771
|
+
username: data.identity,
|
|
1772
|
+
password: data.password,
|
|
1773
|
+
captchaToken: data.captchaToken
|
|
1774
|
+
});
|
|
1775
|
+
onSuccess(response.user);
|
|
1776
|
+
}
|
|
1777
|
+
} catch (err) {
|
|
1778
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
1779
|
+
onError?.(toAuthError(err));
|
|
1780
|
+
} finally {
|
|
1781
|
+
setIsSubmitting(false);
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1784
|
+
[login, loginWithUsername, onSuccess, onError, localization, localizeErrors]
|
|
1785
|
+
);
|
|
1786
|
+
const handleSwitchToPhone = useCallback(() => {
|
|
1787
|
+
setMode("phone");
|
|
1788
|
+
setFormError(null);
|
|
1789
|
+
}, []);
|
|
1790
|
+
const handleSwitchToCredential = useCallback(() => {
|
|
1791
|
+
setMode("credential");
|
|
1792
|
+
setFormError(null);
|
|
1793
|
+
}, []);
|
|
1794
|
+
if (mode === "phone") {
|
|
1795
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
|
|
1796
|
+
/* @__PURE__ */ jsx(PhoneLoginForm, { onSuccess, onError }),
|
|
1797
|
+
/* @__PURE__ */ jsx(
|
|
1798
|
+
Button,
|
|
1799
|
+
{
|
|
1800
|
+
variant: "link",
|
|
1801
|
+
type: "button",
|
|
1802
|
+
className: "p-0 h-auto text-sm",
|
|
1803
|
+
onClick: handleSwitchToCredential,
|
|
1804
|
+
children: localization.USE_EMAIL_INSTEAD
|
|
1805
|
+
}
|
|
1806
|
+
)
|
|
1807
|
+
] }) });
|
|
1808
|
+
}
|
|
1809
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
|
|
1810
|
+
/* @__PURE__ */ jsx(
|
|
1811
|
+
FormInput,
|
|
1812
|
+
{
|
|
1813
|
+
label: localization.SIGN_IN_USERNAME_PLACEHOLDER,
|
|
1814
|
+
id: "login-identity",
|
|
1815
|
+
type: "text",
|
|
1816
|
+
autoComplete: "username",
|
|
1817
|
+
error: errors.identity?.message,
|
|
1818
|
+
...register("identity")
|
|
1819
|
+
}
|
|
1820
|
+
),
|
|
1821
|
+
identityValue && /* @__PURE__ */ jsx(
|
|
1822
|
+
FormPasswordInput,
|
|
1823
|
+
{
|
|
1824
|
+
label: localization.PASSWORD,
|
|
1825
|
+
id: "login-password",
|
|
1826
|
+
autoComplete: "current-password",
|
|
1827
|
+
error: errors.password?.message,
|
|
1828
|
+
...register("password")
|
|
1829
|
+
}
|
|
1830
|
+
),
|
|
1831
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
|
|
1832
|
+
/* @__PURE__ */ jsx(
|
|
1833
|
+
SubmitButton,
|
|
1834
|
+
{
|
|
1835
|
+
loading: isSubmitting,
|
|
1836
|
+
loadingLabel: localization.SIGNING_IN,
|
|
1837
|
+
actionLabel: localization.SIGN_IN
|
|
1838
|
+
}
|
|
1839
|
+
),
|
|
1840
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
|
|
1841
|
+
enableForgotPassword && /* @__PURE__ */ jsx(
|
|
1842
|
+
Button,
|
|
1843
|
+
{
|
|
1844
|
+
variant: "link",
|
|
1845
|
+
type: "button",
|
|
1846
|
+
className: "p-0 h-auto",
|
|
1847
|
+
onClick: onForgotPasswordClick,
|
|
1848
|
+
children: localization.FORGOT_PASSWORD_LINK
|
|
1849
|
+
}
|
|
1850
|
+
),
|
|
1851
|
+
enablePhone && /* @__PURE__ */ jsx(
|
|
1852
|
+
Button,
|
|
1853
|
+
{
|
|
1854
|
+
variant: "link",
|
|
1855
|
+
type: "button",
|
|
1856
|
+
className: "p-0 h-auto",
|
|
1857
|
+
onClick: handleSwitchToPhone,
|
|
1858
|
+
children: localization.USE_PHONE_INSTEAD
|
|
1859
|
+
}
|
|
1860
|
+
)
|
|
1861
|
+
] }),
|
|
1862
|
+
oauthProviders && oauthProviders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1863
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center py-2", children: [
|
|
1864
|
+
/* @__PURE__ */ jsx("div", { className: "flex-grow border-t border-gray-300" }),
|
|
1865
|
+
/* @__PURE__ */ jsx("span", { className: "mx-4 text-sm text-gray-500", children: localization.OR_CONTINUE_WITH }),
|
|
1866
|
+
/* @__PURE__ */ jsx("div", { className: "flex-grow border-t border-gray-300" })
|
|
1867
|
+
] }),
|
|
1868
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: oauthProviders.map((provider) => /* @__PURE__ */ jsx(OAuthButton, { provider, redirectUri: oauthRedirectUri }, provider)) })
|
|
1869
|
+
] })
|
|
1870
|
+
] }) });
|
|
1871
|
+
}
|
|
1872
|
+
var magicLinkSchema = z.object({
|
|
1873
|
+
email: z.string().email("Invalid email address")
|
|
1874
|
+
});
|
|
1875
|
+
function maskEmail(email) {
|
|
1876
|
+
const [local, domain] = email.split("@");
|
|
1877
|
+
if (!local || !domain) return email;
|
|
1878
|
+
const masked = local.length <= 2 ? `${local[0]}***` : `${local[0]}***${local[local.length - 1]}`;
|
|
1879
|
+
return `${masked}@${domain}`;
|
|
1880
|
+
}
|
|
1881
|
+
function MagicLinkForm({ redirectUrl, onSuccess, onError, className }) {
|
|
1882
|
+
const { sendMagicLink } = useAuth();
|
|
1883
|
+
const { localization, localizeErrors } = useAuthLocalization();
|
|
1884
|
+
const [sent, setSent] = useState(false);
|
|
1885
|
+
const [sentEmail, setSentEmail] = useState("");
|
|
1886
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
1887
|
+
const [formError, setFormError] = useState(null);
|
|
1888
|
+
const {
|
|
1889
|
+
register,
|
|
1890
|
+
handleSubmit,
|
|
1891
|
+
formState: { errors },
|
|
1892
|
+
getValues
|
|
1893
|
+
} = useForm({
|
|
1894
|
+
resolver: zodResolver(magicLinkSchema),
|
|
1895
|
+
defaultValues: { email: "" }
|
|
1896
|
+
});
|
|
1897
|
+
const onSubmit = useCallback(
|
|
1898
|
+
async (data) => {
|
|
1899
|
+
setFormError(null);
|
|
1900
|
+
setIsSubmitting(true);
|
|
1901
|
+
try {
|
|
1902
|
+
await sendMagicLink({ email: data.email, redirectUrl });
|
|
1903
|
+
setSent(true);
|
|
1904
|
+
setSentEmail(data.email);
|
|
1905
|
+
onSuccess?.();
|
|
1906
|
+
} catch (err) {
|
|
1907
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
1908
|
+
onError?.(toAuthError(err));
|
|
1909
|
+
} finally {
|
|
1910
|
+
setIsSubmitting(false);
|
|
1911
|
+
}
|
|
1912
|
+
},
|
|
1913
|
+
[sendMagicLink, redirectUrl, onSuccess, onError, localization, localizeErrors]
|
|
1914
|
+
);
|
|
1915
|
+
const handleResend = useCallback(async () => {
|
|
1916
|
+
const email = getValues("email");
|
|
1917
|
+
setFormError(null);
|
|
1918
|
+
setIsSubmitting(true);
|
|
1919
|
+
try {
|
|
1920
|
+
await sendMagicLink({ email, redirectUrl });
|
|
1921
|
+
} catch (err) {
|
|
1922
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
1923
|
+
} finally {
|
|
1924
|
+
setIsSubmitting(false);
|
|
1925
|
+
}
|
|
1926
|
+
}, [sendMagicLink, redirectUrl, getValues, localization, localizeErrors]);
|
|
1927
|
+
if (sent) {
|
|
1928
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4 text-center", children: [
|
|
1929
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm", children: [
|
|
1930
|
+
localization.MAGIC_LINK_EMAIL,
|
|
1931
|
+
" ",
|
|
1932
|
+
/* @__PURE__ */ jsx("strong", { children: maskEmail(sentEmail) })
|
|
1933
|
+
] }),
|
|
1934
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: localization.MAGIC_LINK_HINT }),
|
|
1935
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", disabled: isSubmitting, onClick: handleResend, children: isSubmitting ? localization.RESENDING : localization.RESEND }),
|
|
1936
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError })
|
|
1937
|
+
] }) });
|
|
1938
|
+
}
|
|
1939
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
|
|
1940
|
+
/* @__PURE__ */ jsx(
|
|
1941
|
+
FormInput,
|
|
1942
|
+
{
|
|
1943
|
+
label: localization.EMAIL,
|
|
1944
|
+
id: "magic-link-email",
|
|
1945
|
+
type: "email",
|
|
1946
|
+
autoComplete: "email",
|
|
1947
|
+
error: errors.email?.message,
|
|
1948
|
+
...register("email")
|
|
1949
|
+
}
|
|
1950
|
+
),
|
|
1951
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
|
|
1952
|
+
/* @__PURE__ */ jsx(
|
|
1953
|
+
SubmitButton,
|
|
1954
|
+
{
|
|
1955
|
+
loading: isSubmitting,
|
|
1956
|
+
loadingLabel: localization.SENDING,
|
|
1957
|
+
actionLabel: localization.MAGIC_LINK_ACTION
|
|
1958
|
+
}
|
|
1959
|
+
)
|
|
1960
|
+
] }) });
|
|
1961
|
+
}
|
|
1962
|
+
var signupSchema = z.object({
|
|
1963
|
+
email: z.string().email("Invalid email address"),
|
|
1964
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
1965
|
+
name: z.string().min(1, "Name is required"),
|
|
1966
|
+
username: z.string().optional(),
|
|
1967
|
+
phoneNumber: z.string().optional(),
|
|
1968
|
+
organizationName: z.string().optional(),
|
|
1969
|
+
acceptTerms: z.boolean(),
|
|
1970
|
+
captchaToken: z.string().optional()
|
|
1971
|
+
});
|
|
1972
|
+
function SignupForm({
|
|
1973
|
+
onSuccess,
|
|
1974
|
+
onError,
|
|
1975
|
+
showUsernameField = false,
|
|
1976
|
+
showPhoneField = false,
|
|
1977
|
+
organizationName,
|
|
1978
|
+
acceptTermsRequired = false,
|
|
1979
|
+
onLoginClick,
|
|
1980
|
+
className
|
|
1981
|
+
}) {
|
|
1982
|
+
const { signup } = useAuth();
|
|
1983
|
+
const { localization, localizeErrors } = useAuthLocalization();
|
|
1984
|
+
const [formError, setFormError] = useState(null);
|
|
1985
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
1986
|
+
const {
|
|
1987
|
+
register,
|
|
1988
|
+
handleSubmit,
|
|
1989
|
+
formState: { errors }
|
|
1990
|
+
} = useForm({
|
|
1991
|
+
resolver: zodResolver(
|
|
1992
|
+
acceptTermsRequired ? signupSchema.refine((data) => data.acceptTerms === true, {
|
|
1993
|
+
message: localization.ACCEPT_TERMS_ERROR,
|
|
1994
|
+
path: ["acceptTerms"]
|
|
1995
|
+
}) : signupSchema
|
|
1996
|
+
),
|
|
1997
|
+
defaultValues: {
|
|
1998
|
+
email: "",
|
|
1999
|
+
password: "",
|
|
2000
|
+
name: "",
|
|
2001
|
+
username: "",
|
|
2002
|
+
phoneNumber: "",
|
|
2003
|
+
organizationName: organizationName ?? "",
|
|
2004
|
+
acceptTerms: false
|
|
2005
|
+
}
|
|
2006
|
+
});
|
|
2007
|
+
const onSubmit = useCallback(
|
|
2008
|
+
async (data) => {
|
|
2009
|
+
setFormError(null);
|
|
2010
|
+
setIsSubmitting(true);
|
|
2011
|
+
try {
|
|
2012
|
+
const response = await signup({
|
|
2013
|
+
email: data.email,
|
|
2014
|
+
password: data.password,
|
|
2015
|
+
name: data.name,
|
|
2016
|
+
username: data.username,
|
|
2017
|
+
organizationName: data.organizationName,
|
|
2018
|
+
acceptTerms: data.acceptTerms
|
|
2019
|
+
});
|
|
2020
|
+
onSuccess(response.user);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
setFormError(getLocalizedError(err, localization, localizeErrors));
|
|
2023
|
+
onError?.(toAuthError(err));
|
|
2024
|
+
} finally {
|
|
2025
|
+
setIsSubmitting(false);
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
[signup, onSuccess, onError, localization, localizeErrors]
|
|
2029
|
+
);
|
|
2030
|
+
return /* @__PURE__ */ jsx(Card, { className, children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit(onSubmit), className: "p-6 space-y-4", children: [
|
|
2031
|
+
/* @__PURE__ */ jsx(
|
|
2032
|
+
FormInput,
|
|
2033
|
+
{
|
|
2034
|
+
label: localization.NAME,
|
|
2035
|
+
id: "signup-name",
|
|
2036
|
+
type: "text",
|
|
2037
|
+
autoComplete: "name",
|
|
2038
|
+
error: errors.name?.message,
|
|
2039
|
+
...register("name")
|
|
2040
|
+
}
|
|
2041
|
+
),
|
|
2042
|
+
/* @__PURE__ */ jsx(
|
|
2043
|
+
FormInput,
|
|
2044
|
+
{
|
|
2045
|
+
label: localization.EMAIL,
|
|
2046
|
+
id: "signup-email",
|
|
2047
|
+
type: "email",
|
|
2048
|
+
autoComplete: "email",
|
|
2049
|
+
error: errors.email?.message,
|
|
2050
|
+
...register("email")
|
|
2051
|
+
}
|
|
2052
|
+
),
|
|
2053
|
+
/* @__PURE__ */ jsx(
|
|
2054
|
+
FormPasswordInput,
|
|
2055
|
+
{
|
|
2056
|
+
label: localization.PASSWORD,
|
|
2057
|
+
id: "signup-password",
|
|
2058
|
+
autoComplete: "new-password",
|
|
2059
|
+
error: errors.password?.message,
|
|
2060
|
+
...register("password")
|
|
2061
|
+
}
|
|
2062
|
+
),
|
|
2063
|
+
showUsernameField && /* @__PURE__ */ jsx(
|
|
2064
|
+
FormInput,
|
|
2065
|
+
{
|
|
2066
|
+
label: localization.USERNAME,
|
|
2067
|
+
id: "signup-username",
|
|
2068
|
+
type: "text",
|
|
2069
|
+
autoComplete: "username",
|
|
2070
|
+
error: errors.username?.message,
|
|
2071
|
+
...register("username")
|
|
2072
|
+
}
|
|
2073
|
+
),
|
|
2074
|
+
showPhoneField && /* @__PURE__ */ jsx(
|
|
2075
|
+
FormInput,
|
|
2076
|
+
{
|
|
2077
|
+
label: localization.PHONE_NUMBER_PLACEHOLDER,
|
|
2078
|
+
id: "signup-phone",
|
|
2079
|
+
type: "tel",
|
|
2080
|
+
autoComplete: "tel",
|
|
2081
|
+
error: errors.phoneNumber?.message,
|
|
2082
|
+
...register("phoneNumber")
|
|
2083
|
+
}
|
|
2084
|
+
),
|
|
2085
|
+
acceptTermsRequired && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2086
|
+
/* @__PURE__ */ jsx(
|
|
2087
|
+
"input",
|
|
2088
|
+
{
|
|
2089
|
+
id: "signup-terms",
|
|
2090
|
+
type: "checkbox",
|
|
2091
|
+
"aria-invalid": errors.acceptTerms ? true : void 0,
|
|
2092
|
+
...register("acceptTerms")
|
|
2093
|
+
}
|
|
2094
|
+
),
|
|
2095
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "signup-terms", className: "text-sm", children: localization.ACCEPT_TERMS }),
|
|
2096
|
+
errors.acceptTerms && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: errors.acceptTerms.message })
|
|
2097
|
+
] }),
|
|
2098
|
+
formError && /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "text-sm text-red-600", children: formError }),
|
|
2099
|
+
/* @__PURE__ */ jsx(
|
|
2100
|
+
SubmitButton,
|
|
2101
|
+
{
|
|
2102
|
+
loading: isSubmitting,
|
|
2103
|
+
loadingLabel: localization.SIGNING_IN,
|
|
2104
|
+
actionLabel: localization.SIGN_UP_ACTION
|
|
2105
|
+
}
|
|
2106
|
+
),
|
|
2107
|
+
onLoginClick && /* @__PURE__ */ jsxs("p", { className: "text-sm text-center", children: [
|
|
2108
|
+
localization.ALREADY_HAVE_AN_ACCOUNT,
|
|
2109
|
+
" ",
|
|
2110
|
+
/* @__PURE__ */ jsx(
|
|
2111
|
+
Button,
|
|
2112
|
+
{
|
|
2113
|
+
variant: "link",
|
|
2114
|
+
type: "button",
|
|
2115
|
+
className: "p-0 h-auto text-sm",
|
|
2116
|
+
onClick: onLoginClick,
|
|
2117
|
+
children: localization.SIGN_IN
|
|
2118
|
+
}
|
|
2119
|
+
)
|
|
2120
|
+
] })
|
|
2121
|
+
] }) });
|
|
2122
|
+
}
|
|
2123
|
+
function OrganizationMenu({
|
|
2124
|
+
showCreateNew = false,
|
|
2125
|
+
currentOrgId,
|
|
2126
|
+
onSwitch,
|
|
2127
|
+
onCreateNew,
|
|
2128
|
+
className
|
|
2129
|
+
}) {
|
|
2130
|
+
const { organizations, isLoading, refresh } = useOrganizations();
|
|
2131
|
+
const { localization } = useAuthLocalization();
|
|
2132
|
+
useEffect(() => {
|
|
2133
|
+
refresh();
|
|
2134
|
+
}, [refresh]);
|
|
2135
|
+
const currentOrg = organizations.find((o) => o.id === currentOrgId);
|
|
2136
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2137
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "outline", disabled: isLoading, children: currentOrg ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2138
|
+
currentOrg.logo ? /* @__PURE__ */ jsx("img", { src: currentOrg.logo, alt: "", className: "h-5 w-5 rounded" }) : /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded bg-gray-200 text-xs", children: getInitials(currentOrg.name) }),
|
|
2139
|
+
currentOrg.name
|
|
2140
|
+
] }) : localization.SELECT_ORGANIZATION }) }),
|
|
2141
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
2142
|
+
organizations.map((org) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => onSwitch?.(org), children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2143
|
+
org.logo ? /* @__PURE__ */ jsx("img", { src: org.logo, alt: "", className: "h-5 w-5 rounded" }) : /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 items-center justify-center rounded bg-gray-200 text-xs", children: getInitials(org.name) }),
|
|
2144
|
+
/* @__PURE__ */ jsx("span", { children: org.name }),
|
|
2145
|
+
org.id === currentOrgId && /* @__PURE__ */ jsx("span", { className: "ml-auto", children: "\u2713" })
|
|
2146
|
+
] }) }, org.id)),
|
|
2147
|
+
showCreateNew && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2148
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2149
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onCreateNew, children: localization.CREATE_ORGANIZATION })
|
|
2150
|
+
] })
|
|
2151
|
+
] })
|
|
2152
|
+
] }) });
|
|
2153
|
+
}
|
|
2154
|
+
function UserMenu({ showOrganization = false, menuItems, className }) {
|
|
2155
|
+
const { user } = useSession();
|
|
2156
|
+
const { logout } = useAuth();
|
|
2157
|
+
const { localization } = useAuthLocalization();
|
|
2158
|
+
const handleSignOut = useCallback(async () => {
|
|
2159
|
+
await logout();
|
|
2160
|
+
}, [logout]);
|
|
2161
|
+
if (!user) return null;
|
|
2162
|
+
return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
2163
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
2164
|
+
user.avatar ? /* @__PURE__ */ jsx("img", { src: user.avatar, alt: "", className: "h-8 w-8 rounded-full" }) : /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-gray-200 text-xs font-medium", children: getInitials(user.name) }),
|
|
2165
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: user.name })
|
|
2166
|
+
] }) }) }),
|
|
2167
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
2168
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
2169
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm", children: user.email }),
|
|
2170
|
+
showOrganization && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500", children: localization.ORGANIZATION })
|
|
2171
|
+
] }) }),
|
|
2172
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2173
|
+
menuItems?.map((item) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: item.onClick, children: item.label }, item.label)),
|
|
2174
|
+
menuItems && menuItems.length > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
2175
|
+
/* @__PURE__ */ jsx(DropdownMenuItem, { onClick: handleSignOut, children: localization.SIGN_OUT })
|
|
2176
|
+
] })
|
|
2177
|
+
] }) });
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
export { AUTH_ERROR_CODES, AccountSwitcher, AuthContext, AuthProvider, DeviceList, LoadingBoundary, LocalizationContext, LoginActivityFeed, LoginForm, MagicLinkForm, OAuthButton, OrganizationMenu, PhoneLoginForm, RISK_COLORS, SessionGuard, SignupForm, UserMenu, authLocalization, credentialLoginSchema, getInitials, getLocalizedError, getRiskLevel, magicLinkSchema, otpVerifySchema, phoneLoginSchema, phoneNumberSchema, otpVerifySchema2 as phoneOtpVerifySchema, phonePasswordSchema, signupSchema, toAuthError, useApiKeys, useAuth, useAuthContext, useAuthLocalization, useDevices, useLoginActivity, useOrganizations, usePhone, useSession, useSessions, useUser };
|
|
752
2181
|
//# sourceMappingURL=index.js.map
|
|
753
2182
|
//# sourceMappingURL=index.js.map
|