diff --git a/next.config.js b/next.config.js index debf1cd9..e02aad02 100644 --- a/next.config.js +++ b/next.config.js @@ -3,7 +3,7 @@ const withPWA = require('next-pwa')({ }); const nextConfig = withPWA({ - reactStrictMode: true, + reactStrictMode: false, images: { remotePatterns: [ { diff --git a/package.json b/package.json index 98cb6587..a0409aeb 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-lottie-player": "^1.5.4", "react-tooltip": "^5.20.0", "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "svgstore": "^3.0.1", "svgstore-cli": "^2.0.1", "tailwind-merge": "^1.14.0", diff --git a/public/fonts/Pretendard-Bold.woff2 b/public/fonts/Pretendard-Bold.woff2 deleted file mode 100644 index 8975b802..00000000 Binary files a/public/fonts/Pretendard-Bold.woff2 and /dev/null differ diff --git a/public/fonts/Pretendard-Medium.woff2 b/public/fonts/Pretendard-Medium.woff2 deleted file mode 100644 index 153fd556..00000000 Binary files a/public/fonts/Pretendard-Medium.woff2 and /dev/null differ diff --git a/public/fonts/Pretendard-Regular.woff2 b/public/fonts/Pretendard-Regular.woff2 deleted file mode 100644 index ca8008ff..00000000 Binary files a/public/fonts/Pretendard-Regular.woff2 and /dev/null differ diff --git a/public/fonts/test.woff2 b/public/fonts/test.woff2 deleted file mode 100644 index c232f8f7..00000000 Binary files a/public/fonts/test.woff2 and /dev/null differ diff --git a/public/icons/bookmark_black_fill.svg b/public/icons/bookmark_black_fill.svg deleted file mode 100644 index cef62985..00000000 --- a/public/icons/bookmark_black_fill.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/public/icons/github.svg b/public/icons/github.svg deleted file mode 100644 index 40dddf54..00000000 --- a/public/icons/github.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/public/icons/instagram.svg b/public/icons/instagram.svg deleted file mode 100644 index 5d5ba7aa..00000000 --- a/public/icons/instagram.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/public/icons/profile_default.svg b/public/icons/profile_default.svg index 1bda6108..26b46c7f 100644 --- a/public/icons/profile_default.svg +++ b/public/icons/profile_default.svg @@ -1,15 +1,6 @@ - - - - - - - - - - - - - - + + + + + diff --git a/src/apis/apis.ts b/src/apis/apis.ts index 327c9445..48423826 100644 --- a/src/apis/apis.ts +++ b/src/apis/apis.ts @@ -1,12 +1,15 @@ import { FilterTagsResponse, PoseDetailResponse, + PoseFeedContents, PoseFeedResponse, PosePickResponse, PoseTalkResponse, RegisterResponse, } from '.'; +import privateApi from './config/privateApi'; import publicApi from './config/publicApi'; +import { KAKAO_REDIRECT_URI } from '@/constants/env'; export const getPosePick = (peopleCount: number) => publicApi.get(`/pose/pick/${peopleCount}`); @@ -22,7 +25,7 @@ export const getPoseFeed = async ( tags: string, pageNumber: number ) => - await publicApi.get(`/pose`, { + await privateApi.get(`/pose`, { params: { frameCount, pageNumber, @@ -34,4 +37,38 @@ export const getPoseFeed = async ( export const getFilterTag = () => publicApi.get('/pose/tags'); export const getRegister = (code: string) => - publicApi.get(`/users/login/oauth/kakao?code=${code}`); + publicApi.get( + `/users/login/oauth/kakao?code=${code}&redirectURI=${KAKAO_REDIRECT_URI}` + ); + +export const patchLogout = (accessToken: string, refreshToken: string) => + publicApi.patch('/users/logout', { + accessToken: `Bearer ${accessToken}`, + refreshToken: `Bearer ${refreshToken}`, + }); + +export const patchDeleteAccount = ( + accessToken: string, + refreshToken: string, + withdrawalReason: string +) => + publicApi.patch('/users/deleteAccount', { + accessToken: `Bearer ${accessToken}`, + refreshToken: `Bearer ${refreshToken}`, + withdrawalReason, + }); + +export const postBookmark = (poseId: number) => + privateApi.post(`/bookmark`, null, { + params: { poseId }, + }); + +export const deleteBookmark = (poseId: number) => + privateApi.delete(`/bookmark`, { + params: { poseId }, + }); + +export const getBookmarkFeed = (pageNumber: number) => + privateApi.get('/bookmark/feed', { + params: { pageNumber, pageSize: 10 }, + }); diff --git a/src/apis/config/privateApi.ts b/src/apis/config/privateApi.ts index 3e973d84..c2ef2411 100644 --- a/src/apis/config/privateApi.ts +++ b/src/apis/config/privateApi.ts @@ -1,30 +1,46 @@ -import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; +import axios from 'axios'; +import { CustomInstance } from './type'; import { BASE_API_URL } from '@/constants/env'; -import { getCookie } from '@/utils/cookieController'; -import type { CustomInstance } from './type'; +function getAccesstoken() { + if (typeof window !== 'undefined') { + const item = localStorage.getItem('accesstoken'); + return item; + } +} const privateApi: CustomInstance = axios.create({ baseURL: `${BASE_API_URL}/api`, withCredentials: true, }); +privateApi.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response.status === 415) { + alert('세션이 만료되었어요. 다시 로그인이 필요해요!'); + } else { + alert('오류가 발생했어요. 다시 시도해주세요'); + } + location.href = '/auth/logout'; + return Promise.reject(error); + } +); + privateApi.interceptors.request.use( - async (config: InternalAxiosRequestConfig) => { - try { - const accessToken = getCookie('accessToken'); + (config) => { + const accessToken = getAccesstoken(); + + if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; - return config; - } catch (error) { - return Promise.reject(error); } + + return config; }, - (error: AxiosError) => { - Promise.reject(error); + (error) => { + console.log('request : ' + error.response); } ); -privateApi.interceptors.response.use((response) => response.data); - export default privateApi; diff --git a/src/apis/config/type.ts b/src/apis/config/type.ts index 5e0c6614..a0a46dfc 100644 --- a/src/apis/config/type.ts +++ b/src/apis/config/type.ts @@ -19,4 +19,4 @@ export interface CustomInstance extends AxiosInstance { patch(...params: Parameters): Promise; } -export type ErrorStatus = 400 | 401 | 403 | 412 | 500; +export type ErrorStatus = 400 | 401 | 403 | 412 | 500 | 415; diff --git a/src/apis/queries.ts b/src/apis/queries.ts index c223f5a3..c516b431 100644 --- a/src/apis/queries.ts +++ b/src/apis/queries.ts @@ -7,16 +7,16 @@ import { import { FilterTagsResponse, + PoseFeedContents, PoseFeedResponse, PosePickResponse, PoseTalkResponse, - RegisterResponse, + getBookmarkFeed, getFilterTag, getPoseDetail, getPoseFeed, getPosePick, getPoseTalk, - getRegister, } from '.'; import { FilterState } from '@/hooks/useFilterState'; @@ -38,8 +38,8 @@ export const usePoseTalkQuery = (options?: UseQueryOptions) => export const usePoseFeedQuery = ( { peopleCount, frameCount, tags }: FilterState, options?: UseInfiniteQueryOptions -) => - useSuspenseInfiniteQuery( +) => { + return useSuspenseInfiniteQuery( ['poseFeed', peopleCount, frameCount, tags], ({ pageParam = 0 }) => getPoseFeed(peopleCount, frameCount, tags.join(','), pageParam), { @@ -52,9 +52,22 @@ export const usePoseFeedQuery = ( ...options, } ); +}; + +export const useBookmarkFeedQuery = ( + accesstoken: string, + options?: UseInfiniteQueryOptions +) => + useSuspenseInfiniteQuery( + ['bookmarkFeed'], + ({ pageParam = 0 }) => getBookmarkFeed(pageParam), + { + getNextPageParam: (lastPage) => { + return lastPage.last ? undefined : lastPage.number + 1; + }, + ...options, + } + ); export const useFilterTagQuery = (options?: UseQueryOptions) => useSuspenseQuery(['filterTag'], getFilterTag, { ...options }); - -export const useRegisterQuery = (code: string) => - useSuspenseQuery(['register'], () => getRegister(code), {}); diff --git a/src/apis/type.ts b/src/apis/type.ts index 279edb62..469db938 100644 --- a/src/apis/type.ts +++ b/src/apis/type.ts @@ -8,6 +8,7 @@ export interface PoseInfo { sourceUrl: string; tagAttributes: string; updatedAt: string; + bookmarkCheck: boolean; } // 포즈피드 @@ -16,7 +17,7 @@ interface PoseFeedContentsSort { sorted: boolean; unsorted: boolean; } -interface PoseFeedContents { +export interface PoseFeedContents { content: Array<{ poseInfo: PoseInfo }>; pageable: { sort: PoseFeedContentsSort; diff --git a/src/app/(Main)/feed/FeedContent.tsx b/src/app/(Main)/feed/FeedContent.tsx new file mode 100644 index 00000000..21463166 --- /dev/null +++ b/src/app/(Main)/feed/FeedContent.tsx @@ -0,0 +1,26 @@ +import Link from 'next/link'; + +import { usePoseFeedQuery } from '@/apis'; +import { PrimaryButton } from '@/components/Button'; +import EmptyCase from '@/components/Feed/EmptyCase'; +import FeedSection from '@/components/Feed/FeedSection'; +import { URL } from '@/constants/url'; +import useFilterState from '@/hooks/useFilterState'; + +export default function FeedContent() { + const { filterState } = useFilterState(); + const query = usePoseFeedQuery(filterState); + + return ( + + + + + + + + ); +} diff --git a/src/app/(Main)/feed/FeedSection.tsx b/src/app/(Main)/feed/FeedSection.tsx deleted file mode 100644 index 3ea69d30..00000000 --- a/src/app/(Main)/feed/FeedSection.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import { useInView } from 'react-intersection-observer'; - -import FilterSheet from './components/FilterSheet'; -import FilterTab from './components/FilterTab'; -import { usePoseFeedQuery } from '@/apis'; -import EmptyCase from '@/components/Feed/EmptyCase'; -import PhotoList from '@/components/Feed/PhotoList'; -import { MainHeader } from '@/components/Header'; -import { Spacing } from '@/components/Spacing'; -import { URL } from '@/constants/url'; -import useFilterState from '@/hooks/useFilterState'; - -export default function FeedSection() { - const { filterState } = useFilterState(); - const { data, fetchNextPage } = usePoseFeedQuery(filterState); - - const { ref, inView } = useInView(); - - useEffect(() => { - if (inView) fetchNextPage(); - }, [inView, fetchNextPage]); - - return ( - <> - - - - -
- {data.pages[0].filteredContents.empty ? ( - - ) : ( -
- {data.pages.map((page) => ( - - ))} -
- )} - {data.pages[0].recommendation && ( - <> -

이런 포즈는 어때요?

-
- {data.pages.map((page) => ( - - ))} -
- - )} -
-
- - - ); -} diff --git a/src/app/(Main)/feed/components/FilterSheet.tsx b/src/app/(Main)/feed/FilterSheet.tsx similarity index 96% rename from src/app/(Main)/feed/components/FilterSheet.tsx rename to src/app/(Main)/feed/FilterSheet.tsx index 641f8bc3..9721fbd7 100644 --- a/src/app/(Main)/feed/components/FilterSheet.tsx +++ b/src/app/(Main)/feed/FilterSheet.tsx @@ -1,10 +1,12 @@ +'use client'; + import { useEffect, useState } from 'react'; import { FilterTagsResponse, useFilterTagQuery } from '@/apis'; import { BottomDiv, PrimaryButton } from '@/components/Button'; import BottomSheet from '@/components/Modal/BottomSheet'; import { SelectionBasic, SelectionTagList } from '@/components/Selection'; -import { frameCountList, peopleCountList } from '@/constants/filterList'; +import { frameCountList, peopleCountList } from '@/constants/data'; import useBottomSheet from '@/hooks/useBottomSheet'; import useFilterState from '@/hooks/useFilterState'; diff --git a/src/app/(Main)/feed/page.tsx b/src/app/(Main)/feed/page.tsx index 23262912..d8316409 100644 --- a/src/app/(Main)/feed/page.tsx +++ b/src/app/(Main)/feed/page.tsx @@ -1,6 +1,9 @@ +'use client'; + import { QueryAsyncBoundary } from '@suspensive/react-query'; -import FeedSection from './FeedSection'; +import FeedContent from './FeedContent'; +import FilterSheet from './FilterSheet'; import { RejectedFallback } from '@/components/ErrorBoundary'; import { Loading } from '@/components/Loading'; import { PageAnimation } from '@/components/PageAnimation'; @@ -12,7 +15,8 @@ export default function Feed() { pendingFallback={} > - + + ); diff --git a/src/app/(Main)/layout.tsx b/src/app/(Main)/layout.tsx index fe76aa83..d56c68d0 100644 --- a/src/app/(Main)/layout.tsx +++ b/src/app/(Main)/layout.tsx @@ -1,10 +1,10 @@ -import { Spacing } from '@/components/Spacing'; +import MainHeader from '@/components/Header/MainHeader'; import { StrictPropsWithChildren } from '@/types'; export default function MainLayout({ children }: StrictPropsWithChildren) { return ( -
- +
+ {children}
); diff --git a/src/app/(Main)/mypose/bookmark/BookmarkEmpty.tsx b/src/app/(Main)/mypose/bookmark/BookmarkEmpty.tsx new file mode 100644 index 00000000..8b9b9bd1 --- /dev/null +++ b/src/app/(Main)/mypose/bookmark/BookmarkEmpty.tsx @@ -0,0 +1,17 @@ +import Link from 'next/link'; + +import { PrimaryButton } from '@/components/Button'; +import EmptyCase from '@/components/Feed/EmptyCase'; + +export default function BookmarkEmpty() { + return ( + + + + + + ); +} diff --git a/src/app/(Main)/mypose/bookmark/BookmarkSecion.tsx b/src/app/(Main)/mypose/bookmark/BookmarkSecion.tsx new file mode 100644 index 00000000..42b726ca --- /dev/null +++ b/src/app/(Main)/mypose/bookmark/BookmarkSecion.tsx @@ -0,0 +1,17 @@ +import BookmarkEmpty from './BookmarkEmpty'; +import { useBookmarkFeedQuery } from '@/apis'; +import FeedSection from '@/components/Feed/FeedSection'; + +interface BookmarkSecionI { + accesstoken: string; +} + +export default function BookmarkSecion({ accesstoken }: BookmarkSecionI) { + const query = useBookmarkFeedQuery(accesstoken); + + return ( + + + + ); +} diff --git a/src/app/(Main)/mypose/bookmark/page.tsx b/src/app/(Main)/mypose/bookmark/page.tsx new file mode 100644 index 00000000..356a1730 --- /dev/null +++ b/src/app/(Main)/mypose/bookmark/page.tsx @@ -0,0 +1,12 @@ +'use client'; + +import BookmarkEmpty from './BookmarkEmpty'; +import BookmarkSecion from './BookmarkSecion'; +import useUserState from '@/context/userState'; + +export default function BookmarkPage() { + const { token } = useUserState(); + + if (token) return ; + else return ; +} diff --git a/src/app/(Main)/mypose/layout.tsx b/src/app/(Main)/mypose/layout.tsx new file mode 100644 index 00000000..ee088620 --- /dev/null +++ b/src/app/(Main)/mypose/layout.tsx @@ -0,0 +1,16 @@ +import { QueryAsyncBoundary } from '@suspensive/react-query'; + +import { RejectedFallback } from '@/components/ErrorBoundary'; +import { Loading } from '@/components/Loading'; +import { StrictPropsWithChildren } from '@/types'; + +export default function Layout({ children }: StrictPropsWithChildren) { + return ( + } + > + {children} + + ); +} diff --git a/src/app/(Main)/mypose/page.tsx b/src/app/(Main)/mypose/page.tsx new file mode 100644 index 00000000..a8e96e59 --- /dev/null +++ b/src/app/(Main)/mypose/page.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { PrimaryButton } from '@/components/Button'; +import EmptyCase from '@/components/Feed/EmptyCase'; +import { PreparingPopup } from '@/components/Modal'; +import { useOverlay } from '@/components/Overlay/useOverlay'; + +export default function Page() { + const { open } = useOverlay(); + + return ( + + open(({ exit }) => )} + text="포즈 등록하기" + type="secondary" + /> + + ); +} diff --git a/src/app/(Main)/pick/components/PickSection.tsx b/src/app/(Main)/pick/components/PickSection.tsx deleted file mode 100644 index 07c93388..00000000 --- a/src/app/(Main)/pick/components/PickSection.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import clsx from 'clsx'; -import Image from 'next/image'; -import { useState } from 'react'; -import Lottie from 'react-lottie-player'; - -import lottiePick from '#/lotties/pick.json'; -import { usePosePickQuery } from '@/apis'; -import { BottomFixedDiv, PrimaryButton } from '@/components/Button'; -import { ImageModal } from '@/components/Modal'; -import { useOverlay } from '@/components/Overlay/useOverlay'; -import { SelectionBasic } from '@/components/Selection'; -import { Spacing } from '@/components/Spacing'; -import { peopleCountList } from '@/constants/filterList'; -import useLoading from '@/hooks/useLoading'; - -export default function PickSection() { - const [countState, setCountState] = useState(1); - const { open } = useOverlay(); - const { isLoading, startLoading } = useLoading({ loadingDelay: 1000 }); - const [image, setImage] = useState(''); - const { refetch } = usePosePickQuery(countState, { - onSuccess: (data) => { - setImage(data.poseInfo.imageKey); - }, - }); - - const handlePickClick = () => { - startLoading(); - refetch(); - }; - - return ( - <> -
- -
-
-
- {isLoading && } - - open(({ exit }) => ( - - )) - } - alt="이미지" - /> -
-
- - - - - - ); -} diff --git a/src/app/(Main)/pick/page.tsx b/src/app/(Main)/pick/page.tsx index d09ee22d..727476d1 100644 --- a/src/app/(Main)/pick/page.tsx +++ b/src/app/(Main)/pick/page.tsx @@ -1,11 +1,65 @@ -import PickSection from './components/PickSection'; -import { MainHeader } from '@/components/Header'; +'use client'; + +import { useEffect, useState } from 'react'; +import Lottie from 'react-lottie-player'; + +import lottiePick from '#/lotties/pick.json'; +import { usePosePickQuery } from '@/apis'; +import { BottomFixedDiv, PrimaryButton } from '@/components/Button'; +import PoseImage from '@/components/Modal/PoseImage'; +import { SelectionBasic } from '@/components/Selection'; +import { peopleCountList } from '@/constants/data'; + +export default function Page() { + const [countState, setCountState] = useState(1); + const [image, setImage] = useState('/images/image-frame.png'); + const [isRendered, setIsRendered] = useState(false); + const [isLottie, setIsLottie] = useState(true); + const { refetch } = usePosePickQuery(countState, { + onSuccess: (data) => { + if (data.poseInfo.imageKey === image) { + setIsRendered(true); + } + setImage(data.poseInfo.imageKey); + }, + }); + + useEffect(() => { + setTimeout(() => setIsLottie(false), 2200); + }, []); + + const handlePickClick = () => { + setIsRendered(false); + refetch(); + setIsLottie(true); + setTimeout(() => setIsLottie(false), 900); + }; -export default function Pick() { return ( <> - - +
+ +
+
+ {(isLottie || !isRendered) && ( +
+ +
+ )} +
+ setIsRendered(true)} /> +
+
+ + + ); } diff --git a/src/app/(Main)/talk/components/TitleSection.tsx b/src/app/(Main)/talk/components/TitleSection.tsx index 6f105d11..38db970c 100644 --- a/src/app/(Main)/talk/components/TitleSection.tsx +++ b/src/app/(Main)/talk/components/TitleSection.tsx @@ -49,8 +49,8 @@ export default function TitleSection() { }} >
-

제시어에 맞춰 포즈를 취해 보세요!

-

독특한 나만의 포즈가 완성된답니다.

+

{`일명 <포즈로 말해요> 챌린지!`}

+

제시어에 맞춰 포즈를 취해보세요.

diff --git a/src/app/(Main)/talk/page.tsx b/src/app/(Main)/talk/page.tsx index 6327afc5..07e74130 100644 --- a/src/app/(Main)/talk/page.tsx +++ b/src/app/(Main)/talk/page.tsx @@ -1,18 +1,12 @@ import TalkSection from './components/TalkSection'; import TitleSection from './components/TitleSection'; -import { MainHeader } from '@/components/Header'; import { PageAnimation } from '@/components/PageAnimation'; -import { Spacing } from '@/components/Spacing'; export default function Talk() { return ( - <> - - - - - - - + + + + ); } diff --git a/src/app/(Sub)/api/users/login/oauth/kakao/components/LoginSection.tsx b/src/app/(Sub)/api/users/login/oauth/kakao/components/LoginSection.tsx deleted file mode 100644 index 7a88a6e7..00000000 --- a/src/app/(Sub)/api/users/login/oauth/kakao/components/LoginSection.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; -import { useSetRecoilState } from 'recoil'; - -import { useRegisterQuery } from '@/apis'; -import { Loading } from '@/components/Loading'; -import { isLoginAtom, userAtom } from '@/context/userState'; -import { setCookie } from '@/utils/cookieController'; - -interface LoginSectionProps { - code: string; -} -export default function LoginSection({ code }: LoginSectionProps) { - const router = useRouter(); - const { data } = useRegisterQuery(code); - console.log(data); - - const setLoginState = useSetRecoilState(isLoginAtom); - const setUserState = useSetRecoilState(userAtom); - setLoginState(true); - setUserState(data); - - const { token } = data; - const { accessToken, refreshToken } = token; - setCookie('accessToken', accessToken); - setCookie('refreshToken', refreshToken); - router.replace('/menu'); - - return ; -} diff --git a/src/app/(Sub)/api/users/login/oauth/kakao/page.tsx b/src/app/(Sub)/api/users/login/oauth/kakao/page.tsx deleted file mode 100644 index a4d9c886..00000000 --- a/src/app/(Sub)/api/users/login/oauth/kakao/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { QueryAsyncBoundary } from '@suspensive/react-query'; - -import LoginSection from './components/LoginSection'; -import { getRegister } from '@/apis'; -import { RejectedFallback } from '@/components/ErrorBoundary'; -import { Loading } from '@/components/Loading'; -import { HydrationProvider } from '@/components/Provider/HydrationProvider'; - -interface PageProps { - searchParams: { - code: string; - }; -} - -export default function Page({ searchParams }: PageProps) { - const { code } = searchParams; - - return ( - }> - getRegister(code)}> - - - - ); -} diff --git a/src/app/(Sub)/bookmark/page.tsx b/src/app/(Sub)/bookmark/page.tsx deleted file mode 100644 index 96321991..00000000 --- a/src/app/(Sub)/bookmark/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import EmptyCase from '@/components/Feed/EmptyCase'; -import PhotoList from '@/components/Feed/PhotoList'; -import { BookmarkHeader } from '@/components/Header'; - -export default function BookmarkPage() { - return ( - <> - - - - - ); -} diff --git a/src/app/(Sub)/detail/[id]/components/DetailSection.tsx b/src/app/(Sub)/detail/[id]/DetailSection.tsx similarity index 73% rename from src/app/(Sub)/detail/[id]/components/DetailSection.tsx rename to src/app/(Sub)/detail/[id]/DetailSection.tsx index 07844a36..448038f6 100644 --- a/src/app/(Sub)/detail/[id]/components/DetailSection.tsx +++ b/src/app/(Sub)/detail/[id]/DetailSection.tsx @@ -1,14 +1,14 @@ 'use client'; -import Image from 'next/image'; import { usePathname } from 'next/navigation'; +import { useState } from 'react'; -import LinkShareModal from './LinkShareModal'; import Source from './Source'; import TagButton from './TagButton'; import { usePoseDetailQuery } from '@/apis'; import { BottomFixedDiv, PrimaryButton } from '@/components/Button'; -import ImageModal from '@/components/Modal/ImageModal.client'; +import { Popup } from '@/components/Modal'; +import PoseImage from '@/components/Modal/PoseImage'; import { useOverlay } from '@/components/Overlay/useOverlay'; import { BASE_SITE_URL } from '@/constants/env'; import useKakaoShare from '@/hooks/useKakaoShare'; @@ -24,29 +24,26 @@ export default function DetailSection({ poseId }: DetailSectionProps) { const { open } = useOverlay(); const pathname = usePathname(); + const [isRendered, setIsRendered] = useState(false); + if (!data) return null; const { imageKey, tagAttributes, source, sourceUrl, peopleCount, frameCount } = data.poseInfo; const handleShareLink = async () => { await copy(BASE_SITE_URL + pathname); - - open(({ exit }) => ); + open(({ exit }) => ( + + + + )); }; return ( -
+
{source && } -
-
- detailImage open(({ exit }) => )} - /> -
+
+ {isRendered ||
} + setIsRendered(true)} />
diff --git a/src/app/(Sub)/detail/[id]/components/Source.tsx b/src/app/(Sub)/detail/[id]/Source.tsx similarity index 100% rename from src/app/(Sub)/detail/[id]/components/Source.tsx rename to src/app/(Sub)/detail/[id]/Source.tsx diff --git a/src/app/(Sub)/detail/[id]/components/TagButton.tsx b/src/app/(Sub)/detail/[id]/TagButton.tsx similarity index 100% rename from src/app/(Sub)/detail/[id]/components/TagButton.tsx rename to src/app/(Sub)/detail/[id]/TagButton.tsx diff --git a/src/app/(Sub)/detail/[id]/components/LinkShareModal.tsx b/src/app/(Sub)/detail/[id]/components/LinkShareModal.tsx deleted file mode 100644 index a32daafb..00000000 --- a/src/app/(Sub)/detail/[id]/components/LinkShareModal.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { PrimaryButton } from '@/components/Button'; -import { Modal } from '@/components/Modal'; -import { Spacing } from '@/components/Spacing'; - -interface LinkShareModalProps { - onClose: () => void; -} -export default function LinkShareModal({ onClose }: LinkShareModalProps) { - return ( - -

링크가 복사되었습니다.

- - -
- ); -} diff --git a/src/app/(Sub)/detail/[id]/page.tsx b/src/app/(Sub)/detail/[id]/page.tsx index 8024bf7f..44136648 100644 --- a/src/app/(Sub)/detail/[id]/page.tsx +++ b/src/app/(Sub)/detail/[id]/page.tsx @@ -1,10 +1,10 @@ import { QueryAsyncBoundary } from '@suspensive/react-query'; import { Metadata, ResolvingMetadata } from 'next'; -import DetailSection from './components/DetailSection'; +import DetailSection from './DetailSection'; import { getPoseDetail } from '@/apis'; import { RejectedFallback } from '@/components/ErrorBoundary'; -import { DetailHeader } from '@/components/Header'; +import Header from '@/components/Header'; import { Loading } from '@/components/Loading'; import { PageAnimation } from '@/components/PageAnimation'; import { HydrationProvider } from '@/components/Provider/HydrationProvider'; @@ -35,7 +35,7 @@ export default function DetailPage({ params }: { params: { id: number } }) { return (
- +
} diff --git a/src/app/(Sub)/layout.tsx b/src/app/(Sub)/layout.tsx deleted file mode 100644 index dc6431c8..00000000 --- a/src/app/(Sub)/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Spacing } from '@/components/Spacing'; -import { StrictPropsWithChildren } from '@/types'; - -export default function SubLayout({ children }: StrictPropsWithChildren) { - return ( - <> - - {children} - - ); -} diff --git a/src/app/(Sub)/menu/MenuListSection.tsx b/src/app/(Sub)/menu/MenuListSection.tsx new file mode 100644 index 00000000..61b908ca --- /dev/null +++ b/src/app/(Sub)/menu/MenuListSection.tsx @@ -0,0 +1,47 @@ +'use client'; + +import Link from 'next/link'; + +import LogoutModal from '@/components/Login/LogoutModal'; +import { useOverlay } from '@/components/Overlay/useOverlay'; +import { menuList } from '@/constants/data'; +import useUserState from '@/context/userState'; + +export default function MenuListSection() { + const { isLogin } = useUserState(); + const { open } = useOverlay(); + + function handleLogout() { + open(({ exit }) => ); + } + + return ( +
+ {menuList.map((item, idx) => + item.text ? ( +
window.open(item.link)} + > + {item.text} +
+ ) : ( +
+ ) + )} + {isLogin && ( + <> +
+ 로그아웃 +
+ + + 탈퇴하기 + + + + )} +
+ ); +} diff --git a/src/app/(Sub)/menu/components/LoginModal.tsx b/src/app/(Sub)/menu/components/LoginModal.tsx deleted file mode 100644 index 422ea013..00000000 --- a/src/app/(Sub)/menu/components/LoginModal.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { AppleButton, KakaoButton } from './LoginButton'; -import { Modal } from '@/components/Modal'; -import { Spacing } from '@/components/Spacing'; -import { BASE_SITE_URL, KAKAO_SERVER_KEY } from '@/constants/env'; - -interface LoginModalProps { - onClose: () => void; -} - -export default function LoginModal({ onClose }: LoginModalProps) { - const link = `https://kauth.kakao.com/oauth/authorize?client_id=${KAKAO_SERVER_KEY}&redirect_uri=${BASE_SITE_URL}/api/users/login/oauth/kakao&response_type=code`; - - const handleLogin = () => { - window.location.href = link; - }; - - return ( - -
-

간편 로그인

- -

- 로그인하면 북마크도 쓸 수 있어요! -
- 간편 로그인으로 3초만에 가입해요. -

-
-
- - alert('앱스토어 준비중입니다.')} /> -
-
- ); -} diff --git a/src/app/(Sub)/menu/components/LoginSection.tsx b/src/app/(Sub)/menu/components/LoginSection.tsx deleted file mode 100644 index d2b1bf23..00000000 --- a/src/app/(Sub)/menu/components/LoginSection.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import { useRecoilValue } from 'recoil'; - -import LoginModal from './LoginModal'; -import { Icon } from '@/components/Button/Icon'; -import { PreparingModal } from '@/components/Modal'; -import { useOverlay } from '@/components/Overlay/useOverlay'; -import { Spacing } from '@/components/Spacing'; -import { ICON } from '@/constants/icon'; -import { isLoginAtom, userAtom } from '@/context/userState'; - -function DefaultProfile() { - return ( -
- -
- ); -} - -export default function LoginSection() { - const { open, exit } = useOverlay(); - - const isLogin = useRecoilValue(isLoginAtom); - // const userData = useRecoilValue(userAtom); - // console.log('🚀 ~ LoginSection ~ userData:', userData); - - return ( -
- {isLogin ? ( -
- - - (개발중) 로그인 완료 -
- ) : ( - - )} -
- ); -} diff --git a/src/app/(Sub)/menu/components/MakerSection.tsx b/src/app/(Sub)/menu/components/MakerSection.tsx deleted file mode 100644 index 23d470fe..00000000 --- a/src/app/(Sub)/menu/components/MakerSection.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client'; - -import { Icon } from '@/components/Button'; -import { BottomFixedDiv } from '@/components/Button/BottomFixedDiv'; -import { Spacing } from '@/components/Spacing'; - -export default function MakerSection() { - return ( - -
-
- - -
- -
-

© POSEPICKER

-
- -
-
- ); -} diff --git a/src/app/(Sub)/menu/components/MenuListSection.tsx b/src/app/(Sub)/menu/components/MenuListSection.tsx deleted file mode 100644 index da1d6cf9..00000000 --- a/src/app/(Sub)/menu/components/MenuListSection.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { URL } from '@/constants/url'; - -const MenuList = [ - { text: '공지사항', link: URL.menu.notice }, - { text: '자주 묻는 질문', link: URL.menu.faq }, - { text: '문의하기', link: URL.inquiry }, - { text: '', link: '' }, - { text: '이용약관', link: URL.menu.term }, - { text: '개인정보 처리방침', link: URL.menu.privacy }, -] as const; - -export default function MenuListSection() { - return ( -
- {MenuList.map((item, idx) => - item.text ? ( -
window.open(item.link)}> - {item.text} -
- ) : ( -
- ) - )} -
- ); -} diff --git a/src/app/(Sub)/menu/page.tsx b/src/app/(Sub)/menu/page.tsx index 49b875b7..0ce11ff0 100644 --- a/src/app/(Sub)/menu/page.tsx +++ b/src/app/(Sub)/menu/page.tsx @@ -1,15 +1,15 @@ -import LoginSection from './components/LoginSection'; -import MakerSection from './components/MakerSection'; -import MenuListSection from './components/MenuListSection'; -import { MenuHeader } from '@/components/Header'; +'use client'; + +import MenuListSection from './MenuListSection'; +import Header from '@/components/Header'; +import LoginSection from '@/components/Login/LoginSection'; export default function MenuPage() { return (
- +
-
); } diff --git a/src/app/(Sub)/menu/withdraw/page.tsx b/src/app/(Sub)/menu/withdraw/page.tsx new file mode 100644 index 00000000..bd46e348 --- /dev/null +++ b/src/app/(Sub)/menu/withdraw/page.tsx @@ -0,0 +1,97 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; + +import { BottomFixedDiv, PrimaryButton } from '@/components/Button'; +import Header from '@/components/Header'; +import { Popup } from '@/components/Modal'; +import { useOverlay } from '@/components/Overlay/useOverlay'; +import { withdrawReasonList } from '@/constants/data'; + +const RadioInput = ({ checked }: { checked: boolean }) => { + return checked ? ( + + ) : ( + + ); +}; + +export default function Page() { + const [withdrawalReason, setWithdrawalReason] = useState(); + const [etc, setEtc] = useState(false); + const { open } = useOverlay(); + const router = useRouter(); + + function handleWithdraw() { + open(({ exit }) => ( + + <> + + router.replace(`/auth/withdraw?reason=${withdrawalReason}`)} + type="warning" + /> + + + )); + } + + return ( + <> +
+
+

+ 떠나시는 이유를 +
+ 알려줄 수 있나요? +

+
+ {withdrawReasonList.map((item, idx) => ( +
{ + setWithdrawalReason(item); + setEtc(false); + }} + > + + +
+ ))} +
{ + setEtc(true); + setWithdrawalReason(undefined); + }} + > + + +
+
+ {etc && ( +