Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
920 changes: 889 additions & 31 deletions phd-advisor-frontend/package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions phd-advisor-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@codemirror/legacy-modes": "^6.5.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"@uiw/react-codemirror": "^4.25.9",
"katex": "^0.16.45",
"latex.js": "^0.12.6",
"lucide-react": "^0.544.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-markdown": "^10.1.0",
"react-scripts": "5.0.1",
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"web-vitals": "^2.1.4"
},
"scripts": {
Expand Down
10 changes: 8 additions & 2 deletions phd-advisor-frontend/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ function App() {
setCurrentView('auth');
};

const navigateToCanvas = () => {
const navigateToCanvas = (canvasView) => {
if (['insights', 'workspace', 'deliverables'].includes(canvasView)) {
localStorage.setItem('canvas-view-v2', canvasView);
}
setCurrentView('canvas');
};

Expand Down Expand Up @@ -74,17 +77,20 @@ function App() {
<div className="App">
{currentView === 'home' && (
<HomePage
onNavigateToHome={navigateToHome}
onNavigateToChat={isAuthenticated ? navigateToChat : navigateToAuth}
onNavigateToCanvas={isAuthenticated ? navigateToCanvas : navigateToAuth}
isAuthenticated={isAuthenticated}
/>
)}
{currentView === 'auth' && (
<AuthPage onAuthSuccess={handleAuthSuccess} />
)}
{currentView === 'canvas' && isAuthenticated && (
<CanvasPage
<CanvasPage
user={user}
authToken={authToken}
onNavigateToHome={navigateToHome}
onNavigateToChat={navigateToChat}
onSignOut={handleSignOut}
/>
Expand Down
104 changes: 104 additions & 0 deletions phd-advisor-frontend/src/components/AppHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React from 'react';
import { Home, Menu, Users } from 'lucide-react';
import ThemeToggle from './ThemeToggle';
import { useAppConfig } from '../contexts/AppConfigContext';

/**
* Shared floating header used on every page so the app feels like one surface.
*
* Props:
* currentPage: 'home' | 'chat' | 'canvas'
* onNavigateToHome, onNavigateToChat, onNavigateToCanvas: navigation callbacks
* (onNavigateToCanvas may receive 'insights' | 'workspace' to deep-link a view)
* onMobileMenu?: () => void — when present, shows the mobile menu button
* children?: ReactNode — extra controls slotted between the tabs and the theme toggle
*/
const AppHeader = ({
currentPage = 'home',
onNavigateToHome,
onNavigateToChat,
onNavigateToCanvas,
onMobileMenu,
children,
}) => {
const { config, resolveIcon } = useAppConfig();
const BrandIcon = resolveIcon ? resolveIcon('Users') : Users;

const goToCanvas = (view) => {
if (onNavigateToCanvas) onNavigateToCanvas(view);
};

// Accept either 'canvas' (all canvas tabs highlight equally) or a more specific
// 'canvas-<subview>' from CanvasPage so only the active one highlights.
const isOnHome = currentPage === 'home';
const isOnChat = currentPage === 'chat';
const isOnCanvas = currentPage === 'canvas' || currentPage.startsWith('canvas-');
const canvasSub = currentPage.startsWith('canvas-') ? currentPage.slice(7) : null;
const tabActive = (sub) => isOnCanvas && (canvasSub === null ? false : canvasSub === sub);

return (
<header className="floating-header app-header">
<div className="header-left">
{onMobileMenu && (
<button className="mobile-menu-button" onClick={onMobileMenu}>
<Menu size={20} />
</button>
)}
<button
className="modern-home-btn"
onClick={onNavigateToHome}
title="Home"
disabled={isOnHome}
aria-disabled={isOnHome}
>
<Home size={20} />
</button>
<div className="header-brand">
<div className="brand-icon">
<BrandIcon size={24} />
</div>
<div className="brand-text">
<h1>{config?.app?.title || 'Advisory'}</h1>
<p>{config?.app?.subtitle || 'AI-Powered Guidance'}</p>
</div>
</div>
</div>

{/* Hide the view pill bar on the home page — home is a landing page,
not part of the chat ↔ canvas surface. */}
{!isOnHome && (
<div className="canvas-tabs chat-view-tabs">
<button className={`tab ${isOnChat ? 'active' : ''}`} onClick={onNavigateToChat}>Chat</button>
<button className={`tab ${tabActive('insights') ? 'active' : ''}`} onClick={() => goToCanvas('insights')}>Insights</button>
<button className={`tab ${tabActive('workspace') ? 'active' : ''}`} onClick={() => goToCanvas('workspace')}>Workspace</button>
<button className={`tab ${tabActive('deliverables') ? 'active' : ''}`} onClick={() => goToCanvas('deliverables')}>Documents</button>
</div>
)}

{/* Compact mobile dropdown — appears in place of the pill bar at narrow widths */}
{!isOnHome && (
<select
className="canvas-tabs-mobile"
value={isOnChat ? 'chat' : (canvasSub || 'workspace')}
onChange={(e) => {
const v = e.target.value;
if (v === 'chat') onNavigateToChat();
else goToCanvas(v);
}}
>
<option value="chat">Chat</option>
<option value="insights">Insights</option>
<option value="workspace">Workspace</option>
<option value="deliverables">Documents</option>
</select>
)}

<div className="header-right">
{children}
<ThemeToggle />
</div>
</header>
);
};

export default AppHeader;
Loading
Loading