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:
@@ -0,0 +1,14 @@
|
||||
import { supabase } from './supabase';
|
||||
|
||||
export async function logActivity({ actorId, actorName, action, taskId, taskTitle, projectId, projectName }) {
|
||||
const { error } = await supabase.from('activity_log').insert({
|
||||
actor_id: actorId || null,
|
||||
actor_name: actorName || null,
|
||||
action,
|
||||
task_id: taskId || null,
|
||||
task_title: taskTitle || null,
|
||||
project_id: projectId || null,
|
||||
project_name: projectName || null,
|
||||
});
|
||||
if (error) console.error('logActivity failed:', action, error);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useLiveClock() {
|
||||
const [now, setNow] = useState(() => new Date());
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setNow(new Date()), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
return now;
|
||||
}
|
||||
|
||||
export function DashboardBanner() {
|
||||
const now = useLiveClock();
|
||||
const day = now.toLocaleDateString('en-US', { weekday: 'long', timeZone: 'America/New_York' });
|
||||
const date = now.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric', timeZone: 'America/New_York' });
|
||||
const time = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', timeZone: 'America/New_York', timeZoneName: 'short' });
|
||||
return (
|
||||
<div className="dashboard-banner">
|
||||
Fourge Branding • {day}, {date} • {time}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function getGreeting() {
|
||||
const h = new Date().getHours();
|
||||
if (h < 12) return 'Good morning';
|
||||
if (h < 17) return 'Good afternoon';
|
||||
return 'Good evening';
|
||||
}
|
||||
@@ -47,3 +47,13 @@ export function formatDateOnly(value, fallback = '—') {
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
export function fmtShortDate(value, fallback = '—') {
|
||||
if (!value) return fallback;
|
||||
const date = parseDateOnly(value) || new Date(value);
|
||||
if (isNaN(date)) return fallback;
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
const y = String(date.getFullYear()).slice(2);
|
||||
return `${m}/${d}/${y}`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export async function withTimeout(promise, ms = 12000, label = 'Request') {
|
||||
export async function withTimeout(promise, ms = 25000, label = 'Request') {
|
||||
let timerId;
|
||||
try {
|
||||
return await Promise.race([
|
||||
|
||||
Reference in New Issue
Block a user