forked from nixcn/nixcn-cms
feat(client): user info
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
21
client/src/hooks/data/useUserInfo.ts
Normal file
21
client/src/hooks/data/useUserInfo.ts
Normal 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;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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" />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user