Skip to content

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 queueValidationController lists UserActionStep records with status = EvidenceUploaded, scoped through the user relation (filteredByManagerSessionVia('userAction.user')), alongside a 50-row archive of recently approved/rejected steps, and exposes approve/reject actions that update the step and fire a Mixpanel "Evidence Reviewed" event [@validation-controller]. The frontend page is pages/manager/validation.tsx. This is the same evidence-review queue that Filament's UserActionStepResource surfaces under the "Verification Queue" nav label — see Filament admin panel — but Manager is the workflow ops staff actually use day to day.
  • PayoutsPaymentsController lists 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 with airtime_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 matching manager.payouts.* permission [@manager-routes]. Frontend: pages/manager/payments.tsx.
  • User lookupUsersController (index/show), frontend pages/manager/users.tsx and pages/manager/users/show.tsx.
  • AlertsAlertsController renders scheduled alerts as cards with affected-user counts, frontend pages/manager/alerts.tsx.
  • Analytics and homeAnalyticsController and HomeController back 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 the manager.access gate 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.