@atzentis/auth-react 0.0.12 → 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 +782 -3
- package/dist/index.js +2076 -8
- 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);
|
|
@@ -68,7 +448,87 @@ function useAuthContext() {
|
|
|
68
448
|
return context;
|
|
69
449
|
}
|
|
70
450
|
|
|
71
|
-
// src/use-
|
|
451
|
+
// src/hooks/use-api-keys.ts
|
|
452
|
+
function useApiKeys() {
|
|
453
|
+
const { client } = useAuthContext();
|
|
454
|
+
const [apiKeys, setApiKeys] = useState([]);
|
|
455
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
456
|
+
const [error, setError] = useState(null);
|
|
457
|
+
const refresh = useCallback(async () => {
|
|
458
|
+
setIsLoading(true);
|
|
459
|
+
setError(null);
|
|
460
|
+
try {
|
|
461
|
+
const response = await client.apiKeys.list();
|
|
462
|
+
setApiKeys(response.data);
|
|
463
|
+
} catch (err) {
|
|
464
|
+
setError(err);
|
|
465
|
+
} finally {
|
|
466
|
+
setIsLoading(false);
|
|
467
|
+
}
|
|
468
|
+
}, [client]);
|
|
469
|
+
const create = useCallback(
|
|
470
|
+
async (data) => {
|
|
471
|
+
const response = await client.apiKeys.create(data);
|
|
472
|
+
await refresh();
|
|
473
|
+
return response;
|
|
474
|
+
},
|
|
475
|
+
[client, refresh]
|
|
476
|
+
);
|
|
477
|
+
const get = useCallback(
|
|
478
|
+
async (keyId) => {
|
|
479
|
+
return client.apiKeys.get(keyId);
|
|
480
|
+
},
|
|
481
|
+
[client]
|
|
482
|
+
);
|
|
483
|
+
const update = useCallback(
|
|
484
|
+
async (keyId, data) => {
|
|
485
|
+
const updated = await client.apiKeys.update(keyId, data);
|
|
486
|
+
await refresh();
|
|
487
|
+
return updated;
|
|
488
|
+
},
|
|
489
|
+
[client, refresh]
|
|
490
|
+
);
|
|
491
|
+
const rotate = useCallback(
|
|
492
|
+
async (keyId) => {
|
|
493
|
+
const response = await client.apiKeys.rotate(keyId);
|
|
494
|
+
await refresh();
|
|
495
|
+
return response;
|
|
496
|
+
},
|
|
497
|
+
[client, refresh]
|
|
498
|
+
);
|
|
499
|
+
const revoke = useCallback(
|
|
500
|
+
async (keyId) => {
|
|
501
|
+
await client.apiKeys.revoke(keyId);
|
|
502
|
+
await refresh();
|
|
503
|
+
},
|
|
504
|
+
[client, refresh]
|
|
505
|
+
);
|
|
506
|
+
const validate = useCallback(
|
|
507
|
+
async (keyString) => {
|
|
508
|
+
return client.apiKeys.validate(keyString);
|
|
509
|
+
},
|
|
510
|
+
[client]
|
|
511
|
+
);
|
|
512
|
+
const getUsage = useCallback(
|
|
513
|
+
async (keyId) => {
|
|
514
|
+
return client.apiKeys.getUsage(keyId);
|
|
515
|
+
},
|
|
516
|
+
[client]
|
|
517
|
+
);
|
|
518
|
+
return {
|
|
519
|
+
apiKeys,
|
|
520
|
+
isLoading,
|
|
521
|
+
error,
|
|
522
|
+
refresh,
|
|
523
|
+
create,
|
|
524
|
+
get,
|
|
525
|
+
update,
|
|
526
|
+
rotate,
|
|
527
|
+
revoke,
|
|
528
|
+
validate,
|
|
529
|
+
getUsage
|
|
530
|
+
};
|
|
531
|
+
}
|
|
72
532
|
function useAuth() {
|
|
73
533
|
const { client, user, isAuthenticated, isLoading, error, setUser, clearError } = useAuthContext();
|
|
74
534
|
const login = useCallback(
|
|
@@ -79,6 +539,14 @@ function useAuth() {
|
|
|
79
539
|
},
|
|
80
540
|
[client, setUser]
|
|
81
541
|
);
|
|
542
|
+
const loginWithUsername = useCallback(
|
|
543
|
+
async (credentials) => {
|
|
544
|
+
const response = await client.loginWithUsername(credentials);
|
|
545
|
+
setUser(response.user);
|
|
546
|
+
return response;
|
|
547
|
+
},
|
|
548
|
+
[client, setUser]
|
|
549
|
+
);
|
|
82
550
|
const signup = useCallback(
|
|
83
551
|
async (data) => {
|
|
84
552
|
const response = await client.signup(data);
|
|
@@ -87,18 +555,411 @@ function useAuth() {
|
|
|
87
555
|
},
|
|
88
556
|
[client, setUser]
|
|
89
557
|
);
|
|
558
|
+
const logout = useCallback(async () => {
|
|
559
|
+
await client.logout();
|
|
560
|
+
setUser(null);
|
|
561
|
+
}, [client, setUser]);
|
|
562
|
+
const logoutAllDevices = useCallback(async () => {
|
|
563
|
+
await client.logoutAllDevices();
|
|
564
|
+
setUser(null);
|
|
565
|
+
}, [client, setUser]);
|
|
566
|
+
const getOAuthUrl = useCallback(
|
|
567
|
+
(config) => {
|
|
568
|
+
return client.getOAuthUrl(config);
|
|
569
|
+
},
|
|
570
|
+
[client]
|
|
571
|
+
);
|
|
572
|
+
const verifyOAuthCode = useCallback(
|
|
573
|
+
async (request) => {
|
|
574
|
+
const response = await client.verifyOAuthCode(request);
|
|
575
|
+
setUser(response.user);
|
|
576
|
+
return response;
|
|
577
|
+
},
|
|
578
|
+
[client, setUser]
|
|
579
|
+
);
|
|
580
|
+
const sendMagicLink = useCallback(
|
|
581
|
+
async (request) => {
|
|
582
|
+
await client.sendMagicLink(request);
|
|
583
|
+
},
|
|
584
|
+
[client]
|
|
585
|
+
);
|
|
586
|
+
const verifyMagicLink = useCallback(
|
|
587
|
+
async (request) => {
|
|
588
|
+
const response = await client.verifyMagicLink(request);
|
|
589
|
+
setUser(response.user);
|
|
590
|
+
return response;
|
|
591
|
+
},
|
|
592
|
+
[client, setUser]
|
|
593
|
+
);
|
|
594
|
+
const isUsernameAvailable = useCallback(
|
|
595
|
+
async (username) => {
|
|
596
|
+
return client.isUsernameAvailable(username);
|
|
597
|
+
},
|
|
598
|
+
[client]
|
|
599
|
+
);
|
|
600
|
+
const getAccessToken = useCallback(async () => {
|
|
601
|
+
return client.getAccessToken();
|
|
602
|
+
}, [client]);
|
|
603
|
+
const refreshToken = useCallback(async () => {
|
|
604
|
+
return client.refreshToken();
|
|
605
|
+
}, [client]);
|
|
606
|
+
const getAuthHeader = useCallback(async () => {
|
|
607
|
+
const token = await client.getAccessToken();
|
|
608
|
+
if (token) {
|
|
609
|
+
return { Authorization: `Bearer ${token}` };
|
|
610
|
+
}
|
|
611
|
+
return {};
|
|
612
|
+
}, [client]);
|
|
90
613
|
return {
|
|
91
614
|
user,
|
|
92
615
|
isAuthenticated,
|
|
93
616
|
isLoading,
|
|
94
617
|
error,
|
|
95
618
|
login,
|
|
619
|
+
loginWithUsername,
|
|
96
620
|
signup,
|
|
621
|
+
logout,
|
|
622
|
+
logoutAllDevices,
|
|
623
|
+
getOAuthUrl,
|
|
624
|
+
verifyOAuthCode,
|
|
625
|
+
sendMagicLink,
|
|
626
|
+
verifyMagicLink,
|
|
627
|
+
isUsernameAvailable,
|
|
628
|
+
getAccessToken,
|
|
629
|
+
refreshToken,
|
|
630
|
+
getAuthHeader,
|
|
97
631
|
clearError
|
|
98
632
|
};
|
|
99
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
|
+
}
|
|
639
|
+
function useDevices() {
|
|
640
|
+
const { client } = useAuthContext();
|
|
641
|
+
const [devices, setDevices] = useState([]);
|
|
642
|
+
const [currentDevice, setCurrentDevice] = useState(null);
|
|
643
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
644
|
+
const [error, setError] = useState(null);
|
|
645
|
+
const refresh = useCallback(async () => {
|
|
646
|
+
setIsLoading(true);
|
|
647
|
+
setError(null);
|
|
648
|
+
try {
|
|
649
|
+
const response = await client.devices.list();
|
|
650
|
+
setDevices(response.data);
|
|
651
|
+
const current = response.data.find((d) => d.isCurrent) ?? null;
|
|
652
|
+
setCurrentDevice(current);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
setError(err);
|
|
655
|
+
} finally {
|
|
656
|
+
setIsLoading(false);
|
|
657
|
+
}
|
|
658
|
+
}, [client]);
|
|
659
|
+
const trustDevice = useCallback(
|
|
660
|
+
async (id) => {
|
|
661
|
+
await client.devices.trust(id);
|
|
662
|
+
await refresh();
|
|
663
|
+
},
|
|
664
|
+
[client, refresh]
|
|
665
|
+
);
|
|
666
|
+
const untrustDevice = useCallback(
|
|
667
|
+
async (id) => {
|
|
668
|
+
await client.devices.untrust(id);
|
|
669
|
+
await refresh();
|
|
670
|
+
},
|
|
671
|
+
[client, refresh]
|
|
672
|
+
);
|
|
673
|
+
const removeDevice = useCallback(
|
|
674
|
+
async (id) => {
|
|
675
|
+
await client.devices.remove(id);
|
|
676
|
+
await refresh();
|
|
677
|
+
},
|
|
678
|
+
[client, refresh]
|
|
679
|
+
);
|
|
680
|
+
const removeAllOtherDevices = useCallback(async () => {
|
|
681
|
+
await client.devices.removeAllOthers();
|
|
682
|
+
await refresh();
|
|
683
|
+
}, [client, refresh]);
|
|
684
|
+
return {
|
|
685
|
+
devices,
|
|
686
|
+
currentDevice,
|
|
687
|
+
isLoading,
|
|
688
|
+
error,
|
|
689
|
+
refresh,
|
|
690
|
+
trustDevice,
|
|
691
|
+
untrustDevice,
|
|
692
|
+
removeDevice,
|
|
693
|
+
removeAllOtherDevices
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function useLoginActivity(options = {}) {
|
|
697
|
+
const { client } = useAuthContext();
|
|
698
|
+
const [events, setEvents] = useState([]);
|
|
699
|
+
const [total, setTotal] = useState(0);
|
|
700
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
701
|
+
const [error, setError] = useState(null);
|
|
702
|
+
const [filterOptions, setFilterOptions] = useState({
|
|
703
|
+
limit: options.limit ?? 50,
|
|
704
|
+
offset: 0
|
|
705
|
+
});
|
|
706
|
+
const hasMore = events.length < total;
|
|
707
|
+
const refresh = useCallback(async () => {
|
|
708
|
+
setIsLoading(true);
|
|
709
|
+
setError(null);
|
|
710
|
+
try {
|
|
711
|
+
const response = await client.loginActivity.list(filterOptions);
|
|
712
|
+
setEvents(response.data);
|
|
713
|
+
setTotal(response.total);
|
|
714
|
+
} catch (err) {
|
|
715
|
+
setError(err);
|
|
716
|
+
} finally {
|
|
717
|
+
setIsLoading(false);
|
|
718
|
+
}
|
|
719
|
+
}, [client, filterOptions]);
|
|
720
|
+
const loadMore = useCallback(async () => {
|
|
721
|
+
setIsLoading(true);
|
|
722
|
+
setError(null);
|
|
723
|
+
try {
|
|
724
|
+
const response = await client.loginActivity.list({
|
|
725
|
+
...filterOptions,
|
|
726
|
+
offset: events.length
|
|
727
|
+
});
|
|
728
|
+
setEvents((prev) => [...prev, ...response.data]);
|
|
729
|
+
setTotal(response.total);
|
|
730
|
+
} catch (err) {
|
|
731
|
+
setError(err);
|
|
732
|
+
} finally {
|
|
733
|
+
setIsLoading(false);
|
|
734
|
+
}
|
|
735
|
+
}, [client, filterOptions, events.length]);
|
|
736
|
+
const filter = useCallback(
|
|
737
|
+
(newOptions) => {
|
|
738
|
+
const merged = { ...filterOptions, ...newOptions, offset: 0 };
|
|
739
|
+
setFilterOptions(merged);
|
|
740
|
+
setEvents([]);
|
|
741
|
+
setTotal(0);
|
|
742
|
+
},
|
|
743
|
+
[filterOptions]
|
|
744
|
+
);
|
|
745
|
+
const markRecognized = useCallback(
|
|
746
|
+
async (eventId) => {
|
|
747
|
+
await client.loginActivity.markRecognized(eventId);
|
|
748
|
+
},
|
|
749
|
+
[client]
|
|
750
|
+
);
|
|
751
|
+
const reportSuspicious = useCallback(
|
|
752
|
+
async (eventId) => {
|
|
753
|
+
return client.loginActivity.reportSuspicious(eventId);
|
|
754
|
+
},
|
|
755
|
+
[client]
|
|
756
|
+
);
|
|
757
|
+
return {
|
|
758
|
+
events,
|
|
759
|
+
total,
|
|
760
|
+
hasMore,
|
|
761
|
+
isLoading,
|
|
762
|
+
error,
|
|
763
|
+
refresh,
|
|
764
|
+
loadMore,
|
|
765
|
+
filter,
|
|
766
|
+
markRecognized,
|
|
767
|
+
reportSuspicious
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function useOrganizations() {
|
|
771
|
+
const { client } = useAuthContext();
|
|
772
|
+
const [organizations, setOrganizations] = useState([]);
|
|
773
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
774
|
+
const [error, setError] = useState(null);
|
|
775
|
+
const refresh = useCallback(async () => {
|
|
776
|
+
setIsLoading(true);
|
|
777
|
+
setError(null);
|
|
778
|
+
try {
|
|
779
|
+
const response = await client.organizations.list();
|
|
780
|
+
setOrganizations(response.data);
|
|
781
|
+
} catch (err) {
|
|
782
|
+
setError(err);
|
|
783
|
+
} finally {
|
|
784
|
+
setIsLoading(false);
|
|
785
|
+
}
|
|
786
|
+
}, [client]);
|
|
787
|
+
const create = useCallback(
|
|
788
|
+
async (data) => {
|
|
789
|
+
const org = await client.organizations.create(data);
|
|
790
|
+
await refresh();
|
|
791
|
+
return org;
|
|
792
|
+
},
|
|
793
|
+
[client, refresh]
|
|
794
|
+
);
|
|
795
|
+
const update = useCallback(
|
|
796
|
+
async (orgId, data) => {
|
|
797
|
+
const org = await client.organizations.update(orgId, data);
|
|
798
|
+
await refresh();
|
|
799
|
+
return org;
|
|
800
|
+
},
|
|
801
|
+
[client, refresh]
|
|
802
|
+
);
|
|
803
|
+
const deleteOrg = useCallback(
|
|
804
|
+
async (orgId) => {
|
|
805
|
+
await client.organizations.delete(orgId);
|
|
806
|
+
await refresh();
|
|
807
|
+
},
|
|
808
|
+
[client, refresh]
|
|
809
|
+
);
|
|
810
|
+
const listMembers = useCallback(
|
|
811
|
+
async (orgId, options) => {
|
|
812
|
+
return client.organizations.listMembers(orgId, options);
|
|
813
|
+
},
|
|
814
|
+
[client]
|
|
815
|
+
);
|
|
816
|
+
const updateMember = useCallback(
|
|
817
|
+
async (orgId, userId, data) => {
|
|
818
|
+
return client.organizations.updateMember(orgId, userId, data);
|
|
819
|
+
},
|
|
820
|
+
[client]
|
|
821
|
+
);
|
|
822
|
+
const removeMember = useCallback(
|
|
823
|
+
async (orgId, userId) => {
|
|
824
|
+
await client.organizations.removeMember(orgId, userId);
|
|
825
|
+
},
|
|
826
|
+
[client]
|
|
827
|
+
);
|
|
828
|
+
const inviteMember = useCallback(
|
|
829
|
+
async (orgId, data) => {
|
|
830
|
+
return client.organizations.inviteMember(orgId, data);
|
|
831
|
+
},
|
|
832
|
+
[client]
|
|
833
|
+
);
|
|
834
|
+
const listInvitations = useCallback(
|
|
835
|
+
async (orgId, options) => {
|
|
836
|
+
return client.organizations.listInvitations(orgId, options);
|
|
837
|
+
},
|
|
838
|
+
[client]
|
|
839
|
+
);
|
|
840
|
+
const resendInvitation = useCallback(
|
|
841
|
+
async (orgId, invitationId) => {
|
|
842
|
+
await client.organizations.resendInvitation(orgId, invitationId);
|
|
843
|
+
},
|
|
844
|
+
[client]
|
|
845
|
+
);
|
|
846
|
+
const cancelInvitation = useCallback(
|
|
847
|
+
async (orgId, invitationId) => {
|
|
848
|
+
await client.organizations.cancelInvitation(orgId, invitationId);
|
|
849
|
+
},
|
|
850
|
+
[client]
|
|
851
|
+
);
|
|
852
|
+
const acceptInvitation = useCallback(
|
|
853
|
+
async (data) => {
|
|
854
|
+
await client.organizations.acceptInvitation(data);
|
|
855
|
+
},
|
|
856
|
+
[client]
|
|
857
|
+
);
|
|
858
|
+
const declineInvitation = useCallback(
|
|
859
|
+
async (data) => {
|
|
860
|
+
await client.organizations.declineInvitation(data);
|
|
861
|
+
},
|
|
862
|
+
[client]
|
|
863
|
+
);
|
|
864
|
+
const leave = useCallback(
|
|
865
|
+
async (orgId) => {
|
|
866
|
+
await client.organizations.leave(orgId);
|
|
867
|
+
await refresh();
|
|
868
|
+
},
|
|
869
|
+
[client, refresh]
|
|
870
|
+
);
|
|
871
|
+
return {
|
|
872
|
+
organizations,
|
|
873
|
+
isLoading,
|
|
874
|
+
error,
|
|
875
|
+
refresh,
|
|
876
|
+
create,
|
|
877
|
+
update,
|
|
878
|
+
delete: deleteOrg,
|
|
879
|
+
listMembers,
|
|
880
|
+
updateMember,
|
|
881
|
+
removeMember,
|
|
882
|
+
inviteMember,
|
|
883
|
+
listInvitations,
|
|
884
|
+
resendInvitation,
|
|
885
|
+
cancelInvitation,
|
|
886
|
+
acceptInvitation,
|
|
887
|
+
declineInvitation,
|
|
888
|
+
leave
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
function usePhone() {
|
|
892
|
+
const { client, setUser } = useAuthContext();
|
|
893
|
+
const [isSending, setIsSending] = useState(false);
|
|
894
|
+
const [isVerifying, setIsVerifying] = useState(false);
|
|
895
|
+
const [codeSent, setCodeSent] = useState(false);
|
|
896
|
+
const [expiresIn, setExpiresIn] = useState(null);
|
|
897
|
+
const [error, setError] = useState(null);
|
|
898
|
+
const sendOTP = useCallback(
|
|
899
|
+
async (phoneNumber) => {
|
|
900
|
+
setIsSending(true);
|
|
901
|
+
setError(null);
|
|
902
|
+
try {
|
|
903
|
+
const response = await client.phone.sendOTP({ phoneNumber });
|
|
904
|
+
setCodeSent(true);
|
|
905
|
+
setExpiresIn(response.expiresIn);
|
|
906
|
+
return response;
|
|
907
|
+
} catch (err) {
|
|
908
|
+
setError(err);
|
|
909
|
+
throw err;
|
|
910
|
+
} finally {
|
|
911
|
+
setIsSending(false);
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
[client]
|
|
915
|
+
);
|
|
916
|
+
const verify = useCallback(
|
|
917
|
+
async (phoneNumber, code) => {
|
|
918
|
+
setIsVerifying(true);
|
|
919
|
+
setError(null);
|
|
920
|
+
try {
|
|
921
|
+
const response = await client.phone.verify({ phoneNumber, code });
|
|
922
|
+
setUser(response.user);
|
|
923
|
+
return response;
|
|
924
|
+
} catch (err) {
|
|
925
|
+
setError(err);
|
|
926
|
+
throw err;
|
|
927
|
+
} finally {
|
|
928
|
+
setIsVerifying(false);
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
[client, setUser]
|
|
932
|
+
);
|
|
933
|
+
const signIn = useCallback(
|
|
934
|
+
async (phoneNumber, password) => {
|
|
935
|
+
setIsVerifying(true);
|
|
936
|
+
setError(null);
|
|
937
|
+
try {
|
|
938
|
+
const response = await client.phone.signIn({ phoneNumber, password });
|
|
939
|
+
setUser(response.user);
|
|
940
|
+
return response;
|
|
941
|
+
} catch (err) {
|
|
942
|
+
setError(err);
|
|
943
|
+
throw err;
|
|
944
|
+
} finally {
|
|
945
|
+
setIsVerifying(false);
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
[client, setUser]
|
|
949
|
+
);
|
|
950
|
+
return {
|
|
951
|
+
isSending,
|
|
952
|
+
isVerifying,
|
|
953
|
+
codeSent,
|
|
954
|
+
expiresIn,
|
|
955
|
+
error,
|
|
956
|
+
sendOTP,
|
|
957
|
+
verify,
|
|
958
|
+
signIn
|
|
959
|
+
};
|
|
960
|
+
}
|
|
100
961
|
|
|
101
|
-
// src/use-session.ts
|
|
962
|
+
// src/hooks/use-session.ts
|
|
102
963
|
function useSession() {
|
|
103
964
|
const { user, session, isAuthenticated, isLoading } = useAuthContext();
|
|
104
965
|
return {
|
|
@@ -108,7 +969,1214 @@ function useSession() {
|
|
|
108
969
|
isLoading
|
|
109
970
|
};
|
|
110
971
|
}
|
|
972
|
+
function useSessions() {
|
|
973
|
+
const { client, session, setUser } = useAuthContext();
|
|
974
|
+
const [sessions, setSessions] = useState([]);
|
|
975
|
+
const [deviceSessions, setDeviceSessions] = useState([]);
|
|
976
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
977
|
+
const [error, setError] = useState(null);
|
|
978
|
+
const refresh = useCallback(async () => {
|
|
979
|
+
setIsLoading(true);
|
|
980
|
+
setError(null);
|
|
981
|
+
try {
|
|
982
|
+
const [sessionsResponse, deviceSessionsResponse] = await Promise.all([
|
|
983
|
+
client.sessions.list(),
|
|
984
|
+
client.sessions.listDeviceSessions()
|
|
985
|
+
]);
|
|
986
|
+
setSessions(sessionsResponse.data);
|
|
987
|
+
setDeviceSessions(deviceSessionsResponse);
|
|
988
|
+
} catch (err) {
|
|
989
|
+
setError(err);
|
|
990
|
+
} finally {
|
|
991
|
+
setIsLoading(false);
|
|
992
|
+
}
|
|
993
|
+
}, [client]);
|
|
994
|
+
const revokeSession = useCallback(
|
|
995
|
+
async (id) => {
|
|
996
|
+
await client.sessions.revoke(id);
|
|
997
|
+
await refresh();
|
|
998
|
+
},
|
|
999
|
+
[client, refresh]
|
|
1000
|
+
);
|
|
1001
|
+
const revokeAllSessions = useCallback(
|
|
1002
|
+
async (options) => {
|
|
1003
|
+
await client.sessions.revokeAll(options);
|
|
1004
|
+
await refresh();
|
|
1005
|
+
},
|
|
1006
|
+
[client, refresh]
|
|
1007
|
+
);
|
|
1008
|
+
const switchAccount = useCallback(
|
|
1009
|
+
async (sessionToken) => {
|
|
1010
|
+
const response = await client.sessions.setActive({ sessionToken });
|
|
1011
|
+
setUser(response.user);
|
|
1012
|
+
},
|
|
1013
|
+
[client, setUser]
|
|
1014
|
+
);
|
|
1015
|
+
const revokeDeviceSession = useCallback(
|
|
1016
|
+
async (sessionToken) => {
|
|
1017
|
+
await client.sessions.revokeDeviceSession({ sessionToken });
|
|
1018
|
+
await refresh();
|
|
1019
|
+
},
|
|
1020
|
+
[client, refresh]
|
|
1021
|
+
);
|
|
1022
|
+
return {
|
|
1023
|
+
session,
|
|
1024
|
+
sessions,
|
|
1025
|
+
deviceSessions,
|
|
1026
|
+
isLoading,
|
|
1027
|
+
error,
|
|
1028
|
+
refresh,
|
|
1029
|
+
revokeSession,
|
|
1030
|
+
revokeAllSessions,
|
|
1031
|
+
switchAccount,
|
|
1032
|
+
revokeDeviceSession
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function useUser() {
|
|
1036
|
+
const { client, user, setUser } = useAuthContext();
|
|
1037
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1038
|
+
const [error, setError] = useState(null);
|
|
1039
|
+
const updateProfile = useCallback(
|
|
1040
|
+
async (data) => {
|
|
1041
|
+
setIsLoading(true);
|
|
1042
|
+
setError(null);
|
|
1043
|
+
try {
|
|
1044
|
+
const updated = await client.users.update(data);
|
|
1045
|
+
setUser(updated);
|
|
1046
|
+
return updated;
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
setError(err);
|
|
1049
|
+
throw err;
|
|
1050
|
+
} finally {
|
|
1051
|
+
setIsLoading(false);
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
[client, setUser]
|
|
1055
|
+
);
|
|
1056
|
+
const changePassword = useCallback(
|
|
1057
|
+
async (currentPassword, newPassword) => {
|
|
1058
|
+
await client.users.changePassword({ currentPassword, newPassword });
|
|
1059
|
+
},
|
|
1060
|
+
[client]
|
|
1061
|
+
);
|
|
1062
|
+
const setupTwoFactor = useCallback(async () => {
|
|
1063
|
+
return client.users.setupTwoFactor();
|
|
1064
|
+
}, [client]);
|
|
1065
|
+
const verifyTwoFactorSetup = useCallback(
|
|
1066
|
+
async (code) => {
|
|
1067
|
+
await client.users.verifyTwoFactorSetup({ code });
|
|
1068
|
+
},
|
|
1069
|
+
[client]
|
|
1070
|
+
);
|
|
1071
|
+
const disableTwoFactor = useCallback(
|
|
1072
|
+
async (password) => {
|
|
1073
|
+
await client.users.disableTwoFactor({ password });
|
|
1074
|
+
},
|
|
1075
|
+
[client]
|
|
1076
|
+
);
|
|
1077
|
+
const deleteAccount = useCallback(
|
|
1078
|
+
async (password, reason) => {
|
|
1079
|
+
await client.users.deleteAccount({ password, reason });
|
|
1080
|
+
setUser(null);
|
|
1081
|
+
},
|
|
1082
|
+
[client, setUser]
|
|
1083
|
+
);
|
|
1084
|
+
const listConnectedProviders = useCallback(async () => {
|
|
1085
|
+
return client.users.listConnectedProviders();
|
|
1086
|
+
}, [client]);
|
|
1087
|
+
const connectOAuthProvider = useCallback(
|
|
1088
|
+
async (data) => {
|
|
1089
|
+
return client.users.connectOAuthProvider(data);
|
|
1090
|
+
},
|
|
1091
|
+
[client]
|
|
1092
|
+
);
|
|
1093
|
+
const disconnectOAuthProvider = useCallback(
|
|
1094
|
+
async (provider) => {
|
|
1095
|
+
await client.users.disconnectOAuthProvider(provider);
|
|
1096
|
+
},
|
|
1097
|
+
[client]
|
|
1098
|
+
);
|
|
1099
|
+
const addPhoneNumber = useCallback(
|
|
1100
|
+
async (phoneNumber) => {
|
|
1101
|
+
await client.users.addPhoneNumber({ phoneNumber });
|
|
1102
|
+
},
|
|
1103
|
+
[client]
|
|
1104
|
+
);
|
|
1105
|
+
const verifyPhoneNumber = useCallback(
|
|
1106
|
+
async (code) => {
|
|
1107
|
+
await client.users.verifyPhoneNumber({ code });
|
|
1108
|
+
},
|
|
1109
|
+
[client]
|
|
1110
|
+
);
|
|
1111
|
+
const removePhoneNumber = useCallback(
|
|
1112
|
+
async (password) => {
|
|
1113
|
+
await client.users.removePhoneNumber({ password });
|
|
1114
|
+
},
|
|
1115
|
+
[client]
|
|
1116
|
+
);
|
|
1117
|
+
return {
|
|
1118
|
+
user,
|
|
1119
|
+
isLoading,
|
|
1120
|
+
error,
|
|
1121
|
+
updateProfile,
|
|
1122
|
+
changePassword,
|
|
1123
|
+
setupTwoFactor,
|
|
1124
|
+
verifyTwoFactorSetup,
|
|
1125
|
+
disableTwoFactor,
|
|
1126
|
+
deleteAccount,
|
|
1127
|
+
listConnectedProviders,
|
|
1128
|
+
connectOAuthProvider,
|
|
1129
|
+
disconnectOAuthProvider,
|
|
1130
|
+
addPhoneNumber,
|
|
1131
|
+
verifyPhoneNumber,
|
|
1132
|
+
removePhoneNumber
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
|
|
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
|
+
}
|
|
111
2179
|
|
|
112
|
-
export { AuthContext, AuthProvider, useAuth, useAuthContext, useSession };
|
|
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 };
|
|
113
2181
|
//# sourceMappingURL=index.js.map
|
|
114
2182
|
//# sourceMappingURL=index.js.map
|