diff --git a/package-lock.json b/package-lock.json index 4a9fefdf..b34d1728 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,13 @@ "@types/node": "^16.18.54", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", + "intersection-observer": "^0.12.2", "react": "^18.2.0", "react-cookie": "^6.1.1", "react-daum-postcode": "^3.1.3", "react-dom": "^18.2.0", "react-kakao-maps-sdk": "^1.1.21", + "react-player": "^2.13.0", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "react-slick": "^0.29.0", @@ -9499,6 +9501,11 @@ "node": ">= 0.4" } }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" + }, "node_modules/ipaddr.js": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", @@ -12324,6 +12331,11 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -12486,6 +12498,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -14987,6 +15004,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -15005,6 +15027,21 @@ "react-dom": "^16.8 || ^17 || ^18" } }, + "node_modules/react-player": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.13.0.tgz", + "integrity": "sha512-gkY7ZdbVFztlKFFhCPcnDrFQm+L399b8fhWsKatZ+b2wpKJwfUHBXQFMRxqYQGT0ic1/wQ7D7EZEWy7ZBqk2pw==", + "dependencies": { + "deepmerge": "^4.0.0", + "load-script": "^1.0.0", + "memoize-one": "^5.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.0.1" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", diff --git a/package.json b/package.json index 2e4a96aa..2bb9fb72 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "@types/node": "^16.18.54", "@types/react": "^18.2.22", "@types/react-dom": "^18.2.7", + "intersection-observer": "^0.12.2", "react": "^18.2.0", "react-cookie": "^6.1.1", "react-daum-postcode": "^3.1.3", "react-dom": "^18.2.0", "react-kakao-maps-sdk": "^1.1.21", + "react-player": "^2.13.0", "react-router-dom": "^6.16.0", "react-scripts": "5.0.1", "react-slick": "^0.29.0", @@ -46,6 +48,7 @@ }, "devDependencies": { "@types/react-daum-postcode": "^1.6.1", + "react-player":"2.13.0", "eslint": "^8.50.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", diff --git a/public/assets/images/mute.svg b/public/assets/images/mute.svg new file mode 100644 index 00000000..8f3b5f06 --- /dev/null +++ b/public/assets/images/mute.svg @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/public/assets/images/speaker.svg b/public/assets/images/speaker.svg new file mode 100644 index 00000000..300db852 --- /dev/null +++ b/public/assets/images/speaker.svg @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/src/pages/detailPet/DetailPetData.tsx b/src/pages/detailPet/DetailPetData.tsx index f0cc55d2..f8e9d255 100644 --- a/src/pages/detailPet/DetailPetData.tsx +++ b/src/pages/detailPet/DetailPetData.tsx @@ -13,10 +13,17 @@ const DetailPetData = () => { const { data, isLoading } = useQuery({ queryKey: ['pet', petId], queryFn: () => { - return fetch(`${process.env.REACT_APP_URI}/pet/${petId}`).then((res) => - res.json(), - ); + return fetch(`${process.env.REACT_APP_URI}/pet/${petId}`).then((res) => { + if (res.status === 200) { + return res.json(); + } + if (res.status === 404) { + throw new Error('해당 친구를 찾을 수 없어요...'); + } + throw new Error('데이터 로드 중 문제가 생겼어요...'); + }); }, + suspense: true, }); if (isLoading) return
로딩중
; const labels = ['영리함', '친화력', '운동신경', '적응력', '활발함']; @@ -27,7 +34,7 @@ const DetailPetData = () => { height: 400, canvas, labels, - data: data.response.petPolygonProfileDto, + data: data?.response?.petPolygonProfileDto, willAnimate: true, }; diff --git a/src/pages/detailPet/DetailPetPage.tsx b/src/pages/detailPet/DetailPetPage.tsx index d5ed4af8..cd61cd2f 100644 --- a/src/pages/detailPet/DetailPetPage.tsx +++ b/src/pages/detailPet/DetailPetPage.tsx @@ -1,11 +1,14 @@ import DetailPetData from 'pages/detailPet/DetailPetData'; import GNB from 'layouts/GNB'; +import ErrorBoundary from 'commons/ErrorBoundary'; const DetailPetPage = () => { return ( <> - + + + ); }; diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index dc10fae5..0176e133 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -1,12 +1,11 @@ import GNB from 'layouts/GNB'; -import Home from './Home'; +import TestHome from './TestHome'; const HomePage = () => { return (
- {/* gnb가 감싸게 하기 */} - +
); }; diff --git a/src/pages/home/HomeVideo.tsx b/src/pages/home/HomeVideo.tsx new file mode 100644 index 00000000..f01dbd32 --- /dev/null +++ b/src/pages/home/HomeVideo.tsx @@ -0,0 +1,91 @@ +import { useState, useRef, useEffect } from 'react'; +import ReactPlayer from 'react-player'; + +export interface HomeVideoProps { + url: string; + muted: boolean; + setMuted: (mute: boolean) => void; + handleDoubleClick: () => void; + hovering: boolean; + setHovering: (hover: boolean) => void; +} + +const HomeVideo = (props: HomeVideoProps) => { + const { url, muted, setMuted, handleDoubleClick, hovering, setHovering } = + props; + const [playing, setPlaying] = useState(false); + const [opacity, setOpacity] = useState(1); + const videoRef = useRef(null); + + useEffect(() => { + const options = { + root: null, + rootMargin: '0px', + threshold: 0.5, + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setPlaying(true); + } else { + setPlaying(false); + } + }); + }, options); + + if (videoRef.current) { + observer.observe(videoRef.current); + } + + return () => { + if (videoRef.current) { + observer.unobserve(videoRef.current); + } + }; + }, []); + + return ( + <> + {hovering && ( +
+
왼쪽으로
+
당겨보세요
+
+ )} +
setPlaying((prev) => !prev)} + onDoubleClick={handleDoubleClick} + className="h-screen w-screen items-center justify-center" + onMouseEnter={() => { + setHovering(true); + setOpacity(1); + setTimeout(() => { + setHovering(false); + }, 1500); + setTimeout(() => { + setOpacity(0); + }, 1300); + }} + > + +
+ + ); +}; + +export default HomeVideo; diff --git a/src/pages/home/TestHome.tsx b/src/pages/home/TestHome.tsx new file mode 100644 index 00000000..9fe0c262 --- /dev/null +++ b/src/pages/home/TestHome.tsx @@ -0,0 +1,168 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { useState, useEffect, useRef } from 'react'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import { A11y, Autoplay, Mousewheel, Keyboard } from 'swiper/modules'; +import { useNavigate } from 'react-router-dom'; +import 'swiper/css'; +import 'swiper/css/pagination'; +import HomeVideo from './HomeVideo'; + +const TestHome = () => { + const [muted, setMuted] = useState(true); + const [hovering, setHovering] = useState(false); + const [opacity, setOpacity] = useState(0); + const navigate = useNavigate(); + const nextPageRef = useRef(null); + const { data, isLoading, fetchNextPage } = useInfiniteQuery( + ['home', 1], + ({ pageParam = 1 }) => { + const apiUrl = `${process.env.REACT_APP_URI}/short-forms/home?page=${pageParam}&size=5`; + return fetch(apiUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json()); + }, + { + getNextPageParam: (lastPage) => { + return lastPage.response.hasNext ? lastPage.response.nextPage : false; + }, + suspense: true, + }, + ); + + const io = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + fetchNextPage(); + } + }); + return () => { + io.disconnect(); + }; + }); + useEffect(() => { + if (nextPageRef.current) { + io.observe(nextPageRef.current); + } + return () => { + io.disconnect(); + }; + }); + + const handleDoubleClick = () => { + setMuted((prev) => !prev); + setOpacity(1); + setTimeout(() => { + setOpacity(0); + }, 200); + }; + console.log(hovering); + + return ( +
+ + + {data?.pages.map((page, pagesIndex) => + page.response.shortForms.map((shortForm: any, index: number) => { + if (pagesIndex * 5 + index === 5 * data.pages.length - 1) { + return ( + +
+ + { + navigate(`/pet/${shortForm.petId}`); + }} + > + + + + +
+ + + + ); + } + return ( + + { + navigate(`/pet/${shortForm.petId}`); + }} + > + + + + +
+
동물 정보 가져오겠습니다~
+
+
+
+
+ ); + }), + )} + +
+ ); +}; + +export default TestHome;