Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
AmimiHamza committed May 20, 2024
2 parents 177d705 + f4b14e9 commit 6aa83c7
Show file tree
Hide file tree
Showing 12 changed files with 713 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import ChatArea from "@/app/(dashboard)/_components/chat-area";
import ChatAside from "@/app/(dashboard)/_components/chat-aside";

const ConversationPage = () => {
interface ConversationPageProps {
params: {
chatId: string;
};
}

const ConversationPage = ({ params }: ConversationPageProps) => {
const { chatId } = params;
return (
<main className="flex flex-1 overflow-hidden">
<ChatArea />
<ChatArea chatId={chatId} />
<ChatAside
asideClassName="hidden lg:order-first lg:block lg:flex-shrink-0"
divClassName="gap-3 pt-2 relative flex h-full w-96 flex-col border-r"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { getInitials } from "@/lib/utils";
import { Info } from "lucide-react";

interface ChatAreaHeaderProps {
chatName: string;
isGroup?: boolean;
}
const ChatAreaHeader = ({ chatName, isGroup }: ChatAreaHeaderProps) => {
return (
<header className="h-14 w-full border-b py-2 px-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Avatar>
<AvatarFallback>
{getInitials(chatName ?? "Unknown User")}
</AvatarFallback>
</Avatar>
<span className="font-semibold">{chatName}</span>
</div>
<Button
variant="ghost"
size="icon"
className="rounded-lg group"
aria-label="Info"
>
<Info className="w-5 h-5 text-muted-foreground group-hover:text-foreground" />
</Button>
</header>
);
};

export default ChatAreaHeader;
81 changes: 62 additions & 19 deletions packages/insea-connect-ui/app/(dashboard)/_components/chat-area.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,75 @@
import { auth } from "@/auth";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
CONVERSATION_INFO_ENDPOINT,
GROUP_INFO_ENDPOINT,
USER_INFO_ENDPOINT,
} from "@/lib/constants";
import { getInitials } from "@/lib/utils";
import axios from "axios";
import { Info, Paperclip, SendHorizonal } from "lucide-react";
import ChatAreaHeader from "./chat-area-header";
import ChatMessagesList from "./chat-messages-list";

interface ChatAreaProps {
chatId: string;
}

const ChatArea = async ({ chatId }: ChatAreaProps) => {
const {
// @ts-ignore

tokens: { access_token },
} = await auth();

const {
data: { id },
} = await axios.get(USER_INFO_ENDPOINT, {
headers: {
Authorization: `Bearer ${access_token}`,
},
});

const isGroupChat = chatId.startsWith("group-");

const theChatId = chatId.split("-")[1];

const { data } = await axios.get(
`${
isGroupChat
? `${GROUP_INFO_ENDPOINT}/${theChatId}`
: `${CONVERSATION_INFO_ENDPOINT}/${theChatId}`
}`,
{
headers: {
Authorization: `Bearer ${access_token}`,
},
}
);

console.log(data);

let chatName = "";
if (isGroupChat) {
chatName = data.name;
} else {
const otherUser = data.member1.id === id ? data.member2 : data.member1;
chatName = otherUser.username;
}

const ChatArea = () => {
return (
<section
aria-labelledby="primary-heading"
className="flex h-full min-w-0 flex-1 flex-col overflow-y-auto lg:order-last"
>
<header className="h-14 w-full border-b py-2 px-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Avatar>
<AvatarImage src="/avatar.jpg" alt="John Doe" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<span className="font-semibold">John Doe</span>
</div>
<Button
variant="ghost"
size="icon"
className="rounded-lg group"
aria-label="Info"
>
<Info className="w-5 h-5 text-muted-foreground group-hover:text-foreground" />
</Button>
</header>
<div className="flex-1"></div>
<ChatAreaHeader chatName={chatName} />
<ChatMessagesList
chatId={theChatId}
isGroup={isGroupChat}
connectedUserId={id}
/>
<div className="h-14 border-t flex py-2 px-4 gap-4 items-center">
<Button
variant="ghost"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { capitalize, getInitials } from "@/lib/utils";
import { formatToTimeAgo } from "@/lib/utils";
import { useRouter } from "next/navigation";

interface ChatItemProps {
username: string;
Expand All @@ -9,6 +10,7 @@ interface ChatItemProps {
isCurrentUser?: boolean;
isGroup?: boolean;
senderName?: string;
id?: string;
}

const ChatItem = ({
Expand All @@ -18,9 +20,17 @@ const ChatItem = ({
isCurrentUser,
isGroup,
senderName,
id,
}: ChatItemProps) => {
const router = useRouter();
const chatId = isGroup ? `group-${id}` : `conv-${id}`;
return (
<div className="flex items-center py-3 px-4 rounded-md hover:bg-muted/80 cursor-pointer gap-4">
<div
className="flex items-center py-3 px-4 rounded-md hover:bg-muted/80 cursor-pointer gap-4"
onClick={() => {
router.push(`/chat/${chatId}`);
}}
>
<Avatar className="h-12 w-12">
<AvatarFallback>
{getInitials(username ?? "Unknown User")}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"use client";
import { useEffect, useRef } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { useQuery } from "@tanstack/react-query";
import useUserProfile from "@/hooks/use-user-profile";
import axios from "axios";
import { format } from "date-fns";
import { useSession } from "next-auth/react";
import {
CONVERSATION_INFO_ENDPOINT,
GROUP_INFO_ENDPOINT,
} from "@/lib/constants";
import { cn, getInitials } from "@/lib/utils";

interface ChatMessagesListProps {
chatId: string;
isGroup?: boolean;
connectedUserId?: number;
}

const DATE_FORM = "HH:mm";

const ChatMessagesList = ({
chatId,
isGroup,
connectedUserId,
}: ChatMessagesListProps) => {
const { isPending: isUserProfilePending, data: userProfile } =
useUserProfile();

const { data } = useSession();

const { data: messages, isPending: isMessagesPending } = useQuery({
queryKey: ["chat-messages", chatId],
queryFn: async () => {
const { data: result } = await axios.get(
`${
isGroup ? GROUP_INFO_ENDPOINT : CONVERSATION_INFO_ENDPOINT
}/${chatId}/messages`,
{
headers: {
Authorization: `Bearer ${data?.tokens.access_token}`,
},
}
);
return result;
},
});

const messagesContainerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (messagesContainerRef.current) {
messagesContainerRef.current.scrollTop =
messagesContainerRef.current.scrollHeight;
}
}, [messages]);

if (isMessagesPending && isUserProfilePending) return <span>Loading...</span>;

return (
<div
ref={messagesContainerRef}
className="w-full overflow-y-auto overflow-x-hidden h-full flex flex-col justify-end"
>
<AnimatePresence>
{messages?.map((message: any, index: number) => (
<motion.div
key={index}
layout
initial={{ opacity: 0, scale: 1, y: 50, x: 0 }}
animate={{ opacity: 1, scale: 1, y: 0, x: 0 }}
exit={{ opacity: 0, scale: 1, y: 1, x: 0 }}
transition={{
opacity: { duration: 0.1 },
layout: {
type: "spring",
bounce: 0.3,
duration: messages.indexOf(message) * 0.05 + 0.2,
},
}}
style={{
originX: 0.5,
originY: 0.5,
}}
className={cn(
"flex flex-col gap-2 p-4 whitespace-pre-wrap",
message.senderId === connectedUserId ? "items-end" : "items-start"
)}
>
<div className="flex gap-3 items-center">
{message.senderId !== connectedUserId && (
<Avatar className="flex justify-center items-center">
<AvatarFallback>
{getInitials(message.senderName)}
</AvatarFallback>
</Avatar>
)}
<div className="flex flex-col gap-1">
<div
className={cn(`flex items-center justify-between flex-row`)}
>
<span className="font-medium">{message.senderName}</span>
<span className="text-gray-500 text-[0.75rem] font-normal">
{format(new Date(message.timestamp), DATE_FORM)}
</span>
</div>

<span
className={cn(
`bg-accent p-3 rounded-md max-w-xs`,

message.senderId === connectedUserId
? ""
: "bg-green-800 text-white"
)}
>
{message.content}
</span>
</div>
{message.senderId === connectedUserId && (
<Avatar className="flex justify-center items-center">
<AvatarFallback>
{getInitials(message.senderName)}
</AvatarFallback>
</Avatar>
)}
</div>
</motion.div>
))}
</AnimatePresence>
</div>
);
};

export default ChatMessagesList;
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const ConversationList = ({ search }: ConversationListProps) => {
{conversations?.map((conversation: any) => (
<ChatItem
key={conversation.chatId}
id={conversation.chatId}
username={conversation.username}
message={conversation?.lastMessage?.content}
date={conversation?.lastMessage?.timestamp}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const GroupList = ({ search }: GroupListProps) => {
{groups?.map((group: any) => (
<ChatItem
key={group.id}
id={group.id}
username={group.name}
message={group?.lastMessage?.content}
date={group?.lastMessage?.timestamp}
Expand Down
9 changes: 6 additions & 3 deletions packages/insea-connect-ui/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SessionProvider } from "next-auth/react";
import { ModalProvider } from "@/components/provider/modal-provider";
import { Toaster } from "@/components/ui/toaster";
import QueryProvider from "@/components/provider/query-provider";
import { SocketProvider } from "@/components/provider/socket-provider";
const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
Expand All @@ -29,9 +30,11 @@ export default function RootLayout({
>
<SessionProvider>
<QueryProvider>
<ModalProvider />
{children}
<Toaster />
<SocketProvider>
<ModalProvider />
{children}
<Toaster />
</SocketProvider>
</QueryProvider>
</SessionProvider>
</ThemeProvider>
Expand Down
Loading

0 comments on commit 6aa83c7

Please sign in to comment.