Skip to content

Commit

Permalink
add message box and message form
Browse files Browse the repository at this point in the history
  • Loading branch information
ngquyduc committed Jul 19, 2023
1 parent 03750d2 commit a423874
Show file tree
Hide file tree
Showing 15 changed files with 550 additions and 7 deletions.
4 changes: 3 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ DATABASE_URL="mongodb+srv://quyduc:[email protected]
NEXTAUTH_SECRET = "NEXTAUTH_SECRET"

GOOGLE_CLIENT_ID=409276087119-9hmgc0ad6augp7gkeaqmq2r1ru92afo0.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=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
GOOGLE_CLIENT_SECRET=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

NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=dgp72bk0u
26 changes: 26 additions & 0 deletions app/actions/getConversationById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import prisma from '@/app/libs/prismadb'
import getCurrentUser from './getCurrentUser'

const getConversationById = async (conversationId: string) => {
try {
const currentUser = await getCurrentUser()

if (!currentUser?.email) {
return null
}

const conversation = await prisma.conversation.findUnique({
where: {
id: conversationId,
},
include: {
users: true,
},
})
return conversation
} catch (error: any) {
return null
}
}

export default getConversationById
23 changes: 23 additions & 0 deletions app/actions/getMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import prisma from '@/app/libs/prismadb'

const getMessages = async (conversationId: string) => {
try {
const messages = await prisma.message.findMany({
where: {
conversationId: conversationId,
},
include: {
sender: true,
seen: true,
},
orderBy: {
createdAt: 'asc',
},
})
return messages
} catch (error: any) {
return []
}
}

export default getMessages
61 changes: 61 additions & 0 deletions app/api/conversations/[conversationId]/seen/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import getCurrentUser from '@/app/actions/getCurrentUser'
import prisma from '@/app/libs/prismadb'
import { NextResponse } from 'next/server'

interface IParams {
conversationId?: string
}

export async function POST(request: Request, { params }: { params: IParams }) {
try {
const currentUser = await getCurrentUser()
const { conversationId } = params

if (!currentUser?.id || !currentUser?.email) {
return new NextResponse('Unauthorized', { status: 401 })
}

// find the existing conversation
const conversation = await prisma.conversation.findUnique({
where: {
id: conversationId,
},
include: {
messages: {
include: {
seen: true,
},
},
users: true,
},
})

if (!conversation) return new NextResponse('Invalid Id', { status: 400 })

// find last message
const lastMessage = conversation.messages[conversation.messages.length - 1]
if (!lastMessage) return NextResponse.json(conversation)

// update seen of last message
const updatedMessage = await prisma.message.update({
where: {
id: lastMessage.id,
},
include: {
sender: true,
seen: true,
},
data: {
seen: {
connect: {
id: currentUser.id,
},
},
},
})
return NextResponse.json(updatedMessage)
} catch (error: any) {
console.log(error, 'ERROR_MESSAGES_SEEN')
return new NextResponse('Internal Error', { status: 500 })
}
}
68 changes: 68 additions & 0 deletions app/api/messages/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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 { message, image, conversationId } = body

if (!currentUser?.id || !currentUser?.email) {
return new NextResponse('Unauthorized', { status: 401 })
}

const newMessage = await prisma.message.create({
data: {
body: message,
image: image,
conversation: {
connect: {
id: conversationId,
},
},
sender: {
connect: {
id: currentUser.id,
},
},
seen: {
connect: {
id: currentUser.id,
},
},
},
include: {
seen: true,
sender: true,
},
})

const updatedConversation = await prisma.conversation.update({
where: {
id: conversationId,
},
data: {
lastMessageAt: new Date(),
messages: {
connect: {
id: newMessage.id,
},
},
},
include: {
users: true,
messages: {
include: {
seen: true,
},
},
},
})

return NextResponse.json(newMessage)
} catch (error: any) {
console.log(error, 'ERROR_MESSAGES')
return new NextResponse('InternalError', { status: 500 })
}
}
37 changes: 37 additions & 0 deletions app/conversations/[conversationId]/components/Body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'

import useConversation from '@/app/hooks/useConversation'
import { FullMessageType } from '@/app/types'
import axios from 'axios'
import { useEffect, useRef, useState } from 'react'
import MessageBox from './MessageBox'

interface BodyProps {
initialMessages: FullMessageType[]
}

const Body: React.FC<BodyProps> = ({ initialMessages }) => {
const [messages, setMessages] = useState(initialMessages)
const bottomRef = useRef<HTMLDivElement>(null)

const { conversationId } = useConversation()

useEffect(() => {
axios.post(`/api/conversations/${conversationId}/seen`)
}, [conversationId])

return (
<div className="flex-1 overflow-y-auto">
{messages.map((message, i) => (
<MessageBox
isLast={i === messages.length - 1}
key={message.id}
data={message}
/>
))}
<div ref={bottomRef} className="pt-24" />
</div>
)
}

export default Body
74 changes: 74 additions & 0 deletions app/conversations/[conversationId]/components/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client'

import useConversation from '@/app/hooks/useConversation'
import axios from 'axios'
import { CldUploadButton } from 'next-cloudinary'
import { FieldValues, SubmitHandler, useForm } from 'react-hook-form'
import { HiPaperAirplane, HiPhoto } from 'react-icons/hi2'
import MessageInput from './MessageInput'

const Form = () => {
const { conversationId } = useConversation()

const {
register,
handleSubmit,
setValue,
formState: { errors },
} = useForm<FieldValues>({
defaultValues: {
messages: '',
},
})

const onSubmit: SubmitHandler<FieldValues> = (data) => {
setValue('message', '', { shouldValidate: true })
axios.post('/api/messages', {
...data,
conversationId,
})
}

const handleUpload = (result: any) => {
axios.post('/api/messages', {
image: result?.info?.secure_url,
conversationId,
})
}

return (
<div
className="py-4 px-4 bg-white border-t flex items-center gap-2
lg:gap-4 w-full"
>
<CldUploadButton
options={{ maxFiles: 1 }}
onUpload={handleUpload}
uploadPreset="lfdklgnr"
>
<HiPhoto size={30} className="text-sky-500" />
</CldUploadButton>

<form
onSubmit={handleSubmit(onSubmit)}
className="flex items-center gap-2 lg:gap-4 w-full"
>
<MessageInput
id="message"
register={register}
errors={errors}
required
placeholder="Write a message"
/>
<button
type="submit"
className="rounded-full p-2 bg-sky-500 cursor-pointer hover:bg-sky-600 transition"
>
<HiPaperAirplane size={18} className="text-white" />
</button>
</form>
</div>
)
}

export default Form
60 changes: 60 additions & 0 deletions app/conversations/[conversationId]/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import Avatar from '@/app/components/Avatar'
import useOtherUser from '@/app/hooks/useOtherUser'
import { Conversation, User } from '@prisma/client'
import Link from 'next/link'
import { useMemo, useState } from 'react'
import { HiChevronLeft, HiEllipsisHorizontal } from 'react-icons/hi2'

interface HeaderProps {
conversation: Conversation & {
user: User[]
}
}

const Header: React.FC<HeaderProps> = ({ conversation }) => {
const otherUser = useOtherUser(conversation)
const [drawerOpen, setDrawerOpen] = useState(false)

const statusText = useMemo(() => {
if (conversation.isGroup) {
return `${conversation.user.length} members`
}

return 'Active'
}, [conversation])

return (
<>
<div
className="bg-white w-full flex border-b-[1px] sm:px-4 py-3 px-4
lg:px-6 justify-between items-center shadow-sm"
>
<div className="flex gap-3 items-center">
<Link
className="lg:hidden block text-sky-500 hover:text-sky-600
transition cursor-pointer"
href="/conversations"
>
<HiChevronLeft size={32} />
</Link>
<Avatar user={otherUser} />
<div className="flex flex-col">
<div>{conversation.name || otherUser.name}</div>
<div className="text-sm font-light text-neutral-500">
{statusText}
</div>
</div>
</div>
<HiEllipsisHorizontal
size={32}
onClick={() => {}}
className="text-sky-500 cursor-pointer hover:text-sky-600 transition"
/>
</div>
</>
)
}

export default Header
Loading

0 comments on commit a423874

Please sign in to comment.