Documentation
Authentication

Admin Mode

What an admin user can do, and how the UI gates it.

Who is an admin

Any user whose profiles.role = 'admin'. That is everyone on the FPM (Flooring Pros Marketing) team:

PersonTitleWhat they do in the portal
DanCMO / Co-founderFull admin access. Oversees every portal, owns strategy content, owns the roadmap.
ChesneyAccount ManagerAdds meeting notes, manages deliverables, responds to client requests, sends weekly recap.
KenanHead of FulfillmentManages deliverable pipeline, marks items ready for review, closes requests.
Client Revenue Strategist (CRS)Currently hiringAdds game plan updates, scorecard scores, wins, meeting summaries.
AnielloEngineering / DataOwns the codebase, deployments, integrations and database. Repo lives in his GitHub.

The admin role is set manually in Supabase by Aniello or Dan. There is no self-serve admin promotion.

The admin overlay

Admin and client see the same per-client URL. The difference is the overlay: when you are logged in as admin, the page shows extra UI on top of the client view:

  • The dark #202020 sidebar stays visible on the per-client portal route. Each client row has a pencil icon (visible on hover) for quick edit.
  • Pencil icons on every Client Portal sub-tab card and on every Client Info card.
  • The Connect / Disconnect popup for integrations.
  • Both hubs visible: Client Hub, Reporting, and Client Info.
  • An admin-only toolbar at the top of the main column with date range picker, Visit Site link, IntegrationPanel, and PDF download.
  • An Edit info button on the Client Info hub header that opens the modal at the top of all sections.

Capabilities

CapabilityWhere
View list of all clients/dashboard or the dark sidebar (always visible)
Switch between clients without leaving the portal viewSidebar — click any client row
Edit any client (one click)Pencil icon on hover in the sidebar row, or /dashboard/clients/[id]/edit
Create a new client/dashboard/clients/new
Open any client portal (admin overlay)/dashboard/clients/[id]
Edit client branding (logo upload, tier badge, booking URL)/dashboard/clients/[id]/edit — Branding & Identity section
Edit client info (29 fields — company, contact, address, domain, brand, social, billing, notes)Client Info hub → Edit info button or per-card pencil
Connect / reconnect integrationsGlobal Integrations panel + per-client Connect popups
Edit Client Portal sections (Snapshot, Scorecard, Game Plan, Deliverables, KPI Reporting, Content & Website, Documents)Inline pencil button on each portal sub-tab
Add meeting notes / weekly recapPending UI — for now, edit Game Plan and KPI Reporting sections
Invite a client user (sends magic link)Sidebar action on the client page
Download monthly PDFPDF Download button in the admin toolbar
Manage deliverable pipelineDeliverables sub-tab → editor modal
Respond to / close requestsCurrently surfaced in Slack — UI inbox is on the roadmap

Default route after login

Admin lands on /dashboard which renders the client list. Clicking a client navigates to /dashboard/clients/[id].

Inside the client page, the default tab is Client Portal (same as the client view). This is intentional — admins should see exactly what the client sees by default, then switch to internal tabs as needed.

How the UI knows you are admin

userRole is passed down as a prop from the server component /dashboard/clients/[id]/page.tsx:

  • userRole'admin' | 'client'.
  • Components gate edit buttons with {isAdmin && <button onClick={onEdit}>...</button>}.
  • Tabs that are admin-only are conditionally rendered in the TABS array.
Trust boundary
Hiding a button in JSX is not a security boundary. Every API route that mutates data (e.g. PUT /api/portal/[clientId], the integration endpoints, the invite endpoint) re-checks the role server-side. If you add a new mutating endpoint, you must add the role check — do not rely on the UI alone.

Server-side role check pattern

Every admin-only route looks like this:

const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return new Response('Unauthorized', { status: 401 })

const { data: profile } = await supabase
  .from('profiles').select('role').eq('id', user.id).single()
if (profile?.role !== 'admin') return new Response('Forbidden', { status: 403 })

// proceed with mutation

Viewing as a client

There is no formal impersonation mode yet. Admins see the same Client Portal a client would see; the only difference is the edit pencils and the extra top-level tabs. To preview exactly what a client sees, log out and log in as the client's test account, or hide the pencils with browser dev tools.

A proper "view as client" toggle is on the roadmap.