Project/Airbnb Clone

[Airbnb 클론코딩] Fetching listings whith server components

hu6r1s 2023. 8. 7. 08:48

2023.07.11 - [Project/Airbnb Clone] - [Airbnb 클론코딩] Listing creation

 

[Airbnb 클론코딩] Listing creation

2023.07.02 - [Project/Airbnb Clone] - [Airbnb 클론코딩] Category UI [Airbnb 클론코딩] Category UI fmf2023.06.25 - [Project/Airbnb Clone] - [Airbnb 클론코딩] Register & Login functionality [Airbnb 클론코딩] Register & Login functionality 202

hu-bris.tistory.com

Listing

이번에 하는 것은 생성된 방 목록을 띄워주는 것이다.

먼저 방이 없을 때 나오는 리스트를 띄워주게 한다.

EmptyState.tsx

page.tsx 파일에서 리스트가 비었을 때, EmptyState 컴포넌트를 띄워준다.

const isEmpty = true

if (isEmpty) {
    return (
      <EmptyState showReset />
    )
}

이제 EmptyState 컴포넌트를 만들어보자.

'use client';

import { useRouter } from "next/navigation";
import Button from "./Button";
import Heading from "./Heading";

interface EmptyState {
  title?: string;
  subtitle?: string;
  showReset?: boolean;
}

const EmptyState: React.FC<EmptyState> = ({
  title = "No exact matches",
  subtitle = "Try changing or removing some of your filters",
  showReset
}) => {
  const router = useRouter();

  return (
    <div className="
      h-[60vh]
      flex
      flex-col
      gap-2
      justify-center
      items-center
    ">
      <Heading
        center
        title={title}
        subtitle={subtitle}
      />
      <div className="w-48 mt-4">
        {showReset && (
          <Button
            outline
            label="Remove all filters"
            onClick={() => router.push("/")}
          />
        )}
      </div>
    </div>
  );
};

export default EmptyState;

showReset가 true일 때, 홈으로 갈 수 있는 버튼을 만들어 놨다.

ListingCard

이제 생성된 방 목록을 하나 하나 띄워주도록 해보자.

import getCurrentUser from "./actions/getCurrentUser";
import getListings from "./actions/getListings";
import Container from "./components/Container";
import EmptyState from "./components/EmptyState";
import ListingCard from "./components/listings/ListingCard";

export default async function Home() {
  const listings = await getListings();
  const currentUser = await getCurrentUser();

  if (listings.length === 0) {
    return (
      <EmptyState showReset />
    )
  }
  return (
    <Container>
      <div className="
        pt-24
        grid
        grid-cols-1
        sm:grid-cols-2
        md:grid-cols-3
        lg:grid-cols-4
        xl:grid-cols-5
        2xl:grid-cols-6
        gap-8
      ">
        {listings.map((listing: any) => {
          return (
            <ListingCard
              currentUser={currentUser}
              key={listing.id}
              data={listing}
            />
          )
        })}
      </div>
    </Container>
  )
}

prisma를 통해 Listing 테이블에 있는 데이터를 가져와 반환해주는 getListings과 map 함수를 사용하여 하나씩 출력시켜 주면 된다. 

"use client";

import useCountries from "@/app/hooks/useCountries";
import { SafeUser } from "@/app/types";
import { Listing, Reservation } from "@prisma/client";
import { useRouter } from "next/navigation";
import React, { useCallback, useMemo } from "react";
import { format } from "date-fns";
import Image from "next/image";
import HeartButton from "../HeartButton";
import Button from "../Button";

interface ListingCardProps {
  data: Listing;
  reservation?: Reservation;
  onAction?: (id: string) => void;
  disabled?: boolean;
  actionLabel?: string;
  actionId?: string;
  currentUser?: SafeUser | null;
}

const ListingCard: React.FC<ListingCardProps> = ({
  data,
  reservation,
  onAction,
  disabled,
  actionLabel,
  actionId = "",
  currentUser
}) => {
  const router = useRouter();
  const { getByValue } = useCountries();

  const location = getByValue(data.locationValue);

  const handleCancel = useCallback(
    (e: React.MouseEvent<HTMLButtonElement>) => {
      e.stopPropagation();

      if (disabled) {
        return;
      }

      onAction?.(actionId);
    }, [onAction, actionId, disabled]);

  const price = useMemo(() => {
    if (reservation) {
      return reservation.totalPrice;
    }

    return data.price;
  }, [reservation, data.price]);

  const reservationDate = useMemo(() => {
    if (!reservation) {
      return null;
    }

    const start = new Date(reservation.startDate);
    const end = new Date(reservation.endDate);

    return `${format(start, "PP")} - ${format(end, "PP")}`
  }, [reservation])
  return (
    <div
      onClick={() => router.push(`/listings/${data.id}`)}
      className="
        col-span-1 cursor-pointer group
    ">
      <div className="flex flex-col gap-2 w-full">
        <div
          className="
            aspect-square
            w-full
            relative
            overflow-hidden
            rounded-xl
          "
        >
          <Image
            fill
            alt="Listing"
            src={data.imageSrc}
            className="
              object-cover
              h-full
              w-full
              group-hover:scale-110
              transition
            "
          />
          <div className="absolute top-3 right-3">
            <HeartButton
              listingId={data.id}
              currentUser={currentUser}
            />
          </div>
        </div>
        <div className="font-semibold text-lg">
          {location?.region}, {location?.label}
        </div>
        <div className="font-right text-neutral-500">
          {reservationDate || data.category}
        </div>
        <div className="flex flex-row items-center gap-1">
          <div className="font-semibold">
            $ {price}
          </div>
          {!reservation && (
            <div className="font-light">night</div>
          )}
        </div>
        {onAction && actionLabel && (
          <Button
            disabled={disabled}
            small
            label={actionLabel}
            onClick={handleCancel}
          />
        )}
      </div>
    </div >
  );
};

export default ListingCard;

이걸로 방 목록 카드 하나의 컴포넌트를 만들어 모든 리스트 목록을 출력시켜준다.