@djangocfg/layouts 2.0.7 → 2.0.9

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 (29) hide show
  1. package/README.md +65 -6
  2. package/package.json +5 -5
  3. package/src/auth/context/AuthContext.tsx +11 -6
  4. package/src/auth/hooks/index.ts +1 -0
  5. package/src/auth/hooks/useAuthGuard.ts +2 -2
  6. package/src/auth/hooks/useAutoAuth.ts +2 -2
  7. package/src/auth/hooks/useGithubAuth.ts +184 -0
  8. package/src/components/RedirectPage/RedirectPage.tsx +2 -2
  9. package/src/layouts/AuthLayout/AuthContext.tsx +2 -0
  10. package/src/layouts/AuthLayout/AuthLayout.tsx +22 -5
  11. package/src/layouts/AuthLayout/IdentifierForm.tsx +4 -0
  12. package/src/layouts/AuthLayout/OAuthCallback.tsx +172 -0
  13. package/src/layouts/AuthLayout/OAuthProviders.tsx +85 -0
  14. package/src/layouts/AuthLayout/index.ts +4 -0
  15. package/src/layouts/AuthLayout/types.ts +4 -0
  16. package/src/snippets/Analytics/events.ts +5 -0
  17. package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
  18. package/src/snippets/McpChat/components/AIChatWidget.tsx +3 -39
  19. package/src/snippets/McpChat/components/ChatMessages.tsx +2 -2
  20. package/src/snippets/McpChat/components/ChatPanel.tsx +84 -110
  21. package/src/snippets/McpChat/components/ChatSidebar.tsx +66 -60
  22. package/src/snippets/McpChat/components/ChatWidget.tsx +4 -37
  23. package/src/snippets/McpChat/components/MessageBubble.tsx +5 -5
  24. package/src/snippets/McpChat/components/index.ts +0 -2
  25. package/src/snippets/McpChat/config.ts +42 -0
  26. package/src/snippets/McpChat/context/ChatContext.tsx +5 -7
  27. package/src/snippets/McpChat/hooks/useChatLayout.ts +134 -23
  28. package/src/snippets/McpChat/index.ts +0 -1
  29. package/src/snippets/index.ts +0 -1
package/README.md CHANGED
@@ -71,15 +71,21 @@ import { AppLayout } from '@djangocfg/layouts';
71
71
 
72
72
  ## Auth
73
73
 
74
- Complete authentication system with context, hooks, and middleware.
74
+ Complete authentication system with OTP and OAuth support.
75
75
 
76
76
  ```tsx
77
77
  import { AuthProvider, useAuth } from '@djangocfg/layouts/auth';
78
- import { AuthDialog } from '@djangocfg/layouts/snippets';
79
-
80
- <AuthProvider>
81
- <AuthDialog trigger={<Button>Sign In</Button>} />
82
- </AuthProvider>
78
+ import { AuthLayout, OAuthCallback } from '@djangocfg/layouts';
79
+
80
+ // Basic auth page with OTP and GitHub OAuth
81
+ <AuthLayout
82
+ enablePhoneAuth={false}
83
+ enableGithubAuth={true}
84
+ termsUrl="/legal/terms"
85
+ privacyUrl="/legal/privacy"
86
+ >
87
+ <h1>Welcome Back</h1>
88
+ </AuthLayout>
83
89
  ```
84
90
 
85
91
  | Export | Description |
@@ -89,8 +95,59 @@ import { AuthDialog } from '@djangocfg/layouts/snippets';
89
95
  | `useAuthGuard` | Route protection hook |
90
96
  | `useAuthRedirect` | Redirect hook for auth flows |
91
97
  | `useAutoAuth` | Auto-authentication hook |
98
+ | `useGithubAuth` | GitHub OAuth hook |
92
99
  | `authMiddleware` | Next.js middleware |
93
100
 
101
+ ### GitHub OAuth
102
+
103
+ Complete GitHub OAuth flow with automatic token handling:
104
+
105
+ ```tsx
106
+ // app/auth/page.tsx
107
+ import { AuthLayout } from '@djangocfg/layouts';
108
+
109
+ export default function AuthPage() {
110
+ return (
111
+ <AuthLayout
112
+ enableGithubAuth={true}
113
+ redirectUrl="/dashboard"
114
+ onOAuthSuccess={(user, isNewUser, provider) => console.log('Success!', user)}
115
+ onError={(error) => console.error(error)}
116
+ >
117
+ <h1>Sign In</h1>
118
+ </AuthLayout>
119
+ );
120
+ }
121
+ ```
122
+
123
+ **OAuth Flow:**
124
+ 1. User clicks "Continue with GitHub"
125
+ 2. Redirects to GitHub authorization page
126
+ 3. GitHub redirects to `/auth?provider=github&code=XXX&state=YYY`
127
+ 4. `AuthLayout` automatically handles callback and exchanges code for JWT tokens
128
+ 5. User is logged in and redirected to `redirectUrl`
129
+
130
+ > **Note:** OAuth callback handling is built into `AuthLayout` when `enableGithubAuth={true}`. No need to add `OAuthCallback` separately!
131
+
132
+ **Using the hook directly:**
133
+
134
+ ```tsx
135
+ import { useGithubAuth } from '@djangocfg/layouts';
136
+
137
+ function CustomGithubButton() {
138
+ const { isLoading, startGithubAuth } = useGithubAuth({
139
+ onSuccess: (user) => console.log('Logged in!', user),
140
+ onError: (error) => console.error(error),
141
+ });
142
+
143
+ return (
144
+ <button onClick={startGithubAuth} disabled={isLoading}>
145
+ {isLoading ? 'Connecting...' : 'Login with GitHub'}
146
+ </button>
147
+ );
148
+ }
149
+ ```
150
+
94
151
  ### Auth Context
95
152
 
96
153
  ```tsx
@@ -152,6 +209,7 @@ Analytics.setUser('user-123');
152
209
  | Category | Events |
153
210
  |----------|--------|
154
211
  | **Auth** | `AUTH_OTP_REQUEST`, `AUTH_LOGIN_SUCCESS`, `AUTH_OTP_VERIFY_FAIL`, `AUTH_LOGOUT`, `AUTH_SESSION_EXPIRED`, `AUTH_TOKEN_REFRESH` |
212
+ | **OAuth** | `AUTH_OAUTH_START`, `AUTH_OAUTH_SUCCESS`, `AUTH_OAUTH_FAIL` |
155
213
  | **Error** | `ERROR_BOUNDARY`, `ERROR_API`, `ERROR_VALIDATION`, `ERROR_NETWORK` |
156
214
  | **Navigation** | `NAV_ADMIN_ENTER`, `NAV_DASHBOARD_ENTER`, `NAV_PAGE_VIEW` |
157
215
  | **Engagement** | `THEME_CHANGE`, `SIDEBAR_TOGGLE`, `MOBILE_MENU_OPEN` |
@@ -162,6 +220,7 @@ Built-in tracking for:
162
220
  - **Page views** - on every route change
163
221
  - **User ID** - automatically set when user is authenticated
164
222
  - **Auth events** - login, logout, OTP, session expiry
223
+ - **OAuth events** - GitHub OAuth start, success, failure
165
224
  - **Errors** - React ErrorBoundary errors
166
225
 
167
226
  ## Snippets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -92,9 +92,9 @@
92
92
  "check": "tsc --noEmit"
93
93
  },
94
94
  "peerDependencies": {
95
- "@djangocfg/api": "^1.4.37",
96
- "@djangocfg/centrifugo": "^1.4.37",
97
- "@djangocfg/ui": "^1.4.37",
95
+ "@djangocfg/api": "^1.4.39",
96
+ "@djangocfg/centrifugo": "^1.4.39",
97
+ "@djangocfg/ui": "^1.4.39",
98
98
  "@hookform/resolvers": "^5.2.0",
99
99
  "consola": "^3.4.2",
100
100
  "lucide-react": "^0.545.0",
@@ -114,7 +114,7 @@
114
114
  "uuid": "^11.1.0"
115
115
  },
116
116
  "devDependencies": {
117
- "@djangocfg/typescript-config": "^1.4.37",
117
+ "@djangocfg/typescript-config": "^1.4.39",
118
118
  "@types/node": "^24.7.2",
119
119
  "@types/react": "19.2.2",
120
120
  "@types/react-dom": "19.2.1",
@@ -8,7 +8,7 @@ import React, {
8
8
 
9
9
  import { api, Enums } from '@djangocfg/api';
10
10
  import { useAccountsContext, AccountsProvider } from './AccountsContext';
11
- import { useLocalStorage, useQueryParams, useRouter } from '@djangocfg/ui/hooks';
11
+ import { useLocalStorage, useQueryParams, useCfgRouter } from '@djangocfg/ui/hooks';
12
12
  import { getCachedProfile, clearProfileCache } from '../hooks/useProfileCache';
13
13
 
14
14
  import { authLogger } from '../../utils/logger';
@@ -50,7 +50,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
50
50
  });
51
51
 
52
52
  const [initialized, setInitialized] = useState(false);
53
- const router = useRouter();
53
+ const router = useCfgRouter();
54
54
  const pathname = usePathname();
55
55
  const queryParams = useQueryParams();
56
56
 
@@ -362,13 +362,14 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
362
362
  }
363
363
 
364
364
  // Handle redirect logic here
365
+ // Use hardPush for full page reload - ensures all React contexts reinitialize
365
366
  const defaultCallback = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
366
367
 
367
368
  if (redirectUrl && redirectUrl !== defaultCallback) {
368
369
  clearRedirectUrl();
369
- router.push(redirectUrl);
370
+ router.hardPush(redirectUrl);
370
371
  } else {
371
- router.push(defaultCallback);
372
+ router.hardPush(defaultCallback);
372
373
  }
373
374
 
374
375
  return {
@@ -460,7 +461,11 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
460
461
  accounts.logout(); // Clear tokens and profile
461
462
  setInitialized(true);
462
463
  setIsLoading(false);
463
- pushToDefaultAuthCallbackUrl();
464
+
465
+ // Use hardReplace for full page reload + replace history
466
+ // This ensures contexts reinitialize AND back button won't return to protected page
467
+ const authCallbackUrl = config?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
468
+ router.hardReplace(authCallbackUrl);
464
469
  };
465
470
 
466
471
  // Use config.onConfirm if provided, otherwise use a simple confirm
@@ -482,7 +487,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
482
487
  performLogout();
483
488
  }
484
489
  }
485
- }, [accounts, pushToDefaultAuthCallbackUrl]);
490
+ }, [accounts, config?.routes?.defaultAuthCallback, router]);
486
491
 
487
492
  // Redirect URL methods
488
493
  const getSavedRedirectUrl = useCallback((): string | null => {
@@ -6,6 +6,7 @@ export { useSessionStorage } from './useSessionStorage';
6
6
  export { useLocalStorage } from './useLocalStorage';
7
7
  export { useAuthForm } from './useAuthForm';
8
8
  export { useAutoAuth } from './useAutoAuth';
9
+ export { useGithubAuth, type UseGithubAuthOptions, type UseGithubAuthReturn } from './useGithubAuth';
9
10
  export {
10
11
  getCachedProfile,
11
12
  setCachedProfile,
@@ -3,7 +3,7 @@
3
3
  import { useEffect } from 'react';
4
4
 
5
5
  import { useAuth } from '../context';
6
- import { useRouter } from '@djangocfg/ui/hooks';
6
+ import { useCfgRouter } from '@djangocfg/ui/hooks';
7
7
 
8
8
  interface UseAuthGuardOptions {
9
9
  redirectTo?: string;
@@ -13,7 +13,7 @@ interface UseAuthGuardOptions {
13
13
  export const useAuthGuard = (options: UseAuthGuardOptions = {}) => {
14
14
  const { redirectTo = '/auth', requireAuth = true } = options;
15
15
  const { isAuthenticated, isLoading } = useAuth();
16
- const router = useRouter();
16
+ const router = useCfgRouter();
17
17
 
18
18
  useEffect(() => {
19
19
  if (!isLoading && requireAuth && !isAuthenticated) {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect } from 'react';
4
4
  import { usePathname } from 'next/navigation';
5
- import { useQueryParams, useRouter } from '@djangocfg/ui/hooks';
5
+ import { useQueryParams, useCfgRouter } from '@djangocfg/ui/hooks';
6
6
  import { authLogger } from '../../utils/logger';
7
7
 
8
8
  export interface UseAutoAuthOptions {
@@ -18,7 +18,7 @@ export const useAutoAuth = (options: UseAutoAuthOptions = {}) => {
18
18
  const { onOTPDetected, cleanupUrl = true } = options;
19
19
  const queryParams = useQueryParams();
20
20
  const pathname = usePathname();
21
- const router = useRouter();
21
+ const router = useCfgRouter();
22
22
 
23
23
  const isReady = !!pathname && !!queryParams.get('otp');
24
24
  const hasOTP = !!(queryParams.get('otp'));
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useState } from 'react';
4
+
5
+ import { api } from '@djangocfg/api';
6
+ import { useCfgRouter } from '@djangocfg/ui/hooks';
7
+ import { authLogger } from '../../utils/logger';
8
+ import { Analytics, AnalyticsEvent, AnalyticsCategory } from '../../snippets/Analytics';
9
+
10
+ export interface UseGithubAuthOptions {
11
+ sourceUrl?: string;
12
+ onSuccess?: (user: any, isNewUser: boolean) => void;
13
+ onError?: (error: string) => void;
14
+ redirectUrl?: string;
15
+ }
16
+
17
+ export interface UseGithubAuthReturn {
18
+ isLoading: boolean;
19
+ error: string | null;
20
+ startGithubAuth: () => Promise<void>;
21
+ handleGithubCallback: (code: string, state: string) => Promise<void>;
22
+ }
23
+
24
+ /**
25
+ * Hook for GitHub OAuth authentication flow.
26
+ *
27
+ * Usage:
28
+ * 1. Call startGithubAuth() to redirect user to GitHub
29
+ * 2. After GitHub redirects back, call handleGithubCallback(code, state)
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * const { isLoading, error, startGithubAuth } = useGithubAuth({
34
+ * onSuccess: (user) => router.push('/dashboard'),
35
+ * onError: (error) => console.error(error),
36
+ * });
37
+ *
38
+ * <Button onClick={startGithubAuth} disabled={isLoading}>
39
+ * Continue with GitHub
40
+ * </Button>
41
+ * ```
42
+ */
43
+ export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuthReturn => {
44
+ const { sourceUrl, onSuccess, onError, redirectUrl } = options;
45
+ const router = useCfgRouter();
46
+
47
+ const [isLoading, setIsLoading] = useState(false);
48
+ const [error, setError] = useState<string | null>(null);
49
+
50
+ /**
51
+ * Start GitHub OAuth flow - redirects user to GitHub authorization page.
52
+ */
53
+ const startGithubAuth = useCallback(async () => {
54
+ setIsLoading(true);
55
+ setError(null);
56
+
57
+ try {
58
+ authLogger.info('Starting GitHub OAuth flow...');
59
+
60
+ // Track OAuth start
61
+ Analytics.event(AnalyticsEvent.AUTH_OAUTH_START, {
62
+ category: AnalyticsCategory.AUTH,
63
+ label: 'github',
64
+ });
65
+
66
+ // Call API to get authorization URL
67
+ // The API will auto-generate redirect_uri from config if not provided
68
+ const response = await api.cfg_oauth.accountsOauthGithubAuthorizeCreate({
69
+ source_url: sourceUrl || (typeof window !== 'undefined' ? window.location.href : ''),
70
+ });
71
+
72
+ if (!response.authorization_url) {
73
+ throw new Error('Failed to get authorization URL');
74
+ }
75
+
76
+ authLogger.info('Redirecting to GitHub...', response.authorization_url);
77
+
78
+ // Store state in sessionStorage for verification on callback
79
+ if (typeof window !== 'undefined') {
80
+ sessionStorage.setItem('oauth_state', response.state);
81
+ sessionStorage.setItem('oauth_provider', 'github');
82
+ }
83
+
84
+ // Redirect to GitHub
85
+ window.location.href = response.authorization_url;
86
+
87
+ } catch (err) {
88
+ const errorMessage = err instanceof Error ? err.message : 'Failed to start GitHub authentication';
89
+ authLogger.error('GitHub OAuth start error:', err);
90
+ setError(errorMessage);
91
+ onError?.(errorMessage);
92
+
93
+ // Track OAuth error
94
+ Analytics.event(AnalyticsEvent.AUTH_OAUTH_FAIL, {
95
+ category: AnalyticsCategory.AUTH,
96
+ label: 'github',
97
+ });
98
+ } finally {
99
+ setIsLoading(false);
100
+ }
101
+ }, [sourceUrl, onError]);
102
+
103
+ /**
104
+ * Handle GitHub OAuth callback - exchanges code for JWT tokens.
105
+ *
106
+ * @param code - Authorization code from GitHub callback
107
+ * @param state - State token for CSRF verification
108
+ */
109
+ const handleGithubCallback = useCallback(async (code: string, state: string) => {
110
+ setIsLoading(true);
111
+ setError(null);
112
+
113
+ try {
114
+ authLogger.info('Processing GitHub OAuth callback...');
115
+
116
+ // Verify state matches what we stored
117
+ if (typeof window !== 'undefined') {
118
+ const storedState = sessionStorage.getItem('oauth_state');
119
+ if (storedState && storedState !== state) {
120
+ throw new Error('Invalid OAuth state - possible CSRF attack');
121
+ }
122
+ // Clear stored state
123
+ sessionStorage.removeItem('oauth_state');
124
+ sessionStorage.removeItem('oauth_provider');
125
+ }
126
+
127
+ // Exchange code for tokens
128
+ // The API will auto-generate redirect_uri from config if not provided
129
+ const response = await api.cfg_oauth.accountsOauthGithubCallbackCreate({
130
+ code,
131
+ state,
132
+ });
133
+
134
+ if (!response.access || !response.refresh) {
135
+ throw new Error('Invalid response from OAuth callback');
136
+ }
137
+
138
+ authLogger.info('GitHub OAuth successful, user:', response.user);
139
+
140
+ // Save tokens using API client
141
+ api.setToken(response.access, response.refresh);
142
+
143
+ // Track successful OAuth
144
+ Analytics.event(AnalyticsEvent.AUTH_LOGIN_SUCCESS, {
145
+ category: AnalyticsCategory.AUTH,
146
+ label: 'github',
147
+ });
148
+
149
+ // Set user ID for future tracking
150
+ if (response.user?.id) {
151
+ Analytics.setUser(String(response.user.id));
152
+ }
153
+
154
+ // Call success callback
155
+ onSuccess?.(response.user, response.is_new_user || false);
156
+
157
+ // Redirect to dashboard or specified URL
158
+ // Use hardPush for full page reload - ensures all React contexts reinitialize
159
+ const finalRedirectUrl = redirectUrl || '/dashboard';
160
+ router.hardPush(finalRedirectUrl);
161
+
162
+ } catch (err) {
163
+ const errorMessage = err instanceof Error ? err.message : 'GitHub authentication failed';
164
+ authLogger.error('GitHub OAuth callback error:', err);
165
+ setError(errorMessage);
166
+ onError?.(errorMessage);
167
+
168
+ // Track OAuth error
169
+ Analytics.event(AnalyticsEvent.AUTH_OAUTH_FAIL, {
170
+ category: AnalyticsCategory.AUTH,
171
+ label: 'github',
172
+ });
173
+ } finally {
174
+ setIsLoading(false);
175
+ }
176
+ }, [onSuccess, onError, redirectUrl, router]);
177
+
178
+ return {
179
+ isLoading,
180
+ error,
181
+ startGithubAuth,
182
+ handleGithubCallback,
183
+ };
184
+ };
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect } from 'react';
4
4
  import { useAuth } from '../../auth';
5
- import { useRouter } from '@djangocfg/ui/hooks';
5
+ import { useCfgRouter } from '@djangocfg/ui/hooks';
6
6
  import { Preloader } from '@djangocfg/ui/components';
7
7
 
8
8
  export interface RedirectPageProps {
@@ -49,7 +49,7 @@ export function RedirectPage({
49
49
  loadingText = 'Loading...',
50
50
  }: RedirectPageProps) {
51
51
  const { isAuthenticated } = useAuth();
52
- const router = useRouter();
52
+ const router = useCfgRouter();
53
53
 
54
54
  useEffect(() => {
55
55
  if (!isAuthenticated) {
@@ -15,6 +15,7 @@ export const AuthProvider: React.FC<AuthProps> = ({
15
15
  termsUrl,
16
16
  privacyUrl,
17
17
  enablePhoneAuth = false, // Default to false for backward compatibility
18
+ enableGithubAuth = false, // Default to false for backward compatibility
18
19
  onIdentifierSuccess,
19
20
  onOTPSuccess,
20
21
  onError,
@@ -39,6 +40,7 @@ export const AuthProvider: React.FC<AuthProps> = ({
39
40
  termsUrl,
40
41
  privacyUrl,
41
42
  enablePhoneAuth,
43
+ enableGithubAuth,
42
44
  };
43
45
 
44
46
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
@@ -1,19 +1,22 @@
1
1
  /**
2
2
  * Auth Layout
3
- *
4
- * Layout for authentication pages with OTP authentication (email/phone)
3
+ *
4
+ * Layout for authentication pages with OTP (email/phone) and OAuth (GitHub) support.
5
5
  * Supports two-step authentication flow: identifier input → OTP verification
6
- *
6
+ * Also handles OAuth callbacks automatically when enableGithubAuth is true.
7
+ *
7
8
  * @example
8
9
  * ```tsx
9
10
  * import { AuthLayout } from '@djangocfg/layouts';
10
- *
11
+ *
11
12
  * <AuthLayout
12
13
  * sourceUrl="https://example.com"
13
14
  * supportUrl="https://example.com/support"
14
15
  * termsUrl="https://example.com/terms"
15
16
  * privacyUrl="https://example.com/privacy"
16
- * enablePhoneAuth={true}
17
+ * enablePhoneAuth={false}
18
+ * enableGithubAuth={true}
19
+ * redirectUrl="/dashboard"
17
20
  * >
18
21
  * {/* Optional custom content above forms *\/}
19
22
  * </AuthLayout>
@@ -27,6 +30,7 @@ import React from 'react';
27
30
  import { AuthProvider, useAuthContext } from './AuthContext';
28
31
  import { IdentifierForm } from './IdentifierForm';
29
32
  import { OTPForm } from './OTPForm';
33
+ import { OAuthCallback } from './OAuthCallback';
30
34
  import { Suspense } from '../../components';
31
35
 
32
36
  import type { AuthProps } from './types';
@@ -34,8 +38,21 @@ import type { AuthProps } from './types';
34
38
  export type AuthLayoutProps = AuthProps;
35
39
 
36
40
  export const AuthLayout: React.FC<AuthProps> = (props) => {
41
+ const { enableGithubAuth, redirectUrl = '/dashboard', onOAuthSuccess, onError } = props;
42
+
37
43
  return (
38
44
  <Suspense>
45
+ {/* Handle OAuth callback when GitHub auth is enabled */}
46
+ {enableGithubAuth && (
47
+ <Suspense fallback={null}>
48
+ <OAuthCallback
49
+ redirectUrl={redirectUrl}
50
+ onSuccess={onOAuthSuccess ? (user, isNewUser) => onOAuthSuccess(user, isNewUser, 'github') : undefined}
51
+ onError={onError}
52
+ />
53
+ </Suspense>
54
+ )}
55
+
39
56
  <AuthProvider {...props}>
40
57
  <div
41
58
  className={`min-h-screen flex flex-col items-center justify-center bg-background py-6 px-4 sm:py-12 sm:px-6 lg:px-8 ${props.className || ''}`}
@@ -22,6 +22,7 @@ import {
22
22
 
23
23
  import { useAuthContext } from './AuthContext';
24
24
  import { AuthHelp } from './AuthHelp';
25
+ import { OAuthProviders } from './OAuthProviders';
25
26
 
26
27
  export const IdentifierForm: React.FC = () => {
27
28
  const {
@@ -328,6 +329,9 @@ export const IdentifierForm: React.FC = () => {
328
329
  </form>
329
330
  )}
330
331
 
332
+ {/* OAuth Providers (GitHub, etc.) */}
333
+ <OAuthProviders />
334
+
331
335
  {/* Help Section */}
332
336
  <AuthHelp />
333
337
  </CardContent>