feat(client): user info

Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
2025-12-25 02:50:23 +08:00
committed by Asai Neko
parent 9ac598cd98
commit be3d778420
5 changed files with 37 additions and 18 deletions

View File

@@ -63,7 +63,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<NavSecondary items={data.navSecondary} className="mt-auto" /> <NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>
<NavUser user={data.user} /> <NavUser />
</SidebarFooter> </SidebarFooter>
</Sidebar> </Sidebar>
); );

View File

@@ -22,17 +22,11 @@ import {
SidebarMenuItem, SidebarMenuItem,
useSidebar, useSidebar,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import { useUserInfo } from '@/hooks/data/useUserInfo';
export function NavUser({ export function NavUser() {
user,
}: {
user: {
name: string;
email: string;
avatar: string;
};
}) {
const { isMobile } = useSidebar(); const { isMobile } = useSidebar();
const { data: user } = useUserInfo();
return ( return (
<SidebarMenu> <SidebarMenu>
@@ -44,11 +38,11 @@ export function NavUser({
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
> >
<Avatar className="h-8 w-8 rounded-lg grayscale"> <Avatar className="h-8 w-8 rounded-lg grayscale">
<AvatarImage src={user.avatar} alt={user.name} /> <AvatarImage src={user.avatar} alt={user.nickname} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback> <AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar> </Avatar>
<div className="grid flex-1 text-left text-sm leading-tight"> <div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span> <span className="truncate font-medium">{user.nickname}</span>
<span className="text-muted-foreground truncate text-xs"> <span className="text-muted-foreground truncate text-xs">
{user.email} {user.email}
</span> </span>
@@ -65,11 +59,11 @@ export function NavUser({
<DropdownMenuLabel className="p-0 font-normal"> <DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg"> <Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.name} /> <AvatarImage src={user.avatar} alt={user.nickname} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback> <AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar> </Avatar>
<div className="grid flex-1 text-left text-sm leading-tight"> <div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.name}</span> <span className="truncate font-medium">{user.nickname}</span>
<span className="text-muted-foreground truncate text-xs"> <span className="text-muted-foreground truncate text-xs">
{user.email} {user.email}
</span> </span>

View File

@@ -1,17 +1,22 @@
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardAction, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { useCheckin } from '@/hooks/data/useCheckin'; import { useCheckin } from '@/hooks/data/useCheckin';
import { useUserInfo } from '@/hooks/data/useUserInfo';
export function CheckinCard() { export function CheckinCard() {
const { mutateAsync, isPending } = useCheckin(); const { mutateAsync, isPending } = useCheckin();
const { data } = useUserInfo();
const queryClient = useQueryClient();
return ( return (
<Card className="@container/card"> <Card className="@container/card">
<CardHeader> <CardHeader>
<CardDescription></CardDescription> <CardDescription></CardDescription>
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl"> <CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
{data.checkin !== null ? '已签到' : '未签到'}
{data.checkin !== null && <span className="text-sm font-medium ml-2">{`${new Date(data.checkin).toLocaleString()}`}</span>}
</CardTitle> </CardTitle>
<CardAction> <CardAction>
<Badge variant="outline">Day 1</Badge> <Badge variant="outline">Day 1</Badge>
@@ -23,12 +28,13 @@ export function CheckinCard() {
onClick={() => { onClick={() => {
mutateAsync().then(() => { mutateAsync().then(() => {
toast('签到成功'); toast('签到成功');
void queryClient.invalidateQueries({ queryKey: ['userInfo'] });
}).catch((error) => { }).catch((error) => {
console.error(error); console.error(error);
toast('签到失败'); toast('签到失败');
}); });
}} }}
disabled={isPending} disabled={isPending || data.checkin !== null}
> >
</Button> </Button>

View File

@@ -0,0 +1,21 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import { axiosClient } from '@/lib/axios';
export function useUserInfo() {
return useSuspenseQuery({
queryKey: ['userInfo'],
queryFn: async () => {
const response = await axiosClient.get<{
user_id: string;
email: string;
type: string;
nickname: string;
subtitle: string;
avatar: string;
checkin: string | null;
}
>('/user/info');
return response.data;
},
});
}

View File

@@ -1,6 +1,5 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createRootRoute, Outlet } from '@tanstack/react-router'; import { createRootRoute, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
import { ThemeProvider } from '@/components/theme-provider'; import { ThemeProvider } from '@/components/theme-provider';
import { Toaster } from '@/components/ui/sonner'; import { Toaster } from '@/components/ui/sonner';
import '@/index.css'; import '@/index.css';
@@ -15,7 +14,6 @@ function RootLayout() {
<Outlet /> <Outlet />
</QueryClientProvider> </QueryClientProvider>
</ThemeProvider> </ThemeProvider>
<TanStackRouterDevtools />
<Toaster position="top-right" /> <Toaster position="top-right" />
</> </>
); );