MyProfile.(review, editProfile)

김종민·2022년 8월 9일
0

apple-market

목록 보기
24/37


들어가기
MyProfile에서
1. review(나에게 쓴 리뷰, 내가 쓴 리뷰)
2. editProfile을 알아본다.

1. pages/api/review.ts

import withHandler, { ResponseType } from '@libs/server/withHandler'
import { NextApiRequest, NextApiResponse } from 'next'

import { withApiSession } from '@libs/server/withSession'
import client from '@libs/server/client'

async function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseType>
) {
  const { user } = req.session
  const reviews = await client.review.findMany({
    where: {
      createdForId: user?.id, ///나에게 쓴 review를 찾음.
    },
    include: {       ///나에게 리뷰를 쓴 사람의 정보도 같이 불러움.
      createdBy: {
        select: {
          id: true,
          name: true,
          avatar: true,
        },
      },
    },
  })
  res.json({
    ok: true,
    reviews,  ///ok:true, 작성된 review를 return함.
  })
}

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

2. pages/profile/index.tsx

import useUser from '@libs/client/useUser'
import { cls } from '@libs/client/utils'
import { Review, User } from '@prisma/client'
import type { NextPage } from 'next'
import Link from 'next/link'
import useSWR from 'swr'
import Layout from '../../components/layout'

interface ReviewWithUser extends Review {
  createdBy: User
}
///리턴받는 Review type에 리뷰작성자인 createdBy :User를 추가시킴.

interface ReviewsResponse {
  ok: boolean
  reviews: ReviewWithUser[]
}
///return 받는 data는 ok, review로 각각의 typed을 설정해줌.

const Profile: NextPage = () => {
  const { user } = useUser()
  const { data } = useSWR<ReviewsResponse>('/api/review')
  ///'/api/review' API로 req해서 data를 받음.
  
  return (
    <Layout hasTabBar title="나의 애플마켓">
      <div className="py-10 px-4">
        <div className="flex items-center space-x-5">
          <div className="w-16 h-16 bg-slate-400 rounded-full" />
          <div className="flex flex-col">
            <span className="font-medium text-gray-900">{user?.name}</span>
            <Link href="/profile/edit">
              <a className="text-sm text-gray-700">Edit profile &rarr;</a>
            </Link>
          </div>
        </div>
        <div className="mt-10 flex justify-around">
          <Link href="/profile/sold">
            <a className="flex flex-col items-center">
              <div className="w-14 h-15 text-white bg-orange-500 rounded-full p-3 flex items-center justify-center">
                <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="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
                  ></path>
                </svg>
              </div>
              <span className="text-sm font-medium text-gray-700 mt-2">
                판매내역
              </span>
            </a>
          </Link>
          <Link href="/profile/buy">
            <a className="flex flex-col items-center">
              <div className="w-14 h-15 text-white bg-orange-500 rounded-full p-3 flex items-center justify-center">
                <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="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z"
                  ></path>
                </svg>
              </div>
              <span className="text-sm font-medium text-gray-700 mt-2">
                구매내역
              </span>
            </a>
          </Link>
          <Link href="/profile/loved">
            <a className="flex flex-col items-center">
              <div className="w-14 h-15 text-white bg-orange-500 rounded-full p-3 flex items-center justify-center">
                <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="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
                  ></path>
                </svg>
              </div>
              <span className="text-sm font-medium text-gray-700 mt-2">
                관심목록
              </span>
            </a>
          </Link>
        </div>
        
        /// '/api/review/' API 로 req해서 받은 data.reviews를 map으로 뿌림.
        /// review의 score를 별로(2점이면, 두개 색칠.)시각화 하는거 잘 봐놓을것 
        {data?.reviews.map((review) => (
          <div key={review.id} className="mt-12">
            <div className="flex items-center space-x-4">
              <div className="w-12 h-12 bg-slate-400  rounded-full" />
              <div>
                <h4>{review.createdBy.name}</h4> //review 작성자
                <div className="flex items-center">
                  {[1, 2, 3, 4, 5].map((star) => (
                    <svg
                      key={star}
                      className={cls(
                        'h-5 w-5',
                        review.score >= star  ///review.score를 받아서 
                                               ///score만큼 별을 색칠함.
                          ? 'text-yellow-400'
                          : 'text-gray-400'
                      )}
                      xmlns="http://www.w3.org/2000/svg"
                      viewBox="0 0 20 20"
                      fill="currentColor"
                      aria-hidden="true"
                    >
                      <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
                    </svg>
                  ))}
                </div>
              </div>
            </div>
            <div className="mt-4 text-gray-600">
              <p>{review.review}</p>  //review를 뿌려줌,
            </div>
          </div>
        ))}
      </div>
    </Layout>
  )
}

export default Profile

3. /api/users/me/index.ts

이 API에서 GET으로 받으면, 나의 정보를 보내주고, POST로 받으면, editProfile
data를 받아서 edit함.

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 === 'GET') {
    const profile = await client.user.findUnique({
      where: { id: req.session.user?.id },
    })
    res.json({
      ok: true,
      profile,
    })
  }
  
  if (req.method === 'POST') {
    const { user } = req.session  ///loggedInuser확인
    const { email, phone, name } = req.body ///client에서 보내온 data받음,
    
    const currentUser = await client.user.findUnique({
      where: {
        id: user?.id,
      },
    }) ///req.session으로 현재 로그인 된 user확인
    
    if (email && email !== currentUser?.email) {
    ///req.body로부터 email이 있고 그 email이 현재 edit하기 전의 
    ///email과 같지 않을떄만, update되게 함.
    
      const alreadyExists = Boolean(
        await client.user.findUnique({
          where: {
            email,
          },
          select: {
            id: true,
          },
        })
      ) ///바꾸려는 email이 있는지 확인하고 있으면, error날림.
      
      
      if (alreadyExists) {
        return res.json({ ok: false, error: 'Email already taken' })
      }
      
      ///같은 email이 없으면, update로 user의 email을 update(edit)함.
      await client.user.update({
        where: {
          id: user?.id,
        },
        data: {
          email,
        },
      })
      res.json({ ok: true })
    }
    
    ///phone도 email과 같은 과정을 거침.
    if (phone && phone !== currentUser?.phone) {
      const alreadyExists = Boolean(
        await client.user.findUnique({
          where: {
            phone,
          },
          select: {
            id: true,
          },
        })
      )
      if (alreadyExists) {
        return res.json({ ok: false, error: 'Phone already taken' })
      }
      await client.user.update({
        where: {
          id: user?.id,
        },
        data: {
          phone,
        },
      })
      res.json({ ok: true })
    }
    
    ///name 역시 같은 과정을 거침.
    if (name && name !== currentUser?.name) {
      const alreadyExists = Boolean(
        await client.user.findFirst({
          where: {
            name,
          },
          select: {
            id: true,
          },
        })
      )
      if (alreadyExists) {
        return res.json({ ok: false, error: 'Name already taken' })
      }
      await client.user.update({
        where: {
          id: user?.id,
        },
        data: {
          name,
        },
      })
      res.json({ ok: true })
    }
  }
}

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

4. pages/profile/edit.tsx

pages/profile/index.tsx 페이지에서 editProfile을 클릭하면 넘어오는 page

import useMutation from '@libs/client/useMutation'
import useUser from '@libs/client/useUser'
import type { NextPage } from 'next'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import Button from '../../components/button'
import Input from '../../components/input'
import Layout from '../../components/layout'

interface EditProfileForm {
  email?: string
  phone?: string
  name?: string
  formErrors?: string
}
//EditPrrofileForm에 input되는 argument.
//formErrors는 react-hook-form에서 사용할 예정임.

interface EditProfileResponse {
  ok: boolean
  error?: string
}
///editProfile을 하고 나서 return받는 data.

const EditProfile: NextPage = () => {
  const { user } = useUser()
  const {
    register,
    setValue,
    handleSubmit,
    setError,
    formState: { errors },
  } = useForm<EditProfileForm>()
  ///react-hook-form, setError, setValue, formState:{errors}를 잘 확인한다.

  useEffect(() => {
    if (user?.name) setValue('name', user?.name)
    if (user?.email) setValue('email', user?.email)
    if (user?.phone) setValue('phone', user?.phone)
  }, [user, setValue])
///useUser 훅을 사용해서 받아온 기존의 data로 editProfile을
///눌렀을떄, 기존, name, email, phone등이 입력되어 있게 한다.

  const [editProfile, { data, loading }] =
    useMutation<EditProfileResponse>(`/api/users/me`)
    ///'/api/user/me'를 POST로 호출한다.

///onSubmit을 실행했을때, 실행되는 onValid함수.
  const onValid = ({ email, phone, name }: EditProfileForm) => {
    if (loading) return
    if (email === '' && phone === '' && name === '') {
      return setError('formErrors', {
        message: 'Email Or Phone number are required.',
      })
    }
    ///email, name, phone 세 칸이 바어있으면, error던짐.
    ///react-hook-form에서 error던지는 방법을 잘 본다.
    
    editProfile({
      email, //: email !== user?.email ? email : '',
      phone,
      name, //: phone !== user?.phone ? phone : '',
    })
    ///editProfile useMutaion을 실행시킨다.
    /// //부분은 backend에서 처리했음, front에서 처리하는 방법을 적어놓았음.
  }
  useEffect(() => {
    if (data && !data.ok && data.error) {
      setError('formErrors', { message: data.error })
    }
  }, [data, setError])
  ///useEffect로 server에서 오는 error를 받아서 setError로 front에 뿌려준다.
  
  return (
    <Layout canGoBack title="Edit Profile">
      <form onSubmit={handleSubmit(onValid)} className="py-10 px-4 space-y-4">
        <div className="flex items-center space-x-3">
          <div className="w-14 h-14 rounded-full bg-slate-500" />
          <label
            htmlFor="picture"
            className="cursor-pointer py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm font-medium focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 text-gray-700"
          >
            Change
            <input
              id="picture"
              type="file"
              className="hidden"
              accept="image/*"
            />
          </label>
        </div>
        <Input  ///name react-hook-form
          register={register('name')}
          required={false}
          label="Name"
          name="name"
          type="text"
        />
        <Input  ///email react-hook-form
          register={register('email')}
          required={false}
          label="Email"
          name="email"
          type="email"
        />
        <Input  ///phone react-hook-form
          register={register('phone')}
          required={false}
          label="Phone Number"
          name="phone"
          type="number"
          kind="phone"
        />
        {errors.formErrors ? (  ///error가 있을 시, front에 뿌려주는 코딩
                                ///apple-market에서 처음 나온거니끼
                                ///집중해서 잘 봐놓는다.
          <span className="my-2 text-red-500 font-bold block">
            {errors.formErrors?.message}
          </span>
        ) : null}
        <Button text={loading ? 'Loading' : 'Update Profile'} />
      </form>
    </Layout>
  )
}

export default EditProfile

오늘도 화이팅하자~!!

profile
코딩하는초딩쌤

0개의 댓글