TradeForce migration docs

#Admin verification and ops

Module id: MOD-ADMIN
Doc profile: system

Platform admin console for TradeForce: role-gated /admin host, company and contractor verification lists, skill-rate editors on company detail, open/active shift inspection and edits, and invoice/payment-schedule recording. Target stack is React + Vite in app/ with Supabase Auth JWT + RLS/RPC under app/supabase/. Bubble page admin and its client workflows are the evidence for behavior; every persistent write must become server-authoritative (LOG-CLIENT-WRITES / MON-CLIENT-WRITES).

#Purpose

Platform admins need one console to verify companies and contractors, set skill rates, inspect and correct shifts, and mark time-entry payments as paid with notes. This module owns the /admin body inside the cabinet workspace, the tab query contract (adminnav / admintab), list/detail regions, approve and rate Save mutations, shift edit / stop-hiring, and admin payment confirm (including PostMark notify on edge). It does not own Header/SideBar chrome (MOD-SHELL), public auth (MOD-AUTH), contractor payment read UI (MOD-PAYMENTS), or Stripe Connect / KYC (MON-MARKETPLACE).

#Scope boundaries

In scope Out of scope
/admin host, PageLoaded role gates, empty-tab default Workspace Header / SideBar / mobile admin menu links (MOD-SHELL)
Companies verified / not-verified lists + detailed company rates Company shift creation product UI (MOD-COMPANY-SHIFTS)
Contractors verified / not-verified lists, detail, Approve + skill set Contractor marketplace / Stripe Connect onboarding
Open / Active shifts list + detailed shift edit / stop hiring Inventing schedulers beyond replacing Bubble ScheduleAPIEvent with the stack job approach
Payments tabs (previous / upcoming schedule / all invoices) + paid confirm Contractor /contractor/payments read surface (MOD-PAYMENTS)
Admin-only RLS/RPC for profile rates, approve, shifts, custom_rates, time-entry paid fields Shipping PostMark or service-role secrets to the browser

#Actors

Actor System behavior
Admin May remain on /admin; switches tabs; verifies companies/contractors; saves rates; edits/stops shifts; records admin paid amount/notes.
Company (signed in) PageLoaded redirects off admin to the company shifts host.
Contractor (signed in) PageLoaded redirects off admin to the contractor shifts host.
Visitor (signed out) PageLoaded redirects to /.
Session client Anon-key Supabase client + admin JWT; never ships connector keys.
Edge / RPC Admin-authorized mutations and PostMark notify after payment confirm.

#Admin composition

Admin is one cabinet page whose body regions switch on the tab query. Shell admin menu enters Shifts via admintab openshifts and Companies / Contractors / Payments via adminnav. Sub-tabs and detail tabs reuse the same param. Empty tab on load is filled with adminnav Shift (Bubble writes the option display). G-Shifts itself is visible only when tab matches openshifts or activeshifts — so the empty-tab default alone does not show the shifts list until Open/Active (or shell Shifts) is chosen.

flowchart TB subgraph host["Admin host /admin"] Gate["Role / session gates"] Tabs["tab query regions"] end subgraph regions["Body regions"] Co["Companies + company rates detail"] Ct["Contractors + approve popup"] Sh["Open / Active shifts + shift detail"] Pay["Payments + payment details popup"] end subgraph data["Persistence"] Profiles["profiles"] Rates["custom_rates"] Shifts["shifts"] Entries["time_tracking_entries"] end Gate --> Tabs Tabs --> Co Tabs --> Ct Tabs --> Sh Tabs --> Pay Co --> Profiles Co --> Rates Ct --> Profiles Sh --> Shifts Pay --> Entries

Bubble evidence for composition and gates is listed under Traceability / IR evidence.

#Primary journeys

Journeys are reconstructed from admin page workflows (PageLoaded → ChangePage, ButtonClicked → Search/ChangeThing/NewThing/ScheduleAPIEvent/connector). Where a chain ends in an edge notify, the wire literals are named; PostMark template HTML itself lives outside the export.

#J1 — Open admin and land on a gated host

  1. An admin opens /admin (with or without tab); the page mounts inside the cabinet workspace with the admin body outlet.
  2. If the session is missing, the app redirects to /; if the signed-in role is company, it redirects to the company shifts host; if contractor, to the contractor shifts host.
  3. When the user is an admin and tab is empty, the app navigates to the same admin host with tab set to the Shift admin-nav default (Bubble option display).
  4. Non-admins never see company, contractor, shift, or payment admin regions.

#J2 — Browse and open companies for verification

  1. Admin opens /admin?tab=companies and sees Verified Companies / Not verified Companies sub-tabs.
  2. Verified lists company profiles that completed onboarding and have at least one skill rate column set; Not verified lists the inverse rate filter among the same role/onboarding set.
  3. Admin opens Details on a row and lands on /admin?tab=detailedcompany with that company as detail context.
  4. Empty lists show “No result found”; query failures show a plain-language error in the companies region.

#J3 — Verify a contractor with skill set

  1. Admin opens /admin?tab=contractors, switches Verified / Not verified (preserve wire value spelling veriedcontractors for verified), and opens a contractor into /admin?tab=detailedcontractor.
  2. Verified lists contractor profiles that are active, onboarded, and approved true; Not verified lists the same role/active/onboarding set with approved false. On a not-yet-approved contractor, Admin chooses Approve, which opens the skill-set popup and loads the contractor into it.
  3. Admin confirms; the app persists approved true and the selected skill set via an admin-only server write, then closes the popup.
  4. Cancel closes the popup without approving; non-admin JWTs cannot set another user’s approved flag.

#J4 — Set company and contractor skill rates

  1. From detailed company, Admin sees Company rate and Contractor rate sections with exactly four pay-practice rows (Concrete Carpenter, Concrete Finisher, Concrete Laborer, Concrete Walls).
  2. Admin edits staging inputs and optional custom rate for an exact day (shift, existing rate, new rate).
  3. Admin chooses Save; the app creates a custom_rates row when that path is used, updates the company profile rate columns, and appends the custom rate to the company’s list — all in one admin-authorized transaction.
  4. Save failure shows a plain-language error; success resets inputs as the source does.

#J5 — Inspect and edit open or active shifts

  1. Admin opens /admin?tab=openshifts or /admin?tab=activeshifts, optionally filters by company, and opens a shift into /admin?tab=detailedshift.
  2. Admin edits evidenced shift fields and chooses Edit; the app creates an in-app notification to the company that the shift was changed, persists via admin RPC, and when the shift date is still in the future cancels/reschedules the tracking job that Bubble named shift.
  3. Discard resets the editor and goes back without saving.
  4. Stop hiring confirms stop_hiring and sets shift type upcoming (clearing schedule tracking when present), and updates the related contractor’s dependability for a company-canceled-shift positive; cancel closes the popup only.

#J6 — Record an admin payment on an invoice row

  1. Admin opens /admin?tab=payments and switches Previous payments / Upcoming payment schedule / All invoices.
  2. Admin opens a row into the payment details popup and enters amount and notes.
  3. On confirm with amount present, the app sets admin paid amount, notes, and status paid on the time-tracking entry via admin RPC, then may notify via edge PostMark (user-invitation-3, empty template model) using server secrets only.
  4. Confirm with empty amount runs only when the payment-tabs report list has at least one row; it follows the list-update + in-app notification (“Your payment is on the way!”) path; Cancel closes without writing.
Label / trigger Behavior
Shell Shifts (MOD-SHELL) /admin?tab=openshifts (admintab)
Shell Companies / Contractors / Payments /admin?tab= companies | contractors | payments (adminnav)
Empty tab on admin PageLoaded Self ChangePage with adminnav Shift (display string)
Companies sub-tabs State → verified_companies | notverifiedcompanies; host stays tab=companies
Contractors sub-tabs State → veriedcontractors | notverifiedcontractors; host stays tab=contractors
Open / Active shift sub-tabs /admin?tab=openshifts | activeshifts
Company / contractor / shift Details detailedcompany | detailedcontractor | detailedshift
Payments sub-tabs State → previous_payments | upcoming_payment_schedule | all_invoices; host stays tab=payments
Go-back on detail Restores prior list tab per source go-previous / ChangePage

#Vertical slices (decompose)

Item Product outcome
ADMIN-HOST /admin in cabinet_workspace; PageLoaded gates; empty-tab → Shift default; region visibility by tab. Enabler for the journeys below.
ADMIN-COMPANIES Verified / not-verified company lists; open detailed company.
ADMIN-CONTRACTORS Verified (profiles.approved true) / not-verified (profiles.approved false) contractor lists; detail; Approve popup + server approve.
ADMIN-RATES Four pay-practice rate editors + custom rate Save on detailed company.
ADMIN-SHIFTS Open/Active lists; shift detail edit (+ company notification); stop hiring (+ dependability); schedule cancel/reschedule.
ADMIN-PAYMENTS Three payment tabs; payment details popup; record paid + edge notify (empty-amount also gated on report count ≥ 1).

#Side-effect contracts

Trigger Side effect Owner
PageLoaded signed out ChangePage → index / ADMIN-HOST
PageLoaded non-admin company / contractor ChangePage → company / contractor shifts hosts ADMIN-HOST
PageLoaded admin + empty tab ChangePage self + tab = Shift display ADMIN-HOST
Open company / contractor / shift Details tab → detailed* + selected row context ADMIN-COMPANIES / ADMIN-CONTRACTORS / ADMIN-SHIFTS
Confirm Approve profiles.approved true + skill_set list replace (RPC) ADMIN-CONTRACTORS
Save rates Insert custom_rates + update profile rate columns (RPC) ADMIN-RATES
Edit shift (future date) In-app notification to company; update shift; CancelScheduledAPIEvent + ScheduleAPIEvent shift (→ stack job) ADMIN-SHIFTS
Stop hiring confirm stop_hiring true; shift type upcoming; clear schedule tracking; bump contractor dependability (company_canceled_shift) ADMIN-SHIFTS
Confirm payment (amount present) time entry admin_paid / notes / status paid; optional PostMark edge ADMIN-PAYMENTS
Confirm payment (amount empty + report count ≥ 1) List update + notification “Your payment is on the way!” + PostMark edge ADMIN-PAYMENTS

#Role gate matrix (PageLoaded)

Rule Predicate Destination
Signed out not logged in / (index)
Non-admin company logged in, role ≠ admin, role = company Company shifts host
Non-admin contractor logged in, role ≠ admin, role = contractor Contractor shifts host
Empty tab fill logged in, tab empty /admin with Shift default

#Style and fidelity

Admin uses cabinet_workspace (section → container → workspace head from SHELL + surface-card body). Stamp data-bubble-id on the admin page root and G-admin outlet. Preserve list/table layout (not marketing cards), sub-tab labels including leading spaces on “ Not verified …”, empty copy “No result found” / “No data”, and detail labels (Dependability score, Company Name Rates, payment popup fields). Shared tokens come from the app-level stylesheet that SHELL-LAYOUT (MOD-SHELL) delivers out of IR styles (ir/slices/styles____part_all.json — 49 styles, 8 color tokens, 1 font token); this cycle's module map has no separate design-system module, so do not defer a token to one.

#Failure modes

Case Expected behavior
Signed-out or non-admin hits /admin Redirect away; no admin list data rendered.
Profile/session read fails on host Plain-language error in the outlet; no silent catch.
Companies / contractors / shifts / payments query fails Plain-language error in that region.
Approve / Save rates / shift edit / stop hiring / record payment rejected Plain-language error; no partial success UI; non-admin JWT must fail at RLS/RPC.
Empty-amount payment confirm with zero report rows Confirm empty-amount path does not run (report count gate).
PostMark / notify edge failure after paid write Paid persistence may already be committed; surface notify failure without rolling back inventively unless RPC designs otherwise — do not expose upstream bodies.
Client-only Bubble ChangeThing ported as-is Blocked: money and verification writes require server authority (LOG-CLIENT-WRITES / MON-CLIENT-WRITES).
Stripe Connect / payouts Out of scope (MON-MARKETPLACE); do not invent KYC journeys from this page.

#Needs human clarification

None. After catalog + inventory + sanitized-export chase for PageLoaded gates, adminnav/admintab values, company/contractor Search filters (including not-verified approve1_boolean equals false), Approve/Save/Edit/Stop-hiring/Payment workflows, ScheduleAPIEvent shift, and PostMark body literals (From, TemplateAlias user-invitation-3, empty TemplateModel), admin console behavior is encoded in the export. PostMark template HTML content remains outside the export (same wire alias as MOD-PAYMENTS cycle mail) but is not required to implement the call site. Server-authority migration is a stack obligation, not an open product question.

#Traceability

Product concept Target (route / component) Bubble evidence
Admin page host /admin, AdminPage / AdminHost bTHun0, bTHvk0
Signed-out gate roleGate/ Workflow bTKrc / action bTKrjbTGYf
Non-admin role gates roleGate → company / contractor hosts Workflow bTJeL / bTKmkbTITd, bTKoPbTHMg
Empty tab default tabContract Shift Workflow bTKmq / action bTKmw
Companies region AdminCompaniesPage bTHxi0, bTHwr0, sub-tabs bTHwD0
Company detail + rates CompanyRatesPanel bTHyA0, Save bTKZn / bTKaL
Contractors region + approve AdminContractorsPage, ApproveSkillSetPopup bTIBH0, bTIBv0, element_condition:bTIBv0:1, bTICV0, bTIIT0, bTJKz
Shifts region + detail AdminShiftsPage, ShiftDetailEditor bTIKT0, bTIMv0, bTMYL / bTMYS, Edit bTMcD / notify bTMeM
Stop hiring StopHiringPopup bTMYr, confirm bTMZI / dependability bTMZT
Payments region + popup AdminPaymentsPage, PaymentDetailsPopup bTKHz0, bTKKu0, bTMpJ / bTMqB, custom_state:bTKIM0:report_
Admin nav / tab enums tabContract adminnav, admintab
Pay practices (4 skills) payPractices enum pay_practices
Custom rates entity custom_rates custom_rate
PostMark notify Edge admin-payment-notify external_ns:bTNMZ, actions bTNNo / bTNNp
Schedule tracking job Stack job replacing Bubble schedule bTMcN → API event bTMBT (wf_name shift)

#IR evidence

#Module evidence roots

Supporting segments: ir/slices/bTHuo0____root.json, ir/slices/bTHuo0__elements____remaining_children.json, ir/slices/bTHuo0__elements__bTHvp0__elements____remaining_children.json, ir/slices/bTHuo0__workflows____part_A.json, user types company, custom_rate, rates, option sets adminnav, admintab.

#Path-key ↔ inventory id aliases

Path key / pointer Inventory id Notes
/pages/bTHuo0 bTHun0 admin
/pages/bTHuo0/workflows/bTKrn bTKrc PageLoaded not logged in
/pages/bTHuo0/workflows/bTJeM bTJeL PageLoaded role ≠ admin
/pages/bTHuo0/workflows/bTKmx bTKmq PageLoaded empty tab
/pages/bTHuo0/workflows/bTKaN bTKaL Save rates
/pages/bTHuo0/workflows/bTJLE bTJKz Confirm approve
/pages/bTHuo0/workflows/bTMYN / bTMYX bTMYL / bTMYS Open / Active shifts tabs
/pages/bTHuo0/workflows/bTMcU bTMcD Edit shift
/pages/bTHuo0/workflows/bTMpL / bTMqN bTMpJ / bTMqB Payment confirm (amount / empty) — catalog ids bTMpJ / bTMqB
/api/bTMBU bTMBT Scheduled API wf_name shift

#Tab and option contracts (chased)

adminnav db_values: shift, companies, contractors, payments, detailedcompany, detailedcontractor, detailedshift (shift_type marked deleted).
admintab db_values: veriedcontractors (typo preserved), notverifiedcontractors, verified_companies, notverifiedcompanies, openshifts, activeshifts, all_invoices, upcoming_payment_schedule, previous_payments.
pay_practices: concrete_carpenter, concrete_finisher, concrete_laborer, concrete_walls (four rows).

Empty-tab fill (bTKmw) writes OptionValue display for adminnav/shift. G-Shifts visibility (element_condition:bTIKT0:0) is admintab openshifts or activeshifts only. Companies / Contractors / Payments groups key off adminnav companies / contractors / payments.

#List filters (chased)

#Mutations (chased)

#Privacy & authorization

Target: only profiles.role = admin may read verification lists and mutate other users’ approved/rates, shifts stop/edit, custom_rates inserts, and time-entry admin paid fields. SPA uses user JWT + RLS/RPC; PostMark secrets only on edge. Bubble privacy_role:* rows for these types are answered structurally in decompose → DATA-MODEL RLS, not separate journey items.

#Naming map (idiomatic highlights)

Target Bubble evidence
profiles.role / approved / onboarding_complete / active role_option_role, approve1_boolean, onboarding_boolean, active__boolean
profiles.company_* / contractor_* rate columns company_*_number, contractor_*_number
custom_rates custom_rate (existed_rate_numberexisted_rate, new_rate_numbernew_rate)
time_tracking_entries time_tracking1 (admin_paid___numberadmin_paid_amount, admin_notes_textadmin_notes)
pay_practices enum option pay_practices db_values

Note: inventory user_type rates is an evidence segment; do not invent a live rates table from deleted fields — skill rates live on profiles + custom_rates as chased above. User_type company remains a segment label; admin company lists Search user with role company.

Generated from the repository at 2026-09-22 15:55 UTC · source of truth is docs/ in the workspace, not this site.