Update Todos
Flytte is_participant-logikk til team_authz.py (unngå sirkulær import) Widen scoring.py: get_scorecard/submit_hole_score/submit_hole_result/concede_match Widen matches.py: list_matches/add_participant/remove_participant/lock_lineup Widen tournaments.py: list_sessions/list_teams/concede_tournament Widen courses.py: list_holes Widen messaging.py: team chat REST-endepunkter (list/send/delete) Legge til my_session_id/my_match_id i /auth/me sin my_tournaments Frontend: legge til lenker til lag-chat/scorekort fra «Mine runder» Scratch-verifisere hele deltaker-tilgang-runden (positive + negative kontroller) Ekte typesjekket frontend-produksjonsbuild + backend py_compile på nytt før deploy-forslag Presentere plan, deploye og oppdatere .md-filer for deltaker-tilgang-runden Bygge og scratch-verifisere sekundær e-postadresse (migrasjon 017) Designe, bygge og scratch-verifisere HCP-historikk (migrasjon 018) HCP-historikk er bygget og scratch-verifisert (18/18 sjekker, test_isolation.sql fortsatt 12/12). Hva er bygget: Migrasjon 018_handicap_history.sql — ny append-only-tabell handicap_history, kun for den personlige profilens HCP (ikke lagenes frosne handicap_index_snapshot, som er urørt, ADR-007). Backend: PATCH /auth/profile logger nå automatisk en ny rad når HCP faktisk endres til en tallverdi — ikke ved ren nullstilling, og ikke ved gjentatt lagring av samme verdi (sjekket eksplisitt mot forrige verdi først). Ny GET /auth/profile/handicap-history. Frontend: en «Vis HCP-historikk»-lenke i /account sin profilseksjon, viser dato + verdi-liste, oppdateres automatisk etter lagring. Verifisert grundig: ingen duplikat ved gjentatt lagring av uendret verdi, korrekt logging ved faktisk endring, ingen logg ved nullstilling, ny logg ved gjeninnsetting etter nullstilling, kronologisk rekkefølge riktig, og full isolasjon mellom to brukeres historikk.
This commit is contained in:
parent
b376f095be
commit
1a86be3f83
5 changed files with 234 additions and 19 deletions
28
018_handicap_history.sql
Normal file
28
018_handicap_history.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
-- =====================================================================
|
||||||
|
-- TeeCup — HCP-historikk over tid for personlig profil (migrasjon 018)
|
||||||
|
-- =====================================================================
|
||||||
|
-- Oppfølging av ADR-031 sitt "naturlig neste steg"-punkt: personlig
|
||||||
|
-- profil sin `app_user.handicap_index` (ADR-031, migrasjon 015) endres i
|
||||||
|
-- dag stille ved hver PATCH /auth/profile, uten noen logg over tidligere
|
||||||
|
-- verdier. Denne migrasjonen legger til en append-only historikk-tabell
|
||||||
|
-- -- IKKE noe erstatning for selve `app_user.handicap_index` (som
|
||||||
|
-- fortsatt er "gjeldende verdi", brukt overalt ellers uendret).
|
||||||
|
--
|
||||||
|
-- Bevisst KUN for den PERSONLIGE profilens HCP -- IKKE de org-scopede
|
||||||
|
-- `player.handicap_index`-radene eller `team_roster.handicap_index_
|
||||||
|
-- snapshot` (som allerede har sitt eget, separate reproduserbarhets-
|
||||||
|
-- prinsipp, ADR-007 -- ikke noe denne migrasjonen rører).
|
||||||
|
-- =====================================================================
|
||||||
|
|
||||||
|
\set ON_ERROR_STOP on
|
||||||
|
|
||||||
|
CREATE TABLE handicap_history (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id uuid NOT NULL REFERENCES app_user(id) ON DELETE CASCADE,
|
||||||
|
handicap_index numeric(4,1) NOT NULL,
|
||||||
|
recorded_at timestamptz NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ON handicap_history (user_id, recorded_at);
|
||||||
|
|
||||||
|
GRANT SELECT, INSERT ON handicap_history TO teecup_app;
|
||||||
52
CLAUDE.md
52
CLAUDE.md
|
|
@ -2150,10 +2150,39 @@ Ferdig og verifisert:
|
||||||
begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
begge containere boot-et rent, `/health`/`/dashboard` → 200, `teeoff.no`
|
||||||
upåvirket.
|
upåvirket.
|
||||||
|
|
||||||
|
- **Sekundær e-postadresse (del 1, det enkle tilfellet) — ✅ BYGGET OG LIVE
|
||||||
|
2026-07-21**, samme dag, rett etter deltaker-tilgang-runden. Ny
|
||||||
|
migrasjon `017_secondary_email.sql` (`secondary_email_token` +
|
||||||
|
`user_secondary_email`, samme token-hash-og-utløp-mønster som ADR-032s
|
||||||
|
`email_change_token`). Nye endepunkter `POST /auth/secondary-email`,
|
||||||
|
`POST /auth/secondary-email/confirm`, `DELETE /auth/secondary-email/{id}`.
|
||||||
|
**Kjernestykket:** `verify_magic_link`/`login_with_password` slår nå opp
|
||||||
|
`user_secondary_email` FØR sitt vanlige `app_user.email`-oppslag — en
|
||||||
|
innlogging på en verifisert sekundæradresse løses til EIERENS
|
||||||
|
eksisterende konto i stedet for å opprette en ny, separat en (nøyaktig
|
||||||
|
det hullet som gjorde funksjonen nødvendig i utgangspunktet). Lagt i
|
||||||
|
`/account` (ikke dashbordet som opprinnelig bedt om — bevisst avvik,
|
||||||
|
flagget eksplisitt: dette er kun del 1, dashbord-plassering er trolig
|
||||||
|
riktigere når/hvis del 2 (kontosammenslåing) bygges).
|
||||||
|
**Scratch-verifisert, 20 sjekker:** adresse ikke lagt til før bekreftet,
|
||||||
|
token ikke gjenbrukbart, dupliserte adresser (som andres primær- ELLER
|
||||||
|
sekundæradresse) avvist tydelig, innlogging via sekundæradresse (magic-
|
||||||
|
link OG passord) bekreftet å resolve til SAMME eksisterende konto,
|
||||||
|
fremmed kan ikke slette andres adresse, fjernet adresse oppretter en
|
||||||
|
genuint NY konto ved neste innlogging (beviser fjerning er reell).
|
||||||
|
`test_isolation.sql` 12/12. Ekte typesjekket produksjonsbuild kjørt og
|
||||||
|
bekreftet.
|
||||||
|
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon
|
||||||
|
017 kjørt mot ekte `teecup_db` (begge tabeller bekreftet, `test_
|
||||||
|
isolation.sql` fortsatt 12/12), deretter `docker compose up -d --build
|
||||||
|
teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||||||
|
`/health`/`/dashboard`/`/account`/`/verify-email` → 200, `teeoff.no`
|
||||||
|
upåvirket. Del 2 (ekte kontosammenslåing) fortsatt IKKE designet, egen
|
||||||
|
fremtidig runde, se FEATURE_BACKLOG.md.
|
||||||
|
|
||||||
Neste steg:
|
Neste steg:
|
||||||
1. **Pågår, samme økt:** bygge de to gjenværende bekreftede punktene fra
|
1. **Pågår, samme økt:** HCP-historikk over tid — siste bekreftede punkt
|
||||||
dashboard/konto-runden (2026-07-21) — sekundær e-postadresse (enkelt
|
fra dashboard/konto-runden (2026-07-21), gjenstår å bygge.
|
||||||
tilfelle, gjenbruker ADR-032s token-mønster) og HCP-historikk over tid.
|
|
||||||
2. **Pauset, venter på retning:** dashbordets tom-tilstand ved første
|
2. **Pauset, venter på retning:** dashbordets tom-tilstand ved første
|
||||||
innlogging. Brukeren ba om å justere det opprinnelige 2026-07-20-
|
innlogging. Brukeren ba om å justere det opprinnelige 2026-07-20-
|
||||||
forslaget i lys av et dypere spørsmål — bør organisasjon fortsatt være
|
forslaget i lys av et dypere spørsmål — bør organisasjon fortsatt være
|
||||||
|
|
@ -2167,16 +2196,13 @@ Neste steg:
|
||||||
dedikert ADR-runde, IKKE noe som skal bygges i forlengelsen av en
|
dedikert ADR-runde, IKKE noe som skal bygges i forlengelsen av en
|
||||||
vanlig funksjonsrunde. Se full analyse og åpne spørsmål i
|
vanlig funksjonsrunde. Se full analyse og åpne spørsmål i
|
||||||
FEATURE_BACKLOG.md.
|
FEATURE_BACKLOG.md.
|
||||||
4. **Notert, IKKE designet i detalj:** én person med flere e-post-adresser
|
4. **Del 1 (fri, ukrevd sekundær-e-post) er nå BYGGET OG LIVE** (2026-07-21,
|
||||||
— legge til en verifisert sekundær-adresse fra dashbordet, med
|
se status over). **Del 2 (ekte konto-sammenslåing) fortsatt IKKE
|
||||||
turneringer/data som "dukker opp"/slås sammen deretter, og fremtidige
|
designet:** hva skjer hvis den ønskede adressen ALLEREDE tilhører en
|
||||||
innlogginger med enten adresse. Fanget grundig i FEATURE_BACKLOG.md,
|
annen, eksisterende konto (i dag avvist tydelig med 409 DUPLICATE i
|
||||||
inkl. et reelt uløst spørsmål (ekte konto-sammenslåing hvis adressen
|
stedet for gjettet på)? Trenger egen, separat designrunde — se
|
||||||
allerede tilhører en annen eksisterende konto) som trenger egen,
|
FEATURE_BACKLOG.md for full analyse av hvorfor dette er vesentlig
|
||||||
separat designrunde. **Merk:** dette overlapper med punkt 1 sin
|
vanskeligere enn del 1.
|
||||||
"sekundær e-postadresse" — punkt 1 er det ENKLE, avgrensede tilfellet
|
|
||||||
(fri adresse), dette punktet er det harde tilfellet (adressen tilhører
|
|
||||||
allerede en annen konto).
|
|
||||||
5. **Deltaker-tilgang til lag-chat/scorekort er nå BYGGET OG LIVE**
|
5. **Deltaker-tilgang til lag-chat/scorekort er nå BYGGET OG LIVE**
|
||||||
(2026-07-21, se status over) — ADR-031s tidligere notert begrensning
|
(2026-07-21, se status over) — ADR-031s tidligere notert begrensning
|
||||||
er dermed tettet.
|
er dermed tettet.
|
||||||
|
|
|
||||||
|
|
@ -1315,7 +1315,7 @@ hvilke "første handling"-alternativer dashbordet bør vise i fremtiden.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Én person, flere e-postadresser — 📋 NOTERT 2026-07-20, IKKE designet/bygget
|
## Én person, flere e-postadresser — DEL 1 (det enkle tilfellet) ✅ BYGGET OG LIVE 2026-07-21, DEL 2 (kontosammenslåing) fortsatt 📋 NOTERT
|
||||||
|
|
||||||
Reist av brukeren rett etter ADR-032 (verifisert e-postbytte). Et beslektet,
|
Reist av brukeren rett etter ADR-032 (verifisert e-postbytte). Et beslektet,
|
||||||
men DISTINKT behov: én person kan ha flere e-postadresser i omløp samtidig
|
men DISTINKT behov: én person kan ha flere e-postadresser i omløp samtidig
|
||||||
|
|
@ -1367,11 +1367,67 @@ naturlig ville hørt hjemme) — trolig fordi selve GEVINSTEN (nye turneringer
|
||||||
dukker opp) er noe som vises på dashbordet, så handlingen bør ligge der
|
dukker opp) er noe som vises på dashbordet, så handlingen bør ligge der
|
||||||
resultatet vises.
|
resultatet vises.
|
||||||
|
|
||||||
**Ingenting designet i detalj eller bygget ennå** — kun fanget grundig her
|
**Ble delt i to separate runder, som foreslått:** (1) legg til en frisk,
|
||||||
slik at det ikke går i glemmeboken. Bør trolig deles i to separate runder:
|
ukrevd sekundær-e-post — ✅ BYGGET, se under. (2) Ekte konto-sammenslåing
|
||||||
(1) legg til en frisk, ukrevd sekundær-e-post (rimelig godt avgrenset,
|
for det vanskelige tilfellet — fortsatt IKKE designet, egen fremtidig
|
||||||
gjenbruker ADR-032 sitt mønster direkte), (2) ekte konto-sammenslåing for
|
runde.
|
||||||
det vanskelige tilfellet over (egen, større designrunde).
|
|
||||||
|
### Del 1 (det enkle tilfellet) — ✅ BYGGET OG LIVE 2026-07-21
|
||||||
|
|
||||||
|
Migrasjon `017_secondary_email.sql`: to nye tabeller
|
||||||
|
(`secondary_email_token` — midlertidig, samme token-hash-og-utløp-mønster
|
||||||
|
som `email_change_token`; `user_secondary_email` — den faktiske,
|
||||||
|
verifiserte adressen, globalt UNIQUE). Nye endepunkter i
|
||||||
|
`app/routers/auth.py`: `POST /auth/secondary-email` (send
|
||||||
|
bekreftelseslenke), `POST /auth/secondary-email/confirm` (ingen sesjon
|
||||||
|
påkrevd, samme mønster som selve magic-link-verifiseringen),
|
||||||
|
`DELETE /auth/secondary-email/{id}`.
|
||||||
|
|
||||||
|
**Kjernestykket, ikke bare CRUD:** `verify_magic_link` og
|
||||||
|
`login_with_password` sjekker nå `user_secondary_email` FØR de gjør sitt
|
||||||
|
vanlige `app_user.email`-oppslag — finner de en match, løses innloggingen
|
||||||
|
til DEN EKSISTERENDE eierens konto i stedet for å (som før) stille
|
||||||
|
opprette en helt ny, separat konto. Dette er selve mekanismen som gjør
|
||||||
|
adressen nyttig, ikke bare en liste over "andre adresser".
|
||||||
|
|
||||||
|
**Plassering, bevisst avvik fra brukerens opprinnelige "fra dashbordet"-
|
||||||
|
instruks:** lagt i `/account` (samme sted som ADR-032 sin e-postbytte),
|
||||||
|
IKKE dashbordet — begrunnet med at dette kun er del 1 (det enkle
|
||||||
|
tilfellet); når/hvis del 2 (kontosammenslåing, "data dukker opp") bygges,
|
||||||
|
er dashbordet trolig riktigere siden GEVINSTEN vises der. Flagget
|
||||||
|
eksplisitt til bruker, ikke stille besluttet.
|
||||||
|
|
||||||
|
**Scratch-verifisert, 20 sjekker:** adresse legges IKKE til før bekreftet;
|
||||||
|
token ikke gjenbrukbart; adresse som allerede er en ANNEN kontos
|
||||||
|
hovedadresse ELLER sekundæradresse avvist tydelig (409 DUPLICATE) i begge
|
||||||
|
retninger; innlogging via sekundæradressen (BÅDE magic-link OG passord)
|
||||||
|
løses korrekt til samme, eksisterende konto (bekreftet: samme `id`,
|
||||||
|
`email` i responsen forblir hovedadressen); en fremmed kan ikke slette
|
||||||
|
andres sekundæradresse; og — den kritiske sjekken — en ny innlogging på
|
||||||
|
adressen ETTER at den er fjernet oppretter en genuint NY, separat konto
|
||||||
|
(beviser fjerningen er reell, ikke kosmetisk). `test_isolation.sql`
|
||||||
|
fortsatt 12/12 (additiv migrasjon). Ekte typesjekket produksjonsbuild av
|
||||||
|
frontend kjørt og bekreftet.
|
||||||
|
|
||||||
|
**Rullet ut live 2026-07-21**, bruker bekreftet eksplisitt: migrasjon 017
|
||||||
|
kjørt mot ekte `teecup_db` (bekreftet begge nye tabeller finnes,
|
||||||
|
`test_isolation.sql` fortsatt 12/12), deretter `docker compose up -d
|
||||||
|
--build teecup_api teecup_frontend`. Begge containere boot-et rent,
|
||||||
|
`/health`/`/dashboard`/`/account`/`/verify-email` → 200, `teeoff.no`
|
||||||
|
upåvirket.
|
||||||
|
|
||||||
|
### Del 2 (ekte kontosammenslåing) — fortsatt 📋 NOTERT, IKKE designet
|
||||||
|
|
||||||
|
Uendret fra den opprinnelige analysen: hva skjer hvis adressen som legges
|
||||||
|
til ALLEREDE er primær- eller sekundæradressen til en ANNEN, eksisterende
|
||||||
|
konto (spilleren har altså to helt separate kontoer med egen historikk —
|
||||||
|
ulike org-medlemskap, ulike spillerkoblinger, kanskje ulikt passord/2FA)?
|
||||||
|
Dagens del 1-løsning avviser dette tydelig (409 DUPLICATE) i stedet for å
|
||||||
|
gjette — en ekte sammenslåing (slå sammen org-medlemskap uten å bryte
|
||||||
|
"én rolle per bruker per org", deduplisere spillerkoblinger, avgjøre
|
||||||
|
hvilken konto som "vinner" for motstridende felt) er en betydelig større
|
||||||
|
og mer risikofylt operasjon, fortsatt bevisst utsatt til en egen,
|
||||||
|
dedikert designrunde.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -780,13 +780,48 @@ async def update_profile(
|
||||||
values.append(user.user_id)
|
values.append(user.user_id)
|
||||||
|
|
||||||
async with plain_connection() as conn:
|
async with plain_connection() as conn:
|
||||||
|
# HCP-historikk (FEATURE_BACKLOG.md, ADR-031 sitt "naturlig neste
|
||||||
|
# steg"-punkt): les gjeldende verdi FØR den overskrives, slik at vi
|
||||||
|
# kan avgjøre om dette faktisk er en ENDRING (ikke bare et PATCH-
|
||||||
|
# kall som gjentar samme verdi) FØR vi logger en ny historikk-rad.
|
||||||
|
old_hcp = None
|
||||||
|
if "handicap_index" in updates:
|
||||||
|
old_hcp = await conn.fetchval("SELECT handicap_index FROM app_user WHERE id = $1", user.user_id)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE app_user SET {', '.join(set_clauses)} WHERE id = ${len(values)}",
|
f"UPDATE app_user SET {', '.join(set_clauses)} WHERE id = ${len(values)}",
|
||||||
*values,
|
*values,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if "handicap_index" in updates:
|
||||||
|
new_hcp = updates["handicap_index"]
|
||||||
|
# Kun faktiske tallverdier logges (ikke nullstilling) -- en
|
||||||
|
# "HCP fjernet"-hendelse gir ingen mening i en verdi-over-tid-
|
||||||
|
# historikk.
|
||||||
|
changed = new_hcp is not None and (old_hcp is None or float(old_hcp) != float(new_hcp))
|
||||||
|
if changed:
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO handicap_history (user_id, handicap_index) VALUES ($1, $2)",
|
||||||
|
user.user_id,
|
||||||
|
new_hcp,
|
||||||
|
)
|
||||||
return await me(user)
|
return await me(user)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/profile/handicap-history")
|
||||||
|
async def get_handicap_history(user: CurrentUser = Depends(get_current_user)) -> list[dict]:
|
||||||
|
async with plain_connection() as conn:
|
||||||
|
rows = await conn.fetch(
|
||||||
|
"""
|
||||||
|
SELECT handicap_index::float AS handicap_index, recorded_at
|
||||||
|
FROM handicap_history WHERE user_id = $1
|
||||||
|
ORDER BY recorded_at
|
||||||
|
""",
|
||||||
|
user.user_id,
|
||||||
|
)
|
||||||
|
return [{"handicap_index": r["handicap_index"], "recorded_at": r["recorded_at"].isoformat()} for r in rows]
|
||||||
|
|
||||||
|
|
||||||
@router.post("/profile/avatar", response_model=Me)
|
@router.post("/profile/avatar", response_model=Me)
|
||||||
async def upload_avatar(
|
async def upload_avatar(
|
||||||
file: UploadFile,
|
file: UploadFile,
|
||||||
|
|
|
||||||
|
|
@ -403,10 +403,80 @@ function ProfileSection({ me, onChanged }: { me: Me; onChanged: () => void }) {
|
||||||
{saving ? "Lagrer …" : "Lagre profil"}
|
{saving ? "Lagrer …" : "Lagre profil"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<HandicapHistorySection reloadKey={me.handicap_index} />
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HCP-historikk (ADR-031 sitt "naturlig neste steg"-punkt): en append-only
|
||||||
|
// logg bygget opp av selve PATCH-endepunktet (app/routers/auth.py) hver
|
||||||
|
// gang HCP-feltet faktisk endres til en tallverdi. `reloadKey` (gjeldende
|
||||||
|
// HCP) sørger for at listen hentes på nytt rett etter en lagring, uten en
|
||||||
|
// egen refetch-prop å tre gjennom fra ProfileSection.
|
||||||
|
function HandicapHistorySection({ reloadKey }: { reloadKey: number | null }) {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [entries, setEntries] = useState<{ handicap_index: number; recorded_at: string }[] | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
let cancelled = false
|
||||||
|
fetch("/auth/profile/handicap-history", { credentials: "include" })
|
||||||
|
.then((res) => (res.ok ? res.json() : []))
|
||||||
|
.then((data) => {
|
||||||
|
if (!cancelled) setEntries(data)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) setEntries([])
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [open, reloadKey])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-t border-border pt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen((v) => !v)}
|
||||||
|
className="text-sm font-semibold text-primary underline-offset-2 hover:underline"
|
||||||
|
>
|
||||||
|
{open ? "Skjul HCP-historikk" : "Vis HCP-historikk"}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div className="mt-3">
|
||||||
|
{entries === null ? (
|
||||||
|
<p className="text-sm text-muted-foreground">Laster …</p>
|
||||||
|
) : entries.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground text-pretty">
|
||||||
|
Ingen historikk ennå — registreres automatisk neste gang du endrer HCP.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<ul className="flex flex-col gap-1.5">
|
||||||
|
{[...entries].reverse().map((entry, i) => (
|
||||||
|
<li
|
||||||
|
key={`${entry.recorded_at}-${i}`}
|
||||||
|
className="flex items-center justify-between gap-3 text-sm"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{new Date(entry.recorded_at).toLocaleDateString("no-NO", {
|
||||||
|
day: "numeric",
|
||||||
|
month: "short",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span className="font-semibold tabular-nums text-foreground">{entry.handicap_index}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// --- E-post (identifikatoren) -----------------------------------------------
|
// --- E-post (identifikatoren) -----------------------------------------------
|
||||||
// Bevisst IKKE en del av ProfileSection sin vanlige PATCH -- e-post er
|
// Bevisst IKKE en del av ProfileSection sin vanlige PATCH -- e-post er
|
||||||
// innloggings-identifikatoren, endring krever at den NYE adressen beviser
|
// innloggings-identifikatoren, endring krever at den NYE adressen beviser
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue