add url search params
This commit is contained in:
@@ -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"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -64,6 +64,7 @@ export default async function DashboardPage() {
|
||||
id: user.id,
|
||||
name: user.name || "",
|
||||
apiKey: user.apiKey || "",
|
||||
role: user.role,
|
||||
}}
|
||||
action="/api/url"
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user