-
Notifications
You must be signed in to change notification settings - Fork 1
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
34 changed files
with
2,119 additions
and
24 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
96 changes: 94 additions & 2 deletions
96
app/(main)/(routes)/servers/[serverId]/conversations/[memberId]/page.tsx
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 |
---|---|---|
@@ -1,5 +1,97 @@ | ||
const MemberIdPage = () => { | ||
return <div>MemberIdPage</div>; | ||
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'; | ||
import { ChatMessages } from '@/components/chat/chat-messages'; | ||
import { ChatInput } from '@/components/chat/chat-input'; | ||
import { MediaRoom } from '@/components/media-room'; | ||
|
||
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 ( | ||
<div className="bg-white dark:bg-[#313338] flex flex-col h-full"> | ||
<ChatHeader | ||
imageUrl={otherMember.profile.imageUrl} | ||
name={otherMember.profile.name} | ||
serverId={params.serverId} | ||
type="conversation" | ||
/> | ||
{searchParams.video && ( | ||
<MediaRoom chatId={conversation.id} video={true} audio={true} /> | ||
)} | ||
{!searchParams.video && ( | ||
<> | ||
<ChatMessages | ||
member={currentMember} | ||
name={otherMember.profile.name} | ||
chatId={conversation.id} | ||
type="conversation" | ||
apiUrl="/api/direct-messages" | ||
paramKey="conversationId" | ||
paramValue={conversation.id} | ||
socketUrl="/api/socket/direct-messages" | ||
socketQuery={{ | ||
conversationId: conversation.id, | ||
}} | ||
/> | ||
<ChatInput | ||
name={otherMember.profile.name} | ||
type="conversation" | ||
apiUrl="/api/socket/direct-messages" | ||
query={{ | ||
conversationId: conversation.id, | ||
}} | ||
/> | ||
</> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default MemberIdPage; |
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,81 @@ | ||
import { NextResponse } from 'next/server'; | ||
import { Message } from '@prisma/client'; | ||
|
||
import { currentProfile } from '@/lib/current-profile'; | ||
import { db } from '@/lib/db'; | ||
|
||
const MESSAGES_BATCH = 10; | ||
|
||
export async function GET(req: Request) { | ||
try { | ||
const profile = await currentProfile(); | ||
const { searchParams } = new URL(req.url); | ||
|
||
const cursor = searchParams.get('cursor'); | ||
const channelId = searchParams.get('channelId'); | ||
|
||
if (!profile) { | ||
return new NextResponse('Unauthorized', { status: 401 }); | ||
} | ||
|
||
if (!channelId) { | ||
return new NextResponse('Channel ID missing', { status: 400 }); | ||
} | ||
|
||
let messages: Message[] = []; | ||
|
||
if (cursor) { | ||
messages = await db.message.findMany({ | ||
take: MESSAGES_BATCH, | ||
skip: 1, | ||
cursor: { | ||
id: cursor, | ||
}, | ||
where: { | ||
channelId, | ||
}, | ||
include: { | ||
member: { | ||
include: { | ||
profile: true, | ||
}, | ||
}, | ||
}, | ||
orderBy: { | ||
createdAt: 'desc', | ||
}, | ||
}); | ||
} else { | ||
messages = await db.message.findMany({ | ||
take: MESSAGES_BATCH, | ||
where: { | ||
channelId, | ||
}, | ||
include: { | ||
member: { | ||
include: { | ||
profile: true, | ||
}, | ||
}, | ||
}, | ||
orderBy: { | ||
createdAt: 'desc', | ||
}, | ||
}); | ||
} | ||
|
||
let nextCursor = null; | ||
|
||
if (messages.length === MESSAGES_BATCH) { | ||
nextCursor = messages[MESSAGES_BATCH - 1].id; | ||
} | ||
|
||
return NextResponse.json({ | ||
items: messages, | ||
nextCursor, | ||
}); | ||
} catch (error) { | ||
console.log('[MESSAGES_GET]', 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
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
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,96 @@ | ||
'use client'; | ||
|
||
import * as z from 'zod'; | ||
import axios from 'axios'; | ||
import qs from 'query-string'; | ||
import { useForm } from 'react-hook-form'; | ||
import { zodResolver } from '@hookform/resolvers/zod'; | ||
import { Plus } from 'lucide-react'; | ||
import { useRouter } from 'next/navigation'; | ||
|
||
import { Form, FormControl, FormField, FormItem } from '@/components/ui/form'; | ||
import { Input } from '@/components/ui/input'; | ||
import { useModal } from '@/hooks/use-modal-store'; | ||
import { EmojiPicker } from '@/components/emoji-picker'; | ||
|
||
interface ChatInputProps { | ||
apiUrl: string; | ||
query: Record<string, any>; | ||
name: string; | ||
type: 'conversation' | 'channel'; | ||
} | ||
|
||
const formSchema = z.object({ | ||
content: z.string().min(1), | ||
}); | ||
|
||
export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => { | ||
const { onOpen } = useModal(); | ||
const router = useRouter(); | ||
|
||
const form = useForm<z.infer<typeof formSchema>>({ | ||
resolver: zodResolver(formSchema), | ||
defaultValues: { | ||
content: '', | ||
}, | ||
}); | ||
|
||
const isLoading = form.formState.isSubmitting; | ||
|
||
const onSubmit = async (values: z.infer<typeof formSchema>) => { | ||
try { | ||
const url = qs.stringifyUrl({ | ||
url: apiUrl, | ||
query, | ||
}); | ||
|
||
await axios.post(url, values); | ||
|
||
form.reset(); | ||
router.refresh(); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
}; | ||
|
||
return ( | ||
<Form {...form}> | ||
<form onSubmit={form.handleSubmit(onSubmit)}> | ||
<FormField | ||
control={form.control} | ||
name="content" | ||
render={({ field }) => ( | ||
<FormItem> | ||
<FormControl> | ||
<div className="relative p-4 pb-6"> | ||
<button | ||
type="button" | ||
onClick={() => onOpen('messageFile', { apiUrl, query })} | ||
className="absolute top-7 left-8 h-[24px] w-[24px] bg-zinc-500 dark:bg-zinc-400 hover:bg-zinc-600 dark:hover:bg-zinc-300 transition rounded-full p-1 flex items-center justify-center" | ||
> | ||
<Plus className="text-white dark:text-[#313338]" /> | ||
</button> | ||
<Input | ||
disabled={isLoading} | ||
className="px-14 py-6 bg-zinc-200/90 dark:bg-zinc-700/75 border-none border-0 focus-visible:ring-0 focus-visible:ring-offset-0 text-zinc-600 dark:text-zinc-200" | ||
placeholder={`Message ${ | ||
type === 'conversation' ? name : '#' + name | ||
}`} | ||
{...field} | ||
/> | ||
<div className="absolute top-7 right-8"> | ||
<EmojiPicker | ||
onChange={(emoji: string) => | ||
field.onChange(`${field.value} ${emoji}`) | ||
} | ||
/> | ||
</div> | ||
</div> | ||
</FormControl> | ||
</FormItem> | ||
)} | ||
/> | ||
</form> | ||
</Form> | ||
); | ||
}; |
Oops, something went wrong.