임시로 데이터를 받아오는 함수로 먼저 확인
async function getProduct() {
await new Promise(res => setTimeout(res, 6000))
}
export default async function ProductDetail({params: {id}} : {
params: {id: string}
}) {
const product = await getProduct();
return <span>Product detail {id}</span>
}
app > products > [id] > loading.tsx
import { PhotoIcon } from "@heroicons/react/24/solid";
export default function Loading() {
return (
<div className="animate-pulse p-5 flex flex-col gap-5">
<div className="aspect-square border-neutral-700 text-neutral-700 border-4 border-dashed rounded-md flex justify-center items-center">
<PhotoIcon className="h-28" />
</div>
<div className="flex gap-2 items-center">
<div className="size-14 rounded-full bg-neutral-700" />
<div className="flex flex-col gap-1">
<div className="h-5 w-40 bg-neutral-700 rounded-md" />
<div className="h-5 w-20 bg-neutral-700 rounded-md" />
</div>
</div>
<div className="h-10 w-80 bg-neutral-700 rounded-md" />
</div>
);
}
확인했으면,
app > products > [id] > page.tsx
import { notFound } from "next/navigation";
import db from "../../../lib/db";
import getSession from "../../../lib/session";
import Image from "next/image";
import { UserIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
import { formatToWon } from "../../../lib/utils";
async function getIsOwner(userId:number) {
const session = await getSession();
if(session.id){
return session.id === userId; // 제품을 업로드한 유저와 같다면 true
}
return false;
}
async function getProduct(id: number) {
const product = await db.product.findUnique({
where: {
id
},
include: {
user: {
select: {
username: true,
avatar: true
}
}
}
})
// await new Promise(res => setTimeout(res, 1000))
return product
}
export default async function ProductDetail({params} : {
params: {id: string}
}) {
const id = Number(params.id)
if(isNaN(id)) notFound();
const product = await getProduct(id);
if(!product) notFound();
const isOwner = await getIsOwner(product.userId);
return (
<div>
<div className="relative aspect-square">
<Image fill src={product.photo} alt={product.title}/>
</div>
<div className="p-5 flex items-center gap-3 border-b border-neutral-700">
<div className="size-10 rounded-full" >
{product.user.avatar !== null
? (
<Image src={product.user.avatar} width={40} height={40} alt={product.user.username}/>
) : (
<UserIcon />
)}
</div>
<div>
<h3>{product.user.username}</h3>
</div>
</div>
<div className="p-5">
<h1 className="text-2xl font-semibold">{product.title}</h1>
<p>{product.description}</p>
</div>
<div className="fixed w-full bottom-0 left-0 p-5 pb-10 bg-neutral-800 flex justify-between items-center">
<span className="font-semibold text-xl">{formatToWon(product.price)}원</span>
{ isOwner
? <button className="bg-red-500 px-5 py-2.5 rounded-md text-white font-semibold">Delete product</button>
: null
}
<Link className="bg-orange-500 px-5 py-2.5 rounded-md text-white font-semibold" href={``}>채팅하기</Link>
</div>
</div>
);
}