Passa al contenuto principale

Architettura

Il portale è un'applicazione statica per browser con un confine ristretto verso una Edge Function Supabase per l'attivazione del collegamento identità con ruolo di servizio. Le operazioni CRUD rivolte al contributor passano comunque attraverso il client JavaScript di Supabase, sotto policy di row-level security.

React UI
-> usePortalFlow
-> portalApi
-> Supabase Auth
-> Supabase Postgres with RLS
-> activate-contributor-app-link Edge Function

Stack

React 19
TypeScript 6
Vite 8
Supabase JS 2
lucide-react
Cloudflare Pages

Comandi di build e deploy:

npm run build
npm run deploy:pages

Entry Point

src/App.tsx è volutamente snello. Legge lo stato del portale da usePortalFlow e renderizza il workspace corretto.

src/features/application/PublicApplicationPage.tsx
src/features/onboarding/OnboardingWorkspace.tsx
src/features/rewards/RewardsWorkspace.tsx
src/features/status/StatusView.tsx
src/features/system/EmailSentView.tsx
src/features/system/LoadingView.tsx

src/features/portal/usePortalFlow.ts gestisce:

  • il parsing dei parametri URL per invite, ref, codici di callback, hash di token, frammenti legacy, flag di conferma payout, link_intent e intent_id
  • il bootstrap della sessione Supabase e la sottoscrizione allo stato di auth
  • il caricamento dell'ultima application, dell'onboarding e del receiving-method
  • la selezione della modalità
  • l'invio della public application
  • il salvataggio e l'invio della bozza di onboarding
  • il routing per la revisione in sola lettura dei documenti
  • il salvataggio del receiving-method di payout e la conferma email
  • la sincronizzazione dell'identity-link dell'app dopo l'autenticazione sul portale

src/lib/portalApi.ts è il confine di persistenza lato browser. Usa esclusivamente il client Supabase pubblicabile e non deve mai importare chiavi server-only.

Autenticazione

Il client Supabase è configurato in src/lib/supabase.ts:

flowType = pkce
detectSessionInUrl = false
persistSession = true
autoRefreshToken = true

L'app gestisce manualmente:

  • callback con token_hash
  • callback con code PKCE
  • frammenti access_token impliciti legacy
  • errori di callback di Supabase

Dopo un callback riuscito, i parametri di auth vengono rimossi dall'URL e nella query string viene scritto verified=1.

Parametri URL

Parametri di ingresso e di callback del contributor:

invite=CODE
ref=CODE
link_intent=<signed-app-intent>
intent_id=<legacy-compatible signed-app-intent>
code=<pkce-code>
token_hash=<otp-token>
type=signup|magiclink|email
payout_confirm_token=<64-char hex single-use token>

payout_confirm_token è un token monouso vincolato a una specifica riga di payout_receiving_methods (auth_user_id + status='pending_email_confirmation'). Il portale lo consuma attraverso la RPC SECURITY DEFINER confirm_payout_receiving_method_with_token. Il flag nudo payout_confirm=1 non è più accettato (audit 2026-05-16, EC-231 M1).

link_intent viene creato dalla Edge Function dell'app Playroll e consumato dal Contributor Portal. intent_id è accettato come alias opzionale di compatibilità. Il portale rimuove il link intent dall'URL del browser solo dopo che la verifica è andata a buon fine.

Cataloghi Locali

Il portale usa file dati locali per la normalizzazione italiana:

public/data/italian-comuni.json
public/data/italian-universities.json
src/lib/italianCities.ts
src/lib/italianUniversities.ts

La normalizzazione della città è obbligatoria. La normalizzazione dell'università è best-effort, perché i contributor possono indicare un gruppo o una community invece di un'istituzione formale.

Documenti Generati

src/lib/generatedDocuments.ts gestisce i documenti testuali e la generazione dell'evidenza.

Costanti importanti:

DOCUMENT_PACKAGE_VERSION = playroll-pre-recorder-v1
DOCUMENT_VERSIONS.incarico = playroll-lettera-incarico-v1.1
DOCUMENT_VERSIONS.privacy = playroll-privacy-ack-v1
DOCUMENT_VERSIONS.tax = playroll-dichiarazione-fiscale-v1.1
DOCUMENT_VERSIONS.compensation = playroll-data-minimization-ack-v1

L'evidenza generata usa crypto.subtle.digest del browser per calcolare l'hash del testo esatto mostrato al contributor.

Forma del Deployment

L'app è un'applicazione statica Vite distribuita su Cloudflare Pages:

Project name: playroll-contributor-portal
Production branch: main
Build command: npm run build
Build output directory: dist
Production host: https://playroll.extrinsiccognition.com

Variabili d'ambiente del browser richieste:

VITE_SUPABASE_URL
VITE_SUPABASE_PUBLISHABLE_KEY

Valori server-only come SUPABASE_SECRET_KEY, SUPABASE_SERVICE_ROLE_KEY, CONTRIBUTOR_LINK_INTENT_SECRET e PLAYROLL_APP_SUPABASE_SERVICE_ROLE_KEY devono rimanere in ambienti locali/server ignorati e non devono essere esposti tramite variabili VITE_.

Confine delle Edge Function

Il portale possiede oggi una sola Edge Function con ruolo di servizio:

supabase/functions/activate-contributor-app-link/index.ts

Può:

  • verificare un app link intent firmato
  • controllare il gate di attivazione del portale
  • creare o promuovere righe di contributor_app_links
  • chiamare ensure_current_contributor()
  • fare upsert su playroll_supa_new.public.contributor_status_projection

Non deve:

  • eseguire pagamenti
  • aggirare i gate obbligatori sui documenti o sul receiving-method
  • esporre chiavi service-role al codice del browser
  • copiare PII legali o di pagamento nel progetto Supabase dell'app Playroll
  • diventare un backend admin generalista

Tutto ciò che richiede storage immutabile, chiamate verso provider di pagamento, firma di audit o revisione admin appartiene comunque a percorsi esterni al browser pubblico.