import copy from "copy-to-clipboard"; import { ClipboardIcon, TrashIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import ConfirmDialog from "@/components/ConfirmDialog"; import { Button } from "@/components/ui/button"; import { userServiceClient } from "@/grpcweb"; import useCurrentUser from "@/hooks/useCurrentUser"; import { useDialog } from "@/hooks/useDialog"; import { UserAccessToken } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; import CreateAccessTokenDialog from "../CreateAccessTokenDialog"; const listAccessTokens = async (parent: string) => { const { accessTokens } = await userServiceClient.listUserAccessTokens({ parent }); return accessTokens.sort((a, b) => (b.issuedAt?.getTime() ?? 0) - (a.issuedAt?.getTime() ?? 0)); }; const AccessTokenSection = () => { const t = useTranslate(); const currentUser = useCurrentUser(); const [userAccessTokens, setUserAccessTokens] = useState([]); const createTokenDialog = useDialog(); const [deleteTarget, setDeleteTarget] = useState(undefined); useEffect(() => { listAccessTokens(currentUser.name).then((accessTokens) => { setUserAccessTokens(accessTokens); }); }, []); const handleCreateAccessTokenDialogConfirm = async (created: UserAccessToken) => { const accessTokens = await listAccessTokens(currentUser.name); setUserAccessTokens(accessTokens); toast.success(t("setting.access-token-section.create-dialog.access-token-created", { description: created.description })); }; const handleCreateToken = () => { createTokenDialog.open(); }; const copyAccessToken = (accessToken: string) => { copy(accessToken); toast.success(t("setting.access-token-section.access-token-copied-to-clipboard")); }; const handleDeleteAccessToken = async (userAccessToken: UserAccessToken) => { setDeleteTarget(userAccessToken); }; const confirmDeleteAccessToken = async () => { if (!deleteTarget) return; const { name: tokenName, description } = deleteTarget; await userServiceClient.deleteUserAccessToken({ name: tokenName }); // Filter by stable resource name to avoid ambiguity with duplicate token strings setUserAccessTokens((prev) => prev.filter((token) => token.name !== tokenName)); setDeleteTarget(undefined); toast.success(t("setting.access-token-section.access-token-deleted", { description })); }; const getFormatedAccessToken = (accessToken: string) => { return `${accessToken.slice(0, 4)}****${accessToken.slice(-4)}`; }; return (

{t("setting.access-token-section.title")}

{t("setting.access-token-section.description")}

{userAccessTokens.map((userAccessToken) => ( ))}
{t("setting.access-token-section.token")} {t("common.description")} {t("setting.access-token-section.create-dialog.created-at")} {t("setting.access-token-section.create-dialog.expires-at")} {t("common.delete")}
{getFormatedAccessToken(userAccessToken.accessToken)} {userAccessToken.description} {userAccessToken.issuedAt?.toLocaleString()} {userAccessToken.expiresAt?.toLocaleString() ?? t("setting.access-token-section.create-dialog.duration-never")}
{/* Create Access Token Dialog */} !open && setDeleteTarget(undefined)} title={deleteTarget ? t("setting.access-token-section.access-token-deletion", { description: deleteTarget.description }) : ""} description={t("setting.access-token-section.access-token-deletion-description")} confirmLabel={t("common.delete")} cancelLabel={t("common.cancel")} onConfirm={confirmDeleteAccessToken} confirmVariant="destructive" />
); }; export default AccessTokenSection;