From 2e66a35c19db10642c3670a7a4931ec7dfe43a1d Mon Sep 17 00:00:00 2001 From: rohannrk Date: Sat, 23 Sep 2023 12:19:36 +0530 Subject: [PATCH] Chat header and Conversation --- .../[serverId]/channels/[channelId]/page.tsx | 100 +++++++++++- .../conversations/[memberId]/page.tsx | 68 ++++++++- components/chat/chat-header.tsx | 36 +++++ components/mobile-toggle.tsx | 32 ++++ components/ui/sheet.tsx | 144 ++++++++++++++++++ lib/conversation.ts | 63 ++++++++ 6 files changed, 431 insertions(+), 12 deletions(-) create mode 100644 components/chat/chat-header.tsx create mode 100644 components/mobile-toggle.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 lib/conversation.ts diff --git a/app/(main)/(routes)/servers/[serverId]/channels/[channelId]/page.tsx b/app/(main)/(routes)/servers/[serverId]/channels/[channelId]/page.tsx index 90cada3..930f618 100644 --- a/app/(main)/(routes)/servers/[serverId]/channels/[channelId]/page.tsx +++ b/app/(main)/(routes)/servers/[serverId]/channels/[channelId]/page.tsx @@ -1,9 +1,97 @@ -const ChannelIdPage = () => { - return ( -
- Channel ID Page! -
- ); +import { redirectToSignIn } from "@clerk/nextjs"; +import { redirect } from "next/navigation"; +import { ChannelType } from "@prisma/client"; + +import { currentProfile } from "@/lib/current-profile"; +import { ChatHeader } from "@/components/chat/chat-header"; +// import { ChatInput } from "@/components/chat/chat-input"; +// import { ChatMessages } from "@/components/chat/chat-messages"; +// import { MediaRoom } from "@/components/media-room"; +import { db } from "@/lib/db"; + +interface ChannelIdPageProps { + params: { + serverId: string; + channelId: string; + } } +const ChannelIdPage = async ({ + params +}: ChannelIdPageProps) => { + const profile = await currentProfile(); + + if (!profile) { + return redirectToSignIn(); + } + + const channel = await db.channel.findUnique({ + where: { + id: params.channelId, + }, + }); + + const member = await db.member.findFirst({ + where: { + serverId: params.serverId, + profileId: profile.id, + } + }); + + if (!channel || !member) { + redirect("/"); + } + + return ( +
+ + {/* {channel.type === ChannelType.TEXT && ( + <> + + + + )} + {channel.type === ChannelType.AUDIO && ( + + )} + {channel.type === ChannelType.VIDEO && ( + + )} */} +
+ ); +} + export default ChannelIdPage; \ No newline at end of file diff --git a/app/(main)/(routes)/servers/[serverId]/conversations/[memberId]/page.tsx b/app/(main)/(routes)/servers/[serverId]/conversations/[memberId]/page.tsx index 3b5585b..935b8a7 100644 --- a/app/(main)/(routes)/servers/[serverId]/conversations/[memberId]/page.tsx +++ b/app/(main)/(routes)/servers/[serverId]/conversations/[memberId]/page.tsx @@ -1,9 +1,65 @@ -const MemberIdPage = () => { - return ( -
- Member ID Page! -
- ); +import { redirectToSignIn } from "@clerk/nextjs"; +import { redirect } from "next/navigation"; + +import { db } from "@/lib/db"; +import { getOrCreateConversation } from "@/lib/conversation"; +import { currentProfile } from "@/lib/current-profile"; +import { ChatHeader } from "@/components/chat/chat-header"; + +interface MemberIdPageProps { + params: { + memberId: string; + serverId: string; + }, + searchParams: { + video?: boolean; + } } +const MemberIdPage = async ({ + params, + searchParams, +}: MemberIdPageProps) => { + const profile = await currentProfile(); + + if (!profile) { + return redirectToSignIn(); + } + + const currentMember = await db.member.findFirst({ + where: { + serverId: params.serverId, + profileId: profile.id, + }, + include: { + profile: true, + }, + }); + + if (!currentMember) { + return redirect("/"); + } + + const conversation = await getOrCreateConversation(currentMember.id, params.memberId); + + if (!conversation) { + return redirect(`/servers/${params.serverId}`); + } + + const { memberOne, memberTwo } = conversation; + + const otherMember = memberOne.profileId === profile.id ? memberTwo : memberOne; + + return ( +
+ +
+ ); +} + export default MemberIdPage; \ No newline at end of file diff --git a/components/chat/chat-header.tsx b/components/chat/chat-header.tsx new file mode 100644 index 0000000..aaf07a2 --- /dev/null +++ b/components/chat/chat-header.tsx @@ -0,0 +1,36 @@ +import { Hash } from "lucide-react"; + +import { UserAvatar } from "@/components/user-avatar"; +import { MobileToggle } from "../mobile-toggle"; + +interface ChatHeaderProps { + serverId: string; + name: string; + type: "channel" | "conversation"; + imageUrl?: string; +} + +export const ChatHeader = ({ + serverId, + name, + type, + imageUrl +}: ChatHeaderProps) => { + return ( +
+ + {type === "channel" && ( + + )} + {type === "conversation" && ( + + )} +

+ {name} +

+
+ ) +} \ No newline at end of file diff --git a/components/mobile-toggle.tsx b/components/mobile-toggle.tsx new file mode 100644 index 0000000..33a277f --- /dev/null +++ b/components/mobile-toggle.tsx @@ -0,0 +1,32 @@ +import { Menu } from "lucide-react" + +import { + Sheet, + SheetContent, + SheetTrigger, +} from "@/components/ui/sheet"; +import { Button } from "@/components/ui/button"; +import { NavigationSidebar } from "@/components/navigation/navigation-sidebar"; +import { ServerSidebar } from "@/components/server/server-sidebar"; + +export const MobileToggle = ({ + serverId +}: { + serverId: string; +}) => { + return ( + + + + + +
+ +
+ +
+
+ ) +} \ No newline at end of file diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx new file mode 100644 index 0000000..98cdd40 --- /dev/null +++ b/components/ui/sheet.tsx @@ -0,0 +1,144 @@ +"use client" + +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = ({ + className, + ...props +}: SheetPrimitive.DialogPortalProps) => ( + +) +SheetPortal.displayName = SheetPrimitive.Portal.displayName + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/lib/conversation.ts b/lib/conversation.ts new file mode 100644 index 0000000..436515b --- /dev/null +++ b/lib/conversation.ts @@ -0,0 +1,63 @@ +import { db } from "@/lib/db"; + +export const getOrCreateConversation = async (memberOneId: string, memberTwoId: string) => { + let conversation = await findConversation(memberOneId, memberTwoId) || await findConversation(memberTwoId, memberOneId); + + if (!conversation) { + conversation = await createNewConversation(memberOneId, memberTwoId); + } + + return conversation; +} + +const findConversation = async (memberOneId: string, memberTwoId: string) => { + try { + return await db.conversation.findFirst({ + where: { + AND: [ + { memberOneId: memberOneId }, + { memberTwoId: memberTwoId }, + ] + }, + include: { + memberOne: { + include: { + profile: true, + } + }, + memberTwo: { + include: { + profile: true, + } + } + } + }); + } catch { + return null; + } +} + +const createNewConversation = async (memberOneId: string, memberTwoId: string) => { + try { + return await db.conversation.create({ + data: { + memberOneId, + memberTwoId, + }, + include: { + memberOne: { + include: { + profile: true, + } + }, + memberTwo: { + include: { + profile: true, + } + } + } + }) + } catch { + return null; + } +} \ No newline at end of file