From fbfb11a16b38fb7bb2fc5d8d7d198f7bb4014b28 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Thu, 25 Jan 2024 15:59:03 +0900 Subject: [PATCH 01/11] Style: add styles --- src/components/User/Mywork/Mywork.module.scss | 4 +- .../User/Profile/Profile.module.scss | 3 +- src/mocks/handlers/auth.ts | 147 ++++++++---------- 3 files changed, 72 insertions(+), 82 deletions(-) diff --git a/src/components/User/Mywork/Mywork.module.scss b/src/components/User/Mywork/Mywork.module.scss index 055963d1..a073ca43 100644 --- a/src/components/User/Mywork/Mywork.module.scss +++ b/src/components/User/Mywork/Mywork.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { display: flex; @@ -35,6 +35,6 @@ } .title { - @include typography(subTitle); + @include typography(tabLabel); } } diff --git a/src/components/User/Profile/Profile.module.scss b/src/components/User/Profile/Profile.module.scss index 32ea1f7b..3ec90f70 100644 --- a/src/components/User/Profile/Profile.module.scss +++ b/src/components/User/Profile/Profile.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { } @@ -13,6 +13,7 @@ width: 7.2rem; height: 7.2rem; border-radius: 50%; + border: 2px solid $neutral200; background-position: center; background-repeat: no-repeat; diff --git a/src/mocks/handlers/auth.ts b/src/mocks/handlers/auth.ts index 8a0d3431..6803db2d 100644 --- a/src/mocks/handlers/auth.ts +++ b/src/mocks/handlers/auth.ts @@ -1,4 +1,4 @@ -import { http, HttpResponse, PathParams } from "msw"; +import {http, HttpResponse, PathParams} from 'msw'; interface LoginBody { email: string; @@ -11,7 +11,7 @@ interface Email { interface Sert { email: string; - token: string; + code: string; } interface Form { @@ -27,32 +27,30 @@ interface Find { newPassword: string; } -const UserDummy = [ - { email: "test@naver.com", password: "asdasd123#", nickname: "테스트계정" }, -]; +const UserDummy = [{email: 'test@naver.com', password: 'asdasd123#', nickname: '테스트계정'}]; const FormDummy = [ { - email: "test2@naver.com", - password: "asdasd123#", - nickname: "테스트", - profile: "https://avatars.githubusercontent.com/u/100336573?v=4", - token: "asdf1234", + email: 'test2@naver.com', + password: 'asdasd123#', + nickname: '테스트', + profile: 'https://avatars.githubusercontent.com/u/100336573?v=4', + code: 'asdf1234', }, ]; export const auth = [ /* ----------------------------------- <로그인> ---------------------------------- */ - http.post("/api/login", async ({ request }) => { - const { email, password } = await request.json(); + http.post('/api/login', async ({request}) => { + const {email, password} = await request.json(); // 로그인 유저 정보 일치 if (email === UserDummy[0].email && password === UserDummy[0].password) { return HttpResponse.json(null, { status: 200, headers: { - "Set-Cookie": "access-token=msw-access, refresh-token=msw-refresh", + 'Set-Cookie': 'access-token=msw-access, refresh-token=msw-refresh', }, }); } @@ -62,8 +60,8 @@ export const auth = [ { status: 400, response_code: 401, - detail: "이메일 또는 비밀번호를 확인해주세요.", - issue: "tripvote.site/error", + detail: '이메일 또는 비밀번호를 확인해주세요.', + issue: 'tripvote.site/error', }, { status: 400, @@ -74,51 +72,48 @@ export const auth = [ /* ------------------------------------ <회원가입> ----------------------------------- */ /* -------------------------------- 이메일 인증 요청 ------------------------------- */ - http.post( - "/api/auth/register/send-email", - async ({ request }) => { - const { email } = await request.json(); - - // 이메일 중복체크 실패 - if (email === UserDummy[0].email) { - return HttpResponse.json({ - status: 200, - response_code: 401, - detail: "이미 가입된 이메일입니다.", - issue: "tripvote.site/error", - }); - } - - // 이메일 중복체크 성공 - return HttpResponse.json({ status: 200 }); - }, - ), + http.post('/api/auth/register/send-email', async ({request}) => { + const {email} = await request.json(); + + // 이메일 중복체크 실패 + if (email === UserDummy[0].email) { + return HttpResponse.json({ + status: 200, + responseCode: 101, + detail: '이미 가입된 이메일입니다.', + issue: 'tripvote.site/error', + }); + } + + // 이메일 중복체크 성공 + return HttpResponse.json({status: 200}); + }), /* ------------------------------ 이메일 인증 완료 버튼 ------------------------------ */ - http.post( - "/api/auth/register/check-token", - async ({ request }) => { - const { email, token } = await request.json(); - - // 인증 코드 일치 - if (email === FormDummy[0].email && token === FormDummy[0].token) { - return HttpResponse.json({ - status: 200, - }); - } - - // 인증 코드 불일치 + http.post('/api/auth/register/check-token', async ({request}) => { + const {email, code} = await request.json(); + + // 인증 코드 일치 + if (email === FormDummy[0].email && code === FormDummy[0].code) { return HttpResponse.json({ status: 200, - response_code: 403, - detail: "인증코드가 올바르지 않습니다.", - issue: "tripvote.site/error", + data: { + token: 1234, + }, }); - }, - ), + } + + // 인증 코드 불일치 + return HttpResponse.json({ + status: 200, + responseCode: 203, + detail: '인증코드가 올바르지 않습니다.', + issue: 'tripvote.site/error', + }); + }), /* ------------------------------ 시작하기 버튼 (form 제출) ----------------------------- */ - http.post("/api/auth/register", async () => { + http.post('/api/auth/register', async () => { // 회원가입 성공 return HttpResponse.json({ status: 200, @@ -128,37 +123,31 @@ export const auth = [ /* --------------------------------- 비밀번호 찾기 -------------------------------- */ /* -------------------------- 비밀번호 찾기 (이메일 인증코드 발송)송------------------------- */ - http.post( - "/api/auth/modify/lost-password/send-email", - async () => { - return HttpResponse.json({ - status: 200, - }); - }, - ), + http.post('/api/auth/modify/lost-password/send-email', async () => { + return HttpResponse.json({ + status: 200, + }); + }), /* ---------------------------- 비밀번호 찾기 (인증코드 제출)출--------------------------- */ - http.post( - "/api/auth/modify/lost-password/check-token", - async ({ request }) => { - const { token } = await request.json(); - - if (token === "asdf1234") - return HttpResponse.json({ - status: 200, - }); + http.post('/api/auth/modify/lost-password/check-token', async ({request}) => { + const {token} = await request.json(); + if (token === 'asdf1234') return HttpResponse.json({ status: 200, - response_code: 403, - detail: "인증코드가 올바르지 않습니다.", - issue: "tripvote.site/error", }); - }, - ), + + return HttpResponse.json({ + status: 200, + response_code: 403, + detail: '인증코드가 올바르지 않습니다.', + issue: 'tripvote.site/error', + }); + }), /* ------------------------- 비밀번호 찾기 (새로운 비밀번호로 변경)경------------------------- */ - http.post("/api/auth/modify/lost-password", () => { + http.post('/api/auth/modify/lost-password', () => { return HttpResponse.json({ status: 200, }); @@ -166,13 +155,13 @@ export const auth = [ /* --------------------------------- <로그아웃> --------------------------------- */ - http.post("/api/logout", () => { - console.log("로그아웃"); + http.post('/api/logout', () => { + console.log('로그아웃'); return new HttpResponse(null, { status: 200, headers: { - "Set-Cookie": "tmpToken=abc;Path=/;Max-Age=0", + 'Set-Cookie': 'tmpToken=abc;Path=/;Max-Age=0', }, }); }), From 73fea1c3abfe75f62adc155ec09f28462f76669c Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Thu, 25 Jan 2024 16:09:33 +0900 Subject: [PATCH 02/11] Style: change border width in profile image --- src/components/Auth/Input/Input.module.scss | 3 ++- src/components/User/Profile/Profile.module.scss | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Auth/Input/Input.module.scss b/src/components/Auth/Input/Input.module.scss index 2ebe0eea..2e7b91eb 100644 --- a/src/components/Auth/Input/Input.module.scss +++ b/src/components/Auth/Input/Input.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .email, .sertCode, @@ -124,6 +124,7 @@ height: 7.2rem; padding: 8px; border-radius: 50%; + border: 1px solid $neutral200; &__camera { background-color: $neutral100; diff --git a/src/components/User/Profile/Profile.module.scss b/src/components/User/Profile/Profile.module.scss index 3ec90f70..cde4b547 100644 --- a/src/components/User/Profile/Profile.module.scss +++ b/src/components/User/Profile/Profile.module.scss @@ -13,7 +13,7 @@ width: 7.2rem; height: 7.2rem; border-radius: 50%; - border: 2px solid $neutral200; + border: 1px solid $neutral200; background-position: center; background-repeat: no-repeat; From 21113153bb00740f0642896360b4d729c5061a7b Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Thu, 25 Jan 2024 23:32:47 +0900 Subject: [PATCH 03/11] Feat: connect api & apply infinite scroll --- package-lock.json | 9 ++ package.json | 1 + src/api/spaces.ts | 15 ++ src/assets/icons/city_default.svg | 6 + src/components/MySpaceBody/MySpaceBody.tsx | 128 +----------------- .../MySpaceList/MySpaceList.module.scss | 12 +- .../MySpaceBody/MySpaceList/MySpaceList.tsx | 114 +++++++++++----- src/components/MySpaceBody/Tab/Tab.tsx | 24 ++-- src/components/User/Profile/Profile.tsx | 2 +- src/hooks/Spaces/useSpaces.ts | 24 +++- src/types/user.ts | 1 - 11 files changed, 156 insertions(+), 180 deletions(-) create mode 100644 src/assets/icons/city_default.svg diff --git a/package-lock.json b/package-lock.json index 6dfdd286..2ed647cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "react-helmet-async": "^2.0.4", "react-hook-form": "^7.49.2", "react-icons": "^4.12.0", + "react-intersection-observer": "^9.5.3", "react-kakao-maps-sdk": "^1.1.24", "react-mobile-datepicker": "^4.0.2", "react-router-dom": "^6.21.1", @@ -10984,6 +10985,14 @@ "react": "*" } }, + "node_modules/react-intersection-observer": { + "version": "9.5.3", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.5.3.tgz", + "integrity": "sha512-NJzagSdUPS5rPhaLsHXYeJbsvdpbJwL6yCHtMk91hc0ufQ2BnXis+0QQ9NBh6n9n+Q3OyjR6OQLShYbaNBkThQ==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 6e28e42a..3f46b91c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-helmet-async": "^2.0.4", "react-hook-form": "^7.49.2", "react-icons": "^4.12.0", + "react-intersection-observer": "^9.5.3", "react-kakao-maps-sdk": "^1.1.24", "react-mobile-datepicker": "^4.0.2", "react-router-dom": "^6.21.1", diff --git a/src/api/spaces.ts b/src/api/spaces.ts index eab1dfdb..1b6a4442 100644 --- a/src/api/spaces.ts +++ b/src/api/spaces.ts @@ -13,6 +13,21 @@ export const spacesRequest = { } }, + getOutdated: async ({pageParam}: {pageParam: number}) => { + try { + const res = await axios.get('/api/members/my-spaces/outdated', { + params: { + size: 5, + page: pageParam, + }, + withCredentials: true, + }); + return res.data; + } catch (error) { + console.log(error); + } + }, + post: async () => { try { const res = await axios.post('/api/spaces', {withCredentials: true}); diff --git a/src/assets/icons/city_default.svg b/src/assets/icons/city_default.svg new file mode 100644 index 00000000..083314f6 --- /dev/null +++ b/src/assets/icons/city_default.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/MySpaceBody/MySpaceBody.tsx b/src/components/MySpaceBody/MySpaceBody.tsx index ccb5eeae..b07d032c 100644 --- a/src/components/MySpaceBody/MySpaceBody.tsx +++ b/src/components/MySpaceBody/MySpaceBody.tsx @@ -1,134 +1,18 @@ -import { useEffect, useState } from "react"; +import {useState} from 'react'; -import styles from "./MySpaceBody.module.scss"; +import styles from './MySpaceBody.module.scss'; -import MySpaceList from "./MySpaceList/MySpaceList"; -import Tab from "./Tab/Tab"; - -import { MySpaces } from "@/types/user"; - -const upcomming = [ - { - id: 12312, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-12", - endDate: "2024-01-14", - dueDate: 0, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 12321, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 4, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 12334, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 12343, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 12352, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 12315, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 123253, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2024-01-18", - endDate: "2024-01-22", - dueDate: 15, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 1231617, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2025-01-18", - endDate: "2025-01-22", - dueDate: 418, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, -]; - -const outdated = [ - { - id: 812367, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2023-12-17", - endDate: "2023-12-21", - dueDate: 0, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 867123, - title: "캐리비안베이, 로만바스 온천, 라마드 호텔, 에버랜드", - startDate: "2023-04-17", - endDate: "2023-04-20", - dueDate: 0, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, - { - id: 67123, - title: "강릉, 속초, 전주, 여수 여행", - startDate: "2022-12-29", - endDate: "2022-12-30", - dueDate: 0, - thumbnail: - "https://media.istockphoto.com/id/1356118511/photo/smart-city-and-abstract-dot-point-connect-with-gradient-line.jpg?s=612x612&w=0&k=20&c=GJldTyxDEt0GodKxGONHz9PrN9QcQQAGKONUM0vBvYc=", - }, -]; +import MySpaceList from './MySpaceList/MySpaceList'; +import Tab from './Tab/Tab'; function MySpaceBody() { - const [tab, setTab] = useState("upcomming"); - const [data, setData] = useState(null); - - useEffect(() => { - // const res = await() api호출부 - // const spaces = res.data.data; - // tmp mock data - const spaces = tab === "upcomming" ? upcomming : outdated; - setData(spaces); - }); + const [tab, setTab] = useState('upcoming'); return (
- +
); } diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss b/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss index 63f2013f..eb60f271 100644 --- a/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss +++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.module.scss @@ -1,4 +1,4 @@ -@use "@/sass" as *; +@use '@/sass' as *; .container { display: flex; @@ -24,9 +24,14 @@ width: 6.6rem; background-position: center; background-size: cover; + background-repeat: no-repeat; background-blend-mode: darken; border-radius: 16px 0px 0px 16px; + &.default { + background-size: 75% 75%; + } + & > span { z-index: 100; @include typography(button); @@ -34,7 +39,7 @@ } &::after { - content: ""; + content: ''; position: absolute; width: 100%; height: 100%; @@ -62,4 +67,7 @@ color: $neutral400; } } + + .observeTarget { + } } diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx index e47fb7d5..21ae522e 100644 --- a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx +++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx @@ -1,43 +1,93 @@ -import styles from "./MySpaceList.module.scss"; +import {useEffect} from 'react'; +import {useInView} from 'react-intersection-observer'; -import { setSpaceDate } from "@/utils/formatDate"; +import styles from './MySpaceList.module.scss'; -import { MySpaceListProps } from "@/types/user"; +import {useGetSpaces, useGetSpacesOut} from '@/hooks/Spaces/useSpaces'; + +import defaultCity from '@/assets/icons/city_default.svg'; +import {setSpaceDate} from '@/utils/formatDate'; + +import {Spaces} from '@/types/sidebar'; +import {MySpaceListProps} from '@/types/user'; + +function MySpaceList({tab}: MySpaceListProps) { + const {data: upcomingData} = useGetSpaces(true); + const {data: outdatedData, fetchNextPage} = useGetSpacesOut(); + const [ref, inView] = useInView({ + rootMargin: '-56px', + threshold: 0.8, + }); + + useEffect(() => { + // inView true + next pages exist + if (outdatedData) { + const {last} = outdatedData.pages.at(-1).data; + + if (inView && !last) { + fetchNextPage(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inView]); -function MySpaceList({ data, tab }: MySpaceListProps) { return (
    - {data?.map(({ id, title, startDate, endDate, dueDate, thumbnail }) => ( -
  • { - // navigate(`/trip/${id}`) - }} - > -
    ( +
  • { + // navigate(`/trip/${id}`) }} > - - {dueDate === 0 - ? tab === "upcomming" - ? "여행중" - : "" - : `D-${dueDate}`} - - - -
    -
    {title}
    - -
    - {setSpaceDate(startDate, endDate)} +
    + {dueDate <= 0 ? '여행중' : `D-${dueDate}`} +
    + +
    +
    {title}
    + +
    {setSpaceDate(startDate, endDate)}
    -
    -
  • - ))} + + ))} + + {tab === 'outdated' && + outdatedData?.pages.map((page) => + page.data.spaces.map(({id, title, startDate, endDate, thumbnail}: Spaces) => ( +
  • { + // navigate(`/trip/${id}`) + }} + > +
    + +
    +
    {title}
    + +
    {setSpaceDate(startDate, endDate)}
    +
    +
  • + )), + )} + + {tab === 'outdated' && ( +
    + Fetching... +
    + )}
); } diff --git a/src/components/MySpaceBody/Tab/Tab.tsx b/src/components/MySpaceBody/Tab/Tab.tsx index b67d124d..f9d3f8fa 100644 --- a/src/components/MySpaceBody/Tab/Tab.tsx +++ b/src/components/MySpaceBody/Tab/Tab.tsx @@ -1,36 +1,28 @@ -import styles from "./Tab.module.scss"; +import styles from './Tab.module.scss'; -import { TabProps } from "@/types/user"; +import {TabProps} from '@/types/user'; -function Tab({ tab, setTab }: TabProps) { +function Tab({tab, setTab}: TabProps) { return ( ); } diff --git a/src/components/User/Profile/Profile.tsx b/src/components/User/Profile/Profile.tsx index fa978fde..5d86025c 100644 --- a/src/components/User/Profile/Profile.tsx +++ b/src/components/User/Profile/Profile.tsx @@ -15,7 +15,7 @@ function Profile({data}: ProfileProps) {
-
- - -
- +
-
{rating}
-
{`${setMyReviewDate(visitedAt)} 방문`}
-
+ +
+ +
+
{rating}
+ +
{`${setMyReviewDate(visitedAt)} 방문`}
+
-

{content}

+

{content}

- {images && } - - ))} + {images && } + + )), + )} + {hasNextData && } + ; } + +export interface Reviews { + content: string; + id: number; + images: string[]; + place: Place; + rating: number; + visitedAt: string; +} + +export interface Place { + area: string; + category: string; + id: number; + thumbnail: string; + title: string; +} From 77e231e2846cb0bedaf284c2618ba3a34277cd52 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Fri, 26 Jan 2024 15:43:21 +0900 Subject: [PATCH 09/11] Hotfix: add type in useGetSpaces --- src/hooks/Spaces/useSpaces.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/Spaces/useSpaces.ts b/src/hooks/Spaces/useSpaces.ts index 008895b7..9d53c2a3 100644 --- a/src/hooks/Spaces/useSpaces.ts +++ b/src/hooks/Spaces/useSpaces.ts @@ -1,8 +1,10 @@ -import {useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query'; +import {useInfiniteQuery, useMutation, useQuery, useQueryClient, UseQueryResult} from '@tanstack/react-query'; import {spacesRequest} from '@/api/spaces'; -function useGetSpaces(enabled: boolean) { +import {GetUpcomingProp} from '@/types/sidebar'; + +function useGetSpaces(enabled: boolean): UseQueryResult { return useQuery({ queryKey: ['spaces'], queryFn: spacesRequest.getUpcoming, From 0cccbdcac292eab511b8d4827eac2070a893c5b9 Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Fri, 26 Jan 2024 21:13:21 +0900 Subject: [PATCH 10/11] Refactor: seperate infinite scroll logic to hooks --- .../MySpaceBody/MySpaceList/MySpaceList.tsx | 26 ++--------------- src/hooks/useInfiniteScroll.ts | 29 +++++++++++++++++++ src/pages/User/MyReview/MyReview.tsx | 26 +++-------------- 3 files changed, 35 insertions(+), 46 deletions(-) create mode 100644 src/hooks/useInfiniteScroll.ts diff --git a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx index 859c858f..6033d464 100644 --- a/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx +++ b/src/components/MySpaceBody/MySpaceList/MySpaceList.tsx @@ -1,9 +1,7 @@ -import {useEffect, useState} from 'react'; -import {useInView} from 'react-intersection-observer'; - import styles from './MySpaceList.module.scss'; import {useGetSpaces, useGetSpacesOut} from '@/hooks/Spaces/useSpaces'; +import {useInfiniteScroll} from '@/hooks/useInfiniteScroll'; import ObserveTarget from '@/components/Route/ObserveTarget/ObserveTarget'; @@ -15,27 +13,7 @@ import {MySpaceListProps} from '@/types/user'; function MySpaceList({tab}: MySpaceListProps) { const {data: upcomingData} = useGetSpaces(true); - const {data: outdatedData, fetchNextPage} = useGetSpacesOut(); - const [hasNextData, setHasNextData] = useState(true); - const {ref: inViewRef, inView} = useInView({ - rootMargin: '-56px', - threshold: 0.8, - }); - - useEffect(() => { - // inView true + next pages exist - if (outdatedData) { - const {last} = outdatedData.pages.at(-1).data; - - if (inView && !last) { - fetchNextPage(); - } - if (last) { - setHasNextData(false); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [inView]); + const [outdatedData, hasNextData, inViewRef] = useInfiniteScroll(useGetSpacesOut); return (
    diff --git a/src/hooks/useInfiniteScroll.ts b/src/hooks/useInfiniteScroll.ts new file mode 100644 index 00000000..c55bf884 --- /dev/null +++ b/src/hooks/useInfiniteScroll.ts @@ -0,0 +1,29 @@ +import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'; +import {useEffect, useState} from 'react'; +import {useInView} from 'react-intersection-observer'; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +export const useInfiniteScroll = (infiniteQueryFn: () => UseInfiniteQueryResult, Error>) => { + const {data, fetchNextPage} = infiniteQueryFn(); + const [hasNextData, setHasNextData] = useState(true); + const {ref: inViewRef, inView} = useInView({ + rootMargin: '-40px', + threshold: 0.8, + }); + + useEffect(() => { + if (data) { + const {last} = data.pages.at(-1).data; + + if (inView && !last) { + fetchNextPage(); + } + if (last) { + setHasNextData(false); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inView]); + + return [data, hasNextData, inViewRef] as const; +}; diff --git a/src/pages/User/MyReview/MyReview.tsx b/src/pages/User/MyReview/MyReview.tsx index e64804c5..701d7c26 100644 --- a/src/pages/User/MyReview/MyReview.tsx +++ b/src/pages/User/MyReview/MyReview.tsx @@ -1,9 +1,9 @@ import {useDisclosure} from '@chakra-ui/react'; -import {useEffect, useRef, useState} from 'react'; -import {useInView} from 'react-intersection-observer'; +import {useRef} from 'react'; import styles from './MyReview.module.scss'; +import {useInfiniteScroll} from '@/hooks/useInfiniteScroll'; import {useGetMyReview} from '@/hooks/User/useMyReview'; import Header from '@/components/Auth/Header/Header'; @@ -21,27 +21,9 @@ import {Reviews} from '@/types/myReview'; function MyReview() { const {isOpen: isBottomSlideOpen, onOpen: onBottomSlideOpen, onClose: onBottomSlideClose} = useDisclosure(); - const {data: reviews, fetchNextPage} = useGetMyReview(); - const [hasNextData, setHasNextData] = useState(true); - const {ref: inViewRef, inView} = useInView({ - rootMargin: '-40px', - threshold: 0.8, - }); - const clickedReviewId = useRef(); + const [reviews, hasNextData, inViewRef] = useInfiniteScroll(useGetMyReview); - useEffect(() => { - if (reviews) { - const {last} = reviews.pages.at(-1).data; - - if (inView && !last) { - fetchNextPage(); - } - if (last) { - setHasNextData(false); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [inView]); + const clickedReviewId = useRef(); return (
    From 20b3212bcb6133a03ca410da9629bf65cabef3ef Mon Sep 17 00:00:00 2001 From: NamgungJongMin Date: Fri, 26 Jan 2024 21:42:24 +0900 Subject: [PATCH 11/11] Hotfix: change root margin for observer --- src/hooks/useInfiniteScroll.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useInfiniteScroll.ts b/src/hooks/useInfiniteScroll.ts index c55bf884..95aa8565 100644 --- a/src/hooks/useInfiniteScroll.ts +++ b/src/hooks/useInfiniteScroll.ts @@ -7,8 +7,8 @@ export const useInfiniteScroll = (infiniteQueryFn: () => UseInfiniteQueryResult< const {data, fetchNextPage} = infiniteQueryFn(); const [hasNextData, setHasNextData] = useState(true); const {ref: inViewRef, inView} = useInView({ - rootMargin: '-40px', - threshold: 0.8, + rootMargin: '0px 0px -56px 0px', + threshold: 1, }); useEffect(() => {