Post Detail

김종민·2022년 8월 8일
0

apple-market

목록 보기
22/37


들어가기
Post중 하나를 클릭 했을떄, Detail Page로 이동하게됨

1. pages/community/[id].tsx

DetailPost에는 3개의 API가 들어감.
1. Post Detatl.(useSWR) /api/posts/router.query.id2.댓글달기.(useMutation)/api/posts/{router.query.id} 2. 댓글달기.(useMutation) /api/posts/{router.query.id}/answers
3. 궁금해요 클릭이벤트. (useMutation) /api/posts/${router.query.id}/wonder

router.query.id 는 const router = useRouter()로 받을 수 있음.

import useMutation from '@libs/client/useMutation'
import { cls } from '@libs/client/utils'
import { Answer, Post, User } from '@prisma/client'
import type { NextPage } from 'next'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import useSWR from 'swr'
import Layout from '../../components/layout'
import TextArea from '../../components/textarea'

interface AnswerWithUser extends Answer {
  user: User
  _count: {
    wondering: number
    answers: number
  }
}
///하나의 Post에 대한 댓글(answers)을 불러줄때, 댓글에 
///댓글을 단 user와 _count를 포함시켜줌.
///약간 복잡하게 느껴질 수 도 있음.

interface PostWithUser extends Post {
  user: User
  _count: {
    answers: number
    wondering: number
  }
  answers: AnswerWithUser[]
}
///바로 밑의 하나의 Post를 요청했을때, 
///그 Post의 user와, _count와 answer를 같이 불러줌.

interface CommunityPostResponse {
  ok: boolean
  post: PostWithUser
  isWondering: boolean
}
///나열된 Post에서 하나의 Post를 클릭했을떄, 받는 data

interface AnswerForm {
  answer: string
}
///댓글달기 Form의 Type.

interface AnswerResponse {
  ok: boolean
  answer: Answer
}
///댓글달기를 useMutation한 후, Response한 data.

const CommunityPostDetail: NextPage = () => {
  const router = useRouter()
  const { register, handleSubmit, reset } = useForm<AnswerForm>()
  ///DetailPosr page에서 댓글을 달 수 있는 Form을 만들어 놓음.
  
  const { data, mutate } = useSWR<CommunityPostResponse>(
    router.query.id ? `/api/posts/${router.query.id}` : null
  )
  ///community페이지에서 하나의 질문을 클릭 했을떄, 
  ///DetailPost 페이지에서 router.query.id 로 클릭된 Post id를 받음.
  ///위와같이 해주어야 undefinde가 안뜸.
  ///useSWR을 이용해서 위의 API로 요청 보내고, data를 받음.
  ///DetailPost API는 밑에서 만들 예정.
  
  const [wonder, { loading }] = useMutation(
    `/api/posts/${router.query.id}/wonder`
  )
  /// DetailPost페이지에서 궁금해요를 클릭했을떄, 궁금해요 숫자가 +1되고,
  ///loggedInUser가 눌렀을떄, 색이 변하게, 내가 눌렀는지를 true/false
  ///로 받을 수 있는 API
  
  const [sendAnswer, { data: answerData, loading: answerLoading }] =
    useMutation<AnswerResponse>(`/api/posts/${router.query.id}/answers`)
    ///댓글을 달고 onSubmit했을떄 실행되는 API
    ///data가 겹치는 이유로 rename해줌.

  const onWonderClick = () => {
  ///궁금해요 버튼을 눌렀을떄, 실행되는 함수,
    if (!data) return
    mutate(
      { ///useSWR에서 받은 data모양에서, data?.post._count.wondering만
        ///cache로 다시 써줌. data모양을 그대로 적는거 집중해서 봐 둘것!!!
        ...data,
        post: {
          ...data.post,
          _count: {
            ...data.post._count,
            wondering: data.isWondering
              ? data?.post._count.wondering - 1
              : data?.post._count.wondering + 1, 
          },
        },
        isWondering: !data.isWondering,
      },
      false  ///useSWR을 사용함, DB에 다시 reload안되게 설정.
    )
    if (!loading) {
      wonder({})
      ///위에서 cache를 다시 쓴 다음  `/api/posts/${router.query.id}/wonder`
      ///API를 실행시킴. 단 !loading가 아닐떄만~
    }
  }
  
  const onValid = (form: AnswerForm) => {
    if (answerLoading) return
    sendAnswer(form)
  }
  ///answer(댓글)을 달고 나서 onSubmit했을 시, 실행되는 함수.
  
  useEffect(() => {
    if (answerData && answerData.ok) {
      reset()
      mutate()
    }
  }, [answerData, reset, mutate])
  ///댓글을 달고나서 sendAnswer API 실행후, ok data를 받고나서,
  ///reset()댓글다는 칸을 지우고, mutate()로 useSWR을 실행(data reload)함.
  
  return (
    <Layout canGoBack>
      <span className="inline-flex my-3 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="flex mb-3 px-4 cursor-pointer pb-3  border-b items-center space-x-3">
        <div className="w-10 h-10 rounded-full bg-slate-300" />
        <Link href={`/users/profile/${data?.post?.user.id}`}> ///글쓴이의 profile로 이동
          <a>
            <p className="text-sm font-medium text-gray-700">
              {data?.post?.user?.name}
            </p>
            <p className="text-xs font-medium text-gray-500">
              View profile &rarr;
            </p>
          </a>
        </Link>
      </div>
      <div>
        <div className="mt-10 my-10 px-4  text-gray-700">
          <span className="text-orange-500 font-medium">Q.</span>{' '}
          <span className="text-lg font-extrabold">{data?.post?.question}</span>
        </div>
        <div className="flex px-4 space-x-5 mt-3 text-gray-700 py-2.5 border-t border-b-[2px]  w-full">
          <button
            onClick={onWonderClick} ///궁금해요를 눌렀을떄, 
                                    ///onWonderClick함수가 실행되게~
            className={cls(
              'flex space-x-2 items-center text-sm',
              data?.isWondering ? 'text-teal-400' : ''
            )}
          >
            <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>궁금해요 {data?.post?._count?.wondering}</span>
            /// 궁금해요 뒤에 궁금해요 갯수를 count해줌.
          </button>
          <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>답변 {data?.post._count.answers}</span>
            ///답변을 count해줌.
          </span>
        </div>
      </div>
      <div className="px-4 my-3 space-y-5">
        {data?.post?.answers?.map((answer) => ( ///DetailPost의 댓글들을
                                                ///map으로 뿌려줌.
                                                
          <div key={answer.id} className="flex items-start space-x-3 border-b">
            <div className="w-8 h-8 bg-slate-200 rounded-full" />
            <div>
              <div className="flex space-x-2">
                <span className="text-sm block font-medium text-gray-700">
                  {answer.user.name}
                </span>
                <span className="text-xs mt-1  text-gray-500 block ">
                  2시간전
                </span>
              </div>
              <p className="text-gray-700 text-sm mt-2 mb-2">{answer.answer}</p>
            </div>
          </div>
        ))}
      </div>
      <form className="px-4" onSubmit={handleSubmit(onValid)}>
      ///react-hook-form.
      ///댓글을 다는 Form
        <TextArea
          register={register('answer', { required: true, minLength: 5 })}
          name="description"
          label="Answer"
          required
          placeholder="Answer this question!"
        />
        <button className="mt-2 w-full bg-orange-500 hover:bg-orange-600 text-white py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 focus:outline-none ">
          {answerLoading ? 'Loading....' : 'Reply'}
        </button>
      </form>
    </Layout>
  )
}

export default CommunityPostDetail

2. pages/api/post/[id]/wonder.ts

궁금해요를 클릭했을떄, 그 요청을 처리하는 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>
) {
  const { question } = req.body  ///front에서 온 Data를 받음.
  const { user } = req.session ///loggedInUser 를 확인시켜줌.
  const { id } = req.query ///post를 클릭했을떄, 그 Post id 확인.
///위 3개는 그냥 고정으로

  const alreadyExists = await client.wondering.findFirst({
    where: {
      userId: user?.id,
      postId: Number(id),
    },
    select: {
      id: true,
    },
  })
  ///궁금해요를 클릭했을떄, 궁금해요를 클릭한 Post가 DB에 존재하는지 확인
  ///존재하는지의 확인 여부는 userId, PostId 둘다를 확인함으로써,
  ///user가 궁금해요를 눌렀는지 아닌지를 확인하는 여부가 된다.

  if (alreadyExists) {
    await client.wondering.delete({
      where: {
        id: alreadyExists.id,
      },
    }) ///user가 눌렀다면, 한번 더 누름으로써, delete(false)로 만들어줌 -1시킴.
  } else {
    await client.wondering.create({  ///user가 누른상태가 아니라면, 
                                     ///궁금해요를 true로 만들어줌.
                                     ///userId와 PostId를 connect하는게
                                     ///create하는 것임.
      data: {
        user: {
          connect: {
            id: user?.id,
          },
        },
        post: {
          connect: {
            id: Number(id),
          },
        },
      },
    })
  }

  res.json({ ok: true })  ///ok를 return 해줌.
}

export default withApiSession(
  withHandler({
    methods: ['POST'],
    handler,
  })
)

3.pages/api/post/[id]/answers.ts

댓글(answer)받아서 처리하는 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>
) {
  const { answer } = req.body  //client에서 보내온 댓글(answer)받음.
  const { user } = req.session ///loggedInUser
  const { id } = req.query ///Detail Post Id

  
//   const post = await client.post.findUnique({
//     where: {
//       id: Number(id),
//     },
//     select: {
//       id: true,
//     },
//   })
//   if (!post) return false
  --->더 정확히 코딩하자면, 해당 Post를 찾아서 없으면 return false해야됨.

  const newAnswer = await client.answer.create({
    data: {
      user: {
        connect: {
          id: user?.id,
        },
      },
      post: {
        connect: {
          id: Number(id),
        },
      },
      answer,
    },
  })  ///answer를 req.body로 받아서, user와 post를 connect하고ㅡ
      ///answer를 넣어줌.
  
  console.log(newAnswer)
  res.json({ ok: true, answer: newAnswer })
}
  ///만들어진 answer을 return해줌, answer가 볍쳐서 rename해줌.

export default withApiSession(
  withHandler({
    methods: ['POST'],
    handler,
  })
)

4.pages/api/post/[id]/index.ts

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>
) {
  const { question } = req.body 
  const { user } = req.session
  const { id } = req.query
 ///3개는 위에서 계속 다뤘죠^^!!
 
  const post = await client.post.findUnique({  ///클릭된 Post를 찾음.
    where: {
      id: Number(id),  ///req.query로 받은 id로 Post찾음.
    },
    include: {   ///찾은 Post에 user, answers, _count를 포함시킴.
                 ///prisma에서 post는 user, answers와 relation되어 있으므로
                 ///relation된 model을 어떻게 include하는지 잘 봐 놓을것.
                 ///그리고 relation된 answers와 wondering을 count하는것도
                 ///잘 봐 놓을 것!!
      user: {
        select: {
          id: true,
          name: true,
          avatar: true,
        },
      },
      answers: {
        select: {
          answer: true,
          createdAt: true,
          id: true,
          user: {
            select: {
              id: true,
              avatar: true,
              name: true,
            },
          },
        },
      },
      _count: {
        select: {
          answers: true,
          wondering: true,
        },
      },
    },
  })
  
  const isWondering = Boolean(
    await client.wondering.findFirst({
      where: {
        postId: Number(id),
        userId: user?.id,
      },
      select: {
        id: true,
      },
    })
  )
  ///loggedInUser가 이 Post에 궁금해요를 눌렀는지 아닌지를
  ///Boolean 즉, true, false로 return해줌.
  
  res.json({ ok: true, post, isWondering })
  ///post와 okWondering을 returngowna..
}

export default withApiSession(
  withHandler({
    methods: ['GET'],
    handler,
  })
)

DetailPost에는 3개의 API가 들어가므로
집중집중해서 잘 봐놓는다!!

profile
코딩하는초딩쌤

0개의 댓글