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:
@@ -12,7 +12,7 @@ const TEAM_EMAILS = [
|
||||
'twebb@fourgebranding.com',
|
||||
];
|
||||
|
||||
const ALLOWED_TYPES = ['new_request', 'sent_to_client', 'revision_submitted', 'client_approved', 'invoice_sent', 'receipt_sent', 'subcontractor_po_sent'] as const;
|
||||
const ALLOWED_TYPES = ['new_request', 'sent_to_client', 'revision_submitted', 'client_approved', 'invoice_sent', 'receipt_sent', 'subcontractor_po_sent', 'subcontractor_invoice_submitted'] as const;
|
||||
type EmailType = typeof ALLOWED_TYPES[number];
|
||||
|
||||
// Types that only team members may trigger
|
||||
@@ -391,8 +391,45 @@ serve(async (req) => {
|
||||
`;
|
||||
}
|
||||
|
||||
else if (type === 'subcontractor_invoice_submitted') {
|
||||
const subName = requireStr(data?.subName);
|
||||
const invoiceNumber = requireStr(data?.invoiceNumber);
|
||||
const total = requireStr(data?.total);
|
||||
|
||||
subject = `New Invoice from ${esc(subName)} — #${esc(invoiceNumber)}`;
|
||||
html = `
|
||||
<div style="font-family:sans-serif;max-width:560px;margin:0 auto;color:#1a1a1a;">
|
||||
<div style="background:#141414;padding:20px 28px;border-radius:8px 8px 0 0;">
|
||||
<img src="https://portal.fourgebranding.com/fourge-logo.png" alt="Fourge Branding" style="height:28px;" />
|
||||
</div>
|
||||
<div style="background:#fff;padding:28px;border:1px solid #e5e7eb;border-top:none;border-radius:0 0 8px 8px;">
|
||||
<h2 style="margin:0 0 8px;font-size:20px;">New Subcontractor Invoice</h2>
|
||||
<p style="color:#555;margin:0 0 24px;">${esc(subName)} has submitted an invoice for review.</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin-bottom:24px;">
|
||||
<tr style="background:#f9f9f9;">
|
||||
<td style="padding:10px 14px;font-size:13px;color:#666;">Invoice #</td>
|
||||
<td style="padding:10px 14px;font-size:13px;font-weight:700;text-align:right;">${esc(invoiceNumber)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:10px 14px;font-size:13px;color:#666;">Subcontractor</td>
|
||||
<td style="padding:10px 14px;font-size:13px;font-weight:700;text-align:right;">${esc(subName)}</td>
|
||||
</tr>
|
||||
<tr style="background:#f9f9f9;">
|
||||
<td style="padding:10px 14px;font-size:13px;color:#666;">Amount</td>
|
||||
<td style="padding:10px 14px;font-size:18px;font-weight:700;color:#141414;text-align:right;">$${esc(total)}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a href="https://portal.fourgebranding.com/invoices" style="display:block;background:#141414;color:#fff;text-align:center;padding:14px;border-radius:8px;text-decoration:none;font-weight:700;font-size:16px;margin-bottom:20px;">Review Invoice</a>
|
||||
<p style="font-size:12px;color:#999;text-align:center;margin:0;">
|
||||
Questions? <a href="mailto:hello@fourgebranding.com" style="color:#555;">hello@fourgebranding.com</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ── 5. Resolve recipients ────────────────────────────────────────────────
|
||||
const teamTypes = ['new_request', 'revision_submitted', 'client_approved'];
|
||||
const teamTypes = ['new_request', 'revision_submitted', 'client_approved', 'subcontractor_invoice_submitted'];
|
||||
let recipients: string[];
|
||||
let cc: string[] | undefined;
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
create table if not exists activity_log (
|
||||
id uuid default gen_random_uuid() primary key,
|
||||
created_at timestamptz default now(),
|
||||
actor_id uuid references profiles(id) on delete set null,
|
||||
actor_name text,
|
||||
action text not null,
|
||||
task_id uuid references tasks(id) on delete cascade,
|
||||
task_title text,
|
||||
project_id uuid references projects(id) on delete cascade,
|
||||
project_name text
|
||||
);
|
||||
|
||||
alter table activity_log enable row level security;
|
||||
|
||||
create policy "authenticated read activity_log"
|
||||
on activity_log for select
|
||||
using (auth.role() = 'authenticated');
|
||||
|
||||
create policy "authenticated insert activity_log"
|
||||
on activity_log for insert
|
||||
with check (auth.role() = 'authenticated');
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Auto-add external user to project_members when assigned to a task,
|
||||
-- so RLS "External reads assigned tasks" policy grants them project-wide task visibility.
|
||||
|
||||
create or replace function public.sync_project_member_on_task_assign()
|
||||
returns trigger as $$
|
||||
begin
|
||||
if new.assigned_to is not null and new.project_id is not null then
|
||||
insert into public.project_members (project_id, profile_id)
|
||||
values (new.project_id, new.assigned_to)
|
||||
on conflict (project_id, profile_id) do nothing;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql security definer;
|
||||
|
||||
drop trigger if exists trg_sync_project_member_on_task_assign on public.tasks;
|
||||
create trigger trg_sync_project_member_on_task_assign
|
||||
after insert or update of assigned_to
|
||||
on public.tasks
|
||||
for each row execute function public.sync_project_member_on_task_assign();
|
||||
|
||||
-- Backfill existing assigned tasks
|
||||
insert into public.project_members (project_id, profile_id)
|
||||
select distinct project_id, assigned_to
|
||||
from public.tasks
|
||||
where assigned_to is not null and project_id is not null
|
||||
on conflict (project_id, profile_id) do nothing;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table public.profiles
|
||||
add column if not exists title text;
|
||||
Reference in New Issue
Block a user