들어가기
Post를 쓰는 폼(client)와 API
두개를 만들어 봅시다.
client에서 질문(POST)를 작성하면,
그 작성된 질문을 처리하는 API
import withHandler, { ResponseType } from '@libs/server/withHandler'
import { NextApiRequest, NextApiResponse } from 'next'
import client from '@libs/server/client'
import { withApiSession } from '@libs/server/withSession'
async function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseType>
) {
if (req.method === 'POST') { ///client에서 질문 작성처리 API(POST)
const { question, latitude, longitude } = req.body
///front에서 보낸 data인 question, latitude(위도,가로), longitude(경, 세) 를
///req.body로 받는다
const { user } = req.session ///loggedInUser 받음.
const post = await client.post.create({
data: { ///question, latitude, longitude받고, user와 connect해서
///post create함.
question,
latitude,
longitude,
user: {
connect: {
id: user?.id,
},
},
},
})
res.json({ ok: true, post }) ///ok:true와 만들어진 Post를 return해줌.
}
if (req.method === 'GET') { ///모든 질문을 나열하는 요청을 받았을떄, 처리API
const {
query: { latitude, longitude }, ///req.query로 현재 user의 GPS를 받음.
} = req
// const latitudeNumber = parseFloat(latitude?.toString())
// const longitudeNumber = parseFloat(longitude.toString())
const posts = await client.post.findMany({
include: { ///Post를 찾는데, user의 id, name, avatar포함시킴
user: {
select: {
id: true,
name: true,
avatar: true,
},
},
_count: { ///Post의 wondering숫자와 answer의 숫자를 count해서 같이 보내줌.
select: {
wondering: true,
answers: true,
},
},
},
where: { ///where는 Post의 찾는 범위를 설정해주는건데,
///현재 위도, 경도의 -0.1 ~~~ +0.1 범위에 있는 Post만 나열되게 설정
///위도, 경도가 Float(소수)이기때문에
///니코샘은const longitudeNumber =ㅔarseFloat(longitude.toString())
///이렇게 하라는데, error가 남. 시간 될때, 조금더 연구할 것!!!
latitude: {
gte: Number(latitude) - 0.01,
lte: Number(latitude) + 0.01,
},
longitude: {
gte: Number(longitude) - 0.01,
lte: Number(longitude) + 0.01,
},
},
})
res.json({ ok: true, posts }) ///찾은 Posts들을 return해줌.
}
}
export default withApiSession(
withHandler({
methods: ['POST', 'GET'],
handler,
})
)
pages/api/posts/index.ts 파일은 POST로 받으면, post작성 API, GET으로 받으면, 모든 질문보기임.
import useCoords from '@libs/client/useCoords'
import useMutation from '@libs/client/useMutation'
import { Post } from '@prisma/client'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import Button from '../../components/button'
import Layout from '../../components/layout'
import TextArea from '../../components/textarea'
interface WriteForm {
question: string
}
///question을 적는 Form
interface WriteResponse {
ok: boolean
post: Post
}
///post를 작성하고 나서 return받는 data의 종류의 type지정
const Write: NextPage = () => {
const { latitude, longitude } = useCoords()
///useCoords hook을 이용해서 현재 Post 작성자의 위치를 확인 가능,
///useCoords hook은 3번에서 다룰 예정.
const router = useRouter()
const { register, handleSubmit } = useForm<WriteForm>() ///react-hook-form
const [post, { loading, data }] = useMutation<WriteResponse>('/api/posts')
///useMutaion으로 '/api/posts' 에 작성한 Post를 보내줌.
const onValid = (data: WriteForm) => {
if (loading) return
post({...data, latitude, longitude})
}
///onSubmit을 하면 data(Writeform)와 위도, 경도를 넣어서 API('/api/posts')로 보내줌
useEffect(() => {
if (data && data.ok) {
router.push(`/community/${data.post.id}`)
}
}, [data, router])
///Post를 작성해서 onSubmit가 성공적으로 이루어져서 return으로 data.ok를
///받았다면, post의 detaile Page로 이동시킴.
return (
<Layout canGoBack title="Write Post!">
<form onSubmit={handleSubmit(onValid)} className="p-4 space-y-4">
<TextArea
///TextArea로 register보내줌.
register={register('question', { required: true, minLength: 5 })}
required
placeholder="Ask a question!"
/>
<Button text={loading ? 'Loading' : 'Submit'} />
///loading일떄, 반드시 button title을 바꿔주어야함.
</form>
</Layout>
)
}
export default Write
import useCoords from '@libs/client/useCoords'
import { Post, User } from '@prisma/client'
import type { NextPage } from 'next'
import Link from 'next/link'
import useSWR from 'swr'
import FloatingButton from '../../components/floating-button'
import Layout from '../../components/layout'
interface PostWithUser extends Post {
user: User
_count: {
wondering: number
answers: number
}
}
///api/posts로 GET 요청을 해서(모든 Post를 보는) 받는 return Data에
///user가 포함되어 있어 Post라는 type에 user를 넣어줌.
interface PostsResponse {
ok: boolean
posts: PostWithUser[]
}
///Post를 확장한 type인 PostWithUser를 배열로 받음.
const Community: NextPage = () => {
const { latitude, longitude } = useCoords() ///현재 user의 위도, 경도 찾음.
const { data } = useSWR<PostsResponse>(
latitude && longitude
? `/api/posts?latitude=${latitude}&longitude=${longitude}`
: null
)///useSWR을 이용해서 '/api/post' API에 req해서 data를 받는데
///요청을 보낼때, req.query에 latitude와 longitude를 넣어서 보냄.
///넣어서 보내는 방법은
///API주소 뒤에 '''?latitude=${latitude}&longitude=${longitude}'''
///넣어서 보낸다. API에서 받을떄는 const {latitude, longitude} = req.query
///위도, 경도가 undefined이면, error가 날 수 있으니, 반드시
///앞에 latitude && longitude ? 를 넣어준다
return (
<Layout hasTabBar title="동네생활">
<div className="py-16 space-y-8">
{data?.posts?.map((post: any) => ( ///받은 data를 map으로 뿌려줌.
<Link key={post.id} href={`/community/${post.id}`}>
///나열된 질문을 클릭하면, 상세 질문 페이지로 이동.
<a className="flex cursor-pointer flex-col items-start">
<span className="flex ml-4 items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
동네질문
</span>
<div className="mt-2 px-4 text-gray-700">
<span className="text-orange-500 font-medium">Q .</span>
{post.question} ///질문 뿌리기
</div>
<div className="mt-5 px-4 flex items-center justify-between w-full text-gray-500 font-medium text-xs">
<span>{post.user.name}</span> ///user.name 뿌리기!!
<span>{post.createdAt}</span> ///Post 작성날짜 뿌리기
</div>
<div className="flex px-4 space-x-5 mt-3 text-gray-500 py-3 border-t w-full border-b-[2px] shadow-lg">
<span className="flex space-x-2 items-center text-sm ">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<span>궁금해요 {post._count.wondering}</span>
///wondering count뿌려주기!!!
</span>
<span className="flex space-x-2 items-center text-sm ">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
></path>
</svg>
<span>답변 {post._count.answers}</span>
///answers count 뿌려주기!!
</span>
</div>
</a>
</Link>
))}
<FloatingButton href="/community/write">
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
></path>
</svg>
</FloatingButton>
</div>
</Layout>
)
}
export default Community
현재 위치(GPS)를 위도, 경도로 찍어주는 Hook
이 훅을 사용하면, 현재 작성자의 위치로 위도, 경도를
찍어줄 수 있음.
import { useEffect, useState } from 'react'
interface UseCoordState {
latitude: number | null
longitude: number | null
}
export default function useCoords() {
const [coords, setCoords] = useState<UseCoordState>({
latitude: null,
longitude: null,
})
const onSuccess = ({
coords: { latitude, longitude },
}: GeolocationPosition) => {
setCoords({ latitude, longitude })
}
useEffect(() => {
navigator.geolocation.getCurrentPosition(onSuccess)
}, [])
return coords
}