Internal Help Desk Portal
One page per employee, holding exactly the tools their role uses. The part I care about more is what happens on the way out. Access to the portal dies with the employee record, and the tool stays blunt about what that still does not cover.
Access is the quiet failure on most teams. So the guardrails came first, and the portal was built around them.
Try it live
The demo is open, no login. Use the "Preview as" control to switch roles and watch the tool grid rebuild. Then open the admin panel and look at the roster, the roles, and the audit log. Every name and tool link in it is invented.
What is real and what is made up in the demo
The demo lives at flexscale-help-desk-demo.vercel.app. It is a fictional logistics company, so every person, email, and tool link in it is invented.
It is open on purpose, with no sign-in, so anyone can click through it. The real internal build sat behind Google sign-in with an invite-only roster. Same data model, same admin flows.
The problem
Onboarding a hire, removing someone who left, or changing what a role can see is small, frequent work. It is also the work that goes wrong quietly.
A typo locks out the wrong person. A skipped step leaves someone who left with a live login. Nobody notices either one until it matters.
What I built
- A role-based tool launcher. One role per person, and the role decides the tiles. Nobody scrolls past nine systems they cannot open.
- An admin panel to onboard, offboard, change a role, edit which tools a role sees, and clear the access-request queue.
- A staged-diff approval flow. Every change is shown as a before and after and has to be approved before it touches the live database.
- Google sign-in plus a roster gate, so access has two locks instead of one.
Access follows the employee, not a checklist
Sign-in is Google. On top of that, the person has to exist as a row in the users table. Miss either gate and they do not get in.
The second gate is what makes onboarding a single row. It is also what puts the first gate to work: sign-in rides on Google Workspace, so when somebody is removed there, the portal closes behind them.
That is the whole design goal. Nobody has to remember this tool on someone's last day, because access is attached to the employee record instead of a separate list.
None of that works if the thing has to live somewhere strange. It does not. This is a Next.js app on a Postgres database, so a company can self-host it on its own cloud, Google Cloud included, right beside the Workspace accounts it already signs people in with.
Being precise about the hosting claim
The public demo runs on Vercel, and I have not deployed this to Google Cloud. The claim is portability, not a deployment.
It holds because there is nothing exotic in the stack. A standard Next.js app and a Postgres database will run wherever a company already runs things, which is the whole reason the access story is possible in the first place.
And here is what that still does not do
Taking someone out of the portal removes the portal. It does not remove them from anything the portal links to.
are actually revoked by an offboard here. Their accounts in the underlying systems have to be closed separately, and the tool surfaces that warning every single time an offboard is staged.
Deleting the row is the easy half. The rest is a real list of real accounts in real systems, and somebody has to work it.
Anyone who has actually run an offboarding has been burned by that gap. A tool that stays quiet about it is teaching people the job is finished. This one says it out loud every time, whether or not anyone wants to read it again.
Nothing gets written blindly
Every change is two steps, and they never collapse into one.
- Stage. Read the current state, build the exact change, show it as a before and after, and say how many people it hits. Then stop and ask.
- Apply. Only after an explicit yes. The write runs scoped to one email, one id, or one role, and the audit row goes in with it.
"Edit" is a valid answer. The change gets revised and the diff comes back. A batch request gets one combined diff and one approval, not five separate chances to say yes on autopilot.
Blast radius is on the diff
Role-level edits are the dangerous ones. Adding a tool to a role changes the tiles for everyone in that role, not for one person.
So the number of people affected is shown before anyone approves. Approving a change that hits a whole team should feel different from approving one that hits a single person.
Every change leaves a row
An applied change writes who did it, what action, which table, and the before and after values.
The management workflow writes the same rows the admin panel writes. So a change made by script and a change made by a person clicking are indistinguishable in the log. Neither one gets to hide.
whether a human or a script made the change. An audit log that only catches people is not an audit log.
The safety rails on the write path
- No DROP, no TRUNCATE, and no unfiltered DELETE or UPDATE. Ever.
- Every write is scoped to a specific email, id, or role key.
- Role keys and tool names are validated against the live tables instead of guessed.
- Retiring a tool archives it rather than deleting it, so the history behind it survives.
- Anything ambiguous, like which of two people named Jordan, gets asked about before it is staged.
Why onboarding never waited on an email
The row is what grants access, not a welcome message. A new person can sign in the moment their row exists.
That mattered in practice. Automated welcome emails stayed switched off while the sending domain was still unverified, and it blocked nobody, because sending was never on the path to access.
Reusable by design
The roster and the tool catalog are data, not code. Swapping them is what turns this into a different company's portal, and the access model comes along unchanged.
That is the useful part. The tiles are the easy bit. The two gates, the staged diff, the blast radius, the audit row, and the refusal to call a deleted row an offboarding are what took judgment.