ImageUpload.3.(Resizing image, productImage)

김종민·2022년 8월 18일
0

apple-market

목록 보기
29/37


들어가기
1. CF에서 이미지를 resizing하는 방법을 알아보자.

CF 대시보드에서 변수(Variant)를 클릭.
밑에 appleavatar, avatar, public은 미리 만들어 놓은 것들
20개까지 만드는거 가능함.

그러고 나서는

이미지제공 URL
https://imagedelivery.net/9VhLr461mPKMhcmTPOPfGg/<image_id>/<variant_name>
로 image를 화면에 load할떄, variant_name에 원하는 변수(appleavatar, public..)를
넣어주면 된다.

1. 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, useState } 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
  photo: FileList  ///2. FileList로 type을 설정해줌
}

interface UploadProductMutation {
  ok: boolean
  product: Product
}

const Upload: NextPage = () => {
  const { register, handleSubmit, watch } = useForm<UploadProductForm>()
  const [uploadProduct, { loading, data }] =
    useMutation<UploadProductMutation>('/api/products')
  const onValid = async ({ name, price, description }: UploadProductForm) => {
  ///6.CF에 upload하기!!!
  
    if (loading) return
    if (photo && photo.length > 0) {
      const { uploadURL } = await (await fetch(`/api/files`)).json()
      const form = new FormData()
      form.append('file', photo[0], name)
      const {
        result: { id },
      } = await (await fetch(uploadURL, { method: 'POST', body: form })).json()
      uploadProduct({ name, price, description, photoId: id })
    } else {
      uploadProduct({ name, price, description })
    }
  }
  const router = useRouter()
  useEffect(() => {
    if (data?.ok) {
      router.push(`/products/${data.product.id}`)
    }
  }, [data, router])
  const photo = watch('photo')  ///3.  watch로 file이 choice되는것을 감지
  const [photoPreview, setPhotoPreview] = useState('')  ///4. 미리보기만들기 
  useEffect(() => {
    if (photo && photo.length > 0) {
      const file = photo[0]
      setPhotoPreview(URL.createObjectURL(file))
    }
  }, [photo])
  return (
    <Layout canGoBack title="Upload Product">
      <form className="px-4 space-y-5 py-10" onSubmit={handleSubmit(onValid)}>
        <div>
          {photoPreview ? (   ///5. 미리보기 완성!!!!
            <img
              src={photoPreview}
              className="w-full text-gray-600 aspect-video"
            />
          ) : (
            <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   ////1. imageFile이 들어갈 부분을 먼저 설정해줌.
                {...register('photo')}
                accept="image/*"
                className="hidden"
                type="file"
              />
            </label>
          )}
        </div>
        <div className="my-5">
          <Input
            register={register('name', { required: true })}
            required
            label="Name"
            name="name"
            type="text"
          />
          <Input
            register={register('price', { required: true })}
            required
            label="Price"
            placeholder="0.00"
            name="price"
            tepe="text"
            kind="price"
          />
          <TextArea
            register={register('description', { required: true })}
            name="description"
            label="Description"
            required
          />

          <Button text={loading ? 'Loading..' : 'Upload Product'} />
        </div>
      </form>
    </Layout>
  )
}
export default Upload

2. pages/api/products/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>
) {
  if (req.method === 'GET') {
    const {
      query: { page, limit },
    } = req
    const products = await client.product.findMany({
      include: {
        _count: {
          select: {
            favs: true,
          },
        },
      },
      take: Number(limit),
      skip: (Number(page) - 1) * Number(limit),
      orderBy: { createdAt: 'asc' },
    })
    res.json({
      ok: true,
      products,
    })
  }
  if (req.method === 'POST') {
    const { name, price, description, photoId } = req.body
    const { user } = req.session
    const product = await client.product.create({
      data: {
        name,
        price: +price,
        description,
        image: photoId,  ///image를 photoId로 받으면, DB에 id가 저장됨.
        user: {
          connect: {
            id: user?.id,
          },
        },
      },
    })
    res.json({ ok: true, product })
  }
}
export default withApiSession(
  withHandler({
    methods: ['GET', 'POST'],
    handler,
  })
)

3. pages/products/[id].tsx

CF에서 이미지 가져와서 뿌려주는 부분만 CHECK!!!!


import useMutation from '@libs/client/useMutation'
import useUser from '@libs/client/useUser'
import { cls } from '@libs/client/utils'
import { Product, User } from '@prisma/client'
import type { NextPage } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { userAgent } from 'next/server'
import useSWR, { useSWRConfig } from 'swr'
import Button from '../../components/button'
import Layout from '../../components/layout'

interface ProductWithUser extends Product {
  user: User
}

interface ItemDetailResponse {
  ok: boolean
  product: ProductWithUser
  relatedProducts: Product[]
  isLiked: boolean
}

const ItemDetail: NextPage = () => {
  const { user, isLoading } = useUser()
  const router = useRouter()
  // console.log(router.query) ///product id를 따냄.
  const { mutate } = useSWRConfig()
  const { data, mutate: boundMutate } = useSWR<ItemDetailResponse>(
    router.query.id ? `/api/products/${router.query.id}` : null
  )

  const [toggleFav] = useMutation(`/api/products/${router.query.id}/fav`)

  const onFavClick = () => {
    if (!data) return
    boundMutate({ ...data, isLiked: !data.isLiked }, false)
    toggleFav({})
    //mutate('/api/users/me', { ok: false }, false)
    //mutate('/api/users/me', (prev:any)=>({ok:!prev.ok}), false)
  }
  return (
    <Layout canGoBack>
      <div className="px-4 py-4">
        <div className="mb-8">
          <div className="relative ">
          ///CF에서 이미지 가져와서 뿌려줌!!!
            <Image
              src={`https://imagedelivery.net/9VhLr461mPKMhcmTPOPfGg/${data?.product.image}/public`}
              className=" bg-slate-400 rounded-md"
              width={600}
              height={500}
              alt="Picture of the author"
            />
            <div className="flex cursor-pointer py-3 border-b items-center space-x-3">
            ///CF에서 이미지 가져와서 뿌려줌!!!
            ///CF에서 이미지 가져와서 뿌려줌!!!
              <Image
                width={48}
                height={48}
                src={`https://imagedelivery.net/9VhLr461mPKMhcmTPOPfGg/${data?.product?.user?.avatar}/appleavatar`}
                className="w-12 h-12 rounded-full bg-fuchsia-300"
                alt="Picture of the author"
              />
              <div>
                <p className="text-sm font-medium text-gray-700">
                  {data?.product?.user?.name}
                </p>
                <Link href={`/users/profiles/${data?.product?.user?.id}`}>
                  <a className="rext-xs font-medium">View profile &rarr;</a>
                </Link>
              </div>
            </div>
            <div className="mt-10 ">
              <h1 className="text-3xl font-bold text-gray-900 ">
                {data?.product?.name}
              </h1>
              <p className="text-3xl mt-3 pb-5 text-slate-400 font-black border-b-2">
                {data?.product?.price}원
              </p>
              <p className="text-base my-6 text-gray-800">
                {data?.product?.description}
              </p>
              <div className="flex items-center justify-between">
                <Button large text="Talk to seller" />
                <button
                  onClick={onFavClick}
                  className={cls(
                    'p-3 mx-3 rounde flex items-center justify-center',
                    data?.isLiked
                      ? 'text-red-500 hover:text-red-300'
                      : 'text-gray-400  hover:text-gray-200'
                  )}
                >
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    className="h-9 w-9"
                    viewBox="0 0 20 20"
                    fill="currentColor"
                  >
                    <path
                      fillRule="evenodd"
                      d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z"
                      clipRule="evenodd"
                    />
                  </svg>
                </button>
              </div>
            </div>
          </div>
        </div>
        <div className="mt-6">
          <h2 className="text-2xl font-bold text-gray-800">Similar items</h2>
          <div className="mt-5 grid grid-cols-3 gap-4">
            {data?.relatedProducts.map((product) => (
              <div key={product.id}>
                <div className="h-36 w-full bg-slate-400" />
                <h3>{product.name}</h3>
                <p className="text-sm font-medium text-gray-900">
                  {product.price}원
                </p>
              </div>
            ))}
          </div>
        </div>
      </div>
    </Layout>
  )
}

export default ItemDetail

CF에 이미지 업로드하고 화면에 뿌려주는 방법을
editProfile에서 자세하게 다뤄본 다음,
product page에서 전체적으로 업로드및 사진불러오기 까지 다 해봄.
까먹지 말자!!!!
다음 포스터에서는 NextJS의 Image에 해서 알아본다.

profile
코딩하는초딩쌤

0개의 댓글