들어가기
MyProfile에서
1. review(나에게 쓴 리뷰, 내가 쓴 리뷰)
2. editProfile을 알아본다.
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,
})
)
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 →</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
이 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,
})
)
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
오늘도 화이팅하자~!!