tfrere HF Staff Cursor commited on
Commit
4141164
Β·
1 Parent(s): f9ead24

Lazy OAuth: show welcome screen first, user avatar in topbar

Browse files
frontend/src/components/Layout/AppLayout.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import { useCallback, useRef, useEffect } from 'react';
2
  import {
 
3
  Box,
4
  Drawer,
5
  Typography,
@@ -32,7 +33,7 @@ const DRAWER_WIDTH = 260;
32
 
33
  export default function AppLayout() {
34
  const { sessions, activeSessionId, deleteSession, updateSessionTitle } = useSessionStore();
35
- const { isConnected, isProcessing, getMessages, addMessage, setProcessing, llmHealthError, setLlmHealthError } = useAgentStore();
36
  const {
37
  isLeftSidebarOpen,
38
  isRightPanelOpen,
@@ -300,16 +301,39 @@ export default function AppLayout() {
300
  </Typography>
301
  </Box>
302
 
303
- <IconButton
304
- onClick={toggleTheme}
305
- size="small"
306
- sx={{
307
- color: 'text.secondary',
308
- '&:hover': { color: 'primary.main' },
309
- }}
310
- >
311
- {themeMode === 'dark' ? <LightModeOutlinedIcon fontSize="small" /> : <DarkModeOutlinedIcon fontSize="small" />}
312
- </IconButton>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  </Box>
314
 
315
  {/* ── LLM Health Error Banner ────────────────────────────── */}
 
1
  import { useCallback, useRef, useEffect } from 'react';
2
  import {
3
+ Avatar,
4
  Box,
5
  Drawer,
6
  Typography,
 
33
 
34
  export default function AppLayout() {
35
  const { sessions, activeSessionId, deleteSession, updateSessionTitle } = useSessionStore();
36
+ const { isConnected, isProcessing, getMessages, addMessage, setProcessing, llmHealthError, setLlmHealthError, user } = useAgentStore();
37
  const {
38
  isLeftSidebarOpen,
39
  isRightPanelOpen,
 
301
  </Typography>
302
  </Box>
303
 
304
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
305
+ <IconButton
306
+ onClick={toggleTheme}
307
+ size="small"
308
+ sx={{
309
+ color: 'text.secondary',
310
+ '&:hover': { color: 'primary.main' },
311
+ }}
312
+ >
313
+ {themeMode === 'dark' ? <LightModeOutlinedIcon fontSize="small" /> : <DarkModeOutlinedIcon fontSize="small" />}
314
+ </IconButton>
315
+
316
+ {user?.picture ? (
317
+ <Avatar
318
+ src={user.picture}
319
+ alt={user.username || 'User'}
320
+ sx={{ width: 28, height: 28, ml: 0.5 }}
321
+ />
322
+ ) : user?.username ? (
323
+ <Avatar
324
+ sx={{
325
+ width: 28,
326
+ height: 28,
327
+ ml: 0.5,
328
+ bgcolor: 'primary.main',
329
+ fontSize: '0.75rem',
330
+ fontWeight: 700,
331
+ }}
332
+ >
333
+ {user.username[0].toUpperCase()}
334
+ </Avatar>
335
+ ) : null}
336
+ </Box>
337
  </Box>
338
 
339
  {/* ── LLM Health Error Banner ────────────────────────────── */}
frontend/src/components/WelcomeScreen/WelcomeScreen.tsx CHANGED
@@ -9,18 +9,26 @@ import {
9
  import { useSessionStore } from '@/store/sessionStore';
10
  import { useAgentStore } from '@/store/agentStore';
11
  import { apiFetch } from '@/utils/api';
 
12
 
13
  /** HF brand orange */
14
  const HF_ORANGE = '#FF9D00';
15
 
16
  export default function WelcomeScreen() {
17
  const { createSession } = useSessionStore();
18
- const { setPlan, setPanelContent } = useAgentStore();
19
  const [isCreating, setIsCreating] = useState(false);
20
  const [error, setError] = useState<string | null>(null);
21
 
22
  const handleStart = useCallback(async () => {
23
  if (isCreating) return;
 
 
 
 
 
 
 
24
  setIsCreating(true);
25
  setError(null);
26
 
@@ -44,7 +52,7 @@ export default function WelcomeScreen() {
44
  } finally {
45
  setIsCreating(false);
46
  }
47
- }, [isCreating, createSession, setPlan, setPanelContent]);
48
 
49
  return (
50
  <Box
 
9
  import { useSessionStore } from '@/store/sessionStore';
10
  import { useAgentStore } from '@/store/agentStore';
11
  import { apiFetch } from '@/utils/api';
12
+ import { triggerLogin } from '@/hooks/useAuth';
13
 
14
  /** HF brand orange */
15
  const HF_ORANGE = '#FF9D00';
16
 
17
  export default function WelcomeScreen() {
18
  const { createSession } = useSessionStore();
19
+ const { setPlan, setPanelContent, user } = useAgentStore();
20
  const [isCreating, setIsCreating] = useState(false);
21
  const [error, setError] = useState<string | null>(null);
22
 
23
  const handleStart = useCallback(async () => {
24
  if (isCreating) return;
25
+
26
+ // If user is not authenticated, trigger OAuth login first
27
+ if (!user?.authenticated) {
28
+ triggerLogin();
29
+ return;
30
+ }
31
+
32
  setIsCreating(true);
33
  setError(null);
34
 
 
52
  } finally {
53
  setIsCreating(false);
54
  }
55
+ }, [isCreating, createSession, setPlan, setPanelContent, user]);
56
 
57
  return (
58
  <Box
frontend/src/hooks/useAuth.ts CHANGED
@@ -1,17 +1,21 @@
1
  /**
2
- * Authentication hook β€” non-blocking.
3
  *
4
- * The app renders immediately. This hook fires a background check to /auth/me
5
- * and updates the agent store with user info when it resolves.
6
- * If an API call later returns 401, apiFetch handles the redirect to /auth/login.
7
  *
8
- * This avoids blocking the entire UI on an auth check that depends on backend
9
- * availability (which can be slow during session/MCP initialization).
10
  */
11
 
12
- import { useEffect } from 'react';
13
  import { useAgentStore } from '@/store/agentStore';
14
 
 
 
 
 
 
15
  export function useAuth() {
16
  const setUser = useAgentStore((s) => s.setUser);
17
 
@@ -32,16 +36,19 @@ export function useAuth() {
32
  }
33
  }
34
 
35
- // Not authenticated β€” check if auth is required
36
  const statusRes = await fetch('/auth/status', { credentials: 'include' });
37
  const statusData = await statusRes.json();
38
- if (statusData.auth_enabled) {
39
- window.location.href = '/auth/login';
 
40
  return;
41
  }
42
 
43
- // Dev mode β€” set dev user
44
- setUser({ authenticated: true, username: 'dev' });
 
 
45
  } catch {
46
  // Backend not ready β€” set dev user so the app is usable
47
  setUser({ authenticated: true, username: 'dev' });
@@ -50,4 +57,6 @@ export function useAuth() {
50
 
51
  checkAuth();
52
  }, [setUser]);
 
 
53
  }
 
1
  /**
2
+ * Authentication hook β€” non-blocking, lazy.
3
  *
4
+ * On mount: checks if the user is already authenticated (cookie/dev mode).
5
+ * Does NOT redirect to login automatically β€” the welcome screen handles that.
 
6
  *
7
+ * Exports `triggerLogin()` for components that need to start the OAuth flow
8
+ * (e.g. the "Start Session" button on the welcome screen).
9
  */
10
 
11
+ import { useEffect, useCallback } from 'react';
12
  import { useAgentStore } from '@/store/agentStore';
13
 
14
+ /** Redirect to the OAuth login page. */
15
+ export function triggerLogin() {
16
+ window.location.href = '/auth/login';
17
+ }
18
+
19
  export function useAuth() {
20
  const setUser = useAgentStore((s) => s.setUser);
21
 
 
36
  }
37
  }
38
 
39
+ // Not authenticated β€” check if auth is even enabled
40
  const statusRes = await fetch('/auth/status', { credentials: 'include' });
41
  const statusData = await statusRes.json();
42
+ if (!statusData.auth_enabled) {
43
+ // Dev mode β€” set dev user so the app is usable
44
+ setUser({ authenticated: true, username: 'dev' });
45
  return;
46
  }
47
 
48
+ // Auth is enabled but user is not logged in.
49
+ // Don't redirect β€” let the welcome screen show first.
50
+ // The user will be prompted to log in when they click "Start Session".
51
+ setUser(null);
52
  } catch {
53
  // Backend not ready β€” set dev user so the app is usable
54
  setUser({ authenticated: true, username: 'dev' });
 
57
 
58
  checkAuth();
59
  }, [setUser]);
60
+
61
+ return { triggerLogin };
62
  }