sync me
This commit is contained in:
parent
1b35c937c1
commit
58edc3040e
@ -1,12 +1,20 @@
|
||||
"use client"
|
||||
import { cookies } from "next/headers"
|
||||
import { AppSidebar } from "./sidebar"
|
||||
import { SiteHeader } from "./site-header"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const isLoggedIn = (await cookies()).get('is_logged_in')?.value === 'true'
|
||||
|
||||
if (!isLoggedIn) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<SidebarProvider
|
||||
style={
|
||||
|
||||
@ -1,6 +1,25 @@
|
||||
"use client"
|
||||
// app/dashboard/page.tsx
|
||||
import { redirect } from "next/navigation"
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { useUser } from "../user-context";
|
||||
|
||||
export default function Dashboard() {
|
||||
redirect("/admin/dashboard")
|
||||
|
||||
const { isAuthenticated, isLoading, user } = useUser()
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
if (!isAuthenticated) {
|
||||
router.push('/login');
|
||||
console.log(user?.role)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [isAuthenticated, isLoading, router, user]);
|
||||
|
||||
return (<></>)
|
||||
|
||||
}
|
||||
|
||||
@ -307,10 +307,10 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-32">
|
||||
<DropdownMenuContent align="end" className="w-42">
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||
<DropdownMenuItem>Reset Password</DropdownMenuItem>
|
||||
<DropdownMenuItem>Ban User</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@ -358,39 +358,62 @@ const GET_USERS = gql`
|
||||
`
|
||||
|
||||
const USERS_INFO = gql`
|
||||
query UsersInfo {
|
||||
usersInfo {
|
||||
query UsersInfo($offset: Int, $limit: Int) {
|
||||
usersInfo(offset: $offset, limit: $limit) {
|
||||
totalUsers
|
||||
totalActiveUsers
|
||||
totalAdminUsers
|
||||
totalUserUsers
|
||||
users {
|
||||
id
|
||||
username
|
||||
email
|
||||
role
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export function UserTable() {
|
||||
|
||||
const { data, loading, error, refetch } = useQuery(GET_USERS, {
|
||||
variables: {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
},
|
||||
})
|
||||
const { data, loading, error: usersInfoError, refetch } = useQuery(USERS_INFO)
|
||||
|
||||
const [localData, setLocalData] = React.useState<any[]>([])
|
||||
|
||||
// 同步外部数据到本地状态
|
||||
React.useEffect(() => {
|
||||
if (data && Array.isArray(data.usersInfo.users)) {
|
||||
setLocalData(data.usersInfo.users)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event
|
||||
if (active && over && active.id !== over.id) {
|
||||
setLocalData((currentData) => {
|
||||
const oldIndex = currentData.findIndex((item) => item.id === active.id)
|
||||
const newIndex = currentData.findIndex((item) => item.id === over.id)
|
||||
|
||||
if (oldIndex === -1 || newIndex === -1) return currentData
|
||||
|
||||
// 只做本地排序,不保存到数据库
|
||||
return arrayMove(currentData, oldIndex, newIndex)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const { data: usersInfo, loading: usersInfoLoading, error: usersInfoError } = useQuery(USERS_INFO)
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <div>Loading...</div>}
|
||||
{error && <div>Error: {error.message}</div>}
|
||||
{data && <Tabel data={data} info={usersInfo} refetch={refetch} />}
|
||||
<Tabel data={localData} refetch={refetch} info={data?.usersInfo} isLoading={loading} handleDragEnd={handleDragEnd} />
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any }) {
|
||||
function Tabel({ data, refetch, info, isLoading, handleDragEnd }: { data: any, refetch: any, info: any, isLoading: boolean, handleDragEnd: (event: DragEndEvent) => void }) {
|
||||
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
@ -410,15 +433,9 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
useSensor(KeyboardSensor, {})
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
refetch({
|
||||
offset: pagination.pageIndex * pagination.pageSize,
|
||||
limit: pagination.pageSize,
|
||||
})
|
||||
}, [pagination, refetch])
|
||||
|
||||
const table = useReactTable({
|
||||
data: data?.users || [],
|
||||
data: data || [],
|
||||
columns,
|
||||
state: {
|
||||
sorting,
|
||||
@ -443,16 +460,7 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
manualPagination: true,
|
||||
})
|
||||
|
||||
// function handleDragEnd(event: DragEndEvent) {
|
||||
// const { active, over } = event
|
||||
// if (active && over && active.id !== over.id) {
|
||||
// setData((data) => {
|
||||
// const oldIndex = dataIds.indexOf(active.id)
|
||||
// const newIndex = dataIds.indexOf(over.id)
|
||||
// return arrayMove(data, oldIndex, newIndex)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
@ -467,7 +475,7 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
</DialogContent>
|
||||
|
||||
<Tabs
|
||||
defaultValue="outline"
|
||||
defaultValue="all_users"
|
||||
className="w-full flex-col justify-start gap-6"
|
||||
>
|
||||
<div className="flex items-center justify-between px-4 lg:px-6">
|
||||
@ -483,21 +491,21 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
<SelectValue placeholder="Select a view" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="outline">All Users</SelectItem>
|
||||
<SelectItem value="past-performance">Active Users</SelectItem>
|
||||
<SelectItem value="key-personnel">Administrators</SelectItem>
|
||||
<SelectItem value="focus-documents">Inactive Users</SelectItem>
|
||||
<SelectItem value="all_users">All Users</SelectItem>
|
||||
<SelectItem value="active_users">Active Users</SelectItem>
|
||||
<SelectItem value="admin_users">Administrators</SelectItem>
|
||||
<SelectItem value="inactive_users">Inactive Users</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||
<TabsTrigger value="outline">All Users</TabsTrigger>
|
||||
<TabsTrigger value="past-performance">
|
||||
Active Users <Badge variant="secondary">{info?.usersInfo?.totalActiveUsers}</Badge>
|
||||
<TabsTrigger value="all_users">All Users</TabsTrigger>
|
||||
<TabsTrigger value="active_users">
|
||||
Active Users <Badge variant="secondary">{info?.totalActiveUsers}</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="key-personnel">
|
||||
Administrators <Badge variant="secondary">{info?.usersInfo?.totalAdminUsers}</Badge>
|
||||
<TabsTrigger value="admin_users">
|
||||
Administrators <Badge variant="secondary">{info?.totalAdminUsers}</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="focus-documents">Inactive Users</TabsTrigger>
|
||||
<TabsTrigger value="inactive_users">Inactive Users</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
@ -543,14 +551,14 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
</div>
|
||||
</div>
|
||||
<TabsContent
|
||||
value="outline"
|
||||
value="all_users"
|
||||
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
||||
>
|
||||
<div className="overflow-hidden rounded-lg border">
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
modifiers={[restrictToVerticalAxis]}
|
||||
// onDragEnd={handleDragEnd}
|
||||
onDragEnd={handleDragEnd}
|
||||
sensors={sensors}
|
||||
id={sortableId}
|
||||
>
|
||||
@ -574,9 +582,15 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody className="**:data-[slot=table-cell]:first:w-8">
|
||||
{table.getRowModel().rows?.length ? (
|
||||
{isLoading ? (<>
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 w-full flex items-center justify-center">
|
||||
<IconLoader className="size-4 animate-spin" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>) : table.getRowModel().rows?.length ? (
|
||||
<SortableContext
|
||||
items={data?.users.map((user: any) => user.id) || []}
|
||||
items={data?.map((user: any) => user.id) || []}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
@ -676,16 +690,16 @@ function Tabel({ data, refetch, info }: { data: any, refetch: any, info: any })
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="past-performance"
|
||||
value="active_users"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||
<TabsContent value="admin_users" className="flex flex-col px-4 lg:px-6">
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value="focus-documents"
|
||||
value="inactive_users"
|
||||
className="flex flex-col px-4 lg:px-6"
|
||||
>
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
|
||||
19
app/login/layout.tsx
Normal file
19
app/login/layout.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { cookies } from "next/headers"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const isLoggedIn = (await cookies()).get('is_logged_in')?.value === 'true'
|
||||
|
||||
if (isLoggedIn) {
|
||||
redirect('/')
|
||||
}
|
||||
|
||||
return (
|
||||
<>{children}</>
|
||||
)
|
||||
}
|
||||
@ -17,35 +17,35 @@ export default function LoginPage() {
|
||||
}
|
||||
}, [isAuthenticated, isLoading, router]);
|
||||
|
||||
// 如果正在加载或已认证,显示加载状态
|
||||
if (isLoading || isAuthenticated) {
|
||||
return (
|
||||
<div className="grid min-h-svh lg:grid-cols-2">
|
||||
<div className="flex flex-col gap-4 p-6 md:p-10">
|
||||
<div className="flex justify-center gap-2 md:justify-start">
|
||||
<a href="#" className="flex items-center gap-2 font-medium">
|
||||
<div className="bg-primary text-primary-foreground flex size-6 items-center justify-center rounded-md">
|
||||
<GalleryVerticalEnd className="size-4" />
|
||||
</div>
|
||||
Acme Inc.
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<div className="w-full max-w-xs text-center">
|
||||
<p className="text-muted-foreground">正在跳转...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted relative hidden lg:block">
|
||||
<img
|
||||
src="/placeholder.svg"
|
||||
alt="Image"
|
||||
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// // 如果正在加载或已认证,显示加载状态
|
||||
// if (isLoading || isAuthenticated) {
|
||||
// return (
|
||||
// <div className="grid min-h-svh lg:grid-cols-2">
|
||||
// <div className="flex flex-col gap-4 p-6 md:p-10">
|
||||
// <div className="flex justify-center gap-2 md:justify-start">
|
||||
// <a href="#" className="flex items-center gap-2 font-medium">
|
||||
// <div className="bg-primary text-primary-foreground flex size-6 items-center justify-center rounded-md">
|
||||
// <GalleryVerticalEnd className="size-4" />
|
||||
// </div>
|
||||
// Acme Inc.
|
||||
// </a>
|
||||
// </div>
|
||||
// <div className="flex flex-1 items-center justify-center">
|
||||
// <div className="w-full max-w-xs text-center">
|
||||
// <p className="text-muted-foreground">正在跳转...</p>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className="bg-muted relative hidden lg:block">
|
||||
// <img
|
||||
// src="/placeholder.svg"
|
||||
// alt="Image"
|
||||
// className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="grid min-h-svh lg:grid-cols-2">
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
"use client"
|
||||
import { cookies } from "next/headers"
|
||||
import { AppSidebar } from "./sidebar"
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
export default async function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const isLoggedIn = (await cookies()).get('is_logged_in')?.value === 'true'
|
||||
|
||||
console.log(isLoggedIn)
|
||||
|
||||
if (!isLoggedIn) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<SidebarProvider
|
||||
style={
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { redirect } from "next/navigation"
|
||||
import { redirect, useRouter } from "next/navigation"
|
||||
import { useUser } from "../user-context"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export default function MePage() {
|
||||
const { isAuthenticated, user } = useUser()
|
||||
const { isAuthenticated, isLoading, user } = useUser()
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
redirect("/login")
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
router.push('/login');
|
||||
}
|
||||
}, [isAuthenticated])
|
||||
}, [isAuthenticated, isLoading, router, user]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@ -188,6 +188,8 @@ export function UserProvider({ children }: UserProviderProps) {
|
||||
isLoading: false
|
||||
})
|
||||
|
||||
document.cookie = 'is_logged_in=true; path=/; max-age=3600'
|
||||
|
||||
// 登录成功后重置 Apollo 缓存
|
||||
resetApolloCache()
|
||||
} catch (error) {
|
||||
@ -261,6 +263,8 @@ export function UserProvider({ children }: UserProviderProps) {
|
||||
isLoading: false
|
||||
})
|
||||
|
||||
document.cookie = 'is_logged_in=false; path=/; max-age=3600'
|
||||
|
||||
// 登出后重置 Apollo 缓存
|
||||
resetApolloCache()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user