import { useQuery, useQueryClient } from "@tanstack/react-query"; import { ExternalLink, Trash2 } from "lucide-react"; import { useState } from "react"; import { BackLink } from "@/client/components/ui/back-link"; import { Card } from "@/client/components/ui/card"; import { ConfirmButton } from "@/client/components/ui/confirm-button"; import { PageError } from "@/client/components/ui/page-error"; import { PageHeader } from "@/client/components/ui/page-header"; import { PanelRow } from "@/client/components/ui/row"; import { Skeleton } from "@/client/components/ui/skeleton"; import { useRequireSession } from "@/client/hooks/use-auth-guard"; import { formatDate } from "@/client/lib/format"; import { apiRequest, responseMessage } from "@/client/lib/http"; import { fetchJson, queryKeys } from "@/client/lib/query"; interface ConnectedApp { id: string; client_id: string; client_name: string | null; client_uri: string | null; scopes: string[]; granted_at: string | null; updated_at: string | null; } const AppRowsSkeleton = ({ count }: { count: number }) => (
{Array.from({ length: count }, (_, i) => (
))}
); const AccountAppsPageSkeleton = () => (
); export const AccountAppsPage = () => { const session = useRequireSession("/account/apps"); const queryClient = useQueryClient(); const userId = session.data?.user.id ?? null; const appsQueryKey = queryKeys.accountApps(userId); const { data: apps = null } = useQuery({ queryKey: appsQueryKey, queryFn: () => fetchJson("/api/account/connected-apps"), enabled: Boolean(userId), }); const [error, setError] = useState(null); const [pending, setPending] = useState(null); const handleRevoke = async (app: ConnectedApp) => { setError(null); setPending(app.id); try { const res = await apiRequest(`/api/account/connected-apps/${encodeURIComponent(app.id)}`, { method: "DELETE", }); if (!res.ok && res.status !== 204) { setError(await responseMessage(res, "Could not revoke access.")); return; } await queryClient.invalidateQueries({ queryKey: appsQueryKey, exact: true }); } catch (e) { setError(e instanceof Error ? e.message : "Failed to revoke access."); } finally { setPending(null); } }; if (session.isPending || !session.data) { return ; } return (
Account setError(null)} />

Authorized

{!apps ? ( ) : (
{apps.map((app) => (

{app.client_name ?? app.client_id}

{app.client_uri ? ( ) : null}
{app.client_id} {Array.isArray(app.scopes) && app.scopes.length > 0 ? (
{app.scopes.map((s) => ( {s} ))}
) : null} {app.granted_at ? (

Authorized {formatDate(app.granted_at)}

) : null}
handleRevoke(app)} >
))} {apps.length === 0 ?

No third-party apps authorized yet.

: null}
)}
); };