From 215af4bbc42e193c0693db163e0222c0b208d73a Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 1 Sep 2026 10:46:38 +0200 Subject: [PATCH] DD-018: collegamento automatico giocatore-account per email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Al primo accesso l'app collega da sola l'account Google al giocatore la cui email registrata coincide (case-insensitive), invece di far scegliere il nome da un elenco. Senza corrispondenza compare solo un messaggio d'errore con un pulsante per uscire e riprovare con un altro account: nessuna scelta manuale di ripiego. - Migration m5: colonna `email` su giocatori_squadra, seed per Ivan Cacciari e Davide Grilli, trigger esteso per richiedere anche il match email oltre allo slot libero. - slotPerEmail() sostituisce slotLiberi() (rimossa, senza più chiamanti in produzione). Co-Authored-By: Claude Mythos --- PROJECT_STATE.md | 12 ++- docs/CHANGELOG.md | 5 + docs/DATABASE.md | 2 +- docs/DESIGN_DECISIONS.md | 27 +++++ docs/modules/profilo-giocatore.md | 5 +- src/lib/auth.ts | 7 +- src/lib/giocatori-squadra.ts | 16 ++- src/routes/benvenuto.tsx | 102 ++++++++---------- ...60901120000_m5_email_giocatori_squadra.sql | 62 +++++++++++ test/unit/giocatori-squadra.test.ts | 29 +++-- test/unit/profili-core.test.ts | 13 +-- 11 files changed, 190 insertions(+), 90 deletions(-) create mode 100644 supabase/migrations/20260901120000_m5_email_giocatori_squadra.sql diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 2344f21..9252334 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -36,7 +36,9 @@ admin) implementati su `develop`, da attivare in produzione seguendo i passaggi ## Database - Schema v1.0 + M1 applicati al nuovo Supabase -- `public.giocatori_squadra`: 17 giocatori iniziali presenti +- `public.giocatori_squadra`: 17 giocatori iniziali presenti; solo 2 hanno l'email + registrata (`email`, migration `m5_email_giocatori_squadra`, DD-018) — le altre 15 + arriveranno con una migration futura --- @@ -86,8 +88,12 @@ Passaggi in ordine, nessuno dei quali è reversibile a metà: produzione senza toccare il comportamento attuale. 3. **Primo admin**, dopo il primo login (l'ID esiste solo da quel momento): `INSERT INTO public.user_roles (user_id, role) SELECT id, 'admin' FROM auth.users WHERE email = '';` -4. **Collegamento dei 17 account**: ciascuno accede con Google e sceglie il proprio nome una - volta sola. Uno slot già collegato può essere liberato solo da un admin. +4. **Collegamento dei 17 account**: ciascuno accede con Google e viene collegato in + automatico al proprio giocatore per email (DD-018, migration + `m5_email_giocatori_squadra`) — nessuna scelta manuale. Finché l'email di un giocatore + non è impostata (oggi solo 2 dei 17 la hanno), il suo accesso mostra un errore e va + sbloccato aggiungendo l'email con una nuova migration. Uno slot già collegato può essere + liberato solo da un admin. 5. **Solo a squadra collegata**: migration `m4_solo_autenticati`, che toglie al ruolo `anon` l'accesso alle tabelle v1.0. Da lì in poi i dati sono raggiungibili solo con una sessione; le route in `src/routes/api/public/` usano la service role e continuano a funzionare. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f38f9d9..fe72568 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -18,6 +18,11 @@ qui: sta in [ROADMAP.md](ROADMAP.md). di nomi in `crapp-data.ts` è stata eliminata, altrimenti bastava scegliere il nome giusto per amministrare. - Migration `m4_solo_autenticati`: toglie al ruolo `anon` l'accesso alle tabelle v1.0. +- Collegamento automatico al proprio giocatore per email (DD-018, migration + `m5_email_giocatori_squadra`): niente più scelta manuale da un elenco, `/benvenuto` + confronta l'email dell'account Google con `giocatori_squadra.email` e collega da solo. + Senza corrispondenza compare solo un messaggio d'errore, con un pulsante per uscire e + riprovare con un altro account. **Da applicare solo a squadra collegata**, altrimenti chi non ha ancora fatto login vede l'app vuota. - Profilo giocatore: da `/profilo` ognuno compila i propri dati anagrafici e carica diff --git a/docs/DATABASE.md b/docs/DATABASE.md index 849b099..228054c 100644 --- a/docs/DATABASE.md +++ b/docs/DATABASE.md @@ -9,7 +9,7 @@ non in questo file. | Tabella | Scopo | Note | |---|---|---| -| `giocatori_squadra` | Anagrafica operativa della squadra, con ID testuali (`g1`…`gN`), dati gestiti dagli admin (nome, cognome, numero, ruolo) e collegamento all'account (`auth_user_id`). | Introdotta dalla migration `m1_giocatori_squadra`, già popolata (17 giocatori) ma **non ancora letta dal codice**: la rosa arriva tuttora da `src/lib/crapp-data.ts`, che resta il fallback anche dopo il passaggio. Destinata a diventare la source of truth. Vedi DD-015 e DD-016. | +| `giocatori_squadra` | Anagrafica operativa della squadra, con ID testuali (`g1`…`gN`), dati gestiti dagli admin (nome, cognome, numero, ruolo), collegamento all'account (`auth_user_id`) ed email registrata (`email`). | Introdotta dalla migration `m1_giocatori_squadra`, già popolata (17 giocatori) ma **non ancora letta dal codice**: la rosa arriva tuttora da `src/lib/crapp-data.ts`, che resta il fallback anche dopo il passaggio. Destinata a diventare la source of truth. Vedi DD-015 e DD-016. La colonna `email` (migration `m5_email_giocatori_squadra`) è la chiave del collegamento automatico account↔giocatore al primo accesso (DD-018): NULL finché non nota, oggi impostata solo per 2 dei 17 giocatori. | | `giocatori` | Anagrafica giocatori con UUID. | Presente ma **non usata** dal codice attuale: la convergenza è rinviata (DD-012, DD-014). | | `profili_giocatore` | Dati personali, metadati del documento d'identità, certificato medico e path dei file, in relazione 1:1 con `giocatori_squadra`. | Creata dalla migration `m2_profili_giocatore` (DD-016). Letta da `src/lib/profili.ts`; le policy mostrano al giocatore solo il proprio profilo e all'admin tutti. I file non stanno qui: la tabella conserva i path nel bucket. | | `user_roles` | Ruoli applicativi (es. amministratore, giocatore). | Fonte dei permessi di amministrazione, letta da `src/lib/ruoli.ts` (DD-011). Il primo admin va inserito a mano; vedi [PROJECT_STATE.md](../PROJECT_STATE.md). | diff --git a/docs/DESIGN_DECISIONS.md b/docs/DESIGN_DECISIONS.md index ce954cf..bcb2783 100644 --- a/docs/DESIGN_DECISIONS.md +++ b/docs/DESIGN_DECISIONS.md @@ -33,6 +33,7 @@ Serve a rispondere a domande del tipo: | [DD-013](#dd-013--portabilità-lapp-non-deve-dipendere-da-servizi-esclusivi) | Portabilità dello stack | | [DD-016](#dd-016--schema-dati-profilo-giocatore-v11-f0) | Schema dati Profilo Giocatore v1.1 | | [DD-017](#dd-017--lamministratore-può-compilare-i-dati-al-posto-del-giocatore) | L'admin scrive al posto del giocatore | +| [DD-018](#dd-018--collegamento-automatico-giocatoreaccount-per-email) | Collegamento automatico per email | **In valutazione** @@ -474,6 +475,32 @@ Restano fuori, e non cambiano: --- +### DD-018 — Collegamento automatico giocatore↔account per email + +**Data:** settembre 2026 +**Stato:** Accettata + +**Contesto** +DD-016 regola 2 prevedeva che, al primo accesso, il giocatore scegliesse manualmente il proprio slot libero da un elenco (`/benvenuto`). In pratica ogni giocatore ha un'email nota (o presto nota), quindi far scegliere un nome da una lista è un passaggio superfluo e un rischio: un giocatore può selezionare per errore lo slot di un compagno, e nulla nel flusso lo impedisce a livello di prodotto. + +**Decisione** +Al primo accesso, `giocatori_squadra` viene interrogata per email (case-insensitive, tramite la nuova colonna `email`) invece di mostrare un elenco di slot liberi. Se l'email dell'account Google corrisponde a una riga libera, il collegamento avviene automaticamente. Se non corrisponde a nessuna riga (email non ancora nota, o nessun profilo per quella persona), l'utente vede solo un messaggio d'errore che invita a contattare un amministratore, con un pulsante per uscire e riprovare con un altro account — nessuna selezione manuale di ripiego. Le email sono popolate via migration (`m5_email_giocatori_squadra`), non tramite un'interfaccia amministrativa in questa iterazione. Il trigger `enforce_giocatori_squadra_update` (DD-016) viene esteso per richiedere anche la corrispondenza email, non solo lo slot libero: il vincolo resta nel database, non solo nella UI. + +**Alternative scartate** +- Mantenere la selezione manuale come ripiego quando l'email non trova corrispondenza → scartata: vanificherebbe la garanzia "ognuno collega solo il proprio profilo" e reintrodurrebbe il rischio di scelta errata che questa decisione vuole eliminare. +- Un'interfaccia admin per scrivere l'email dei giocatori → rimandata: non necessaria finché le email arrivano da migration; si può aggiungere in futuro senza toccare questa decisione. + +**Conseguenze** +- Le righe senza email nota restano bloccate — nessuno può collegarle, nemmeno per errore — finché una migration futura non la imposta. Oggi solo 2 dei 17 giocatori hanno l'email registrata. +- `slotLiberi` (funzione ed elenco "slot liberi" in `/benvenuto`) è stato rimosso: non aveva più chiamanti in produzione dopo il cambio. +- Un utente che accede con l'account Google sbagliato resta bloccato su `/benvenuto` finché non esce e riprova con l'account giusto. + +**Riesame** +- Quando tutte le 17 email saranno note e verificate. +- Se in futuro serve un'assistenza admin diretta dal flusso di login invece che da `/admin`. + +--- + ## Decisioni in valutazione --- diff --git a/docs/modules/profilo-giocatore.md b/docs/modules/profilo-giocatore.md index 43fab85..9782dce 100644 --- a/docs/modules/profilo-giocatore.md +++ b/docs/modules/profilo-giocatore.md @@ -40,7 +40,10 @@ responsabilità del giocatore che li fornisce. 1. Login tramite Google oppure Email. *Implementato con il solo Google: la squadra ha tutti un account Google, e un secondo metodo è additivo (un bottone in più sulla stessa schermata) il giorno che serve.* -2. Selezione del proprio giocatore. +2. Collegamento automatico al proprio giocatore, confrontando l'email dell'account Google + con l'email registrata in `giocatori_squadra` (DD-018). Nessuna scelta manuale: se + l'email non corrisponde a nessun profilo, l'accesso si ferma con un messaggio che invita + a contattare un amministratore. 3. Accesso alla Home. Se il profilo non è completo compare automaticamente un widget di completamento. diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 9c90a07..4eb14f1 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -39,7 +39,12 @@ export function useSessione() { } }, []); - return { sessione, pronta, utenteId: sessione?.user.id ?? null }; + return { + sessione, + pronta, + utenteId: sessione?.user.id ?? null, + emailUtente: sessione?.user.email ?? null, + }; } export async function accediConGoogle(): Promise { diff --git a/src/lib/giocatori-squadra.ts b/src/lib/giocatori-squadra.ts index 21d53f2..101d24a 100644 --- a/src/lib/giocatori-squadra.ts +++ b/src/lib/giocatori-squadra.ts @@ -15,6 +15,7 @@ export type GiocatoreSquadra = { ruolo: string; authUserId: string | null; attivo: boolean; + email: string | null; }; type RigaGiocatoreSquadra = { @@ -25,6 +26,7 @@ type RigaGiocatoreSquadra = { ruolo: string; auth_user_id: string | null; attivo: boolean; + email: string | null; }; export const SQUADRA_KEY = ["giocatori-squadra"] as const; @@ -45,6 +47,7 @@ export function rosaFallback(): GiocatoreSquadra[] { ruolo: g.ruolo, authUserId: null, attivo: true, + email: null, })); } @@ -61,14 +64,20 @@ export function slotDi( return righe.find((g) => g.authUserId === utenteId) ?? null; } -export function slotLiberi(righe: GiocatoreSquadra[]): GiocatoreSquadra[] { - return righe.filter((g) => g.attivo && !g.authUserId); +/** Lo slot libero la cui email coincide con quella dell'account Google (case-insensitive). */ +export function slotPerEmail( + righe: GiocatoreSquadra[], + email: string | null, +): GiocatoreSquadra | null { + if (!email) return null; + const cercata = email.trim().toLowerCase(); + return righe.find((g) => !g.authUserId && g.email?.trim().toLowerCase() === cercata) ?? null; } async function fetchSquadra(): Promise { const { data, error } = await supabaseNuoveTabelle .from("giocatori_squadra") - .select("id, nome, cognome, numero, ruolo, auth_user_id, attivo") + .select("id, nome, cognome, numero, ruolo, auth_user_id, attivo, email") .order("id"); if (error) throw error; const righe = (data ?? []) as RigaGiocatoreSquadra[]; @@ -80,6 +89,7 @@ async function fetchSquadra(): Promise { ruolo: r.ruolo, authUserId: r.auth_user_id, attivo: r.attivo, + email: r.email, })); } diff --git a/src/routes/benvenuto.tsx b/src/routes/benvenuto.tsx index aaab088..c52bdb6 100644 --- a/src/routes/benvenuto.tsx +++ b/src/routes/benvenuto.tsx @@ -1,16 +1,14 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useEffect, useState } from "react"; -import { LogIn } from "lucide-react"; +import { LogIn, LogOut } from "lucide-react"; import { toast } from "sonner"; import { TeamLogo } from "@/components/crapp/ui-bits"; -import { accediConGoogle, useSessione } from "@/lib/auth"; +import { accediConGoogle, esci, useSessione } from "@/lib/auth"; import { - nomeCompleto, slotDi, - slotLiberi, + slotPerEmail, useCollegaGiocatore, useGiocatoriSquadra, - type GiocatoreSquadra, } from "@/lib/giocatori-squadra"; import { impostaGiocatore, resetGiocatore, useGiocatoreCorrente } from "@/lib/user-store"; @@ -32,43 +30,17 @@ export const Route = createFileRoute("/benvenuto")({ component: Benvenuto, }); -function Scheda({ - titolo, - sottotitolo, - onClick, - iniziali, -}: { - titolo: string; - sottotitolo: string; - onClick: () => void; - iniziali: string; -}) { - return ( - - ); -} - function Benvenuto() { const navigate = useNavigate(); const giocatore = useGiocatoreCorrente(); - const { pronta, utenteId } = useSessione(); + const { pronta, utenteId, emailUtente } = useSessione(); const { righe, daDatabase } = useGiocatoriSquadra(); const collega = useCollegaGiocatore(); const [inCorso, setInCorso] = useState(false); + const [tentato, setTentato] = useState(false); const mioSlot = slotDi(righe, utenteId); + const slotEmail = slotPerEmail(righe, emailUtente); // Si entra solo da loggati e con uno slot collegato (DD-011). const puoEntrare = !!giocatore && !!utenteId; @@ -83,6 +55,18 @@ function Benvenuto() { else if (utenteId && daDatabase) resetGiocatore(); }, [mioSlot, utenteId, daDatabase]); + // Collegamento automatico per email (DD-018): un solo tentativo, mai su dati di + // fallback. Se fallisce o non trova corrispondenza resta lo stato d'errore, senza + // scelta manuale di ripiego. + useEffect(() => { + if (!utenteId || !daDatabase || mioSlot || !slotEmail || tentato) return; + setTentato(true); + collega + .mutateAsync({ giocatoreId: slotEmail.id, utenteId }) + .then(() => impostaGiocatore(slotEmail.id)) + .catch(() => toast.error("Collegamento non riuscito. Riprova o contatta un amministratore.")); + }, [utenteId, daDatabase, mioSlot, slotEmail, tentato, collega]); + async function accedi() { setInCorso(true); try { @@ -93,17 +77,19 @@ function Benvenuto() { } } - async function reclama(g: GiocatoreSquadra) { - if (!utenteId) return; + async function esciERiprova() { try { - await collega.mutateAsync({ giocatoreId: g.id, utenteId }); - impostaGiocatore(g.id); - } catch { - toast.error("Profilo già collegato a un altro account. Chiedi a un amministratore."); + await esci(); + } catch (error) { + toast.error(error instanceof Error ? error.message : "Uscita non riuscita"); } } - const liberi = slotLiberi(righe); + // Errore: email senza corrispondenza, oppure trovata ma il tentativo di collegamento + // è fallito (slot nel frattempo reclamato da altri, o errore di rete). + const erroreCollegamento = + !!utenteId && daDatabase && !mioSlot && (!slotEmail || collega.isError); + const inAttesaCollegamento = !!utenteId && !mioSlot && !erroreCollegamento; return (
@@ -126,29 +112,25 @@ function Benvenuto() { Accedi con Google - ) : ( + ) : erroreCollegamento ? ( <>

- Sei entrato. Scegli il tuo nome: resterà collegato a questo account. + Nessun profilo trovato per la tua email. Contatta un amministratore per collegare il tuo + account.

-
- {liberi.map((g) => ( - void reclama(g)} - /> - ))} - {liberi.length === 0 ? ( -

- Nessun profilo libero: chiedi a un amministratore di collegarti. -

- ) : null} -
+ - )} + ) : inAttesaCollegamento ? ( +

+ Ti stiamo collegando al tuo profilo... +

+ ) : null}
); } diff --git a/supabase/migrations/20260901120000_m5_email_giocatori_squadra.sql b/supabase/migrations/20260901120000_m5_email_giocatori_squadra.sql new file mode 100644 index 0000000..ab90ac9 --- /dev/null +++ b/supabase/migrations/20260901120000_m5_email_giocatori_squadra.sql @@ -0,0 +1,62 @@ +-- M5 — Collegamento automatico per email al primo accesso (DD-018) +-- +-- Sostituisce la scelta manuale del proprio slot in `/benvenuto` con un confronto tra +-- l'email dell'account Google e l'email registrata per ciascun giocatore. Additiva: +-- non tocca `profili_giocatore` né altre tabelle v1.0. + +ALTER TABLE public.giocatori_squadra + ADD COLUMN email text; + +COMMENT ON COLUMN public.giocatori_squadra.email IS + 'Email nota in anticipo per il giocatore (non l''email personale in profili_giocatore): ' + 'chiave del collegamento automatico account <-> giocatore al primo accesso (DD-018). ' + 'NULL finché non nota: la riga resta non collegabile da nessuno.'; + +-- Case-insensitive, tollerante a più righe con email NULL (non ancora note). +CREATE UNIQUE INDEX giocatori_squadra_email_unica + ON public.giocatori_squadra (lower(email)) + WHERE email IS NOT NULL; + +-- Il trigger di DD-016 (m1_giocatori_squadra) blocca qualunque UPDATE che non arrivi da un +-- admin autenticato o dal giocatore stesso: durante la migration non c'è sessione, quindi +-- va disattivato solo per il seed e riattivato subito dopo. +ALTER TABLE public.giocatori_squadra DISABLE TRIGGER enforce_giocatori_squadra_update; + +-- Solo le due email confermate finora; le altre 15 arriveranno con una migration futura. +UPDATE public.giocatori_squadra SET email = 'ivan.cacciari.1995@gmail.com' WHERE id = 'g4'; +UPDATE public.giocatori_squadra SET email = 'davide3011@gmail.com' WHERE id = 'g10'; + +ALTER TABLE public.giocatori_squadra ENABLE TRIGGER enforce_giocatori_squadra_update; + +-- Estende il trigger di DD-016: oltre a "slot libero, nessun altro campo cambia", un +-- non-admin può collegarsi solo se l'email del token coincide con l'email della riga +-- (case-insensitive). Righe senza email restano bloccate per chiunque. +CREATE OR REPLACE FUNCTION public.enforce_giocatori_squadra_update() +RETURNS TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +BEGIN + IF public.has_role(auth.uid(), 'admin'::public.app_role) THEN + RETURN NEW; + END IF; + + IF OLD.auth_user_id IS NULL + AND NEW.auth_user_id = auth.uid() + AND OLD.email IS NOT NULL + AND lower(OLD.email) = lower(coalesce(auth.jwt() ->> 'email', '')) + AND NEW.id IS NOT DISTINCT FROM OLD.id + AND NEW.nome IS NOT DISTINCT FROM OLD.nome + AND NEW.cognome IS NOT DISTINCT FROM OLD.cognome + AND NEW.numero IS NOT DISTINCT FROM OLD.numero + AND NEW.ruolo IS NOT DISTINCT FROM OLD.ruolo + AND NEW.attivo IS NOT DISTINCT FROM OLD.attivo + AND NEW.email IS NOT DISTINCT FROM OLD.email + AND NEW.creato_il IS NOT DISTINCT FROM OLD.creato_il THEN + RETURN NEW; + END IF; + + RAISE EXCEPTION 'Aggiornamento non autorizzato su giocatori_squadra'; +END; +$$; diff --git a/test/unit/giocatori-squadra.test.ts b/test/unit/giocatori-squadra.test.ts index cdf85a4..f1ef2c3 100644 --- a/test/unit/giocatori-squadra.test.ts +++ b/test/unit/giocatori-squadra.test.ts @@ -6,7 +6,7 @@ import { numeroGiaUsato, rosaFallback, slotDi, - slotLiberi, + slotPerEmail, validaDatiSquadra, type GiocatoreSquadra, } from "@/lib/giocatori-squadra"; @@ -20,6 +20,7 @@ const riga = (parziale: Partial = {}): GiocatoreSquadra => ({ ruolo: "schiacciatore", authUserId: null, attivo: true, + email: null, ...parziale, }); @@ -62,17 +63,25 @@ assert.equal(slotDi(righe, "u1")?.id, "g1"); assert.equal(slotDi(righe, "u9"), null, "nessuno slot per un account non collegato"); assert.equal(slotDi(righe, null), null, "senza sessione non c'è slot"); -// --- slotLiberi ---------------------------------------------------------------- -const rosaMista: GiocatoreSquadra[] = [ - riga({ id: "g1", attivo: true, authUserId: null }), - riga({ id: "g2", attivo: true, authUserId: "u1" }), - riga({ id: "g3", attivo: false, authUserId: null }), +// --- slotPerEmail ---------------------------------------------------------------- +const rosaEmail: GiocatoreSquadra[] = [ + riga({ id: "g1", email: "foo@bar.com", authUserId: null }), + riga({ id: "g2", email: "baz@qux.com", authUserId: "u1" }), + riga({ id: "g3", email: null, authUserId: null }), ]; -assert.deepEqual( - slotLiberi(rosaMista).map((g) => g.id), - ["g1"], - "libero solo chi è attivo e senza account collegato", +assert.equal(slotPerEmail(rosaEmail, "foo@bar.com")?.id, "g1", "match esatto"); +assert.equal( + slotPerEmail(rosaEmail, "Foo@Bar.com")?.id, + "g1", + "il confronto ignora maiuscole/minuscole", ); +assert.equal(slotPerEmail(rosaEmail, null), null, "senza email non c'è match"); +assert.equal( + slotPerEmail(rosaEmail, "baz@qux.com"), + null, + "uno slot già collegato non risulta in match anche con email coincidente", +); +assert.equal(slotPerEmail(rosaEmail, ""), null, "stringa vuota trattata come nessuna email"); // --- validaDatiSquadra ---------------------------------------------------------------- const datiOk = { nome: "Mario", cognome: "Rossi", numero: 7, ruolo: "schiacciatore" }; diff --git a/test/unit/profili-core.test.ts b/test/unit/profili-core.test.ts index d18f209..2aec358 100644 --- a/test/unit/profili-core.test.ts +++ b/test/unit/profili-core.test.ts @@ -14,7 +14,6 @@ import { numeroGiaUsato, rosaFallback, slotDi, - slotLiberi, validaDatiSquadra, type GiocatoreSquadra, } from "@/lib/giocatori-squadra"; @@ -112,6 +111,7 @@ const squadra: GiocatoreSquadra[] = [ ruolo: "Banda", authUserId: null, attivo: true, + email: null, }, { id: "g2", @@ -121,6 +121,7 @@ const squadra: GiocatoreSquadra[] = [ ruolo: "Libero", authUserId: "u2", attivo: true, + email: null, }, ]; const csv = csvTesseramento(squadra, { g1: completo }); @@ -146,16 +147,6 @@ assert.deepEqual(dividiNome("Ivan"), { nome: "Ivan", cognome: "" }); assert.equal(slotDi(squadra, null), null, "senza sessione nessuno slot"); assert.equal(slotDi(squadra, "u2")?.id, "g2"); assert.equal(slotDi(squadra, "sconosciuto"), null); -assert.deepEqual( - slotLiberi(squadra).map((g) => g.id), - ["g1"], - "uno slot già collegato non è più libero", -); -assert.deepEqual( - slotLiberi([...squadra, { ...squadra[0]!, id: "g3", attivo: false }]).map((g) => g.id), - ["g1"], - "i giocatori non attivi restano fuori", -); // --- dati squadra modificabili dall'admin (DD-017) --------------------------- const datiOk = { nome: "Ivan", cognome: "Cacciari", numero: 23, ruolo: "Banda" };