상품의 상세페이지

app > products > [id] > page.tsx 생성

임시로 데이터를 받아오는 함수로 먼저 확인

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

Detail product Skeleton

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

확인했으면,

detail 화면에 데이터 그리기

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