forked from administration/panel
feat: search users by username or tag
parent
9c531f3d99
commit
a3e5db0886
|
@ -3,7 +3,7 @@
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { toast } from "@/components/ui/use-toast";
|
import { toast } from "@/components/ui/use-toast";
|
||||||
import { lookupEmail } from "@/lib/actions";
|
import { lookupEmail, searchUserByTag } from "@/lib/actions";
|
||||||
import { API_URL } from "@/lib/constants";
|
import { API_URL } from "@/lib/constants";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -11,6 +11,8 @@ import { useState } from "react";
|
||||||
export default function Inspect() {
|
export default function Inspect() {
|
||||||
const [id, setId] = useState("");
|
const [id, setId] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [discriminator, setDiscriminator] = useState("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const searchEmail = async () => {
|
const searchEmail = async () => {
|
||||||
|
@ -31,6 +33,29 @@ export default function Inspect() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const searchUsername = async () => {
|
||||||
|
try {
|
||||||
|
if (!discriminator) {
|
||||||
|
// Display all users with this username
|
||||||
|
router.push(`/panel/inspect/search?username=${encodeURIComponent(username)}`);
|
||||||
|
} else {
|
||||||
|
// Show the specific user that matches username#discriminator
|
||||||
|
const result = await searchUserByTag(username, discriminator);
|
||||||
|
if (!result) toast({
|
||||||
|
title: "Couldn't find user",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
else router.push(`/panel/inspect/user/${result}`);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
toast({
|
||||||
|
title: "Failed to search",
|
||||||
|
description: String(e),
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const createHandler = (type: string) => () =>
|
const createHandler = (type: string) => () =>
|
||||||
router.push(`/panel/inspect/${type}/${id}`);
|
router.push(`/panel/inspect/${type}/${id}`);
|
||||||
|
|
||||||
|
@ -41,7 +66,7 @@ export default function Inspect() {
|
||||||
value={id}
|
value={id}
|
||||||
onChange={(e) => setId(e.currentTarget.value)}
|
onChange={(e) => setId(e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-col md:flex-row gap-2">
|
||||||
<Button
|
<Button
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
@ -92,7 +117,8 @@ export default function Inspect() {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className="flex gap-2 justify-between">
|
<div className="flex flex-col lg:flex-row gap-2 w-full">
|
||||||
|
<div className="flex gap-2 justify-between grow">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Enter an email..."
|
placeholder="Enter an email..."
|
||||||
value={email}
|
value={email}
|
||||||
|
@ -108,6 +134,33 @@ export default function Inspect() {
|
||||||
Lookup
|
Lookup
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-2 justify-between grow">
|
||||||
|
<div className="flex flex-row items-center w-full gap-1">
|
||||||
|
<Input
|
||||||
|
placeholder="Username"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.currentTarget.value)}
|
||||||
|
onKeyDown={(e) => e.key == "Enter" && username && searchUsername()}
|
||||||
|
/>
|
||||||
|
<span className="select-none text-gray-500">#</span>
|
||||||
|
<Input
|
||||||
|
placeholder="0000"
|
||||||
|
value={discriminator}
|
||||||
|
onChange={(e) => setDiscriminator(e.currentTarget.value)}
|
||||||
|
onKeyDown={(e) => e.key == "Enter" && username && searchUsername()}
|
||||||
|
className="flex-shrink-[2]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="flex"
|
||||||
|
variant="outline"
|
||||||
|
disabled={!username}
|
||||||
|
onClick={searchUsername}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { UserCard } from "@/components/cards/UserCard";
|
||||||
|
import { fetchUsersByUsername } from "@/lib/actions";
|
||||||
|
import { SearchX } from "lucide-react";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default async function Search({ searchParams }: { searchParams: any }) {
|
||||||
|
const username = searchParams.username;
|
||||||
|
|
||||||
|
if (!username) return redirect("/panel/inspect");
|
||||||
|
const users = await fetchUsersByUsername(username);
|
||||||
|
|
||||||
|
if (!users.length) return (
|
||||||
|
<>
|
||||||
|
<h2 className="mt-8 flex justify-center">
|
||||||
|
<SearchX className="text-gray-400" />
|
||||||
|
</h2>
|
||||||
|
<h3 className="text-xs text-center pb-2 text-gray-400">
|
||||||
|
No search results
|
||||||
|
</h3>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{
|
||||||
|
users.map((user) => (
|
||||||
|
<a key={user._id} href={`/panel/inspect/user/${user._id}`}>
|
||||||
|
<UserCard user={user} subtitle={user._id} />
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -62,6 +62,8 @@ type Permission =
|
||||||
| `/fetch${
|
| `/fetch${
|
||||||
| ""
|
| ""
|
||||||
| "/by-id"
|
| "/by-id"
|
||||||
|
| "/by-tag"
|
||||||
|
| "/bulk-by-username"
|
||||||
| "/memberships"
|
| "/memberships"
|
||||||
| "/strikes"
|
| "/strikes"
|
||||||
| "/notices"
|
| "/notices"
|
||||||
|
@ -163,6 +165,8 @@ const PermissionSets = {
|
||||||
// Moderate users
|
// Moderate users
|
||||||
"moderate-users": [
|
"moderate-users": [
|
||||||
"users/fetch/by-id",
|
"users/fetch/by-id",
|
||||||
|
"users/fetch/by-tag",
|
||||||
|
"users/fetch/bulk-by-username",
|
||||||
"users/fetch/strikes",
|
"users/fetch/strikes",
|
||||||
"users/fetch/notices",
|
"users/fetch/notices",
|
||||||
|
|
||||||
|
|
|
@ -1027,3 +1027,29 @@ export async function deleteEmailClassification(domain: string) {
|
||||||
{ _id: domain },
|
{ _id: domain },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function searchUserByTag(username: string, discriminator: string): Promise<string | false> {
|
||||||
|
await checkPermission("users/fetch/by-tag", { username, discriminator });
|
||||||
|
|
||||||
|
const result = await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<User>("users")
|
||||||
|
.findOne({
|
||||||
|
username,
|
||||||
|
discriminator,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result?._id || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchUsersByUsername(username: string) {
|
||||||
|
await checkPermission("users/fetch/bulk-by-username", { username });
|
||||||
|
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<User>("users")
|
||||||
|
.find({
|
||||||
|
username,
|
||||||
|
})
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue