@payez/next-mvp 3.9.1 → 4.0.0

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.
Files changed (147) hide show
  1. package/dist/api/auth-handler.d.ts +1 -2
  2. package/dist/api/auth-handler.js +9 -9
  3. package/dist/api-handlers/account/change-password.js +110 -112
  4. package/dist/api-handlers/admin/analytics.d.ts +19 -20
  5. package/dist/api-handlers/admin/analytics.js +378 -379
  6. package/dist/api-handlers/admin/audit.d.ts +19 -20
  7. package/dist/api-handlers/admin/audit.js +213 -214
  8. package/dist/api-handlers/admin/index.d.ts +21 -22
  9. package/dist/api-handlers/admin/index.js +42 -43
  10. package/dist/api-handlers/admin/redis-sessions.d.ts +35 -36
  11. package/dist/api-handlers/admin/redis-sessions.js +203 -204
  12. package/dist/api-handlers/admin/sessions.d.ts +20 -21
  13. package/dist/api-handlers/admin/sessions.js +283 -284
  14. package/dist/api-handlers/admin/site-logs.d.ts +45 -46
  15. package/dist/api-handlers/admin/site-logs.js +317 -318
  16. package/dist/api-handlers/admin/stats.d.ts +20 -21
  17. package/dist/api-handlers/admin/stats.js +239 -240
  18. package/dist/api-handlers/admin/users.d.ts +19 -20
  19. package/dist/api-handlers/admin/users.js +221 -222
  20. package/dist/api-handlers/admin/vibe-data.d.ts +79 -80
  21. package/dist/api-handlers/admin/vibe-data.js +267 -268
  22. package/dist/api-handlers/auth/refresh.js +633 -635
  23. package/dist/api-handlers/auth/signout.js +186 -187
  24. package/dist/api-handlers/auth/status.js +4 -7
  25. package/dist/api-handlers/auth/update-session.d.ts +1 -1
  26. package/dist/api-handlers/auth/update-session.js +12 -14
  27. package/dist/api-handlers/auth/verify-code.d.ts +43 -43
  28. package/dist/api-handlers/auth/verify-code.js +90 -94
  29. package/dist/api-handlers/session/viability.js +114 -146
  30. package/dist/api-handlers/test/force-expire.js +59 -65
  31. package/dist/auth/auth-decision.js +182 -182
  32. package/dist/auth/better-auth.d.ts +3 -6
  33. package/dist/auth/better-auth.js +3 -6
  34. package/dist/auth/route-config.js +2 -2
  35. package/dist/auth/utils/token-utils.d.ts +83 -84
  36. package/dist/auth/utils/token-utils.js +218 -219
  37. package/dist/client/AuthContext.js +115 -112
  38. package/dist/client/better-auth-client.d.ts +1020 -1020
  39. package/dist/client/fetch-with-auth.js +2 -2
  40. package/dist/components/SessionSync.js +121 -119
  41. package/dist/components/account/MobileNavDrawer.js +64 -64
  42. package/dist/components/account/UserAvatarMenu.js +91 -88
  43. package/dist/components/admin/VibeAdminLayout.js +71 -69
  44. package/dist/hooks/useAuth.js +9 -7
  45. package/dist/hooks/useAuthSettings.js +93 -93
  46. package/dist/hooks/useAvailableProviders.d.ts +43 -45
  47. package/dist/hooks/useAvailableProviders.js +112 -108
  48. package/dist/hooks/useSessionExpiration.d.ts +2 -3
  49. package/dist/hooks/useSessionExpiration.js +2 -2
  50. package/dist/hooks/useViabilitySession.js +3 -2
  51. package/dist/index.js +4 -6
  52. package/dist/lib/app-slug.d.ts +95 -95
  53. package/dist/lib/app-slug.js +172 -172
  54. package/dist/lib/standardized-client-api.js +10 -5
  55. package/dist/lib/startup-init.js +21 -25
  56. package/dist/lib/test-aware-get-token.js +86 -81
  57. package/dist/lib/token-lifecycle.d.ts +78 -52
  58. package/dist/lib/token-lifecycle.js +360 -398
  59. package/dist/pages/admin-login/page.js +73 -83
  60. package/dist/pages/client-admin/ClientSiteAdminPage.js +179 -177
  61. package/dist/pages/login/page.js +202 -211
  62. package/dist/pages/showcase/ShowcasePage.js +142 -140
  63. package/dist/pages/test-env/EmergencyLogoutPage.js +99 -98
  64. package/dist/pages/test-env/JwtInspectPage.js +116 -114
  65. package/dist/pages/test-env/RefreshTokenPage.js +4 -2
  66. package/dist/pages/test-env/TestEnvPage.js +51 -49
  67. package/dist/pages/verify-code/page.js +412 -408
  68. package/dist/routes/auth/logout.d.ts +31 -31
  69. package/dist/routes/auth/logout.js +98 -113
  70. package/dist/routes/auth/nextauth.d.ts +14 -11
  71. package/dist/routes/auth/nextauth.js +25 -57
  72. package/dist/routes/auth/session.js +157 -179
  73. package/dist/routes/auth/viability.js +190 -201
  74. package/dist/server/auth.d.ts +50 -0
  75. package/dist/server/auth.js +62 -0
  76. package/dist/stores/authStore.js +19 -23
  77. package/dist/utils/logout.js +5 -5
  78. package/package.json +1 -3
  79. package/src/api/auth-handler.ts +550 -549
  80. package/src/api-handlers/account/change-password.ts +5 -8
  81. package/src/api-handlers/admin/analytics.ts +4 -6
  82. package/src/api-handlers/admin/audit.ts +5 -7
  83. package/src/api-handlers/admin/index.ts +1 -2
  84. package/src/api-handlers/admin/redis-sessions.ts +6 -8
  85. package/src/api-handlers/admin/sessions.ts +5 -7
  86. package/src/api-handlers/admin/site-logs.ts +8 -10
  87. package/src/api-handlers/admin/stats.ts +4 -6
  88. package/src/api-handlers/admin/users.ts +5 -7
  89. package/src/api-handlers/admin/vibe-data.ts +10 -12
  90. package/src/api-handlers/auth/refresh.ts +5 -7
  91. package/src/api-handlers/auth/signout.ts +5 -6
  92. package/src/api-handlers/auth/status.ts +4 -7
  93. package/src/api-handlers/auth/update-session.ts +123 -125
  94. package/src/api-handlers/auth/verify-code.ts +9 -13
  95. package/src/api-handlers/session/viability.ts +10 -47
  96. package/src/api-handlers/test/force-expire.ts +4 -11
  97. package/src/auth/auth-decision.ts +1 -1
  98. package/src/auth/better-auth.ts +138 -141
  99. package/src/auth/route-config.ts +219 -219
  100. package/src/auth/utils/token-utils.ts +0 -1
  101. package/src/client/AuthContext.tsx +6 -2
  102. package/src/client/fetch-with-auth.ts +47 -47
  103. package/src/components/SessionSync.tsx +6 -5
  104. package/src/components/account/MobileNavDrawer.tsx +3 -3
  105. package/src/components/account/UserAvatarMenu.tsx +6 -3
  106. package/src/components/admin/VibeAdminLayout.tsx +4 -2
  107. package/src/config/logger.ts +1 -1
  108. package/src/hooks/useAuth.ts +117 -115
  109. package/src/hooks/useAuthSettings.ts +2 -2
  110. package/src/hooks/useAvailableProviders.ts +9 -5
  111. package/src/hooks/useSessionExpiration.ts +101 -102
  112. package/src/hooks/useViabilitySession.ts +336 -335
  113. package/src/index.ts +60 -63
  114. package/src/lib/api-handler.ts +0 -1
  115. package/src/lib/app-slug.ts +6 -6
  116. package/src/lib/standardized-client-api.ts +901 -895
  117. package/src/lib/startup-init.ts +243 -247
  118. package/src/lib/test-aware-get-token.ts +22 -12
  119. package/src/lib/token-lifecycle.ts +12 -53
  120. package/src/pages/admin-login/page.tsx +9 -17
  121. package/src/pages/client-admin/ClientSiteAdminPage.tsx +4 -2
  122. package/src/pages/login/page.tsx +21 -28
  123. package/src/pages/showcase/ShowcasePage.tsx +4 -2
  124. package/src/pages/test-env/EmergencyLogoutPage.tsx +7 -6
  125. package/src/pages/test-env/JwtInspectPage.tsx +5 -3
  126. package/src/pages/test-env/RefreshTokenPage.tsx +157 -155
  127. package/src/pages/test-env/TestEnvPage.tsx +4 -2
  128. package/src/pages/verify-code/page.tsx +10 -6
  129. package/src/routes/auth/logout.ts +7 -25
  130. package/src/routes/auth/nextauth.ts +45 -71
  131. package/src/routes/auth/session.ts +25 -50
  132. package/src/routes/auth/viability.ts +7 -19
  133. package/src/server/auth.ts +60 -0
  134. package/src/stores/authStore.ts +1899 -1904
  135. package/src/utils/logout.ts +30 -30
  136. package/src/auth/auth-options.ts +0 -237
  137. package/src/auth/callbacks/index.ts +0 -7
  138. package/src/auth/callbacks/jwt.ts +0 -382
  139. package/src/auth/callbacks/session.ts +0 -243
  140. package/src/auth/callbacks/signin.ts +0 -56
  141. package/src/auth/events/index.ts +0 -5
  142. package/src/auth/events/signout.ts +0 -33
  143. package/src/auth/providers/credentials.ts +0 -256
  144. package/src/auth/providers/index.ts +0 -6
  145. package/src/auth/providers/oauth.ts +0 -114
  146. package/src/lib/nextauth-secret.ts +0 -121
  147. package/src/types/next-auth.d.ts +0 -15
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { ReactNode } from 'react';
4
- import { useSession } from 'next-auth/react';
4
+ import { authClient } from '../../client/better-auth-client';
5
5
  import { useRouter } from 'next/navigation';
6
6
  import { useEffect } from 'react';
7
7
  import Link from 'next/link';
@@ -50,7 +50,9 @@ export function VibeAdminLayout({
50
50
  isDarkMode: isDarkModeProp,
51
51
  adminRole = 'vibe_app_admin',
52
52
  }: VibeAdminLayoutProps) {
53
- const { data: session, status } = useSession();
53
+ const { data: sessionData, isPending } = authClient.useSession();
54
+ const session = sessionData;
55
+ const status = isPending ? 'loading' : session ? 'authenticated' : 'unauthenticated';
54
56
  const router = useRouter();
55
57
  const adminConfig = useVibeAdmin();
56
58
 
@@ -45,7 +45,7 @@ const createLogger = (): any => {
45
45
  };
46
46
 
47
47
  let logger: any;
48
- if (!globalThis.process?.browser) {
48
+ if (!(globalThis.process as any)?.browser) {
49
49
  logger = createLogger();
50
50
  addConfigWatcher((updatedConfig) => { loggingConfig = updatedConfig; if (logger && logger.level !== undefined) { logger.level = loggingConfig.logLevel; } });
51
51
  } else { logger = console; }
@@ -1,115 +1,117 @@
1
- 'use client';
2
-
3
- /**
4
- * ⚠️ WARNING: This hook cannot be used directly from the @payez/next-mvp package!
5
- *
6
- * @deprecated Import this hook from your local app instead
7
- *
8
- * **Why?** React Context (SessionProvider) cannot cross package boundaries in file:// linked packages.
9
- *
10
- * **Solution:** Create a local version in your app (e.g., src/hooks/useAuth.ts) that imports standardizedApi:
11
- *
12
- * @example
13
- * ```typescript
14
- * // src/hooks/useAuth.ts (YOUR APP)
15
- * 'use client';
16
- * import { useSession } from 'next-auth/react';
17
- * import { standardizedApi } from '@payez/next-mvp/lib/standardized-client-api';
18
- *
19
- * export function useAuth() {
20
- * const { data: session } = useSession(); // Works in your app context
21
- * // ... implementation
22
- * }
23
- * ```
24
- *
25
- * @see {@link https://github.com/payez/next-mvp/blob/main/docs/centralized-auth-api-pattern.md#why-local-hooks-for-auth Complete documentation}
26
- */
27
-
28
- import { useSession, signOut } from 'next-auth/react';
29
- import { useCallback } from 'react';
30
- import { useRouter } from 'next/navigation';
31
- import { standardizedApi, ApiResult } from '../lib/standardized-client-api';
32
-
33
- // Custom session type matching NextAuth structure
34
- export interface CustomSession {
35
- user: {
36
- id: string;
37
- email: string;
38
- roles?: string[];
39
- twoFactorMethod?: string;
40
- twoFactorSessionVerified?: boolean;
41
- requiresTwoFactor?: boolean;
42
- };
43
- accessToken?: string;
44
- refreshToken?: string;
45
- expires: string;
46
- error?: string;
47
- }
48
-
49
- export interface UseAuthResult {
50
- session: CustomSession | null;
51
- status: 'loading' | 'authenticated' | 'unauthenticated';
52
- isLoading: boolean;
53
- isAuthenticated: boolean;
54
- // API helper that automatically includes the auth token
55
- apiCall: <T>(url: string, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', data?: any) => Promise<ApiResult<T>>;
56
- }
57
-
58
- export function useAuth(): UseAuthResult {
59
- const { data: session, status } = useSession();
60
- const router = useRouter();
61
-
62
- const isLoading = status === 'loading';
63
- const isAuthenticated = status === 'authenticated' && !!session?.accessToken;
64
-
65
- // Handle sign out with redirect
66
- const handleSignOut = useCallback(async () => {
67
- await signOut({ redirect: false });
68
- router.push('/account-auth/login?error=SessionExpired');
69
- }, [router]);
70
-
71
- // API helper that automatically includes the auth token and uses standardized API
72
- const apiCall = useCallback(
73
- async <T>(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', data?: any): Promise<ApiResult<T>> => {
74
- if (!session?.accessToken) {
75
- console.error('[useAuth] No access token available');
76
- throw new Error('Not authenticated');
77
- }
78
-
79
- try {
80
- // Use standardized API which handles token refresh and validates response format
81
- switch (method) {
82
- case 'GET':
83
- return await standardizedApi.get<T>(url, session.accessToken!);
84
- case 'POST':
85
- return await standardizedApi.post<T>(url, data, session.accessToken!);
86
- case 'PUT':
87
- return await standardizedApi.put<T>(url, data);
88
- case 'DELETE':
89
- return await standardizedApi.delete<T>(url);
90
- default:
91
- throw new Error(`Unsupported method: ${method}`);
92
- }
93
- } catch (error) {
94
- console.error('[useAuth] API call failed:', error);
95
-
96
- // standardizedApi handles token refresh internally, but if we still get auth errors, sign out
97
- if (error instanceof Error && error.message.includes('Authentication failed')) {
98
- console.warn('[useAuth] Authentication failed, signing out...');
99
- await handleSignOut();
100
- }
101
-
102
- throw error;
103
- }
104
- },
105
- [session?.accessToken, handleSignOut]
106
- );
107
-
108
- return {
109
- session: session as CustomSession | null,
110
- status,
111
- isLoading,
112
- isAuthenticated,
113
- apiCall
114
- };
115
- }
1
+ 'use client';
2
+
3
+ /**
4
+ * ⚠️ WARNING: This hook cannot be used directly from the @payez/next-mvp package!
5
+ *
6
+ * @deprecated Import this hook from your local app instead
7
+ *
8
+ * **Why?** React Context (SessionProvider) cannot cross package boundaries in file:// linked packages.
9
+ *
10
+ * **Solution:** Create a local version in your app (e.g., src/hooks/useAuth.ts) that imports standardizedApi:
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // src/hooks/useAuth.ts (YOUR APP)
15
+ * 'use client';
16
+ * import { authClient } from '@payez/next-mvp/client/better-auth-client';
17
+ * import { standardizedApi } from '@payez/next-mvp/lib/standardized-client-api';
18
+ *
19
+ * export function useAuth() {
20
+ * const { data: session } = useSession(); // Works in your app context
21
+ * // ... implementation
22
+ * }
23
+ * ```
24
+ *
25
+ * @see {@link https://github.com/payez/next-mvp/blob/main/docs/centralized-auth-api-pattern.md#why-local-hooks-for-auth Complete documentation}
26
+ */
27
+
28
+ import { authClient } from '../client/better-auth-client';
29
+ import { useCallback } from 'react';
30
+ import { useRouter } from 'next/navigation';
31
+ import { standardizedApi, ApiResult } from '../lib/standardized-client-api';
32
+
33
+ // Custom session type matching NextAuth structure
34
+ export interface CustomSession {
35
+ user: {
36
+ id: string;
37
+ email: string;
38
+ roles?: string[];
39
+ twoFactorMethod?: string;
40
+ twoFactorSessionVerified?: boolean;
41
+ requiresTwoFactor?: boolean;
42
+ };
43
+ accessToken?: string;
44
+ refreshToken?: string;
45
+ expires: string;
46
+ error?: string;
47
+ }
48
+
49
+ export interface UseAuthResult {
50
+ session: CustomSession | null;
51
+ status: 'loading' | 'authenticated' | 'unauthenticated';
52
+ isLoading: boolean;
53
+ isAuthenticated: boolean;
54
+ // API helper that automatically includes the auth token
55
+ apiCall: <T>(url: string, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', data?: any) => Promise<ApiResult<T>>;
56
+ }
57
+
58
+ export function useAuth(): UseAuthResult {
59
+ const { data: sessionData, isPending } = authClient.useSession();
60
+ const session = sessionData;
61
+ const status = isPending ? 'loading' : session ? 'authenticated' : 'unauthenticated';
62
+ const router = useRouter();
63
+
64
+ const isLoading = status === 'loading';
65
+ const isAuthenticated = status === 'authenticated' && !!(session as any)?.accessToken;
66
+
67
+ // Handle sign out with redirect
68
+ const handleSignOut = useCallback(async () => {
69
+ await authClient.signOut();
70
+ router.push('/account-auth/login?error=SessionExpired');
71
+ }, [router]);
72
+
73
+ // API helper that automatically includes the auth token and uses standardized API
74
+ const apiCall = useCallback(
75
+ async <T>(url: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', data?: any): Promise<ApiResult<T>> => {
76
+ if (!(session as any)?.accessToken) {
77
+ console.error('[useAuth] No access token available');
78
+ throw new Error('Not authenticated');
79
+ }
80
+
81
+ try {
82
+ // Use standardized API which handles token refresh and validates response format
83
+ switch (method) {
84
+ case 'GET':
85
+ return await standardizedApi.get<T>(url, (session as any).accessToken!);
86
+ case 'POST':
87
+ return await standardizedApi.post<T>(url, data, (session as any).accessToken!);
88
+ case 'PUT':
89
+ return await standardizedApi.put<T>(url, data);
90
+ case 'DELETE':
91
+ return await standardizedApi.delete<T>(url);
92
+ default:
93
+ throw new Error(`Unsupported method: ${method}`);
94
+ }
95
+ } catch (error) {
96
+ console.error('[useAuth] API call failed:', error);
97
+
98
+ // standardizedApi handles token refresh internally, but if we still get auth errors, sign out
99
+ if (error instanceof Error && error.message.includes('Authentication failed')) {
100
+ console.warn('[useAuth] Authentication failed, signing out...');
101
+ await handleSignOut();
102
+ }
103
+
104
+ throw error;
105
+ }
106
+ },
107
+ [(session as any)?.accessToken, handleSignOut]
108
+ );
109
+
110
+ return {
111
+ session: session as CustomSession | null,
112
+ status,
113
+ isLoading,
114
+ isAuthenticated,
115
+ apiCall
116
+ };
117
+ }
@@ -7,7 +7,7 @@
7
7
 
8
8
  'use client';
9
9
 
10
- import { useSession } from 'next-auth/react';
10
+ import { authClient } from '../client/better-auth-client';
11
11
  import type { AuthSettings } from '../lib/idp-client-config';
12
12
 
13
13
  /**
@@ -39,7 +39,7 @@ interface SessionWithAuthSettings {
39
39
  * ```
40
40
  */
41
41
  export function useAuthSettings(): AuthSettings | null {
42
- const { data: session } = useSession();
42
+ const { data: session } = authClient.useSession();
43
43
 
44
44
  if (!session) {
45
45
  return null;
@@ -8,8 +8,7 @@
8
8
  'use client';
9
9
 
10
10
  import { useState, useEffect } from 'react';
11
- import { getProviders, LiteralUnion, ClientSafeProvider } from 'next-auth/react';
12
- import { BuiltInProviderType } from 'next-auth/providers/index';
11
+ import { authClient } from '../client/better-auth-client';
13
12
  import type { FederatedProvider } from '../types/auth';
14
13
 
15
14
  // Map NextAuth provider IDs to our FederatedProvider type
@@ -29,7 +28,7 @@ export interface UseAvailableProvidersResult {
29
28
  providers: FederatedProvider[];
30
29
  isLoading: boolean;
31
30
  error: Error | null;
32
- rawProviders: Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider> | null;
31
+ rawProviders: Record<string, any> | null;
33
32
  }
34
33
 
35
34
  /**
@@ -56,7 +55,7 @@ export interface UseAvailableProvidersResult {
56
55
  */
57
56
  export function useAvailableProviders(): UseAvailableProvidersResult {
58
57
  const [providers, setProviders] = useState<FederatedProvider[]>([]);
59
- const [rawProviders, setRawProviders] = useState<Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider> | null>(null);
58
+ const [rawProviders, setRawProviders] = useState<Record<string, any> | null>(null);
60
59
  const [isLoading, setIsLoading] = useState(true);
61
60
  const [error, setError] = useState<Error | null>(null);
62
61
 
@@ -65,7 +64,12 @@ export function useAvailableProviders(): UseAvailableProvidersResult {
65
64
 
66
65
  async function fetchProviders() {
67
66
  try {
68
- const result = await getProviders();
67
+ // Fetch available providers from Better Auth
68
+ // Better Auth doesn't have a getProviders equivalent, so we use a static list
69
+ // based on configured social providers
70
+ const result: Record<string, { id: string; name: string }> = {
71
+ google: { id: 'google', name: 'Google' },
72
+ };
69
73
 
70
74
  if (!mounted) return;
71
75
 
@@ -1,102 +1,101 @@
1
- /**
2
- * Hook to detect and handle stale/expired sessions during 2FA flow
3
- *
4
- * Use this in verify-code pages to automatically redirect to login
5
- * when the provisional bearer token has expired.
6
- *
7
- * @example
8
- * ```tsx
9
- * import { useSessionExpiration } from '@payez/next-mvp/hooks/useSessionExpiration';
10
- *
11
- * function VerifyCodePage() {
12
- * const { data: session } = useSession();
13
- * const router = useRouter();
14
- * const searchParams = useSearchParams();
15
- * const [error, setError] = useState<string | null>(null);
16
- *
17
- * const callbackUrl = searchParams?.get('callbackUrl') || '/dashboard';
18
- *
19
- * // Automatically handles session expiration
20
- * const sessionValid = useSessionExpiration({
21
- * session,
22
- * router,
23
- * callbackUrl,
24
- * onExpired: (message) => setError(message)
25
- * });
26
- *
27
- * if (!sessionValid) return null; // Will redirect
28
- * // ... rest of component
29
- * }
30
- * ```
31
- */
32
-
33
- import { useEffect } from 'react';
34
- import { signOut } from 'next-auth/react';
35
- import type { Session } from 'next-auth';
36
-
37
- export interface UseSessionExpirationOptions {
38
- /** NextAuth session object */
39
- session: Session | null | undefined;
40
- /** Next.js router for navigation */
41
- router: {
42
- push: (url: string) => void;
43
- };
44
- /** URL to redirect to after login */
45
- callbackUrl?: string;
46
- /** Callback when session expires - use to set error state */
47
- onExpired?: (message: string) => void;
48
- /** Delay before redirect in milliseconds (default: 1500) */
49
- redirectDelay?: number;
50
- /** Custom redirect URL (default: /account-auth/login) */
51
- loginUrl?: string;
52
- }
53
-
54
- /**
55
- * Detects stale sessions and redirects to login
56
- *
57
- * Returns:
58
- * - `true` if session is valid (has accessToken)
59
- * - `false` if session is loading (no session yet)
60
- * - `null` if session is stale (will trigger redirect)
61
- */
62
- export function useSessionExpiration({
63
- session,
64
- router,
65
- callbackUrl = '/dashboard',
66
- onExpired,
67
- redirectDelay = 1500,
68
- loginUrl = '/account-auth/login'
69
- }: UseSessionExpirationOptions): boolean | null {
70
- useEffect(() => {
71
- // If session exists but no accessToken, the token is stale/expired
72
- if (session && !(session as any).accessToken) {
73
- const message = 'Your session has expired. Redirecting to login...';
74
- if (onExpired) {
75
- onExpired(message);
76
- }
77
-
78
- setTimeout(async () => {
79
- // Clear the session before redirecting
80
- await signOut({ redirect: false });
81
-
82
- const params = new URLSearchParams({
83
- callbackUrl,
84
- error: 'SessionExpired'
85
- });
86
-
87
- router.push(`${loginUrl}?${params.toString()}`);
88
- }, redirectDelay);
89
- }
90
- }, [session, router, callbackUrl, onExpired, redirectDelay, loginUrl]);
91
-
92
- // Return session validity state
93
- if (session && !(session as any).accessToken) {
94
- return null; // Stale session - will redirect
95
- }
96
-
97
- if ((session as any)?.accessToken) {
98
- return true; // Valid session
99
- }
100
-
101
- return false; // No session yet - loading
102
- }
1
+ /**
2
+ * Hook to detect and handle stale/expired sessions during 2FA flow
3
+ *
4
+ * Use this in verify-code pages to automatically redirect to login
5
+ * when the provisional bearer token has expired.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { useSessionExpiration } from '@payez/next-mvp/hooks/useSessionExpiration';
10
+ *
11
+ * function VerifyCodePage() {
12
+ * const { data: session } = useSession();
13
+ * const router = useRouter();
14
+ * const searchParams = useSearchParams();
15
+ * const [error, setError] = useState<string | null>(null);
16
+ *
17
+ * const callbackUrl = searchParams?.get('callbackUrl') || '/dashboard';
18
+ *
19
+ * // Automatically handles session expiration
20
+ * const sessionValid = useSessionExpiration({
21
+ * session,
22
+ * router,
23
+ * callbackUrl,
24
+ * onExpired: (message) => setError(message)
25
+ * });
26
+ *
27
+ * if (!sessionValid) return null; // Will redirect
28
+ * // ... rest of component
29
+ * }
30
+ * ```
31
+ */
32
+
33
+ import { useEffect } from 'react';
34
+ import { authClient } from '../client/better-auth-client';
35
+
36
+ export interface UseSessionExpirationOptions {
37
+ /** Session object */
38
+ session: any | null | undefined;
39
+ /** Next.js router for navigation */
40
+ router: {
41
+ push: (url: string) => void;
42
+ };
43
+ /** URL to redirect to after login */
44
+ callbackUrl?: string;
45
+ /** Callback when session expires - use to set error state */
46
+ onExpired?: (message: string) => void;
47
+ /** Delay before redirect in milliseconds (default: 1500) */
48
+ redirectDelay?: number;
49
+ /** Custom redirect URL (default: /account-auth/login) */
50
+ loginUrl?: string;
51
+ }
52
+
53
+ /**
54
+ * Detects stale sessions and redirects to login
55
+ *
56
+ * Returns:
57
+ * - `true` if session is valid (has accessToken)
58
+ * - `false` if session is loading (no session yet)
59
+ * - `null` if session is stale (will trigger redirect)
60
+ */
61
+ export function useSessionExpiration({
62
+ session,
63
+ router,
64
+ callbackUrl = '/dashboard',
65
+ onExpired,
66
+ redirectDelay = 1500,
67
+ loginUrl = '/account-auth/login'
68
+ }: UseSessionExpirationOptions): boolean | null {
69
+ useEffect(() => {
70
+ // If session exists but no accessToken, the token is stale/expired
71
+ if (session && !(session as any).accessToken) {
72
+ const message = 'Your session has expired. Redirecting to login...';
73
+ if (onExpired) {
74
+ onExpired(message);
75
+ }
76
+
77
+ setTimeout(async () => {
78
+ // Clear the session before redirecting
79
+ await authClient.signOut();
80
+
81
+ const params = new URLSearchParams({
82
+ callbackUrl,
83
+ error: 'SessionExpired'
84
+ });
85
+
86
+ router.push(`${loginUrl}?${params.toString()}`);
87
+ }, redirectDelay);
88
+ }
89
+ }, [session, router, callbackUrl, onExpired, redirectDelay, loginUrl]);
90
+
91
+ // Return session validity state
92
+ if (session && !(session as any).accessToken) {
93
+ return null; // Stale session - will redirect
94
+ }
95
+
96
+ if ((session as any)?.accessToken) {
97
+ return true; // Valid session
98
+ }
99
+
100
+ return false; // No session yet - loading
101
+ }