added channel list and members to server sidebar

This commit is contained in:
Bob Burningham 2023-10-18 14:56:26 -07:00
parent f8f4f0f94a
commit d3cdd24d80
7 changed files with 263 additions and 5 deletions

View File

@ -33,6 +33,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue SelectValue
} from "@/components/ui/select"; } from "@/components/ui/select";
import { useEffect } from "react";
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(1, { name: z.string().min(1, {
@ -47,20 +48,30 @@ const formSchema = z.object({
}); });
export const CreateChannelModal = () => { export const CreateChannelModal = () => {
const { isOpen, onClose, type } = useModal(); const { isOpen, onClose, type, data } = useModal();
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
const isModalOpen = isOpen && type === "createChannel"; const isModalOpen = isOpen && type === "createChannel";
const { channelType } = data;
const form = useForm({ const form = useForm({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
name: "", name: "",
type: ChannelType.TEXT, type: channelType || ChannelType.TEXT,
} }
}); });
useEffect(() => {
if (channelType) {
form.setValue("type", channelType);
}
else{
form.setValue("type", ChannelType.TEXT);
}
}, [channelType, form])
const isLoading = form.formState.isSubmitting; const isLoading = form.formState.isSubmitting;
const onSubmit = async (values: z.infer<typeof formSchema>) => { const onSubmit = async (values: z.infer<typeof formSchema>) => {

View File

@ -0,0 +1,64 @@
"use client"
import { cn } from "@/lib/utils";
import { Channel, ChannelType, MemberRole, Server } from "@prisma/client";
import { Edit, Hash, Lock, Mic, Trash, Video } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { ActionTooltip } from "@/components/action-tooltip";
interface ServerChannelProps {
channel: Channel;
server: Server;
role?: MemberRole;
}
const iconMap = {
[ChannelType.TEXT]: Hash,
[ChannelType.AUDIO]: Mic,
[ChannelType.VIDEO]: Video
}
export const ServerChannel = ({
channel,
server,
role
}: ServerChannelProps) => {
const params = useParams();
const router = useRouter();
const Icon = iconMap[channel.type];
return (
<button
onClick={() => {}}
className={cn(
"group px-2 py-2 rounded-md flex items-center gap-x-2 w-full hover:bg-zinc-700/10 dark:hover:bg-zinc-700/50 transition mb-1",
params?.channelId === channel.id && "bg-zinc-700/10 dark:bg-zinc-700"
)}
>
<Icon className="flex-shrink-0 w-5 h-5 text-zinc-500 dark:text-zinc-400"/>
<p className={cn(
"link-clamp-1 font-semibold text-sm text-zinc-500 group-hover:text-zinc-600 dark:text-zinc-400 dark:group-hover:text-zinc-300 transition",
params?.channelId === channel.id && "text-primary dark:text-zinc-200 dark:group-hover:text-white"
)}>
{channel.name}
</p>
{channel.name !== "general" && role !== MemberRole.GUEST && (
<div className="ml-auto flex items-center gap-x-2">
<ActionTooltip label="Edit">
<Edit className="hidden group-hover:block w-4 h-4 text-zinc-500 hove:text-zinc-600 dark:text-zinc-400 dark:hove:text-zinc-300 transition"
/>
</ActionTooltip>
<ActionTooltip label="Delete">
<Trash className="hidden group-hover:block w-4 h-4 text-zinc-500 hove:text-zinc-600 dark:text-zinc-400 dark:hove:text-zinc-300 transition"
/>
</ActionTooltip>
</div>
)}
{channel.name === "general" && (
<Lock className="ml-auto w-4 h-4 text-zinc-500 dark:text-zinc-400"/>
)}
</button>
)
}

View File

@ -11,7 +11,7 @@ interface ServerHeaderProps {
role?: MemberRole; role?: MemberRole;
} }
export const SeverHeader = ({server, role}: ServerHeaderProps) => { export const ServerHeader = ({server, role}: ServerHeaderProps) => {
const { onOpen } = useModal(); const { onOpen } = useModal();
const isAdmin = role === MemberRole.ADMIN; const isAdmin = role === MemberRole.ADMIN;
const isModerator = isAdmin || role === MemberRole.MODERATOR; const isModerator = isAdmin || role === MemberRole.MODERATOR;

View File

@ -0,0 +1,49 @@
"use client"
import { cn } from "@/lib/utils";
import { Member, MemberRole, Profile, Server } from "@prisma/client"
import { ShieldAlert, ShieldCheck } from "lucide-react";
import { useParams } from "next/navigation";
import { useRouter } from "next/router";
import { UserAvatar } from "@/components/user-avatar";
interface ServerMemberProps {
member: Member & {
profile: Profile;
};
server: Server;
}
const roleIconMap = {
[MemberRole.GUEST]: null,
[MemberRole.MODERATOR]: <ShieldCheck className="h-4 w-4 ml-2 text-indigo-500" />,
[MemberRole.ADMIN]: <ShieldAlert className="w-4 h-4 ml-2 text-rose-500" />
}
export const ServerMember = ({
member,
server
}: ServerMemberProps) => {
const params = useParams();
//const router = useRouter();
const icon = roleIconMap[member.role];
return (
<button className={cn(
"group px-2 py-2 rounded-md flex items-center gap-x-2 w-full hover:bg-zinc-700/10 dark:hover:bg-zinc-700/50 transition mb-1",
params?.memberId === member.id && "bg-zinc-700/20 dark:bg-zinc-700"
)}>
<UserAvatar src={member.profile.imageUrl}
className="h-8 w-8 md:h-8 md:w-8"
/>
<p
className={cn(
"font-semibold text-sm text-zinc-500 group-hover:text-zinc-600 dark:text-zinc-400 dark:group-hover:text-zinc-300 transition",
)}
>
{member.profile.name}
</p>
{icon}
</button>
)
}

View File

@ -0,0 +1,47 @@
"use client"
import { ServerWithMembersWithProfiles } from "@/types";
import { ChannelType, MemberRole } from "@prisma/client";
import { ActionTooltip } from "../action-tooltip";
import { Plus, Settings } from "lucide-react";
import { useModal } from "@/hooks/use-modal-store";
interface ServerSectionProps {
label: string;
role?: MemberRole;
sectionType: "channels" | "members";
channelType?: ChannelType;
server?: ServerWithMembersWithProfiles;
}
export const ServerSection = ({
label,
role,
sectionType,
channelType,
server
}: ServerSectionProps)=> {
const {onOpen } = useModal();
return (
<div className="flex items-center justify-between py-2">
<p className="text-xs uppercase font-semibold text-zinc-500 dark:text-zinc-400">
{label}
</p>
{role !== MemberRole.GUEST && sectionType === "channels" && (
<ActionTooltip label="Create Channel" side="top">
<button onClick={() => onOpen("createChannel", {channelType})} className="text-zinc-500 hove:text-zinc-600 dark:text-zinc-400 dark:hover:text-zinc-300 transition">
<Plus className="h-4 w-4"/>
</button>
</ActionTooltip>
)}
{role === MemberRole.ADMIN && sectionType === "members" && (
<ActionTooltip label="Manage Members" side="top">
<button onClick={() => onOpen("members", {server})} className="text-zinc-500 hove:text-zinc-600 dark:text-zinc-400 dark:hover:text-zinc-300 transition">
<Settings className="h-4 w-4"/>
</button>
</ActionTooltip>
)}
</div>
)
}

View File

@ -5,9 +5,14 @@ import { Hash, Mic, ShieldAlert, ShieldCheck, Video } from "lucide-react";
import { currentProfile } from "@/lib/current-profile"; import { currentProfile } from "@/lib/current-profile";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import { Separator } from "@/components/ui/separator";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { ServerHeader } from "./server-header"; import { ServerHeader } from "./server-header";
import { ServerSearch } from "./server-search"; import { ServerSearch } from "./server-search";
import { ServerSection } from "./server-section";
import { ServerChannel } from "./server-channel";
import { ServerMember } from "./server-member";
interface ServerSidebarProps { interface ServerSidebarProps {
@ -116,6 +121,87 @@ export const ServerSidebar = async ({
]} ]}
/> />
</div> </div>
<Separator className="bg-zinc-200 dark:bg-zinc-700 rounded-md my-2"/>
{!!textChannels?.length && (
<div className="mb-2">
<ServerSection
sectionType="channels"
channelType={ChannelType.TEXT}
role={role}
label="Text Channels"
/>
<div className="space-y-[2px]">
{textChannels?.map((channel) => (
<ServerChannel
key={channel.id}
channel={channel}
role={role}
server={server}
/>
))}
</div>
</div>
)}
{!!audioChannels?.length && (
<div className="mb-2">
<ServerSection
sectionType="channels"
channelType={ChannelType.AUDIO}
role={role}
label="Voice Channels"
/>
<div className="space-y-[2px]">
{audioChannels?.map((channel) => (
<ServerChannel
key={channel.id}
channel={channel}
role={role}
server={server}
/>
))}
</div>
</div>
)}
{!!videoChannels?.length && (
<div className="mb-2">
<ServerSection
sectionType="channels"
channelType={ChannelType.VIDEO}
role={role}
label="Video Channels"
/>
<div className="space-y-[2px]">
{videoChannels?.map((channel) => (
<ServerChannel
key={channel.id}
channel={channel}
role={role}
server={server}
/>
))}
</div>
</div>
)}
{!!members?.length && (
<div className="mb-2">
<ServerSection
sectionType="members"
channelType={ChannelType.VIDEO}
role={role}
label="Members"
server={server}
/>
<div className="space-y-[2px]">
{members?.map((member) => (
<ServerMember
key={member.id}
member={member}
server={server}
/>
))}
</div>
</div>
)}
</ScrollArea> </ScrollArea>
</div> </div>
) )

View File

@ -1,11 +1,12 @@
import { Server } from "@prisma/client"; import { ChannelType, Server } from "@prisma/client";
import { create } from "zustand"; import { create } from "zustand";
export type ModalType = "createServer" | "invite" | "editServer" | "members" | "createChannel" | "leaveServer" | "deleteServer"; export type ModalType = "createServer" | "invite" | "editServer" | "members" | "createChannel" | "leaveServer" | "deleteServer";
interface ModalData { interface ModalData {
server?: Server server?: Server;
channelType?: ChannelType;
} }
interface ModalStore { interface ModalStore {