The Dashboard "Recent Activity" widget and the header notifications
dropdown both rendered raw activity-type strings (e.g. the literal
"feature_flags_updated") for any type missing from their lookup
maps — including everything emitted by the recently-added customer
portal (#354), webhooks (#327), API tokens (#322), event types,
event-publish flow, admin user management (#350), and the
feature-flags reorg itself.
Two coordinated changes:
1. Smart formatter for feature_flags_updated. The backend writes
`metadata.changed = { [flagKey]: { from, to } }` on every save.
New formatFeatureFlagsChanged() helper in admin.service.ts reads
that diff and renders:
- 1 change → "Customer Portal enabled"
- N changes → "3 features updated: Customer Portal enabled,
Calendar disabled, Quotes enabled"
Per-flag display labels source from `settings.features.<key>.title`
so they stay in sync with the Features tab. Unknown flag keys
fall through to a humanised version of the key.
2. 33 missing activity types added to BOTH renderers and to the
`admin.activities.*` + `admin.notificationMessages.*` i18n
namespaces across all six locales. Coverage groups: customer
portal (13 types), admin user management (6), webhooks (3),
API tokens (2), event types (4), event publish/logo (3), bulk
delete (1), and assorted post-merge surfaces (4).
The notifications.service.ts switch + admin.service.ts fallback
message map are still duplicated; consolidating them into a
single source of truth is a follow-up worth doing before the
next significant addition. For now both stay in sync via this PR.
en + de hand-translated. nl + pt + ru + fr machine-translated and
flagged for native review per project convention.
formatBrandingSettings was updated when the BrandingSettings
interface added the footer-overhaul fields (#441 / #440), so the
admin BrandingPage initialised them as empty strings on every load.
Saving any other field then sent the form's empty socials /
promo_markdown / promo_position back to the backend and wiped the
saved values from the DB. The public gallery footer kept rendering
the old values until the next save, which is why the bug appeared
asymmetric (visible to galleries, gone from the admin form).
Add the missing read mappings for the seven branding_* keys so the
form round-trips them correctly.
Reported by @Rekoo-PS in #460 (split out of #447).
The pagination-clamp useEffect added in #448 (commit 9c4a96f) was
inserted at the top of the component body, BEFORE the useQuery that
declares `data`. Because the useEffect's dependency array
`[data?.pagination, page]` is evaluated immediately when that line
executes, every render hit a temporal dead zone access on `data` and
threw `ReferenceError: Cannot access 'data' before initialization`
— minified to "Cannot access 'I' before initialization" in the
production bundle, crashing the entire page.
TypeScript caught this at the time
(`Block-scoped variable 'data' used before its declaration`) but the
project's build doesn't fail on TS errors so it shipped anyway.
Move the effect to immediately after the useQuery so `data` is in
scope. Behavior unchanged otherwise — same dep array, same setPage
clamp logic.
Reported by @derooijmnl on v3.45.1-beta.0.
Post-merge cleanups after #403 (customer portal):
- Renumber 090_backfill_photo_dimensions_v2.js → 096 to follow #403's
090_add_customer_accounts ... 095_add_customer_portal_flag chain.
- customerAccountsService.js: TODO note on must_change_password
documenting that the column is decorative until an admin
pre-loaded-password flow ships (mirrors what adminAuth does for
must_change_password today).
- customerAuth.js: doc-comment on the /login route explaining why the
customerPortal feature flag deliberately doesn't gate it (toggle off
hides UI, doesn't revoke existing-customer access; deactivate
individual accounts to lock out).
- 095_add_customer_portal_flag.js: header comment said "Migration 094"
(copy-paste from 094) — now matches the filename.
The aspect-aware gallery layouts (masonry / mosaic / justified) read
photo.width and photo.height to size each card to the source's real
proportions. Two import paths were inserting rows without those
fields, which forced MasonryGalleryLayout to fall back to a hard-coded
800×600 default — every card came out the same shape, so users
reported masonry as "always cropped to 1:1ish" no matter which
thumbnail fit mode they chose.
- fileWatcher.js: extract dims with sharp.metadata() before insert.
- s3AutoImporter.js: same, materialising a tmp local copy via
withLocalCopy so it works in S3 mode.
- migration 090: backfill any pre-existing rows with NULL dims
(skips videos, skips S3 deployments — those need the writer fix
alone since migrations cannot reach the storage backend).
- imageProcessor.js: change DEFAULT_THUMBNAIL_FIT from 'cover' to
'inside' (only kicks in when the seed setting is missing — existing
installs keep their saved value). Add UI tooltip recommending
'inside' for masonry/mosaic/justified, 'cover' for uniform grids.
i18n covers all six locales.
Previously these locales fell through to en for every customer.* /
customers.* / settings.customerSurface / settings.features.customerPortal
key. Machine-translated and flagged in the PR description as needing
native review.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keeps the customer-surface branding toggles adjacent to the other
brand-visibility controls instead of floating at the bottom of the
page, where they were easy to miss.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds back the "Show logo" / "Show company name" toggles for the
customer dashboard, scoped to /customer/* surfaces only. Lives as a
dedicated card at the bottom of Settings → Branding, gated by the
customerPortal feature flag so admins who haven't enabled the portal
don't see it.
* Backend: restored GET/PUT /admin/settings/customer-surface
endpoints, whitelisted only to the two branding keys
(customer_show_logo, customer_show_company_name). The
calendar/quotes/bills feature globals that used to live on this
endpoint are now driven by the Features tab (feature_flags table).
* customerAccountsService.getCustomerSurfaceGlobals() reads from
app_settings again so /api/customer/auth/session honours the
toggles in its branding payload.
* New CustomerDashboardBrandingCard component with its own save
flow — separate from the main BrandingPage payload so flipping a
toggle doesn't replay the full branding mutation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RequireFeature calls useFeatureFlags(), which throws unless mounted
inside FeatureFlagsProvider — and that provider only wraps
AdminLayout. So unauthenticated visitors hitting /customer/login
crashed into the React error boundary with 'Oops! Something went
wrong'.
The customerPortal flag continues to hide every admin-side surface
(sidebar entry, /admin/customers routes, CustomerAccountPicker on
event forms), which is what the flag is actually for. The
customer-side tree stays reachable so existing customers can still
log in even if the admin flips the flag off temporarily.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The route was registered in upstream/beta's server.js but dropped
during the rebase squash — the Features tab GET/PUT both 404'd, so
the customerPortal flag (and every other flag) couldn't be toggled.
Restored the mount in its upstream/beta position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
server.js was still requiring ./src/middleware/requireCustomerPortal
— a file deleted during the AdvancedFeaturesTab cleanup — which
crashed the backend on boot in production (MODULE_NOT_FOUND).
The customerPortal feature flag is now enforced on the frontend via
<RequireFeature flag="customerPortal" /> route guards (App.tsx) and
AdminSidebar visibility. Defence in depth is provided by
customerAccountsService.isCustomerPortalEnabled() in adminEvents.
Routes themselves are still protected by adminAuth / customerAuth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The customer-portal squash inadvertently reverted the upstream/beta
fix from PR #427: production NODE_ENV was flipping the cookie Secure
flag back to hard `true`, which broke admin login on
HTTPS-frontend → HTTP-backend reverse-proxy stacks (browser drops
the Secure cookie over HTTP, login loops indefinitely).
Restored upstream/beta's tokenUtils.js verbatim and re-layered only
the customer cookie helpers (CUSTOMER_COOKIE_NAME,
setCustomerAuthCookie, clearCustomerAuthCookie,
getCustomerTokenFromRequest) on top.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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 <noreply@anthropic.com>
The "apply recommended preset on event-type change" effect was firing on
the initial mount AND every time the eventTypes API resolved (because
availableEventTypes is recomputed when that query settles). The first
fire matched the wedding default and clobbered the global Branding
theme that the previous effect had just applied.
Track the previous event_type in a ref and bail out when it hasn't
actually changed. The Branding-default effect now wins on first paint,
and the recommended-preset behaviour still kicks in when the user
manually picks a different event type.
Restores the green state of smoke spec 07 (#323-B regression).