teecup/frontend/components/more-menu.tsx

118 lines
3.8 KiB
TypeScript
Raw Normal View History

Ja, jeg forsto rekkefølgen — steg 1 og 2 er nå ferdig og scratch-verifisert: Steg 1 (backend, HCP-prosent + Match-HCP): ny migrasjon 051_round_allowance_override.sql (round.allowance_override jsonb), koblet inn i _recompute_side_handicaps/_relative_strokes_for_round + POST/PATCH /rounds. Verifisert presist i scratch: 100%→50%-prosent ga playing_handicap 24/10 → 12/5 nøyaktig som beregnet for hånd, og use_matchplay_handicap slått av ga rå 12/5 i stedet for differensial 7/0. Slagspill upåvirket (regresjon bekreftet). Steg 2 (kode-fiks, lag-gruppering): ScorecardGrid (live-scoring) og MatchScorecardGrid (post-runde/fremtidig sammenslått side) sorterer nå stabilt på round_side_id før rendering. Bygget en fersk scratch-fourball-runde med akkurat ditt bug-scenario (A→Rødt, B→Blått, C→Rødt, D→Blått, lagt til i den rekkefølgen) og bekreftet visuelt i nettleseren i begge visningene: Rødt lag vises samlet, deretter Blått — ikke lenger interleaved. Typesjekk + full produksjonsbuild kompilerte rent gjennom hele runden. Gjenstår før jeg går videre til V0-promptene: migrasjon 051 er kun kjørt i scratch, ikke mot ekte teecup_db. Planen er: docker exec -i teeoff_db psql -U teeoff_admin -d teecup_db -v ON_ERROR_STOP=1 < 051_round_allowance_override.sql — ett rent additivt ALTER TABLE round ADD COLUMN allowance_override jsonb, ingen eksisterende rader berøres. Deretter docker compose up -d --build teecup_api teecup_frontend (backend for allowance-koblingen, frontend for gruppering-fiksen). Bekreft så kjører jeg dette, og går videre til V0-prompt 1 (rundeoppsett-veiviseren).
2026-08-02 08:24:01 +02:00
"use client"
// "Mer"-siden (2026-08-01) -- den femte fanen i den nye bunn-navigasjonen
// (dashboard.tsx sin BottomTabBar) trengte et reelt mål, ikke en lenke til
// en side som ikke fantes. Samler destinasjoner som allerede finnes andre
// steder i appen (header, dashbordets organisasjon-fot) på ett sted, i
// stedet for å bygge noe nytt -- en ren navigasjons-samleside.
import { useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import Link from "next/link"
import { Bell, Building2, ChevronRight, LogOut, UserCircle, UserPlus } from "lucide-react"
import { BottomTabBar } from "@/components/dashboard"
const C = {
card: "#ffffff",
ink: "#012c11",
muted: "#424941",
border: "#c1c9bf",
primary: "#1f6b08",
} as const
type MyOrg = { organization_id: string; name: string; role: string }
type Me = { display_name: string; first_name: string | null; organizations: MyOrg[] }
export function MoreMenu() {
const router = useRouter()
const [me, setMe] = useState<Me | null>(null)
useEffect(() => {
fetch("/auth/me", { credentials: "include" })
.then((res) => (res.ok ? res.json() : null))
.then((data: Me | null) => setMe(data))
.catch(() => {})
}, [])
async function handleLogout() {
try {
await fetch("/auth/logout", { method: "POST", credentials: "include" })
} finally {
router.replace("/")
}
}
return (
Update Todos Skrive migrasjon 052 (guest_first_name/guest_last_name + backfill) Bygge backend: splitt navn i ParticipantCreate/Update, auto-synk guest_name Bygge backend: GET known-guest-oppslag (e-post-basert, scoped til egen bruker) Bygge backend: retroaktiv e-post-kobling ved innlogging (auth.py) Bygge backend: HTML-e-post-støtte i email.py (multipart/alternative) Bygge backend: scorekort/statistikk-e-post ved complete_round (individuell + delt-ball) Frontend: e-post-felt + navnesplitt + autofyll fra søk i AddGuestForm Frontend: "vi kjenner igjen denne e-posten"-forslag i AddGuestForm Scratch-verifisere hele kjeden (migrasjon+backend+frontend+e-post) end-to-end Kjøre migrasjon mot ekte teecup_db og rulle ut (etter bekreftelse) Alt er bygget og scratch-verifisert. Før jeg ruller ut mot ekte teecup_db, her er planen — trenger din bekreftelse per sikkerhetsregelen: Migrasjon 052_guest_name_split.sql (rent additiv, ingen sletting): ALTER TABLE round_participant ADD COLUMN guest_first_name text; + guest_last_name text Backfiller eksisterende guest_name-rader ved samme "første ord/resten"-heuristikk som frontend bruker til autofyll Deretter: docker compose up -d --build teecup_api teecup_frontend Hva som er bygget: E-post som valgfritt felt i "Legg til uten konto (gjest)" (fantes i backend fra før, manglet i selve skjemaet) Navn splittet i for-/etternavn (etternavn valgfritt) Retroaktiv kobling: spiller runder som gjest før du har konto → runden dukker opp i "Egne runder" når du logger inn med samme e-post, men teller ikke automatisk mot faktisk HCP (må bekreftes selv — unngår at en fremmed kan påvirke HCP-en din uten samtykke) E-post med scorekort + statistikk + invitasjon sendes automatisk når runden fullføres, til enhver gjest med registrert e-post — første HTML-e-post i appen Autofyll fra søkefeltet inn i gjesteskjemaet "Vi kjenner igjen denne e-posten"-forslag, scoped til dine egne tidligere gjester (ikke globalt — unngår en personvernlekkasje)
2026-08-03 06:24:57 +02:00
<div className="flex min-h-[100dvh] flex-col bg-background">
Ja, jeg forsto rekkefølgen — steg 1 og 2 er nå ferdig og scratch-verifisert: Steg 1 (backend, HCP-prosent + Match-HCP): ny migrasjon 051_round_allowance_override.sql (round.allowance_override jsonb), koblet inn i _recompute_side_handicaps/_relative_strokes_for_round + POST/PATCH /rounds. Verifisert presist i scratch: 100%→50%-prosent ga playing_handicap 24/10 → 12/5 nøyaktig som beregnet for hånd, og use_matchplay_handicap slått av ga rå 12/5 i stedet for differensial 7/0. Slagspill upåvirket (regresjon bekreftet). Steg 2 (kode-fiks, lag-gruppering): ScorecardGrid (live-scoring) og MatchScorecardGrid (post-runde/fremtidig sammenslått side) sorterer nå stabilt på round_side_id før rendering. Bygget en fersk scratch-fourball-runde med akkurat ditt bug-scenario (A→Rødt, B→Blått, C→Rødt, D→Blått, lagt til i den rekkefølgen) og bekreftet visuelt i nettleseren i begge visningene: Rødt lag vises samlet, deretter Blått — ikke lenger interleaved. Typesjekk + full produksjonsbuild kompilerte rent gjennom hele runden. Gjenstår før jeg går videre til V0-promptene: migrasjon 051 er kun kjørt i scratch, ikke mot ekte teecup_db. Planen er: docker exec -i teeoff_db psql -U teeoff_admin -d teecup_db -v ON_ERROR_STOP=1 < 051_round_allowance_override.sql — ett rent additivt ALTER TABLE round ADD COLUMN allowance_override jsonb, ingen eksisterende rader berøres. Deretter docker compose up -d --build teecup_api teecup_frontend (backend for allowance-koblingen, frontend for gruppering-fiksen). Bekreft så kjører jeg dette, og går videre til V0-prompt 1 (rundeoppsett-veiviseren).
2026-08-02 08:24:01 +02:00
<header className="border-b pt-[env(safe-area-inset-top)]" style={{ borderColor: C.border }}>
<div className="mx-auto w-full max-w-2xl px-5 py-4">
<h1 className="text-xl font-extrabold tracking-tight" style={{ color: C.ink }}>
Mer
</h1>
{me && (
<p className="text-sm" style={{ color: C.muted }}>
Innlogget som {me.first_name ?? me.display_name}
</p>
)}
</div>
</header>
<main className="mx-auto w-full max-w-2xl flex-1 px-5 pb-24 pt-4">
<ul
className="flex flex-col overflow-hidden rounded-2xl"
style={{ border: `1px solid ${C.border}`, backgroundColor: C.card }}
>
<MenuLink href="/account" icon={UserCircle} label="Konto" />
<MenuLink href="/my-friends" icon={UserPlus} label="Venner" />
<MenuLink href="/my-notifications" icon={Bell} label="Varsler" last={me?.organizations.length === 0} />
{me?.organizations.map((o, i) => (
<MenuLink
key={o.organization_id}
href={`/organizations/${o.organization_id}/members?name=${encodeURIComponent(o.name)}`}
icon={Building2}
label={o.name}
last={i === me.organizations.length - 1}
/>
))}
</ul>
<button
type="button"
onClick={handleLogout}
className="mt-6 flex min-h-[52px] w-full items-center justify-center gap-2 rounded-2xl text-sm font-bold"
style={{ border: `1px solid ${C.border}`, backgroundColor: C.card, color: C.ink }}
>
<LogOut aria-hidden="true" className="size-4" />
Logg ut
</button>
</main>
<BottomTabBar active="more" />
</div>
)
}
function MenuLink({
href,
icon: Icon,
label,
last,
}: {
href: string
icon: typeof Bell
label: string
last?: boolean
}) {
return (
<li style={last ? undefined : { borderBottom: `1px solid ${C.border}` }}>
<Link href={href} className="flex min-h-[56px] items-center gap-3 px-4">
<Icon aria-hidden="true" className="size-5" color={C.primary} />
<span className="flex-1 truncate text-base font-semibold" style={{ color: C.ink }}>
{label}
</span>
<ChevronRight aria-hidden="true" className="size-5" color={C.muted} />
</Link>
</li>
)
}