AdminStack Client Portal is Complete — From Auth to File Uploads to Live Polling
Just wrapped up what might be my longest single coding session in months. The AdminStack client portal went from "mostly working" to "actually complete" in one marathon 10-commit push.
The Big Picture
I started the day with a basic portal that could show messages. I'm ending it with a fully-featured client communication hub that rivals what I've seen in $200/month tools. Public access, real-time updates, file sharing, message archiving, invoice tracking — the whole nine yards.
What Got Built
Authentication & Routing
First things first — clients need to access their portals without logging into our system. Added /portal to the public routes so the token-based URLs work seamlessly.
UI Polish That Actually Matters Spent way more time than I should have on the visual details, but honestly? It was worth it. Rounded cards, gradient progress bars with glows, distinct milestone dots, proper chat bubble alignment. The header shows the project name with client name as subtitle, plus a subtle "Powered by AdminStack™" badge.
Live Updates Done Right Implemented dual polling rates: 15 seconds for portal data and invoices (the slow stuff), 10 seconds for messages (the chatty stuff). No more refreshing pages to see if the team responded.
File Uploads (The Tricky Part) This is where I hit the most bugs. The storage paths were inconsistent between admin and client uploads, the database foreign keys were fighting with Supabase auth, and the bucket calls were wrong. Fixed all of it:
- Aligned storage paths to
{portalId}/{role}/{timestamp}-{filename} - Removed the problematic
uploaded_byforeign key - Added a
sourcecolumn to distinguish team vs client files - Built a public API endpoint for client uploads
Now you get clean "From Team" (blue) and "From Client" (teal) badges on both sides.
Message Archiving Teams need to clean up old conversations without deleting them entirely. Added an archive system with a toggle to show/hide archived messages. Required a database migration but keeps things tidy.
Dashboard Improvements The admin side got some love too:
- Clickable client names in the portals table
- Save progress with proper loading states and feedback
- Edit modal instead of navigation (much smoother UX)
- Better error handling throughout
Invoice Integration Clients can now see their invoices right in the portal with status badges and a running total of outstanding amounts. Auto-refreshes with the rest of the portal data.
The Side Quest
Midway through, I got distracted and built an entire demo for something called "Zenith Intelligence Platform" — a sales intelligence tool with DISC profiling and battle cards. Sometimes you just need to build something completely different to reset your brain. Deployed it and moved on.
Technical Decisions
A few key choices that future me will thank present me for:
- Set
uploaded_byto null for all uploads and use thesourcecolumn instead — cleaner data model - Message archiving is team-only but both sides respect the archived flag
- Reused form state variables in the edit modal to avoid duplicate code
- Kept polling rates conservative to avoid hammering the database
What's Next
The portal is functionally complete. I need to run migration 038 on the production database, then it's ready for real client testing.
Honestly, I'm pretty proud of how this turned out. It feels like a real product now, not just a developer tool. The kind of thing I'd actually want to use if I were a client.
Time to ship it and see what breaks in the real world.