feat: photo visibility control with client access (#172)

Add two-tier gallery access system allowing clients (e.g., wedding couples) to
review and hide photos before the gallery is shared with guests.

Backend:
- Migration 074: add visibility column to photos, client_access_enabled/
  client_password_hash/client_share_token to events
- Client login endpoint (POST /auth/gallery/:slug/client-login) with bcrypt PIN
- Gallery photo list filters hidden photos for guests, shows all for clients
- Visibility toggle endpoints (single + bulk) for client access level
- Admin event CRUD supports client access fields
- Email template includes client access link + PIN (EN/DE/RU/PT)

Frontend:
- ClientAccessPage: PIN entry form at /gallery/:slug/client-access
- GalleryView: client mode banner, visibility counter, toggle controls
- GridGalleryLayout: eye/eye-off overlay per photo for clients
- AdminPhotoGrid: visibility badge, bulk Hide/Show buttons
- EventDetailsPage: Client Access settings section (toggle, PIN, link)
- CreateEventPage: client access toggle + PIN in event creation form
- GalleryAuthContext: accessLevel/isClient/clientLogin support
- New complete pt-BR locale (pt.json) with all translations
- Client access i18n keys for EN, DE, RU, PT
This commit is contained in:
Paul Nothaft
2026-03-17 13:04:41 +01:00
parent 999c66dbbf
commit e1b6e43e52
25 changed files with 2217 additions and 1112 deletions
+8
View File
@@ -55,8 +55,13 @@ export interface Event {
css_template_id?: number | null;
// Photo cap
photo_cap?: number | null;
// Client access (#172)
client_access_enabled?: boolean;
client_share_token?: string;
}
export type GalleryAccessLevel = 'guest' | 'client';
export interface GalleryInfo {
event_name: string;
event_type: string;
@@ -92,6 +97,8 @@ export interface Photo {
audio_codec?: string;
width?: number;
height?: number;
// Visibility (#172)
visibility?: 'visible' | 'hidden';
// Feedback fields
has_feedback?: boolean;
average_rating?: number;
@@ -206,6 +213,7 @@ export interface GalleryAuthResponse {
require_password?: boolean;
photo_cap?: number | null;
};
accessLevel?: GalleryAccessLevel;
}
// API Error type