@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.cjs.js CHANGED
@@ -1,17 +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 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";
7
- const STYLE_ELEMENT_ID = "rq-auth-flow-styles";
8
- if (typeof document !== "undefined" && !document.getElementById(STYLE_ELEMENT_ID)) {
9
- const styleElement = document.createElement("style");
10
- styleElement.id = STYLE_ELEMENT_ID;
11
- styleElement.textContent = authStyles;
12
- document.head.appendChild(styleElement);
13
- }
14
- const AuthContext = React.createContext(
6
+ const AuthContext = react.createContext(
15
7
  void 0
16
8
  );
17
9
  AuthContext.displayName = "AuthContext";
@@ -72,150 +64,304 @@ const DEFAULT_ENDPOINTS = {
72
64
  refresh: "/auth/refresh",
73
65
  forgotPassword: "/auth/forgot-password",
74
66
  resetPassword: "/auth/reset-password",
75
- verifyEmail: "/auth/verify-email"
76
- };
77
- const defaultAdapter = async (url, options) => {
78
- const response = await fetch(url, options);
79
- return response;
67
+ verifyEmail: "/auth/verify-email",
68
+ twoFactorVerify: "/auth/2fa/verify"
80
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
+ }
81
130
  function createAuthClient(config = {}) {
82
131
  const {
83
- baseURL = "",
132
+ baseURL,
133
+ baseUrl,
84
134
  endpoints = {},
85
- headers = {},
135
+ headers: initialHeaders = {},
86
136
  credentials = "include",
87
- adapter = defaultAdapter
137
+ requestAdapter,
138
+ adapter
88
139
  } = config;
89
- 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
+ }));
90
156
  async function request(method, endpoint, data) {
91
- const url = `${baseURL}${endpoint}`;
92
- const options = {
93
- method,
94
- credentials,
95
- headers: {
96
- "Content-Type": "application/json",
97
- ...headers
98
- }
99
- };
100
- if (data) {
101
- options.body = JSON.stringify(data);
102
- }
103
157
  try {
104
- const response = await adapter(url, options);
105
- if (!response.ok) {
106
- let errorData = null;
107
- try {
108
- errorData = await response.json();
109
- } catch {
110
- errorData = {
111
- error: {
112
- message: response.statusText,
113
- statusCode: response.status
114
- }
115
- };
116
- }
117
- throw errorData;
118
- }
119
- const contentType = response.headers.get("content-type");
120
- if (contentType && contentType.includes("application/json")) {
121
- 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);
122
166
  }
123
- return {};
167
+ return result ?? {};
124
168
  } catch (error) {
125
169
  throw normalizeError(error);
126
170
  }
127
171
  }
128
172
  return {
129
- /**
130
- * Login with email and password
131
- */
132
173
  async login(email, password, rememberMe) {
133
- const response = await request(
134
- "POST",
135
- finalEndpoints.login,
136
- { email, password, rememberMe }
137
- );
138
- return response;
174
+ return request("POST", finalEndpoints.login, {
175
+ email,
176
+ password,
177
+ rememberMe
178
+ });
139
179
  },
140
- /**
141
- * Register a new account
142
- */
143
180
  async register(payload) {
144
- const response = await request(
145
- "POST",
146
- finalEndpoints.register,
147
- payload
148
- );
149
- return response;
181
+ return request("POST", finalEndpoints.register, payload);
150
182
  },
151
- /**
152
- * Logout the user
153
- */
154
183
  async logout() {
155
184
  await request("POST", finalEndpoints.logout);
156
185
  },
157
- /**
158
- * Get current user
159
- */
160
186
  async me() {
161
- const response = await request(
162
- "GET",
163
- finalEndpoints.me
164
- );
165
- return response;
187
+ return request("GET", finalEndpoints.me);
166
188
  },
167
- /**
168
- * Refresh the authentication session
169
- */
170
189
  async refresh() {
171
- const response = await request(
172
- "POST",
173
- finalEndpoints.refresh
174
- );
175
- return response;
190
+ return request("POST", finalEndpoints.refresh);
176
191
  },
177
- /**
178
- * Request a password reset
179
- */
180
192
  async forgotPassword(email) {
181
- await request("POST", finalEndpoints.forgotPassword, { email });
193
+ return request("POST", finalEndpoints.forgotPassword, {
194
+ email
195
+ });
182
196
  },
183
- /**
184
- * Reset password with token
185
- */
186
197
  async resetPassword(token, password, confirmPassword) {
187
- await request("POST", finalEndpoints.resetPassword, {
198
+ return request("POST", finalEndpoints.resetPassword, {
188
199
  token,
189
200
  password,
190
201
  confirmPassword
191
202
  });
192
203
  },
193
- /**
194
- * Verify email with token
195
- */
196
204
  async verifyEmail(token, email) {
197
- 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
+ });
198
214
  },
199
- /**
200
- * Set custom headers for subsequent requests
201
- */
202
215
  setHeaders(newHeaders) {
203
216
  Object.assign(headers, newHeaders);
204
217
  },
205
- /**
206
- * Get current endpoints configuration
207
- */
208
218
  getEndpoints() {
209
219
  return finalEndpoints;
210
220
  }
211
221
  };
212
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
+ }
213
353
  const initialState = {
214
354
  user: null,
215
355
  isAuthenticated: false,
216
356
  isLoading: false,
217
357
  error: null
218
358
  };
359
+ const defaultTheme = {
360
+ primaryColor: "#2563eb",
361
+ primaryHoverColor: "#1d4ed8",
362
+ radius: "14px",
363
+ fontFamily: "inherit"
364
+ };
219
365
  function authReducer(state, action) {
220
366
  switch (action.type) {
221
367
  case "SET_LOADING":
@@ -231,65 +377,129 @@ function authReducer(state, action) {
231
377
  isLoading: false
232
378
  };
233
379
  case "LOGOUT":
234
- return initialState;
380
+ return { ...initialState };
235
381
  default:
236
382
  return state;
237
383
  }
238
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
+ }
239
394
  const AuthProvider = ({
240
395
  children,
241
- baseURL = "",
396
+ baseURL,
397
+ baseUrl,
242
398
  endpoints = {},
399
+ headers = {},
400
+ credentials = "include",
243
401
  onAuthError,
244
402
  autoRefresh = true,
245
- refreshInterval = 5 * 60 * 1e3
246
- // 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
247
410
  }) => {
248
- const [state, dispatch] = React.useReducer(authReducer, initialState);
249
- const clientRef = React.useRef(
250
- createAuthClient({
251
- 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({
252
416
  endpoints,
253
- credentials: "include"
254
- })
255
- );
256
- const client = clientRef.current;
257
- 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(
258
438
  (error) => {
259
439
  dispatch({ type: "SET_ERROR", payload: error });
260
440
  onAuthError?.(error);
261
441
  },
262
442
  [onAuthError]
263
443
  );
264
- 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 () => {
265
469
  dispatch({ type: "SET_LOADING", payload: true });
266
470
  try {
267
471
  const response = await client.me();
268
472
  if (response.user) {
269
473
  dispatch({ type: "SET_USER", payload: response.user });
270
- } else {
271
- dispatch({ type: "LOGOUT" });
474
+ return response.user;
272
475
  }
273
- } catch (_error) {
274
- dispatch({ type: "SET_LOADING", payload: false });
476
+ dispatch({ type: "LOGOUT" });
477
+ return null;
478
+ } catch {
479
+ dispatch({ type: "LOGOUT" });
480
+ return null;
275
481
  }
276
482
  }, [client]);
277
- const refreshSession = React.useCallback(async () => {
483
+ const refreshSession = react.useCallback(async () => {
484
+ dispatch({ type: "SET_LOADING", payload: true });
278
485
  try {
279
486
  const response = await client.refresh();
280
- if (response.user) {
281
- dispatch({ type: "SET_USER", payload: response.user });
282
- }
487
+ return resolveAction(response);
283
488
  } catch (error) {
284
489
  const authError = normalizeError(error);
285
490
  handleError(authError);
491
+ return null;
286
492
  }
287
- }, [client, handleError]);
288
- const login = React.useCallback(
493
+ }, [client, handleError, resolveAction]);
494
+ const login = react.useCallback(
289
495
  async (payload) => {
290
496
  dispatch({ type: "SET_LOADING", payload: true });
291
497
  try {
292
- 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
+ );
293
503
  if (!response.user) {
294
504
  throw new Error("No user data in response");
295
505
  }
@@ -303,7 +513,7 @@ const AuthProvider = ({
303
513
  },
304
514
  [client, handleError]
305
515
  );
306
- const register = React.useCallback(
516
+ const register = react.useCallback(
307
517
  async (payload) => {
308
518
  dispatch({ type: "SET_LOADING", payload: true });
309
519
  try {
@@ -321,93 +531,174 @@ const AuthProvider = ({
321
531
  },
322
532
  [client, handleError]
323
533
  );
324
- const logout = React.useCallback(async () => {
534
+ const logout = react.useCallback(async () => {
325
535
  dispatch({ type: "SET_LOADING", payload: true });
326
536
  try {
327
537
  await client.logout();
328
- } catch (error) {
329
- console.error("Logout error:", error);
538
+ } catch {
330
539
  } finally {
331
540
  dispatch({ type: "LOGOUT" });
332
541
  }
333
542
  }, [client]);
334
- const forgotPassword = React.useCallback(
543
+ const forgotPassword = react.useCallback(
335
544
  async (payload) => {
336
545
  dispatch({ type: "SET_LOADING", payload: true });
337
546
  try {
338
- await client.forgotPassword(payload.email);
339
- dispatch({ type: "SET_LOADING", payload: false });
547
+ const response = await client.forgotPassword(payload.email);
548
+ resolveAction(response);
549
+ return response;
340
550
  } catch (error) {
341
551
  const authError = normalizeError(error);
342
552
  handleError(authError);
343
553
  throw authError;
344
554
  }
345
555
  },
346
- [client, handleError]
556
+ [client, handleError, resolveAction]
347
557
  );
348
- const resetPassword = React.useCallback(
558
+ const resetPassword = react.useCallback(
349
559
  async (payload) => {
350
560
  dispatch({ type: "SET_LOADING", payload: true });
351
561
  try {
352
- await client.resetPassword(
562
+ const response = await client.resetPassword(
353
563
  payload.token,
354
564
  payload.password,
355
565
  payload.confirmPassword
356
566
  );
357
- dispatch({ type: "SET_LOADING", payload: false });
567
+ resolveAction(response);
568
+ return response;
358
569
  } catch (error) {
359
570
  const authError = normalizeError(error);
360
571
  handleError(authError);
361
572
  throw authError;
362
573
  }
363
574
  },
364
- [client, handleError]
575
+ [client, handleError, resolveAction]
365
576
  );
366
- const verifyEmail = React.useCallback(
577
+ const verifyEmail = react.useCallback(
367
578
  async (payload) => {
368
579
  dispatch({ type: "SET_LOADING", payload: true });
369
580
  try {
370
- await client.verifyEmail(payload.token, payload.email);
371
- dispatch({ type: "SET_LOADING", payload: false });
581
+ const response = await client.verifyEmail(payload.token, payload.email);
582
+ resolveAction(response);
583
+ return response;
372
584
  } catch (error) {
373
585
  const authError = normalizeError(error);
374
586
  handleError(authError);
375
587
  throw authError;
376
588
  }
377
589
  },
378
- [client, handleError]
590
+ [client, handleError, resolveAction]
379
591
  );
380
- const setUser = React.useCallback((user) => {
381
- dispatch({ type: "SET_USER", payload: user });
382
- }, []);
383
- React.useEffect(() => {
384
- restoreSession();
385
- }, [restoreSession]);
386
- 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(() => {
387
638
  if (!autoRefresh || !state.isAuthenticated) {
388
639
  return;
389
640
  }
390
- const interval = setInterval(() => {
391
- refreshSession();
641
+ const intervalId = window.setInterval(() => {
642
+ void refreshSession();
392
643
  }, refreshInterval);
393
- return () => clearInterval(interval);
394
- }, [autoRefresh, state.isAuthenticated, refreshSession, refreshInterval]);
395
- const value = {
396
- ...state,
397
- login,
398
- register,
399
- logout,
400
- forgotPassword,
401
- resetPassword,
402
- verifyEmail,
403
- refreshSession,
404
- restoreSession,
405
- setUser
406
- };
407
- 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 }) });
408
699
  };
409
700
  function useAuth() {
410
- const context = React.useContext(AuthContext);
701
+ const context = react.useContext(AuthContext);
411
702
  if (!context) {
412
703
  throw new Error("useAuth must be used within an AuthProvider");
413
704
  }
@@ -418,6 +709,10 @@ function cn(...classes) {
418
709
  }
419
710
  const AUTH_FORM_CLASSES = {
420
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",
421
716
  header: "rq-auth-header",
422
717
  form: "rq-auth-form",
423
718
  field: "rq-auth-field",
@@ -432,8 +727,33 @@ const AUTH_FORM_CLASSES = {
432
727
  subtitle: "rq-auth-subtitle",
433
728
  checkbox: "rq-auth-checkbox",
434
729
  checkboxInput: "rq-auth-checkbox-input",
435
- checkboxLabel: "rq-auth-checkbox-label"
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"
436
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";
437
757
  const LoginForm = ({
438
758
  className,
439
759
  formClassName,
@@ -454,15 +774,21 @@ const LoginForm = ({
454
774
  onSuccess,
455
775
  onError
456
776
  }) => {
457
- const { login, isLoading: authLoading, error: authError } = useAuth();
458
- const [formError, setFormError] = React.useState("");
459
- const [email, setEmail] = React.useState("");
460
- const [password, setPassword] = React.useState("");
461
- 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);
462
787
  const isLoading = authLoading;
463
788
  const error = formError || authError?.message;
464
789
  const handleSubmit = async (e) => {
465
790
  e.preventDefault();
791
+ clearError();
466
792
  setFormError("");
467
793
  if (!email || !password) {
468
794
  setFormError("Email and password are required");
@@ -587,16 +913,22 @@ const RegisterForm = ({
587
913
  onSuccess,
588
914
  onError
589
915
  }) => {
590
- const { register, isLoading: authLoading, error: authError } = useAuth();
591
- const [formError, setFormError] = React.useState("");
592
- const [name, setName] = React.useState("");
593
- const [email, setEmail] = React.useState("");
594
- const [password, setPassword] = React.useState("");
595
- 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("");
596
927
  const isLoading = authLoading;
597
928
  const error = formError || authError?.message;
598
929
  const handleSubmit = async (e) => {
599
930
  e.preventDefault();
931
+ clearError();
600
932
  setFormError("");
601
933
  if (!name || !email || !password || !confirmPassword) {
602
934
  setFormError("All fields are required");
@@ -764,16 +1096,18 @@ const ForgotPasswordForm = ({
764
1096
  }) => {
765
1097
  const {
766
1098
  forgotPassword,
1099
+ clearError,
767
1100
  isLoading: authLoading,
768
1101
  error: authError
769
1102
  } = useAuth();
770
- const [formError, setFormError] = React.useState("");
771
- const [email, setEmail] = React.useState("");
772
- const [successMessage, setSuccessMessage] = React.useState("");
1103
+ const [formError, setFormError] = react.useState("");
1104
+ const [email, setEmail] = react.useState("");
1105
+ const [successMessage, setSuccessMessage] = react.useState("");
773
1106
  const isLoading = authLoading;
774
1107
  const error = formError || authError?.message;
775
1108
  const handleSubmit = async (e) => {
776
1109
  e.preventDefault();
1110
+ clearError();
777
1111
  setFormError("");
778
1112
  setSuccessMessage("");
779
1113
  if (!email) {
@@ -876,17 +1210,19 @@ const ResetPasswordForm = ({
876
1210
  }) => {
877
1211
  const {
878
1212
  resetPassword,
1213
+ clearError,
879
1214
  isLoading: authLoading,
880
1215
  error: authError
881
1216
  } = useAuth();
882
- const [formError, setFormError] = React.useState("");
883
- const [password, setPassword] = React.useState("");
884
- const [confirmPassword, setConfirmPassword] = React.useState("");
885
- 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("");
886
1221
  const isLoading = authLoading;
887
1222
  const error = formError || authError?.message;
888
1223
  const handleSubmit = async (e) => {
889
1224
  e.preventDefault();
1225
+ clearError();
890
1226
  setFormError("");
891
1227
  setSuccessMessage("");
892
1228
  if (!password || !confirmPassword) {
@@ -1030,17 +1366,19 @@ const VerifyEmailForm = ({
1030
1366
  }) => {
1031
1367
  const {
1032
1368
  verifyEmail,
1369
+ clearError,
1033
1370
  isLoading: authLoading,
1034
1371
  error: authError
1035
1372
  } = useAuth();
1036
- const [formError, setFormError] = React.useState("");
1037
- const [email, setEmail] = React.useState(initialEmail || "");
1038
- const [token, setToken] = React.useState(initialToken || "");
1039
- 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("");
1040
1377
  const isLoading = authLoading;
1041
1378
  const error = formError || authError?.message;
1042
1379
  const handleSubmit = async (e) => {
1043
1380
  e.preventDefault();
1381
+ clearError();
1044
1382
  setFormError("");
1045
1383
  setSuccessMessage("");
1046
1384
  if (!token) {
@@ -1064,7 +1402,7 @@ const VerifyEmailForm = ({
1064
1402
  });
1065
1403
  }
1066
1404
  };
1067
- React.useEffect(() => {
1405
+ react.useEffect(() => {
1068
1406
  if (token && email && !successMessage && !error) {
1069
1407
  void handleSubmit({ preventDefault: () => {
1070
1408
  } });
@@ -1145,44 +1483,275 @@ const VerifyEmailForm = ({
1145
1483
  ] });
1146
1484
  };
1147
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" }),
1638
+ /* @__PURE__ */ jsxRuntime.jsx(
1639
+ OtpInput,
1640
+ {
1641
+ length,
1642
+ value: code,
1643
+ onChange: setCode,
1644
+ disabled: isLoading
1645
+ }
1646
+ )
1647
+ ] }),
1648
+ error ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: AUTH_FORM_CLASSES.error, role: "alert", children: error }) : null,
1649
+ /* @__PURE__ */ jsxRuntime.jsx(
1650
+ "button",
1651
+ {
1652
+ type: "submit",
1653
+ disabled: isLoading,
1654
+ className: AUTH_FORM_CLASSES.button,
1655
+ children: isLoading ? loadingText : submitButtonText
1656
+ }
1657
+ )
1658
+ ] })
1659
+ ] });
1660
+ };
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";
1148
1698
  const ProtectedRoute = ({
1149
1699
  children,
1150
1700
  redirectTo = "/login",
1151
1701
  fallback,
1152
1702
  roles,
1153
- permissions
1703
+ permissions,
1704
+ unauthorizedTo,
1705
+ requireAllPermissions = true
1154
1706
  }) => {
1155
- const { isAuthenticated, isLoading, user } = useAuth();
1707
+ const {
1708
+ isAuthenticated,
1709
+ isLoading,
1710
+ hasAllPermissions,
1711
+ hasAnyPermission,
1712
+ hasAnyRole
1713
+ } = useAuth();
1156
1714
  if (isLoading) {
1157
1715
  return fallback || /* @__PURE__ */ jsxRuntime.jsx("div", { className: "auth-loading", children: "Loading..." });
1158
1716
  }
1159
1717
  if (!isAuthenticated) {
1160
1718
  return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: redirectTo, replace: true });
1161
1719
  }
1162
- if (roles && roles.length > 0) {
1163
- const hasRole = user?.roles?.some((role) => roles.includes(role));
1164
- if (!hasRole) {
1165
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "auth-forbidden", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "You do not have permission to access this page." }) });
1166
- }
1167
- }
1168
- if (permissions && permissions.length > 0) {
1169
- const hasPermission = user?.permissions?.some(
1170
- (permission) => permissions.includes(permission)
1171
- );
1172
- if (!hasPermission) {
1173
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "auth-forbidden", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "You do not have permission to access this page." }) });
1174
- }
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 });
1175
1724
  }
1176
1725
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
1177
1726
  };
1178
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";
1179
1743
  exports.AuthContext = AuthContext;
1744
+ exports.AuthLayout = AuthLayout;
1180
1745
  exports.AuthProvider = AuthProvider;
1181
1746
  exports.ForgotPasswordForm = ForgotPasswordForm;
1747
+ exports.GuestRoute = GuestRoute;
1182
1748
  exports.LoginForm = LoginForm;
1749
+ exports.OtpInput = OtpInput;
1183
1750
  exports.ProtectedRoute = ProtectedRoute;
1184
1751
  exports.RegisterForm = RegisterForm;
1185
1752
  exports.ResetPasswordForm = ResetPasswordForm;
1753
+ exports.SocialLoginButton = SocialLoginButton;
1754
+ exports.TwoFactorForm = TwoFactorForm;
1186
1755
  exports.VerifyEmailForm = VerifyEmailForm;
1187
1756
  exports.cn = cn;
1188
1757
  exports.createAuthClient = createAuthClient;