Created
December 7, 2023 07:46
-
-
Save therealdhrxv/ec13127d1474c0982a24b321ef77e511 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use client"; | |
import qs from "query-string"; | |
import axios from "axios"; | |
import { useRouter } from "next/navigation"; | |
import { | |
Check, | |
Gavel, | |
Loader2, | |
MoreVertical, | |
Shield, | |
ShieldAlert, | |
ShieldCheck, | |
ShieldQuestion, | |
} from "lucide-react"; | |
import { useState } from "react"; | |
import { MemberRole } from "@prisma/client"; | |
import { useModal } from "@/hooks/use-modal-store"; | |
import { ScrollArea } from "@/components/ui/scroll-area"; | |
import { UserAvatar } from "@/components/user-avatar"; | |
import { ServerWithMembersWithProfiles } from "@/types"; | |
import { | |
Dialog, | |
DialogContent, | |
DialogDescription, | |
DialogHeader, | |
DialogTitle, | |
} from "@/components/ui/dialog"; | |
import { | |
DropdownMenu, | |
DropdownMenuItem, | |
DropdownMenuContent, | |
DropdownMenuPortal, | |
DropdownMenuSeparator, | |
DropdownMenuSub, | |
DropdownMenuSubContent, | |
DropdownMenuTrigger, | |
DropdownMenuSubTrigger, | |
} from "@/components/ui/dropdown-menu"; | |
const roleIconMap = { | |
GUEST: null, | |
MODERATOR: <ShieldCheck className="w-4 h-4 ml-2 text-indigo-500" />, | |
ADMIN: <ShieldAlert className="w-4 h-4 ml-2 text-red-500" />, | |
}; | |
export const MembersModal = () => { | |
const router = useRouter(); | |
const { type, isOpen, onClose, data, onOpen } = useModal(); | |
const [loadingId, setLoadingId] = useState(""); | |
const isModalOpen = isOpen && type === "members"; | |
const { server } = data as { | |
server: ServerWithMembersWithProfiles; | |
}; | |
const onKick = async (memberId: string) => { | |
try { | |
setLoadingId(memberId); | |
const url = qs.stringifyUrl({ | |
url: `/api/members/${memberId}`, | |
query: { | |
serverId: server.id, | |
}, | |
}); | |
const response = await axios.delete(url); | |
router.refresh(); | |
onOpen("members", { server: response.data }); | |
} catch (error) { | |
console.log(error); | |
} finally { | |
setLoadingId(""); | |
} | |
}; | |
const onRoleChange = async (memberId: string, role: MemberRole) => { | |
try { | |
setLoadingId(memberId); | |
const url = qs.stringifyUrl({ | |
url: `/api/members/${memberId}`, | |
query: { | |
serverId: server.id, | |
}, | |
}); | |
const response = await axios.patch(url, { role }); | |
router.refresh(); | |
onOpen("members", { server: response.data }); | |
} catch (error) { | |
console.error(error); | |
} finally { | |
setLoadingId(""); | |
} | |
}; | |
return ( | |
<> | |
<Dialog open={isModalOpen} onOpenChange={onClose}> | |
<DialogContent className="bg-white text-black overflow-hidden"> | |
<DialogHeader className="pt-8 px-6"> | |
<DialogTitle className="text-2xl text-center font-bold"> | |
Manage Members | |
</DialogTitle> | |
<DialogDescription className="text-center text-zinc-500"> | |
{server?.members?.length} members | |
</DialogDescription> | |
</DialogHeader> | |
<ScrollArea className="mt-8 max-h-[420px] pr-6"> | |
{server?.members?.map((member) => ( | |
<div | |
key={member.id} | |
className="flex items-center gap-x-2 mb-6" | |
> | |
<UserAvatar | |
src={member.profile.imageURL} | |
/> | |
<div className="flex flex-col gap-y-1"> | |
<div className="text-xs font-semibold flex items-center gap-x-1"> | |
{member.profile.name} | |
{roleIconMap[member.role]} | |
</div> | |
<p className="text-xs text-zinc-500"> | |
{member.profile.email} | |
</p> | |
</div> | |
{server.profileId !== member.profile.id && | |
loadingId !== member.id && ( | |
<div className="ml-auto"> | |
<DropdownMenu> | |
<DropdownMenuTrigger> | |
<MoreVertical className="w-4 h-4 text-zinc-500" /> | |
</DropdownMenuTrigger> | |
<DropdownMenuContent side="left"> | |
<DropdownMenuSub> | |
<DropdownMenuSubTrigger> | |
<ShieldQuestion className="w-4 h-4 mr-2" /> | |
<span> | |
Role | |
</span> | |
</DropdownMenuSubTrigger> | |
<DropdownMenuPortal> | |
<DropdownMenuSubContent> | |
<DropdownMenuItem | |
onClick={() => { | |
onRoleChange( | |
member.id, | |
"GUEST" | |
); | |
}} | |
> | |
<Shield className="w-4 h-4 mr-2" /> | |
Guest | |
{member.role === | |
"GUEST" && ( | |
<Check className="h-4 w-4 ml-auto" /> | |
)} | |
</DropdownMenuItem> | |
<DropdownMenuItem | |
onClick={() => { | |
onRoleChange( | |
member.id, | |
"MODERATOR" | |
); | |
}} | |
> | |
<ShieldCheck className="w-4 h-4 mr-2" /> | |
Moderator | |
{member.role === | |
"MODERATOR" && ( | |
<Check className="h-4 w-4 ml-auto" /> | |
)} | |
</DropdownMenuItem> | |
</DropdownMenuSubContent> | |
</DropdownMenuPortal> | |
</DropdownMenuSub> | |
<DropdownMenuSeparator /> | |
<DropdownMenuItem | |
onClick={() => { | |
onKick( | |
member.id | |
); | |
}} | |
> | |
<Gavel className="w-4 h-4 mr-2" /> | |
Kick | |
</DropdownMenuItem> | |
</DropdownMenuContent> | |
</DropdownMenu> | |
</div> | |
)} | |
{loadingId === member.id && ( | |
<Loader2 className="animate-spin text-zinc-500 ml-auto w-4 h-4" /> | |
)} | |
</div> | |
))} | |
</ScrollArea> | |
<div className="p-6">Hello members</div> | |
</DialogContent> | |
</Dialog> | |
</> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { NextResponse } from "next/server"; | |
import { currentProfile } from "@/lib/current-profile"; | |
import { db } from "@/lib/db"; | |
export async function DELETE( | |
req: Request, | |
{ params }: { params: { memberId: string } } | |
) { | |
try { | |
const profile = await currentProfile(); | |
const { searchParams } = new URL(req.url); | |
const serverId = searchParams.get("serverId"); | |
if (!profile) { | |
return new NextResponse("Unauthorized", { status: 401 }); | |
} | |
if (!serverId) { | |
return new NextResponse("Server ID missing", { status: 400 }); | |
} | |
if (!params.memberId) { | |
return new NextResponse("Member ID missing", { status: 400 }); | |
} | |
const server = db.server.update({ | |
where: { | |
id: serverId, | |
profileId: profile.id, | |
}, | |
data: { | |
members: { | |
deleteMany: { | |
id: params.memberId, | |
profileId: { | |
not: profile.id, | |
}, | |
}, | |
}, | |
}, | |
include: { | |
members: { | |
include: { | |
profile: true, | |
}, | |
orderBy: { | |
role: "asc", | |
}, | |
}, | |
}, | |
}); | |
return NextResponse.json(server); | |
} catch (error) { | |
console.log("[MEMBERS_ID_DELETE]: ", error); | |
return new NextResponse("Internal Error", { | |
status: 500, | |
}); | |
} | |
} | |
export async function PATCH( | |
req: Request, | |
{ params }: { params: { memberId: string } } | |
) { | |
try { | |
const profile = await currentProfile(); | |
const { searchParams } = new URL(req.url); | |
const { role } = await req.json(); | |
const serverId = searchParams.get("serverId"); | |
if (!profile) { | |
return new NextResponse("Unauthorized", { status: 401 }); | |
} | |
if (!serverId) { | |
return new NextResponse("Server ID missing", { status: 400 }); | |
} | |
if (!params.memberId) { | |
return new NextResponse("Member ID missing", { status: 400 }); | |
} | |
const server = await db.server.update({ | |
where: { | |
id: serverId, | |
profileId: profile.id, | |
}, | |
data: { | |
members: { | |
update: { | |
where: { | |
id: params.memberId, | |
profileId: { | |
not: profile.id, | |
}, | |
}, | |
data: { | |
role: role, | |
}, | |
}, | |
}, | |
}, | |
include: { | |
members: { | |
include: { | |
profile: true, | |
}, | |
orderBy: { | |
role: "asc", | |
}, | |
}, | |
}, | |
}); | |
return NextResponse.json(server); | |
} catch (error) { | |
console.error("[MEMBERS_ID_PATCH]: ", error); | |
return new NextResponse("Internal Error", { | |
status: 500, | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment