feat: add admin managements
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
|
||||
|
||||
export const metadata = constructMetadata({
|
||||
title: "Orders – WR.DO",
|
||||
description: "Check and manage your latest orders.",
|
||||
});
|
||||
|
||||
export default async function OrdersPage() {
|
||||
// const user = await getCurrentUser();
|
||||
// if (!user || user.role !== "ADMIN") redirect("/login");
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Orders"
|
||||
text="Check and manage your latest orders."
|
||||
/>
|
||||
<EmptyPlaceholder>
|
||||
<EmptyPlaceholder.Icon name="package" />
|
||||
<EmptyPlaceholder.Title>No orders listed</EmptyPlaceholder.Title>
|
||||
<EmptyPlaceholder.Description>
|
||||
You don't have any orders yet. Start ordering a product.
|
||||
</EmptyPlaceholder.Description>
|
||||
<Button>Buy Products</Button>
|
||||
</EmptyPlaceholder>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { getUserRecordCount } from "@/lib/dto/cloudflare-dns-record";
|
||||
import { getUserShortUrlCount } from "@/lib/dto/short-urls";
|
||||
import { getAllUsersCount } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardInfoCard } from "@/components/dashboard/dashboard-info-card";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
import InfoCard from "@/components/dashboard/info-card";
|
||||
import TransactionsList from "@/components/dashboard/transactions-list";
|
||||
@@ -13,7 +18,11 @@ export const metadata = constructMetadata({
|
||||
|
||||
export default async function AdminPage() {
|
||||
const user = await getCurrentUser();
|
||||
if (!user || user.role !== "ADMIN") redirect("/login");
|
||||
if (!user || !user.id || user.role !== "ADMIN") redirect("/login");
|
||||
|
||||
const user_count = await getAllUsersCount();
|
||||
const record_count = await getUserRecordCount(user.id, 1, "ADMIN");
|
||||
const url_count = await getUserShortUrlCount(user.id, 1, "ADMIN");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -22,14 +31,27 @@ export default async function AdminPage() {
|
||||
text="Access only for users with ADMIN role."
|
||||
/>
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<InfoCard />
|
||||
<InfoCard />
|
||||
<InfoCard />
|
||||
<InfoCard />
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3 xl:grid-cols-3">
|
||||
<DashboardInfoCard
|
||||
userId={user.id}
|
||||
title="Users"
|
||||
count={user_count}
|
||||
link="/admin/users"
|
||||
/>
|
||||
<DashboardInfoCard
|
||||
userId={user.id}
|
||||
title="DNS Records"
|
||||
count={record_count}
|
||||
link="/admin/records"
|
||||
/>
|
||||
<DashboardInfoCard
|
||||
userId={user.id}
|
||||
title="Short URLs"
|
||||
count={url_count}
|
||||
link="/admin/urls"
|
||||
/>
|
||||
</div>
|
||||
<TransactionsList />
|
||||
<TransactionsList />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
+2
-5
@@ -1,13 +1,10 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
export default function OrdersLoading() {
|
||||
export default function DashboardRecordsLoading() {
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Orders"
|
||||
text="Check and manage your latest orders."
|
||||
/>
|
||||
<DashboardHeader heading="DNS Records" text="" />
|
||||
<Skeleton className="size-full rounded-lg" />
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
import UserRecordsList from "../../dashboard/records/record-list";
|
||||
|
||||
export const metadata = constructMetadata({
|
||||
title: "DNS Records - WR.DO",
|
||||
description: "List and manage records.",
|
||||
});
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const user = await getCurrentUser();
|
||||
|
||||
if (!user?.id) redirect("/login");
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Manage DNS Records"
|
||||
text="List and manage records."
|
||||
link="/docs/dns-records"
|
||||
linkText="DNS Records."
|
||||
/>
|
||||
<UserRecordsList
|
||||
user={{ id: user.id, name: user.name || "" }}
|
||||
action="/api/record/admin"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
export default function DashboardUrlsLoading() {
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader heading="Short Urls" text="" />
|
||||
<Skeleton className="size-full rounded-lg" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
|
||||
import UserUrlsList from "../../dashboard/urls/url-list";
|
||||
|
||||
export const metadata = constructMetadata({
|
||||
title: "Short URLs - WR.DO",
|
||||
description: "List and manage records.",
|
||||
});
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const user = await getCurrentUser();
|
||||
|
||||
if (!user?.id) redirect("/login");
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
heading="Manage Short URLs"
|
||||
text="List and manage short urls."
|
||||
link="/docs/short-urls"
|
||||
linkText="Short urls."
|
||||
/>
|
||||
<UserUrlsList
|
||||
user={{ id: user.id, name: user.name || "" }}
|
||||
action="/api/url/admin"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Pagination } from "@nextui-org/pagination";
|
||||
import { User } from "@prisma/client";
|
||||
import { PenLine, RefreshCwIcon } from "lucide-react";
|
||||
import { ArrowLeft, ArrowRight, PenLine, RefreshCwIcon } from "lucide-react";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { PaginationWrapper } from "@/components/ui/pagination";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
@@ -73,10 +75,12 @@ export default function UsersList({ user }: UrlListProps) {
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [isShowForm, setShowForm] = useState(false);
|
||||
const [currentEditUser, setcurrentEditUser] = useState<User | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const { mutate } = useSWRConfig();
|
||||
const { data, error, isLoading } = useSWR<User[]>(
|
||||
"/api/user/admin",
|
||||
const { data, error, isLoading } = useSWR<{ total: number; list: User[] }>(
|
||||
`/api/user/admin?page=${currentPage}&size=${pageSize}`,
|
||||
fetcher,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
@@ -84,21 +88,19 @@ export default function UsersList({ user }: UrlListProps) {
|
||||
);
|
||||
|
||||
const handleRefresh = () => {
|
||||
mutate("/api/user/admin", undefined);
|
||||
mutate(`/api/user/admin?page=${currentPage}&size=${pageSize}`, undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="xl:col-span-2">
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<div className="grid gap-2">
|
||||
<CardDescription className="text-balance text-lg font-bold">
|
||||
<span>Total Users:</span>{" "}
|
||||
<span className="font-bold">
|
||||
{data && <CountUpFn count={data.length} />}
|
||||
</span>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<CardDescription className="text-balance text-lg font-bold">
|
||||
<span>Total Users:</span>{" "}
|
||||
<span className="font-bold">
|
||||
{data && <CountUpFn count={data.total} />}
|
||||
</span>
|
||||
</CardDescription>
|
||||
<div className="ml-auto flex items-center justify-end gap-3">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
@@ -111,17 +113,6 @@ export default function UsersList({ user }: UrlListProps) {
|
||||
<RefreshCwIcon className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
{/* <Button
|
||||
className="w-[120px] shrink-0 gap-1"
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
setcurrentEditUser(null);
|
||||
setShowForm(false);
|
||||
setShowForm(!isShowForm);
|
||||
}}
|
||||
>
|
||||
Add user
|
||||
</Button> */}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -165,59 +156,53 @@ export default function UsersList({ user }: UrlListProps) {
|
||||
<TableColumnSekleton />
|
||||
<TableColumnSekleton />
|
||||
</>
|
||||
) : data && data.length > 0 ? (
|
||||
data
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.createdAt).getTime() -
|
||||
new Date(b.createdAt).getTime(),
|
||||
)
|
||||
.map((user) => (
|
||||
<TableRow
|
||||
key={user.id}
|
||||
className="grid animate-fade-in grid-cols-3 items-center animate-in sm:grid-cols-7"
|
||||
>
|
||||
<TableCell className="col-span-1">
|
||||
{user.name || "Anonymous"}
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 flex items-center gap-1 truncate sm:col-span-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger className="truncate">
|
||||
{user.email}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{user.email}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
<Badge className="text-xs" variant="outline">
|
||||
{user.role}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
<StatusDot status={user.active} />
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
{timeAgo(user.createdAt || "")}
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 flex justify-center">
|
||||
<Button
|
||||
className="text-sm hover:bg-slate-100"
|
||||
size="sm"
|
||||
variant={"outline"}
|
||||
onClick={() => {
|
||||
setcurrentEditUser(user);
|
||||
setShowForm(false);
|
||||
setShowForm(!isShowForm);
|
||||
}}
|
||||
>
|
||||
<p>Edit</p>
|
||||
<PenLine className="ml-1 size-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : data && data.list ? (
|
||||
data.list.map((user) => (
|
||||
<TableRow
|
||||
key={user.id}
|
||||
className="grid animate-fade-in grid-cols-3 items-center animate-in sm:grid-cols-7"
|
||||
>
|
||||
<TableCell className="col-span-1">
|
||||
{user.name || "Anonymous"}
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 flex items-center gap-1 truncate sm:col-span-2">
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger className="truncate">
|
||||
{user.email}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{user.email}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
<Badge className="text-xs" variant="outline">
|
||||
{user.role}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
<StatusDot status={user.active} />
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
{timeAgo(user.createdAt || "")}
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 flex justify-center">
|
||||
<Button
|
||||
className="text-sm hover:bg-slate-100"
|
||||
size="sm"
|
||||
variant={"outline"}
|
||||
onClick={() => {
|
||||
setcurrentEditUser(user);
|
||||
setShowForm(false);
|
||||
setShowForm(!isShowForm);
|
||||
}}
|
||||
>
|
||||
<p>Edit</p>
|
||||
<PenLine className="ml-1 size-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<EmptyPlaceholder>
|
||||
<EmptyPlaceholder.Icon name="user" />
|
||||
@@ -239,6 +224,13 @@ export default function UsersList({ user }: UrlListProps) {
|
||||
</EmptyPlaceholder>
|
||||
)}
|
||||
</TableBody>
|
||||
{data && Math.ceil(data.total / pageSize) > 1 && (
|
||||
<PaginationWrapper
|
||||
total={Math.ceil(data.total / pageSize)}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { GlobeLock, Link as LinkIcon } from "lucide-react";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { getUserRecordCount } from "@/lib/dto/cloudflare-dns-record";
|
||||
import { getUserShortUrlCount } from "@/lib/dto/short-urls";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import CountUp from "@/components/dashboard/count-up";
|
||||
|
||||
export async function DNSInfoCard({ userId }: { userId: string }) {
|
||||
const count = await getUserRecordCount(userId);
|
||||
|
||||
return (
|
||||
<Card className="grids group bg-gray-50/70 backdrop-blur-lg dark:bg-primary-foreground">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
<Link
|
||||
className="font-semibold text-slate-500 duration-500 group-hover:text-blue-500 group-hover:underline"
|
||||
href="/dashboard/records"
|
||||
>
|
||||
DNS Records
|
||||
</Link>
|
||||
</CardTitle>
|
||||
<GlobeLock className="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{[-1, undefined].includes(count) ? (
|
||||
<Skeleton className="h-5 w-20" />
|
||||
) : (
|
||||
<div className="flex items-end gap-2 text-2xl font-bold">
|
||||
<CountUp count={count} />
|
||||
<span className="align-top text-base text-slate-500">
|
||||
/ {siteConfig.freeQuota.record}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">total</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export async function UrlsInfoCard({ userId }: { userId: string }) {
|
||||
const count = await getUserShortUrlCount(userId);
|
||||
return (
|
||||
<Card className="grids group bg-gray-50/70 backdrop-blur-lg dark:bg-primary-foreground">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
<Link
|
||||
className="font-semibold text-slate-500 duration-500 group-hover:text-blue-500 group-hover:underline"
|
||||
href="/dashboard/urls"
|
||||
>
|
||||
Short URLs
|
||||
</Link>
|
||||
</CardTitle>
|
||||
<LinkIcon className="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{[-1, undefined].includes(count) ? (
|
||||
<Skeleton className="h-5 w-20" />
|
||||
) : (
|
||||
<div className="flex items-end gap-2 text-2xl font-bold">
|
||||
<CountUp count={count} />
|
||||
<span className="align-top text-base text-slate-500">
|
||||
/ {siteConfig.freeQuota.url}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">total</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { siteConfig } from "@/config/site";
|
||||
import { getUserRecordCount } from "@/lib/dto/cloudflare-dns-record";
|
||||
import { getUserShortUrlCount } from "@/lib/dto/short-urls";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
import { constructMetadata } from "@/lib/utils";
|
||||
import { DashboardHeader } from "@/components/dashboard/header";
|
||||
import InfoCard from "@/components/dashboard/info-card";
|
||||
|
||||
import { DNSInfoCard, UrlsInfoCard } from "./info-card";
|
||||
import {
|
||||
DashboardInfoCard,
|
||||
HeroCard,
|
||||
} from "../../../components/dashboard/dashboard-info-card";
|
||||
import UserRecordsList from "./records/record-list";
|
||||
import UserUrlsList from "./urls/url-list";
|
||||
|
||||
@@ -20,6 +25,9 @@ export default async function DashboardPage() {
|
||||
|
||||
if (!user?.id) redirect("/login");
|
||||
|
||||
const record_count = await getUserRecordCount(user.id);
|
||||
const url_count = await getUserShortUrlCount(user.id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DashboardHeader
|
||||
@@ -28,26 +36,30 @@ export default async function DashboardPage() {
|
||||
/>
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3 xl:grid-cols-3">
|
||||
<div className="group relative mb-4 h-full w-full shrink-0 origin-left overflow-hidden rounded-lg border bg-gray-50/70 p-4 text-left duration-500 before:absolute before:right-1 before:top-1 before:z-[2] before:h-12 before:w-12 before:rounded-full before:bg-violet-500 before:blur-lg before:duration-500 after:absolute after:right-8 after:top-3 after:z-[2] after:h-20 after:w-20 after:rounded-full after:bg-rose-300 after:blur-lg after:duration-500 hover:border-cyan-600 hover:decoration-2 hover:duration-500 hover:before:-bottom-8 hover:before:right-12 hover:before:blur hover:before:[box-shadow:_20px_20px_20px_30px_#a21caf] hover:after:-right-8 group-hover:before:duration-500 group-hover:after:duration-500 dark:bg-primary-foreground md:max-w-[350px]">
|
||||
<h1 className="mb-7 text-lg font-bold duration-500 group-hover:text-blue-500">
|
||||
Free Records & URLs
|
||||
</h1>
|
||||
<p className="text-sm">
|
||||
Learn more from{" "}
|
||||
<Link
|
||||
className="text-blue-500 hover:underline"
|
||||
href="/docs"
|
||||
target="_blank"
|
||||
>
|
||||
documents.
|
||||
</Link>{" "}
|
||||
</p>
|
||||
</div>
|
||||
<DNSInfoCard userId={user.id} />
|
||||
<UrlsInfoCard userId={user.id} />
|
||||
<HeroCard />
|
||||
<DashboardInfoCard
|
||||
userId={user.id}
|
||||
title="DNS Records"
|
||||
count={record_count}
|
||||
total={siteConfig.freeQuota.record}
|
||||
link="/dashboard/records"
|
||||
/>
|
||||
<DashboardInfoCard
|
||||
userId={user.id}
|
||||
title="Short URLs"
|
||||
count={url_count}
|
||||
total={siteConfig.freeQuota.url}
|
||||
link="/dashboard/urls"
|
||||
/>
|
||||
</div>
|
||||
<UserRecordsList user={{ id: user.id, name: user.name || "" }} />
|
||||
<UserUrlsList user={{ id: user.id, name: user.name || "" }} />
|
||||
<UserRecordsList
|
||||
user={{ id: user.id, name: user.name || "" }}
|
||||
action="/api/record"
|
||||
/>
|
||||
<UserUrlsList
|
||||
user={{ id: user.id, name: user.name || "" }}
|
||||
action="/api/url"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,10 @@ export default async function DashboardPage() {
|
||||
link="/docs/dns-records"
|
||||
linkText="DNS Records."
|
||||
/>
|
||||
<UserRecordsList user={{ id: user.id, name: user.name || "" }} />
|
||||
<UserRecordsList
|
||||
user={{ id: user.id, name: user.name || "" }}
|
||||
action="/api/record"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { User } from "@prisma/client";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
DotSquareIcon,
|
||||
PenLine,
|
||||
RefreshCwIcon,
|
||||
} from "lucide-react";
|
||||
import { PenLine, RefreshCwIcon } from "lucide-react";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
|
||||
import { TTL_ENUMS } from "@/lib/cloudflare";
|
||||
@@ -23,6 +18,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { PaginationWrapper } from "@/components/ui/pagination";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
@@ -38,15 +34,17 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import CountUpFn from "@/components/dashboard/count-up";
|
||||
import StatusDot from "@/components/dashboard/status-dot";
|
||||
import { FormType, RecordForm } from "@/components/forms/record-form";
|
||||
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
|
||||
|
||||
export interface RecordListProps {
|
||||
user: Pick<User, "id" | "name">;
|
||||
action: string;
|
||||
}
|
||||
|
||||
function TableColumnSekleton({ className }: { className?: string }) {
|
||||
function TableColumnSekleton() {
|
||||
return (
|
||||
<TableRow className="grid grid-cols-3 items-center sm:grid-cols-7">
|
||||
<TableCell className="col-span-1">
|
||||
@@ -71,35 +69,48 @@ function TableColumnSekleton({ className }: { className?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function UserRecordsList({ user }: RecordListProps) {
|
||||
export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
const [isShowForm, setShowForm] = useState(false);
|
||||
const [formType, setFormType] = useState<FormType>("add");
|
||||
const [currentEditRecord, setCurrentEditRecord] =
|
||||
useState<UserRecordFormData | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const { mutate } = useSWRConfig();
|
||||
const { data, error, isLoading } = useSWR<UserRecordFormData[]>(
|
||||
"/api/record",
|
||||
fetcher,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
},
|
||||
);
|
||||
|
||||
console.log("base", action);
|
||||
|
||||
const { data, error, isLoading } = useSWR<{
|
||||
total: number;
|
||||
list: UserRecordFormData[];
|
||||
}>(`${action}?page=${currentPage}&size=${pageSize}`, fetcher, {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const handleRefresh = () => {
|
||||
mutate("/api/record", undefined);
|
||||
mutate(`${action}?page=${currentPage}&size=${pageSize}`, undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="xl:col-span-2">
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<div className="grid gap-2">
|
||||
<CardTitle>Records</CardTitle>
|
||||
<CardDescription className="text-balance">
|
||||
All DNS Records
|
||||
{action.includes("/admin") ? (
|
||||
<CardDescription className="text-balance text-lg font-bold">
|
||||
<span>Total Records:</span>{" "}
|
||||
<span className="font-bold">
|
||||
{data && <CountUpFn count={data.total ?? 0} />}
|
||||
</span>
|
||||
</CardDescription>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
<CardTitle>DNS Records</CardTitle>
|
||||
<CardDescription className="text-balance">
|
||||
Your DNS Records
|
||||
</CardDescription>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-auto flex items-center justify-end gap-3">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
@@ -134,6 +145,7 @@ export default function UserRecordsList({ user }: RecordListProps) {
|
||||
setShowForm={setShowForm}
|
||||
type={formType}
|
||||
initData={currentEditRecord}
|
||||
action={action}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
)}
|
||||
@@ -167,8 +179,8 @@ export default function UserRecordsList({ user }: RecordListProps) {
|
||||
<TableColumnSekleton />
|
||||
<TableColumnSekleton />
|
||||
</>
|
||||
) : data && data.length > 0 ? (
|
||||
data.map((record) => (
|
||||
) : data && data.list ? (
|
||||
data.list.map((record) => (
|
||||
<TableRow
|
||||
key={record.id}
|
||||
className="grid animate-fade-in grid-cols-3 items-center animate-in sm:grid-cols-7"
|
||||
@@ -179,9 +191,16 @@ export default function UserRecordsList({ user }: RecordListProps) {
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1">
|
||||
{record.name.endsWith(".wr.do")
|
||||
? record.name.slice(0, -6)
|
||||
: record.name}
|
||||
<Link
|
||||
className="text-slate-600 hover:text-blue-400 hover:underline dark:text-slate-400"
|
||||
href={"https://" + record.name}
|
||||
target="_blank"
|
||||
prefetch={false}
|
||||
>
|
||||
{record.name.endsWith(".wr.do")
|
||||
? record.name.slice(0, -6)
|
||||
: record.name}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-2 hidden truncate text-nowrap sm:inline-block">
|
||||
<TooltipProvider>
|
||||
@@ -240,6 +259,13 @@ export default function UserRecordsList({ user }: RecordListProps) {
|
||||
</EmptyPlaceholder>
|
||||
)}
|
||||
</TableBody>
|
||||
{data && Math.ceil(data.total / pageSize) > 1 && (
|
||||
<PaginationWrapper
|
||||
total={Math.ceil(data.total / pageSize)}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -24,7 +24,10 @@ export default async function DashboardPage() {
|
||||
link="/docs/short-urls"
|
||||
linkText="Short urls."
|
||||
/>
|
||||
<UserUrlsList user={{ id: user.id, name: user.name || "" }} />
|
||||
<UserUrlsList
|
||||
user={{ id: user.id, name: user.name || "" }}
|
||||
action="/api/url"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { PaginationWrapper } from "@/components/ui/pagination";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import {
|
||||
Table,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import CountUpFn from "@/components/dashboard/count-up";
|
||||
import StatusDot from "@/components/dashboard/status-dot";
|
||||
import { FormType } from "@/components/forms/record-form";
|
||||
import { UrlForm } from "@/components/forms/url-form";
|
||||
@@ -35,6 +37,7 @@ import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
|
||||
|
||||
export interface UrlListProps {
|
||||
user: Pick<User, "id" | "name">;
|
||||
action: string;
|
||||
}
|
||||
|
||||
function TableColumnSekleton({ className }: { className?: string }) {
|
||||
@@ -59,37 +62,47 @@ function TableColumnSekleton({ className }: { className?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function UserUrlsList({ user }: UrlListProps) {
|
||||
export default function UserUrlsList({ user, action }: UrlListProps) {
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [isShowForm, setShowForm] = useState(false);
|
||||
const [formType, setFormType] = useState<FormType>("add");
|
||||
const [currentEditUrl, setCurrentEditUrl] = useState<ShortUrlFormData | null>(
|
||||
null,
|
||||
);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const { mutate } = useSWRConfig();
|
||||
const { data, error, isLoading } = useSWR<ShortUrlFormData[]>(
|
||||
"/api/url",
|
||||
fetcher,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
},
|
||||
);
|
||||
const { data, error, isLoading } = useSWR<{
|
||||
total: number;
|
||||
list: ShortUrlFormData[];
|
||||
}>(`${action}?page=${currentPage}&size=${pageSize}`, fetcher, {
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
|
||||
const handleRefresh = () => {
|
||||
mutate("/api/url", undefined);
|
||||
mutate(`${action}?page=${currentPage}&size=${pageSize}`, undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="xl:col-span-2">
|
||||
<CardHeader className="flex flex-row items-center">
|
||||
<div className="grid gap-2">
|
||||
<CardTitle>Short URLs</CardTitle>
|
||||
<CardDescription className="text-balance">
|
||||
All Short URLs
|
||||
{action.includes("/admin") ? (
|
||||
<CardDescription className="text-balance text-lg font-bold">
|
||||
<span>Total URLs:</span>{" "}
|
||||
<span className="font-bold">
|
||||
{data && <CountUpFn count={data.total ?? 0} />}
|
||||
</span>
|
||||
</CardDescription>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid gap-2">
|
||||
<CardTitle>Short URLs</CardTitle>
|
||||
<CardDescription className="text-balance">
|
||||
Your Short URLs
|
||||
</CardDescription>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-auto flex items-center justify-end gap-3">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
@@ -124,6 +137,7 @@ export default function UserUrlsList({ user }: UrlListProps) {
|
||||
setShowForm={setShowForm}
|
||||
type={formType}
|
||||
initData={currentEditUrl}
|
||||
action={action}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
)}
|
||||
@@ -154,8 +168,8 @@ export default function UserUrlsList({ user }: UrlListProps) {
|
||||
<TableColumnSekleton />
|
||||
<TableColumnSekleton />
|
||||
</>
|
||||
) : data && data.length > 0 ? (
|
||||
data.map((short) => (
|
||||
) : data && data.list ? (
|
||||
data.list.map((short) => (
|
||||
<TableRow
|
||||
key={short.id}
|
||||
className="grid animate-fade-in grid-cols-3 items-center animate-in sm:grid-cols-7"
|
||||
@@ -170,7 +184,6 @@ export default function UserUrlsList({ user }: UrlListProps) {
|
||||
{short.target.startsWith("http")
|
||||
? short.target.split("//")[1]
|
||||
: short.target}
|
||||
{/* {isMobile ? short.target.split("//")[1] :short.target} */}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 flex items-center gap-1 sm:col-span-2">
|
||||
@@ -236,6 +249,13 @@ export default function UserUrlsList({ user }: UrlListProps) {
|
||||
</EmptyPlaceholder>
|
||||
)}
|
||||
</TableBody>
|
||||
{data && Math.ceil(data.total / pageSize) > 1 && (
|
||||
<PaginationWrapper
|
||||
total={Math.ceil(data.total / pageSize)}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { getUserRecords } from "@/lib/dto/cloudflare-dns-record";
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const user = checkUserStatus(await getCurrentUser());
|
||||
if (user instanceof Response) return user;
|
||||
if (user.role !== "ADMIN") {
|
||||
return Response.json("Unauthorized", {
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(req.url);
|
||||
const page = url.searchParams.get("page");
|
||||
const size = url.searchParams.get("size");
|
||||
|
||||
const data = await getUserRecords(
|
||||
user.id,
|
||||
1,
|
||||
Number(page || "1"),
|
||||
Number(size || "10"),
|
||||
"ADMIN",
|
||||
);
|
||||
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
statusText: error.statusText || "Server error",
|
||||
});
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -7,11 +7,19 @@ export async function GET(req: Request) {
|
||||
try {
|
||||
const user = checkUserStatus(await getCurrentUser());
|
||||
if (user instanceof Response) return user;
|
||||
// const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env;
|
||||
|
||||
const user_records = await getUserRecords(user.id, 1);
|
||||
const url = new URL(req.url);
|
||||
const page = url.searchParams.get("page");
|
||||
const size = url.searchParams.get("size");
|
||||
|
||||
return Response.json(user_records);
|
||||
const data = await getUserRecords(
|
||||
user.id,
|
||||
1,
|
||||
Number(page || "1"),
|
||||
Number(size || "10"),
|
||||
);
|
||||
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { env } from "@/env.mjs";
|
||||
import { getUserShortUrls } from "@/lib/dto/short-urls";
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
import { getCurrentUser } from "@/lib/session";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const user = checkUserStatus(await getCurrentUser());
|
||||
if (user instanceof Response) return user;
|
||||
if (user.role !== "ADMIN") {
|
||||
return Response.json("Unauthorized", {
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(req.url);
|
||||
const page = url.searchParams.get("page");
|
||||
const size = url.searchParams.get("size");
|
||||
const data = await getUserShortUrls(
|
||||
user.id,
|
||||
1,
|
||||
Number(page || "1"),
|
||||
Number(size || "10"),
|
||||
"ADMIN",
|
||||
);
|
||||
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
statusText: error.statusText || "Server error",
|
||||
});
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -8,10 +8,17 @@ export async function GET(req: Request) {
|
||||
const user = checkUserStatus(await getCurrentUser());
|
||||
if (user instanceof Response) return user;
|
||||
|
||||
const user_urls = await getUserShortUrls(user.id, 1);
|
||||
// TODO: get meta info
|
||||
const url = new URL(req.url);
|
||||
const page = url.searchParams.get("page");
|
||||
const size = url.searchParams.get("size");
|
||||
const data = await getUserShortUrls(
|
||||
user.id,
|
||||
1,
|
||||
Number(page || "1"),
|
||||
Number(size || "10"),
|
||||
);
|
||||
|
||||
return Response.json(user_urls);
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
|
||||
@@ -13,9 +13,12 @@ export async function GET(req: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
const user_records = await getAllUsers();
|
||||
const url = new URL(req.url);
|
||||
const page = url.searchParams.get("page");
|
||||
const size = url.searchParams.get("size");
|
||||
const data = await getAllUsers(Number(page || "1"), Number(size || "10"));
|
||||
|
||||
return Response.json(user_records);
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
// const user = checkUserStatus(await getCurrentUser());
|
||||
// if (user instanceof Response) return user;
|
||||
// if (user.role !== "ADMIN") {
|
||||
// return Response.json("Unauthorized", {
|
||||
// status: 401,
|
||||
// statusText: "Unauthorized",
|
||||
// });
|
||||
// }
|
||||
// const body = await req.json();
|
||||
// const { id, ...data } = body;
|
||||
// const result = await updateUserById(id, data);
|
||||
return Response.json("success");
|
||||
} catch (error) {
|
||||
return Response.json(error?.statusText || error, {
|
||||
status: error.status || 500,
|
||||
statusText: error.statusText || "Server error",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { auth } from "@/auth";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { deleteUserById } from "@/lib/dto/user";
|
||||
|
||||
export const DELETE = auth(async (req) => {
|
||||
if (!req.auth) {
|
||||
@@ -8,16 +8,12 @@ export const DELETE = auth(async (req) => {
|
||||
}
|
||||
|
||||
const currentUser = req.auth.user;
|
||||
if (!currentUser) {
|
||||
if (!currentUser || !currentUser?.id) {
|
||||
return new Response("Invalid user", { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.user.delete({
|
||||
where: {
|
||||
id: currentUser.id,
|
||||
},
|
||||
});
|
||||
await deleteUserById(currentUser.id);
|
||||
} catch (error) {
|
||||
return new Response("Internal server error", { status: 500 });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import Link from "next/link";
|
||||
import { Link as LinkIcon } from "lucide-react";
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import CountUp from "@/components/dashboard/count-up";
|
||||
import { Icons } from "@/components/shared/icons";
|
||||
|
||||
export async function DashboardInfoCard({
|
||||
userId,
|
||||
title,
|
||||
total,
|
||||
count,
|
||||
link,
|
||||
// icon = "user",
|
||||
}: {
|
||||
userId: string;
|
||||
title: string;
|
||||
total?: number;
|
||||
count: number;
|
||||
link: string;
|
||||
// icon: keyof typeof Icons;
|
||||
}) {
|
||||
// const iconCpn = Icons[icon];
|
||||
return (
|
||||
<Card className="grids group bg-gray-50/70 backdrop-blur-lg dark:bg-primary-foreground">
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
<Link
|
||||
className="font-semibold text-slate-500 duration-500 group-hover:text-blue-500 group-hover:underline"
|
||||
href={link}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
</CardTitle>
|
||||
{/* {icon && <iconCpn />} */}
|
||||
<LinkIcon className="size-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{[-1, undefined].includes(count) ? (
|
||||
<Skeleton className="h-5 w-20" />
|
||||
) : (
|
||||
<div className="flex items-end gap-2 text-2xl font-bold">
|
||||
<CountUp count={count} />
|
||||
{total && (
|
||||
<span className="align-top text-base text-slate-500">
|
||||
/ {total}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">total</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function HeroCard() {
|
||||
return (
|
||||
<div className="group relative mb-4 h-full w-full shrink-0 origin-left overflow-hidden rounded-lg border bg-gray-50/70 p-4 text-left duration-500 before:absolute before:right-1 before:top-1 before:z-[2] before:h-12 before:w-12 before:rounded-full before:bg-violet-500 before:blur-lg before:duration-500 after:absolute after:right-8 after:top-3 after:z-[2] after:h-20 after:w-20 after:rounded-full after:bg-rose-300 after:blur-lg after:duration-500 hover:border-cyan-600 hover:decoration-2 hover:duration-500 hover:before:-bottom-8 hover:before:right-12 hover:before:blur hover:before:[box-shadow:_20px_20px_20px_30px_#a21caf] hover:after:-right-8 group-hover:before:duration-500 group-hover:after:duration-500 dark:bg-primary-foreground md:max-w-[350px]">
|
||||
<h1 className="mb-7 text-lg font-bold duration-500 group-hover:text-blue-500">
|
||||
Free Records & URLs
|
||||
</h1>
|
||||
<p className="text-sm">
|
||||
Learn more from{" "}
|
||||
<Link
|
||||
className="text-blue-500 hover:underline"
|
||||
href="/docs"
|
||||
target="_blank"
|
||||
>
|
||||
documents.
|
||||
</Link>{" "}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export interface RecordFormProps {
|
||||
setShowForm: Dispatch<SetStateAction<boolean>>;
|
||||
type: FormType;
|
||||
initData?: UserRecordFormData | null;
|
||||
action: string;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
@@ -47,6 +48,7 @@ export function RecordForm({
|
||||
setShowForm,
|
||||
type,
|
||||
initData,
|
||||
action,
|
||||
onRefresh,
|
||||
}: RecordFormProps) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
@@ -86,7 +88,7 @@ export function RecordForm({
|
||||
|
||||
const handleCreateRecord = async (data: CreateDNSRecord) => {
|
||||
startTransition(async () => {
|
||||
const response = await fetch("/api/record/add", {
|
||||
const response = await fetch(`${action}/add`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
records: [data],
|
||||
@@ -108,7 +110,7 @@ export function RecordForm({
|
||||
const handleUpdateRecord = async (data: CreateDNSRecord) => {
|
||||
startTransition(async () => {
|
||||
if (type === "edit") {
|
||||
const response = await fetch("/api/record/update", {
|
||||
const response = await fetch(`${action}/update`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
recordId: initData?.record_id,
|
||||
@@ -132,7 +134,7 @@ export function RecordForm({
|
||||
const handleDeleteRecord = async () => {
|
||||
if (type === "edit") {
|
||||
startDeleteTransition(async () => {
|
||||
const response = await fetch("/api/record/delete", {
|
||||
const response = await fetch(`${action}/delete`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
record_id: initData?.record_id,
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface RecordFormProps {
|
||||
setShowForm: Dispatch<SetStateAction<boolean>>;
|
||||
type: FormType;
|
||||
initData?: ShortUrlFormData | null;
|
||||
action: string;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
@@ -35,6 +36,7 @@ export function UrlForm({
|
||||
setShowForm,
|
||||
type,
|
||||
initData,
|
||||
action,
|
||||
onRefresh,
|
||||
}: RecordFormProps) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
@@ -147,7 +147,7 @@ export function UserAuthForm({ className, type, ...props }: UserAuthFormProps) {
|
||||
{isGithubLoading ? (
|
||||
<Icons.spinner className="mr-2 size-4 animate-spin" />
|
||||
) : (
|
||||
<Icons.gitHub className="mr-2 size-4" />
|
||||
<Icons.github className="mr-2 size-4" />
|
||||
)}{" "}
|
||||
Github
|
||||
</button>
|
||||
|
||||
@@ -179,7 +179,7 @@ export function DashboardSidebar({ links }: DashboardSidebarProps) {
|
||||
</nav>
|
||||
|
||||
{isSidebarExpanded && (
|
||||
<p className="mx-3 mb-3 mt-auto font-mono text-xs text-muted-foreground/70">
|
||||
<p className="mx-3 mt-auto pb-3 pt-6 font-mono text-xs text-muted-foreground/70">
|
||||
© 2024{" "}
|
||||
<Link
|
||||
href={siteConfig.links.github}
|
||||
|
||||
@@ -121,7 +121,7 @@ export function NavMobile() {
|
||||
|
||||
<div className="mt-5 flex items-center justify-end space-x-4">
|
||||
<Link href={siteConfig.links.github} target="_blank" rel="noreferrer">
|
||||
<Icons.gitHub className="size-6" />
|
||||
<Icons.github className="size-6" />
|
||||
<span className="sr-only">GitHub</span>
|
||||
</Link>
|
||||
<ModeToggle />
|
||||
|
||||
@@ -96,7 +96,7 @@ export function NavBar({ scroll = false }: NavBarProps) {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Icons.gitHub className="size-7" />
|
||||
<Icons.github className="size-7" />
|
||||
<span className="sr-only">GitHub</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -82,7 +82,7 @@ export function SiteFooter({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
rel="noreferrer"
|
||||
className="font-medium underline underline-offset-1"
|
||||
>
|
||||
<Icons.gitHub className="size-5" />
|
||||
<Icons.github className="size-5" />
|
||||
</Link>
|
||||
<ModeToggle />
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@ export default async function HeroLanding() {
|
||||
"px-4 text-[15px]",
|
||||
)}
|
||||
>
|
||||
<Icons.gitHub className="mr-2 size-4" />
|
||||
<Icons.github className="mr-2 size-4" />
|
||||
<p>
|
||||
<span className="hidden sm:inline-block">Star on</span> GitHub
|
||||
</p>
|
||||
|
||||
@@ -51,7 +51,7 @@ export const Icons = {
|
||||
copy: Copy,
|
||||
dashboard: LayoutPanelLeft,
|
||||
ellipsis: MoreVertical,
|
||||
gitHub: ({ ...props }: LucideProps) => (
|
||||
github: ({ ...props }: LucideProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Pagination } from "@nextui-org/pagination";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
|
||||
import { Button } from "./button";
|
||||
|
||||
export function PaginationWrapper({
|
||||
total,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
}: {
|
||||
total: number;
|
||||
currentPage: number;
|
||||
setCurrentPage: any;
|
||||
}) {
|
||||
return (
|
||||
<div className="my-3 flex items-center justify-end gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
color="secondary"
|
||||
onClick={() => setCurrentPage((prev) => (prev > 1 ? prev - 1 : prev))}
|
||||
>
|
||||
<ArrowLeft className="size-4" />
|
||||
</Button>
|
||||
<Pagination
|
||||
isCompact
|
||||
total={total}
|
||||
color="success"
|
||||
page={currentPage}
|
||||
onChange={setCurrentPage}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
color="secondary"
|
||||
onClick={() => setCurrentPage((prev) => (prev < 10 ? prev + 1 : prev))}
|
||||
>
|
||||
<ArrowRight className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+10
-3
@@ -25,13 +25,19 @@ export const sidebarLinks: SidebarNavItem[] = [
|
||||
{
|
||||
href: "/admin/users",
|
||||
icon: "user",
|
||||
title: "User Management",
|
||||
title: "Users",
|
||||
authorizeOnly: UserRole.ADMIN,
|
||||
},
|
||||
{
|
||||
href: "/dashboard/charts",
|
||||
href: "/admin/records",
|
||||
icon: "lineChart",
|
||||
title: "Charts",
|
||||
title: "Records",
|
||||
authorizeOnly: UserRole.ADMIN,
|
||||
},
|
||||
{
|
||||
href: "/admin/urls",
|
||||
icon: "post",
|
||||
title: "URLs",
|
||||
authorizeOnly: UserRole.ADMIN,
|
||||
},
|
||||
],
|
||||
@@ -42,6 +48,7 @@ export const sidebarLinks: SidebarNavItem[] = [
|
||||
{ href: "/dashboard/settings", icon: "settings", title: "Settings" },
|
||||
{ href: "/", icon: "home", title: "Homepage" },
|
||||
{ href: "/docs", icon: "bookOpen", title: "Documentation" },
|
||||
{ href: siteConfig.links.github, icon: "github", title: "Github" },
|
||||
{
|
||||
href: "mailto:" + siteConfig.mailSupport,
|
||||
icon: "mail",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use server";
|
||||
|
||||
import { auth } from "@/auth";
|
||||
import { UserRole } from "@prisma/client";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import {
|
||||
@@ -82,22 +83,53 @@ export async function createUserRecord(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserRecords(userId: string, active: number = 1) {
|
||||
return await prisma.userRecord.findMany({
|
||||
where: {
|
||||
userId: userId,
|
||||
active,
|
||||
},
|
||||
});
|
||||
export async function getUserRecords(
|
||||
userId: string,
|
||||
active: number = 1,
|
||||
page: number,
|
||||
size: number,
|
||||
role: UserRole = "USER",
|
||||
) {
|
||||
const option =
|
||||
role === "USER"
|
||||
? {
|
||||
userId,
|
||||
active,
|
||||
}
|
||||
: {};
|
||||
const [total, list] = await prisma.$transaction([
|
||||
prisma.userRecord.count({
|
||||
where: option,
|
||||
}),
|
||||
prisma.userRecord.findMany({
|
||||
where: option,
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
orderBy: {
|
||||
modified_on: "asc",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getUserRecordCount(userId: string, active: number = 1) {
|
||||
export async function getUserRecordCount(
|
||||
userId: string,
|
||||
active: number = 1,
|
||||
role: UserRole = "USER",
|
||||
) {
|
||||
try {
|
||||
return await prisma.userRecord.count({
|
||||
where: {
|
||||
userId: userId,
|
||||
active,
|
||||
},
|
||||
where:
|
||||
role === "USER"
|
||||
? {
|
||||
userId: userId,
|
||||
active,
|
||||
}
|
||||
: {},
|
||||
});
|
||||
} catch (error) {
|
||||
return -1;
|
||||
|
||||
+44
-13
@@ -1,5 +1,5 @@
|
||||
import { auth } from "@/auth";
|
||||
import { UrlMeta } from "@prisma/client";
|
||||
import { UrlMeta, UserRole } from "@prisma/client";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
@@ -20,22 +20,53 @@ export interface UserShortUrlInfo extends ShortUrlFormData {
|
||||
meta?: UrlMeta;
|
||||
}
|
||||
|
||||
export async function getUserShortUrls(userId: string, active: number = 1) {
|
||||
return await prisma.userUrl.findMany({
|
||||
where: {
|
||||
userId,
|
||||
active,
|
||||
},
|
||||
});
|
||||
export async function getUserShortUrls(
|
||||
userId: string,
|
||||
active: number = 1,
|
||||
page: number,
|
||||
size: number,
|
||||
role: UserRole = "USER",
|
||||
) {
|
||||
const option =
|
||||
role === "USER"
|
||||
? {
|
||||
userId,
|
||||
active,
|
||||
}
|
||||
: {};
|
||||
const [total, list] = await prisma.$transaction([
|
||||
prisma.userUrl.count({
|
||||
where: option,
|
||||
}),
|
||||
prisma.userUrl.findMany({
|
||||
where: option,
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
orderBy: {
|
||||
updatedAt: "asc",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getUserShortUrlCount(userId: string, active: number = 1) {
|
||||
export async function getUserShortUrlCount(
|
||||
userId: string,
|
||||
active: number = 1,
|
||||
role: UserRole = "USER",
|
||||
) {
|
||||
try {
|
||||
return await prisma.userUrl.count({
|
||||
where: {
|
||||
userId,
|
||||
active,
|
||||
},
|
||||
where:
|
||||
role === "USER"
|
||||
? {
|
||||
userId,
|
||||
active,
|
||||
}
|
||||
: {},
|
||||
});
|
||||
} catch (error) {
|
||||
return -1;
|
||||
|
||||
+53
-6
@@ -1,4 +1,4 @@
|
||||
import { User } from "@prisma/client";
|
||||
import { User, UserRole } from "@prisma/client";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
@@ -43,12 +43,59 @@ export const getUserById = async (id: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllUsers = async () => {
|
||||
export const getAllUsers = async (page: number, size: number) => {
|
||||
try {
|
||||
// TODO: paginate
|
||||
const users = await prisma.user.findMany();
|
||||
return users;
|
||||
} catch {
|
||||
const [total, list] = await prisma.$transaction([
|
||||
prisma.user.count(), // 获取所有用户的总数
|
||||
prisma.user.findMany({
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
orderBy: {
|
||||
createdAt: "asc",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return {
|
||||
total,
|
||||
list,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export async function getAllUsersCount() {
|
||||
try {
|
||||
return await prisma.user.count();
|
||||
} catch (error) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
export const updateUser = async (userId: string, data: UpdateUserForm) => {
|
||||
try {
|
||||
const session = await prisma.user.update({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
data: data,
|
||||
});
|
||||
return session;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteUserById = async (userId: string) => {
|
||||
try {
|
||||
const session = await prisma.user.delete({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
return session;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ export const createRecordSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9-]+$/, "Invalid characters")
|
||||
.min(1)
|
||||
.min(2)
|
||||
.max(32),
|
||||
content: z
|
||||
.string()
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^2.4.1",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@nextui-org/pagination": "^2.0.35",
|
||||
"@prisma/client": "^5.17.0",
|
||||
"@radix-ui/react-accessible-icon": "^1.1.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
|
||||
Generated
+476
@@ -14,6 +14,9 @@ importers:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.9.0
|
||||
version: 3.9.0(react-hook-form@7.52.1(react@18.3.1))
|
||||
'@nextui-org/pagination':
|
||||
specifier: ^2.0.35
|
||||
version: 2.0.35(@nextui-org/system@2.2.5(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(framer-motion@10.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@prisma/client':
|
||||
specifier: ^5.17.0
|
||||
version: 5.17.0(prisma@5.17.0)
|
||||
@@ -782,6 +785,21 @@ packages:
|
||||
'@floating-ui/utils@0.1.6':
|
||||
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
|
||||
|
||||
'@formatjs/ecma402-abstract@2.0.0':
|
||||
resolution: {integrity: sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==}
|
||||
|
||||
'@formatjs/fast-memoize@2.2.0':
|
||||
resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==}
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.7.8':
|
||||
resolution: {integrity: sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==}
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.2':
|
||||
resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==}
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.4':
|
||||
resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==}
|
||||
|
||||
'@grpc/grpc-js@1.10.6':
|
||||
resolution: {integrity: sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==}
|
||||
engines: {node: '>=12.10.0'}
|
||||
@@ -941,6 +959,18 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@internationalized/date@3.5.5':
|
||||
resolution: {integrity: sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ==}
|
||||
|
||||
'@internationalized/message@3.1.4':
|
||||
resolution: {integrity: sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==}
|
||||
|
||||
'@internationalized/number@3.5.3':
|
||||
resolution: {integrity: sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==}
|
||||
|
||||
'@internationalized/string@3.2.3':
|
||||
resolution: {integrity: sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1124,6 +1154,53 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@nextui-org/pagination@2.0.35':
|
||||
resolution: {integrity: sha512-07KJgZcJBt2e9RY6TsiQm5qrjDLH+gT3yB7yQ4jPdCK9fkTB0r2kvTOYdPUvrtVJYRq2bwFCWOz+9mokdNfcwg==}
|
||||
peerDependencies:
|
||||
'@nextui-org/system': '>=2.0.0'
|
||||
'@nextui-org/theme': '>=2.1.0'
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
'@nextui-org/react-rsc-utils@2.0.13':
|
||||
resolution: {integrity: sha512-QewsXtoQlMsR9stThdazKEImg9oyZkPLs7wsymhrzh6/HdQCl9bTdb6tJcROg4vg5LRYKGG11USSQO2nKlfCcQ==}
|
||||
|
||||
'@nextui-org/react-utils@2.0.16':
|
||||
resolution: {integrity: sha512-QdDoqzhx+4t9cDTVmtw5iOrfyLvpqyKsq8PARHUniCiQQDQd1ao7FCpzHgvU9poYcEdRk+Lsna66zbeMkFBB6w==}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
|
||||
'@nextui-org/shared-icons@2.0.9':
|
||||
resolution: {integrity: sha512-WG3yinVY7Tk9VqJgcdF4V8Ok9+fcm5ey7S1els7kujrfqLYxtqoKywgiY/7QHwZlfQkzpykAfy+NAlHkTP5hMg==}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
|
||||
'@nextui-org/shared-utils@2.0.7':
|
||||
resolution: {integrity: sha512-FxY3N0i1Al7Oz3yOQN0dSpG8UUrLIP3iYh3ubD7BhdQoZLl5xbG6++q1gqOzZXV+ZWeUFMY/or0ofzWxGHiOow==}
|
||||
|
||||
'@nextui-org/system-rsc@2.1.5':
|
||||
resolution: {integrity: sha512-tkJLAyJu34Rr5KUMMqoB7cZjOVXB+7a/7N4ushZfuiLdoYijgmcXFMzLxjm+tbt9zA5AV+ivsfbHvscg77dJ6w==}
|
||||
peerDependencies:
|
||||
'@nextui-org/theme': '>=2.1.0'
|
||||
react: '>=18'
|
||||
|
||||
'@nextui-org/system@2.2.5':
|
||||
resolution: {integrity: sha512-nrX6768aiyWtpxX3OTFBIVWR+v9nlMsC3KaBinNfek97sNm7gAfTHi7q5kylE3L5yIMpNG+DclAKpuxgDQEmvw==}
|
||||
peerDependencies:
|
||||
framer-motion: '>=10.17.0'
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
|
||||
'@nextui-org/theme@2.2.9':
|
||||
resolution: {integrity: sha512-TN2I9sMriLaj00pXsIMlg19+UHeOdjzS2JV0u4gjL14mSbQl5BYNxgbvU3gbMqkZZQ6OpwT4RnT8RS+ks6TXCw==}
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.4.0'
|
||||
|
||||
'@nextui-org/use-pagination@2.0.9':
|
||||
resolution: {integrity: sha512-p5Gssyb71/SjRezq2o1aRsYTmC9idziW3pLCJFpVwLGfgWNARf9C6NS1oQsqKgjF5lvzoa88soZRDhKKvRAt/g==}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -2089,6 +2166,58 @@ packages:
|
||||
'@radix-ui/rect@1.1.0':
|
||||
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
|
||||
|
||||
'@react-aria/focus@3.17.1':
|
||||
resolution: {integrity: sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-aria/focus@3.18.1':
|
||||
resolution: {integrity: sha512-N0Cy61WCIv+57mbqC7hiZAsB+3rF5n4JKabxUmg/2RTJL6lq7hJ5N4gx75ymKxkN8GnVDwt4pKZah48Wopa5jw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/i18n@3.11.1':
|
||||
resolution: {integrity: sha512-vuiBHw1kZruNMYeKkTGGnmPyMnM5T+gT8bz97H1FqIq1hQ6OPzmtBZ6W6l6OIMjeHI5oJo4utTwfZl495GALFQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-aria/interactions@3.21.3':
|
||||
resolution: {integrity: sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-aria/interactions@3.22.1':
|
||||
resolution: {integrity: sha512-5TLzQaDAQQ5C70yG8GInbO4wIylKY67RfTIIwQPGR/4n5OIjbUD8BOj3NuSsuZ/frUPaBXo1VEBBmSO23fxkjw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/overlays@3.22.1':
|
||||
resolution: {integrity: sha512-GHiFMWO4EQ6+j6b5QCnNoOYiyx1Gk8ZiwLzzglCI4q1NY5AG2EAmfU4Z1+Gtrf2S5Y0zHbumC7rs9GnPoGLUYg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-aria/ssr@3.9.5':
|
||||
resolution: {integrity: sha512-xEwGKoysu+oXulibNUSkXf8itW0npHHTa6c4AyYeZIJyRoegeteYuFpZUBPtIDE8RfHdNsSmE1ssOkxRnwbkuQ==}
|
||||
engines: {node: '>= 12'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/utils@3.24.1':
|
||||
resolution: {integrity: sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-aria/utils@3.25.1':
|
||||
resolution: {integrity: sha512-5Uj864e7T5+yj78ZfLnfHqmypLiqW2mN+nsdslog2z5ssunTqjolVeM15ootXskjISlZ7MojLpq97kIC4nlnAw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-aria/visually-hidden@3.8.14':
|
||||
resolution: {integrity: sha512-DV3yagbAgO4ywQTq6D/AxcIaTC8c77r/SxlIMhQBMQ6vScJWTCh6zFG55wmLe3NKqvRrowv1OstlmYfZQ4v/XA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-email/body@0.0.8':
|
||||
resolution: {integrity: sha512-gqdkNYlIaIw0OdpWu8KjIcQSIFvx7t2bZpXVxMMvBS859Ia1+1X3b5RNbjI3S1ZqLddUf7owOHkO4MiXGE+nxg==}
|
||||
peerDependencies:
|
||||
@@ -2218,6 +2347,41 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@react-stately/overlays@3.6.9':
|
||||
resolution: {integrity: sha512-4chfyzKw7P2UEainm0yzjUgYwG1ovBejN88eTrn+O62x5huuMCwe0cbMxmYh4y7IhRFSee3jIJd0SP0u/+i39w==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-stately/utils@3.10.1':
|
||||
resolution: {integrity: sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-stately/utils@3.10.2':
|
||||
resolution: {integrity: sha512-fh6OTQtbeQC0ywp6LJuuKs6tKIgFvt/DlIZEcIpGho6/oZG229UnIk6TUekwxnDbumuYyan6D9EgUtEMmT8UIg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-types/button@3.9.6':
|
||||
resolution: {integrity: sha512-8lA+D5JLbNyQikf8M/cPP2cji91aVTcqjrGpDqI7sQnaLFikM8eFR6l1ZWGtZS5MCcbfooko77ha35SYplSQvw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-types/overlays@3.8.9':
|
||||
resolution: {integrity: sha512-9ni9upQgXPnR+K9cWmbYWvm3ll9gH8P/XsEZprqIV5zNLMF334jADK48h4jafb1X9RFnj0WbHo6BqcSObzjTig==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@react-types/shared@3.23.1':
|
||||
resolution: {integrity: sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
|
||||
|
||||
'@react-types/shared@3.24.1':
|
||||
resolution: {integrity: sha512-AUQeGYEm/zDTN6zLzdXolDxz3Jk5dDL7f506F07U8tBwxNNI3WRdhU84G0/AaFikOZzDXhOZDr3MhQMzyE7Ydw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
|
||||
|
||||
'@resvg/resvg-wasm@2.4.0':
|
||||
resolution: {integrity: sha512-C7c51Nn4yTxXFKvgh2txJFNweaVcfUPQxwEUFw4aWsCmfiBDJsTSwviIF8EcwjQ6k8bPyMWCl1vw4BdxE569Cg==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -2925,6 +3089,9 @@ packages:
|
||||
color-string@1.9.1:
|
||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||
|
||||
color2k@2.0.3:
|
||||
resolution: {integrity: sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==}
|
||||
|
||||
color@4.2.3:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
@@ -2954,6 +3121,9 @@ packages:
|
||||
compare-func@2.0.0:
|
||||
resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==}
|
||||
|
||||
compute-scroll-into-view@3.1.0:
|
||||
resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -3559,6 +3729,10 @@ packages:
|
||||
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
||||
flat@5.0.2:
|
||||
resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
|
||||
hasBin: true
|
||||
|
||||
flatted@3.2.9:
|
||||
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
|
||||
|
||||
@@ -3867,6 +4041,9 @@ packages:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
intl-messageformat@10.5.14:
|
||||
resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==}
|
||||
|
||||
invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
|
||||
@@ -4156,18 +4333,30 @@ packages:
|
||||
lodash.castarray@4.4.0:
|
||||
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
||||
|
||||
lodash.foreach@4.5.0:
|
||||
resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
|
||||
|
||||
lodash.get@4.4.2:
|
||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
|
||||
lodash.kebabcase@4.1.1:
|
||||
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
|
||||
|
||||
lodash.mapkeys@4.6.0:
|
||||
resolution: {integrity: sha512-0Al+hxpYvONWtg+ZqHpa/GaVzxuN3V7Xeo2p+bY06EaK/n+Y9R7nBePPN2o1LxmL0TWQSwP8LYZ008/hc9JzhA==}
|
||||
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
lodash.mergewith@4.6.2:
|
||||
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
|
||||
|
||||
lodash.omit@4.5.0:
|
||||
resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==}
|
||||
|
||||
lodash.snakecase@4.1.1:
|
||||
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
|
||||
|
||||
@@ -5174,6 +5363,9 @@ packages:
|
||||
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
|
||||
engines: {node: '>= 10.13.0'}
|
||||
|
||||
scroll-into-view-if-needed@3.0.10:
|
||||
resolution: {integrity: sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==}
|
||||
|
||||
section-matter@1.0.0:
|
||||
resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -5421,12 +5613,21 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
tailwind-merge@1.14.0:
|
||||
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
|
||||
|
||||
tailwind-merge@2.2.0:
|
||||
resolution: {integrity: sha512-SqqhhaL0T06SW59+JVNfAqKdqLs0497esifRrZ7jOaefP3o64fdFNDMrAQWZFMxTLJPiHVjRLUywT8uFz1xNWQ==}
|
||||
|
||||
tailwind-merge@2.4.0:
|
||||
resolution: {integrity: sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==}
|
||||
|
||||
tailwind-variants@0.1.20:
|
||||
resolution: {integrity: sha512-AMh7x313t/V+eTySKB0Dal08RHY7ggYK0MSn/ad8wKWOrDUIzyiWNayRUm2PIJ4VRkvRnfNuyRuKbLV3EN+ewQ==}
|
||||
engines: {node: '>=16.x', pnpm: '>=7.x'}
|
||||
peerDependencies:
|
||||
tailwindcss: '*'
|
||||
|
||||
tailwindcss-animate@1.0.7:
|
||||
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
|
||||
peerDependencies:
|
||||
@@ -6367,6 +6568,30 @@ snapshots:
|
||||
|
||||
'@floating-ui/utils@0.1.6': {}
|
||||
|
||||
'@formatjs/ecma402-abstract@2.0.0':
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.5.4
|
||||
tslib: 2.6.2
|
||||
|
||||
'@formatjs/fast-memoize@2.2.0':
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
|
||||
'@formatjs/icu-messageformat-parser@2.7.8':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.0.0
|
||||
'@formatjs/icu-skeleton-parser': 1.8.2
|
||||
tslib: 2.6.2
|
||||
|
||||
'@formatjs/icu-skeleton-parser@1.8.2':
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.0.0
|
||||
tslib: 2.6.2
|
||||
|
||||
'@formatjs/intl-localematcher@0.5.4':
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
|
||||
'@grpc/grpc-js@1.10.6':
|
||||
dependencies:
|
||||
'@grpc/proto-loader': 0.7.12
|
||||
@@ -6482,6 +6707,23 @@ snapshots:
|
||||
'@img/sharp-win32-x64@0.33.4':
|
||||
optional: true
|
||||
|
||||
'@internationalized/date@3.5.5':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
|
||||
'@internationalized/message@3.1.4':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
intl-messageformat: 10.5.14
|
||||
|
||||
'@internationalized/number@3.5.3':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
|
||||
'@internationalized/string@3.2.3':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
@@ -6636,6 +6878,80 @@ snapshots:
|
||||
'@next/swc-win32-x64-msvc@14.2.5':
|
||||
optional: true
|
||||
|
||||
'@nextui-org/pagination@2.0.35(@nextui-org/system@2.2.5(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(framer-motion@10.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@nextui-org/react-utils': 2.0.16(react@18.3.1)
|
||||
'@nextui-org/shared-icons': 2.0.9(react@18.3.1)
|
||||
'@nextui-org/shared-utils': 2.0.7
|
||||
'@nextui-org/system': 2.2.5(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(framer-motion@10.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/theme': 2.2.9(tailwindcss@3.4.6)
|
||||
'@nextui-org/use-pagination': 2.0.9(react@18.3.1)
|
||||
'@react-aria/focus': 3.17.1(react@18.3.1)
|
||||
'@react-aria/i18n': 3.11.1(react@18.3.1)
|
||||
'@react-aria/interactions': 3.21.3(react@18.3.1)
|
||||
'@react-aria/utils': 3.24.1(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
scroll-into-view-if-needed: 3.0.10
|
||||
|
||||
'@nextui-org/react-rsc-utils@2.0.13': {}
|
||||
|
||||
'@nextui-org/react-utils@2.0.16(react@18.3.1)':
|
||||
dependencies:
|
||||
'@nextui-org/react-rsc-utils': 2.0.13
|
||||
'@nextui-org/shared-utils': 2.0.7
|
||||
react: 18.3.1
|
||||
|
||||
'@nextui-org/shared-icons@2.0.9(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@nextui-org/shared-utils@2.0.7': {}
|
||||
|
||||
'@nextui-org/system-rsc@2.1.5(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@nextui-org/theme': 2.2.9(tailwindcss@3.4.6)
|
||||
'@react-types/shared': 3.23.1(react@18.3.1)
|
||||
clsx: 1.2.1
|
||||
react: 18.3.1
|
||||
|
||||
'@nextui-org/system@2.2.5(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(framer-motion@10.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@internationalized/date': 3.5.5
|
||||
'@nextui-org/react-utils': 2.0.16(react@18.3.1)
|
||||
'@nextui-org/system-rsc': 2.1.5(@nextui-org/theme@2.2.9(tailwindcss@3.4.6))(react@18.3.1)
|
||||
'@react-aria/i18n': 3.11.1(react@18.3.1)
|
||||
'@react-aria/overlays': 3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@react-aria/utils': 3.24.1(react@18.3.1)
|
||||
'@react-stately/utils': 3.10.1(react@18.3.1)
|
||||
framer-motion: 10.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@nextui-org/theme'
|
||||
|
||||
'@nextui-org/theme@2.2.9(tailwindcss@3.4.6)':
|
||||
dependencies:
|
||||
clsx: 1.2.1
|
||||
color: 4.2.3
|
||||
color2k: 2.0.3
|
||||
deepmerge: 4.3.1
|
||||
flat: 5.0.2
|
||||
lodash.foreach: 4.5.0
|
||||
lodash.get: 4.4.2
|
||||
lodash.kebabcase: 4.1.1
|
||||
lodash.mapkeys: 4.6.0
|
||||
lodash.omit: 4.5.0
|
||||
tailwind-merge: 1.14.0
|
||||
tailwind-variants: 0.1.20(tailwindcss@3.4.6)
|
||||
tailwindcss: 3.4.6
|
||||
|
||||
'@nextui-org/use-pagination@2.0.9(react@18.3.1)':
|
||||
dependencies:
|
||||
'@nextui-org/shared-utils': 2.0.7
|
||||
'@react-aria/i18n': 3.11.1(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@@ -7900,6 +8216,99 @@ snapshots:
|
||||
|
||||
'@radix-ui/rect@1.1.0': {}
|
||||
|
||||
'@react-aria/focus@3.17.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.21.3(react@18.3.1)
|
||||
'@react-aria/utils': 3.24.1(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/focus@3.18.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.22.1(react@18.3.1)
|
||||
'@react-aria/utils': 3.25.1(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/i18n@3.11.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@internationalized/date': 3.5.5
|
||||
'@internationalized/message': 3.1.4
|
||||
'@internationalized/number': 3.5.3
|
||||
'@internationalized/string': 3.2.3
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-aria/utils': 3.24.1(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/interactions@3.21.3(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-aria/utils': 3.24.1(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/interactions@3.22.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-aria/utils': 3.25.1(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/overlays@3.22.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/focus': 3.18.1(react@18.3.1)
|
||||
'@react-aria/i18n': 3.11.1(react@18.3.1)
|
||||
'@react-aria/interactions': 3.22.1(react@18.3.1)
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-aria/utils': 3.24.1(react@18.3.1)
|
||||
'@react-aria/visually-hidden': 3.8.14(react@18.3.1)
|
||||
'@react-stately/overlays': 3.6.9(react@18.3.1)
|
||||
'@react-types/button': 3.9.6(react@18.3.1)
|
||||
'@react-types/overlays': 3.8.9(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@react-aria/ssr@3.9.5(react@18.3.1)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/utils@3.24.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-stately/utils': 3.10.2(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/utils@3.25.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/ssr': 3.9.5(react@18.3.1)
|
||||
'@react-stately/utils': 3.10.2(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
clsx: 2.1.1
|
||||
react: 18.3.1
|
||||
|
||||
'@react-aria/visually-hidden@3.8.14(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-aria/interactions': 3.22.1(react@18.3.1)
|
||||
'@react-aria/utils': 3.25.1(react@18.3.1)
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-email/body@0.0.8(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
@@ -8024,6 +8433,41 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@react-stately/overlays@3.6.9(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-stately/utils': 3.10.2(react@18.3.1)
|
||||
'@react-types/overlays': 3.8.9(react@18.3.1)
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-stately/utils@3.10.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-stately/utils@3.10.2(react@18.3.1)':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.5
|
||||
react: 18.3.1
|
||||
|
||||
'@react-types/button@3.9.6(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@react-types/overlays@3.8.9(react@18.3.1)':
|
||||
dependencies:
|
||||
'@react-types/shared': 3.24.1(react@18.3.1)
|
||||
react: 18.3.1
|
||||
|
||||
'@react-types/shared@3.23.1(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@react-types/shared@3.24.1(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@resvg/resvg-wasm@2.4.0': {}
|
||||
|
||||
'@rushstack/eslint-patch@1.5.1': {}
|
||||
@@ -8777,6 +9221,8 @@ snapshots:
|
||||
color-name: 1.1.4
|
||||
simple-swizzle: 0.2.2
|
||||
|
||||
color2k@2.0.3: {}
|
||||
|
||||
color@4.2.3:
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
@@ -8805,6 +9251,8 @@ snapshots:
|
||||
array-ify: 1.0.0
|
||||
dot-prop: 5.3.0
|
||||
|
||||
compute-scroll-into-view@3.1.0: {}
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
concurrently@8.2.2:
|
||||
@@ -9604,6 +10052,8 @@ snapshots:
|
||||
keyv: 4.5.4
|
||||
rimraf: 3.0.2
|
||||
|
||||
flat@5.0.2: {}
|
||||
|
||||
flatted@3.2.9: {}
|
||||
|
||||
for-each@0.3.3:
|
||||
@@ -9984,6 +10434,13 @@ snapshots:
|
||||
|
||||
internmap@2.0.3: {}
|
||||
|
||||
intl-messageformat@10.5.14:
|
||||
dependencies:
|
||||
'@formatjs/ecma402-abstract': 2.0.0
|
||||
'@formatjs/fast-memoize': 2.2.0
|
||||
'@formatjs/icu-messageformat-parser': 2.7.8
|
||||
tslib: 2.6.2
|
||||
|
||||
invariant@2.2.4:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
@@ -10240,14 +10697,22 @@ snapshots:
|
||||
|
||||
lodash.castarray@4.4.0: {}
|
||||
|
||||
lodash.foreach@4.5.0: {}
|
||||
|
||||
lodash.get@4.4.2: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.kebabcase@4.1.1: {}
|
||||
|
||||
lodash.mapkeys@4.6.0: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash.mergewith@4.6.2: {}
|
||||
|
||||
lodash.omit@4.5.0: {}
|
||||
|
||||
lodash.snakecase@4.1.1: {}
|
||||
|
||||
lodash.startcase@4.4.0: {}
|
||||
@@ -11686,6 +12151,10 @@ snapshots:
|
||||
ajv: 6.12.6
|
||||
ajv-keywords: 3.5.2(ajv@6.12.6)
|
||||
|
||||
scroll-into-view-if-needed@3.0.10:
|
||||
dependencies:
|
||||
compute-scroll-into-view: 3.1.0
|
||||
|
||||
section-matter@1.0.0:
|
||||
dependencies:
|
||||
extend-shallow: 2.0.1
|
||||
@@ -11987,12 +12456,19 @@ snapshots:
|
||||
react: 18.3.1
|
||||
use-sync-external-store: 1.2.2(react@18.3.1)
|
||||
|
||||
tailwind-merge@1.14.0: {}
|
||||
|
||||
tailwind-merge@2.2.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.4
|
||||
|
||||
tailwind-merge@2.4.0: {}
|
||||
|
||||
tailwind-variants@0.1.20(tailwindcss@3.4.6):
|
||||
dependencies:
|
||||
tailwind-merge: 1.14.0
|
||||
tailwindcss: 3.4.6
|
||||
|
||||
tailwindcss-animate@1.0.7(tailwindcss@3.4.6):
|
||||
dependencies:
|
||||
tailwindcss: 3.4.6
|
||||
|
||||
Reference in New Issue
Block a user