들어가기
1. CF에서 이미지를 resizing하는 방법을 알아보자.
CF 대시보드에서 변수(Variant)를 클릭.
밑에 appleavatar, avatar, public은 미리 만들어 놓은 것들
20개까지 만드는거 가능함.
그러고 나서는
이미지제공 URL
https://imagedelivery.net/9VhLr461mPKMhcmTPOPfGg/<image_id>/<variant_name>
로 image를 화면에 load할떄, variant_name에 원하는 변수(appleavatar, public..)를
넣어주면 된다.
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
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,
})
)
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 →</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에 해서 알아본다.