● ● ● ● ● ● ● ● SaveTheDay app organisation. Stack technique final (réservé) Backend : Node.js + Express + TypeScript DB : supabase Auth & Notifications : Supabase Auth (client) + Supabase jwt Front : Next.js (App Router) + TypeScript + Tailwind Images : Publit (upload direct signed + CDN) ( https://publit.io/ ) Hébergement : Fly.io / Vercel Emails : Nodemailer + Zohomail Payment link from Anka pay ( https://www.anka.africa/fr/payments ) File Structure (Fullstack) root/ │ ├── apps/ │ ├── frontend/ # Next.js (App Router) │ │ ├── app/ │ │ │ ├── (public pages)/ # Landing, Contact, Login, Signup │ │ │ ├── photographer/ # /dashboard /galleries etc. │ │ │ ├── host/ # host dashboard │ │ │ ├── app-holder/ # admin dashboards │ │ │ ├── g/ # public event pages │ │ │ ├── layout.tsx │ │ │ └── globals.css │ │ ├── components/ │ │ │ ├── ui/ # Button, Modal, Skeleton, Toast, Loader, Lightbox │ │ │ ├── dashboard/ # Sidebar, Topbar, Cards │ │ │ ├── forms/ │ │ │ └── gallery/ │ │ ├── lib/ │ │ │ ├── supabaseClient.ts │ │ │ ├── publit.ts │ │ │ ├── api/ │ │ │ │ ├── auth.js # Supabase Auth helpers │ │ │ │ ├── db.js # Supabase client (for galleries, hosts, etc.) │ │ │ │ ├── uploads.js # Publit.io direct upload logic + signed URL retrieval │ │ │ │ ├── notifications.js # Realtime subscription with Supabase │ │ │ │ └── payments.js # AnkPay integration │ │ │ └── utils.ts │ │ └── package.json │ │ │ └── backend/ # Express + TypeScript API │ ├── src/ │ │ ├── server.ts # Express/Fly.io entrypoint │ │ ├── routes/ │ │ │ ├── auth.ts # JWT/Supabase auth sync │ │ │ ├── publit.ts # Signed URL generation + metadata validation │ │ │ ├── media.ts # Store file metadata + relations │ │ │ ├── galleries.ts │ │ │ ├── admin.ts │ │ │ ├── domains.ts │ │ │ └── notifications.ts │ │ ├── middleware/ │ │ │ ├── verifyJWT.ts │ │ │ ├── requireRole.ts │ │ │ └── errorHandler.ts │ │ ├── services/ │ │ │ ├── supabase.service.ts # Centralized Supabase queries (instead of Mongo) │ │ │ ├── publit.service.ts # Calls Publit.io API for signatures │ │ │ ├── email.service.ts │ │ │ ├── galleryService.ts │ │ │ ├── userService.ts │ │ │ ├── publitService.ts │ │ │ ├── domainService.ts │ │ │ └── notificationService.ts │ │ ├── utils/ │ │ │ ├── jwt.js # Verify Supabase JWT │ │ │ ├── validateUpload.ts │ │ │ ├── nodemailer.ts │ │ │ ├── supabaseAdmin.ts │ │ │ ├── ankaPay.ts │ │ │ └── qr.ts │ │ ├── workers/ │ │ │ ├── scheduler.ts │ │ │ ├── media.ts │ │ └── types/ │ │ └── index.d.ts │ └── package.json │ ├── fly.toml ├── dockerfile └── .env External Services — Setup & Integration Steps Service Purpose Setup Steps Before Integration Supabase DB + Auth + Realtime + Storage Create project → get project URL + anon and service _ role keys. Enable Auth (email confirmation). Create tables via SQL. Set RLS (Row Level Security) policies. Create .env variables in Fly.io: SUPABASE _ URL, SUPABASE _ KEY, SUPABASE _ JWT _ SECRET. Publit.io Image & video hosting Create account → get API key + API secret. Set up 2 buckets: public and private. Configure upload presets (signed upload). Save credentials to .env. Zoho Mail + Nodemailer Outgoing emails Create Zoho Mail account. Enable App password or SMTP credentials. Set SMTP _ USER, SMTP _ PASS, SMTP _ HOST, SMTP _ PORT in .env. AnkaPay External payment links Create business account. Generate payment link templates for each package. Optionally set webhook URL /webhooks/ ankapay. Hostinger Domain management Create admin account. Configure DNS templates (point to Fly.io IP). For automation, use Hostinger API (token) for verifying DNS. Fly.io Fullstack hosting Install Fly CLI. fly launch → deploy both frontend + backend. Use secrets: fly secrets set SUPABASE _ URL=.... Configure scaling for concurrent users. Realtime (optional) For instant updates Already part of Supabase. Enable Realtime replication on all tables with REPLICA IDENTITY FULL. Frontend technical details: A complete, developer-ready breakdown of **every frontend page + UI element + interactions + components + loading types + messages** for your Online Event Gallery app, based exactly on your spec. I included UX copy examples, where buttons go, what API calls they trigger, forms validations, and all important popups/toasts/edge states (approval flows, payment screenshot flow, domain requests, three app-holder logins, etc.). I organized it by pages and screens so you can hand this to a designer or developer and they’ll know exactly what to build. --- # Overview (shared UI primitives used everywhere) Common components and UI patterns used across pages: * **TopNav** (logo, search, user avatar + dropdown) — always present on logged-in areas * **LeftSidebar** (collapsible): dashboard nav: Overview / Galleries / Hosts / Requests / Payments / Settings / Notifications / Help * **PageHeader**: title + small action buttons (Create, Share, QR, Domain) * **Cards**: metric cards (views, uploads, occupancy) * **ListRows**: for lists of galleries/hosts/requests with actions (accept/decline) * **Modals**: centered, dimmed backdrop; used for confirmations, forms, uploads * **Toasts**: top-right small notifications for success/error/info (auto-dismiss 4s) * **Snackbars**: for persistent small messages (e.g., "Pending approval — check email") * **Skeleton loaders**: gray animated blocks for lists/cards, used while fetching lists or heavy pages (preferred for gallery lists & dashboards) * **Spinners/inline loaders**: small circular loader used for button-level async actions (saving, uploading) * **Form controls**: inputs, selects, multi-select, date+time pickers, toggle switches, hex color input + preset palette swatches * **Image modal / Lightbox**: full-screen viewer with caption, download, share, next/prev, pin options * **Confirm dialog**: “Are you sure ? ” with destructive action in red * **QR code card**: show QR + copy button + download PNG * **API error banner**: top of page when global API fails Auth & role logic: * Roles: `app _ holder` (3 accounts share same email but different username & password), `photographer`, `host`, `admin` (app-holder-level). JWT-based auth; role gating on UI components. * All links/actions that require approval show the **pending 24h** message flow until an admin approves; UI prevents login/creation until email confirmation & admin approval. --- # 1. App presentation website (public marketing) Purpose: explain the product, show features, call-to-action contact for collaboration. Pages / Sections: * **Home / Landing** * Hero: big image of event, app name, tagline, CTA buttons: **Try Demo**, **Contact for Collaboration** * Try Demo → opens modal with demo gallery (read-only) or route to signup (if user wants account) * Contact for Collaboration → opens contact form modal (Name, Email, Company, Message, Submit) * Feature blocks: cards with icons + short text (Publish galleries, domain, templates, host validation, payments) * Pricing section (not price CTA but contact): shows summarized Free / Standard / Premium tiers and a **Contact us** button under each that opens the collaboration/contact modal. * Testimonials / logos / footer * Footer: links to Terms, Privacy, Contact, Support email * Loading: skeleton placeholders while page CMS loads * **Contact / Collaboration Page** (or modal) * Form: Company name, Contact name, Email, Phone, Message, Preferred plan * Buttons: **Send Request** (POST `/contact`) → on success: toast “Thanks ! We’ll contact you within 24h.”, on error: toast error. * After submit: store lead in DB and send notification email to app-holders. * **Docs / How it Works** (optional) * Step-by-step illustrations of photographer → host → event page flow. * CTA: **Start Now** → signup pages. Navigation: * Public navbar: Home, Features, How it works, Contact, Login (opens role pick: Photographer / Host / App holder) --- # 2. Auth flows / Shared auth pages (used by all roles) General pages and messages: * **/signup** (photographer generic) * Fields: Name, Email, Password, Confirm password, Phone (WhatsApp), Social media URL * Photographer-specific copy: “Signup as a Photographer — you will receive an approval email within 24h.” * Buttons: * **Create Account** → client validate passwords, POST `/auth/signup` with role=photographer → response: * 200: show modal: “Account submitted — wait 24h for approval. Check your email.” (skeleton during submit; button disabled while request) * 400: show field errors inline (email invalid, weak password) * After sign-up: blocked from login until admin approves email + clicks confirmation link. If tries to login before approval: global error banner: “Wait for account validation.” * **/photographerdomainname.site/signup** (host sign-up via photographer domain) * Same form as above but role=host; on submit, photographer must validate host (photographer dashboard shows pending host requests). The signup page shows photographer’s theme color + logo if domain purchased. * After submit: show message “Request sent to Photographer for validation. Expect email after approval.” * **/login** * Fields: Email, Password, Role selector (photographer | host | app holder) * Special logic for App holder: they share same email among three accounts with **different usernames & passwords** — so they pick username from dropdown (or they use separate login boxes per app holder account). If the user enters shared email but wrong password, message: “Wrong credentials”. If not yet approved: “Wait for account validation.” * Buttons: * **Login** → POST `/auth/login`. On success: if first login after approval, show modal: “Consult your email for first login — confirm link” then redirect to appropriate dashboard. * **Forgot password** → opens modal or navigates `/forgot-password` * **/forgot-password** * Input email → POST `/auth/forgot` → success: “Check your email for reset link.” Error: “Email not found.” * **Email flows (server-generated)** * **Account created** (to photographer/host): “We received your request. Admins will review within 24h. You must confirm via email when approved.” * **Approval email**: “[Action approved] Click here to confirm and login.” Clicking confirmation allows login. * **Rejected email**: reason + link to contact support. UI patterns: * Use **skeleton loaders** for forms if any prefetch happens; otherwise no skeleton. * Buttons show inline spinner while request pending. --- # 3. App holders dashboard (account) — (3 accounts share single email) Role: Admin/Owner operations (manage photographers, approve domains/payment screenshots, view metrics, receive posting requests). Main route: `/app-holder/dashboard` Pages / sections: ## Dashboard / Overview * **Top metrics**: Total galleries, Pending requests, Total revenue via AnkaPay links, Active hosts, Total views * **Recent activity feed**: recent approvals, domain requests, payment screenshots submitted (list with thumbnails) * **Quick actions**: Create Photographer, Create Host, View Requests Loaders: skeleton cards for metrics; spinner for real-time metrics. ## Photographers management (list) * Table: Photographer name, email, status (pending/approved/blocked), number of galleries, actions * Actions per row: * **View** → open modal with photographer profile, galleries list, uploaded docs * **Approve** (if pending) → confirm modal: “Approve account ? ” (on confirm, POST `/admin/ approve-photographer`) → success toast + send approval email * **Delete** → confirm destructive modal: “Delete this photographer ? This deletes all their galleries.” (Sure/Cancel) * **Send message** → opens email compose modal → sends via Nodemailer ## Requests (posting / domain / personalized domain requests) * Card/list of requests with details: * Request type: Gallery posting request / Domain request / Personalized domain + payment screenshot * Fields shown: Photographer name, gallery id, amount to pay, gallery characteristics, payment screenshot (thumbnail), requested personalised domain * Actions: * **View payment screenshot** → opens modal with full image + metadata (timestamp, uploader) * **Approve posting** → POST `/admin/approve-posting` → action verifies screenshot ? (admin inspects) → on approval: create gallery live, email notifications to photographer & host * **Reject** → modal: reason input, send rejection email * **Purchase domain** (if admin needs to buy domain): **Mark Domain Purchased** → admin indicates domain is purchased/connected. On success: system generates the personalised domain link and QR. * **Generate QR** → creates QR for the personalised domain; shows QR modal with copy & download. ## Account Management & Security (three passwords) * UI to manage the three app-holder accounts (change password per username) * Buttons: * **Create new app-holder account** (rare) → opens form; creates new username linked to same email (backend handles conflict) * **Reset password** per user → sends reset email or allow inline change with current password * Access control: Provide a modal that explains roles and action limits for each password. ## Notifications & Email alerts * List of important alerts (from point 4: host nearing event end, gallery limits near capacity, pending approvals) * Settings to configure email templates / recipients * Buttons: * **Mark as read**, **Archive**, **Send manual reminder** ## Metrics & Reports * Charts: gallery views over time, active hosts, photographer signups * Loading: charts use skeletons then render * Export CSV button for reports (POST to server, then download) Components used: DataTables, ChartJS (or Recharts), Modal, Toasts, File preview. --- # 4. Photographer dashboard (account) Route: `/photographer/dashboard` — full tool for gallery creation, host validation, templates. ## Topbar & Overview * Metrics: total galleries, pending requests, accepted hosts, revenue pending * Buttons: **Create Gallery**, **Create Private Signup Page**, **Manage Hosts**, **Settings** ## Create Gallery (multi-step wizard) **Step 1 — Basic info** * Fields: Gallery name, Description, Event date & time (start/end), Template select (grid/list/ hero), Theme color (preset swatches + hex input), Allow Captions toggle (yes/no default true), Personalized domain request checkbox * Buttons: * **Next** (validate required fields) * **Save draft** (POST `/galleries` with status=draft) → toast “Draft saved” * Loading: button spinner, skeleton for template thumbnails **Step 2 — Galleries Characteristics & Payment** * Select Payment package from pre-defined packages (Free / Standard ( $ 35) / Premium ( $ 95)). Each shows features, space, activation timing. * Input for price override (if photographer allowed to override) * Upload payment screenshot directly to Publit.io using a signed upload URL. The backend only generates and verifies the signature. Once uploaded, the client sends file metadata (URL, type, timestamp) to the backend for approval. — optional until publish * Buttons: * **Save & Continue** * **Back** **Step 3 — Media & Template Customization** * Template preview picker (thumbnails); select template to preview the event landing page. * Upload hero images / custom logo (upload to Publit via signed upload)(Uploads are handled client-side directly to Publit.io using server-signed URLs , eliminating the intermediate backend relay.)— upload component shows progress bar and small spinner. * Arrange cover image order (drag & drop) * Buttons: * **Preview Landing Page** → open new tab or modal showing event page preview (non-live) * **Save** **Step 4 — Publish / Host selection** * List of Hosts associated with photographer (multi-select) * Option: Make gallery visible to hosts only for preview (toggle: “Only hosts can see preview”) * Final CTA buttons: * **Request posting (submit for approval)** → triggers server to create posting request for Admin and shows modal: “Request submitted. Please upload payment screenshot to complete the request.” After submit, toast and redirect to Gallery detail page. * **Publish now (if package paid & approved)** — only enabled if an approved payment screenshot is present and admin approved domain. Popups & messages: * On submit: modal: “Your request is sent — admins will approve within 24h. You will receive an email.” (OK button) * If trying to publish without payment screenshot: inline error: “Payment proof required.” ## Galleries list / Gallery detail * List of galleries (card per gallery) with status badge: Draft / Pending Approval / Live / Archived * Each card actions: * **Edit** → opens Create Gallery wizard with prefilled data * **View** → open live event page (if live) * **Preview** → open preview modal * **Delete** → confirm destructive modal * **Attach domain** → opens Domain Request modal (enter desired domain) → on submit creates domain request; admin will approve & buyer admins purchase domain (payment link for 5 $ and send screenshot before submitting) * Gallery Detail page: * Preview + Gallery settings sidebar (templates, colors, allowed hosts) * Posting requests & payment screenshots history * Link to manage Hosts for that gallery (add/remove) * Button: **Send official images to host** → choose images, choose hosts, set message, send privately (uploads stored as private until host approves to post) ## Create Private Signup Page (per photographer) * Form: page title, theme color, logo upload, URL slug (e.g., /photographername/signup) * Button: **Create Signup Page** → POST → success toast & shows page URL * The signup page will be themed and used by hosts to sign up and then photographer validates them. ## Host validation UI (pending hosts) * List of host requests with actions: * **Validate Host** → Accept (moves host to validated list & sends email) * **Reject Host** → provide reason (textarea), send email * **View host signup page** → shows filled details * On Validate: photographer can optionally auto-add host to certain galleries. ## Profile & Settings * Modify password, profile fields, contact info * Domain preferences & purchased domain linking (if photographer arranges domain) * Buttons: **Request Domain Purchase** → admin flow ## Notifications * In-app list + email settings: * Receive posting requests * Gallery limits reminder (e.g., 2 days before event) * Payment confirmations Loading behaviour: * Use skeletons for lists and a spinner for uploads. --- # 5. Host dashboard (account) Route: `/host/dashboard` — host manages sub-galleries, content moderation, accepts images. ## Overview * Shows assigned galleries (those the host has access to) * Quick actions: **View Event Page**, **Manage Sub-galleries**, **Screen QR Code**, **Share Event link** ## Gallery Detail (Host view) * Header: event title, active status, theme color set by photographer, link to event page, QR code card (copy link + download QR) * Occupancy & limits: occupancy percentage card, current uploads, max allowed (based on package) * Buttons: * **Activate/Deactivate captions** (toggle) * **Accept images without caption** (toggle) * **Enable live gallery** (toggle) * **Enable validate before post** (toggle: default off) * **Create sub-gallery** → opens modal with name, description, pin toggle * **Manage sub-galleries** → navigate to sub-galleries manager ## Sub-galleries manager * List of sub-galleries (default non-deletable: `pictures`, `videos`) with actions: * **Edit** → change name, description, pin * **Pin/Unpin** → limit of 3 pinned sub-galleries (if exceeding show error: “Max 3 pinned sub- galleries”) * **Activate ‘Official Content’** → toggle creates special sub-gallery and counts towards 3 pins * **Delete** → confirmation: “Delete sub-gallery and its contents ? ” Choose: Delete content too / Move content to main gallery (radio) * UI indicates which sub-galleries are compulsory and cannot be deleted. ## Incoming images from Photographer (private) * Dedicated inbox showing private image batches with thumbnails, timestamp, photographer note * Actions per image or batch: * **Approve Post** → choose destination sub-gallery (dropdown), optional caption input → click **Post** (if validate-before-post is enabled, post goes to pending queue) * **Reject** → modal with reason (optional) — sends message back to photographer * **Download original** → direct download (Publit link) * Bulk actions: Select multiple images → Approve to sub-gallery / Reject / Move / Copy * Posting workflow: * If **validate before post** ON: host approves -> images remain pending until host publishes (or until photographer/publish flow) * If OFF: images go live immediately in chosen sub-gallery ## Notifications & Reminders * Alerts: “2 days to event” / “Gallery limit near full” with action buttons: **Extend limit** (opens purchase flow) or **Contact Photographer** * Email integration: host gets email reminders automatically ## Profile & Settings * Edit profile, change password * When host pays for domain: update logo and theme color appear in host’s login & dashboard pages (immediately applied on UI) Loading: * Image lists use skeleton grid placeholders, then render thumbnails. --- # 6. Event page (gallery) — public-facing single-page event Route: `/g/:gallerySlug` or via personalised domain. General: single-page app that shows landing presentation + gallery + interactions. This is the core consumer experience. ## Top section: Landing / Hero * Photographer-chosen presentation: hero image/video, title, event date/time, description, CTA buttons: * **View Gallery** (scrolls to gallery section) * **Post Content** (shows upload instructions & if upload allowed) * If personalised domain exists, canonical meta tags / QR / link show personalised URL. ## Gallery & Sub-galleries * Sub-gallery tabs or horizontal chips (Pinned ones first). Default tabs: **Pictures**, **Videos**, plus others. Clicking tab filters images. * Grid of image thumbnails with lazy-loading (infinite scroll). Loading: skeleton cards for the grid, then thumbnail images load progressively. * Thumbnail actions (hover or tap): * **View** (opens Lightbox) * **Download** (single image) * **Add caption** (if caption allowed) — opens inline caption modal; POST `/images/:id/caption` * **Select** (checkbox) — for multi-select bulk download or create custom selection * Bulk download: * Select images → bottom sticky action bar shows selected count + **Download selected** (starts zip generation; show spinner + progress; uses Publit or server to produce link). * **Download whole gallery** → if allowed by package; if heavy, show confirm modal: “This may take some time — we’ll email you the download link.” ## Lightbox (image viewer) * Full-screen modal shows image/video, photographer caption (if any), host caption (if allowed), uploader name (if visible), date/time, download button, share buttons (copy link / share to social), next/prev arrows. * If captions are allowed: show “Add caption” button (if user is authenticated), otherwise show disabled tooltip: “Login as host to add caption.” * If image is private and host has not published it: show notice “Not yet published” and no download. ## Posting content (upload flow) * **Post Content** CTA: * For all users(event page is a public page): show Upload modal uses direct client-to-Publit.io upload (secured with backend-signed URLs). After upload, the client notifies backend with metadata: * Drag & drop or select files, add per-file caption (optional), choose sub-gallery (dropdown), checkbox “Send privately to host” (if true, image stored private until host posts) * Buttons: **Upload** (starts uploading to Publit; show per-file progress bars) * On complete: show toast “Upload complete — will appear once approved, Backend validates metadata and links the uploaded file to the corresponding gallery entry.” or if published instantly show “Now live” ## Download & Cloud options * **Select & Download**: selecting images shows actions: Download selected, Create share link (generate secure link), Add to favorites. * Download confirmation: modal "Your download is being prepared. We'll let you know when ready." If quick, initiate download; else send email with link. ## Interaction & Comments * Under images, comments/captions: users can add captions (if allowed). * Like / Reaction buttons on each image (optional). * Share links & social buttons for the event. ## Live gallery mode * If live mode enabled: page auto-updates via WebSocket / Supabase Realtime; new images appear at top and a small toast “New images added — click to show” appears; user can toggle auto-scroll vs manual. Edge states & messages: * If gallery expired (based on package): show banner “This gallery has expired; downloads are disabled.” with CTA **Renew** (opens payment link). * If gallery is private: show 403 message with contact info to request access. Loading patterns: * Use **skeleton** for initial grid; use lazy-loading for images with low-res placeholders then high-res via Publit CDN. Accessibility: * Keyboard navigation for gallery, alt text for images, and ARIA attributes for modals. --- # 7. Payment & Posting request flows Payment is external via AnkaPay link but integrated in flows. ## Flow: Photographer wants to post a paid gallery * Photographer selects package & submits posting request → system creates a Request record and Notifies Admins. * Photographer sees UI: **Upload payment screenshot** (file input) * After upload: show thumbnail + **Submit payment proof** button * On submit: POST `/requests/:id/payment-proof` → success toast: “Payment proof uploaded. Waiting admin confirmation (24h).” * Admins review screenshot in Requests page and either **Approve** or **Reject**. * On Admin Approve: send automated email to photographer & host: “Your posting approved. Gallery will be live.” * If Domain requested, once admin marks domain purchased & connected, the personalised domain and QR are generated and sent to photographer & host. ## Flow: Host buys personalised domain via admin * Host clicks **Request Domain** in host dashboard → fills desired domain & logo + color → admin receives domain request * Admin purchases domain on Hostinger (external), configures SSL & points to Vercel / Fly routing * Admin updates request to **Domain Connected** → system generates QR and personalised link → emails host. Copy examples for modals & toasts: * Success toast: “Payment proof received. Admin will review within 24 hours.” * Error toast: “Payment proof invalid. Please re-upload a clear screenshot.” * Approval email: “Your gallery posting has been approved. Click here to confirm and publish.” --- # 8. Profile, security, & account deletion Available across Photographer & Host dashboards. ## Profile page * Fields: Name, Email (non-editable ? ), Phone, Social link, Logo (if host paid domain), Theme color (host can change if domain purchased) * Buttons: * **Save profile** (spinner while saving) * **Change password** → modal: current password, new password, confirm * **Delete account** → destructive confirm modal: “Delete account ? This will remove all your galleries and cannot be undone.” Options: Cancel / Delete (type DELETE `/users/:id`) Messages: * On delete: require typing `DELETE` or the account email to confirm. * On password change success: toast “Password updated.” --- # 9. Notifications center & email templates In-app notifications area and email flows. Notifications types: * Account approval pending / approved / rejected * Payment screenshot reviewed * Gallery nearing end date (e.g., 2 days left) * Gallery capacity near full * Host assignment requests * Domain connected UI: * Bell icon in header with badge count * Notifications list: each has message, small icon, timestamp, actions (View, Mark read, Archive) * Email templates stored in admin settings (subject + body preview); admin can update wording. --- # 10. Admin approval states & messages shown across UI Uniform pattern for any create action that needs admin approval: When user performs action requiring approval (signup, gallery post, domain request): * Immediately show modal: ``` "Request received" "Your request is under review. It usually takes up to 24 hours. Check your email for updates." [OK] ``` * In dashboards: show **status pill** (Pending approval) with **Cancel** or **Upload missing docs** options. * If user tries restricted action before approval (e.g., login or publish): show inline error: * Not approved: “Wait for account validation.” * Approved but no email confirmation clicked: “Consult your email for first login.” --- # 11. Edge cases, errors & fallback UX * **API network failure**: global banner “Unable to reach server. Some features may be unavailable. Retry.” * **Session expired**: toast “Session expired — please login again.” Redirect to login after 3s. * **Payment failures**: display error from AnkaPay and show alternative contact. * **Domain misconfiguration**: in Admin/Host, show banner with DNS check results and instructions to fix. * **Storage or bandwidth limit reached**: show banner: “You’ve reached your storage limit — upgrade to continue uploading.” Link to pricing/contact. --- # 12. Component-to-API mapping (high level) Examples of what buttons call (replace with actual endpoints later): * `POST /auth/signup` (role: photographer/host) * `POST /auth/login` → returns JWT * `POST /galleries` → create gallery (draft) * `PUT /galleries/:id` → edit gallery * `POST /galleries/:id/publish-request` → create posting request * `POST /requests/:id/payment-proof` → attach screenshot * `POST /admin/approve-posting` → admin approves screenshot and triggers gallery live * `GET /galleries/:slug` → fetch public event data * GET /publit/sign-url → backend returns a signed upload URL; frontend uploads directly to Publit.io; then POST /media/metadata to backend with file details (name, size, URL, galleryId, uploaderRole). * `GET /hosts` / `POST /hosts` / `DELETE /hosts/:id` * `POST /images/:id/caption` → add host caption * `POST /download/prepare` → create a zip for selected images (returns job id) → `GET / download/:jobid/status` * `GET /notifications` / `POST /notifications/mark-read` * `POST /domains/request` → domain request by host/photographer * `POST /admin/mark-domain-connected` → generate QR & personalised link --- # 13. Loading and animation rules (consistent)