@rqdhw3n/react-auth-flow 1.0.4 → 1.0.6

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.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import React, { createContext, useReducer, useCallback, useEffect, useContext, useState } from "react";
2
+ import { createContext, useReducer, useMemo, useCallback, useEffect, useContext, useState, useRef, useId } from "react";
3
3
  import { Navigate } from "react-router-dom";
4
4
  const AuthContext = createContext(
5
5
  void 0
@@ -62,150 +62,304 @@ const DEFAULT_ENDPOINTS = {
62
62
  refresh: "/auth/refresh",
63
63
  forgotPassword: "/auth/forgot-password",
64
64
  resetPassword: "/auth/reset-password",
65
- verifyEmail: "/auth/verify-email"
66
- };
67
- const defaultAdapter = async (url, options) => {
68
- const response = await fetch(url, options);
69
- return response;
65
+ verifyEmail: "/auth/verify-email",
66
+ twoFactorVerify: "/auth/2fa/verify"
70
67
  };
68
+ async function parseResponse(response) {
69
+ if (!response.ok) {
70
+ let errorData = null;
71
+ try {
72
+ errorData = await response.json();
73
+ } catch {
74
+ errorData = {
75
+ error: {
76
+ message: response.statusText || "Request failed",
77
+ statusCode: response.status
78
+ }
79
+ };
80
+ }
81
+ throw errorData;
82
+ }
83
+ if (response.status === 204) {
84
+ return {};
85
+ }
86
+ const contentType = response.headers.get("content-type") ?? "";
87
+ if (contentType.includes("application/json")) {
88
+ return response.json();
89
+ }
90
+ const text = await response.text();
91
+ return text ? { message: text } : {};
92
+ }
93
+ function isResponseLike(value) {
94
+ return typeof value === "object" && value !== null && "ok" in value && "status" in value && "headers" in value;
95
+ }
96
+ function createDefaultRequestAdapter(config) {
97
+ const { baseURL, credentials, headers } = config;
98
+ return async ({ endpoint, method, data, headers: requestHeaders }) => {
99
+ const response = await fetch(`${baseURL}${endpoint}`, {
100
+ method,
101
+ credentials,
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ ...headers,
105
+ ...requestHeaders
106
+ },
107
+ body: data === void 0 ? void 0 : JSON.stringify(data)
108
+ });
109
+ return parseResponse(response);
110
+ };
111
+ }
112
+ function createLegacyAdapter(config) {
113
+ const { adapter, baseURL, credentials, headers } = config;
114
+ return async ({ endpoint, method, data, headers: requestHeaders }) => {
115
+ const response = await adapter(`${baseURL}${endpoint}`, {
116
+ method,
117
+ credentials,
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ ...headers,
121
+ ...requestHeaders
122
+ },
123
+ body: data === void 0 ? void 0 : JSON.stringify(data)
124
+ });
125
+ return parseResponse(response);
126
+ };
127
+ }
71
128
  function createAuthClient(config = {}) {
72
129
  const {
73
- baseURL = "",
130
+ baseURL,
131
+ baseUrl,
74
132
  endpoints = {},
75
- headers = {},
133
+ headers: initialHeaders = {},
76
134
  credentials = "include",
77
- adapter = defaultAdapter
135
+ requestAdapter,
136
+ adapter
78
137
  } = config;
79
- const finalEndpoints = { ...DEFAULT_ENDPOINTS, ...endpoints };
138
+ const resolvedBaseURL = baseURL ?? baseUrl ?? "";
139
+ const finalEndpoints = {
140
+ ...DEFAULT_ENDPOINTS,
141
+ ...endpoints
142
+ };
143
+ const headers = { ...initialHeaders };
144
+ const resolvedAdapter = requestAdapter ?? (adapter ? createLegacyAdapter({
145
+ adapter,
146
+ baseURL: resolvedBaseURL,
147
+ credentials,
148
+ headers
149
+ }) : createDefaultRequestAdapter({
150
+ baseURL: resolvedBaseURL,
151
+ credentials,
152
+ headers
153
+ }));
80
154
  async function request(method, endpoint, data) {
81
- const url = `${baseURL}${endpoint}`;
82
- const options = {
83
- method,
84
- credentials,
85
- headers: {
86
- "Content-Type": "application/json",
87
- ...headers
88
- }
89
- };
90
- if (data) {
91
- options.body = JSON.stringify(data);
92
- }
93
155
  try {
94
- const response = await adapter(url, options);
95
- if (!response.ok) {
96
- let errorData = null;
97
- try {
98
- errorData = await response.json();
99
- } catch {
100
- errorData = {
101
- error: {
102
- message: response.statusText,
103
- statusCode: response.status
104
- }
105
- };
106
- }
107
- throw errorData;
108
- }
109
- const contentType = response.headers.get("content-type");
110
- if (contentType && contentType.includes("application/json")) {
111
- return await response.json();
156
+ const result = await resolvedAdapter({
157
+ endpoint,
158
+ method,
159
+ data,
160
+ headers
161
+ });
162
+ if (isResponseLike(result)) {
163
+ return await parseResponse(result);
112
164
  }
113
- return {};
165
+ return result ?? {};
114
166
  } catch (error) {
115
167
  throw normalizeError(error);
116
168
  }
117
169
  }
118
170
  return {
119
- /**
120
- * Login with email and password
121
- */
122
171
  async login(email, password, rememberMe) {
123
- const response = await request(
124
- "POST",
125
- finalEndpoints.login,
126
- { email, password, rememberMe }
127
- );
128
- return response;
172
+ return request("POST", finalEndpoints.login, {
173
+ email,
174
+ password,
175
+ rememberMe
176
+ });
129
177
  },
130
- /**
131
- * Register a new account
132
- */
133
178
  async register(payload) {
134
- const response = await request(
135
- "POST",
136
- finalEndpoints.register,
137
- payload
138
- );
139
- return response;
179
+ return request("POST", finalEndpoints.register, payload);
140
180
  },
141
- /**
142
- * Logout the user
143
- */
144
181
  async logout() {
145
182
  await request("POST", finalEndpoints.logout);
146
183
  },
147
- /**
148
- * Get current user
149
- */
150
184
  async me() {
151
- const response = await request(
152
- "GET",
153
- finalEndpoints.me
154
- );
155
- return response;
185
+ return request("GET", finalEndpoints.me);
156
186
  },
157
- /**
158
- * Refresh the authentication session
159
- */
160
187
  async refresh() {
161
- const response = await request(
162
- "POST",
163
- finalEndpoints.refresh
164
- );
165
- return response;
188
+ return request("POST", finalEndpoints.refresh);
166
189
  },
167
- /**
168
- * Request a password reset
169
- */
170
190
  async forgotPassword(email) {
171
- await request("POST", finalEndpoints.forgotPassword, { email });
191
+ return request("POST", finalEndpoints.forgotPassword, {
192
+ email
193
+ });
172
194
  },
173
- /**
174
- * Reset password with token
175
- */
176
195
  async resetPassword(token, password, confirmPassword) {
177
- await request("POST", finalEndpoints.resetPassword, {
196
+ return request("POST", finalEndpoints.resetPassword, {
178
197
  token,
179
198
  password,
180
199
  confirmPassword
181
200
  });
182
201
  },
183
- /**
184
- * Verify email with token
185
- */
186
202
  async verifyEmail(token, email) {
187
- await request("POST", finalEndpoints.verifyEmail, { token, email });
203
+ return request("POST", finalEndpoints.verifyEmail, {
204
+ token,
205
+ email
206
+ });
207
+ },
208
+ async verifyTwoFactor(code) {
209
+ return request("POST", finalEndpoints.twoFactorVerify, {
210
+ code
211
+ });
188
212
  },
189
- /**
190
- * Set custom headers for subsequent requests
191
- */
192
213
  setHeaders(newHeaders) {
193
214
  Object.assign(headers, newHeaders);
194
215
  },
195
- /**
196
- * Get current endpoints configuration
197
- */
198
216
  getEndpoints() {
199
217
  return finalEndpoints;
200
218
  }
201
219
  };
202
220
  }
221
+ const DEFAULT_STORAGE_KEY = "rq-auth-flow-user";
222
+ const DEFAULT_OTP_LENGTH = 6;
223
+ let memoryUser = null;
224
+ function getStorage() {
225
+ if (typeof window === "undefined") {
226
+ return null;
227
+ }
228
+ try {
229
+ return window.localStorage;
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ function readStoredUser(storageKey) {
235
+ const storage = getStorage();
236
+ if (!storage) {
237
+ return memoryUser;
238
+ }
239
+ const rawValue = storage.getItem(storageKey);
240
+ if (!rawValue) {
241
+ return null;
242
+ }
243
+ try {
244
+ return JSON.parse(rawValue);
245
+ } catch {
246
+ storage.removeItem(storageKey);
247
+ return null;
248
+ }
249
+ }
250
+ function writeStoredUser(storageKey, user) {
251
+ const storage = getStorage();
252
+ memoryUser = user;
253
+ if (!storage) {
254
+ return;
255
+ }
256
+ if (user === null) {
257
+ storage.removeItem(storageKey);
258
+ return;
259
+ }
260
+ storage.setItem(storageKey, JSON.stringify(user));
261
+ }
262
+ function createAdminUser(email, mockUser) {
263
+ return {
264
+ id: 1,
265
+ name: "Admin Test",
266
+ email,
267
+ roles: ["admin"],
268
+ permissions: ["users.manage", "billing.edit"],
269
+ ...mockUser
270
+ };
271
+ }
272
+ function createRegisteredUser(name, email, mockUser) {
273
+ return {
274
+ id: 2,
275
+ name: name || "New User",
276
+ email,
277
+ roles: ["user"],
278
+ permissions: ["users.create"],
279
+ ...mockUser
280
+ };
281
+ }
282
+ function toSuccessResponse(message, user) {
283
+ return user ? { success: true, message, user } : { success: true, message };
284
+ }
285
+ function createMockAuthAdapter(options = {}) {
286
+ const {
287
+ endpoints = {},
288
+ mockStorageKey = DEFAULT_STORAGE_KEY,
289
+ mockUser
290
+ } = options;
291
+ const resolvedEndpoints = {
292
+ login: "/auth/login",
293
+ register: "/auth/register",
294
+ logout: "/auth/logout",
295
+ me: "/auth/me",
296
+ refresh: "/auth/refresh",
297
+ forgotPassword: "/auth/forgot-password",
298
+ resetPassword: "/auth/reset-password",
299
+ verifyEmail: "/auth/verify-email",
300
+ twoFactorVerify: "/auth/2fa/verify",
301
+ ...endpoints
302
+ };
303
+ return async ({ endpoint, data }) => {
304
+ if (endpoint === resolvedEndpoints.login) {
305
+ const payload = data ?? {};
306
+ const user = createAdminUser(payload.email ?? "admin@example.com", mockUser);
307
+ writeStoredUser(mockStorageKey, user);
308
+ return toSuccessResponse("Mock login successful.", user);
309
+ }
310
+ if (endpoint === resolvedEndpoints.register) {
311
+ const payload = data ?? {};
312
+ const user = createRegisteredUser(
313
+ payload.name,
314
+ payload.email ?? "user@example.com",
315
+ mockUser
316
+ );
317
+ writeStoredUser(mockStorageKey, user);
318
+ return toSuccessResponse("Mock registration successful.", user);
319
+ }
320
+ if (endpoint === resolvedEndpoints.logout) {
321
+ writeStoredUser(mockStorageKey, null);
322
+ return toSuccessResponse("Mock logout successful.");
323
+ }
324
+ if (endpoint === resolvedEndpoints.me || endpoint === resolvedEndpoints.refresh) {
325
+ const user = readStoredUser(mockStorageKey);
326
+ return user ? { success: true, user } : { success: true, user: null };
327
+ }
328
+ if (endpoint === resolvedEndpoints.forgotPassword) {
329
+ return toSuccessResponse("Mock password reset request accepted.");
330
+ }
331
+ if (endpoint === resolvedEndpoints.resetPassword) {
332
+ return toSuccessResponse("Mock password reset successful.");
333
+ }
334
+ if (endpoint === resolvedEndpoints.verifyEmail) {
335
+ return toSuccessResponse("Mock email verification successful.");
336
+ }
337
+ if (endpoint === resolvedEndpoints.twoFactorVerify) {
338
+ const payload = data ?? {};
339
+ const code = payload.code?.trim() ?? "";
340
+ if (code.length !== DEFAULT_OTP_LENGTH) {
341
+ throw {
342
+ code: "INVALID_2FA_CODE",
343
+ message: `Mock 2FA code must be exactly ${DEFAULT_OTP_LENGTH} characters.`
344
+ };
345
+ }
346
+ return toSuccessResponse("Mock 2FA verification successful.");
347
+ }
348
+ return toSuccessResponse("Mock request successful.");
349
+ };
350
+ }
203
351
  const initialState = {
204
352
  user: null,
205
353
  isAuthenticated: false,
206
354
  isLoading: false,
207
355
  error: null
208
356
  };
357
+ const defaultTheme = {
358
+ primaryColor: "#2563eb",
359
+ primaryHoverColor: "#1d4ed8",
360
+ radius: "14px",
361
+ fontFamily: "inherit"
362
+ };
209
363
  function authReducer(state, action) {
210
364
  switch (action.type) {
211
365
  case "SET_LOADING":
@@ -221,29 +375,63 @@ function authReducer(state, action) {
221
375
  isLoading: false
222
376
  };
223
377
  case "LOGOUT":
224
- return initialState;
378
+ return { ...initialState };
225
379
  default:
226
380
  return state;
227
381
  }
228
382
  }
383
+ function toAuthError(error) {
384
+ if (!error) {
385
+ return null;
386
+ }
387
+ return typeof error === "string" ? {
388
+ code: "AUTH_ERROR",
389
+ message: error
390
+ } : error;
391
+ }
229
392
  const AuthProvider = ({
230
393
  children,
231
- baseURL = "",
394
+ baseURL,
395
+ baseUrl,
232
396
  endpoints = {},
397
+ headers = {},
398
+ credentials = "include",
233
399
  onAuthError,
234
400
  autoRefresh = true,
235
- refreshInterval = 5 * 60 * 1e3
236
- // 5 minutes
401
+ autoRestore = true,
402
+ refreshInterval = 5 * 60 * 1e3,
403
+ requestAdapter,
404
+ theme,
405
+ mock = false,
406
+ mockStorageKey = "rq-auth-flow-user",
407
+ mockUser
237
408
  }) => {
238
409
  const [state, dispatch] = useReducer(authReducer, initialState);
239
- const clientRef = React.useRef(
240
- createAuthClient({
241
- baseURL,
410
+ const resolvedTheme = { ...defaultTheme, ...theme };
411
+ const resolvedBaseURL = baseURL ?? baseUrl ?? "";
412
+ const client = useMemo(() => {
413
+ const resolvedRequestAdapter = mock ? createMockAuthAdapter({
242
414
  endpoints,
243
- credentials: "include"
244
- })
245
- );
246
- const client = clientRef.current;
415
+ mockStorageKey,
416
+ mockUser
417
+ }) : requestAdapter;
418
+ return createAuthClient({
419
+ baseURL: resolvedBaseURL,
420
+ endpoints,
421
+ headers,
422
+ credentials,
423
+ requestAdapter: resolvedRequestAdapter
424
+ });
425
+ }, [
426
+ credentials,
427
+ endpoints,
428
+ headers,
429
+ mock,
430
+ mockStorageKey,
431
+ mockUser,
432
+ requestAdapter,
433
+ resolvedBaseURL
434
+ ]);
247
435
  const handleError = useCallback(
248
436
  (error) => {
249
437
  dispatch({ type: "SET_ERROR", payload: error });
@@ -251,35 +439,65 @@ const AuthProvider = ({
251
439
  },
252
440
  [onAuthError]
253
441
  );
442
+ const clearError = useCallback(() => {
443
+ dispatch({ type: "SET_ERROR", payload: null });
444
+ }, []);
445
+ const setError = useCallback(
446
+ (error) => {
447
+ const nextError = toAuthError(error);
448
+ dispatch({ type: "SET_ERROR", payload: nextError });
449
+ if (nextError) {
450
+ onAuthError?.(nextError);
451
+ }
452
+ },
453
+ [onAuthError]
454
+ );
455
+ const setUser = useCallback((user) => {
456
+ dispatch({ type: "SET_USER", payload: user });
457
+ }, []);
458
+ const resolveAction = useCallback((response) => {
459
+ if (response?.user) {
460
+ dispatch({ type: "SET_USER", payload: response.user });
461
+ return response.user;
462
+ }
463
+ dispatch({ type: "SET_ERROR", payload: null });
464
+ return null;
465
+ }, []);
254
466
  const restoreSession = useCallback(async () => {
255
467
  dispatch({ type: "SET_LOADING", payload: true });
256
468
  try {
257
469
  const response = await client.me();
258
470
  if (response.user) {
259
471
  dispatch({ type: "SET_USER", payload: response.user });
260
- } else {
261
- dispatch({ type: "LOGOUT" });
472
+ return response.user;
262
473
  }
263
- } catch (_error) {
264
- dispatch({ type: "SET_LOADING", payload: false });
474
+ dispatch({ type: "LOGOUT" });
475
+ return null;
476
+ } catch {
477
+ dispatch({ type: "LOGOUT" });
478
+ return null;
265
479
  }
266
480
  }, [client]);
267
481
  const refreshSession = useCallback(async () => {
482
+ dispatch({ type: "SET_LOADING", payload: true });
268
483
  try {
269
484
  const response = await client.refresh();
270
- if (response.user) {
271
- dispatch({ type: "SET_USER", payload: response.user });
272
- }
485
+ return resolveAction(response);
273
486
  } catch (error) {
274
487
  const authError = normalizeError(error);
275
488
  handleError(authError);
489
+ return null;
276
490
  }
277
- }, [client, handleError]);
491
+ }, [client, handleError, resolveAction]);
278
492
  const login = useCallback(
279
493
  async (payload) => {
280
494
  dispatch({ type: "SET_LOADING", payload: true });
281
495
  try {
282
- const response = await client.login(payload.email, payload.password);
496
+ const response = await client.login(
497
+ payload.email,
498
+ payload.password,
499
+ payload.rememberMe
500
+ );
283
501
  if (!response.user) {
284
502
  throw new Error("No user data in response");
285
503
  }
@@ -315,8 +533,7 @@ const AuthProvider = ({
315
533
  dispatch({ type: "SET_LOADING", payload: true });
316
534
  try {
317
535
  await client.logout();
318
- } catch (error) {
319
- console.error("Logout error:", error);
536
+ } catch {
320
537
  } finally {
321
538
  dispatch({ type: "LOGOUT" });
322
539
  }
@@ -325,76 +542,158 @@ const AuthProvider = ({
325
542
  async (payload) => {
326
543
  dispatch({ type: "SET_LOADING", payload: true });
327
544
  try {
328
- await client.forgotPassword(payload.email);
329
- dispatch({ type: "SET_LOADING", payload: false });
545
+ const response = await client.forgotPassword(payload.email);
546
+ resolveAction(response);
547
+ return response;
330
548
  } catch (error) {
331
549
  const authError = normalizeError(error);
332
550
  handleError(authError);
333
551
  throw authError;
334
552
  }
335
553
  },
336
- [client, handleError]
554
+ [client, handleError, resolveAction]
337
555
  );
338
556
  const resetPassword = useCallback(
339
557
  async (payload) => {
340
558
  dispatch({ type: "SET_LOADING", payload: true });
341
559
  try {
342
- await client.resetPassword(
560
+ const response = await client.resetPassword(
343
561
  payload.token,
344
562
  payload.password,
345
563
  payload.confirmPassword
346
564
  );
347
- dispatch({ type: "SET_LOADING", payload: false });
565
+ resolveAction(response);
566
+ return response;
348
567
  } catch (error) {
349
568
  const authError = normalizeError(error);
350
569
  handleError(authError);
351
570
  throw authError;
352
571
  }
353
572
  },
354
- [client, handleError]
573
+ [client, handleError, resolveAction]
355
574
  );
356
575
  const verifyEmail = useCallback(
357
576
  async (payload) => {
358
577
  dispatch({ type: "SET_LOADING", payload: true });
359
578
  try {
360
- await client.verifyEmail(payload.token, payload.email);
361
- dispatch({ type: "SET_LOADING", payload: false });
579
+ const response = await client.verifyEmail(payload.token, payload.email);
580
+ resolveAction(response);
581
+ return response;
362
582
  } catch (error) {
363
583
  const authError = normalizeError(error);
364
584
  handleError(authError);
365
585
  throw authError;
366
586
  }
367
587
  },
368
- [client, handleError]
588
+ [client, handleError, resolveAction]
589
+ );
590
+ const verifyTwoFactor = useCallback(
591
+ async (payload) => {
592
+ dispatch({ type: "SET_LOADING", payload: true });
593
+ try {
594
+ const response = await client.verifyTwoFactor(payload.code);
595
+ resolveAction(response);
596
+ return response;
597
+ } catch (error) {
598
+ const authError = normalizeError(error);
599
+ handleError(authError);
600
+ throw authError;
601
+ }
602
+ },
603
+ [client, handleError, resolveAction]
604
+ );
605
+ const hasRole = useCallback(
606
+ (role) => state.user?.roles?.includes(role) ?? false,
607
+ [state.user]
608
+ );
609
+ const hasAnyRole = useCallback(
610
+ (roles) => roles.some((role) => state.user?.roles?.includes(role)),
611
+ [state.user]
612
+ );
613
+ const hasPermission = useCallback(
614
+ (permission) => state.user?.permissions?.includes(permission) ?? false,
615
+ [state.user]
616
+ );
617
+ const hasAnyPermission = useCallback(
618
+ (permissions) => permissions.some(
619
+ (permission) => state.user?.permissions?.includes(permission)
620
+ ),
621
+ [state.user]
622
+ );
623
+ const hasAllPermissions = useCallback(
624
+ (permissions) => permissions.every(
625
+ (permission) => state.user?.permissions?.includes(permission)
626
+ ),
627
+ [state.user]
369
628
  );
370
- const setUser = useCallback((user) => {
371
- dispatch({ type: "SET_USER", payload: user });
372
- }, []);
373
629
  useEffect(() => {
374
- restoreSession();
375
- }, [restoreSession]);
630
+ if (!autoRestore) {
631
+ return;
632
+ }
633
+ void restoreSession();
634
+ }, [autoRestore, restoreSession]);
376
635
  useEffect(() => {
377
636
  if (!autoRefresh || !state.isAuthenticated) {
378
637
  return;
379
638
  }
380
- const interval = setInterval(() => {
381
- refreshSession();
639
+ const intervalId = window.setInterval(() => {
640
+ void refreshSession();
382
641
  }, refreshInterval);
383
- return () => clearInterval(interval);
384
- }, [autoRefresh, state.isAuthenticated, refreshSession, refreshInterval]);
385
- const value = {
386
- ...state,
387
- login,
388
- register,
389
- logout,
390
- forgotPassword,
391
- resetPassword,
392
- verifyEmail,
393
- refreshSession,
394
- restoreSession,
395
- setUser
396
- };
397
- return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
642
+ return () => window.clearInterval(intervalId);
643
+ }, [autoRefresh, refreshInterval, refreshSession, state.isAuthenticated]);
644
+ const themeStyle = useMemo(
645
+ () => ({
646
+ "--rq-auth-primary": resolvedTheme.primaryColor,
647
+ "--rq-auth-primary-hover": resolvedTheme.primaryHoverColor,
648
+ "--rq-auth-radius": resolvedTheme.radius,
649
+ "--rq-auth-font-family": resolvedTheme.fontFamily,
650
+ fontFamily: resolvedTheme.fontFamily
651
+ }),
652
+ [resolvedTheme.fontFamily, resolvedTheme.primaryColor, resolvedTheme.primaryHoverColor, resolvedTheme.radius]
653
+ );
654
+ const value = useMemo(
655
+ () => ({
656
+ ...state,
657
+ login,
658
+ register,
659
+ logout,
660
+ forgotPassword,
661
+ resetPassword,
662
+ verifyEmail,
663
+ verifyTwoFactor,
664
+ refreshSession,
665
+ restoreSession,
666
+ clearError,
667
+ setError,
668
+ setUser,
669
+ hasRole,
670
+ hasAnyRole,
671
+ hasPermission,
672
+ hasAnyPermission,
673
+ hasAllPermissions
674
+ }),
675
+ [
676
+ clearError,
677
+ forgotPassword,
678
+ hasAllPermissions,
679
+ hasAnyPermission,
680
+ hasAnyRole,
681
+ hasPermission,
682
+ hasRole,
683
+ login,
684
+ logout,
685
+ refreshSession,
686
+ register,
687
+ resetPassword,
688
+ restoreSession,
689
+ setError,
690
+ setUser,
691
+ state,
692
+ verifyEmail,
693
+ verifyTwoFactor
694
+ ]
695
+ );
696
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { className: "rq-auth-theme", style: themeStyle, children }) });
398
697
  };
399
698
  function useAuth() {
400
699
  const context = useContext(AuthContext);
@@ -406,16 +705,53 @@ function useAuth() {
406
705
  function cn(...classes) {
407
706
  return classes.filter(Boolean).join(" ");
408
707
  }
409
- const DEFAULT_CLASSES$4 = {
410
- form: "space-y-5",
411
- field: "space-y-2",
412
- label: "block text-sm font-medium text-slate-700",
413
- input: "w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-100",
414
- button: "w-full rounded-xl bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
415
- error: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600",
416
- title: "text-2xl font-bold text-slate-900",
417
- subtitle: "mt-1 text-sm text-slate-500"
708
+ const AUTH_FORM_CLASSES = {
709
+ container: "rq-auth-container",
710
+ card: "rq-auth-card",
711
+ shell: "rq-auth-shell",
712
+ brand: "rq-auth-brand",
713
+ footer: "rq-auth-footer",
714
+ header: "rq-auth-header",
715
+ form: "rq-auth-form",
716
+ field: "rq-auth-field",
717
+ label: "rq-auth-label",
718
+ input: "rq-auth-input",
719
+ button: "rq-auth-button",
720
+ buttonContent: "rq-auth-button-content",
721
+ buttonSpinner: "rq-auth-button-spinner",
722
+ error: "rq-auth-error",
723
+ success: "rq-auth-success",
724
+ title: "rq-auth-title",
725
+ subtitle: "rq-auth-subtitle",
726
+ checkbox: "rq-auth-checkbox",
727
+ checkboxInput: "rq-auth-checkbox-input",
728
+ checkboxLabel: "rq-auth-checkbox-label",
729
+ otpGroup: "rq-auth-otp-group",
730
+ otpInput: "rq-auth-otp-input",
731
+ socialButton: "rq-auth-social-button",
732
+ socialIcon: "rq-auth-social-icon",
733
+ socialLabel: "rq-auth-social-label"
418
734
  };
735
+ const AuthLayout = ({
736
+ children,
737
+ title,
738
+ subtitle,
739
+ className,
740
+ cardClassName,
741
+ brand,
742
+ footer
743
+ }) => {
744
+ return /* @__PURE__ */ jsx("section", { className: cn(AUTH_FORM_CLASSES.shell, className), children: /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.card, cardClassName), children: [
745
+ brand ? /* @__PURE__ */ jsx("div", { className: AUTH_FORM_CLASSES.brand, children: brand }) : null,
746
+ (title || subtitle) && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
747
+ title ? /* @__PURE__ */ jsx("h1", { className: AUTH_FORM_CLASSES.title, children: title }) : null,
748
+ subtitle ? /* @__PURE__ */ jsx("p", { className: AUTH_FORM_CLASSES.subtitle, children: subtitle }) : null
749
+ ] }),
750
+ children,
751
+ footer ? /* @__PURE__ */ jsx("div", { className: AUTH_FORM_CLASSES.footer, children: footer }) : null
752
+ ] }) });
753
+ };
754
+ AuthLayout.displayName = "AuthLayout";
419
755
  const LoginForm = ({
420
756
  className,
421
757
  formClassName,
@@ -436,7 +772,12 @@ const LoginForm = ({
436
772
  onSuccess,
437
773
  onError
438
774
  }) => {
439
- const { login, isLoading: authLoading, error: authError } = useAuth();
775
+ const {
776
+ login,
777
+ clearError,
778
+ isLoading: authLoading,
779
+ error: authError
780
+ } = useAuth();
440
781
  const [formError, setFormError] = useState("");
441
782
  const [email, setEmail] = useState("");
442
783
  const [password, setPassword] = useState("");
@@ -445,6 +786,7 @@ const LoginForm = ({
445
786
  const error = formError || authError?.message;
446
787
  const handleSubmit = async (e) => {
447
788
  e.preventDefault();
789
+ clearError();
448
790
  setFormError("");
449
791
  if (!email || !password) {
450
792
  setFormError("Email and password are required");
@@ -462,89 +804,93 @@ const LoginForm = ({
462
804
  });
463
805
  }
464
806
  };
465
- return /* @__PURE__ */ jsxs("div", { className, children: [
466
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
467
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES$4.title, titleClassName), children: title }),
468
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES$4.subtitle, subtitleClassName), children: subtitle })
807
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
808
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
809
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
810
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
469
811
  ] }),
470
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: cn(DEFAULT_CLASSES$4.form, formClassName), children: [
471
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$4.field, fieldClassName), children: [
472
- /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(DEFAULT_CLASSES$4.label, labelClassName), children: labels.email || "Email" }),
473
- /* @__PURE__ */ jsx(
474
- "input",
475
- {
476
- id: "email",
477
- type: "email",
478
- value: email,
479
- onChange: (e) => setEmail(e.target.value),
480
- placeholder: placeholders.email || "your@email.com",
481
- disabled: isLoading,
482
- className: cn(DEFAULT_CLASSES$4.input, inputClassName),
483
- required: true
484
- }
485
- )
486
- ] }),
487
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$4.field, fieldClassName), children: [
488
- /* @__PURE__ */ jsx("label", { htmlFor: "password", className: cn(DEFAULT_CLASSES$4.label, labelClassName), children: labels.password || "Password" }),
489
- /* @__PURE__ */ jsx(
490
- "input",
491
- {
492
- id: "password",
493
- type: "password",
494
- value: password,
495
- onChange: (e) => setPassword(e.target.value),
496
- placeholder: placeholders.password || "••••••••",
497
- disabled: isLoading,
498
- className: cn(DEFAULT_CLASSES$4.input, inputClassName),
499
- required: true
500
- }
501
- )
502
- ] }),
503
- /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
504
- /* @__PURE__ */ jsx(
505
- "input",
506
- {
507
- id: "rememberMe",
508
- type: "checkbox",
509
- checked: rememberMe,
510
- onChange: (e) => setRememberMe(e.target.checked),
511
- disabled: isLoading,
512
- className: "h-4 w-4 rounded border-slate-300"
513
- }
514
- ),
515
- /* @__PURE__ */ jsx(
516
- "label",
517
- {
518
- htmlFor: "rememberMe",
519
- className: "ml-2 text-sm text-slate-700",
520
- children: labels.rememberMe || "Remember me"
521
- }
522
- )
523
- ] }),
524
- error && /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES$4.error, errorClassName), children: error }),
525
- /* @__PURE__ */ jsx(
526
- "button",
527
- {
528
- type: "submit",
529
- disabled: isLoading,
530
- className: cn(DEFAULT_CLASSES$4.button, buttonClassName),
531
- children: isLoading ? loadingText : submitButtonText
532
- }
533
- )
534
- ] })
812
+ /* @__PURE__ */ jsxs(
813
+ "form",
814
+ {
815
+ onSubmit: handleSubmit,
816
+ className: cn(AUTH_FORM_CLASSES.form, formClassName),
817
+ "aria-busy": isLoading,
818
+ children: [
819
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
820
+ /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(AUTH_FORM_CLASSES.label, labelClassName), children: labels.email || "Email" }),
821
+ /* @__PURE__ */ jsx(
822
+ "input",
823
+ {
824
+ id: "email",
825
+ type: "email",
826
+ value: email,
827
+ onChange: (e) => setEmail(e.target.value),
828
+ placeholder: placeholders.email || "your@email.com",
829
+ disabled: isLoading,
830
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
831
+ autoComplete: "username",
832
+ required: true
833
+ }
834
+ )
835
+ ] }),
836
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
837
+ /* @__PURE__ */ jsx(
838
+ "label",
839
+ {
840
+ htmlFor: "password",
841
+ className: cn(AUTH_FORM_CLASSES.label, labelClassName),
842
+ children: labels.password || "Password"
843
+ }
844
+ ),
845
+ /* @__PURE__ */ jsx(
846
+ "input",
847
+ {
848
+ id: "password",
849
+ type: "password",
850
+ value: password,
851
+ onChange: (e) => setPassword(e.target.value),
852
+ placeholder: placeholders.password || "Enter your password",
853
+ disabled: isLoading,
854
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
855
+ autoComplete: "current-password",
856
+ required: true
857
+ }
858
+ )
859
+ ] }),
860
+ /* @__PURE__ */ jsxs("label", { htmlFor: "rememberMe", className: AUTH_FORM_CLASSES.checkbox, children: [
861
+ /* @__PURE__ */ jsx(
862
+ "input",
863
+ {
864
+ id: "rememberMe",
865
+ type: "checkbox",
866
+ checked: rememberMe,
867
+ onChange: (e) => setRememberMe(e.target.checked),
868
+ disabled: isLoading,
869
+ className: AUTH_FORM_CLASSES.checkboxInput
870
+ }
871
+ ),
872
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.checkboxLabel, children: labels.rememberMe || "Remember me" })
873
+ ] }),
874
+ error && /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.error, errorClassName), role: "alert", children: error }),
875
+ /* @__PURE__ */ jsx(
876
+ "button",
877
+ {
878
+ type: "submit",
879
+ disabled: isLoading,
880
+ className: cn(AUTH_FORM_CLASSES.button, buttonClassName),
881
+ "aria-busy": isLoading,
882
+ children: isLoading ? /* @__PURE__ */ jsxs("span", { className: AUTH_FORM_CLASSES.buttonContent, children: [
883
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.buttonSpinner, "aria-hidden": "true" }),
884
+ loadingText
885
+ ] }) : submitButtonText
886
+ }
887
+ )
888
+ ]
889
+ }
890
+ )
535
891
  ] });
536
892
  };
537
893
  LoginForm.displayName = "LoginForm";
538
- const DEFAULT_CLASSES$3 = {
539
- form: "space-y-5",
540
- field: "space-y-2",
541
- label: "block text-sm font-medium text-slate-700",
542
- input: "w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-100",
543
- button: "w-full rounded-xl bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
544
- error: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600",
545
- title: "text-2xl font-bold text-slate-900",
546
- subtitle: "mt-1 text-sm text-slate-500"
547
- };
548
894
  const RegisterForm = ({
549
895
  className,
550
896
  formClassName,
@@ -565,7 +911,12 @@ const RegisterForm = ({
565
911
  onSuccess,
566
912
  onError
567
913
  }) => {
568
- const { register, isLoading: authLoading, error: authError } = useAuth();
914
+ const {
915
+ register,
916
+ clearError,
917
+ isLoading: authLoading,
918
+ error: authError
919
+ } = useAuth();
569
920
  const [formError, setFormError] = useState("");
570
921
  const [name, setName] = useState("");
571
922
  const [email, setEmail] = useState("");
@@ -575,6 +926,7 @@ const RegisterForm = ({
575
926
  const error = formError || authError?.message;
576
927
  const handleSubmit = async (e) => {
577
928
  e.preventDefault();
929
+ clearError();
578
930
  setFormError("");
579
931
  if (!name || !email || !password || !confirmPassword) {
580
932
  setFormError("All fields are required");
@@ -605,101 +957,120 @@ const RegisterForm = ({
605
957
  });
606
958
  }
607
959
  };
608
- return /* @__PURE__ */ jsxs("div", { className, children: [
609
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
610
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES$3.title, titleClassName), children: title }),
611
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES$3.subtitle, subtitleClassName), children: subtitle })
960
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
961
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
962
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
963
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
612
964
  ] }),
613
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: cn(DEFAULT_CLASSES$3.form, formClassName), children: [
614
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$3.field, fieldClassName), children: [
615
- /* @__PURE__ */ jsx("label", { htmlFor: "name", className: cn(DEFAULT_CLASSES$3.label, labelClassName), children: labels.name || "Full Name" }),
616
- /* @__PURE__ */ jsx(
617
- "input",
618
- {
619
- id: "name",
620
- type: "text",
621
- value: name,
622
- onChange: (e) => setName(e.target.value),
623
- placeholder: placeholders.name || "John Doe",
624
- disabled: isLoading,
625
- className: cn(DEFAULT_CLASSES$3.input, inputClassName),
626
- required: true
627
- }
628
- )
629
- ] }),
630
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$3.field, fieldClassName), children: [
631
- /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(DEFAULT_CLASSES$3.label, labelClassName), children: labels.email || "Email" }),
632
- /* @__PURE__ */ jsx(
633
- "input",
634
- {
635
- id: "email",
636
- type: "email",
637
- value: email,
638
- onChange: (e) => setEmail(e.target.value),
639
- placeholder: placeholders.email || "your@email.com",
640
- disabled: isLoading,
641
- className: cn(DEFAULT_CLASSES$3.input, inputClassName),
642
- required: true
643
- }
644
- )
645
- ] }),
646
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$3.field, fieldClassName), children: [
647
- /* @__PURE__ */ jsx("label", { htmlFor: "password", className: cn(DEFAULT_CLASSES$3.label, labelClassName), children: labels.password || "Password" }),
648
- /* @__PURE__ */ jsx(
649
- "input",
650
- {
651
- id: "password",
652
- type: "password",
653
- value: password,
654
- onChange: (e) => setPassword(e.target.value),
655
- placeholder: placeholders.password || "••••••••",
656
- disabled: isLoading,
657
- className: cn(DEFAULT_CLASSES$3.input, inputClassName),
658
- required: true
659
- }
660
- )
661
- ] }),
662
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$3.field, fieldClassName), children: [
663
- /* @__PURE__ */ jsx("label", { htmlFor: "confirmPassword", className: cn(DEFAULT_CLASSES$3.label, labelClassName), children: labels.confirmPassword || "Confirm Password" }),
664
- /* @__PURE__ */ jsx(
665
- "input",
666
- {
667
- id: "confirmPassword",
668
- type: "password",
669
- value: confirmPassword,
670
- onChange: (e) => setConfirmPassword(e.target.value),
671
- placeholder: placeholders.confirmPassword || "••••••••",
672
- disabled: isLoading,
673
- className: cn(DEFAULT_CLASSES$3.input, inputClassName),
674
- required: true
675
- }
676
- )
677
- ] }),
678
- error && /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES$3.error, errorClassName), children: error }),
679
- /* @__PURE__ */ jsx(
680
- "button",
681
- {
682
- type: "submit",
683
- disabled: isLoading,
684
- className: cn(DEFAULT_CLASSES$3.button, buttonClassName),
685
- children: isLoading ? loadingText : submitButtonText
686
- }
687
- )
688
- ] })
965
+ /* @__PURE__ */ jsxs(
966
+ "form",
967
+ {
968
+ onSubmit: handleSubmit,
969
+ className: cn(AUTH_FORM_CLASSES.form, formClassName),
970
+ "aria-busy": isLoading,
971
+ children: [
972
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
973
+ /* @__PURE__ */ jsx("label", { htmlFor: "name", className: cn(AUTH_FORM_CLASSES.label, labelClassName), children: labels.name || "Full Name" }),
974
+ /* @__PURE__ */ jsx(
975
+ "input",
976
+ {
977
+ id: "name",
978
+ type: "text",
979
+ value: name,
980
+ onChange: (e) => setName(e.target.value),
981
+ placeholder: placeholders.name || "John Doe",
982
+ disabled: isLoading,
983
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
984
+ autoComplete: "name",
985
+ required: true
986
+ }
987
+ )
988
+ ] }),
989
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
990
+ /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(AUTH_FORM_CLASSES.label, labelClassName), children: labels.email || "Email" }),
991
+ /* @__PURE__ */ jsx(
992
+ "input",
993
+ {
994
+ id: "email",
995
+ type: "email",
996
+ value: email,
997
+ onChange: (e) => setEmail(e.target.value),
998
+ placeholder: placeholders.email || "your@email.com",
999
+ disabled: isLoading,
1000
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1001
+ autoComplete: "email",
1002
+ required: true
1003
+ }
1004
+ )
1005
+ ] }),
1006
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1007
+ /* @__PURE__ */ jsx(
1008
+ "label",
1009
+ {
1010
+ htmlFor: "password",
1011
+ className: cn(AUTH_FORM_CLASSES.label, labelClassName),
1012
+ children: labels.password || "Password"
1013
+ }
1014
+ ),
1015
+ /* @__PURE__ */ jsx(
1016
+ "input",
1017
+ {
1018
+ id: "password",
1019
+ type: "password",
1020
+ value: password,
1021
+ onChange: (e) => setPassword(e.target.value),
1022
+ placeholder: placeholders.password || "Create a password",
1023
+ disabled: isLoading,
1024
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1025
+ autoComplete: "new-password",
1026
+ required: true
1027
+ }
1028
+ )
1029
+ ] }),
1030
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1031
+ /* @__PURE__ */ jsx(
1032
+ "label",
1033
+ {
1034
+ htmlFor: "confirmPassword",
1035
+ className: cn(AUTH_FORM_CLASSES.label, labelClassName),
1036
+ children: labels.confirmPassword || "Confirm Password"
1037
+ }
1038
+ ),
1039
+ /* @__PURE__ */ jsx(
1040
+ "input",
1041
+ {
1042
+ id: "confirmPassword",
1043
+ type: "password",
1044
+ value: confirmPassword,
1045
+ onChange: (e) => setConfirmPassword(e.target.value),
1046
+ placeholder: placeholders.confirmPassword || "Confirm your password",
1047
+ disabled: isLoading,
1048
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1049
+ autoComplete: "new-password",
1050
+ required: true
1051
+ }
1052
+ )
1053
+ ] }),
1054
+ error && /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.error, errorClassName), role: "alert", children: error }),
1055
+ /* @__PURE__ */ jsx(
1056
+ "button",
1057
+ {
1058
+ type: "submit",
1059
+ disabled: isLoading,
1060
+ className: cn(AUTH_FORM_CLASSES.button, buttonClassName),
1061
+ "aria-busy": isLoading,
1062
+ children: isLoading ? /* @__PURE__ */ jsxs("span", { className: AUTH_FORM_CLASSES.buttonContent, children: [
1063
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.buttonSpinner, "aria-hidden": "true" }),
1064
+ loadingText
1065
+ ] }) : submitButtonText
1066
+ }
1067
+ )
1068
+ ]
1069
+ }
1070
+ )
689
1071
  ] });
690
1072
  };
691
1073
  RegisterForm.displayName = "RegisterForm";
692
- const DEFAULT_CLASSES$2 = {
693
- form: "space-y-5",
694
- field: "space-y-2",
695
- label: "block text-sm font-medium text-slate-700",
696
- input: "w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-100",
697
- button: "w-full rounded-xl bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
698
- error: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600",
699
- success: "rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700",
700
- title: "text-2xl font-bold text-slate-900",
701
- subtitle: "mt-1 text-sm text-slate-500"
702
- };
703
1074
  const ForgotPasswordForm = ({
704
1075
  className,
705
1076
  formClassName,
@@ -723,6 +1094,7 @@ const ForgotPasswordForm = ({
723
1094
  }) => {
724
1095
  const {
725
1096
  forgotPassword,
1097
+ clearError,
726
1098
  isLoading: authLoading,
727
1099
  error: authError
728
1100
  } = useAuth();
@@ -733,6 +1105,7 @@ const ForgotPasswordForm = ({
733
1105
  const error = formError || authError?.message;
734
1106
  const handleSubmit = async (e) => {
735
1107
  e.preventDefault();
1108
+ clearError();
736
1109
  setFormError("");
737
1110
  setSuccessMessage("");
738
1111
  if (!email) {
@@ -754,61 +1127,63 @@ const ForgotPasswordForm = ({
754
1127
  }
755
1128
  };
756
1129
  if (successMessage) {
757
- return /* @__PURE__ */ jsxs("div", { className, children: [
758
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
759
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES$2.title, titleClassName), children: title }),
760
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES$2.subtitle, subtitleClassName), children: subtitle })
1130
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1131
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1132
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
1133
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
761
1134
  ] }),
762
- /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES$2.success, successClassName), children: /* @__PURE__ */ jsx("p", { children: successMessage }) })
1135
+ /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.success, successClassName), role: "status", children: /* @__PURE__ */ jsx("p", { children: successMessage }) })
763
1136
  ] });
764
1137
  }
765
- return /* @__PURE__ */ jsxs("div", { className, children: [
766
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
767
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES$2.title, titleClassName), children: title }),
768
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES$2.subtitle, subtitleClassName), children: subtitle })
1138
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1139
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1140
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
1141
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
769
1142
  ] }),
770
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: cn(DEFAULT_CLASSES$2.form, formClassName), children: [
771
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$2.field, fieldClassName), children: [
772
- /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(DEFAULT_CLASSES$2.label, labelClassName), children: labels.email || "Email" }),
773
- /* @__PURE__ */ jsx(
774
- "input",
775
- {
776
- id: "email",
777
- type: "email",
778
- value: email,
779
- onChange: (e) => setEmail(e.target.value),
780
- placeholder: placeholders.email || "your@email.com",
781
- disabled: isLoading,
782
- className: cn(DEFAULT_CLASSES$2.input, inputClassName),
783
- required: true
784
- }
785
- )
786
- ] }),
787
- error && /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES$2.error, errorClassName), children: error }),
788
- /* @__PURE__ */ jsx(
789
- "button",
790
- {
791
- type: "submit",
792
- disabled: isLoading,
793
- className: cn(DEFAULT_CLASSES$2.button, buttonClassName),
794
- children: isLoading ? loadingText : submitButtonText
795
- }
796
- )
797
- ] })
1143
+ /* @__PURE__ */ jsxs(
1144
+ "form",
1145
+ {
1146
+ onSubmit: handleSubmit,
1147
+ className: cn(AUTH_FORM_CLASSES.form, formClassName),
1148
+ "aria-busy": isLoading,
1149
+ children: [
1150
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1151
+ /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(AUTH_FORM_CLASSES.label, labelClassName), children: labels.email || "Email" }),
1152
+ /* @__PURE__ */ jsx(
1153
+ "input",
1154
+ {
1155
+ id: "email",
1156
+ type: "email",
1157
+ value: email,
1158
+ onChange: (e) => setEmail(e.target.value),
1159
+ placeholder: placeholders.email || "your@email.com",
1160
+ disabled: isLoading,
1161
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1162
+ autoComplete: "email",
1163
+ required: true
1164
+ }
1165
+ )
1166
+ ] }),
1167
+ error && /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.error, errorClassName), role: "alert", children: error }),
1168
+ /* @__PURE__ */ jsx(
1169
+ "button",
1170
+ {
1171
+ type: "submit",
1172
+ disabled: isLoading,
1173
+ className: cn(AUTH_FORM_CLASSES.button, buttonClassName),
1174
+ "aria-busy": isLoading,
1175
+ children: isLoading ? /* @__PURE__ */ jsxs("span", { className: AUTH_FORM_CLASSES.buttonContent, children: [
1176
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.buttonSpinner, "aria-hidden": "true" }),
1177
+ loadingText
1178
+ ] }) : submitButtonText
1179
+ }
1180
+ )
1181
+ ]
1182
+ }
1183
+ )
798
1184
  ] });
799
1185
  };
800
1186
  ForgotPasswordForm.displayName = "ForgotPasswordForm";
801
- const DEFAULT_CLASSES$1 = {
802
- form: "space-y-5",
803
- field: "space-y-2",
804
- label: "block text-sm font-medium text-slate-700",
805
- input: "w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-100",
806
- button: "w-full rounded-xl bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
807
- error: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600",
808
- success: "rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700",
809
- title: "text-2xl font-bold text-slate-900",
810
- subtitle: "mt-1 text-sm text-slate-500"
811
- };
812
1187
  const ResetPasswordForm = ({
813
1188
  token,
814
1189
  className,
@@ -833,6 +1208,7 @@ const ResetPasswordForm = ({
833
1208
  }) => {
834
1209
  const {
835
1210
  resetPassword,
1211
+ clearError,
836
1212
  isLoading: authLoading,
837
1213
  error: authError
838
1214
  } = useAuth();
@@ -844,6 +1220,7 @@ const ResetPasswordForm = ({
844
1220
  const error = formError || authError?.message;
845
1221
  const handleSubmit = async (e) => {
846
1222
  e.preventDefault();
1223
+ clearError();
847
1224
  setFormError("");
848
1225
  setSuccessMessage("");
849
1226
  if (!password || !confirmPassword) {
@@ -874,77 +1251,94 @@ const ResetPasswordForm = ({
874
1251
  }
875
1252
  };
876
1253
  if (successMessage) {
877
- return /* @__PURE__ */ jsxs("div", { className, children: [
878
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
879
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES$1.title, titleClassName), children: title }),
880
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES$1.subtitle, subtitleClassName), children: subtitle })
1254
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1255
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1256
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
1257
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
881
1258
  ] }),
882
- /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES$1.success, successClassName), children: /* @__PURE__ */ jsx("p", { children: successMessage }) })
1259
+ /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.success, successClassName), role: "status", children: /* @__PURE__ */ jsx("p", { children: successMessage }) })
883
1260
  ] });
884
1261
  }
885
- return /* @__PURE__ */ jsxs("div", { className, children: [
886
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
887
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES$1.title, titleClassName), children: title }),
888
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES$1.subtitle, subtitleClassName), children: subtitle })
1262
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1263
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1264
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
1265
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
889
1266
  ] }),
890
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: cn(DEFAULT_CLASSES$1.form, formClassName), children: [
891
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$1.field, fieldClassName), children: [
892
- /* @__PURE__ */ jsx("label", { htmlFor: "password", className: cn(DEFAULT_CLASSES$1.label, labelClassName), children: labels.password || "New Password" }),
893
- /* @__PURE__ */ jsx(
894
- "input",
895
- {
896
- id: "password",
897
- type: "password",
898
- value: password,
899
- onChange: (e) => setPassword(e.target.value),
900
- placeholder: placeholders.password || "••••••••",
901
- disabled: isLoading,
902
- className: cn(DEFAULT_CLASSES$1.input, inputClassName),
903
- required: true
904
- }
905
- )
906
- ] }),
907
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES$1.field, fieldClassName), children: [
908
- /* @__PURE__ */ jsx("label", { htmlFor: "confirmPassword", className: cn(DEFAULT_CLASSES$1.label, labelClassName), children: labels.confirmPassword || "Confirm Password" }),
909
- /* @__PURE__ */ jsx(
910
- "input",
911
- {
912
- id: "confirmPassword",
913
- type: "password",
914
- value: confirmPassword,
915
- onChange: (e) => setConfirmPassword(e.target.value),
916
- placeholder: placeholders.confirmPassword || "••••••••",
917
- disabled: isLoading,
918
- className: cn(DEFAULT_CLASSES$1.input, inputClassName),
919
- required: true
920
- }
921
- )
922
- ] }),
923
- error && /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES$1.error, errorClassName), children: error }),
924
- /* @__PURE__ */ jsx(
925
- "button",
926
- {
927
- type: "submit",
928
- disabled: isLoading,
929
- className: cn(DEFAULT_CLASSES$1.button, buttonClassName),
930
- children: isLoading ? loadingText : submitButtonText
931
- }
932
- )
933
- ] })
1267
+ /* @__PURE__ */ jsxs(
1268
+ "form",
1269
+ {
1270
+ onSubmit: handleSubmit,
1271
+ className: cn(AUTH_FORM_CLASSES.form, formClassName),
1272
+ "aria-busy": isLoading,
1273
+ children: [
1274
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1275
+ /* @__PURE__ */ jsx(
1276
+ "label",
1277
+ {
1278
+ htmlFor: "password",
1279
+ className: cn(AUTH_FORM_CLASSES.label, labelClassName),
1280
+ children: labels.password || "New Password"
1281
+ }
1282
+ ),
1283
+ /* @__PURE__ */ jsx(
1284
+ "input",
1285
+ {
1286
+ id: "password",
1287
+ type: "password",
1288
+ value: password,
1289
+ onChange: (e) => setPassword(e.target.value),
1290
+ placeholder: placeholders.password || "Enter a new password",
1291
+ disabled: isLoading,
1292
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1293
+ autoComplete: "new-password",
1294
+ required: true
1295
+ }
1296
+ )
1297
+ ] }),
1298
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1299
+ /* @__PURE__ */ jsx(
1300
+ "label",
1301
+ {
1302
+ htmlFor: "confirmPassword",
1303
+ className: cn(AUTH_FORM_CLASSES.label, labelClassName),
1304
+ children: labels.confirmPassword || "Confirm Password"
1305
+ }
1306
+ ),
1307
+ /* @__PURE__ */ jsx(
1308
+ "input",
1309
+ {
1310
+ id: "confirmPassword",
1311
+ type: "password",
1312
+ value: confirmPassword,
1313
+ onChange: (e) => setConfirmPassword(e.target.value),
1314
+ placeholder: placeholders.confirmPassword || "Confirm your new password",
1315
+ disabled: isLoading,
1316
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1317
+ autoComplete: "new-password",
1318
+ required: true
1319
+ }
1320
+ )
1321
+ ] }),
1322
+ error && /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.error, errorClassName), role: "alert", children: error }),
1323
+ /* @__PURE__ */ jsx(
1324
+ "button",
1325
+ {
1326
+ type: "submit",
1327
+ disabled: isLoading,
1328
+ className: cn(AUTH_FORM_CLASSES.button, buttonClassName),
1329
+ "aria-busy": isLoading,
1330
+ children: isLoading ? /* @__PURE__ */ jsxs("span", { className: AUTH_FORM_CLASSES.buttonContent, children: [
1331
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.buttonSpinner, "aria-hidden": "true" }),
1332
+ loadingText
1333
+ ] }) : submitButtonText
1334
+ }
1335
+ )
1336
+ ]
1337
+ }
1338
+ )
934
1339
  ] });
935
1340
  };
936
1341
  ResetPasswordForm.displayName = "ResetPasswordForm";
937
- const DEFAULT_CLASSES = {
938
- form: "space-y-5",
939
- field: "space-y-2",
940
- label: "block text-sm font-medium text-slate-700",
941
- input: "w-full rounded-xl border border-slate-300 bg-white px-4 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-blue-500 focus:ring-4 focus:ring-blue-100 disabled:cursor-not-allowed disabled:bg-slate-100",
942
- button: "w-full rounded-xl bg-blue-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60",
943
- error: "rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-600",
944
- success: "rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700",
945
- title: "text-2xl font-bold text-slate-900",
946
- subtitle: "mt-1 text-sm text-slate-500"
947
- };
948
1342
  const VerifyEmailForm = ({
949
1343
  token: initialToken,
950
1344
  email: initialEmail,
@@ -970,6 +1364,7 @@ const VerifyEmailForm = ({
970
1364
  }) => {
971
1365
  const {
972
1366
  verifyEmail,
1367
+ clearError,
973
1368
  isLoading: authLoading,
974
1369
  error: authError
975
1370
  } = useAuth();
@@ -979,14 +1374,9 @@ const VerifyEmailForm = ({
979
1374
  const [successMessage, setSuccessMessage] = useState("");
980
1375
  const isLoading = authLoading;
981
1376
  const error = formError || authError?.message;
982
- useEffect(() => {
983
- if (token && email && !successMessage && !error) {
984
- handleSubmit({ preventDefault: () => {
985
- } });
986
- }
987
- }, [token, email]);
988
1377
  const handleSubmit = async (e) => {
989
1378
  e.preventDefault();
1379
+ clearError();
990
1380
  setFormError("");
991
1381
  setSuccessMessage("");
992
1382
  if (!token) {
@@ -1010,106 +1400,357 @@ const VerifyEmailForm = ({
1010
1400
  });
1011
1401
  }
1012
1402
  };
1403
+ useEffect(() => {
1404
+ if (token && email && !successMessage && !error) {
1405
+ void handleSubmit({ preventDefault: () => {
1406
+ } });
1407
+ }
1408
+ }, [token, email]);
1013
1409
  if (successMessage) {
1014
- return /* @__PURE__ */ jsxs("div", { className, children: [
1015
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
1016
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES.title, titleClassName), children: title }),
1017
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES.subtitle, subtitleClassName), children: subtitle })
1410
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1411
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1412
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
1413
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
1018
1414
  ] }),
1019
- /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES.success, successClassName), children: /* @__PURE__ */ jsx("p", { children: successMessage }) })
1415
+ /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.success, successClassName), role: "status", children: /* @__PURE__ */ jsx("p", { children: successMessage }) })
1020
1416
  ] });
1021
1417
  }
1022
- return /* @__PURE__ */ jsxs("div", { className, children: [
1023
- showTitle && title && /* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
1024
- /* @__PURE__ */ jsx("h1", { className: cn(DEFAULT_CLASSES.title, titleClassName), children: title }),
1025
- subtitle && /* @__PURE__ */ jsx("p", { className: cn(DEFAULT_CLASSES.subtitle, subtitleClassName), children: subtitle })
1418
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1419
+ showTitle && title && /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1420
+ /* @__PURE__ */ jsx("h1", { className: cn(AUTH_FORM_CLASSES.title, titleClassName), children: title }),
1421
+ subtitle && /* @__PURE__ */ jsx("p", { className: cn(AUTH_FORM_CLASSES.subtitle, subtitleClassName), children: subtitle })
1026
1422
  ] }),
1027
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: cn(DEFAULT_CLASSES.form, formClassName), children: [
1028
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES.field, fieldClassName), children: [
1029
- /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(DEFAULT_CLASSES.label, labelClassName), children: labels.email || "Email" }),
1030
- /* @__PURE__ */ jsx(
1031
- "input",
1032
- {
1033
- id: "email",
1034
- type: "email",
1035
- value: email,
1036
- onChange: (e) => setEmail(e.target.value),
1037
- placeholder: placeholders.email || "your@email.com",
1038
- disabled: isLoading,
1039
- className: cn(DEFAULT_CLASSES.input, inputClassName),
1040
- required: true
1041
- }
1042
- )
1043
- ] }),
1044
- /* @__PURE__ */ jsxs("div", { className: cn(DEFAULT_CLASSES.field, fieldClassName), children: [
1045
- /* @__PURE__ */ jsx("label", { htmlFor: "token", className: cn(DEFAULT_CLASSES.label, labelClassName), children: labels.token || "Verification Code" }),
1423
+ /* @__PURE__ */ jsxs(
1424
+ "form",
1425
+ {
1426
+ onSubmit: handleSubmit,
1427
+ className: cn(AUTH_FORM_CLASSES.form, formClassName),
1428
+ "aria-busy": isLoading,
1429
+ children: [
1430
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1431
+ /* @__PURE__ */ jsx("label", { htmlFor: "email", className: cn(AUTH_FORM_CLASSES.label, labelClassName), children: labels.email || "Email" }),
1432
+ /* @__PURE__ */ jsx(
1433
+ "input",
1434
+ {
1435
+ id: "email",
1436
+ type: "email",
1437
+ value: email,
1438
+ onChange: (e) => setEmail(e.target.value),
1439
+ placeholder: placeholders.email || "your@email.com",
1440
+ disabled: isLoading,
1441
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1442
+ autoComplete: "email",
1443
+ required: true
1444
+ }
1445
+ )
1446
+ ] }),
1447
+ /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.field, fieldClassName), children: [
1448
+ /* @__PURE__ */ jsx("label", { htmlFor: "token", className: cn(AUTH_FORM_CLASSES.label, labelClassName), children: labels.token || "Verification Code" }),
1449
+ /* @__PURE__ */ jsx(
1450
+ "input",
1451
+ {
1452
+ id: "token",
1453
+ type: "text",
1454
+ value: token,
1455
+ onChange: (e) => setToken(e.target.value),
1456
+ placeholder: placeholders.token || "Enter verification code",
1457
+ disabled: isLoading,
1458
+ className: cn(AUTH_FORM_CLASSES.input, inputClassName),
1459
+ autoComplete: "one-time-code",
1460
+ required: true
1461
+ }
1462
+ )
1463
+ ] }),
1464
+ error && /* @__PURE__ */ jsx("div", { className: cn(AUTH_FORM_CLASSES.error, errorClassName), role: "alert", children: error }),
1465
+ /* @__PURE__ */ jsx(
1466
+ "button",
1467
+ {
1468
+ type: "submit",
1469
+ disabled: isLoading,
1470
+ className: cn(AUTH_FORM_CLASSES.button, buttonClassName),
1471
+ "aria-busy": isLoading,
1472
+ children: isLoading ? /* @__PURE__ */ jsxs("span", { className: AUTH_FORM_CLASSES.buttonContent, children: [
1473
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.buttonSpinner, "aria-hidden": "true" }),
1474
+ loadingText
1475
+ ] }) : submitButtonText
1476
+ }
1477
+ )
1478
+ ]
1479
+ }
1480
+ )
1481
+ ] });
1482
+ };
1483
+ VerifyEmailForm.displayName = "VerifyEmailForm";
1484
+ function toDigits(value, length) {
1485
+ return Array.from({ length }, (_, index) => value[index] ?? "");
1486
+ }
1487
+ const OtpInput = ({
1488
+ length = 6,
1489
+ value,
1490
+ onChange,
1491
+ onComplete,
1492
+ disabled = false,
1493
+ className,
1494
+ inputClassName
1495
+ }) => {
1496
+ const [internalValue, setInternalValue] = useState("");
1497
+ const inputRefs = useRef([]);
1498
+ const baseId = useId();
1499
+ const currentValue = value ?? internalValue;
1500
+ const digits = useMemo(
1501
+ () => toDigits(currentValue.slice(0, length), length),
1502
+ [currentValue, length]
1503
+ );
1504
+ const commitValue = (nextDigits) => {
1505
+ const nextValue = nextDigits.join("").slice(0, length);
1506
+ if (value === void 0) {
1507
+ setInternalValue(nextValue);
1508
+ }
1509
+ onChange?.(nextValue);
1510
+ if (nextDigits.every(Boolean) && nextValue.length === length) {
1511
+ onComplete?.(nextValue);
1512
+ }
1513
+ };
1514
+ const focusInput = (index) => {
1515
+ inputRefs.current[index]?.focus();
1516
+ inputRefs.current[index]?.select();
1517
+ };
1518
+ const handleChange = (index, nextChunk) => {
1519
+ const sanitized = nextChunk.replace(/\s+/g, "");
1520
+ const nextDigits = [...digits];
1521
+ if (!sanitized) {
1522
+ nextDigits[index] = "";
1523
+ commitValue(nextDigits);
1524
+ return;
1525
+ }
1526
+ if (sanitized.length > 1) {
1527
+ sanitized.slice(0, length - index).split("").forEach((character, offset) => {
1528
+ nextDigits[index + offset] = character;
1529
+ });
1530
+ commitValue(nextDigits);
1531
+ focusInput(Math.min(index + sanitized.length, length - 1));
1532
+ return;
1533
+ }
1534
+ nextDigits[index] = sanitized;
1535
+ commitValue(nextDigits);
1536
+ if (index < length - 1) {
1537
+ focusInput(index + 1);
1538
+ }
1539
+ };
1540
+ const handleKeyDown = (index, event) => {
1541
+ if (event.key === "Backspace" && !digits[index] && index > 0) {
1542
+ focusInput(index - 1);
1543
+ }
1544
+ if (event.key === "ArrowLeft" && index > 0) {
1545
+ event.preventDefault();
1546
+ focusInput(index - 1);
1547
+ }
1548
+ if (event.key === "ArrowRight" && index < length - 1) {
1549
+ event.preventDefault();
1550
+ focusInput(index + 1);
1551
+ }
1552
+ };
1553
+ const handlePaste = (index, event) => {
1554
+ event.preventDefault();
1555
+ handleChange(index, event.clipboardData.getData("text"));
1556
+ };
1557
+ return /* @__PURE__ */ jsx(
1558
+ "div",
1559
+ {
1560
+ className: cn(AUTH_FORM_CLASSES.otpGroup, className),
1561
+ style: {
1562
+ "--rq-auth-otp-columns": String(length)
1563
+ },
1564
+ children: digits.map((digit, index) => /* @__PURE__ */ jsx(
1565
+ "input",
1566
+ {
1567
+ ref: (element) => {
1568
+ inputRefs.current[index] = element;
1569
+ },
1570
+ id: `${baseId}-${index}`,
1571
+ type: "text",
1572
+ inputMode: "text",
1573
+ autoComplete: index === 0 ? "one-time-code" : "off",
1574
+ maxLength: 1,
1575
+ value: digit,
1576
+ disabled,
1577
+ className: cn(AUTH_FORM_CLASSES.otpInput, inputClassName),
1578
+ onChange: (event) => handleChange(index, event.target.value),
1579
+ onKeyDown: (event) => handleKeyDown(index, event),
1580
+ onPaste: (event) => handlePaste(index, event),
1581
+ "aria-label": `OTP digit ${index + 1}`
1582
+ },
1583
+ `otp-${index}`
1584
+ ))
1585
+ }
1586
+ );
1587
+ };
1588
+ OtpInput.displayName = "OtpInput";
1589
+ const TwoFactorForm = ({
1590
+ onSuccess,
1591
+ onError,
1592
+ className,
1593
+ submitButtonText = "Verify Code",
1594
+ loadingText = "Verifying...",
1595
+ title = "Two-factor authentication",
1596
+ subtitle = "Enter the verification code to continue.",
1597
+ length = 6
1598
+ }) => {
1599
+ const {
1600
+ verifyTwoFactor,
1601
+ clearError,
1602
+ isLoading,
1603
+ error: authError
1604
+ } = useAuth();
1605
+ const [code, setCode] = useState("");
1606
+ const [formError, setFormError] = useState("");
1607
+ const error = formError || authError?.message;
1608
+ const handleSubmit = async (event) => {
1609
+ event.preventDefault();
1610
+ clearError();
1611
+ setFormError("");
1612
+ if (code.length !== length) {
1613
+ setFormError(`Enter the full ${length}-digit code.`);
1614
+ return;
1615
+ }
1616
+ try {
1617
+ const response = await verifyTwoFactor({ code });
1618
+ onSuccess?.(response);
1619
+ } catch (caughtError) {
1620
+ const message = caughtError instanceof Error ? caughtError.message : "Two-factor verification failed";
1621
+ setFormError(message);
1622
+ onError?.({
1623
+ code: "TWO_FACTOR_ERROR",
1624
+ message
1625
+ });
1626
+ }
1627
+ };
1628
+ return /* @__PURE__ */ jsxs("div", { className: cn(AUTH_FORM_CLASSES.container, className), children: [
1629
+ /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.header, children: [
1630
+ /* @__PURE__ */ jsx("h1", { className: AUTH_FORM_CLASSES.title, children: title }),
1631
+ /* @__PURE__ */ jsx("p", { className: AUTH_FORM_CLASSES.subtitle, children: subtitle })
1632
+ ] }),
1633
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: AUTH_FORM_CLASSES.form, children: [
1634
+ /* @__PURE__ */ jsxs("div", { className: AUTH_FORM_CLASSES.field, children: [
1635
+ /* @__PURE__ */ jsx("label", { className: AUTH_FORM_CLASSES.label, children: "Verification code" }),
1046
1636
  /* @__PURE__ */ jsx(
1047
- "input",
1637
+ OtpInput,
1048
1638
  {
1049
- id: "token",
1050
- type: "text",
1051
- value: token,
1052
- onChange: (e) => setToken(e.target.value),
1053
- placeholder: placeholders.token || "Enter verification code",
1054
- disabled: isLoading,
1055
- className: cn(DEFAULT_CLASSES.input, inputClassName),
1056
- required: true
1639
+ length,
1640
+ value: code,
1641
+ onChange: setCode,
1642
+ disabled: isLoading
1057
1643
  }
1058
1644
  )
1059
1645
  ] }),
1060
- error && /* @__PURE__ */ jsx("div", { className: cn(DEFAULT_CLASSES.error, errorClassName), children: error }),
1646
+ error ? /* @__PURE__ */ jsx("div", { className: AUTH_FORM_CLASSES.error, role: "alert", children: error }) : null,
1061
1647
  /* @__PURE__ */ jsx(
1062
1648
  "button",
1063
1649
  {
1064
1650
  type: "submit",
1065
1651
  disabled: isLoading,
1066
- className: cn(DEFAULT_CLASSES.button, buttonClassName),
1652
+ className: AUTH_FORM_CLASSES.button,
1067
1653
  children: isLoading ? loadingText : submitButtonText
1068
1654
  }
1069
1655
  )
1070
1656
  ] })
1071
1657
  ] });
1072
1658
  };
1073
- VerifyEmailForm.displayName = "VerifyEmailForm";
1659
+ TwoFactorForm.displayName = "TwoFactorForm";
1660
+ const defaultLabels = {
1661
+ google: "Continue with Google",
1662
+ github: "Continue with GitHub",
1663
+ facebook: "Continue with Facebook",
1664
+ custom: "Continue"
1665
+ };
1666
+ const defaultIcons = {
1667
+ google: "G",
1668
+ github: "GH",
1669
+ facebook: "f",
1670
+ custom: "+"
1671
+ };
1672
+ const SocialLoginButton = ({
1673
+ provider,
1674
+ label,
1675
+ icon,
1676
+ onClick,
1677
+ className,
1678
+ disabled = false
1679
+ }) => {
1680
+ return /* @__PURE__ */ jsxs(
1681
+ "button",
1682
+ {
1683
+ type: "button",
1684
+ className: cn(AUTH_FORM_CLASSES.socialButton, className),
1685
+ "data-provider": provider,
1686
+ onClick,
1687
+ disabled,
1688
+ children: [
1689
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.socialIcon, "aria-hidden": "true", children: icon ?? defaultIcons[provider] }),
1690
+ /* @__PURE__ */ jsx("span", { className: AUTH_FORM_CLASSES.socialLabel, children: label ?? defaultLabels[provider] })
1691
+ ]
1692
+ }
1693
+ );
1694
+ };
1695
+ SocialLoginButton.displayName = "SocialLoginButton";
1074
1696
  const ProtectedRoute = ({
1075
1697
  children,
1076
1698
  redirectTo = "/login",
1077
1699
  fallback,
1078
1700
  roles,
1079
- permissions
1701
+ permissions,
1702
+ unauthorizedTo,
1703
+ requireAllPermissions = true
1080
1704
  }) => {
1081
- const { isAuthenticated, isLoading, user } = useAuth();
1705
+ const {
1706
+ isAuthenticated,
1707
+ isLoading,
1708
+ hasAllPermissions,
1709
+ hasAnyPermission,
1710
+ hasAnyRole
1711
+ } = useAuth();
1082
1712
  if (isLoading) {
1083
1713
  return fallback || /* @__PURE__ */ jsx("div", { className: "auth-loading", children: "Loading..." });
1084
1714
  }
1085
1715
  if (!isAuthenticated) {
1086
1716
  return /* @__PURE__ */ jsx(Navigate, { to: redirectTo, replace: true });
1087
1717
  }
1088
- if (roles && roles.length > 0) {
1089
- const hasRole = user?.roles?.some((role) => roles.includes(role));
1090
- if (!hasRole) {
1091
- return /* @__PURE__ */ jsx("div", { className: "auth-forbidden", children: /* @__PURE__ */ jsx("p", { children: "You do not have permission to access this page." }) });
1092
- }
1093
- }
1094
- if (permissions && permissions.length > 0) {
1095
- const hasPermission = user?.permissions?.some(
1096
- (permission) => permissions.includes(permission)
1097
- );
1098
- if (!hasPermission) {
1099
- return /* @__PURE__ */ jsx("div", { className: "auth-forbidden", children: /* @__PURE__ */ jsx("p", { children: "You do not have permission to access this page." }) });
1100
- }
1718
+ const hasRequiredRole = !roles?.length || hasAnyRole(roles);
1719
+ const hasRequiredPermission = !permissions?.length || (requireAllPermissions ? hasAllPermissions(permissions) : hasAnyPermission(permissions));
1720
+ if (!hasRequiredRole || !hasRequiredPermission) {
1721
+ return /* @__PURE__ */ jsx(Navigate, { to: unauthorizedTo ?? redirectTo, replace: true });
1101
1722
  }
1102
1723
  return /* @__PURE__ */ jsx(Fragment, { children });
1103
1724
  };
1104
1725
  ProtectedRoute.displayName = "ProtectedRoute";
1726
+ const GuestRoute = ({
1727
+ children,
1728
+ redirectTo = "/",
1729
+ fallback
1730
+ }) => {
1731
+ const { isAuthenticated, isLoading } = useAuth();
1732
+ if (isLoading) {
1733
+ return fallback || /* @__PURE__ */ jsx("div", { className: "auth-loading", children: "Loading..." });
1734
+ }
1735
+ if (isAuthenticated) {
1736
+ return /* @__PURE__ */ jsx(Navigate, { to: redirectTo, replace: true });
1737
+ }
1738
+ return /* @__PURE__ */ jsx(Fragment, { children });
1739
+ };
1740
+ GuestRoute.displayName = "GuestRoute";
1105
1741
  export {
1106
1742
  AuthContext,
1743
+ AuthLayout,
1107
1744
  AuthProvider,
1108
1745
  ForgotPasswordForm,
1746
+ GuestRoute,
1109
1747
  LoginForm,
1748
+ OtpInput,
1110
1749
  ProtectedRoute,
1111
1750
  RegisterForm,
1112
1751
  ResetPasswordForm,
1752
+ SocialLoginButton,
1753
+ TwoFactorForm,
1113
1754
  VerifyEmailForm,
1114
1755
  cn,
1115
1756
  createAuthClient,