들어가기
상품 업로드 페이지 구성
-method를 배열로 하나의 API주소(/api/products)에서 'GET, POST'다 받을 수 있게끔
withHandler를 바꿔줌,
import { NextApiRequest, NextApiResponse } from 'next'
export interface ResponseType {
ok: boolean
[key: string]: any ///ok외의 다른 응답은 [key:string]:any 로 처리
}
type method = 'GET' | 'POST' | 'DELETE' ///method type을 만들어줌.
interface ConfigType {
methods: method[] ///ConfigType에 methods를 method의 배열로 지정해줌.
handler: (req: NextApiRequest, res: NextApiResponse) => void
isPrivate?: boolean
}
export default function withHandler({
methods,
handler,
isPrivate = true,
}: ConfigType) {
return async function (
req: NextApiRequest,
res: NextApiResponse
): Promise<any> {
if (req.method && !methods.includes(req.method as any)) {
return res.status(405).end()
///req가 있는데, req.method가 methods에 포함되어 있지 않으면, return
}
if (isPrivate && !req.session.user) {
return res.status(401).json({ ok: false, error: 'Plz log in' })
}
try {
await handler(req, res)
} catch (error) {
console.log(error)
return res.status(500).json({ error })
}
}
}
-upload API를 따로 만들지 않는 이유는 그렇게 길지 않아서
-index.ts에 상품보는 API(GET)와 upload API(POST)를 같이 만듬.
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') {
///GET으로 요청이 오면, index.tsx page, 모든 상품을 뿌려주는 page
const products = await client.product.findMany({
include: { ///각각의 상품에 heart(fav) 숫자를 count해서 같이 return해줌.
///computed Field와 같은 느낌.
_count: {
select: {
favs: true,
},
},
},
})
res.json({
ok: true,
products, ///ok:true와 products return해줌.
})
}
if (req.method === 'POST') {
///POST로 api요청을 받을 경우(upload시~)
const { name, price, description } = req.body ///client에서 보내준 data받음
const { user } = req.session ///loginㅎ된 유저 확인함.
const product = await client.product.create({
data: {
name,
price: +price, ///가격은 Number라서 +붙여줌,
///혹은 Number(price) 라고 해도 됨
description,
image: 'aaa', ///image는 나중에 다룰거라서 일단, 'aaa'로 입력함.
user: { ///product랑 user 연결해 줌.
connect: {
id: user?.id,
},
},
},
})
res.json({ ok: true, product }) ///upload가 성공적으로 이루어지면,
///ok:true, product return함.
}
}
export default withApiSession(
withHandler({
methods: ['GET', 'POST'], ///wihtHadler에서 methods를 method배열로
///바꾸어 주어서 이렇게 처리함.
handler,
})
)
import useMutation from '@libs/client/useMutation'
import { Product } from '@prisma/client'
import type { NextPage } from 'next'
import { useRouter } from 'next/router'
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'
import TextArea from '../../components/textarea'
interface UploadProductForm {
name: string
price: number
description: string
}
///upload시 입력해 주어야 하는 argument
interface UploadProductMutation {
ok: boolean
product: Product
}
///입력 후 return 받는 data 종류
///Product는 schema.prisma의 model임.
const Upload: NextPage = () => {
const { register, handleSubmit } = useForm<UploadProductForm>()
///react-hook-form, form에 입력되는 argument는 <UploadUploadProductForm>임
const [uploadProduct, { loading, data }] =
useMutation<UploadProductMutation>('/api/products')
///useMutation을 사용해서, uploadForm의 입력 data를
///API('/api/products')로 보내줌.
///이 useMutation은 uploadProduct라는 이름으로 사용됨,
///data를 보내고 loading, data를 return 받음.
const onValid = (data: UploadProductForm) => {
if (loading) return
uploadProduct(data)
}
///react-hook-form, uploadItem 클릭시, 실행되는 함수
///onSubmit={handleSubmit(onValid)}로 사용됨.
const router = useRouter()
useEffect(() => {
if (data?.ok) {
router.push(`/products/${data.product.id}`)
}
}, [data, router])
///상품 업로드를 하고, data.ok 를 return받으면,
///상품 detail page로 이동시킴!!
return (
<Layout canGoBack title="Upload Product">
<form className="px-4 space-y-5 py-10" onSubmit={handleSubmit(onValid)}>
///react-hook-form, onSubmit={handleSubmit(onValid)}
<div>
<label className="w-full cursor-pointer text-gray-600 hover:border-orange-500 hover:text-orange-500 flex items-center justify-center border-2 border-dashed border-gray-300 h-48 rounded-md">
<svg
className="h-12 w-12"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<input className="hidden" type="file" /> ///file 업로드 칸 잘 봐둘것!!
</label>
</div>
<div className="my-5">
<Input ///Input component로 register 날려줌.
register={register('name', { required: true })}
required
label="Name"
name="name"
type="text"
/>
<Input ///Input Component로 price 날려줌.
register={register('price', { required: true })}
required
label="Price"
placeholder="0.00"
name="price"
tepe="text"
kind="price"
/>
<TextArea ///TextArea로 register 날려줌.
register={register('description', { required: true })}
name="description"
label="Description"
required
/>
<Button text={loading ? 'Loading..' : 'Upload Product'} />
</div>
</form>
</Layout>
)
}
export default Upload
interface TextAreaProps {
label?: string
name?: string
[key: string]: any ///label, name 외의 props들을 다 받는다는 뜻,
}
export default function TextArea({
label,
name,
register, ///[key: string]: any로 받는것~
...rest ///[key: string]: any로 받는것~
}: TextAreaProps) {
return (
<div>
{label ? (
<label
htmlFor={name}
className="mb-1 block text-sm font-medium text-gray-700"
>
{label}
</label>
) : null}
<textarea
id={name}
className="mt-3 shadow-lg w-full focus:ring-orange-500 rounded-md border-gray-300 focus:border-orange-500"
rows={4}
{...register} ///register받는것
{...rest} ///그외 기타등등 모두 받는것!
/>
</div>
)
}
훌륭한 사이트에 계속해서 더 많은 게시물을 게시해 보세요! spend elon musk money