Yaya Manager Inertia Ops Console¶
Yaya Manager is the second admin surface in Yaya Engine: an Inertia.js +
React console mounted at /manager (routes/manager.php), built for ops
staff who run the same handful of workflows every day — reviewing evidence,
processing payouts, looking up a user, checking alerts — without navigating a
generic resource table [@manager-routes]. It authenticates through the same
admin guard and AdminUser model as the
Filament admin panel, but where Filament
exposes every table as CRUD, Manager exposes only the handful of routes each
workflow needs, gated route-by-route with granular manager.* permissions
rather than a single panel-wide gate [@manager-routes] [@admin-access].
Route gating and session-scoped filters¶
Every route under /manager sits behind three layers of middleware:
auth:admin, can:manager.access, and a custom manager.filters middleware,
registered in bootstrap/app.php as App\Http\Middleware\ManagerGlobalFilters
[@manager-routes] [@bootstrap-app]. Destructive or restricted actions add a
second permission check on top: validation approve/reject requires
manager.validation.review; processing or marking payouts paid requires
manager.payouts.process; cancelling a payout requires
manager.payouts.cancel; and both payout export routes and the data-benefits
export route require manager.payouts.export [@manager-routes]. This mirrors
the role table in AdminAccess: the manager role gets manager.access plus
all three payout sub-permissions and manager.validation.review, so in
practice any manager account can do everything Manager exposes, but the route
layer still enforces each permission independently in case that ever changes
[@admin-access].
ManagerGlobalFilters reads filter_country and filter_fsp query
parameters on any request, writes them into the session as
manager_filter_country / manager_filter_fsp (or forgets them if the value
is empty), and re-merges the resolved values back onto the request
[@manager-global-filters]. HandleInertiaRequests then shares those two
session values to every Inertia page as manager_filters.country /
manager_filters.fsp, which is how the frontend renders the current
country/FSP scope without a dedicated API call [@inertia-requests]. Every
manager controller applies this same scope to its queries through a
filteredByManagerSession() query scope, and single-record routes
(payments.cancel, users.show, alerts.show) additionally call
authorizeManagerAccess($user) from the AuthorizesManagerAccess trait, which
aborts with a 403 if the record's country or FSP does not match the
session-held filter [@authorizes-manager-access] [@payments-controller]. The
net effect is that a manager account scoped to one country or FSP cannot see
or act on another country's records, even by guessing a record's URL — the
filter is enforced at the query and record level, not just hidden in the UI.
List-style controllers (UsersController, AlertsController) also use
HasSegmentedFilters, a small trait that turns a segmentDefinitions() map of
named query scopes into both a filtered query and a set of segment counts for
tab badges, so the same segment definitions drive both "which rows show" and
"how many rows are in each tab" [@segmented-filters].
Workflow controllers¶
Each Manager controller owns one workflow and renders one Inertia page under
resources/js/pages/manager/ [@manager-controllers-dir] [@manager-pages-dir]:
- Validation queue —
ValidationControllerlistsUserActionSteprecords withstatus = EvidenceUploaded, scoped through the user relation (filteredByManagerSessionVia('userAction.user')), alongside a 50-row archive of recently approved/rejected steps, and exposesapprove/rejectactions that update the step and fire a Mixpanel "Evidence Reviewed" event [@validation-controller]. The frontend page ispages/manager/validation.tsx. This is the same evidence-review queue that Filament'sUserActionStepResourcesurfaces under the "Verification Queue" nav label — see Filament admin panel — but Manager is the workflow ops staff actually use day to day. - Payouts —
PaymentsControllerlists payouts scoped by the session filter, plus aggregate pending/approved/paid totals, and separately queries users eligible for data-benefit (airtime) disbursement — onboarded, with a viewed in-app alert, not yet disbursed, on an FSP withairtime_support_enabled— as its own "Data Benefits" tab [@payments-controller]. It exposes process-pending, mark-as-paid, cancel, export, and data-benefits mark-disbursed/export endpoints, each gated by the matchingmanager.payouts.*permission [@manager-routes]. Frontend:pages/manager/payments.tsx. - User lookup —
UsersController(index/show), frontendpages/manager/users.tsxandpages/manager/users/show.tsx. - Alerts —
AlertsControllerrenders scheduled alerts as cards with affected-user counts, frontendpages/manager/alerts.tsx. - Analytics and home —
AnalyticsControllerandHomeControllerback the dashboard-style landing pages (pages/manager/analytics.tsx,pages/manager/home.tsx); analytics is a placeholder page today. - Manager also has its own login flow (
Auth\AuthenticatedSessionController,pages/manager/auth/login.tsx) and an in-app notification center (ManagerNotificationController), both outside themanager.accessgate so an unauthenticated admin can still reach the login screen.
Division of labor with Filament¶
Bulk, filterable exports live in Filament, not Manager: the equivalent
CSV-export workflow for the general user base is UserExportPage in the
Filament panel, filtering by FSP, gender, country, and onboarded/verified
state, rather than a Manager route [@user-export-page]. The split is
intentional — Manager's payout and data-benefits export endpoints exist
because they are steps inside a payout workflow a manager runs repeatedly,
while UserExportPage is a one-off reporting tool an engineer or super-admin
reaches for occasionally, so it stays in the resource-oriented Filament panel
instead of the task-oriented Manager console. Both consoles ultimately read
and write the same tables — FSPs,
preparation plans and their steps, users — so a
change to one of those models is a change both surfaces need to account for.
The Manager frontend is built on the same
Inertia/React + Wayfinder stack as
the rest of the app's non-Filament pages.