From 994f6867f568ba5ec5d5d68ce2ecbe134ab19f42 Mon Sep 17 00:00:00 2001 From: Chirag Chandrashekhar Date: Wed, 24 Jul 2024 09:01:56 +0530 Subject: [PATCH] fix: API token deletion not reflected in cache until page reload (#1128) Stops the API token copy card from continuing to appear when a newly created token has been subsequently deleted. --- .../app/(dashboard)/settings/tokens/page.tsx | 2 +- .../t/[teamUrl]/settings/tokens/page.tsx | 4 +- apps/web/src/components/forms/token.tsx | 71 +++++++++++++------ 3 files changed, 51 insertions(+), 26 deletions(-) diff --git a/apps/web/src/app/(dashboard)/settings/tokens/page.tsx b/apps/web/src/app/(dashboard)/settings/tokens/page.tsx index cc062f220..a04894f27 100644 --- a/apps/web/src/app/(dashboard)/settings/tokens/page.tsx +++ b/apps/web/src/app/(dashboard)/settings/tokens/page.tsx @@ -32,7 +32,7 @@ export default async function ApiTokensPage() {
- +
diff --git a/apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx b/apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx index 7602ac70f..135a05e85 100644 --- a/apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx +++ b/apps/web/src/app/(teams)/t/[teamUrl]/settings/tokens/page.tsx @@ -26,7 +26,7 @@ export default async function ApiTokensPage({ params }: ApiTokensPageProps) { const team = await getTeamByUrl({ userId: user.id, teamUrl }); - let tokens: GetTeamTokensResponse | null = null; + let tokens: GetTeamTokensResponse | undefined = undefined; try { tokens = await getTeamTokens({ userId: user.id, teamId: team.id }); @@ -63,7 +63,7 @@ export default async function ApiTokensPage({ params }: ApiTokensPageProps) {
- +
diff --git a/apps/web/src/components/forms/token.tsx b/apps/web/src/components/forms/token.tsx index 7081ac3c9..40a6c9199 100644 --- a/apps/web/src/components/forms/token.tsx +++ b/apps/web/src/components/forms/token.tsx @@ -1,14 +1,16 @@ 'use client'; -import { useState } from 'react'; +import { useState, useTransition } from 'react'; import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; +import { AnimatePresence, motion } from 'framer-motion'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard'; +import type { ApiToken } from '@documenso/prisma/client'; import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; import type { TCreateTokenMutationSchema } from '@documenso/trpc/server/api-token-router/schema'; @@ -44,23 +46,37 @@ const ZCreateTokenFormSchema = ZCreateTokenMutationSchema.extend({ type TCreateTokenFormSchema = z.infer; +type NewlyCreatedToken = { + id: number; + token: string; +}; + export type ApiTokenFormProps = { className?: string; teamId?: number; + tokens?: Pick[]; }; -export const ApiTokenForm = ({ className, teamId }: ApiTokenFormProps) => { +export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) => { const router = useRouter(); + const [isTransitionPending, startTransition] = useTransition(); const [, copy] = useCopyToClipboard(); const { toast } = useToast(); - const [newlyCreatedToken, setNewlyCreatedToken] = useState(''); + const [newlyCreatedToken, setNewlyCreatedToken] = useState(); const [noExpirationDate, setNoExpirationDate] = useState(false); + // This lets us hide the token from being copied if it has been deleted without + // resorting to a useEffect or any other fanciness. This comes at the cost of it + // taking slighly longer to appear since it will need to wait for the router.refresh() + // to finish updating. + const hasNewlyCreatedToken = + tokens?.find((token) => token.id === newlyCreatedToken?.id) !== undefined; + const { mutateAsync: createTokenMutation } = trpc.apiToken.createToken.useMutation({ onSuccess(data) { - setNewlyCreatedToken(data.token); + setNewlyCreatedToken(data); }, }); @@ -110,7 +126,7 @@ export const ApiTokenForm = ({ className, teamId }: ApiTokenFormProps) => { form.reset(); - router.refresh(); + startTransition(() => router.refresh()); } catch (error) { if (error instanceof TRPCClientError && error.data?.code === 'BAD_REQUEST') { toast({ @@ -216,7 +232,7 @@ export const ApiTokenForm = ({ className, teamId }: ApiTokenFormProps) => { type="submit" className="hidden md:inline-flex" disabled={!form.formState.isDirty} - loading={form.formState.isSubmitting} + loading={form.formState.isSubmitting || isTransitionPending} > Create token @@ -225,7 +241,7 @@ export const ApiTokenForm = ({ className, teamId }: ApiTokenFormProps) => { @@ -234,24 +250,33 @@ export const ApiTokenForm = ({ className, teamId }: ApiTokenFormProps) => { - {newlyCreatedToken && ( - - -

- Your token was created successfully! Make sure to copy it because you won't be able to - see it again! -

+ + {newlyCreatedToken && hasNewlyCreatedToken && ( + + + +

+ Your token was created successfully! Make sure to copy it because you won't be + able to see it again! +

-

- {newlyCreatedToken} -

+

+ {newlyCreatedToken.token} +

- -
-
- )} + +
+
+ + )} + ); };