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:
+29
-7
@@ -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 />} />
|
||||
|
||||
Reference in New Issue
Block a user