From 9f511c06824c5789ac388b440aa377d52d9585ce Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Fri, 13 Feb 2026 12:46:12 +0800 Subject: [PATCH] refactor: improve token fetch experience and refactor spinners Signed-off-by: Noa Virellia --- .../events/event-join.dialog.view.tsx | 4 +-- .../kyc/kyc-method-selection.dialog.view.tsx | 4 +-- client/cms/src/components/login-form.tsx | 4 +-- .../profile/edit-profile.dialog.view.tsx | 4 +-- .../src/components/profile/profile.view.tsx | 17 ++++++----- client/cms/src/components/ui/spinner.tsx | 16 ++++++++++ client/cms/src/routes/token.tsx | 30 +++++++++++-------- 7 files changed, 51 insertions(+), 28 deletions(-) create mode 100644 client/cms/src/components/ui/spinner.tsx diff --git a/client/cms/src/components/events/event-join.dialog.view.tsx b/client/cms/src/components/events/event-join.dialog.view.tsx index 27bf33e..2803859 100644 --- a/client/cms/src/components/events/event-join.dialog.view.tsx +++ b/client/cms/src/components/events/event-join.dialog.view.tsx @@ -1,7 +1,7 @@ import type { EventInfo } from './types'; -import { Loader2 } from 'lucide-react'; import { Button } from '../ui/button'; import { DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog'; +import { Spinner } from '../ui/spinner'; export function EventJoinDialogView({ event, onJoinEvent, isPending }: { event: EventInfo; onJoinEvent: () => void; isPending: boolean }) { return ( @@ -19,7 +19,7 @@ export function EventJoinDialogView({ event, onJoinEvent, isPending }: { event: - + ); diff --git a/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx b/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx index 19f23d6..05bc529 100644 --- a/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx +++ b/client/cms/src/components/events/kyc/kyc-method-selection.dialog.view.tsx @@ -1,6 +1,5 @@ import type { KycSubmission } from './kyc.types'; import { useForm } from '@tanstack/react-form'; -import { Loader2 } from 'lucide-react'; import { useState } from 'react'; import { toast } from 'sonner'; import z from 'zod'; @@ -12,6 +11,7 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Spinner } from '@/components/ui/spinner'; import { Button } from '../../ui/button'; import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../../ui/dialog'; @@ -81,7 +81,7 @@ function CnridForm({ onSubmit }: { onSubmit: OnSubmit }) { [state.canSubmit, state.isPristine, state.isSubmitting]} children={([canSubmit, isPristine, isSubmitting]) => ( - + )} /> diff --git a/client/cms/src/components/login-form.tsx b/client/cms/src/components/login-form.tsx index d03042f..b28f957 100644 --- a/client/cms/src/components/login-form.tsx +++ b/client/cms/src/components/login-form.tsx @@ -3,7 +3,6 @@ import type { AuthorizeSearchParams } from '@/routes/authorize'; import { Turnstile } from '@marsidev/react-turnstile'; import { useForm } from '@tanstack/react-form'; import { useNavigate } from '@tanstack/react-router'; -import { Loader2 } from 'lucide-react'; import { useRef, useState } from 'react'; import { toast } from 'sonner'; import z from 'zod'; @@ -18,6 +17,7 @@ import { import { Input } from '@/components/ui/input'; import { useGetMagicLink } from '@/hooks/data/useGetMagicLink'; import { cn } from '@/lib/utils'; +import { Spinner } from './ui/spinner'; export function LoginForm({ oauthParams, @@ -91,7 +91,7 @@ export function LoginForm({ diff --git a/client/cms/src/components/profile/edit-profile.dialog.view.tsx b/client/cms/src/components/profile/edit-profile.dialog.view.tsx index 884800d..f6f8718 100644 --- a/client/cms/src/components/profile/edit-profile.dialog.view.tsx +++ b/client/cms/src/components/profile/edit-profile.dialog.view.tsx @@ -1,6 +1,5 @@ import type { ServiceUserUserInfoData } from '@/client'; import { useForm } from '@tanstack/react-form'; -import { Loader2 } from 'lucide-react'; import { useEffect, useState, @@ -25,6 +24,7 @@ import { import { Input, } from '@/components/ui/input'; +import { Spinner } from '../ui/spinner'; import { Switch } from '../ui/switch'; const formSchema = z.object({ @@ -168,7 +168,7 @@ export function EditProfileDialogView({ user, updateProfile }: { user: ServiceUs selector={state => [state.canSubmit, state.isSubmitting]} children={([canSubmit, isSubmitting]) => ( )} /> diff --git a/client/cms/src/components/profile/profile.view.tsx b/client/cms/src/components/profile/profile.view.tsx index 7cd5438..295957d 100644 --- a/client/cms/src/components/profile/profile.view.tsx +++ b/client/cms/src/components/profile/profile.view.tsx @@ -6,13 +6,14 @@ import { isEmpty, isNil, } from 'lodash-es'; -import { Loader2, Mail, Pencil } from 'lucide-react'; +import { Mail, Pencil } from 'lucide-react'; import { useMemo, useState } from 'react'; import Markdown from 'react-markdown'; import { toast } from 'sonner'; import { Avatar, AvatarImage } from '@/components/ui/avatar'; import { base64ToUtf8 } from '@/lib/utils'; import { Button } from '../ui/button'; +import { Spinner } from '../ui/spinner'; import { EditProfileDialogContainer } from './edit-profile.dialog.container'; export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData; onSaveBio: (bio: string) => Promise }) { @@ -54,12 +55,12 @@ export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData {/* Bio */} {enableBioEdit ? ( - - ) + + ) :
{bio}
} diff --git a/client/cms/src/components/ui/spinner.tsx b/client/cms/src/components/ui/spinner.tsx new file mode 100644 index 0000000..a70e713 --- /dev/null +++ b/client/cms/src/components/ui/spinner.tsx @@ -0,0 +1,16 @@ +import { Loader2Icon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Spinner({ className, ...props }: React.ComponentProps<"svg">) { + return ( + + ) +} + +export { Spinner } diff --git a/client/cms/src/routes/token.tsx b/client/cms/src/routes/token.tsx index e619c33..9d418cf 100644 --- a/client/cms/src/routes/token.tsx +++ b/client/cms/src/routes/token.tsx @@ -2,10 +2,10 @@ import { useMutation } from '@tanstack/react-query'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { useEffect, - useState, } from 'react'; import z from 'zod'; import { postAuthTokenMutation } from '@/client/@tanstack/react-query.gen'; +import { Spinner } from '@/components/ui/spinner'; import { setAccessToken, setRefreshToken } from '@/lib/token'; const tokenCodeSchema = z.object({ @@ -19,27 +19,33 @@ export const Route = createFileRoute('/token')({ function RouteComponent() { const { code } = Route.useSearch(); - const [status, setStatus] = useState('Loading...'); const navigate = useNavigate(); - const mutation = useMutation({ + const { mutate } = useMutation({ ...postAuthTokenMutation(), onSuccess: (data) => { setAccessToken(data.data!.access_token!); setRefreshToken(data.data!.refresh_token!); void navigate({ to: '/' }); }, - onError: () => { - setStatus('Error getting token'); - }, + throwOnError: true, }); useEffect(() => { - if (mutation.isIdle) { - mutation.mutate({ body: { code } }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const controller = new AbortController(); + mutate({ + body: { code }, + signal: controller.signal, + }); - return
{status}
; + return () => { + controller.abort(); + }; + }, [code, mutate]); + + return ( +
+ +
+ ); }