From 41c91f98ae562fb5f01c8a6d2a6a927b42e17379 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Fri, 31 Oct 2025 10:13:25 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20API=20token=20m?= =?UTF-8?q?anagement=20to=20use=20tRPC=20(#2305)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/builder/src/components/TimeSince.tsx | 7 ++- .../user/components/ApiTokensList.tsx | 36 ++++++------- .../user/components/CreateApiTokenDialog.tsx | 32 ++++++------ .../user/components/MyAccountForm.tsx | 2 +- .../src/features/user/hooks/useApiTokens.ts | 23 ++------- .../user/queries/createApiTokenQuery.ts | 14 ------ .../user/queries/deleteApiTokenQuery.ts | 14 ------ .../features/user/server/createApiToken.ts | 45 +++++++++++++++++ .../features/user/server/deleteApiToken.ts | 50 +++++++++++++++++++ .../src/features/user/server/listApiTokens.ts | 37 ++++++++++++++ .../src/features/user/server/routers.ts | 6 +++ apps/builder/src/features/user/types.ts | 5 -- .../pages/api/users/[userId]/api-tokens.ts | 39 --------------- .../users/[userId]/api-tokens/[tokenId].ts | 20 -------- 14 files changed, 181 insertions(+), 149 deletions(-) delete mode 100644 apps/builder/src/features/user/queries/createApiTokenQuery.ts delete mode 100644 apps/builder/src/features/user/queries/deleteApiTokenQuery.ts create mode 100644 apps/builder/src/features/user/server/createApiToken.ts create mode 100644 apps/builder/src/features/user/server/deleteApiToken.ts create mode 100644 apps/builder/src/features/user/server/listApiTokens.ts delete mode 100644 apps/builder/src/features/user/types.ts delete mode 100644 apps/builder/src/pages/api/users/[userId]/api-tokens.ts delete mode 100644 apps/builder/src/pages/api/users/[userId]/api-tokens/[tokenId].ts diff --git a/apps/builder/src/components/TimeSince.tsx b/apps/builder/src/components/TimeSince.tsx index 4939af456..63a6c12c9 100644 --- a/apps/builder/src/components/TimeSince.tsx +++ b/apps/builder/src/components/TimeSince.tsx @@ -1,11 +1,14 @@ import { T } from "@tolgee/react"; type Props = { - date: string; + date: string | Date; }; export const TimeSince = ({ date }: Props) => { - const seconds = Math.floor((Date.now() - new Date(date).getTime()) / 1000); + const seconds = Math.floor( + (Date.now() - (date instanceof Date ? date : new Date(date)).getTime()) / + 1000, + ); let interval = seconds / 31536000; diff --git a/apps/builder/src/features/user/components/ApiTokensList.tsx b/apps/builder/src/features/user/components/ApiTokensList.tsx index 230f131a9..af3f3e7d9 100644 --- a/apps/builder/src/features/user/components/ApiTokensList.tsx +++ b/apps/builder/src/features/user/components/ApiTokensList.tsx @@ -1,3 +1,4 @@ +import { useMutation } from "@tanstack/react-query"; import { T, useTranslate } from "@tolgee/react"; import { byId, isDefined } from "@typebot.io/lib/utils"; import { Button } from "@typebot.io/ui/components/Button"; @@ -5,22 +6,17 @@ import { Checkbox } from "@typebot.io/ui/components/Checkbox"; import { Skeleton } from "@typebot.io/ui/components/Skeleton"; import { Table } from "@typebot.io/ui/components/Table"; import { useOpenControls } from "@typebot.io/ui/hooks/useOpenControls"; -import type { ClientUser } from "@typebot.io/user/schemas"; import { useState } from "react"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import { TimeSince } from "@/components/TimeSince"; +import { trpc } from "@/lib/queryClient"; import { toast } from "@/lib/toast"; import { useApiTokens } from "../hooks/useApiTokens"; -import { deleteApiTokenQuery } from "../queries/deleteApiTokenQuery"; -import type { ApiTokenFromServer } from "../types"; import { CreateApiTokenDialog } from "./CreateApiTokenDialog"; -type Props = { user: ClientUser }; - -export const ApiTokensList = ({ user }: Props) => { +export const ApiTokensList = () => { const { t } = useTranslate(); - const { apiTokens, isLoading, mutate } = useApiTokens({ - userId: user.id, + const { apiTokens, isLoading, refetch } = useApiTokens({ onError: (e) => toast({ title: "Failed to fetch tokens", @@ -34,16 +30,17 @@ export const ApiTokensList = ({ user }: Props) => { } = useOpenControls(); const [deletingId, setDeletingId] = useState(); - const refreshListWithNewToken = (token: ApiTokenFromServer) => { - if (!apiTokens) return; - mutate({ apiTokens: [token, ...apiTokens] }); - }; + const { mutate: deleteToken } = useMutation( + trpc.user.deleteApiToken.mutationOptions({ + onSuccess: () => { + refetch(); + setDeletingId(undefined); + }, + }), + ); - const deleteToken = async (tokenId?: string) => { - if (!apiTokens || !tokenId) return; - const { error } = await deleteApiTokenQuery({ userId: user.id, tokenId }); - if (!error) - mutate({ apiTokens: apiTokens.filter((t) => t.id !== tokenId) }); + const handleTokenCreated = () => { + refetch(); }; return ( @@ -55,9 +52,8 @@ export const ApiTokensList = ({ user }: Props) => { {t("account.apiTokens.createButton.label")} @@ -107,7 +103,7 @@ export const ApiTokensList = ({ user }: Props) => { deleteToken(deletingId)} + onConfirm={() => deletingId && deleteToken({ tokenId: deletingId })} onClose={() => setDeletingId(undefined)} actionType="destructive" confirmButtonLabel={t("account.apiTokens.deleteButton.label")} diff --git a/apps/builder/src/features/user/components/CreateApiTokenDialog.tsx b/apps/builder/src/features/user/components/CreateApiTokenDialog.tsx index 3610b6000..bb13ddc4c 100644 --- a/apps/builder/src/features/user/components/CreateApiTokenDialog.tsx +++ b/apps/builder/src/features/user/components/CreateApiTokenDialog.tsx @@ -1,3 +1,4 @@ +import { useMutation } from "@tanstack/react-query"; import { useTranslate } from "@tolgee/react"; import { Button } from "@typebot.io/ui/components/Button"; import { Dialog } from "@typebot.io/ui/components/Dialog"; @@ -5,20 +6,17 @@ import { Input } from "@typebot.io/ui/components/Input"; import type { FormEvent } from "react"; import { useRef, useState } from "react"; import { CopyInput } from "@/components/inputs/CopyInput"; -import { createApiTokenQuery } from "../queries/createApiTokenQuery"; -import type { ApiTokenFromServer } from "../types"; +import { trpc } from "@/lib/queryClient"; type Props = { - userId: string; isOpen: boolean; - onNewToken: (token: ApiTokenFromServer) => void; + onNewToken: () => void; onClose: () => void; }; const ANIMATION_DURATION = 150; export const CreateApiTokenDialog = ({ - userId, isOpen, onClose, onNewToken, @@ -26,18 +24,20 @@ export const CreateApiTokenDialog = ({ const inputRef = useRef(null); const { t } = useTranslate(); const [name, setName] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); const [newTokenValue, setNewTokenValue] = useState(); - const createToken = async (e: FormEvent) => { + const { mutate: createToken, isPending: isSubmitting } = useMutation( + trpc.user.createApiToken.mutationOptions({ + onSuccess: (data) => { + setNewTokenValue(data.apiToken.token); + onNewToken(); + }, + }), + ); + + const handleSubmit = (e: FormEvent) => { e.preventDefault(); - setIsSubmitting(true); - const { data } = await createApiTokenQuery(userId, { name }); - if (data?.apiToken) { - setNewTokenValue(data.apiToken.token); - onNewToken(data.apiToken); - } - setIsSubmitting(false); + createToken({ name }); }; const handleClose = () => { @@ -51,7 +51,7 @@ export const CreateApiTokenDialog = ({ return ( } + render={
} initialFocus={inputRef} > @@ -87,7 +87,7 @@ export const CreateApiTokenDialog = ({ {newTokenValue ? null : (