Kopierbar offentlig lenke + trygg fallback for ikke-innlogget besøkende
Organisator delte ved et uhell sin egen admin-URL (/tournaments/{id}?org=...)
i stedet for den ekte offentlige (/t/{id}) -- lenken fantes fra før, men var
kun ren, ikke-kopierbar tekst. tournament-presentation.tsx fikk en ekte
kopier-knapp.
Reell, uavhengig bug: tournament-router.tsx falt ubetinget tilbake til
Cup-format-adminskjermen ved ENHVER feil i formatoppslaget, inkl. 401 for en
ikke-innlogget besøkende -- viste et ødelagt lag-oppsett-skjermbilde for en
individuell turnering. Skiller nå 401 fra andre feil og sender videre til
den ekte offentlige siden i stedet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
6644447b3e
commit
f9ae32f4e1
2 changed files with 65 additions and 2 deletions
|
|
@ -15,6 +15,7 @@ import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Camera,
|
Camera,
|
||||||
Check,
|
Check,
|
||||||
|
Copy,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Globe,
|
Globe,
|
||||||
Lock,
|
Lock,
|
||||||
|
|
@ -105,6 +106,21 @@ export function TournamentPresentationPanel({
|
||||||
const [sponsorName, setSponsorName] = useState("")
|
const [sponsorName, setSponsorName] = useState("")
|
||||||
const [sponsorUrl, setSponsorUrl] = useState("")
|
const [sponsorUrl, setSponsorUrl] = useState("")
|
||||||
const [addingSponsor, setAddingSponsor] = useState(false)
|
const [addingSponsor, setAddingSponsor] = useState(false)
|
||||||
|
|
||||||
|
// Kopier-lenke (2026-08-21) -- fant reell forvirring: organisator delte
|
||||||
|
// sin egen administrator-URL (/tournaments/{id}?org=...) i stedet for
|
||||||
|
// denne, siden lenken under kun var ren, ikke-kopierbar tekst. Samme
|
||||||
|
// kopier-mønster som JoinCodeChip i individual-tournament-detail.tsx.
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false)
|
||||||
|
async function copyPublicLink() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(`${window.location.origin}/t/${tournamentId}`)
|
||||||
|
setLinkCopied(true)
|
||||||
|
setTimeout(() => setLinkCopied(false), 1500)
|
||||||
|
} catch {
|
||||||
|
// stille -- ikke kritisk
|
||||||
|
}
|
||||||
|
}
|
||||||
const [sponsorError, setSponsorError] = useState<string | null>(null)
|
const [sponsorError, setSponsorError] = useState<string | null>(null)
|
||||||
const [logoTargetId, setLogoTargetId] = useState<string | null>(null)
|
const [logoTargetId, setLogoTargetId] = useState<string | null>(null)
|
||||||
const [uploadingLogoFor, setUploadingLogoFor] = useState<string | null>(null)
|
const [uploadingLogoFor, setUploadingLogoFor] = useState<string | null>(null)
|
||||||
|
|
@ -282,7 +298,7 @@ export function TournamentPresentationPanel({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="mb-4 flex flex-col gap-1">
|
<div className="mb-4 flex flex-col gap-2">
|
||||||
<p className="text-sm leading-relaxed text-muted-foreground text-pretty">
|
<p className="text-sm leading-relaxed text-muted-foreground text-pretty">
|
||||||
Hvordan turneringen vises på sin offentlige side (
|
Hvordan turneringen vises på sin offentlige side (
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -295,6 +311,18 @@ export function TournamentPresentationPanel({
|
||||||
</Link>
|
</Link>
|
||||||
) og klubbsiden.
|
) og klubbsiden.
|
||||||
</p>
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={copyPublicLink}
|
||||||
|
className="inline-flex min-h-11 w-fit items-center gap-1.5 rounded-xl border border-border bg-card px-3 py-2 text-sm font-bold text-foreground transition-colors hover:bg-accent/50"
|
||||||
|
>
|
||||||
|
{linkCopied ? (
|
||||||
|
<Check aria-hidden="true" className="size-4 text-primary" />
|
||||||
|
) : (
|
||||||
|
<Copy aria-hidden="true" className="size-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
{linkCopied ? "Kopiert!" : "Kopier offentlig lenke"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
// egen rad"-mønster som tournament-detail.tsx allerede bruker for join_code.
|
// egen rad"-mønster som tournament-detail.tsx allerede bruker for join_code.
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
import { TournamentDetail } from "@/components/tournament-detail"
|
import { TournamentDetail } from "@/components/tournament-detail"
|
||||||
import { IndividualTournamentDetail } from "@/components/individual-tournament-detail"
|
import { IndividualTournamentDetail } from "@/components/individual-tournament-detail"
|
||||||
|
|
||||||
|
|
@ -20,13 +21,30 @@ export function TournamentRouter({
|
||||||
tournamentId: string
|
tournamentId: string
|
||||||
tournamentName: string
|
tournamentName: string
|
||||||
}) {
|
}) {
|
||||||
|
const router = useRouter()
|
||||||
const [formatType, setFormatType] = useState<"team" | "individual" | null>(null)
|
const [formatType, setFormatType] = useState<"team" | "individual" | null>(null)
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
|
// Ikke-innlogget besøkende på denne organisator-URL-en (f.eks. en delt
|
||||||
|
// /tournaments/{id}?org=...-lenke) -- funnet 2026-08-21: den generiske
|
||||||
|
// feil-fallback-en under viste tidligere ALLTID Cup/lag-format-
|
||||||
|
// administrasjonsskjermen (samme "trygt mot krasj"-begrunnelse som før),
|
||||||
|
// helt uavhengig av turneringens faktiske format og helt uavhengig av
|
||||||
|
// ÅRSAKEN til feilen -- en anonym besøkende på en individuell turnering
|
||||||
|
// fikk et forvirrende, ødelagt Ryder Cup-oppsett-skjermbilde i stedet for
|
||||||
|
// en ren "logg inn"-beskjed. Skiller nå eksplisitt 401 (ikke innlogget)
|
||||||
|
// fra andre feil, og sender i stedet videre til den ekte offentlige siden.
|
||||||
|
const [notAuthenticated, setNotAuthenticated] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
fetch(`/orgs/${organizationId}/tournaments`, { credentials: "include" })
|
fetch(`/orgs/${organizationId}/tournaments`, { credentials: "include" })
|
||||||
.then((res) => (res.ok ? res.json() : Promise.reject()))
|
.then((res) => {
|
||||||
|
if (res.status === 401) {
|
||||||
|
if (!cancelled) setNotAuthenticated(true)
|
||||||
|
return Promise.reject()
|
||||||
|
}
|
||||||
|
return res.ok ? res.json() : Promise.reject()
|
||||||
|
})
|
||||||
.then((list: { id: string; format_type: string }[]) => {
|
.then((list: { id: string; format_type: string }[]) => {
|
||||||
if (cancelled) return
|
if (cancelled) return
|
||||||
const mine = list.find((t) => t.id === tournamentId)
|
const mine = list.find((t) => t.id === tournamentId)
|
||||||
|
|
@ -40,10 +58,27 @@ export function TournamentRouter({
|
||||||
}
|
}
|
||||||
}, [organizationId, tournamentId])
|
}, [organizationId, tournamentId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (notAuthenticated) router.replace(`/t/${tournamentId}`)
|
||||||
|
}, [notAuthenticated, tournamentId, router])
|
||||||
|
|
||||||
|
if (notAuthenticated) {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4 bg-background">
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
// Samme trygge fallback som resten av appen: en ukjent/utilgjengelig
|
// Samme trygge fallback som resten av appen: en ukjent/utilgjengelig
|
||||||
// turnering skal ikke krasje -- TournamentDetail sin egen feilhåndtering
|
// turnering skal ikke krasje -- TournamentDetail sin egen feilhåndtering
|
||||||
// (join_code/teams/players) tar over og viser en tydelig feilmelding.
|
// (join_code/teams/players) tar over og viser en tydelig feilmelding.
|
||||||
|
// (Denne grenen er nå kun for EKTE feil hos en innlogget bruker --
|
||||||
|
// 401/ikke-innlogget håndteres over, IKKE lenger her.)
|
||||||
return (
|
return (
|
||||||
<TournamentDetail organizationId={organizationId} tournamentId={tournamentId} tournamentName={tournamentName} />
|
<TournamentDetail organizationId={organizationId} tournamentId={tournamentId} tournamentName={tournamentName} />
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue