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_intenteintent_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
codePKCE - frammenti
access_tokenimpliciti 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.