@rqdhw3n/react-auth-flow 1.0.5 → 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,14 +1,6 @@
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
- const authStyles = ".rq-auth-container,\n.rq-auth-container * {\n box-sizing: border-box;\n}\n\n.rq-auth-container {\n --rq-auth-primary: #2563eb;\n --rq-auth-primary-hover: #1d4ed8;\n --rq-auth-primary-soft: rgba(37, 99, 235, 0.16);\n --rq-auth-border: #dbe4f0;\n --rq-auth-border-strong: #c7d4e5;\n --rq-auth-text: #0f172a;\n --rq-auth-muted: #64748b;\n --rq-auth-surface: #ffffff;\n --rq-auth-surface-alt: #f8fbff;\n --rq-auth-disabled: #eef2f7;\n --rq-auth-error: #b91c1c;\n --rq-auth-error-border: #fecaca;\n --rq-auth-error-bg: #fef2f2;\n --rq-auth-success: #047857;\n --rq-auth-success-border: #a7f3d0;\n --rq-auth-success-bg: #ecfdf5;\n width: min(100%, 28rem);\n margin: 0 auto;\n padding: clamp(1.25rem, 3vw, 2rem);\n border: 1px solid var(--rq-auth-border);\n border-radius: 1.5rem;\n background:\n radial-gradient(circle at top, rgba(96, 165, 250, 0.14), transparent 34%),\n linear-gradient(180deg, var(--rq-auth-surface) 0%, var(--rq-auth-surface-alt) 100%);\n box-shadow:\n 0 24px 48px rgba(15, 23, 42, 0.08),\n 0 8px 20px rgba(15, 23, 42, 0.05);\n color: var(--rq-auth-text);\n}\n\n.rq-auth-header {\n margin-bottom: 1.5rem;\n}\n\n.rq-auth-title {\n margin: 0;\n font-size: clamp(1.625rem, 4vw, 2rem);\n font-weight: 700;\n line-height: 1.15;\n letter-spacing: -0.02em;\n color: var(--rq-auth-text);\n}\n\n.rq-auth-subtitle {\n margin: 0.5rem 0 0;\n font-size: 0.95rem;\n line-height: 1.6;\n color: var(--rq-auth-muted);\n}\n\n.rq-auth-form {\n display: grid;\n gap: 1rem;\n}\n\n.rq-auth-field {\n display: grid;\n gap: 0.5rem;\n}\n\n.rq-auth-label {\n font-size: 0.94rem;\n font-weight: 600;\n line-height: 1.4;\n color: var(--rq-auth-text);\n}\n\n.rq-auth-input {\n width: 100%;\n min-width: 0;\n padding: 0.9rem 1rem;\n border: 1px solid var(--rq-auth-border);\n border-radius: 0.95rem;\n background: rgba(255, 255, 255, 0.92);\n color: var(--rq-auth-text);\n font: inherit;\n line-height: 1.5;\n outline: none;\n transition:\n border-color 0.2s ease,\n box-shadow 0.2s ease,\n background-color 0.2s ease,\n transform 0.2s ease;\n box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.03);\n}\n\n.rq-auth-input::placeholder {\n color: #94a3b8;\n}\n\n.rq-auth-input:hover {\n border-color: var(--rq-auth-border-strong);\n}\n\n.rq-auth-input:focus {\n border-color: var(--rq-auth-primary);\n box-shadow:\n 0 0 0 4px var(--rq-auth-primary-soft),\n inset 0 1px 2px rgba(15, 23, 42, 0.03);\n}\n\n.rq-auth-input:disabled {\n cursor: not-allowed;\n background: var(--rq-auth-disabled);\n color: #94a3b8;\n}\n\n.rq-auth-checkbox {\n display: flex;\n align-items: flex-start;\n gap: 0.75rem;\n font-size: 0.94rem;\n line-height: 1.5;\n color: var(--rq-auth-muted);\n}\n\n.rq-auth-checkbox-input {\n width: 1.05rem;\n height: 1.05rem;\n margin: 0.2rem 0 0;\n border-radius: 0.35rem;\n accent-color: var(--rq-auth-primary);\n flex: 0 0 auto;\n}\n\n.rq-auth-checkbox-input:disabled {\n cursor: not-allowed;\n}\n\n.rq-auth-checkbox-label {\n color: var(--rq-auth-muted);\n}\n\n.rq-auth-button {\n width: 100%;\n border: 0;\n border-radius: 0.95rem;\n padding: 0.95rem 1.15rem;\n background: linear-gradient(180deg, #3b82f6 0%, var(--rq-auth-primary) 100%);\n color: #ffffff;\n font: inherit;\n font-size: 0.98rem;\n font-weight: 600;\n line-height: 1.2;\n cursor: pointer;\n transition:\n background 0.2s ease,\n box-shadow 0.2s ease,\n transform 0.2s ease,\n opacity 0.2s ease;\n box-shadow:\n 0 14px 28px rgba(37, 99, 235, 0.22),\n inset 0 1px 0 rgba(255, 255, 255, 0.18);\n}\n\n.rq-auth-button:hover:not(:disabled) {\n background: linear-gradient(180deg, #2563eb 0%, var(--rq-auth-primary-hover) 100%);\n transform: translateY(-1px);\n}\n\n.rq-auth-button:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 4px var(--rq-auth-primary-soft),\n 0 14px 28px rgba(37, 99, 235, 0.22);\n}\n\n.rq-auth-button:disabled {\n cursor: not-allowed;\n opacity: 0.68;\n transform: none;\n box-shadow: none;\n}\n\n.rq-auth-button-content {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.625rem;\n}\n\n.rq-auth-button-spinner {\n width: 1rem;\n height: 1rem;\n border: 2px solid rgba(255, 255, 255, 0.4);\n border-top-color: #ffffff;\n border-radius: 999px;\n animation: rq-auth-spin 0.75s linear infinite;\n}\n\n.rq-auth-error,\n.rq-auth-success,\n.auth-loading,\n.auth-forbidden {\n padding: 0.95rem 1rem;\n border-radius: 1rem;\n border: 1px solid transparent;\n font-size: 0.94rem;\n line-height: 1.55;\n}\n\n.rq-auth-error {\n border-color: var(--rq-auth-error-border);\n background: var(--rq-auth-error-bg);\n color: var(--rq-auth-error);\n}\n\n.rq-auth-success {\n border-color: var(--rq-auth-success-border);\n background: var(--rq-auth-success-bg);\n color: var(--rq-auth-success);\n}\n\n.auth-loading {\n border-color: var(--rq-auth-border);\n background: var(--rq-auth-surface-alt);\n color: var(--rq-auth-muted);\n}\n\n.auth-forbidden {\n border-color: var(--rq-auth-error-border);\n background: var(--rq-auth-error-bg);\n color: var(--rq-auth-error);\n}\n\n@keyframes rq-auth-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (max-width: 640px) {\n .rq-auth-container {\n width: 100%;\n padding: 1.125rem;\n border-radius: 1.25rem;\n }\n\n .rq-auth-form {\n gap: 0.9rem;\n }\n\n .rq-auth-input,\n .rq-auth-button {\n padding-inline: 0.95rem;\n }\n}\n";
5
- const STYLE_ELEMENT_ID = "rq-auth-flow-styles";
6
- if (typeof document !== "undefined" && !document.getElementById(STYLE_ELEMENT_ID)) {
7
- const styleElement = document.createElement("style");
8
- styleElement.id = STYLE_ELEMENT_ID;
9
- styleElement.textContent = authStyles;
10
- document.head.appendChild(styleElement);
11
- }
12
4
  const AuthContext = createContext(
13
5
  void 0
14
6
  );
@@ -70,150 +62,304 @@ const DEFAULT_ENDPOINTS = {
70
62
  refresh: "/auth/refresh",
71
63
  forgotPassword: "/auth/forgot-password",
72
64
  resetPassword: "/auth/reset-password",
73
- verifyEmail: "/auth/verify-email"
74
- };
75
- const defaultAdapter = async (url, options) => {
76
- const response = await fetch(url, options);
77
- return response;
65
+ verifyEmail: "/auth/verify-email",
66
+ twoFactorVerify: "/auth/2fa/verify"
78
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
+ }
79
128
  function createAuthClient(config = {}) {
80
129
  const {
81
- baseURL = "",
130
+ baseURL,
131
+ baseUrl,
82
132
  endpoints = {},
83
- headers = {},
133
+ headers: initialHeaders = {},
84
134
  credentials = "include",
85
- adapter = defaultAdapter
135
+ requestAdapter,
136
+ adapter
86
137
  } = config;
87
- 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
+ }));
88
154
  async function request(method, endpoint, data) {
89
- const url = `${baseURL}${endpoint}`;
90
- const options = {
91
- method,
92
- credentials,
93
- headers: {
94
- "Content-Type": "application/json",
95
- ...headers
96
- }
97
- };
98
- if (data) {
99
- options.body = JSON.stringify(data);
100
- }
101
155
  try {
102
- const response = await adapter(url, options);
103
- if (!response.ok) {
104
- let errorData = null;
105
- try {
106
- errorData = await response.json();
107
- } catch {
108
- errorData = {
109
- error: {
110
- message: response.statusText,
111
- statusCode: response.status
112
- }
113
- };
114
- }
115
- throw errorData;
116
- }
117
- const contentType = response.headers.get("content-type");
118
- if (contentType && contentType.includes("application/json")) {
119
- 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);
120
164
  }
121
- return {};
165
+ return result ?? {};
122
166
  } catch (error) {
123
167
  throw normalizeError(error);
124
168
  }
125
169
  }
126
170
  return {
127
- /**
128
- * Login with email and password
129
- */
130
171
  async login(email, password, rememberMe) {
131
- const response = await request(
132
- "POST",
133
- finalEndpoints.login,
134
- { email, password, rememberMe }
135
- );
136
- return response;
172
+ return request("POST", finalEndpoints.login, {
173
+ email,
174
+ password,
175
+ rememberMe
176
+ });
137
177
  },
138
- /**
139
- * Register a new account
140
- */
141
178
  async register(payload) {
142
- const response = await request(
143
- "POST",
144
- finalEndpoints.register,
145
- payload
146
- );
147
- return response;
179
+ return request("POST", finalEndpoints.register, payload);
148
180
  },
149
- /**
150
- * Logout the user
151
- */
152
181
  async logout() {
153
182
  await request("POST", finalEndpoints.logout);
154
183
  },
155
- /**
156
- * Get current user
157
- */
158
184
  async me() {
159
- const response = await request(
160
- "GET",
161
- finalEndpoints.me
162
- );
163
- return response;
185
+ return request("GET", finalEndpoints.me);
164
186
  },
165
- /**
166
- * Refresh the authentication session
167
- */
168
187
  async refresh() {
169
- const response = await request(
170
- "POST",
171
- finalEndpoints.refresh
172
- );
173
- return response;
188
+ return request("POST", finalEndpoints.refresh);
174
189
  },
175
- /**
176
- * Request a password reset
177
- */
178
190
  async forgotPassword(email) {
179
- await request("POST", finalEndpoints.forgotPassword, { email });
191
+ return request("POST", finalEndpoints.forgotPassword, {
192
+ email
193
+ });
180
194
  },
181
- /**
182
- * Reset password with token
183
- */
184
195
  async resetPassword(token, password, confirmPassword) {
185
- await request("POST", finalEndpoints.resetPassword, {
196
+ return request("POST", finalEndpoints.resetPassword, {
186
197
  token,
187
198
  password,
188
199
  confirmPassword
189
200
  });
190
201
  },
191
- /**
192
- * Verify email with token
193
- */
194
202
  async verifyEmail(token, email) {
195
- 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
+ });
196
212
  },
197
- /**
198
- * Set custom headers for subsequent requests
199
- */
200
213
  setHeaders(newHeaders) {
201
214
  Object.assign(headers, newHeaders);
202
215
  },
203
- /**
204
- * Get current endpoints configuration
205
- */
206
216
  getEndpoints() {
207
217
  return finalEndpoints;
208
218
  }
209
219
  };
210
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
+ }
211
351
  const initialState = {
212
352
  user: null,
213
353
  isAuthenticated: false,
214
354
  isLoading: false,
215
355
  error: null
216
356
  };
357
+ const defaultTheme = {
358
+ primaryColor: "#2563eb",
359
+ primaryHoverColor: "#1d4ed8",
360
+ radius: "14px",
361
+ fontFamily: "inherit"
362
+ };
217
363
  function authReducer(state, action) {
218
364
  switch (action.type) {
219
365
  case "SET_LOADING":
@@ -229,29 +375,63 @@ function authReducer(state, action) {
229
375
  isLoading: false
230
376
  };
231
377
  case "LOGOUT":
232
- return initialState;
378
+ return { ...initialState };
233
379
  default:
234
380
  return state;
235
381
  }
236
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
+ }
237
392
  const AuthProvider = ({
238
393
  children,
239
- baseURL = "",
394
+ baseURL,
395
+ baseUrl,
240
396
  endpoints = {},
397
+ headers = {},
398
+ credentials = "include",
241
399
  onAuthError,
242
400
  autoRefresh = true,
243
- refreshInterval = 5 * 60 * 1e3
244
- // 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
245
408
  }) => {
246
409
  const [state, dispatch] = useReducer(authReducer, initialState);
247
- const clientRef = React.useRef(
248
- createAuthClient({
249
- baseURL,
410
+ const resolvedTheme = { ...defaultTheme, ...theme };
411
+ const resolvedBaseURL = baseURL ?? baseUrl ?? "";
412
+ const client = useMemo(() => {
413
+ const resolvedRequestAdapter = mock ? createMockAuthAdapter({
250
414
  endpoints,
251
- credentials: "include"
252
- })
253
- );
254
- 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
+ ]);
255
435
  const handleError = useCallback(
256
436
  (error) => {
257
437
  dispatch({ type: "SET_ERROR", payload: error });
@@ -259,35 +439,65 @@ const AuthProvider = ({
259
439
  },
260
440
  [onAuthError]
261
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
+ }, []);
262
466
  const restoreSession = useCallback(async () => {
263
467
  dispatch({ type: "SET_LOADING", payload: true });
264
468
  try {
265
469
  const response = await client.me();
266
470
  if (response.user) {
267
471
  dispatch({ type: "SET_USER", payload: response.user });
268
- } else {
269
- dispatch({ type: "LOGOUT" });
472
+ return response.user;
270
473
  }
271
- } catch (_error) {
272
- dispatch({ type: "SET_LOADING", payload: false });
474
+ dispatch({ type: "LOGOUT" });
475
+ return null;
476
+ } catch {
477
+ dispatch({ type: "LOGOUT" });
478
+ return null;
273
479
  }
274
480
  }, [client]);
275
481
  const refreshSession = useCallback(async () => {
482
+ dispatch({ type: "SET_LOADING", payload: true });
276
483
  try {
277
484
  const response = await client.refresh();
278
- if (response.user) {
279
- dispatch({ type: "SET_USER", payload: response.user });
280
- }
485
+ return resolveAction(response);
281
486
  } catch (error) {
282
487
  const authError = normalizeError(error);
283
488
  handleError(authError);
489
+ return null;
284
490
  }
285
- }, [client, handleError]);
491
+ }, [client, handleError, resolveAction]);
286
492
  const login = useCallback(
287
493
  async (payload) => {
288
494
  dispatch({ type: "SET_LOADING", payload: true });
289
495
  try {
290
- 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
+ );
291
501
  if (!response.user) {
292
502
  throw new Error("No user data in response");
293
503
  }
@@ -323,8 +533,7 @@ const AuthProvider = ({
323
533
  dispatch({ type: "SET_LOADING", payload: true });
324
534
  try {
325
535
  await client.logout();
326
- } catch (error) {
327
- console.error("Logout error:", error);
536
+ } catch {
328
537
  } finally {
329
538
  dispatch({ type: "LOGOUT" });
330
539
  }
@@ -333,76 +542,158 @@ const AuthProvider = ({
333
542
  async (payload) => {
334
543
  dispatch({ type: "SET_LOADING", payload: true });
335
544
  try {
336
- await client.forgotPassword(payload.email);
337
- dispatch({ type: "SET_LOADING", payload: false });
545
+ const response = await client.forgotPassword(payload.email);
546
+ resolveAction(response);
547
+ return response;
338
548
  } catch (error) {
339
549
  const authError = normalizeError(error);
340
550
  handleError(authError);
341
551
  throw authError;
342
552
  }
343
553
  },
344
- [client, handleError]
554
+ [client, handleError, resolveAction]
345
555
  );
346
556
  const resetPassword = useCallback(
347
557
  async (payload) => {
348
558
  dispatch({ type: "SET_LOADING", payload: true });
349
559
  try {
350
- await client.resetPassword(
560
+ const response = await client.resetPassword(
351
561
  payload.token,
352
562
  payload.password,
353
563
  payload.confirmPassword
354
564
  );
355
- dispatch({ type: "SET_LOADING", payload: false });
565
+ resolveAction(response);
566
+ return response;
356
567
  } catch (error) {
357
568
  const authError = normalizeError(error);
358
569
  handleError(authError);
359
570
  throw authError;
360
571
  }
361
572
  },
362
- [client, handleError]
573
+ [client, handleError, resolveAction]
363
574
  );
364
575
  const verifyEmail = useCallback(
365
576
  async (payload) => {
366
577
  dispatch({ type: "SET_LOADING", payload: true });
367
578
  try {
368
- await client.verifyEmail(payload.token, payload.email);
369
- dispatch({ type: "SET_LOADING", payload: false });
579
+ const response = await client.verifyEmail(payload.token, payload.email);
580
+ resolveAction(response);
581
+ return response;
370
582
  } catch (error) {
371
583
  const authError = normalizeError(error);
372
584
  handleError(authError);
373
585
  throw authError;
374
586
  }
375
587
  },
376
- [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]
377
628
  );
378
- const setUser = useCallback((user) => {
379
- dispatch({ type: "SET_USER", payload: user });
380
- }, []);
381
629
  useEffect(() => {
382
- restoreSession();
383
- }, [restoreSession]);
630
+ if (!autoRestore) {
631
+ return;
632
+ }
633
+ void restoreSession();
634
+ }, [autoRestore, restoreSession]);
384
635
  useEffect(() => {
385
636
  if (!autoRefresh || !state.isAuthenticated) {
386
637
  return;
387
638
  }
388
- const interval = setInterval(() => {
389
- refreshSession();
639
+ const intervalId = window.setInterval(() => {
640
+ void refreshSession();
390
641
  }, refreshInterval);
391
- return () => clearInterval(interval);
392
- }, [autoRefresh, state.isAuthenticated, refreshSession, refreshInterval]);
393
- const value = {
394
- ...state,
395
- login,
396
- register,
397
- logout,
398
- forgotPassword,
399
- resetPassword,
400
- verifyEmail,
401
- refreshSession,
402
- restoreSession,
403
- setUser
404
- };
405
- 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 }) });
406
697
  };
407
698
  function useAuth() {
408
699
  const context = useContext(AuthContext);
@@ -416,6 +707,10 @@ function cn(...classes) {
416
707
  }
417
708
  const AUTH_FORM_CLASSES = {
418
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",
419
714
  header: "rq-auth-header",
420
715
  form: "rq-auth-form",
421
716
  field: "rq-auth-field",
@@ -430,8 +725,33 @@ const AUTH_FORM_CLASSES = {
430
725
  subtitle: "rq-auth-subtitle",
431
726
  checkbox: "rq-auth-checkbox",
432
727
  checkboxInput: "rq-auth-checkbox-input",
433
- checkboxLabel: "rq-auth-checkbox-label"
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"
434
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";
435
755
  const LoginForm = ({
436
756
  className,
437
757
  formClassName,
@@ -452,7 +772,12 @@ const LoginForm = ({
452
772
  onSuccess,
453
773
  onError
454
774
  }) => {
455
- const { login, isLoading: authLoading, error: authError } = useAuth();
775
+ const {
776
+ login,
777
+ clearError,
778
+ isLoading: authLoading,
779
+ error: authError
780
+ } = useAuth();
456
781
  const [formError, setFormError] = useState("");
457
782
  const [email, setEmail] = useState("");
458
783
  const [password, setPassword] = useState("");
@@ -461,6 +786,7 @@ const LoginForm = ({
461
786
  const error = formError || authError?.message;
462
787
  const handleSubmit = async (e) => {
463
788
  e.preventDefault();
789
+ clearError();
464
790
  setFormError("");
465
791
  if (!email || !password) {
466
792
  setFormError("Email and password are required");
@@ -585,7 +911,12 @@ const RegisterForm = ({
585
911
  onSuccess,
586
912
  onError
587
913
  }) => {
588
- const { register, isLoading: authLoading, error: authError } = useAuth();
914
+ const {
915
+ register,
916
+ clearError,
917
+ isLoading: authLoading,
918
+ error: authError
919
+ } = useAuth();
589
920
  const [formError, setFormError] = useState("");
590
921
  const [name, setName] = useState("");
591
922
  const [email, setEmail] = useState("");
@@ -595,6 +926,7 @@ const RegisterForm = ({
595
926
  const error = formError || authError?.message;
596
927
  const handleSubmit = async (e) => {
597
928
  e.preventDefault();
929
+ clearError();
598
930
  setFormError("");
599
931
  if (!name || !email || !password || !confirmPassword) {
600
932
  setFormError("All fields are required");
@@ -762,6 +1094,7 @@ const ForgotPasswordForm = ({
762
1094
  }) => {
763
1095
  const {
764
1096
  forgotPassword,
1097
+ clearError,
765
1098
  isLoading: authLoading,
766
1099
  error: authError
767
1100
  } = useAuth();
@@ -772,6 +1105,7 @@ const ForgotPasswordForm = ({
772
1105
  const error = formError || authError?.message;
773
1106
  const handleSubmit = async (e) => {
774
1107
  e.preventDefault();
1108
+ clearError();
775
1109
  setFormError("");
776
1110
  setSuccessMessage("");
777
1111
  if (!email) {
@@ -874,6 +1208,7 @@ const ResetPasswordForm = ({
874
1208
  }) => {
875
1209
  const {
876
1210
  resetPassword,
1211
+ clearError,
877
1212
  isLoading: authLoading,
878
1213
  error: authError
879
1214
  } = useAuth();
@@ -885,6 +1220,7 @@ const ResetPasswordForm = ({
885
1220
  const error = formError || authError?.message;
886
1221
  const handleSubmit = async (e) => {
887
1222
  e.preventDefault();
1223
+ clearError();
888
1224
  setFormError("");
889
1225
  setSuccessMessage("");
890
1226
  if (!password || !confirmPassword) {
@@ -1028,6 +1364,7 @@ const VerifyEmailForm = ({
1028
1364
  }) => {
1029
1365
  const {
1030
1366
  verifyEmail,
1367
+ clearError,
1031
1368
  isLoading: authLoading,
1032
1369
  error: authError
1033
1370
  } = useAuth();
@@ -1039,6 +1376,7 @@ const VerifyEmailForm = ({
1039
1376
  const error = formError || authError?.message;
1040
1377
  const handleSubmit = async (e) => {
1041
1378
  e.preventDefault();
1379
+ clearError();
1042
1380
  setFormError("");
1043
1381
  setSuccessMessage("");
1044
1382
  if (!token) {
@@ -1143,45 +1481,276 @@ const VerifyEmailForm = ({
1143
1481
  ] });
1144
1482
  };
1145
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" }),
1636
+ /* @__PURE__ */ jsx(
1637
+ OtpInput,
1638
+ {
1639
+ length,
1640
+ value: code,
1641
+ onChange: setCode,
1642
+ disabled: isLoading
1643
+ }
1644
+ )
1645
+ ] }),
1646
+ error ? /* @__PURE__ */ jsx("div", { className: AUTH_FORM_CLASSES.error, role: "alert", children: error }) : null,
1647
+ /* @__PURE__ */ jsx(
1648
+ "button",
1649
+ {
1650
+ type: "submit",
1651
+ disabled: isLoading,
1652
+ className: AUTH_FORM_CLASSES.button,
1653
+ children: isLoading ? loadingText : submitButtonText
1654
+ }
1655
+ )
1656
+ ] })
1657
+ ] });
1658
+ };
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";
1146
1696
  const ProtectedRoute = ({
1147
1697
  children,
1148
1698
  redirectTo = "/login",
1149
1699
  fallback,
1150
1700
  roles,
1151
- permissions
1701
+ permissions,
1702
+ unauthorizedTo,
1703
+ requireAllPermissions = true
1152
1704
  }) => {
1153
- const { isAuthenticated, isLoading, user } = useAuth();
1705
+ const {
1706
+ isAuthenticated,
1707
+ isLoading,
1708
+ hasAllPermissions,
1709
+ hasAnyPermission,
1710
+ hasAnyRole
1711
+ } = useAuth();
1154
1712
  if (isLoading) {
1155
1713
  return fallback || /* @__PURE__ */ jsx("div", { className: "auth-loading", children: "Loading..." });
1156
1714
  }
1157
1715
  if (!isAuthenticated) {
1158
1716
  return /* @__PURE__ */ jsx(Navigate, { to: redirectTo, replace: true });
1159
1717
  }
1160
- if (roles && roles.length > 0) {
1161
- const hasRole = user?.roles?.some((role) => roles.includes(role));
1162
- if (!hasRole) {
1163
- return /* @__PURE__ */ jsx("div", { className: "auth-forbidden", children: /* @__PURE__ */ jsx("p", { children: "You do not have permission to access this page." }) });
1164
- }
1165
- }
1166
- if (permissions && permissions.length > 0) {
1167
- const hasPermission = user?.permissions?.some(
1168
- (permission) => permissions.includes(permission)
1169
- );
1170
- if (!hasPermission) {
1171
- return /* @__PURE__ */ jsx("div", { className: "auth-forbidden", children: /* @__PURE__ */ jsx("p", { children: "You do not have permission to access this page." }) });
1172
- }
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 });
1173
1722
  }
1174
1723
  return /* @__PURE__ */ jsx(Fragment, { children });
1175
1724
  };
1176
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";
1177
1741
  export {
1178
1742
  AuthContext,
1743
+ AuthLayout,
1179
1744
  AuthProvider,
1180
1745
  ForgotPasswordForm,
1746
+ GuestRoute,
1181
1747
  LoginForm,
1748
+ OtpInput,
1182
1749
  ProtectedRoute,
1183
1750
  RegisterForm,
1184
1751
  ResetPasswordForm,
1752
+ SocialLoginButton,
1753
+ TwoFactorForm,
1185
1754
  VerifyEmailForm,
1186
1755
  cn,
1187
1756
  createAuthClient,