feat(customers): customer portal (#354) on top of feature-flags reorg

Implements the recurring-customer login surface from
the-luap/picpeak#354 plugged into the maintainer's
new feature-flag infrastructure (PR #443) instead of
a parallel toggle.

* New `customerPortal` feature flag (foundation flag for the
  not-yet-built calendar/quotes/bills/messaging customer
  surfaces). Defaults FALSE on fresh installs, TRUE on existing
  installs (events > 0) via migration 095 so live customer
  accounts don't disappear mid-deployment.
* Foundation schema: customer_accounts, customer_invitations,
  event_customer_assignments, customer_password_resets, plus
  RBAC permissions customers.view / .create / .delete granted
  to super_admin + admin system roles.
* Backend: /api/admin/customers (invite, list, search, assign,
  deactivate, reset password) + /api/customer/auth/* +
  /api/customer/* (login, dashboard, accept-invite, reset).
  Customer JWT bypass minted via
  /api/customer/events/:slug/access-token so existing gallery
  middleware stays untouched.
* Frontend: /customer/* route tree gated by RequireFeature flag
  customerPortal, with login / dashboard / accept-invite /
  reset pages and a customer-side sidebar layout.
  /admin/customers and /admin/customers/:id gated identically.
* Settings → Features grows a "Customers" section with a
  Customer portal card. The maintainer's Features tab stays the
  single source of truth — no parallel Advanced features tab.
* CustomerAccountPicker on event create/edit forms hides itself
  when the flag is off; backend ignores customer_account_ids in
  that case instead of erroring the whole event save.

Translations: en + de hand-translated. nl/pt/ru fall through to
en — flagged here as needing native review.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Luca
2026-05-11 00:05:20 +02:00
co-authored by Claude Opus 4.6
parent f2f48f31b0
commit 087ef45942
54 changed files with 9816 additions and 54 deletions
@@ -11,6 +11,7 @@ import {
Receipt,
BarChart3,
Users,
UserCog,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button, Card } from '../../../components/common';
@@ -107,6 +108,26 @@ export const FeaturesTab: React.FC = () => {
/>
</Section>
{/* Customer accounts (#354) — recurring customer logins. The
calendar / quotes / bills / messaging cards below are
customer-side surfaces; they only render in the customer
dashboard when this is on. */}
<Section title={t('settings.features.sections.customers', 'Customers')}>
<FeatureCard
icon={UserCog}
title={t('settings.features.customerPortal.title', 'Customer portal')}
description={t(
'settings.features.customerPortal.description',
'Persistent customer logins. Recurring clients see all their assigned galleries from one place — no per-event passwords. Foundation for Calendar / Quotes / Bills (which only render in the customer dashboard when this is on).',
)}
status="beta"
statusLabel={statusLabel('beta')}
sidebarLabel={t('navigation.customers', 'Customers')}
enabled={staged.customerPortal}
onToggle={(next) => setFlag('customerPortal', next)}
/>
</Section>
{/* Communication */}
<Section title={t('settings.features.sections.communication', 'Communication')}>
<FeatureCard