// SPDX-FileCopyrightText: 2026 Progmasoft // SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 "use client"; import { useEffect, useState } from "react"; import { callOAuthApi, signInPath, type AllowedApplication, } from "@/lib/credentials"; import { format, getCredentialsMessages } from "@/lib/credentials-messages"; import type { Locale } from "@/lib/localization"; type AllowedState = | { kind: "loading" } | { kind: "failed" } | { kind: "ready"; allowed: AllowedApplication[] }; /** The applications the signed-in account has allowed, each with a Withdraw button. */ export function AllowedApplications({ locale }: { locale: Locale }) { const { allowed: text, common } = getCredentialsMessages(locale); const [state, setState] = useState({ kind: "loading" }); useEffect(() => { const controller = new AbortController(); void callOAuthApi("/allowed", { signal: controller.signal, }).then((result) => { if (result.kind === "signedOut") { window.location.replace(signInPath("/account/applications")); } else if (result.kind === "ok") { setState({ kind: "ready", allowed: result.value }); } else { setState({ kind: "failed" }); } }); return () => controller.abort(); }, []); async function withdraw(authorizationId: string) { const result = await callOAuthApi( `/allowed/${encodeURIComponent(authorizationId)}`, { method: "DELETE" }, ); if (result.kind === "ok" || result.kind === "notFound") { setState((current) => current.kind === "ready" ? { kind: "ready", allowed: current.allowed.filter( (entry) => entry.authorizationId !== authorizationId, ), } : current, ); } else if (result.kind === "signedOut") { window.location.assign(signInPath("/account/applications")); } } if (state.kind === "loading") { return

{common.loading}

; } if (state.kind === "failed") { return (
{common.unreachable}
); } return (
{state.allowed.length === 0 ? (

{text.empty}

) : (
    {state.allowed.map((entry) => (
  • {entry.displayName ?? text.deleted} {entry.createdAt && ( {format(text.since, { date: new Date(entry.createdAt).toLocaleDateString( locale, ), })} )} {entry.scopes.map((scope) => ( {scope} ))}
  • ))}
)}
); }