Customer Portal — Developer Guide
Marketplace Operations module (customer-portal 1.0.0). Flat namespaces (no Modules/ package). Invite-only Contact accounts with a dedicated Sanctum guard.
Identity
| Concern | Staff | Portal |
|---|---|---|
| Model | User | PortalUser |
| Guard | tenant-api | portal-api |
| Provider | users | portal_users |
| Permissions | Spatie tenant-api | None — PortalRecordScope only |
| Token name | tenant-token | portal-token |
PortalUser: contact_id (unique per tenant), email, password, status (invited | active | disabled), invite token hash/expiry. Soft deletes. Email must match Contact email at invite.
Services
App\Services\Central\PortalWorkspaceLookupService— email → workspaces that already have that portal account (POST /api/central/v1/public/portal-workspaces)App\Services\Tenant\CustomerPortal\PortalUserService— invite / resend / disable / enable / accept inviteApp\Services\Tenant\CustomerPortal\PortalAuthService— login / logout / token / password resetApp\Services\Tenant\CustomerPortal\PortalRecordScope— contact + optional company scope- Document services:
PortalInvoiceService,PortalPaymentService,PortalQuotationService,PortalContractService PortalHelpDeskService— create stampscontact_id/company_id; notes setportal_user_id
Frontend
- Staff:
src/pages/customer-portal/, Contact invite actions, nav under Operations - Portal SPA:
src/pages/portal/*,PortalLayout/PortalAuthLayout,usePortalAuthStore(isolated token storage). Shared-host login/forgot: email first, then searchable company picker from the central lookup (invite/reset links still carry?workspace=). - Routes:
/#/portal/login, invite/reset, authenticated/#/portal/... FrontendUrl::portalInvite()/portalResetPassword()
Help Desk note authorship
help_desk_notes.portal_user_id (nullable) — catalog help-desk 1.12.0 → 1.13.0. Staff resources expose portal_author when present.
Hard exclusions (v1)
Projects/tasks, magic-link login, public KB, portal 2FA, online checkout.