Session 2026-05-28: profile page overhaul, nav fixes, dashboard activity links

- Fix nav links not working from profile page (useEffect infinite re-render via unstable profile object ref)
- Fix nav hover/active: gold icon highlight, no background change; active links non-clickable
- Fix hover layout shift: add border: 1px solid transparent to all interactive elements
- Header icon buttons (search, theme toggle) now highlight gold on hover
- Profile page: replace calendar with activity feed (60/40 grid), add stat cards (tasks completed, active projects, revision requests, submissions)
- Profile card: title field, icon rows for location/email/linkedin, member since + role bottom-right, edit button top-right
- Profile portrait: remove wrapper column, fix left-gap alignment
- Add profiles.title migration
- Dashboard recent activity: name → /profile/{id}, task → /requests/{id} (clickable links)
- Icon-only sidebar with gold active/hover state, pointer-events: none on active links
- layout.md updated with profile page geometry rules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-28 15:32:46 -04:00
parent 565d2ed4bc
commit 283511bf3a
48 changed files with 4151 additions and 1889 deletions
+29 -7
View File
@@ -1,18 +1,32 @@
import { lazy, Suspense, Component } from 'react';
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import { AuthProvider, useAuth } from './context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
import PageLoader from './components/PageLoader';
class ChunkErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) { return { error }; }
componentDidCatch(error) {
const isChunkError = error?.name === 'ChunkLoadError'
|| error?.message?.includes('dynamically imported module')
|| error?.message?.includes('Failed to fetch');
if (isChunkError) {
const key = 'chunk_reload_attempted';
if (!sessionStorage.getItem(key)) {
sessionStorage.setItem(key, '1');
window.location.reload();
return;
}
sessionStorage.removeItem(key);
}
}
render() {
if (this.state.error) {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '60vh', gap: 16 }}>
<div style={{ fontSize: 14, color: 'var(--text-secondary)' }}>Page failed to load.</div>
<button className="btn btn-primary" onClick={() => { this.setState({ error: null }); window.location.reload(); }}>
<button className="btn btn-primary" onClick={() => { sessionStorage.removeItem('chunk_reload_attempted'); this.setState({ error: null }); window.location.reload(); }}>
Reload
</button>
</div>
@@ -25,11 +39,10 @@ class ChunkErrorBoundary extends Component {
import Login from './pages/Login';
import PayInvoice from './pages/PayInvoice';
const Settings = lazy(() => import('./pages/Settings'));
const ProfilePage = lazy(() => import('./pages/Settings'));
const CompaniesPage = lazy(() => import('./pages/CompaniesPage'));
const CompanyDetail = lazy(() => import('./pages/CompanyDetail'));
const Invoices = lazy(() => import('./pages/team/Invoices'));
const MeetingNotes = lazy(() => import('./pages/team/MeetingNotes'));
const RequestDetail = lazy(() => import('./pages/RequestDetail'));
const CreateInvoice = lazy(() => import('./pages/team/CreateInvoice'));
const CreateSubcontractorPO = lazy(() => import('./pages/team/CreateSubcontractorPO'));
@@ -48,6 +61,7 @@ const ExternalMyInvoiceCreate = lazy(() => import('./pages/external/MyInvoiceCre
const Projects = lazy(() => import('./pages/Projects'));
const ProjectDetailPage = lazy(() => import('./pages/ProjectDetailPage'));
const DashboardPage = lazy(() => import('./pages/DashboardPage'));
const TeamDashboard = lazy(() => import('./pages/team/TeamDashboard'));
const RequestsPage = lazy(() => import('./pages/RequestsPage'));
const MyInvoices = lazy(() => import('./pages/client/MyInvoices'));
const NewRequest = lazy(() => import('./pages/client/NewRequest'));
@@ -68,6 +82,12 @@ function NavigateCompanyDetail() {
return <Navigate to={`/company/${id}`} replace />;
}
function DashboardRoute() {
const { currentUser } = useAuth();
if (currentUser?.role === 'team') return <Navigate to="/team/dashboard" replace />;
return <DashboardPage />;
}
export default function App() {
return (
<AuthProvider>
@@ -77,7 +97,8 @@ export default function App() {
<Routes>
<Route path="/" element={<Login />} />
<Route path="/dashboard" element={<ProtectedRoute role={['team', 'external', 'client']}><DashboardPage /></ProtectedRoute>} />
<Route path="/dashboard" element={<ProtectedRoute role={['external', 'client']}><DashboardRoute /></ProtectedRoute>} />
<Route path="/team/dashboard" element={<ProtectedRoute role={['team']}><TeamDashboard /></ProtectedRoute>} />
<Route path="/projects" element={<ProtectedRoute role={['team', 'external', 'client']}><Projects /></ProtectedRoute>} />
<Route path="/projects/:id" element={<ProtectedRoute role={['team', 'external', 'client']}><ProjectDetailPage /></ProtectedRoute>} />
<Route path="/tasks/:id" element={<RedirectRequestDetail />} />
@@ -88,7 +109,6 @@ export default function App() {
<Route path="/companies/:id" element={<NavigateCompanyDetail />} />
<Route path="/requests" element={<ProtectedRoute role={['team', 'external', 'client']}><RequestsPage /></ProtectedRoute>} />
<Route path="/team-projects" element={<Navigate to="/projects" replace />} />
<Route path="/meeting-notes" element={<ProtectedRoute role="team"><MeetingNotes /></ProtectedRoute>} />
<Route path="/invoices" element={<ProtectedRoute role="team"><Invoices /></ProtectedRoute>} />
<Route path="/invoices/new" element={<ProtectedRoute role="team"><CreateInvoice /></ProtectedRoute>} />
<Route path="/subcontractor-pos/new" element={<ProtectedRoute role="team"><CreateSubcontractorPO /></ProtectedRoute>} />
@@ -109,7 +129,9 @@ export default function App() {
<Route path="/my-invoices-sub/new" element={<ProtectedRoute role="external"><ExternalMyInvoiceCreate /></ProtectedRoute>} />
<Route path="/my-invoices-sub/:id" element={<ProtectedRoute role="external"><ExternalMyInvoiceDetail /></ProtectedRoute>} />
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
<Route path="/profile" element={<ProtectedRoute><ProfilePage /></ProtectedRoute>} />
<Route path="/profile/:id" element={<ProtectedRoute><ProfilePage /></ProtectedRoute>} />
<Route path="/settings" element={<Navigate to="/profile" replace />} />
<Route path="/my-dashboard" element={<Navigate to="/dashboard" replace />} />
<Route path="/my-company" element={<Navigate to="/company" replace />} />