Product_UploadForm

김종민·2022년 8월 5일
0

apple-market

목록 보기
18/37


들어가기
상품 업로드 페이지 구성

0.withHandler.ts

-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 })
    }
  }
}

1. API(pages/api/products/index.ts)

-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,
  })
)

2. client(pages/products/upload.tsx)

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

3.components/textarea.tsx

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>
  )
}
profile
코딩하는초딩쌤

1개의 댓글

comment-user-thumbnail
2024년 2월 5일

훌륭한 사이트에 계속해서 더 많은 게시물을 게시해 보세요! spend elon musk money

답글 달기