Appearance
How the admin works
The admin pair is a React SPA and a Laravel API on one host, v3.togoactive.com, behind one nginx server block. The SPA is static files; anything under /api goes to PHP.
The shape of a request
Two details in that nginx config are load-bearing:
- The whole
/apiURI reaches Laravel's front controller. The standardfastcgi-php.confsnippet assumes a.phpin the path and 405s sub-path POSTs, soSCRIPT_FILENAMEis pinned toindex.phpinstead. - The SPA falls back to
index.html, so client-side routes work on a hard refresh.index.htmlis servedno-cachewhile hashed assets cache forever — that combination is what makes a deploy visible on a normal refresh.
The panel also carries a blanket X-Robots-Tag: noindex on every response. It is repeated inside the index.html location block, because nginx's add_header overrides rather than merges — without the repeat, the one page that most needed the header would be served without it.
Authentication and authorisation
Three middleware layers run in order, and they answer different questions:
| Middleware | Question |
|---|---|
auth:admin | Is this a signed-in admin at all? |
admin.can:<permission> | Does their role grant this capability — events.view, event_setup.edit? |
admin.event (CheckEventAccess) | Do they have access to this specific event? |
Access is granted per event, through admin_user_events.role. There is no account that implicitly sees every event. Website content has a finer split again, per surface, so someone can edit pages without reaching settings.
The frontend
A Vite SPA. The conventions worth knowing before writing a screen:
src/utils/api.jswraps every call.api.gettakes an endpoint, not a full URL.- Settings screens share a shape:
SectionCard/ToggleCard/SaveBar/SetupHelpPanel,useStateseeded bygetInitialForm, andisDirtyfrom a JSON comparison — which is what makes the save bar appear only on a real change. - Event context comes from
useOutletContextviaEventLayout. - Never use native
alert/confirm/prompt— use theswalutility. - File uploads use
POSTwith_method=PUT, notapi.put. A realPUTdoes not carryFormDatathrough PHP.
Help tips
Tooltips are <HelpTip id="...">, resolved from src/help/helptips.json, which is generated from this documentation site. See the help tip pipeline.
Deploying
Building the admin frontend publishes it
nginx serves admin-frontend/dist directly. npm run build is the deploy — there is no separate publish step, and the change is live the moment the build finishes.
To type-check without publishing, build somewhere else:
npx vite build --outDir /tmp/build-check --emptyOutDirTwo more traps on this box:
- Never run
artisanas root inadmin-backend. It leaves root-owned cache files that php-fpm cannot read, and the API starts failing afterwards. - Run migrations one file at a time with
--path. There are pending migrations, and the first of them fails.
The admin's own API quirks
Documented properly in Admin backend API; the ones that surprise people:
- Six
GETendpoints write rows. They are not safe to retry blindly. - Publishing validates nothing. A page can be published in a state the builder would not let you construct.
Read next
- Admin frontend and Admin backend API — the full service docs
- How the WL side works
- The four codebases