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:
+14
-10
@@ -7,6 +7,7 @@ import { useAuth } from '../context/AuthContext';
|
||||
import { serviceTypes } from '../data/mockData';
|
||||
import { cleanupTaskStorage, deleteCompanyData } from '../lib/deleteHelpers';
|
||||
import { renameClientFolder, backfillClientFolders } from '../lib/filebrowserFolders';
|
||||
import { logActivity } from '../lib/activityLog';
|
||||
|
||||
export default function CompanyDetail() {
|
||||
const { id } = useParams();
|
||||
@@ -174,6 +175,7 @@ export default function CompanyDetail() {
|
||||
status: 'active',
|
||||
}).select().single();
|
||||
if (data) {
|
||||
logActivity({ actorId: currentUser.id, actorName: currentUser.name, action: 'project_created', projectId: data.id, projectName: data.name });
|
||||
setProjects(prev => [data, ...prev]);
|
||||
setNewProjectName('');
|
||||
setShowNewProject(false);
|
||||
@@ -241,7 +243,7 @@ export default function CompanyDetail() {
|
||||
onChange={e => setNameVal(e.target.value)}
|
||||
autoFocus
|
||||
required
|
||||
style={{ fontSize: 22, fontWeight: 700, padding: '4px 10px', margin: 0, width: 260 }}
|
||||
style={{ fontSize: 22, fontWeight: 400, padding: '4px 10px', margin: 0, width: 260 }}
|
||||
/>
|
||||
<button type="submit" className="btn btn-primary btn-sm" disabled={savingName}>{savingName ? '...' : 'Save'}</button>
|
||||
<button type="button" className="btn btn-outline btn-sm" onClick={() => setEditingName(false)}>Cancel</button>
|
||||
@@ -253,10 +255,12 @@ export default function CompanyDetail() {
|
||||
</div>
|
||||
)}
|
||||
<div className="page-subtitle">
|
||||
{users[0]?.name && <>{users[0].name}</>}
|
||||
{users[0]?.name && (company.phone || company.address) && ' · '}
|
||||
{company.phone && <>{company.phone}</>}
|
||||
{company.phone && company.address && ' · '}
|
||||
{company.address && <>{company.address}</>}
|
||||
{!company.phone && !company.address && 'No contact info'}
|
||||
{!users[0]?.name && !company.phone && !company.address && 'No contact info'}
|
||||
</div>
|
||||
</div>
|
||||
{isTeam && <button
|
||||
@@ -299,7 +303,7 @@ export default function CompanyDetail() {
|
||||
>
|
||||
{t}
|
||||
{t === 'users' && availableUsers.length > 0 && (
|
||||
<span style={{ marginLeft: 6, fontSize: 10, background: tab === t ? 'rgba(0,0,0,0.3)' : 'var(--danger)', color: 'white', padding: '1px 5px', borderRadius: 10, fontWeight: 700 }}>
|
||||
<span style={{ marginLeft: 6, fontSize: 10, background: tab === t ? 'rgba(0,0,0,0.3)' : 'var(--danger)', color: 'white', padding: '1px 5px', borderRadius: 4, fontWeight: 400 }}>
|
||||
{availableUsers.length}
|
||||
</span>
|
||||
)}
|
||||
@@ -326,7 +330,7 @@ export default function CompanyDetail() {
|
||||
<div style={{
|
||||
width: 32, height: 32, borderRadius: 4, background: 'var(--accent)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontSize: 12, fontWeight: 700, color: '#111', flexShrink: 0,
|
||||
fontSize: 12, fontWeight: 400, color: '#111', flexShrink: 0,
|
||||
}}>
|
||||
{user.name?.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)}
|
||||
</div>
|
||||
@@ -346,7 +350,7 @@ export default function CompanyDetail() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ fontWeight: 600, fontSize: 14 }}>{user.name}</div>
|
||||
<div style={{ fontWeight: 400, fontSize: 14 }}>{user.name}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{user.email || '—'}</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-muted)', textTransform: 'capitalize', marginTop: 2 }}>{user.role || '—'}</div>
|
||||
</>
|
||||
@@ -388,7 +392,7 @@ export default function CompanyDetail() {
|
||||
</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{availableUsers.map(user => (
|
||||
<div key={user.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 14px', background: 'var(--bg)', borderRadius: 8, border: '1px solid var(--border)' }}>
|
||||
<div key={user.id} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 14px', background: 'var(--bg)', borderRadius: 4, border: '1px solid var(--border)' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
{editingUserId === user.id ? (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
@@ -405,7 +409,7 @@ export default function CompanyDetail() {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ fontWeight: 600, fontSize: 13 }}>{user.name}</div>
|
||||
<div style={{ fontWeight: 400, fontSize: 13 }}>{user.name}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{user.email}</div>
|
||||
<div style={{ fontSize: 11, color: 'var(--text-muted)', textTransform: 'capitalize', marginTop: 2 }}>{user.role || '—'}</div>
|
||||
</>
|
||||
@@ -476,10 +480,10 @@ export default function CompanyDetail() {
|
||||
const active = projectTasks.filter(t => t.status !== 'client_approved').length;
|
||||
const done = projectTasks.filter(t => t.status === 'client_approved').length;
|
||||
return (
|
||||
<div key={project.id} className="interactive-surface" style={{ display: 'flex', alignItems: 'center', background: 'var(--card-bg)', border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden' }}>
|
||||
<div key={project.id} className="interactive-surface" style={{ display: 'flex', alignItems: 'center', background: 'var(--card-bg)', border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden' }}>
|
||||
<Link to={`/projects/${project.id}`} className="interactive-row" style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px', textDecoration: 'none', cursor: 'pointer' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600, fontSize: 14, color: 'var(--text-primary)' }}>{project.name}</div>
|
||||
<div style={{ fontWeight: 400, fontSize: 14, color: 'var(--text-primary)' }}>{project.name}</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 3 }}>
|
||||
{projectTasks.length} job{projectTasks.length !== 1 ? 's' : ''}
|
||||
{active > 0 && <> · <span style={{ color: 'var(--accent)' }}>{active} active</span></>}
|
||||
@@ -513,7 +517,7 @@ export default function CompanyDetail() {
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 130px 130px 60px', gap: 8, marginBottom: 8, alignItems: 'center' }}>
|
||||
<div />
|
||||
{['New', 'Revision'].map(label => (
|
||||
<div key={label} style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)', textAlign: 'right' }}>{label}</div>
|
||||
<div key={label} style={{ fontSize: 11, fontWeight: 400, textTransform: 'uppercase', letterSpacing: '0.5px', color: 'var(--text-muted)', textAlign: 'right' }}>{label}</div>
|
||||
))}
|
||||
<div />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user