refactor: improve token fetch experience and refactor spinners
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -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:
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">取消</Button>
|
||||
</DialogClose>
|
||||
<Button onClick={onJoinEvent} disabled={isPending}>{isPending ? <Loader2 className="animate-spin" /> : '加入'}</Button>
|
||||
<Button onClick={onJoinEvent} disabled={isPending}>{isPending ? <Spinner /> : '加入'}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
|
||||
@@ -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 }) {
|
||||
<form.Subscribe
|
||||
selector={state => [state.canSubmit, state.isPristine, state.isSubmitting]}
|
||||
children={([canSubmit, isPristine, isSubmitting]) => (
|
||||
<Button type="submit" disabled={!canSubmit || isPristine || isSubmitting}>{isSubmitting ? <Loader2 className="animate-spin" /> : '开始认证'}</Button>
|
||||
<Button type="submit" disabled={!canSubmit || isPristine || isSubmitting}>{isSubmitting ? <Spinner /> : '开始认证'}</Button>
|
||||
)}
|
||||
/>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -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({
|
||||
</form.Field>
|
||||
<Field>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading && <Loader2 className="animate-spin" />}
|
||||
{isLoading && <Spinner />}
|
||||
{token === null ? '等待 Turnstile' : isPending ? '发送中...' : '发送登录链接'}
|
||||
</Button>
|
||||
</Field>
|
||||
|
||||
@@ -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]) => (
|
||||
<Button type="submit" disabled={!canSubmit}>
|
||||
{isSubmitting ? <Loader2 className="animate-spin" /> : '保存'}
|
||||
{isSubmitting ? <Spinner /> : '保存'}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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<void> }) {
|
||||
@@ -54,12 +55,12 @@ export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData
|
||||
{/* Bio */}
|
||||
{enableBioEdit
|
||||
? (
|
||||
<MDEditor
|
||||
value={bio}
|
||||
onChange={setBio}
|
||||
height="100%"
|
||||
/>
|
||||
)
|
||||
<MDEditor
|
||||
value={bio}
|
||||
onChange={setBio}
|
||||
height="100%"
|
||||
/>
|
||||
)
|
||||
: <div className="p-6 prose dark:prose-invert"><Markdown>{bio}</Markdown></div>}
|
||||
<Button
|
||||
className="absolute bottom-4 right-4"
|
||||
@@ -89,7 +90,7 @@ export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData
|
||||
variant={enableBioEdit ? 'default' : 'outline'}
|
||||
disabled={isSubmittingBio}
|
||||
>
|
||||
{isSubmittingBio ? <Loader2 className="animate-spin" /> : <Pencil />}
|
||||
{isSubmittingBio ? <Spinner /> : <Pencil />}
|
||||
</Button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
16
client/cms/src/components/ui/spinner.tsx
Normal file
16
client/cms/src/components/ui/spinner.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Loader2Icon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||
return (
|
||||
<Loader2Icon
|
||||
role="status"
|
||||
aria-label="Loading"
|
||||
className={cn("size-4 animate-spin", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Spinner }
|
||||
@@ -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 <div>{status}</div>;
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [code, mutate]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center">
|
||||
<Spinner className="size-8" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user