Inertia.js bridges Laravel controllers to React pages under apps/web/resources/js/Pages/. Vite bundles TypeScript + Tailwind.
Structure #
Pages/Dashboard/— KPI dashboard builderPages/TenantJourney/— campaigns, experiments, reporting, Editor (click-to-translate), Translations (scope override grid)Pages/RapidStorSettings/— native RapidStor facility/corp settings (schema-driven tabs, WYSIWYG, per-locale translations editor)Pages/RevenueManagement/— Home (portfolio landing), rules, approval centre (activity feed + bulk pricing), Portfolio, revenue mapPages/Admin/— internal admin includingStats.tsxPages/Tasks/,Contacts/,Settings/— operations modules
Admin Stats page #
Route: GET /admin/stats. Tabs:
- Application Interest — coming-soon sign-ups
- System Alerts — admin notifications
- Benchmarks — cross-corp booking funnel benchmarks (
BenchmarksTab.tsx)
Benchmarks tab consumes Inertia prop benchmarks from ApplicationInterestController@stats. Date range changes trigger partial reload (only: ['benchmarks']). Five views: by corporation, timezone, fleet baseline, one-vs-rest, top/bottom. Conversion rates are RapidStor-only; move-in volumes span all channels (footnote in UI).
Admin — Data Sync tab #
Component: Pages/Admin/components/DataSyncTab.tsx on the corporations admin page. Shows per-corporation importer schedules with last-run status, manual sync triggers, and enable/disable toggles.
- Requirement gating — Each importer declares
ImporterRequirementcases (SITELINK_API,RAPIDSTOR_TOKEN,GOOGLE_INTEGRATION,MTAS_API,BIGQUERY_CONFIG).ImporterRequirement::unmet()is the single source of truth shared by manual sync dispatch (AdminController::syncData), scheduled command (RunScheduledSyncs), and queuedDataImportJobexecution. - Schedule toggle —
SyncScheduleRowblocks enabling a schedule when requirements are unmet, but always allows disabling (no backend check on disable). - Scheduled sync guard —
app:run-scheduled-syncsskips due schedules when the corporation no longer satisfies importer requirements, preventing failing jobs after config is revoked.
Tenant Journey — Editor & translations #
Editor (Pages/TenantJourney/Editor.tsx): embeds RapidStor v2 preview iframe; click-to-translate opens TranslationInspectorPanel for snippet keys. Elements controlled by RapidStor site settings are flagged to avoid false positives. Translations page (Translations.tsx) edits language/corp/location overrides via TranslationGrid + SnippetTranslationsDialog.
RapidStor Settings screen #
Page: Pages/RapidStorSettings/Index.tsx. Schema tabs from server; client helpers handle path get/set, feature gating (customFeatures, multi-site secondary read-only), validation parity with legacy AngularJS, and per-locale TranslationsEditor for translatable fields. Facility dropdown lives in page header via AuthenticatedLayout props; form state resyncs on facility change.
Helpdesk Center panel #
Component: features/helpdesk-center/HelpdeskPanel.tsx. Opened from the site header when the helpdesk Pennant flag is enabled. Tabs: Articles, Tours, legacy Helpdesk index.
Articles tab (ArticleSearch): loads the full article list once via RTK Query useListHelpdeskArticlesQuery → GET /api/v1/helpdesk/articles, then filters titles client-side as the user types (no debounced server search). Drill-in fetches rendered content via GET /api/v1/helpdesk/page/{id}. Analytics capture coarse metadata only (query length, never raw search text).
Revenue Management — Home & Approval Centre #
Home (Pages/RevenueManagement/Home.tsx): corp-scoped landing at /revenue-management/home/{facility?}. Sections include HubCards, AttentionStrip (portfolio-wide pending / below-market), KpiRoiStrip, OperationsSection, FacilitiesTable, ActivityFeed, and ValueReportSection. Hydrates from engine portfolio bundle; missing surfaces show not-wired placeholders.
Approval Centre (Pages/RevenueManagement/ApprovalCentre.tsx): three-column layout — bulk-pricing grid (left), alerts filter (centre), activity feed (right).
- Bulk pricing grid (
BulkPricingTable.tsx): rows keyed on RapidStor unit-type_id; columns Unit group, Units/avail, Occ, Base, Competitive, Pricing formula, Unit attributes, Seasonal, Price discovery, Suggested, Override, Action. Live inventory fields from corp-wide bulk-pricing path. Discriminator sub-label (Type · Floor N · Features) when present. Collapsible facility groups with Approve-all. Why modal (WhyModal.tsx): Starting point → factor contributions → Suggested, with Stage-as-override / Approve. - Activity feed (
ActivityFeed.tsx): wired viauseGetActivityQuery→GET /api/v1/revenue-management/activity?facility=; client-sidegroupActivity()groups events by date (Today/Yesterday/date) with relative timestamps in browser TZ. Also reused on Home. - Engine-connected gating: UI surfaces disabled states when facility is not
engine_connected; manual engine runs and undo are blocked for disconnected facilities.
PostHog analytics #
app.tsx identifies the logged-in user and captures $pageview / feature_viewed only when authenticated. Anonymous pages (Login, ForgotPassword, Invite, Welcome) do not emit pageviews, so DAU/WAU unique-user insights reflect real operators rather than anonymous session noise.
Feature-gated UI #
Pages check Pennant application flags client-side where needed; server middleware enforces access. Disabled apps are hidden from navigation. Sidebar assembly is centralized in ApplicationMenuBuilder (see Architecture).
Admin Application Management tab: corporation rows show a read-only "Resolves to" hint when global ceiling changes effective state (ApplicationStateResolver::effectiveFromValues). RM-family child rows are read-only in corporation mode (entitlement-driven); global mode remains editable. Grid save for a deliberately-gated RM corp (onboarded before, now un-entitled) shows a warning flash instead of silently re-entitling.