8/7 TIL

Hwi·2024년 8월 7일

TIL

목록 보기
87/96

커스텀훅 Tanstack Query로 수정

useFetchGoods.tsx

import { useQuery, UseQueryResult } from '@tanstack/react-query';
import axios from 'axios';

interface Goods {
  id: number;
  goods_img: string;
  goods_name: string;
  goods_price: number;
}

const fetchGoods = async (): Promise<Goods[]> => {
  const { data } = await axios.get('/api/goods');
  return data;
};

const useFetchGoods = (): UseQueryResult<Goods[], Error> => {
  return useQuery<Goods[], Error>({
    queryKey: ['goods'],
    queryFn: fetchGoods,
  });
};

export default useFetchGoods;

useFetchTourDetail.tsx

import { useQuery, UseQueryResult } from '@tanstack/react-query';
import axios from 'axios';

interface Goods {
  id: number;
  goods_img: string;
  goods_name: string;
  goods_price: number;
}

const fetchGoods = async (): Promise<Goods[]> => {
  const { data } = await axios.get('/api/goods');
  return data;
};

const useFetchGoods = (): UseQueryResult<Goods[], Error> => {
  return useQuery<Goods[], Error>({
    queryKey: ['goods'],
    queryFn: fetchGoods,
  });
};

export default useFetchGoods;

(main)/page.tsx

'use client';
import React, { useRef, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import useFetchGoods from '@/hooks/useFetchGoods';
import Footer from '@/components/common/Footer';
import useFetchTourDetail from '@/hooks/useFetchTourDetail';
import TopPostsSection from '@/components/main/TopPostsSection';
import useScrollTrigger from '@/hooks/useScrollTrigger';
import useSlideAnimation from '@/hooks/useSlideAnimation';
import VideoSection from '@/components/main/VideoSection';

const MainPage = () => {
  const sectionsRef = useRef<(HTMLDivElement | null)[]>([]);
  const planetsRef = useRef<(HTMLDivElement | null)[]>([]);
  const [videoLoaded, setVideoLoaded] = useState<boolean>(false);
  const [currentSlide, setCurrentSlide] = useState<number>(0);

  const { data: goods, isLoading: goodsLoading,  error: goodsError } = useFetchGoods();
  const { data: tourDetail, isLoading: tourLoading, error: tourError} = useFetchTourDetail();

  const planets = tourDetail?.planets || [];
  const visiblePlanetsCount = 3; // 처음에 보일 행성 수

  const handleNextSlide = () => {
    setCurrentSlide((prev) => (prev + 1) % planets.length);
  };

  const handlePrevSlide = () => {
    setCurrentSlide((prev) => (prev - 1 + planets.length) % planets.length);
  };

  // 비디오 로드 확인
  useScrollTrigger(videoLoaded, sectionsRef);

  // 슬라이드 행성 애니메이션
  useSlideAnimation(videoLoaded, planets, currentSlide, visiblePlanetsCount, planetsRef);

  return (
    <div className='w-full'>
      <VideoSection
        videoSrc='/videos/main.mp4'
        heading='Voyage X'
        subHeading='상상을 현실로, 우주에서의 만남'
        sectionRef={{ current: sectionsRef.current[0] }} // sectionRef 타입 맞추기 위해 명시적 지정
        setVideoLoaded={setVideoLoaded}
      />

      <section
        ref={(el) => {
          sectionsRef.current[1] = el as HTMLDivElement;
        }}
        className='section h-screen flex flex-col items-center justify-center relative bg-center bg-cover bg-no-repeat'
        style={{ backgroundImage: 'url(/images/section2.png)' }}
      >
        <div className='absolute top-32 left-4 sm:top-44 sm:left-16 text-white font-yangpyeong text-2xl sm:text-4xl font-bold fade-text'>
          Let&apos;s Find Popular Planets!
        </div>
        <div className='scroll-container h-full w-full relative flex items-center justify-center'>
          <button
            onClick={handlePrevSlide}
            className='absolute left-2 sm:left-4 z-10 p-2 bg-white rounded-full'
          ></button>
          <div className='slider-container relative flex items-center justify-center'>
            {planets.map((planet, index) => {
              const isVisible =
                (index >= currentSlide &&
                  index < currentSlide + visiblePlanetsCount) ||
                (index < currentSlide &&
                  index + planets.length < currentSlide + visiblePlanetsCount);

              const adjustedIndex =
                (index +
                  (planets.length - Math.floor(visiblePlanetsCount / 2))) %
                planets.length;

              const isActive =
                index ===
                (currentSlide + Math.floor(visiblePlanetsCount / 2)) %
                  planets.length;

              return (
                <div
                  key={index}
                  ref={(el) => {
                    planetsRef.current[index] = el as HTMLDivElement;
                  }}
                  data-id={planet.id}
                  className={`absolute w-20 h-20 sm:w-24 sm:h-24 transform-gpu transition-opacity duration-500 ${
                    isVisible ? 'opacity-100' : 'opacity-0'
                  }`}
                  style={{
                    transform: `translate3d(${
                      150 *
                      Math.sin(
                        ((adjustedIndex - currentSlide) * (2 * Math.PI)) /
                          planets.length,
                      )
                    }px, 0, ${
                      150 *
                      Math.cos(
                        ((adjustedIndex - currentSlide) * (2 * Math.PI)) /
                          planets.length,
                      )
                    }px) scale(${isActive ? 1.5 : 1})`,
                    zIndex: isActive ? 10 : 0,
                    opacity: isVisible ? (isActive ? 1 : 0.5) : 0,
                  }}
                >
                  <Image
                    src={planet.planet_img}
                    alt={`Planet ${index + 1}`}
                    layout='fill'
                    objectFit='contain'
                  />
                  {isActive && (
                    <div className='text-center absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full w-max'>
                      <p>{planet.name}</p>
                      <p>
                        {planet.price
                          ? `${planet.price.toLocaleString()}`
                          : 'Price Does Not Exist'}
                      </p>
                    </div>
                  )}
                </div>
              );
            })}
          </div>
          <button
            onClick={handleNextSlide}
            className='absolute right-2 sm:right-4 z-10 p-2 bg-white rounded-full'
          ></button>
        </div>
      </section>

      <section
        ref={(el) => {
          sectionsRef.current[2] = el as HTMLDivElement;
        }}
        className='section section-bg h-screen flex flex-col items-center justify-center'
      >
        <h1 className='text-4xl font-bold mb-8 absolute top-44 left-12'>
          Goods Item
        </h1>
        <Link href='/shop'>
          <p className='absolute top-44 right-24 underline'>More+</p>
        </Link>
        {goodsError && <p className='text-red-500'>{goodsError.message}</p>}
        {goodsLoading ? (
          <p>Loading...</p>
        ) : (
          <div className='grid grid-cols-3 gap-4'>
            {goods?.map((item) => (
              <div key={item.id} className='p-4 rounded shadow'>
                <Image
                  src={item.goods_img}
                  alt={item.goods_name}
                  width={300}
                  height={300}
                  className='object-cover bg-white-600'
                />
                <h2 className='text-xl font-semibold mt-4'>
                  {item.goods_name}
                </h2>
                <p className='text-sm'>{item.goods_price}</p>
              </div>
            ))}
          </div>
        )}
      </section>

      <section
        ref={(el) => {
          sectionsRef.current[3] = el as HTMLDivElement;
        }}
        className='section section-bg h-screen flex items-center justify-center'
      >
        <TopPostsSection />
      </section>

      <section
        ref={(el) => {
          sectionsRef.current[4] = el as HTMLDivElement;
        }}
        className='section h-screen flex items-center justify-center'
        style={{
          backgroundImage: "url('/images/section5-bg.png')",
          backgroundSize: 'cover',
          backgroundRepeat: 'no-repeat',
          backgroundPosition: 'center',
          backgroundAttachment: 'fixed',
        }}
      >
        <h1>5번째 섹션입니다</h1>
      </section>
      <Footer />
    </div>
  );
};

export default MainPage;
profile
개발자가 되고 싶어~~~

0개의 댓글