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