-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
434 additions
and
75 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import prisma from '@/app/libs/prismadb' | ||
import getCurrentUser from './getCurrentUser' | ||
|
||
const getConversations = async () => { | ||
const currentUser = await getCurrentUser() | ||
|
||
if (!currentUser?.id) { | ||
return [] | ||
} | ||
|
||
try { | ||
const conversations = await prisma.conversation.findMany({ | ||
orderBy: { | ||
lastMessageAt: 'desc', | ||
}, | ||
where: { | ||
userIds: { | ||
has: currentUser.id, | ||
}, | ||
}, | ||
include: { | ||
users: true, | ||
messages: { | ||
include: { | ||
sender: true, | ||
seen: true, | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
return conversations | ||
} catch (error: any) { | ||
return [] | ||
} | ||
} | ||
|
||
export default getConversations |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import getCurrentUser from '@/app/actions/getCurrentUser' | ||
import prisma from '@/app/libs/prismadb' | ||
import { NextResponse } from 'next/server' | ||
|
||
export async function POST(request: Request) { | ||
try { | ||
const currentUser = await getCurrentUser() | ||
const body = await request.json() | ||
const { userId, isGroup, members, name } = body | ||
|
||
if (!currentUser?.id || !currentUser?.email) { | ||
return new NextResponse('Unauthorized', { status: 401 }) | ||
} | ||
|
||
if (isGroup && (!members || members.length < 2 || !name)) { | ||
return new NextResponse('Invalid Data', { status: 400 }) | ||
} | ||
|
||
if (isGroup) { | ||
const newConversation = await prisma.conversation.create({ | ||
data: { | ||
name, | ||
isGroup, | ||
users: { | ||
connect: [ | ||
...members.map((member: { value: string }) => ({ | ||
id: member.value, | ||
})), | ||
{ id: currentUser.id }, | ||
], | ||
}, | ||
}, | ||
include: { | ||
users: true, | ||
}, | ||
}) | ||
|
||
return NextResponse.json(newConversation) | ||
} else { | ||
const existingConversations = await prisma.conversation.findMany({ | ||
where: { | ||
OR: [ | ||
{ | ||
userIds: { | ||
equals: [currentUser.id, userId], | ||
}, | ||
}, | ||
{ | ||
userIds: { | ||
equals: [userId, currentUser.id], | ||
}, | ||
}, | ||
], | ||
}, | ||
}) | ||
|
||
const singleConversation = existingConversations[0] | ||
|
||
if (singleConversation) { | ||
return NextResponse.json(singleConversation) | ||
} | ||
|
||
const newConversation = await prisma.conversation.create({ | ||
data: { | ||
users: { | ||
connect: [ | ||
{ | ||
id: currentUser.id, | ||
}, | ||
{ | ||
id: userId, | ||
}, | ||
], | ||
}, | ||
}, | ||
include: { | ||
users: true, | ||
}, | ||
}) | ||
|
||
return NextResponse.json(newConversation) | ||
} | ||
} catch (error) { | ||
return new NextResponse('Internal Error', { status: 500 }) | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
'use client' | ||
|
||
import Avatar from '@/app/components/Avatar' | ||
import useOtherUser from '@/app/hooks/useOtherUser' | ||
import { FullConversationType } from '@/app/types' | ||
import { Conversation, Message, User } from '@prisma/client' | ||
import clsx from 'clsx' | ||
import { format } from 'date-fns' | ||
import { useSession } from 'next-auth/react' | ||
import { useRouter } from 'next/navigation' | ||
import { useCallback, useMemo } from 'react' | ||
|
||
interface ConversationBoxProps { | ||
data: FullConversationType | ||
selected?: boolean | ||
} | ||
|
||
const ConversationBox: React.FC<ConversationBoxProps> = ({ | ||
data, | ||
selected, | ||
}) => { | ||
const otherUser = useOtherUser(data) | ||
const session = useSession() | ||
const router = useRouter() | ||
|
||
const handleClick = useCallback(() => { | ||
router.push(`/conversations/${data.id}`) | ||
}, [data.id, router]) | ||
|
||
const lastMessage = useMemo(() => { | ||
const messages = data.messages || [] | ||
|
||
return messages[messages.length - 1] | ||
}, [data.messages]) | ||
|
||
const userEmail = useMemo(() => { | ||
return session.data?.user?.email | ||
}, [session.data?.user?.email]) | ||
|
||
const hasSeen = useMemo(() => { | ||
if (!lastMessage) return false | ||
|
||
const seenArray = lastMessage.seen || [] | ||
|
||
if (!userEmail) return false | ||
|
||
return seenArray.filter((user) => user.email === userEmail).length !== 0 | ||
}, [userEmail, lastMessage]) | ||
|
||
const lastMessageText = useMemo(() => { | ||
if (lastMessage?.image) { | ||
return 'Sent an image' | ||
} | ||
|
||
if (lastMessage?.body) { | ||
return lastMessage.body | ||
} | ||
|
||
return 'Started a conversation' | ||
}, [lastMessage]) | ||
|
||
return ( | ||
<div | ||
onClick={handleClick} | ||
className={clsx( | ||
`w-full relative flex items-center space-x-3 | ||
hover:bg-neutral-100 rounded-lg transition cursor-pointer p-3`, | ||
selected ? 'bg-neutral-100' : 'bg-white' | ||
)} | ||
> | ||
<Avatar user={otherUser} /> | ||
<div className="min-w-0 flex-1"> | ||
<div className="focus:outline-none"> | ||
<div className="flex justify-between items-center mb-1"> | ||
<p className="text-md font-medium text-gray-900"> | ||
{data.name || otherUser.name} | ||
</p> | ||
{lastMessage?.createdAt && ( | ||
<p className="text-xs text-gray-400 font-light"> | ||
{format(new Date(lastMessage.createdAt), 'p')} | ||
</p> | ||
)} | ||
</div> | ||
<p | ||
className={clsx( | ||
`truncate text-sm`, | ||
hasSeen ? 'text-gray-500' : 'text-black font-medium' | ||
)} | ||
> | ||
{lastMessageText} | ||
</p> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default ConversationBox |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
'use client' | ||
|
||
import useConversation from '@/app/hooks/useConversation' | ||
import { FullConversationType } from '@/app/types' | ||
import { Conversation } from '@prisma/client' | ||
import clsx from 'clsx' | ||
import { useRouter } from 'next/navigation' | ||
import { useState } from 'react' | ||
import { MdOutlineGroupAdd } from 'react-icons/md' | ||
import ConversationBox from './ConversationBox' | ||
|
||
interface ConversationListProps { | ||
initialItems: FullConversationType[] | ||
} | ||
|
||
const ConversationList: React.FC<ConversationListProps> = ({ | ||
initialItems, | ||
}) => { | ||
const [items, setItems] = useState(initialItems) | ||
|
||
const router = useRouter() | ||
|
||
const { conversationId, isOpen } = useConversation() | ||
|
||
return ( | ||
<aside | ||
className={clsx( | ||
`fixed inset-y-0 pb-20 lg:pb-0 lg:left-20 lg:w-80 | ||
lg:block overflow-y-auto border-r border-gray-200`, | ||
isOpen ? 'hidden' : 'block w-full left-0' | ||
)} | ||
> | ||
<div className="px-5"> | ||
<div className="flex justify-between mb-4 pt-4"> | ||
<div className="text-2xl font-bold text-neutral-800">Messages</div> | ||
<div | ||
className="rounded-full p-2 bg-gray-100 text-gray-600 | ||
cursor-pointer hover:opacity-75 transition" | ||
> | ||
<MdOutlineGroupAdd size={20} /> | ||
</div> | ||
</div> | ||
{items.map((item) => ( | ||
<ConversationBox | ||
key={item.id} | ||
data={item} | ||
selected={conversationId === item.id} | ||
/> | ||
))} | ||
</div> | ||
</aside> | ||
) | ||
} | ||
|
||
export default ConversationList |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import getConversations from '../actions/getConversations' | ||
import Sidebar from '../components/sidebar/Sidebar' | ||
import ConversationList from './components/ConversationList' | ||
|
||
export default async function ConversationsLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
const conversations = await getConversations() | ||
return ( | ||
<Sidebar> | ||
<div className="h-full"> | ||
<ConversationList initialItems={conversations} /> | ||
{children} | ||
</div> | ||
</Sidebar> | ||
) | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
'use client' | ||
|
||
import clsx from 'clsx' | ||
import EmptyState from '../components/EmptyState' | ||
import useConversation from '../hooks/useConversation' | ||
|
||
const Home = () => { | ||
const { isOpen } = useConversation() | ||
|
||
return ( | ||
<div | ||
className={clsx('lg:pl-80 h-full lg:block', isOpen ? 'block' : 'hidden')} | ||
> | ||
<EmptyState /> | ||
</div> | ||
) | ||
} | ||
|
||
export default Home |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { User } from '@prisma/client' | ||
import { useSession } from 'next-auth/react' | ||
import { useMemo } from 'react' | ||
import { FullConversationType } from '../types' | ||
|
||
const useOtherUser = ( | ||
conversation: | ||
| FullConversationType | ||
| { | ||
users: User[] | ||
} | ||
) => { | ||
const session = useSession() | ||
const otherUser = useMemo(() => { | ||
const currentUserEmail = session.data?.user?.email | ||
|
||
const otherUser = conversation.users.filter( | ||
(user) => user.email !== currentUserEmail | ||
) | ||
|
||
return otherUser[0] | ||
}, [session.data?.user?.email, conversation.users]) | ||
|
||
return otherUser | ||
} | ||
|
||
export default useOtherUser |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Conversation, Message, User } from '@prisma/client' | ||
|
||
export type FullMessageType = Message & { | ||
sender: User | ||
seen: User[] | ||
} | ||
|
||
export type FullConversationType = Conversation & { | ||
users: User[] | ||
messages: FullMessageType[] | ||
} |
Oops, something went wrong.