"use client" // Medlemsstyring (ADR-022): invitere, endre rolle, fjerne/forlate. Rolle- // grensene håndheves også server-side (app/routers/organizations.py) -- // UI-et her skjuler bare handlinger som uansett ville blitt avvist, for en // tydeligere opplevelse, ikke som eneste håndhevelse. import type React from "react" import { useEffect, useState } from "react" import Link from "next/link" import { ArrowLeft, Mail, Trash2, Trophy, UserPlus, Users, UsersRound } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" type Role = "owner" | "admin" | "member" type Membership = { id: string user_id: string email: string display_name: string role: Role } type Invitation = { id: string email: string role: Role expires_at: string } const ROLE_LABELS: Record = { owner: "Eier", admin: "Administrator", member: "Medlem" } export function OrgMembers({ organizationId, orgName }: { organizationId: string; orgName: string }) { const [myUserId, setMyUserId] = useState(null) const [myRole, setMyRole] = useState(null) const [members, setMembers] = useState([]) const [invitations, setInvitations] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) async function loadAll() { try { const [meRes, membersRes, invitationsRes] = await Promise.all([ fetch("/auth/me", { credentials: "include" }), fetch(`/orgs/${organizationId}/memberships`, { credentials: "include" }), fetch(`/orgs/${organizationId}/invitations`, { credentials: "include" }), ]) if (meRes.ok) { const me: { id: string; organizations: { organization_id: string; role: Role }[] } = await meRes.json() setMyUserId(me.id) setMyRole(me.organizations.find((o) => o.organization_id === organizationId)?.role ?? null) } if (membersRes.ok) setMembers(await membersRes.json()) if (invitationsRes.ok) setInvitations(await invitationsRes.json()) } catch { setError("Klarte ikke å laste medlemmer. Prøv å laste siden på nytt.") } finally { setLoading(false) } } useEffect(() => { void loadAll() // eslint-disable-next-line react-hooks/exhaustive-deps }, [organizationId]) async function inviteMember(email: string, role: Role) { setError(null) try { const res = await fetch(`/orgs/${organizationId}/invitations`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ email, role }), }) if (!res.ok) { const body = await res.json().catch(() => null) throw new Error(body?.detail?.message ?? "Klarte ikke å sende invitasjonen.") } const created: Invitation = await res.json() setInvitations((prev) => [created, ...prev]) } catch (err) { setError(err instanceof Error ? err.message : "Klarte ikke å sende invitasjonen.") } } async function revokeInvitation(id: string) { const res = await fetch(`/orgs/${organizationId}/invitations/${id}`, { method: "DELETE", credentials: "include", }) if (res.ok || res.status === 204) { setInvitations((prev) => prev.filter((i) => i.id !== id)) } } async function changeRole(membershipId: string, role: Role) { setError(null) try { const res = await fetch(`/orgs/${organizationId}/memberships/${membershipId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ role }), }) if (!res.ok) { const body = await res.json().catch(() => null) throw new Error(body?.detail?.message ?? "Klarte ikke å endre rollen.") } const updated: Membership = await res.json() setMembers((prev) => prev.map((m) => (m.id === updated.id ? updated : m))) } catch (err) { setError(err instanceof Error ? err.message : "Klarte ikke å endre rollen.") } } async function removeMember(membershipId: string, isSelf: boolean) { const confirmMsg = isSelf ? "Er du sikker på at du vil forlate organisasjonen?" : "Er du sikker på at du vil fjerne dette medlemmet?" if (!confirm(confirmMsg)) return setError(null) try { const res = await fetch(`/orgs/${organizationId}/memberships/${membershipId}`, { method: "DELETE", credentials: "include", }) if (!res.ok && res.status !== 204) { const body = await res.json().catch(() => null) throw new Error(body?.detail?.message ?? "Klarte ikke å fjerne medlemmet.") } setMembers((prev) => prev.filter((m) => m.id !== membershipId)) if (isSelf) window.location.href = "/dashboard" } catch (err) { setError(err instanceof Error ? err.message : "Klarte ikke å fjerne medlemmet.") } } const canInvite = myRole === "owner" || myRole === "admin" const inviteRoleOptions: Role[] = myRole === "owner" ? ["member", "admin", "owner"] : ["member"] if (loading) { return (
) } return (
{error && (

{error}

)} {canInvite && }
Spillerpool Se og rediger alle spillere organisasjonen har registrert
Order of Merit Sesong-sammenlagt rangering på tvers av turneringer

{members.map((m) => ( ))}
{invitations.length > 0 && (

{invitations.map((inv) => (
{inv.email} Invitert som {ROLE_LABELS[inv.role]}
{(myRole === "owner" || (myRole === "admin" && inv.role === "member")) && ( )}
))}
)}
) } function InviteForm({ roleOptions, onInvite, }: { roleOptions: Role[] onInvite: (email: string, role: Role) => void }) { const [email, setEmail] = useState("") const [role, setRole] = useState(roleOptions[0]) const [sending, setSending] = useState(false) async function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!email.trim() || sending) return setSending(true) try { await onInvite(email.trim(), role) setEmail("") } finally { setSending(false) } } return (
setEmail(e.target.value)} className="h-11 rounded-xl" />
) } function MemberRow({ member, isSelf, canManage, onChangeRole, onRemove, }: { member: Membership isSelf: boolean canManage: boolean onChangeRole: (id: string, role: Role) => void onRemove: (id: string, isSelf: boolean) => void }) { // Selvbetjent nedgradering (aldri oppgradering, se app/routers/organizations.py) // -- viser kun rollene som faktisk er lavere enn nåværende for egen rad når // brukeren ikke er owner (owner kan endre fritt). const rank: Record = { member: 0, admin: 1, owner: 2 } const roleOptionsForRow: Role[] = canManage ? ["owner", "admin", "member"] : isSelf ? (["owner", "admin", "member"] as Role[]).filter((r) => rank[r] <= rank[member.role]) : [] const canEditRole = canManage || (isSelf && roleOptionsForRow.length > 1) const canRemove = canManage || isSelf return (
{member.display_name} {isSelf && (deg)} {member.email}
{canEditRole ? ( ) : ( {ROLE_LABELS[member.role]} )} {canRemove && ( )}
) }