add url search params

This commit is contained in:
oiov
2025-03-23 23:02:54 +08:00
parent 731728c94c
commit a3b27361ec
10 changed files with 114 additions and 17 deletions
+6 -1
View File
@@ -27,7 +27,12 @@ export default async function DashboardPage() {
/>
<LiveLog admin={true} />
<UserUrlsList
user={{ id: user.id, name: user.name || "", apiKey: user.apiKey || "" }}
user={{
id: user.id,
name: user.name || "",
apiKey: user.apiKey || "",
role: user.role,
}}
action="/api/url/admin"
/>
</>
+1
View File
@@ -64,6 +64,7 @@ export default async function DashboardPage() {
id: user.id,
name: user.name || "",
apiKey: user.apiKey || "",
role: user.role,
}}
action="/api/url"
/>
+12 -8
View File
@@ -150,7 +150,9 @@ export default function LiveLog({ admin }: { admin: boolean }) {
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-base text-gray-800">Live Log</CardTitle>
<CardTitle className="text-base text-gray-800 dark:text-gray-100">
Live Log
</CardTitle>
<CardDescription>
Real-time updates of short URL visits.
</CardDescription>
@@ -161,7 +163,9 @@ export default function LiveLog({ admin }: { admin: boolean }) {
variant={"outline"}
size="sm"
className={`ml-auto gap-2 transition-colors hover:border-blue-600 hover:text-blue-600 ${
isLive ? "border-blue-600 text-blue-500" : ""
isLive
? "animate-pulse border-dashed border-blue-600 text-blue-500"
: ""
}`}
>
<Icons.CirclePlay className="h-4 w-4" /> {isLive ? "Stop" : "Live"}
@@ -171,7 +175,7 @@ export default function LiveLog({ admin }: { admin: boolean }) {
variant={"outline"}
size="sm"
onClick={() => handleRefresh()}
disabled={isLoading}
disabled={!isLive}
>
{isLoading ? (
<RefreshCwIcon className="size-4 animate-spin" />
@@ -190,17 +194,17 @@ export default function LiveLog({ admin }: { admin: boolean }) {
}`}
disabled={logs.length === 0}
>
<Icons.trash className="h-4 w-4" />
<Icons.paintbrush className="h-4 w-4" />
</Button>
</div>
</CardHeader>
<CardContent>
<CardContent className={"pb-0" + (logs.length > 0 ? " pb-6" : "")}>
{error ? (
<div className="text-center text-red-500">{error.message}</div>
) : logs.length === 0 && !newLogs ? (
<Skeleton className="h-8 w-full" />
// <Skeleton className="h-8 w-full" />
<></>
) : (
// <></>
<div className="scrollbar-hidden h-96 overflow-y-auto">
<Table>
<TableHeader>
@@ -213,7 +217,7 @@ export default function LiveLog({ admin }: { admin: boolean }) {
<TableHead className="h-8 w-1/12 px-1">Clicks</TableHead>
</TableRow>
</TableHeader>
<TableBody className="">
<TableBody>
<AnimatePresence initial={false}>
{logs.map((log, index) => (
<motion.tr
+6 -1
View File
@@ -27,7 +27,12 @@ export default async function DashboardPage() {
/>
<LiveLog admin={false} />
<UserUrlsList
user={{ id: user.id, name: user.name || "", apiKey: user.apiKey || "" }}
user={{
id: user.id,
name: user.name || "",
apiKey: user.apiKey || "",
role: user.role,
}}
action="/api/url"
/>
</>
+55 -5
View File
@@ -23,6 +23,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Skeleton } from "@/components/ui/skeleton";
import {
Table,
@@ -49,7 +50,7 @@ import { PaginationWrapper } from "@/components/shared/pagination";
import UserUrlMetaInfo from "./meta";
export interface UrlListProps {
user: Pick<User, "id" | "name" | "apiKey">;
user: Pick<User, "id" | "name" | "apiKey" | "role">;
action: string;
}
@@ -91,17 +92,29 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
const [pageSize, setPageSize] = useState(10);
const [isShowStats, setShowStats] = useState(false);
const [selectedUrlId, setSelectedUrlId] = useState("");
const [searchParams, setSearchParams] = useState({
slug: "",
target: "",
userName: "",
});
const { mutate } = useSWRConfig();
const { data, error, isLoading } = useSWR<{
total: number;
list: ShortUrlFormData[];
}>(`${action}?page=${currentPage}&size=${pageSize}`, fetcher, {
revalidateOnFocus: false,
});
}>(
`${action}?page=${currentPage}&size=${pageSize}&slug=${searchParams.slug}&userName=${searchParams.userName}&target=${searchParams.target}`,
fetcher,
{
revalidateOnFocus: false,
},
);
const handleRefresh = () => {
mutate(`${action}?page=${currentPage}&size=${pageSize}`, undefined);
mutate(
`${action}?page=${currentPage}&size=${pageSize}&slug=${searchParams.slug}&userName=${searchParams.userName}&target=${searchParams.target}`,
undefined,
);
};
return (
@@ -148,6 +161,41 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
</div>
</CardHeader>
<CardContent>
<div className="mb-2 flex-row items-center sm:flex sm:gap-2">
<Input
className=""
placeholder="Search by slug..."
value={searchParams.slug}
onChange={(e) => {
setSearchParams({
...searchParams,
slug: e.target.value,
});
}}
/>
<Input
placeholder="Search by target..."
value={searchParams.target}
onChange={(e) => {
setSearchParams({
...searchParams,
target: e.target.value,
});
}}
/>
{user.role === "ADMIN" && (
<Input
placeholder="Search by user name..."
value={searchParams.userName}
onChange={(e) => {
setSearchParams({
...searchParams,
userName: e.target.value,
});
}}
/>
)}
</div>
{isShowForm && (
<UrlForm
user={{ id: user.id, name: user.name || "" }}
@@ -191,6 +239,8 @@ export default function UserUrlsList({ user, action }: UrlListProps) {
<TableColumnSekleton />
<TableColumnSekleton />
<TableColumnSekleton />
<TableColumnSekleton />
<TableColumnSekleton />
</>
) : data && data.list && data.list.length ? (
data.list.map((short) => (
+8
View File
@@ -17,16 +17,24 @@ export async function GET(req: Request) {
const url = new URL(req.url);
const page = url.searchParams.get("page");
const size = url.searchParams.get("size");
const userName = url.searchParams.get("userName") || "";
const slug = url.searchParams.get("slug") || "";
const target = url.searchParams.get("target") || "";
const data = await getUserShortUrls(
user.id,
1,
Number(page || "1"),
Number(size || "10"),
"ADMIN",
userName,
slug,
target,
);
return Response.json(data);
} catch (error) {
console.log(error);
return Response.json(error?.statusText || error, {
status: error.status || 500,
statusText: error.statusText || "Server error",
+7
View File
@@ -11,11 +11,18 @@ export async function GET(req: Request) {
const url = new URL(req.url);
const page = url.searchParams.get("page");
const size = url.searchParams.get("size");
const userName = url.searchParams.get("userName") || "";
const slug = url.searchParams.get("slug") || "";
const target = url.searchParams.get("target") || "";
const data = await getUserShortUrls(
user.id,
1,
Number(page || "1"),
Number(size || "10"),
"USER",
userName,
slug,
target,
);
return Response.json(data);
+2
View File
@@ -33,6 +33,7 @@ import {
Moon,
MoreVertical,
Package,
Paintbrush,
Plus,
QrCode,
Search,
@@ -62,6 +63,7 @@ export const Icons = {
fileText: FileText,
dashboard: LayoutPanelLeft,
ellipsis: MoreVertical,
paintbrush: Paintbrush,
github: ({ ...props }: LucideProps) => (
<svg
aria-hidden="true"
+16 -1
View File
@@ -27,14 +27,29 @@ export async function getUserShortUrls(
page: number,
size: number,
role: UserRole = "USER",
userName: string = "",
url: string = "",
target: string = "",
) {
const option =
let option: any =
role === "USER"
? {
userId,
active,
}
: {};
if (userName) {
option.userName = userName;
}
if (url) {
option.url = url;
}
if (target) {
option.target = target;
}
console.log(option);
const [total, list] = await prisma.$transaction([
prisma.userUrl.count({
where: option,
+1 -1
View File
File diff suppressed because one or more lines are too long