From 4ee60fadac430672e25122a74499f230e90d3cf0 Mon Sep 17 00:00:00 2001 From: aaminha Date: Mon, 3 Jun 2024 10:08:30 +0900 Subject: [PATCH 01/20] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/client.ts | 28 +++---- .../getAccessToken.ts} | 9 ++- src/services/AuthService.ts | 43 ----------- src/services/TokenService.ts | 75 +++++++++++++++++++ src/services/UserService.ts | 7 +- 5 files changed, 97 insertions(+), 65 deletions(-) rename src/apis/{tokenAPI.ts => tokenAPI/getAccessToken.ts} (50%) delete mode 100644 src/services/AuthService.ts create mode 100644 src/services/TokenService.ts diff --git a/src/apis/client.ts b/src/apis/client.ts index 7088a0f..3305cfe 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -1,7 +1,6 @@ import axios, { AxiosInstance } from 'axios'; -import { authService } from '@/services/AuthService'; -import { userService } from '@/services/UserService'; +import { tokenService } from '@/services/TokenService'; export const noAuthClient: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, @@ -14,30 +13,23 @@ export const authClient: AxiosInstance = axios.create({ }); authClient.interceptors.request.use((config) => { - if (userService.getUserNickname() === '') { - const registerToken = authService.getRegisterToken(); - if (registerToken !== null && registerToken !== undefined) { - config.headers['Authorization'] = `Bearer ${registerToken}`; - } else { - window.alert('로그인이 필요합니다.'); - authService.onLogout(); - } - } - + config.headers['Authorization'] = tokenService.getHeader(); return config; }); authClient.interceptors.response.use( - (response) => { - return response; - }, + (response) => response, async (error) => { const originalRequest = error.config; if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; - const access_token = await authService.getRefreshToken(); - authService.setAuthToken(access_token); - return authClient(originalRequest); + try { + await tokenService.updateAccessToken(); + originalRequest.headers['Authorization'] = tokenService.getHeader(); + return axios(originalRequest); + } catch (e) { + return Promise.reject(e); + } } return Promise.reject(error); } diff --git a/src/apis/tokenAPI.ts b/src/apis/tokenAPI/getAccessToken.ts similarity index 50% rename from src/apis/tokenAPI.ts rename to src/apis/tokenAPI/getAccessToken.ts index 6058433..4b1eae9 100644 --- a/src/apis/tokenAPI.ts +++ b/src/apis/tokenAPI/getAccessToken.ts @@ -1,8 +1,11 @@ import { noAuthClient } from '@/apis/client'; -export const tokenAPI = { - refresh: async () => { +export const getAccessToken = async () => { + try { const response = await noAuthClient.get('/api/reissue/access-token'); return response.data; - }, + } catch (error) { + console.error('Access Token 갱신 실패:', error); + throw error; + } }; diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts deleted file mode 100644 index 19ad254..0000000 --- a/src/services/AuthService.ts +++ /dev/null @@ -1,43 +0,0 @@ -import axios from 'axios'; - -import { authClient } from '@/apis/client'; -import { tokenAPI } from '@/apis/tokenAPI'; -import { userService } from '@/services/UserService'; - -class AuthService { - getRefreshToken = async () => { - try { - const response = await tokenAPI.refresh(); - return response.payload.access_token; - } catch (error) { - if (axios.isAxiosError(error) && error.response && error.response.status === 401) { - window.alert('로그인이 필요합니다.'); - this.onLogout(); - } - } - }; - - setAuthToken(token: string) { - authClient.defaults.headers.common['Authorization'] = `Bearer ${token}`; - } - - setRegisterToken(registerToken: string) { - window.sessionStorage.setItem('registerToken', registerToken); - } - - getRegisterToken = () => { - return window.sessionStorage.getItem('registerToken'); - }; - - deleteRegisterToken = () => { - window.sessionStorage.removeItem('registerToken'); - }; - - onLogout = () => { - delete authClient.defaults.headers.common['Authorization']; - userService.removeUser(); - window.location.href = '/auth'; - }; -} - -export const authService = new AuthService(); diff --git a/src/services/TokenService.ts b/src/services/TokenService.ts new file mode 100644 index 0000000..be74a88 --- /dev/null +++ b/src/services/TokenService.ts @@ -0,0 +1,75 @@ +import axios from 'axios'; +import { Cookies } from 'react-cookie'; + +import { getAccessToken } from '@/apis/tokenAPI/getAccessToken'; +import { userService } from '@/services/UserService'; + +class TokenService { + cookies = new Cookies(); + + async updateAccessToken() { + try { + const response = await getAccessToken(); + this.setAccessToken(response.payload.access_token); + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + if (error.response.status === 401) { + window.alert('로그인이 필요합니다.'); + this.onLogout(); + } else { + window.alert('알 수 없는 오류가 발생했습니다.'); + } + } + } + } + + setAccessToken(token: string) { + // TODO: 만료일자 수정 + const expires = new Date(Date.now() + 60 * 60 * 24 * 7 * 1000); + this.cookies.set('selpiece-access-token', token, { + path: '/', + expires, + }); + } + + setRegisterToken(token: string) { + // TODO: 만료일자 수정 + const expires = new Date(Date.now() + 60 * 60 * 24 * 7 * 1000); + this.cookies.set('selpiece-register-token', token, { + path: '/', + expires, + }); + } + + getAccessToken() { + return this.cookies.get('selpiece-access-token'); + } + + getRegisterToken() { + return this.cookies.get('selpiece-register-token'); + } + + getHeader() { + if (userService.getUserState() === 'PRE_MEMBER') { + if (!this.getRegisterToken()) { + window.alert('로그인이 필요합니다.'); + this.onLogout(); + } + + return `Bearer ${this.getRegisterToken()}`; + } + + if (userService.getUserState() === 'MEMBER') { + return `Bearer ${this.getAccessToken()}`; + } + } + + onLogout = () => { + this.cookies.remove('selpiece-access-token'); + this.cookies.remove('selpiece-register-token'); + userService.removeUser(); + window.location.href = '/auth'; + }; +} + +export const tokenService = new TokenService(); diff --git a/src/services/UserService.ts b/src/services/UserService.ts index a022610..9f03562 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -29,7 +29,12 @@ class UserService { const user = this.getUser(); if (!user) { return 'NON_MEMBER'; - } else if (user.nickname === '') { + } + return this.determineUserState(user); + } + + determineUserState(user: User) { + if (user.nickname === '') { return 'PRE_MEMBER'; } else { return 'MEMBER'; From 2144f4454e75d43edd273fd55904daab3bfa17ce Mon Sep 17 00:00:00 2001 From: aaminha Date: Mon, 3 Jun 2024 21:09:35 +0900 Subject: [PATCH 02/20] =?UTF-8?q?design:=2080%=20=EC=A0=81=EC=9A=A9=20(#77?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DefineStartPage/DefineDesktopView.tsx | 2 +- src/components/DefineStartPage/DefineMobileView.tsx | 2 +- src/components/DefineTest/DefineTestView.tsx | 2 +- src/components/DesignStartPage/DesignStartView.tsx | 2 +- src/components/DesignTest/DesignTestView.tsx | 2 +- src/components/ExperienceRecommendPage/AmountModal.tsx | 6 +++--- src/components/LoginPage/LoginDesktopPage.tsx | 2 +- src/components/LoginPage/LoginMobilePage.tsx | 2 +- src/components/common/Modal/BrandCardModal.tsx | 4 ++-- src/components/common/Modal/DefaultModal.tsx | 4 ++-- src/pages/DefineResultPage.tsx | 4 ++-- src/pages/DesignResultPage.tsx | 2 +- src/pages/DiscoverResultPage.tsx | 2 +- src/pages/DiscoverStartPage.tsx | 2 +- src/pages/DiscoverTestPage.tsx | 2 +- src/pages/LoadingPage.tsx | 2 +- src/pages/Mypage.tsx | 2 +- src/pages/OnboardingPage.tsx | 2 +- src/styles/global/GlobalStyle.ts | 1 + src/styles/global/zoom.css | 5 +++++ 20 files changed, 29 insertions(+), 23 deletions(-) create mode 100644 src/styles/global/zoom.css diff --git a/src/components/DefineStartPage/DefineDesktopView.tsx b/src/components/DefineStartPage/DefineDesktopView.tsx index c7f502f..54e875e 100644 --- a/src/components/DefineStartPage/DefineDesktopView.tsx +++ b/src/components/DefineStartPage/DefineDesktopView.tsx @@ -111,7 +111,7 @@ const Styled2Container = styled.div` `; export const ViewContainer = styled.div` - height: 100vh; + height: var(--full-height); background-image: url(${backgroundImg}); background-size: cover; diff --git a/src/components/DefineStartPage/DefineMobileView.tsx b/src/components/DefineStartPage/DefineMobileView.tsx index be9634a..2b0877a 100644 --- a/src/components/DefineStartPage/DefineMobileView.tsx +++ b/src/components/DefineStartPage/DefineMobileView.tsx @@ -129,7 +129,7 @@ const ViewContainer = styled.div` justify-content: center; align-items: center; width: 100%; - height: 100vh; + height: var(--full-height); min-height: 700px; background-image: url(${backgroundImg}); background-size: cover; diff --git a/src/components/DefineTest/DefineTestView.tsx b/src/components/DefineTest/DefineTestView.tsx index cd820c8..901fcbb 100644 --- a/src/components/DefineTest/DefineTestView.tsx +++ b/src/components/DefineTest/DefineTestView.tsx @@ -12,7 +12,7 @@ const StyledContainer = styled.section` flex-direction: column; background: ${({ theme }) => `${theme.color.primary50}`}; - min-height: 100vh; + min-height: var(--full-height); padding: 118px 0 48px 0; diff --git a/src/components/DesignStartPage/DesignStartView.tsx b/src/components/DesignStartPage/DesignStartView.tsx index f508d93..020c18a 100644 --- a/src/components/DesignStartPage/DesignStartView.tsx +++ b/src/components/DesignStartPage/DesignStartView.tsx @@ -87,7 +87,7 @@ const Styled1Container = styled.div` `; export const ViewContainer = styled.div` - height: 100vh; + height: var(--full-height); background-image: url(${backgroundImg}); background-size: cover; diff --git a/src/components/DesignTest/DesignTestView.tsx b/src/components/DesignTest/DesignTestView.tsx index 5c90007..65920ce 100644 --- a/src/components/DesignTest/DesignTestView.tsx +++ b/src/components/DesignTest/DesignTestView.tsx @@ -20,7 +20,7 @@ const StyledContainer = styled.section` flex-direction: column; background: ${({ theme }) => `${theme.color.primary50}`}; - min-height: 100vh; + min-height: var(--full-height); padding: 118px 0 48px 0; diff --git a/src/components/ExperienceRecommendPage/AmountModal.tsx b/src/components/ExperienceRecommendPage/AmountModal.tsx index 62d4711..43003eb 100644 --- a/src/components/ExperienceRecommendPage/AmountModal.tsx +++ b/src/components/ExperienceRecommendPage/AmountModal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; import styled from 'styled-components'; @@ -62,8 +62,8 @@ const ModalOverlay = styled.div<{ isOpen: boolean }>` left: 0; z-index: 100; - width: 100vw; - height: 100vh; + width: var(--full-width); + height: var(--full-height); //padding: 24px; background: ${({ theme }) => theme.color.bgModal}; box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25); diff --git a/src/components/LoginPage/LoginDesktopPage.tsx b/src/components/LoginPage/LoginDesktopPage.tsx index 34e2287..5be5336 100644 --- a/src/components/LoginPage/LoginDesktopPage.tsx +++ b/src/components/LoginPage/LoginDesktopPage.tsx @@ -34,7 +34,7 @@ export const ViewContainer = styled.div` display: flex; justify-content: center; align-items: center; - height: 100vh; + height: var(--full-height); background-image: url(${backgroundImg}); background-size: cover; `; diff --git a/src/components/LoginPage/LoginMobilePage.tsx b/src/components/LoginPage/LoginMobilePage.tsx index ecf93d6..1c8c3ab 100644 --- a/src/components/LoginPage/LoginMobilePage.tsx +++ b/src/components/LoginPage/LoginMobilePage.tsx @@ -37,7 +37,7 @@ const ViewContainer = styled.div` justify-content: center; align-items: center; width: 100%; - height: 100vh; + height: var(--full-height); background-image: url(${backgroundImg}); background-size: cover; background-position: calc(50% + 70px) center; diff --git a/src/components/common/Modal/BrandCardModal.tsx b/src/components/common/Modal/BrandCardModal.tsx index 83d628c..493f775 100644 --- a/src/components/common/Modal/BrandCardModal.tsx +++ b/src/components/common/Modal/BrandCardModal.tsx @@ -127,8 +127,8 @@ const StyledContainer = styled.div<{ isOpen: boolean }>` left: 0; z-index: 100; - width: 100vw; - height: 100vh; + width: var(--full-width); + height: var(--full-height); //padding: 24px; background: ${({ theme }) => theme.color.bgModal}; box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.25); diff --git a/src/components/common/Modal/DefaultModal.tsx b/src/components/common/Modal/DefaultModal.tsx index a69ba2c..495447a 100644 --- a/src/components/common/Modal/DefaultModal.tsx +++ b/src/components/common/Modal/DefaultModal.tsx @@ -37,8 +37,8 @@ const StyledOverlay = styled.div` left: 0; z-index: 100; - width: 100vw; - height: 100vh; + width: var(--full-width); + height: var(--full-height); display: flex; align-items: center; diff --git a/src/pages/DefineResultPage.tsx b/src/pages/DefineResultPage.tsx index 403f8bc..b72e132 100644 --- a/src/pages/DefineResultPage.tsx +++ b/src/pages/DefineResultPage.tsx @@ -34,13 +34,13 @@ export const DefineResultPage = () => { }; const StyledLoading = styled.div` - height: 100vh; + height: var(--full-height); padding-top: 90px; padding-left: 20px; `; const StyledContainer = styled.section` - min-height: 100vh; + min-height: var(--full-height); padding: 76px 64px 40px 64px; background: ${({ theme }) => diff --git a/src/pages/DesignResultPage.tsx b/src/pages/DesignResultPage.tsx index c18151c..b6429b4 100644 --- a/src/pages/DesignResultPage.tsx +++ b/src/pages/DesignResultPage.tsx @@ -20,7 +20,7 @@ export const DesignResultPage = () => { if (persona) return ( - + { const StyledContainer = styled.div` padding-top: 76px; - min-height: 100vh; + min-height: var(--full-height); display: flex; justify-content: center; diff --git a/src/pages/DiscoverStartPage.tsx b/src/pages/DiscoverStartPage.tsx index 46c630a..85a2d8a 100644 --- a/src/pages/DiscoverStartPage.tsx +++ b/src/pages/DiscoverStartPage.tsx @@ -86,7 +86,7 @@ const Styled1Container = styled.div` `; export const ViewContainer = styled.div` - height: 100vh; + height: var(--full-height); background-image: url(${backgroundImg}); background-size: cover; diff --git a/src/pages/DiscoverTestPage.tsx b/src/pages/DiscoverTestPage.tsx index a7081ba..4e4c487 100644 --- a/src/pages/DiscoverTestPage.tsx +++ b/src/pages/DiscoverTestPage.tsx @@ -82,7 +82,7 @@ export const DiscoverTestPage = () => { }; const StyledContainer = styled.div` - height: 100vh; + height: var(--full-height); `; const StyledInnerContainer = styled.div` diff --git a/src/pages/LoadingPage.tsx b/src/pages/LoadingPage.tsx index bdd81cf..7dd9080 100644 --- a/src/pages/LoadingPage.tsx +++ b/src/pages/LoadingPage.tsx @@ -58,7 +58,7 @@ const StyledContainer = styled.div` justify-content: center; width: 100%; - height: 100vh; + height: var(--full-height); padding: 20px; padding-top: 96px; diff --git a/src/pages/Mypage.tsx b/src/pages/Mypage.tsx index 0a854af..3f934ea 100644 --- a/src/pages/Mypage.tsx +++ b/src/pages/Mypage.tsx @@ -25,7 +25,7 @@ export const MyPage1 = () => { }; return ( -
+
{renderContent()}
diff --git a/src/pages/OnboardingPage.tsx b/src/pages/OnboardingPage.tsx index c461250..040524c 100644 --- a/src/pages/OnboardingPage.tsx +++ b/src/pages/OnboardingPage.tsx @@ -54,7 +54,7 @@ export const OnboardingPage = () => { }; const StyledContainer = styled.div` - min-height: 100vh; + min-height: var(--full-height); background-image: url(${BackgroundImage}); background-size: cover; display: flex; diff --git a/src/styles/global/GlobalStyle.ts b/src/styles/global/GlobalStyle.ts index 15f2543..c0c7895 100644 --- a/src/styles/global/GlobalStyle.ts +++ b/src/styles/global/GlobalStyle.ts @@ -1,5 +1,6 @@ import styled, { createGlobalStyle, keyframes } from 'styled-components'; import reset from 'styled-reset'; +import './zoom.css'; export const GlobalStyle = createGlobalStyle` ${reset} diff --git a/src/styles/global/zoom.css b/src/styles/global/zoom.css new file mode 100644 index 0000000..bff8265 --- /dev/null +++ b/src/styles/global/zoom.css @@ -0,0 +1,5 @@ +:root { + zoom: 0.8; + --full-height: 125vh; + --full-width: 125vw; +} \ No newline at end of file From b193bb5b0052dde21d510fc739cdfc4e3c23dad8 Mon Sep 17 00:00:00 2001 From: aaminha Date: Tue, 4 Jun 2024 11:38:56 +0900 Subject: [PATCH 03/20] =?UTF-8?q?fix:=20=ED=91=B8=ED=84=B0=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=ED=95=98=EB=8B=A8=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Footer/index.tsx | 13 ++++++++++--- src/components/common/Layout/MainLayout.tsx | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/common/Footer/index.tsx b/src/components/common/Footer/index.tsx index 1126ed7..c488aab 100644 --- a/src/components/common/Footer/index.tsx +++ b/src/components/common/Footer/index.tsx @@ -1,4 +1,3 @@ -import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { ReactComponent as Logo } from '@/assets/logos/mainLogo.svg'; @@ -24,9 +23,15 @@ export const Footer = () => { @@ -54,6 +59,8 @@ const StyledContainer = styled.footer` .logo { width: 135px; } + + width: 100%; `; const StyledMenuContainer = styled.div` diff --git a/src/components/common/Layout/MainLayout.tsx b/src/components/common/Layout/MainLayout.tsx index 4198f9b..a7ec5cb 100644 --- a/src/components/common/Layout/MainLayout.tsx +++ b/src/components/common/Layout/MainLayout.tsx @@ -8,10 +8,10 @@ const FOOTER_VISIBLE_PATHS = ['/', '/understand', '/program']; export const MainLayout = () => { const location = useLocation(); return ( - <> +
{FOOTER_VISIBLE_PATHS.includes(location.pathname) &&
} - +
); }; From 16ec99cbf996ae336a5faac355d40ae1a0785013 Mon Sep 17 00:00:00 2001 From: aaminha Date: Tue, 4 Jun 2024 11:41:51 +0900 Subject: [PATCH 04/20] =?UTF-8?q?fix:=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=97=B0=EA=B2=B0=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/Navigation/SideNavigation.tsx | 112 ++++++++++-------- .../common/Navigation/TopNavigation.tsx | 94 ++++++++------- 2 files changed, 111 insertions(+), 95 deletions(-) diff --git a/src/components/common/Navigation/SideNavigation.tsx b/src/components/common/Navigation/SideNavigation.tsx index c22c3ca..90b2d5f 100644 --- a/src/components/common/Navigation/SideNavigation.tsx +++ b/src/components/common/Navigation/SideNavigation.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { userAPI } from '@/apis/userAPI'; @@ -8,7 +8,7 @@ import { ReactComponent as ArrowIcon } from '@/assets/icons/arrowRight.svg'; import { ReactComponent as CloseIcon } from '@/assets/icons/close.svg'; import { PlainButton } from '@/components/common/Button/PlainButton'; import { NAVIGATION_MENU } from '@/constants/navigation'; -import { authService } from '@/services/AuthService'; +import { tokenService } from '@/services/TokenService'; interface SideNavigationProps { isLoggedIn: boolean; @@ -35,7 +35,7 @@ export const SideNavigation = ({ isLoggedIn, setOpen }: SideNavigationProps) => try { await userAPI.logout(); window.alert('로그아웃 되었습니다.'); - authService.onLogout(); + tokenService.onLogout(); } catch (error) { console.error(error); } @@ -44,46 +44,57 @@ export const SideNavigation = ({ isLoggedIn, setOpen }: SideNavigationProps) => return ( - - - {NAVIGATION_MENU.map((item) => ( - setOpen(false)} - > - {item.menu} - - - ))} - - - {/* TODO: 각 페이지 path로 이동하도록 click 핸들러 추가 */} - {isLoggedIn ? ( - <> - - 마이페이지 +
+ + + {NAVIGATION_MENU.map((item) => ( + + ))} + + + {isLoggedIn ? ( + <> + { + navigate('/mypage'); + setOpen(false); + }} + > + 마이페이지 + + + 로그아웃 + + + ) : ( + { + navigate('/auth'); + setOpen(false); + }} + > + 로그인 - - 로그아웃 - - - ) : ( - { - navigate('/auth'); - setOpen(false); - }} - > - 로그인 - - )} - + )} + +
); @@ -94,24 +105,27 @@ const StyledContainer = styled.div` justify-content: flex-end; width: 100%; - height: calc(var(--vh, 1vh) * 100); + height: 100%; position: fixed; - top: 0; - right: 0; + z-index: 10; background: ${({ theme }) => theme.color.bgModal}; `; const StyledContent = styled.nav` - display: flex; - flex-direction: column; - justify-content: space-between; - width: 320px; height: 100%; background: ${({ theme }) => theme.color.white}; + + .inner-container { + height: calc(var(--vh, 1vh) * 125); + + display: flex; + flex-direction: column; + justify-content: space-between; + } `; const StyledMenuButtonList = styled.div` diff --git a/src/components/common/Navigation/TopNavigation.tsx b/src/components/common/Navigation/TopNavigation.tsx index 4b2d49a..592be97 100644 --- a/src/components/common/Navigation/TopNavigation.tsx +++ b/src/components/common/Navigation/TopNavigation.tsx @@ -20,53 +20,54 @@ export const TopNavigation = () => { const location = useLocation(); return ( - - - - - {!MENU_VISIBLE_PATHS.includes(location.pathname) && ( - - {NAVIGATION_MENU.map((item) => ( - { - navigate(item.path); - }} - > - {item.menu} - - ))} - {loggedIn ? ( - - - - ) : ( - { - navigate('auth'); - }} - > - 로그인 - - )} - - )} - {!MENU_VISIBLE_PATHS.includes(location.pathname) && ( - { - //TODO: 마이페이지로 이동하도록 수정 - setShowSideNav((prev) => !prev); - }} - > - - - )} + <> + + + + + {!MENU_VISIBLE_PATHS.includes(location.pathname) && ( + + {NAVIGATION_MENU.map((item) => ( + { + navigate(item.path); + }} + > + {item.menu} + + ))} + {loggedIn ? ( + navigate('/mypage')}> + + + ) : ( + { + navigate('auth'); + }} + > + 로그인 + + )} + + )} + {!MENU_VISIBLE_PATHS.includes(location.pathname) && ( + { + setShowSideNav((prev) => !prev); + }} + > + + + )} + {showSideNav && } - + ); }; @@ -81,6 +82,7 @@ const StyledContainer = styled.header` z-index: 10; width: 100%; + padding: 20px 42px; border-bottom: 1px solid ${({ theme }) => theme.color.primary100}; From 032e4d6aa5aeeb60d6149bfc028b924c70cfc17f Mon Sep 17 00:00:00 2001 From: aaminha Date: Wed, 5 Jun 2024 00:38:13 +0900 Subject: [PATCH 05/20] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20=EB=B0=8F=20=EB=9D=BC=EC=9A=B0?= =?UTF-8?q?=ED=8C=85=20=EC=A0=9C=ED=95=9C=20=EB=B0=A9=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OnboardingPage/SetupBranding.tsx | 6 +-- src/pages/OnboardingPage.tsx | 2 +- src/pages/RedirectPage.tsx | 8 ++- src/routers/ExceptPreMemberRoute.tsx | 13 ----- src/routers/MemberPrivateRoute.tsx | 7 ++- src/routers/router.tsx | 42 ++++++++------- src/services/TokenService.ts | 53 +++++++++++++------ 7 files changed, 73 insertions(+), 58 deletions(-) delete mode 100644 src/routers/ExceptPreMemberRoute.tsx diff --git a/src/components/OnboardingPage/SetupBranding.tsx b/src/components/OnboardingPage/SetupBranding.tsx index 80423b6..8b6b722 100644 --- a/src/components/OnboardingPage/SetupBranding.tsx +++ b/src/components/OnboardingPage/SetupBranding.tsx @@ -13,7 +13,7 @@ import { import { KeywordChip } from '@/components/common/Chip/KeywordChip'; import { IMAGE_KEYWORD_LIST } from '@/constants/onboarding'; import { onboardingState } from '@/recoil/onboardingState'; -import { authService } from '@/services/AuthService'; +import { tokenService } from '@/services/TokenService'; import { userService } from '@/services/UserService'; export const SetupBranding = () => { @@ -24,9 +24,9 @@ export const SetupBranding = () => { userAPI .register(onboarding) .then((res) => { - authService.setAuthToken(res.payload.access_token); + tokenService.setAccessToken(res.payload.access_token); userService.updateUserNickname(res.payload.nickname); - authService.deleteRegisterToken(); + tokenService.removeRegisterToken(); navigate('/'); }) .catch(() => { diff --git a/src/pages/OnboardingPage.tsx b/src/pages/OnboardingPage.tsx index 040524c..8fd67cc 100644 --- a/src/pages/OnboardingPage.tsx +++ b/src/pages/OnboardingPage.tsx @@ -25,7 +25,7 @@ export const OnboardingPage = () => { if (user !== 'PRE_MEMBER') { navigate('/'); } - }, []); + }, [user]); const nextClickHandler = (nextStep: string) => { setStep(nextStep); diff --git a/src/pages/RedirectPage.tsx b/src/pages/RedirectPage.tsx index 1077544..1d924fd 100644 --- a/src/pages/RedirectPage.tsx +++ b/src/pages/RedirectPage.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { authService } from '@/services/AuthService'; +import { tokenService } from '@/services/TokenService'; import { userService } from '@/services/UserService'; export const RedirectPage = () => { @@ -18,18 +18,16 @@ export const RedirectPage = () => { const registerToken = params.get('register_token'); if (registerToken) { - authService.setAuthToken(registerToken); - authService.setRegisterToken(registerToken); + tokenService.setRegisterToken(registerToken); userService.setUser({ nickname: '', is_test: false }); navigate('/onboarding'); } if (accessToken) { - authService.setAuthToken(accessToken); + tokenService.setAccessToken(accessToken); nickname && userService.setUser({ nickname, is_test: isTest === 'T' ? true : false }); navigate('/'); } - // TODO: warning 해결 // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/routers/ExceptPreMemberRoute.tsx b/src/routers/ExceptPreMemberRoute.tsx deleted file mode 100644 index a615f17..0000000 --- a/src/routers/ExceptPreMemberRoute.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Navigate, Outlet } from 'react-router-dom'; - -import { userService } from '@/services/UserService'; - -export const ExceptPreMemberRoute = () => { - const user = userService.getUserState(); - - if (user === 'PRE_MEMBER') { - return ; - } - - return ; -}; diff --git a/src/routers/MemberPrivateRoute.tsx b/src/routers/MemberPrivateRoute.tsx index 0e4c1f2..6de9f9d 100644 --- a/src/routers/MemberPrivateRoute.tsx +++ b/src/routers/MemberPrivateRoute.tsx @@ -5,10 +5,15 @@ import { userService } from '@/services/UserService'; export const MemberPrivateRoute = () => { const user = userService.getUserState(); - if (user !== 'MEMBER') { + if (user === 'NON_MEMBER') { window.alert('로그인이 필요한 페이지입니다.'); return ; } + if (user === 'PRE_MEMBER') { + window.alert('온보딩 후 이용 가능한 페이지입니다.'); + return ; + } + return ; }; diff --git a/src/routers/router.tsx b/src/routers/router.tsx index b448c38..2d6190b 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -25,18 +25,22 @@ import { MyPage1 } from '@/pages/Mypage'; import { OnboardingPage } from '@/pages/OnboardingPage'; import { RedirectPage } from '@/pages/RedirectPage'; import { SelfUnderstandPage } from '@/pages/SelfUnderstandPage'; +import { MemberPrivateRoute } from '@/routers/MemberPrivateRoute'; export const Router = () => { return ( }> + } /> } /> } /> } /> } /> - } /> - } /> - } /> + }> + } /> + } /> + } /> + }> @@ -47,23 +51,25 @@ export const Router = () => { } /> } /> - - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - } /> - } /> - } /> + }> + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + } /> + } /> + } /> + - } /> + } /> ); diff --git a/src/services/TokenService.ts b/src/services/TokenService.ts index be74a88..e839953 100644 --- a/src/services/TokenService.ts +++ b/src/services/TokenService.ts @@ -1,4 +1,3 @@ -import axios from 'axios'; import { Cookies } from 'react-cookie'; import { getAccessToken } from '@/apis/tokenAPI/getAccessToken'; @@ -6,26 +5,37 @@ import { userService } from '@/services/UserService'; class TokenService { cookies = new Cookies(); + alertShown = false; + isRefreshing = false; + + constructor() { + this.alertShown = false; + this.isRefreshing = false; + } async updateAccessToken() { - try { - const response = await getAccessToken(); - this.setAccessToken(response.payload.access_token); - } catch (error) { - if (axios.isAxiosError(error) && error.response) { - if (error.response.status === 401) { + if (!this.isRefreshing) { + this.isRefreshing = true; + try { + const response = await getAccessToken(); + this.setAccessToken(response.payload.access_token); + this.alertShown = false; + } catch (error) { + if (!this.alertShown) { window.alert('로그인이 필요합니다.'); - this.onLogout(); - } else { - window.alert('알 수 없는 오류가 발생했습니다.'); + this.alertShown = true; + this.removeData(); + window.location.href = '/auth'; } + } finally { + this.isRefreshing = false; } } } setAccessToken(token: string) { - // TODO: 만료일자 수정 - const expires = new Date(Date.now() + 60 * 60 * 24 * 7 * 1000); + // 1시간 + const expires = new Date(Date.now() + 60 * 60 * 1000); this.cookies.set('selpiece-access-token', token, { path: '/', expires, @@ -33,8 +43,8 @@ class TokenService { } setRegisterToken(token: string) { - // TODO: 만료일자 수정 - const expires = new Date(Date.now() + 60 * 60 * 24 * 7 * 1000); + // 1시간 + const expires = new Date(Date.now() + 60 * 60 * 1000); this.cookies.set('selpiece-register-token', token, { path: '/', expires, @@ -49,11 +59,16 @@ class TokenService { return this.cookies.get('selpiece-register-token'); } + removeRegisterToken() { + this.cookies.remove('selpiece-register-token'); + } + getHeader() { if (userService.getUserState() === 'PRE_MEMBER') { if (!this.getRegisterToken()) { window.alert('로그인이 필요합니다.'); - this.onLogout(); + this.removeData(); + window.location.href = '/auth'; } return `Bearer ${this.getRegisterToken()}`; @@ -64,11 +79,15 @@ class TokenService { } } - onLogout = () => { + removeData() { this.cookies.remove('selpiece-access-token'); this.cookies.remove('selpiece-register-token'); userService.removeUser(); - window.location.href = '/auth'; + } + + onLogout = () => { + this.removeData(); + window.location.href = '/'; }; } From 45878b51d0842e2e4edb6c2309d77cd9b588bf8b Mon Sep 17 00:00:00 2001 From: aaminha Date: Wed, 5 Jun 2024 01:39:19 +0900 Subject: [PATCH 06/20] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EB=9E=A8=20=EB=A7=81=ED=81=AC=20=EC=9D=B4=EB=8F=99=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HomePage/RecommendSectionTemplate.tsx | 2 +- src/components/common/Card/PreviewCard.tsx | 44 ++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/components/HomePage/RecommendSectionTemplate.tsx b/src/components/HomePage/RecommendSectionTemplate.tsx index 427d62e..0e5986d 100644 --- a/src/components/HomePage/RecommendSectionTemplate.tsx +++ b/src/components/HomePage/RecommendSectionTemplate.tsx @@ -46,7 +46,7 @@ export const RecommendSectionTemplate = ({ imageUrl={item.selfUnderstandingUrl} title={item.name} keywords={item.keywords} - path={item.link} + path={item.link === '' ? `/program/${item.type}/${item.programsId}` : item.link} /> ))} diff --git a/src/components/common/Card/PreviewCard.tsx b/src/components/common/Card/PreviewCard.tsx index 6965cb3..4543f32 100644 --- a/src/components/common/Card/PreviewCard.tsx +++ b/src/components/common/Card/PreviewCard.tsx @@ -2,6 +2,7 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { PlainChip } from '@/components/common/Chip/PlainChip'; +import { userService } from '@/services/UserService'; interface PreviewCardProps { imageUrl: string; @@ -11,8 +12,47 @@ interface PreviewCardProps { } export const PreviewCard = ({ imageUrl, title, keywords, path }: PreviewCardProps) => { + const userState = userService.getUserState(); + const isExternal = path && (path.startsWith('http://') || path.startsWith('https://')); + + const CardLink = ({ children }: { children: React.ReactNode }) => { + if (isExternal) { + return ( + + {children} + + ); + } else { + if (userState === 'NON_MEMBER') { + return ( +
{ + window.alert('로그인이 필요한 서비스입니다.'); + }} + > + {children} +
+ ); + } + + if (userState === 'PRE_MEMBER') { + return ( +
{ + window.alert('온보딩 진행 후 이용 가능합니다.'); + }} + > + {children} +
+ ); + } + + return {children}; + } + }; + return ( - + @@ -26,7 +66,7 @@ export const PreviewCard = ({ imageUrl, title, keywords, path }: PreviewCardProp
- + ); }; From 4e87801e11e2c8596e5a7122dbaa70781a27dab6 Mon Sep 17 00:00:00 2001 From: aaminha Date: Wed, 5 Jun 2024 02:10:39 +0900 Subject: [PATCH 07/20] =?UTF-8?q?design:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20&=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=94=94=EC=9E=90=EC=9D=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/LoginPage/LoginMobilePage.tsx | 5 +++++ src/pages/OnboardingPage.tsx | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/LoginPage/LoginMobilePage.tsx b/src/components/LoginPage/LoginMobilePage.tsx index 1c8c3ab..c2c32d7 100644 --- a/src/components/LoginPage/LoginMobilePage.tsx +++ b/src/components/LoginPage/LoginMobilePage.tsx @@ -40,7 +40,12 @@ const ViewContainer = styled.div` height: var(--full-height); background-image: url(${backgroundImg}); background-size: cover; + background-repeat: no-repeat; background-position: calc(50% + 70px) center; + + @media (min-width: 1108px) { + background-position: center; + } `; const MainContainer = styled.div` diff --git a/src/pages/OnboardingPage.tsx b/src/pages/OnboardingPage.tsx index 8fd67cc..1abc32b 100644 --- a/src/pages/OnboardingPage.tsx +++ b/src/pages/OnboardingPage.tsx @@ -58,8 +58,6 @@ const StyledContainer = styled.div` background-image: url(${BackgroundImage}); background-size: cover; display: flex; - - overflow-x: auto; `; const StyledStepSection = styled.section` From a17f19d5ee62f4a3a1246ddeaa67304e65595fb6 Mon Sep 17 00:00:00 2001 From: aaminha Date: Wed, 5 Jun 2024 04:46:50 +0900 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=EC=A1=B0=20=EB=B0=8F=20=EB=94=94?= =?UTF-8?q?=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/MyPage/Card.tsx | 44 ++++--------- src/components/MyPage/MyExperienceView.tsx | 14 +--- src/components/MyPage/MyPageSidebar.tsx | 64 +++++++++---------- src/components/MyPage/PersonaView.tsx | 9 +-- src/components/common/Layout/MyPageLayout.tsx | 25 ++++++++ src/pages/Mypage.tsx | 33 ---------- src/routers/router.tsx | 15 ++++- 7 files changed, 88 insertions(+), 116 deletions(-) create mode 100644 src/components/common/Layout/MyPageLayout.tsx delete mode 100644 src/pages/Mypage.tsx diff --git a/src/components/MyPage/Card.tsx b/src/components/MyPage/Card.tsx index 36578a5..089e43e 100644 --- a/src/components/MyPage/Card.tsx +++ b/src/components/MyPage/Card.tsx @@ -1,39 +1,36 @@ import styled from 'styled-components'; +const Card = ({ title, description }: { title: string; description: string }) => { + return ( + + {title} + {description} + + ); +}; + +export default Card; + const Container = styled.div` - width: 100%; - height: 100%; + width: 300px; + height: 112px; padding: 20px; background: ${({ theme }) => `${theme.color.white}`}; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.13); border-radius: 8px; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - gap: 12px; - display: inline-flex; -`; -const InnerContainer = styled.div` - align-self: stretch; - height: 100px; + display: flex; flex-direction: column; - justify-content: flex-start; - align-items: flex-start; gap: 4px; - display: flex; `; const Title = styled.div` - align-self: stretch; color: ${({ theme }) => `${theme.color.gray800}`}; ${({ theme }) => theme.font.desktop.body1m}; word-wrap: break-word; `; const Description = styled.div` - align-self: stretch; - flex: 1; color: ${({ theme }) => `${theme.color.gray500}`}; ${({ theme }) => theme.font.desktop.label2}; word-wrap: break-word; @@ -41,16 +38,3 @@ const Description = styled.div` text-overflow: ellipsis; white-space: pre-wrap; `; - -const Card = ({ title, description }: { title: string; description: string }) => { - return ( - - - {title} - {description} - - - ); -}; - -export default Card; diff --git a/src/components/MyPage/MyExperienceView.tsx b/src/components/MyPage/MyExperienceView.tsx index 664ea3b..35667fc 100644 --- a/src/components/MyPage/MyExperienceView.tsx +++ b/src/components/MyPage/MyExperienceView.tsx @@ -7,25 +7,14 @@ import { MyExperienceWholeView } from '@/components/MyPage/MyExperienceWholeView import { MyUnderstandingView } from '@/components/MyPage/MyUnderstandingView'; import { Dropdown } from '@/components/common/Dropdown/Dropdown'; import { MyPageTab } from '@/components/common/Tab/MyPageTab'; -import { MyPageFilter } from '@/types/myPage.type'; -interface MyExperienceViewTemplateProps { - title: string | React.ReactNode; - subTitle: string; - backgroundColor: string; - filters: MyPageFilter[]; -} - -export const MyExperienceView = ({ - filters, -}: Omit) => { +export const MyExperienceView = () => { const [programDate, setProgramDate] = useState([]); const [sortOrder, setSortOrder] = useState('desc'); const resetFilters = () => { setProgramDate([]); setSortOrder('desc'); - filters.forEach((filter: MyPageFilter) => filter.setSelected([])); }; const tabs = [ @@ -45,7 +34,6 @@ export const MyExperienceView = ({ setSortOrder(newSelected === '최신순' ? 'desc' : 'asc'); }} width="201px" - contentMaxHeight="172px" multiple={false} /> diff --git a/src/components/MyPage/MyPageSidebar.tsx b/src/components/MyPage/MyPageSidebar.tsx index 967fbe5..404561b 100644 --- a/src/components/MyPage/MyPageSidebar.tsx +++ b/src/components/MyPage/MyPageSidebar.tsx @@ -1,17 +1,44 @@ +import { useLocation, useNavigate } from 'react-router-dom'; import styled from 'styled-components'; import { theme } from '@/styles'; +const menuItems = [ + { label: '브랜드 관리', key: 'brand' }, + { label: '내 페르소나', key: 'persona' }, + { label: '신청한 경험', key: 'experience' }, + { label: '환경설정', key: 'settings' }, +]; + +export const MypageSidebar = () => { + const location = useLocation(); + const navigate = useNavigate(); + + return ( + + {menuItems.map((item) => ( + navigate(`/mypage/${item.key}`)} + > +
{item.label}
+
+ ))} +
+ ); +}; + const SidebarContainer = styled.div` width: 196px; height: auto; padding-top: 81px; + background-color: ${({ theme }) => `${theme.color.white}`}; border-right: 2px ${({ theme }) => `${theme.color.gray150}`} solid; display: flex; flex-direction: column; - justify-content: flex-start; - align-items: flex-start; + flex-shrink: 0; `; const MenuItem = styled.div<{ $active: boolean }>` @@ -21,7 +48,8 @@ const MenuItem = styled.div<{ $active: boolean }>` padding-right: 24px; background-color: ${({ $active }) => $active ? `${theme.color.primary50}` : `${theme.color.white}`}; - border-left: ${({ $active }) => ($active ? '4px #915AFB solid' : 'none')}; + border-left: 4px solid transparent; + border-color: ${({ $active }) => $active && '#915AFB'}; display: flex; justify-content: flex-start; align-items: center; @@ -38,33 +66,3 @@ const MenuItem = styled.div<{ $active: boolean }>` background-color: ${(props) => props.theme.color.gray150}; } `; - -interface SidebarProps { - activeMenu: string; - setActiveMenu: (menu: string) => void; -} - -const Sidebar = ({ activeMenu, setActiveMenu }: SidebarProps) => { - const menuItems = [ - { label: '브랜드 관리', key: '브랜드 관리' }, - { label: '내 페르소나', key: '내 페르소나' }, - { label: '신청한 경험', key: '신청한 경험' }, - { label: '환경설정', key: '환경설정' }, - ]; - - return ( - - {menuItems.map((item) => ( - setActiveMenu(item.key)} - > -
{item.label}
-
- ))} -
- ); -}; - -export default Sidebar; diff --git a/src/components/MyPage/PersonaView.tsx b/src/components/MyPage/PersonaView.tsx index 3a07246..95d0725 100644 --- a/src/components/MyPage/PersonaView.tsx +++ b/src/components/MyPage/PersonaView.tsx @@ -20,12 +20,13 @@ export const PersonaView = () => { const [isFront, setIsFront] = useState(true); const captureRef = useRef(null); const navigate = useNavigate(); + const settings = { - dots: false, - infinite: true, + infinite: false, speed: 500, slidesToShow: 3, slidesToScroll: 3, + variableWidth: true, arrows: false, }; @@ -186,7 +187,6 @@ const TopContainer = styled.div` const BottomContainer = styled.div` align-self: stretch; height: 627px; - //flex-grow: 1; padding: 24px; background-color: ${({ theme }) => `${theme.color.gray150}`}; border-radius: 16px; @@ -259,7 +259,7 @@ const BottomImageContainer = styled.div` width: 100%; height: 100%; background: var(--modal-bg, rgba(18, 18, 18, 0.36)); - /* blur */ + backdrop-filter: blur(5px); position: absolute; top: 0; @@ -282,6 +282,7 @@ const BottomImageContainer = styled.div` .text-container { margin-bottom: 20px; + text-align: center; } } `; diff --git a/src/components/common/Layout/MyPageLayout.tsx b/src/components/common/Layout/MyPageLayout.tsx new file mode 100644 index 0000000..f48acad --- /dev/null +++ b/src/components/common/Layout/MyPageLayout.tsx @@ -0,0 +1,25 @@ +import { Outlet } from 'react-router-dom'; +import styled from 'styled-components'; + +import { MypageSidebar } from '@/components/MyPage/MyPageSidebar'; + +export const MyPageLayout = () => { + return ( + + + + + + + ); +}; + +const StyledContainer = styled.div` + display: flex; + min-height: var(--full-height); + width: 100%; +`; + +const StyledOutletContainer = styled.div` + width: calc(var(--full-width) - 196px); +`; diff --git a/src/pages/Mypage.tsx b/src/pages/Mypage.tsx deleted file mode 100644 index 3f934ea..0000000 --- a/src/pages/Mypage.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useState } from 'react'; - -import { BrandView } from '@/components/MyPage/BrandView'; -import { MyExperienceView } from '@/components/MyPage/MyExperienceView'; -import MyPageSidebar from '@/components/MyPage/MyPageSidebar'; -import { PersonaView } from '@/components/MyPage/PersonaView'; -import { SettingView } from '@/components/MyPage/SettingView'; - -export const MyPage1 = () => { - const [activeMenu, setActiveMenu] = useState('브랜드 관리'); - - const renderContent = () => { - switch (activeMenu) { - case '브랜드 관리': - return ; - case '내 페르소나': - return ; - case '신청한 경험': - return ; - case '환경설정': - return ; - default: - return ; - } - }; - - return ( -
- -
{renderContent()}
-
- ); -}; diff --git a/src/routers/router.tsx b/src/routers/router.tsx index 2d6190b..e8005da 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -1,6 +1,11 @@ import { Navigate, Route, Routes } from 'react-router-dom'; +import { BrandView } from '@/components/MyPage/BrandView'; +import { MyExperienceView } from '@/components/MyPage/MyExperienceView'; +import { PersonaView } from '@/components/MyPage/PersonaView'; +import { SettingView } from '@/components/MyPage/SettingView'; import { MainLayout } from '@/components/common/Layout/MainLayout'; +import { MyPageLayout } from '@/components/common/Layout/MyPageLayout'; import { TestLayout } from '@/components/common/Layout/TestLayout'; import { DefineResultPage } from '@/pages/DefineResultPage'; import { DefineStartPage } from '@/pages/DefineStartPage'; @@ -21,7 +26,6 @@ import { ExperienceDetailPage } from '@/pages/ExperienceDetailPage'; import { ExperienceRecommendPage } from '@/pages/ExperienceRecommendPage'; import { HomePage } from '@/pages/HomePage'; import { LoginPage } from '@/pages/LoginPage'; -import { MyPage1 } from '@/pages/Mypage'; import { OnboardingPage } from '@/pages/OnboardingPage'; import { RedirectPage } from '@/pages/RedirectPage'; import { SelfUnderstandPage } from '@/pages/SelfUnderstandPage'; @@ -39,7 +43,13 @@ export const Router = () => { }> } /> } /> - } /> + }> + } /> + } /> + } /> + } /> + } /> + }> @@ -69,7 +79,6 @@ export const Router = () => { - } /> ); From 713b3c17d8a8c3fe3dd03c4ee4c3854b5368fa82 Mon Sep 17 00:00:00 2001 From: aaminha Date: Wed, 5 Jun 2024 05:36:59 +0900 Subject: [PATCH 09/20] =?UTF-8?q?fix:=20=EB=B8=8C=EB=9E=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=B7=B0=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/cards/piece/connector.png | Bin 0 -> 6691 bytes src/assets/cards/piece/creator.png | Bin 0 -> 6356 bytes src/assets/cards/piece/encourager.png | Bin 0 -> 7042 bytes src/assets/cards/piece/innovator.png | Bin 0 -> 6307 bytes src/assets/cards/piece/insighter.png | Bin 0 -> 5846 bytes src/assets/cards/piece/inventor.png | Bin 0 -> 6628 bytes src/assets/cards/piece/organizer.png | Bin 0 -> 7004 bytes src/assets/cards/piece/projector.png | Bin 0 -> 6249 bytes src/assets/logos/brandLogo.svg | 25 ----------- src/components/MyPage/BrandView.tsx | 30 ++++++++++--- .../common/Modal/BrandCardModal.tsx | 40 ++++++++++++++---- src/constants/piece.ts | 19 +++++++++ src/styles/global/GlobalStyle.ts | 2 +- 13 files changed, 76 insertions(+), 40 deletions(-) create mode 100644 src/assets/cards/piece/connector.png create mode 100644 src/assets/cards/piece/creator.png create mode 100644 src/assets/cards/piece/encourager.png create mode 100644 src/assets/cards/piece/innovator.png create mode 100644 src/assets/cards/piece/insighter.png create mode 100644 src/assets/cards/piece/inventor.png create mode 100644 src/assets/cards/piece/organizer.png create mode 100644 src/assets/cards/piece/projector.png delete mode 100644 src/assets/logos/brandLogo.svg create mode 100644 src/constants/piece.ts diff --git a/src/assets/cards/piece/connector.png b/src/assets/cards/piece/connector.png new file mode 100644 index 0000000000000000000000000000000000000000..8e3ace34281301dd0eb7634458fb3ae6dfe074da GIT binary patch literal 6691 zcmV+;8rdFpc?Ed7N736Q=t z=!8~EfS_@bHfdmawroo>Es+)}iV`V`GkJOEd+r%_*S?esBYlxYN>%`F;EEJ^hqKrD zzV)qdediF^SvzZI?W~=(vv$_b+F3hmXMMJH{J7Mll-kLYQkRh8=KqCy;J0ku(F1M* zathiQppWyhpAW16b{bkWhcFf*m~#<6{WL-;c`!mq{1&V`1ez2lPeM~5#dFXaKtij* zapG_z7giJ?v}>rDCK~lBJX64%yN1T$?;vO%6ax_VaQqp#151U=FA^|T$}kf;l1UR8 zg4G%6NZBSFsY3=D%05u^Lu}N64W|Xwv;T=Y7T_~|!xP60@UkhavG7^>RMbx};_^%yD z^s*wrdn|3Y4YZqtF2+v5DEGt3l^9(ndXjMr2nTy*9A90_w1JKCgzeKsW`*BrlC;uP`Nsea(^e}$T$MQs8gSDF95implM@cFp_!1WyA=x&@Bzz zr<>7%C@o&RauvI3Z-x*-hk40e&x_BxaCNE+RDu9w`#@su`G_U6}Q~al5`J za_mcf5c_pd*UtJDw5SBUWg~bBh*(_74|lgX+(x9S3I_d&B%B9Z5w}tV%))L~ykR?r z$7+j>7sn^`gjW%`zr{nQOCm!KvpN|?Z4#QwwMi3izvaLnm^PJdM^;DL*0^+_JeQ3ID7~u zxhozf(ayB#wruP%Ep0y|<8hOu$>!F>%I1&t8Gf z4j?!Sv{2=Cv;pNbQiGMAjp4rg1)ew>BAMg4vgJ`>ic4sqY-dnvV^?u% z3?A{n};k7q_`80v#qY{@$ygGP`68d6jKR@kXf<{8hC23Gar`cc<$2#Q zgl7wM=L00W8HTM9Og;o9J|zeuMqg#c#N4QZYqKKA?T<>it|+~{Ahc-$6HCA?jPX7NrUsLR;hy74PjM1lG89vFmyLQmRW#lJi&j8gJKbVGZojQ>6{P z^ZInyKRSgzZ&}*@vcSwWwgzz>u5DpgPYAglL8fdNF>#>2fwP*8B`1efmqj6$W3#Wk z5rKOLG+I3(xrvQIrEUoeiHM$0jVVQrgSQS?Sw;yrrdlO(9i&h1C*&sMO>F>oKwIj# zU;CJVUNHj9@N0!prH&z$OriaOgZk_PhtN5>fZ?(42fLBv6RJ99rVMm2Y9*_WUiT2# zPReZ!9;>by3xSD69>>NA2pS)9vq8P1z}<2o7$1i$t-++%6wgMSO?#JV7w~9pA_n~=|F0Och1)-adE`yd63$SzghcJX9oI4zA zw+!0L`yomOA~w`KK2(`~6?R7B2}k0`7l5^CptM0>$1zCO+?1ubjJ?$gg(@Ahbc@tY z7nHN8u<_aOYFH~SVS!J%UMu2qxPeQ4ouCDn*se}O5w zW=c#gm7_v+QsTeglbBy1ST|}-DmINs$ue>pi$lqSTdlxKMZ?^nMKjWHUC0bA@5|>= zuC5}#QDTGcVV3^d9Gm`y?q1BH(xJ{bCb9<-Tn99O9Ncf>&QXj$i*9G?NvLXc!DAmkw;&3`Bf(RQ6q4 zW0YNqFz8a7>1qzZjZtQ33PDy2)mQ7L?=~W{AS3MtU5q?Atzc(S*Dt`=bCAqp%M(Zv zgdEeZEwfv+XCD`t?85?XC%LnQe6UJUOu?-4bce2P@Fc{78dh1o6+4<%8WvB8!3;JY zXh(G80%Yso(M}pWjuR)gr%C+`9f6R?UX}sRV9h0k`3VLW z<*gkF**U++moq)%Z32BQlp7?i4cn$^CzTEYQ+^0>aj;QC%wjuBhj@vRx)O7-z$9LC zGT4~1hA;5JjDZW6O;neZ z!N_)630sRGGSYa^u(U?j67m4Kz0)GkrxHk!|6IG1o7T|kxac65y;hEVMQ6iLbLzqm z4_$#;Odz1W#5YEr+*YUf4dxCeGtUZnVX zmt;vn-P9N>zJ}E@yAc{nVO(_Uae3IV9QhZp2?C7-0`Ubm4zo0dbY-pQA3(_rv3gQlqDDEG5LJZK7*$xX}DUJJtqt+B__L3Mx| zORJe2O~wmX=xDrcBU<3hj;Ef94J}lZ62jGMU*I?WB>X{&-yx>x9wUasPFz08xhTr2 zli>4<{Jz5Xv-ly70#BhrZqzm+#7)1cVcaq|(omequk`1XUaS;lrd%Uvlz9wErz^(2 zp4uMJKC?BXQdDu%ffMjPd5Q0q*07w1{7|S&Yp}!UOeTZpeNPjG3Xo?C_VV*c@R(m^dJ^=WjtH%_ zI=cdctWF>+4cbU#PSwY;Qd4Y0y&HW4g{xhxquo)}SVx?ZZJr*-P3^X|VsP8T;#2z} zAM@e)8z_Y-tkUh6zwb!wYmd>8WOPvGH+jxZ zg%1S{qsFueQ|am>q43htf{O|P*9f+%oYnxrrrb#INYm9iR(|f_26OYkmzu+PSY__N z$mpYqNq4X5?j%dvU z0f&ECVwJsYf}dsbecbzK74IeptQyHDs09}c)E%27#a@@)x-tgzqUsbHkkl)h>Z@`* zm?>6bx=s9%9m|mhC!6~XvBkBe%Mzwr5OqcHJRb#C(-rdbeD{zw*}f9(EmuYS2v2Sc z)#??d*du8T=dS8Vyav3$a~~scrl_HmN7w3{fw-GOibtcYAIaA!P1l+vY4Wwb6%R`v zDYICoCecyvB0Vx%lj|NO4Y`iR06amG1Sshihjd3ZB3{dq&zPDlTB`6Gte~2jZEO{| z+Xk%}ZLEoBe*mAnR`3k0I58}mNqz3{P%u@}#Y6Q=A@+v`$^>DF+4M!ajIT{lxn=~O zNpjHANvo)avAk-l6$V>zhh6-LJFpXQgC1RYLww^|n_eIJ82HvxT1XOwA5e zTZS*A9am2gfHti&eAfsR5H(Fq|X zq+&=Ab5%FpdIctb_W4xLPcP+?QnrZ~d*Bx)QEts*wLv@IFtr<2N>1#F#Sa9XlmeUw z%r8s4Ix6rjPL-b{C7=A2flK2t&__d-PR1}RW9$*O1d4@N3?2SVQ~Hh%)n__%PEv;{ zWY0uQV-^vnXGAfr8Ol-NSJizqeOor2Wjq^zCgKVv6QpOuTa;$(dBxhCGuad|! z2N|jzBzSIx*$A!5Hfe*+<17!7I?N=9PKu5&B}I&pnWnEMnchOBqOL}Ro3^cLbJc-V zN*1p2Jse*wk_NBw=SyM(`98|Oo+DyQoVMlkRO>h`jKjOh(|L;BGFI3rEE1ElkYs_NbWs|s>GYP>*7((DIu>Yj z(zK-@1U|^?h%zh-%?^N37N{rjsR5e(uA;Rnt@WRdQs~xYmh|Q*`9}IP+AQc>wTiao zqQ2%qoInsh1~1guTB(6<91ApYFRmdP)?=DQ3x{Axyz#EUdCCep*+`cpX3ubg)MF!m z#LZINsdQ9AO3fG=j2MID9^*`uws!MpZ5;FU5!l*-gM{xfvS>kZSmBo~8Us9*_MkbM zBEOb)2#`l$ zlEltqItsQ@3FcIa^G}N%fl|69ekoliaG|>XBf^a`JAPM<^e9;`RlOu@k6HvW#)<=yi<8?6#U>^&2dHR}YiM2*%;^Ou?L$5~!E9NUskyCavx zzrrrzsS;#wg9G<@)MHuprg{@Fotn$l+R(1Ms@rC38b|%Dp9?(wMS{^+MN3N^bJKAw zlaH>CQKK?yVgx~_CTq_Ji1%`eGHT%$XKcKGorBMm#0xzQR0!}S%cso_;}RpaOmKVI zT?Fm2n23uDECdy4Hph?AE3bTVvj=W#Z98Z;&!BolCr?Uw?3>hYr+~A0)Nq($^?zV> z9c2W$NYTe%OS0g-eoaGp!o>@}$NA_WBW|P!H|1jHs)LXbmtvRTavU791B|}o;AdwD z4kPG^t_EW4)(fnjW2-La2tpg9tA*UqMK0_u$yK{e{E`EpX&RfWQ4M9QjQallTl3ZK z_&Nt$( z5tNJ5I==e#3es5;kRGCbjX(Rjj-^F*5V?TS<)cXVw^TN9y`Z6fb1sk?0{*Ns9v#Vw zsev}Uqb{*$!!XgPV>YT{-J`$Bh?j@e5p5w{?l@?lUJ5{_Y;m{xLN z_>kdG>DrgXg?jhWnuhQHa~xlN%0q%B_QII<=h6)=6Co z>Gla6`I=}v*(pX74nCr|p3);svh|vA`8m-t)`nbi+QQbaWBVL#+mhnqNzQm%SP&9u z;lQbjQLJ>dBPc z%~or$pFQpKdi{w3yyfb+O#ZseCUlY&dzKPr)mubieHz};mlfBFaI1FrT?g%xW|`fC zcHkR`rLt(H7U(ocRTTPrxS`!E>v-_IkB3vbw89C_6=oxL0GvDp9MtS$at!k=Rw5P; z^4==DhCk4YU+9%@yM~wu2^d}EsY(TN9N(=K^C{HuH2?euUunfpk9KLv_e33qB?ph&BU)_nLxIH)H4GjS*x+QU{5Hici-Y2@ zU5Bx_w-0aXLR_W)ILT9Tm3G=3ceYLgv*NeGKm0rbY~%VI(sOgEo9p1Iy|&sEgd!b zah}QH)ZVnHOi#-r$s&eJ>NX&CG0c#LJ?*&I@8Sc#`ZC+lsYV6Y$YG0wX+v|-c>nwG z2Tl?&vH4lp#&u8Jzk8GCv}>nfZd`+t*~jsyhps4wev1=$b`Arpb%IvF{$eCWZx$c5 znqq>z z^A^Y+jxsxFn8Y@3btkzusP;l!r)fD?Sj7q}WXWkkFmkMUH5PCOukya0q#u34ax-d{Hf-` ztz#r^&XdV?s4u>aB4+~&w9IBGM9^}lUGp?SLiuwMWG002ovPDHLkV1oC1rt<&* literal 0 HcmV?d00001 diff --git a/src/assets/cards/piece/creator.png b/src/assets/cards/piece/creator.png new file mode 100644 index 0000000000000000000000000000000000000000..855478e9fc4d252c9c43b7fb13f68133ea2d96bf GIT binary patch literal 6356 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 F{U82B9~uAv literal 0 HcmV?d00001 diff --git a/src/assets/cards/piece/encourager.png b/src/assets/cards/piece/encourager.png new file mode 100644 index 0000000000000000000000000000000000000000..41fcd067aacead7005248b3a73afc9ee18120040 GIT binary patch literal 7042 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 literal 0 HcmV?d00001 diff --git a/src/assets/cards/piece/innovator.png b/src/assets/cards/piece/innovator.png new file mode 100644 index 0000000000000000000000000000000000000000..44e57397f8bdf02e266fe1622fac0d5b64cafd7e GIT binary patch literal 6307 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 I#x(SQ03-MMa{vGU literal 0 HcmV?d00001 diff --git a/src/assets/cards/piece/insighter.png b/src/assets/cards/piece/insighter.png new file mode 100644 index 0000000000000000000000000000000000000000..d8b6ba99f86bfda3e50ff458f5faa2646f34f21c GIT binary patch literal 5846 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 literal 0 HcmV?d00001 diff --git a/src/assets/logos/brandLogo.svg b/src/assets/logos/brandLogo.svg deleted file mode 100644 index 6297bc7..0000000 --- a/src/assets/logos/brandLogo.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/MyPage/BrandView.tsx b/src/components/MyPage/BrandView.tsx index f6b7894..ea87fb9 100644 --- a/src/components/MyPage/BrandView.tsx +++ b/src/components/MyPage/BrandView.tsx @@ -1,17 +1,20 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import styled from 'styled-components'; +import { personaAPI } from '@/apis/personaAPI'; import { ReactComponent as AddIcon } from '@/assets/icons/add.svg'; import { ReactComponent as ArrowRight } from '@/assets/icons/arrowRight.svg'; import { ReactComponent as CalendarIcon } from '@/assets/icons/calendar.svg'; import { ReactComponent as InfoIcon } from '@/assets/icons/info.svg'; -import { ReactComponent as BrandLogoImage } from '@/assets/logos/brandLogo.svg'; import Card from '@/components/MyPage/Card'; import DateCard from '@/components/MyPage/DateCard'; import { BrandChip } from '@/components/common/Chip/BrandChip'; import { BrandCardModal } from '@/components/common/Modal/BrandCardModal'; +import { PERSONA } from '@/constants/persona'; +import { PIECE_IMAGE } from '@/constants/piece'; import { userService } from '@/services/UserService'; +import { DefineResult } from '@/types/test.type'; interface DateCardProps { title: string; @@ -24,6 +27,13 @@ export const BrandView = () => { const [readyCards, setReadyCards] = useState([]); const [progressCards, setProgressCards] = useState([]); const [completeCards, setCompleteCards] = useState([]); + const [defineResult, setDefineResult] = useState(null); + + useEffect(() => { + personaAPI.getDefineMember().then((res) => { + setDefineResult(res.payload); + }); + }, []); const openModal = () => setIsModalOpen(true); const closeModal = () => setIsModalOpen(false); @@ -49,9 +59,18 @@ export const BrandView = () => { - + {defineResult && ( + card.name === defineResult.name.toLowerCase())?.[ + 'image' + ] + } + alt={defineResult?.name} + /> + )} - 브랜드 이름 + {defineResult ? PERSONA[defineResult.name] : ''} 상반기 100만 구독자 확보 @@ -94,7 +113,7 @@ export const BrandView = () => { 준비 - + {Dummy3.map((item, index) => ( @@ -185,6 +204,7 @@ const BrandHeader = styled.div` align-items: center; display: inline-flex; `; + const HeaderLeft = styled.div` justify-content: flex-start; align-items: center; diff --git a/src/components/common/Modal/BrandCardModal.tsx b/src/components/common/Modal/BrandCardModal.tsx index 493f775..e2123b6 100644 --- a/src/components/common/Modal/BrandCardModal.tsx +++ b/src/components/common/Modal/BrandCardModal.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import styled from 'styled-components'; +import Scrollbar from '@/components/Scrollbar'; import { PlainButton } from '@/components/common/Button/PlainButton'; interface ModalProps { @@ -63,7 +64,7 @@ export const BrandCardModal = ({ isOpen, onClose, onAdd }: ModalProps) => { setStatus('준비')} @@ -71,7 +72,7 @@ export const BrandCardModal = ({ isOpen, onClose, onAdd }: ModalProps) => { 준비 setStatus('진행중')} @@ -79,7 +80,7 @@ export const BrandCardModal = ({ isOpen, onClose, onAdd }: ModalProps) => { 진행 setStatus('완료')} @@ -158,6 +159,10 @@ const Title = styled.input` color: ${({ theme }) => `${theme.color.gray300}`}; ${({ theme }) => theme.font.desktop.title2}; word-wrap: break-word; + + &::placeholder { + color: ${({ theme }) => `${theme.color.gray300}`}; + } `; const Frame = styled.div` @@ -189,20 +194,37 @@ const Input = styled.input` align-items: center; gap: 12px; display: flex; + + ${({ theme }) => theme.font.desktop.body2r}; + color: ${({ theme }) => `${theme.color.gray700}`}; + + &::placeholder { + color: ${({ theme }) => `${theme.color.gray300}`}; + } `; -const DescriptionInput = styled.input` - align-self: stretch; +const DescriptionInput = styled.textarea` + resize: none; + border: none; + padding: 0; + margin: 0; + width: 100%; height: 116px; padding: 16px; background: ${({ theme }) => `${theme.color.gray50}`}; border-radius: 8px; border: 2px solid #efefef; - justify-content: flex-start; - align-items: flex-start; - gap: 12px; - display: inline-flex; + + ${({ theme }) => theme.font.desktop.body2r}; + color: ${({ theme }) => `${theme.color.gray700}`}; + + &::placeholder { + color: ${({ theme }) => `${theme.color.gray300}`}; + } + + overflow-y: auto; + ${Scrollbar} `; const Label = styled.div` diff --git a/src/constants/piece.ts b/src/constants/piece.ts new file mode 100644 index 0000000..a54c0ff --- /dev/null +++ b/src/constants/piece.ts @@ -0,0 +1,19 @@ +import Connector from '@/assets/cards/piece/connector.png'; +import Creator from '@/assets/cards/piece/creator.png'; +import Encourager from '@/assets/cards/piece/encourager.png'; +import Innovator from '@/assets/cards/piece/innovator.png'; +import Insighter from '@/assets/cards/piece/insighter.png'; +import Inventor from '@/assets/cards/piece/inventor.png'; +import Organizer from '@/assets/cards/piece/organizer.png'; +import Projector from '@/assets/cards/piece/projector.png'; + +export const PIECE_IMAGE = [ + { name: 'creator', image: Creator }, + { name: 'insighter', image: Insighter }, + { name: 'innovator', image: Innovator }, + { name: 'inventor', image: Inventor }, + { name: 'projector', image: Projector }, + { name: 'connector', image: Connector }, + { name: 'encourager', image: Encourager }, + { name: 'organizer', image: Organizer }, +]; diff --git a/src/styles/global/GlobalStyle.ts b/src/styles/global/GlobalStyle.ts index c0c7895..347c372 100644 --- a/src/styles/global/GlobalStyle.ts +++ b/src/styles/global/GlobalStyle.ts @@ -53,7 +53,7 @@ input, textarea { -ms-user-select: auto; user-select: auto; } -input:focus { +input:focus, textarea:focus { outline: none; } button { From a0bdcb56286331be7bf14e50eb79a69ee77b3b1b Mon Sep 17 00:00:00 2001 From: aaminha Date: Thu, 6 Jun 2024 00:41:38 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=98=EA=B2=BD=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20TopNaviga?= =?UTF-8?q?tion=20=EB=86=92=EC=9D=B4=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/userAPI.ts | 12 +- src/assets/backgrounds/setting.png | Bin 74739 -> 0 bytes src/assets/icons/user.svg | 2 +- src/components/DiscoverTestPage/SpeechBox.tsx | 2 +- src/components/MyPage/MyExperienceView.tsx | 2 +- src/components/MyPage/MyPageSidebar.tsx | 2 +- src/components/MyPage/PersonaView.tsx | 2 +- src/components/MyPage/SettingView.tsx | 389 +++++++++++++++++- src/components/common/Input/DefaultInput.tsx | 24 +- .../common/Navigation/TopNavigation.tsx | 4 +- src/styles/global/zoom.css | 1 + src/types/user.type.ts | 9 + src/types/userAPI.type.ts | 5 +- 13 files changed, 426 insertions(+), 28 deletions(-) delete mode 100644 src/assets/backgrounds/setting.png diff --git a/src/apis/userAPI.ts b/src/apis/userAPI.ts index a42e91e..7736e2a 100644 --- a/src/apis/userAPI.ts +++ b/src/apis/userAPI.ts @@ -1,5 +1,5 @@ import { authClient, noAuthClient } from '@/apis/client'; -import { RegisterRequest } from '@/types/userAPI.type'; +import { ReRegisterRequest, RegisterRequest } from '@/types/userAPI.type'; export const userAPI = { // 신규 유저 등록 @@ -12,6 +12,16 @@ export const userAPI = { const response = await noAuthClient.get(`/api/users/check-nickname/${nickname}`); return response.data; }, + // 유저 정보 조회 + getUserInfo: async () => { + const response = await authClient.get('/api/users/infos'); + return response.data; + }, + // 유저 정보 수정 + updateUserInfo: async (userInfo: ReRegisterRequest) => { + const response = await authClient.patch('/api/users/infos', userInfo); + return response.data; + }, // 로그아웃 logout: async () => { const response = await noAuthClient.post('/api/users/logout'); diff --git a/src/assets/backgrounds/setting.png b/src/assets/backgrounds/setting.png deleted file mode 100644 index 4132aaf0a84d7ebe2d620ef9bb022d94179c1c44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74739 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%0AFtFt!eB7bd;m|$D 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 E00vyAxBvhE diff --git a/src/assets/icons/user.svg b/src/assets/icons/user.svg index b0614aa..229a8fa 100644 --- a/src/assets/icons/user.svg +++ b/src/assets/icons/user.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/src/components/DiscoverTestPage/SpeechBox.tsx b/src/components/DiscoverTestPage/SpeechBox.tsx index b7ceb1a..9104e07 100644 --- a/src/components/DiscoverTestPage/SpeechBox.tsx +++ b/src/components/DiscoverTestPage/SpeechBox.tsx @@ -20,7 +20,7 @@ export const SpeechBox = ({ children, isUser = false, isContinuous, isEnd }: Spe
) : isUser ? ( - + ) : ( profile diff --git a/src/components/MyPage/MyExperienceView.tsx b/src/components/MyPage/MyExperienceView.tsx index 35667fc..4c160ea 100644 --- a/src/components/MyPage/MyExperienceView.tsx +++ b/src/components/MyPage/MyExperienceView.tsx @@ -122,7 +122,7 @@ export const MyExperienceView = () => { const StyledContainer = styled.div` width: 100%; height: 100%; - padding-top: 81px; + padding-top: var(--top-navigation-height); padding-bottom: 52px; `; diff --git a/src/components/MyPage/MyPageSidebar.tsx b/src/components/MyPage/MyPageSidebar.tsx index 404561b..b1801bc 100644 --- a/src/components/MyPage/MyPageSidebar.tsx +++ b/src/components/MyPage/MyPageSidebar.tsx @@ -32,7 +32,7 @@ export const MypageSidebar = () => { const SidebarContainer = styled.div` width: 196px; height: auto; - padding-top: 81px; + padding-top: var(--top-navigation-height); background-color: ${({ theme }) => `${theme.color.white}`}; border-right: 2px ${({ theme }) => `${theme.color.gray150}`} solid; diff --git a/src/components/MyPage/PersonaView.tsx b/src/components/MyPage/PersonaView.tsx index 95d0725..7f10007 100644 --- a/src/components/MyPage/PersonaView.tsx +++ b/src/components/MyPage/PersonaView.tsx @@ -120,7 +120,7 @@ export const PersonaView = () => { }; const StyledContainer = styled.div` - padding-top: 81px; + padding-top: var(--top-navigation-height); `; const StyledInnerContainer = styled.div` diff --git a/src/components/MyPage/SettingView.tsx b/src/components/MyPage/SettingView.tsx index a6f7176..f6e869a 100644 --- a/src/components/MyPage/SettingView.tsx +++ b/src/components/MyPage/SettingView.tsx @@ -1,25 +1,382 @@ -import { styled } from 'styled-components'; +import { useEffect, useState } from 'react'; + +import styled, { css } from 'styled-components'; + +import { userAPI } from '@/apis/userAPI'; +import { ReactComponent as CheckIcon } from '@/assets/icons/check.svg'; +import { ReactComponent as UserIcon } from '@/assets/icons/user.svg'; +import { PlainButton } from '@/components/common/Button/PlainButton'; +import { ButtonInput } from '@/components/common/Input/ButtonInput'; +import { DefaultInput } from '@/components/common/Input/DefaultInput'; +import { tokenService } from '@/services/TokenService'; +import { userService } from '@/services/UserService'; +import { UserData } from '@/types/user.type'; -import backgroundImg from '@/assets/backgrounds/setting.png'; export const SettingView = () => { - return ( - - - - ); + const [checkAlarm, setCheckAlarm] = useState(false); + const [userInfo, setUserInfo] = useState(); + const [checkDuplicate, setCheckDuplicate] = useState(false); + const [isDuplicate, setIsDuplicate] = useState(false); + const [isSpecialCharacter, setIsSpecialCharacter] = useState(false); + + useEffect(() => { + userAPI.getUserInfo().then((res) => { + setUserInfo(res.payload); + }); + }, []); + + const handleInputChange = (e: React.ChangeEvent) => { + const { value } = e.target; + userInfo && setUserInfo({ ...userInfo, nickname: value }); + + setIsDuplicate(false); + setCheckDuplicate(false); + + if (value.match(/[^a-zA-Z0-9ㄱ-ㅎ가-힣ㅏ-ㅣ\s]/g) !== null) { + setIsSpecialCharacter(true); + } else { + setIsSpecialCharacter(false); + } + }; + + const handleCheckDuplicate = () => { + if (userInfo) { + userAPI + .duplicateCheck(userInfo.nickname) + .then(() => { + setIsDuplicate(false); + setCheckDuplicate(true); + }) + .catch(() => { + setIsDuplicate(true); + setCheckDuplicate(false); + }); + } + }; + + const handleLogout = async () => { + try { + await userAPI.logout(); + window.alert('로그아웃 되었습니다.'); + tokenService.onLogout(); + } catch (error) { + console.error(error); + } + }; + + const handleUpdate = async () => { + if (userInfo) { + try { + await userAPI.updateUserInfo({ + nickname: userInfo.nickname, + job: userInfo.job, + understanding_score: userInfo.understanding_score, + }); + userService.updateUserNickname(userInfo.nickname); + window.alert('수정되었습니다.'); + } catch (error) { + console.error(error); + } + } + }; + + if (userInfo) + return ( + + +
기본설정
+ +
+ + + +
+
+ 닉네임 설정 +

한글 8자, 영문 및 숫자 10자까지 입력 가능해요.

+
+
+ + {!isSpecialCharacter && !isDuplicate && !checkDuplicate && ( + 중복된 이름·특수문자 사용불가 + )} + {isSpecialCharacter && ( + 특수문자 사용 불가 + )} + {!isSpecialCharacter && isDuplicate && !checkDuplicate && ( + 닉네임 중복으로 사용 불가 + )} + {!isSpecialCharacter && !isDuplicate && checkDuplicate && ( + 중복 확인 완료 + )} +
+
+
+
+ 소셜 로그인 + +
+
+ 직업 + setUserInfo({ ...userInfo, job: e.target.value })} + /> +
+
+ 링크등록 + + +
+
+
+ + 로그아웃 + + + 수정하기 + +
+
+ + +
+
셀피스 멤버십
+
+ + 현재 이용 중인 멤버십이 없어요. +
+ 셀피스 프리미엄에 가입하고 더 다양한 경험을 누려보세요! +
+
+
+ + 셀피스 프리미엄 구독하러 가기 + +
+ +
결제수단 관리
+
+ 결제 수단 + 현재 등록되어 있는 결제 수단이 없어요. +
+ + 결제 수단 등록 + +
+ +
+
알림설정
+
+ 이메일 알림 받기 + { + setCheckAlarm((prev) => !prev); + }} + > + {checkAlarm && } + +
+
+ + 셀피스에서는 이벤트가 발생할 때 (예: 경험 신청이 완료되었을 때) 이메일을 통해 알림을 + 보낼 수 있습니다. 이 설정은 사용자의 설정에 의해 변경될 수 있습니다. + +
+
+
+ ); }; const StyledContainer = styled.div` - width: 100%; - height: 100%; - padding: 100px 24px 52px 24px; + padding: 24px; + padding-top: calc(var(--top-navigation-height) + 24px); + + display: flex; + gap: 24px; +`; + +const StyledSection = styled.div` + min-width: 506px; + padding: 20px; + + border-radius: 16px; + background: ${({ theme }) => theme.color.white}; + box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.13); + + .title { + ${({ theme }) => theme.font.desktop.body1b}; + color: ${({ theme }) => theme.color.gray700}; + } `; -const ImageContainer = styled.div` - width: 100%; +const StyledDefaultSetting = styled(StyledSection)` + flex: 1; + display: flex; + flex-direction: column; + gap: 20px; + + .button-container { + display: flex; + gap: 8px; + } +`; + +const StyledDefaultSettingContent = styled.div` height: 100%; - background-image: url(${backgroundImg}); - background-size: contain; - background-position: top; - background-repeat: no-repeat; + display: flex; + flex-direction: column; + gap: 20px; + + .profile { + display: flex; + gap: 16px; + } + + .section { + display: flex; + flex-direction: column; + gap: 8px; + + width: 100%; + span { + ${({ theme }) => theme.font.desktop.body2m}; + color: ${({ theme }) => theme.color.gray700}; + margin-bottom: 4px; + } + + .section-title { + display: flex; + justify-content: space-between; + align-items: center; + + p { + ${({ theme }) => theme.font.desktop.label2}; + color: ${({ theme }) => theme.color.gray400}; + } + } + } +`; + +const StyledCondition = styled.div<{ $warning?: boolean }>` + margin-top: 4px; + ${({ theme }) => theme.font.desktop.label2}; + + color: ${({ $warning, theme }) => ($warning ? theme.color.secondary500 : theme.color.gray600)}; +`; + +const StyledUserImage = styled.div` + width: fit-content; + padding: 24px; + + border-radius: 16px; + background: ${({ theme }) => theme.color.primary50}; + + path { + fill: ${({ theme }) => theme.color.primary500}; + } +`; + +const StyledMemberShipSetting = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 25px; +`; + +const StyledMemberShip = styled(StyledSection)` + height: 218px; + display: flex; + flex-direction: column; + justify-content: space-between; + + .content { + margin-top: 8px; + span { + ${({ theme }) => theme.font.desktop.label1r}; + color: ${({ theme }) => theme.color.gray500}; + } + } +`; + +const StyledPaymentMethod = styled(StyledSection)` + height: 218px; + + display: flex; + flex-direction: column; + justify-content: space-between; + + .content { + display: flex; + flex-direction: column; + gap: 4px; + + span:first-child { + ${({ theme }) => theme.font.desktop.body1m}; + color: ${({ theme }) => theme.color.gray800}; + } + + span:last-child { + ${({ theme }) => theme.font.desktop.label1r}; + color: ${({ theme }) => theme.color.gray500}; + } + } +`; + +const StyledAlarm = styled(StyledSection)` + height: 124px; + + .title-container { + display: flex; + justify-content: space-between; + margin-bottom: 16px; + } + + .alarm { + display: flex; + align-items: center; + gap: 8px; + + ${({ theme }) => theme.font.desktop.body2r}; + color: ${({ theme }) => theme.color.gray700}; + } + + .content { + ${({ theme }) => theme.font.desktop.label1r}; + color: ${({ theme }) => theme.color.gray500}; + } +`; + +const CheckBox = styled.button<{ $checked: boolean }>` + flex-shrink: 0; + width: 24px; + height: 24px; + + display: flex; + align-items: center; + justify-content: center; + + ${({ $checked, theme }) => + $checked + ? css` + background: ${theme.color.primary500}; + border: 1px solid ${theme.color.primary500}; + ` + : css` + background: ${theme.color.white}; + border: 1px solid ${theme.color.gray300}; + `} `; diff --git a/src/components/common/Input/DefaultInput.tsx b/src/components/common/Input/DefaultInput.tsx index d896db0..188b3ab 100644 --- a/src/components/common/Input/DefaultInput.tsx +++ b/src/components/common/Input/DefaultInput.tsx @@ -6,10 +6,11 @@ interface DefaultInputProps extends React.InputHTMLAttributes warning?: boolean; children?: React.ReactNode; width?: string | number; // 타입 수정 + disabled?: boolean; } export const DefaultInput = forwardRef( - ({ warning = false, width, children, ...props }, ref) => { + ({ warning = false, width, children, disabled = false, ...props }, ref) => { const [isFocused, setIsFocused] = useState(false); const handleFocus = () => { @@ -21,8 +22,15 @@ export const DefaultInput = forwardRef( }; return ( - - + + {children} ); @@ -33,6 +41,7 @@ const StyledContainer = styled.div<{ $warning: boolean; $focused: boolean; $width: string | number | undefined; // 타입 수정 + $disabled: boolean; }>` display: flex; align-items: center; @@ -74,4 +83,13 @@ const StyledContainer = styled.div<{ color: ${({ theme }) => theme.color.secondary500}; } `} + + ${({ $disabled, theme }) => + $disabled && + css` + background: ${theme.color.gray100}; + input { + color: ${theme.color.gray300}; + } + `} `; diff --git a/src/components/common/Navigation/TopNavigation.tsx b/src/components/common/Navigation/TopNavigation.tsx index 592be97..96160a3 100644 --- a/src/components/common/Navigation/TopNavigation.tsx +++ b/src/components/common/Navigation/TopNavigation.tsx @@ -40,7 +40,7 @@ export const TopNavigation = () => { ))} {loggedIn ? ( navigate('/mypage')}> - + ) : ( theme.color.primary100}; background: rgba(255, 255, 255, 0.6); diff --git a/src/styles/global/zoom.css b/src/styles/global/zoom.css index bff8265..3a2fb03 100644 --- a/src/styles/global/zoom.css +++ b/src/styles/global/zoom.css @@ -2,4 +2,5 @@ zoom: 0.8; --full-height: 125vh; --full-width: 125vw; + --top-navigation-height: 77px; } \ No newline at end of file diff --git a/src/types/user.type.ts b/src/types/user.type.ts index 5f6444b..060a873 100644 --- a/src/types/user.type.ts +++ b/src/types/user.type.ts @@ -10,3 +10,12 @@ export interface OnboardingRequest { interest_list: string[]; keyword_list: string[]; } + +export interface UserData { + name: string; + provider: string; + nickname: string; + job: string; + understanding_score: number; + profile_img_url: string; +} diff --git a/src/types/userAPI.type.ts b/src/types/userAPI.type.ts index e733541..7e6fe33 100644 --- a/src/types/userAPI.type.ts +++ b/src/types/userAPI.type.ts @@ -1,7 +1,10 @@ -export interface RegisterRequest { +export interface ReRegisterRequest { nickname: string; job: string; understanding_score: number; +} + +export interface RegisterRequest extends ReRegisterRequest { interest_list: string[]; keyword_list: string[]; } From f72065b827a68a11b692f110706a8717d7d2ae59 Mon Sep 17 00:00:00 2001 From: aaminha Date: Thu, 6 Jun 2024 01:14:22 +0900 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20=EC=9E=90=EA=B8=B0=EC=9D=B4?= =?UTF-8?q?=ED=95=B4=20=ED=99=88=EC=97=90=EC=84=9C=20Discover=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EB=B7=B0=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20api=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiscoverResultPage/ResultView.tsx | 405 ++++++++++++++++++ .../SelfUnderstandPage/DiscoverResultView.tsx | 4 +- src/pages/DiscoverResultPage.tsx | 393 +---------------- 3 files changed, 409 insertions(+), 393 deletions(-) create mode 100644 src/components/DiscoverResultPage/ResultView.tsx diff --git a/src/components/DiscoverResultPage/ResultView.tsx b/src/components/DiscoverResultPage/ResultView.tsx new file mode 100644 index 0000000..7620773 --- /dev/null +++ b/src/components/DiscoverResultPage/ResultView.tsx @@ -0,0 +1,405 @@ +import { useEffect, useState } from 'react'; + +import { useNavigate } from 'react-router-dom'; +import styled, { css } from 'styled-components'; + +import { personaAPI } from '@/apis/personaAPI'; +import { SummaryCard } from '@/components/DiscoverTestPage/SummaryCard'; +import { CategoryButton } from '@/components/common/Button/CategoryButton'; +import { PlainButton } from '@/components/common/Button/PlainButton'; +import { userService } from '@/services/UserService'; +import { moveDown, moveLeft, moveRight, moveUp } from '@/styles'; + +const CATEGORY_LIST: { [key: string]: string } = { + all: '전체', + health: '건강', + career: '커리어', + love: '사랑', + leisure: '여가', +}; + +export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) => { + const [selectedCategory, setSelectedCategory] = useState('all'); + const [categoryKeywords, setCategoryKeywords] = useState<{ [key: string]: string[] }>({ + all: [], + health: [], + career: [], + love: [], + leisure: [], + }); + const [summary, setSummary] = useState<{ [key: string]: string[] } | undefined>(undefined); + const navigate = useNavigate(); + + useEffect(() => { + const fetchAllKeywords = async () => { + try { + const response = await personaAPI.getDiscoverAllKeyword(); + setCategoryKeywords({ ...categoryKeywords, all: response.payload.keywords }); + } catch (error) { + console.error('Failed to fetch all keywords:', error); + } + }; + + const fetchSummary = async () => { + try { + const response = await personaAPI.getDefaultSummary(); + setSummary(response.payload); + } catch (error) { + console.error('Failed to fetch summary:', error); + } + }; + + fetchAllKeywords(); + fetchSummary(); + }, []); + + useEffect(() => { + const fetchCategoryKeywords = async (category: string) => { + if (categoryKeywords[category]) { + try { + const response = await personaAPI.getDiscoverCategoryKeyword(category); + setCategoryKeywords((prev) => ({ + ...prev, + [category]: response.payload.keywords, // Assuming the payload has a keywords array + })); + } catch (error) { + console.error(`Failed to fetch keywords for category ${category}:`, error); + } + } + }; + + if (selectedCategory !== 'all') { + fetchCategoryKeywords(selectedCategory); + } + }, [selectedCategory, categoryKeywords]); + + return ( + + + +
이해하기 테스트의 결과에요!
+
+ 셀피스에서 분석한 지금까지의{' '} + {userService.getUserNickname()}님 + 은, 이런 사람이에요. +
+ + {Object.keys(CATEGORY_LIST).map((category) => ( + setSelectedCategory(category)} + > + {CATEGORY_LIST[category]} + + ))} + +
+ + {categoryKeywords[selectedCategory].length !== 0 ? ( +
+ {showSummary && ( +
+ 상위 6개만 보여주고 있어요! +
+ )} + + {categoryKeywords[selectedCategory].map((keyword, index) => ( + + {keyword} + + ))} + + {!showSummary && ( +
+ 상위 6개만 보여주고 있어요! +
+ )} + {showSummary && ( + +
{`${userService.getUserNickname()}님과의 대화한 내용을 요약했어요!`}
+
+ {selectedCategory === 'all' && + summary && + Object.values(summary) + .map((arr) => (arr.length > 0 ? arr[0] : '')) + .map( + (item, index) => + item !== null && + item !== '' && ( + + ) + )} + {selectedCategory !== 'all' && + summary && + summary[selectedCategory].map((item) => ( + + ))} +
+
+ )} +
+ ) : ( +
+ +
+ 아직 {CATEGORY_LIST[selectedCategory]} 테스트를 + 완료하지 않았어요! +
+
남은 테스트를 진행해주세요.
+
+ + ? + +
+ )} +
+ {categoryKeywords[selectedCategory].length === 0 && ( + { + navigate('/test/discover'); + }} + style={{ marginTop: '56px' }} + > + 테스트 이어하기 + + )} +
+
+ ); +}; + +const StyledContainer = styled.div<{ $showSummary: boolean }>` + padding-top: ${({ $showSummary }) => ($showSummary ? '76px' : '9px')}; + min-width: 100%; + + display: flex; + justify-content: center; + background: linear-gradient(180deg, rgba(255, 255, 255, 0) 53.94%, #ccb3fd 100%), #fff; +`; + +const StyledInnerContainer = styled.div` + width: 1280px; + padding: 40px 0; + + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; +`; + +const StyledHeader = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + .notice { + ${({ theme }) => theme.font.desktop.body1b}; + color: ${({ theme }) => theme.color.gray400}; + margin-bottom: 10px; + } + + .title { + ${({ theme }) => theme.font.desktop.title1}; + color: ${({ theme }) => theme.color.gray800}; + } + + .highlight { + color: ${({ theme }) => theme.color.primary500}; + } +`; + +const StyledChipContainer = styled.div` + padding-top: 24px; + display: flex; + gap: 8px; +`; +const StyledContent = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 36px; + + .highlight { + color: ${({ theme }) => theme.color.primary500}; + } + + .no-result { + display: flex; + flex-direction: column; + align-items: center; + } + + .result { + text-align: center; + margin: 24px 0; + + .description { + ${({ theme }) => theme.font.desktop.body1m}; + color: ${({ theme }) => theme.color.gray800}; + margin-bottom: 24px; + } + } +`; + +const BubbleSection = styled.div<{ $noResult: boolean }>` + width: 800px; + height: 350px; + padding: 0 38px; + margin: 0 auto; + position: relative; + + ${({ $noResult }) => $noResult && css``} +`; + +const ResultSection = styled.div` + .title { + ${({ theme }) => theme.font.desktop.body1b}; + color: ${({ theme }) => theme.color.gray800}; + margin-bottom: 24px; + margin-top: 36px; + } + + .card-container { + display: flex; + gap: 12px; + + > * { + flex: 1; + } + } +`; + +const StyledNoBubble = styled.div` + width: 184px; + height: 184px; + + display: flex; + justify-content: center; + align-items: center; + + text-align: center; + color: ${({ theme }) => theme.color.primary800}; + ${({ theme }) => theme.font.desktop.body1b}; + + border-radius: 50%; + background: radial-gradient( + 50% 50% at 50% 50%, + rgba(255, 255, 255, 0) 76%, + rgba(204, 179, 253, 0.4) 100% + ); + + box-shadow: + -0.319px 51.263px 151.773px 0px rgba(48, 13, 115, 0.12), + -0.04px 6.444px 21.116px 0px rgba(48, 13, 115, 0.06); + + animation: ${moveUp} 3000ms ease-in-out infinite; +`; + +const StyledBubble = styled.div<{ $weight: number }>` + width: ${({ $weight }) => $weight * 244}px; + height: ${({ $weight }) => $weight * 244}px; + + display: flex; + justify-content: center; + align-items: center; + will-change: transform; + + text-align: center; + color: ${({ theme }) => theme.color.primary800}; + ${({ theme }) => theme.font.desktop.body1b}; + + position: absolute; + + border-radius: 50%; + background: radial-gradient( + 50% 50% at 50% 50%, + rgba(255, 255, 255, 0) 76%, + rgba(204, 179, 253, 0.4) 100% + ); + + box-shadow: + -0.319px 51.263px 151.773px 0px rgba(48, 13, 115, 0.12), + -0.04px 6.444px 21.116px 0px rgba(48, 13, 115, 0.06); + + &.b0 { + bottom: 7%; + right: 15%; + + animation: + ${moveDown} 2000ms ease-in-out infinite, + ${moveLeft} 3000ms ease-in-out infinite; + } + + &.b1 { + top: 5%; + left: 30%; + + animation: + ${moveDown} 3000ms ease-in-out infinite, + ${moveRight} 2000ms ease-in-out infinite; + } + + &.b2 { + bottom: 6%; + left: 15%; + + animation: + ${moveUp} 2500ms ease-in-out infinite, + ${moveLeft} 3000ms ease-in-out infinite; + } + + &.b3 { + top: 4%; + left: 4%; + + animation: + ${moveUp} 2300ms ease-in-out infinite, + ${moveLeft} 2700ms ease-in-out infinite; + } + + &.b4 { + top: 10%; + right: 5%; + + animation: + ${moveDown} 2500ms ease-in-out infinite, + ${moveRight} 2300ms ease-in-out infinite; + } + + &.b5 { + bottom: 10%; + right: 5%; + + animation: + ${moveUp} 2600ms ease-in-out infinite, + ${moveRight} 3000ms ease-in-out infinite; + } + + &.sole { + background: green; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +`; + +const NoResultText = styled.div` + text-align: center; + margin-top: 56px; + margin-bottom: 24px; + + .title { + ${({ theme }) => theme.font.desktop.title2}; + color: ${({ theme }) => theme.color.gray700}; + } + + .subtitle { + ${({ theme }) => theme.font.desktop.body1m}; + color: ${({ theme }) => theme.color.gray500}; + } +`; diff --git a/src/components/SelfUnderstandPage/DiscoverResultView.tsx b/src/components/SelfUnderstandPage/DiscoverResultView.tsx index 139bd05..c97537b 100644 --- a/src/components/SelfUnderstandPage/DiscoverResultView.tsx +++ b/src/components/SelfUnderstandPage/DiscoverResultView.tsx @@ -3,8 +3,8 @@ import { useEffect, useState } from 'react'; import axios from 'axios'; import { personaAPI } from '@/apis/personaAPI'; +import { ResultView } from '@/components/DiscoverResultPage/ResultView'; import { NoResultSection } from '@/components/SelfUnderstandPage/NoResultTemplate'; -import { DiscoverResultPage } from '@/pages/DiscoverResultPage'; import { userService } from '@/services/UserService'; export const DiscoverResultView = () => { @@ -28,7 +28,7 @@ export const DiscoverResultView = () => { }, []); if (isTest) { - return ; + return ; } return ; diff --git a/src/pages/DiscoverResultPage.tsx b/src/pages/DiscoverResultPage.tsx index f210c61..e117a9f 100644 --- a/src/pages/DiscoverResultPage.tsx +++ b/src/pages/DiscoverResultPage.tsx @@ -1,394 +1,5 @@ -import { useEffect, useState } from 'react'; - -import { useNavigate } from 'react-router-dom'; -import styled, { css } from 'styled-components'; - -import { personaAPI } from '@/apis/personaAPI'; -import { SummaryCard } from '@/components/DiscoverTestPage/SummaryCard'; -import { CategoryButton } from '@/components/common/Button/CategoryButton'; -import { PlainButton } from '@/components/common/Button/PlainButton'; -import { userService } from '@/services/UserService'; -import { moveDown, moveLeft, moveRight, moveUp } from '@/styles'; - -const CATEGORY_LIST: { [key: string]: string } = { - all: '전체', - health: '건강', - career: '커리어', - love: '사랑', - leisure: '여가', -}; +import { ResultView } from '@/components/DiscoverResultPage/ResultView'; export const DiscoverResultPage = () => { - const [selectedCategory, setSelectedCategory] = useState('all'); - const [categoryKeywords, setCategoryKeywords] = useState<{ [key: string]: string[] }>({ - all: [], - health: [], - career: [], - love: [], - leisure: [], - }); - const [summary, setSummary] = useState<{ [key: string]: string[] } | undefined>(undefined); - const navigate = useNavigate(); - - useEffect(() => { - const fetchAllKeywords = async () => { - try { - const response = await personaAPI.getDiscoverAllKeyword(); - setCategoryKeywords({ ...categoryKeywords, all: response.payload.keywords }); - } catch (error) { - console.error('Failed to fetch all keywords:', error); - } - }; - - const fetchSummary = async () => { - try { - const response = await personaAPI.getDefaultSummary(); - setSummary(response.payload); - } catch (error) { - console.error('Failed to fetch summary:', error); - } - }; - - fetchAllKeywords(); - fetchSummary(); - }, []); - - useEffect(() => { - const fetchCategoryKeywords = async (category: string) => { - if (!categoryKeywords[category]) { - try { - const response = await personaAPI.getDiscoverCategoryKeyword(category); - setCategoryKeywords((prev) => ({ - ...prev, - [category]: response.payload.keywords, // Assuming the payload has a keywords array - })); - } catch (error) { - console.error(`Failed to fetch keywords for category ${category}:`, error); - } - } - }; - - if (selectedCategory !== 'all') { - fetchCategoryKeywords(selectedCategory); - } - }, [selectedCategory, categoryKeywords]); - - return ( - - - -
이해하기 테스트의 결과에요!
-
- 셀피스에서 분석한 지금까지의{' '} - {userService.getUserNickname()}님 - 은, 이런 사람이에요. -
- - {Object.keys(CATEGORY_LIST).map((category) => ( - setSelectedCategory(category)} - > - {CATEGORY_LIST[category]} - - ))} - -
- - {categoryKeywords[selectedCategory].length === 0 ? ( -
- -
- 아직 {CATEGORY_LIST[selectedCategory]} 테스트를 - 완료하지 않았어요! -
-
남은 테스트를 진행해주세요.
-
- - ? - -
- ) : ( -
-
- 상위 6개만 보여주고 있어요! -
- - {categoryKeywords[selectedCategory].map((keyword, index) => ( - - {keyword} - - ))} - - -
{`${userService.getUserNickname()}님과의 대화한 내용을 요약했어요!`}
-
- {selectedCategory === 'all' && - summary && - Object.values(summary) - .map((arr) => (arr.length > 0 ? arr[0] : '')) - .map( - (item, index) => - item !== null && - item !== '' && ( - - ) - )} - {selectedCategory !== 'all' && - summary && - summary[selectedCategory].map((item) => ( - - ))} -
-
-
- )} -
- {categoryKeywords[selectedCategory].length === 0 && ( - { - navigate('/test/discover'); - }} - > - 테스트 이어하기 - - )} -
-
- ); + return ; }; - -const StyledContainer = styled.div` - padding-top: 76px; - min-height: var(--full-height); - - display: flex; - justify-content: center; - background: linear-gradient(180deg, rgba(255, 255, 255, 0) 53.94%, #ccb3fd 100%), #fff; -`; - -const StyledInnerContainer = styled.div` - width: 1280px; - padding: 40px 0; - - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; -`; - -const StyledHeader = styled.div` - display: flex; - flex-direction: column; - align-items: center; - - .notice { - ${({ theme }) => theme.font.desktop.body1b}; - color: ${({ theme }) => theme.color.gray400}; - margin-bottom: 10px; - } - - .title { - ${({ theme }) => theme.font.desktop.title1}; - color: ${({ theme }) => theme.color.gray800}; - } - - .highlight { - color: ${({ theme }) => theme.color.primary500}; - } -`; - -const StyledChipContainer = styled.div` - padding-top: 24px; - display: flex; - gap: 8px; -`; -const StyledContent = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 36px; - - .highlight { - color: ${({ theme }) => theme.color.primary500}; - } - - .no-result { - display: flex; - flex-direction: column; - align-items: center; - } - - .result { - text-align: center; - margin: 24px 0; - - .description { - ${({ theme }) => theme.font.desktop.body1m}; - color: ${({ theme }) => theme.color.gray800}; - margin-bottom: 24px; - } - } -`; - -const BubbleSection = styled.div<{ $noResult: boolean }>` - width: 800px; - height: 350px; - padding: 0 38px; - margin: 0 auto; - position: relative; - - ${({ $noResult }) => $noResult && css``} -`; - -const ResultSection = styled.div` - .title { - ${({ theme }) => theme.font.desktop.body1b}; - color: ${({ theme }) => theme.color.gray800}; - margin-bottom: 24px; - margin-top: 36px; - } - - .card-container { - display: flex; - gap: 12px; - - > * { - flex: 1; - } - } -`; - -const StyledNoBubble = styled.div` - width: 184px; - height: 184px; - - display: flex; - justify-content: center; - align-items: center; - - text-align: center; - color: ${({ theme }) => theme.color.primary800}; - ${({ theme }) => theme.font.desktop.body1b}; - - border-radius: 50%; - background: radial-gradient( - 50% 50% at 50% 50%, - rgba(255, 255, 255, 0) 76%, - rgba(204, 179, 253, 0.4) 100% - ); - - box-shadow: - -0.319px 51.263px 151.773px 0px rgba(48, 13, 115, 0.12), - -0.04px 6.444px 21.116px 0px rgba(48, 13, 115, 0.06); - - animation: ${moveUp} 3000ms ease-in-out infinite; -`; - -const StyledBubble = styled.div<{ $weight: number }>` - width: ${({ $weight }) => $weight * 244}px; - height: ${({ $weight }) => $weight * 244}px; - - display: flex; - justify-content: center; - align-items: center; - will-change: transform; - - text-align: center; - color: ${({ theme }) => theme.color.primary800}; - ${({ theme }) => theme.font.desktop.body1b}; - - position: absolute; - - border-radius: 50%; - background: radial-gradient( - 50% 50% at 50% 50%, - rgba(255, 255, 255, 0) 76%, - rgba(204, 179, 253, 0.4) 100% - ); - - box-shadow: - -0.319px 51.263px 151.773px 0px rgba(48, 13, 115, 0.12), - -0.04px 6.444px 21.116px 0px rgba(48, 13, 115, 0.06); - - &.b0 { - bottom: 7%; - right: 15%; - - animation: - ${moveDown} 2000ms ease-in-out infinite, - ${moveLeft} 3000ms ease-in-out infinite; - } - - &.b1 { - top: 5%; - left: 30%; - - animation: - ${moveDown} 3000ms ease-in-out infinite, - ${moveRight} 2000ms ease-in-out infinite; - } - - &.b2 { - bottom: 6%; - left: 15%; - - animation: - ${moveUp} 2500ms ease-in-out infinite, - ${moveLeft} 3000ms ease-in-out infinite; - } - - &.b3 { - top: 4%; - left: 4%; - - animation: - ${moveUp} 2300ms ease-in-out infinite, - ${moveLeft} 2700ms ease-in-out infinite; - } - - &.b4 { - top: 10%; - right: 5%; - - animation: - ${moveDown} 2500ms ease-in-out infinite, - ${moveRight} 2300ms ease-in-out infinite; - } - - &.b5 { - bottom: 10%; - right: 5%; - - animation: - ${moveUp} 2600ms ease-in-out infinite, - ${moveRight} 3000ms ease-in-out infinite; - } - - &.sole { - background: green; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } -`; - -const NoResultText = styled.div` - text-align: center; - margin-bottom: 24px; - - .title { - ${({ theme }) => theme.font.desktop.title2}; - color: ${({ theme }) => theme.color.gray700}; - } - - .subtitle { - ${({ theme }) => theme.font.desktop.body1m}; - color: ${({ theme }) => theme.color.gray500}; - } -`; From eb6737afb4b6ea71e21a02d45b5241c37ee4456e Mon Sep 17 00:00:00 2001 From: aaminha Date: Thu, 6 Jun 2024 01:28:24 +0900 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20styled-components=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DiscoverResultPage/ResultView.tsx | 3 ++- .../ExperienceRecommendPage/AmountModal.tsx | 4 ++-- .../SelfUnderstandPage/SelfUnderstandCard.tsx | 22 ++++++++++++------- .../SelfUnderstandPage/TestResultSection.tsx | 2 +- .../common/Modal/BrandCardModal.tsx | 4 ++-- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/components/DiscoverResultPage/ResultView.tsx b/src/components/DiscoverResultPage/ResultView.tsx index 7620773..097c59c 100644 --- a/src/components/DiscoverResultPage/ResultView.tsx +++ b/src/components/DiscoverResultPage/ResultView.tsx @@ -86,6 +86,7 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = {Object.keys(CATEGORY_LIST).map((category) => ( setSelectedCategory(category)} > @@ -135,7 +136,7 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = {selectedCategory !== 'all' && summary && summary[selectedCategory].map((item) => ( - + ))}
diff --git a/src/components/ExperienceRecommendPage/AmountModal.tsx b/src/components/ExperienceRecommendPage/AmountModal.tsx index 43003eb..fe519d5 100644 --- a/src/components/ExperienceRecommendPage/AmountModal.tsx +++ b/src/components/ExperienceRecommendPage/AmountModal.tsx @@ -23,7 +23,7 @@ export const AmountModal = ({ isOpen, onApply, onClose }: AmountModalProps) => { }; return ( - + 금액 선택 @@ -56,7 +56,7 @@ export const AmountModal = ({ isOpen, onApply, onClose }: AmountModalProps) => { ); }; -const ModalOverlay = styled.div<{ isOpen: boolean }>` +const ModalOverlay = styled.div<{ $isOpen: boolean }>` position: fixed; top: 0; left: 0; diff --git a/src/components/SelfUnderstandPage/SelfUnderstandCard.tsx b/src/components/SelfUnderstandPage/SelfUnderstandCard.tsx index 1df2ac2..f63a814 100644 --- a/src/components/SelfUnderstandPage/SelfUnderstandCard.tsx +++ b/src/components/SelfUnderstandPage/SelfUnderstandCard.tsx @@ -24,13 +24,13 @@ interface OutlineProps { } const Container = styled.button<{ - background?: string; - width?: string; - height?: string; + $background?: string; + $width?: string; + $height?: string; }>` position: relative; - width: ${(props) => props.width || '100%'}; - height: ${(props) => props.height || '100%'}; + width: ${(props) => props.$width || '100%'}; + height: ${(props) => props.$height || '100%'}; padding: 24px; filter: drop-shadow(0px 4px 10px rgba(87, 11, 255, 0.15)); border-radius: 16px; @@ -38,7 +38,7 @@ const Container = styled.button<{ flex-direction: column; justify-content: space-between; align-items: flex-start; - background: ${(props) => props.background || 'transparent'}; + background: ${(props) => props.$background || 'transparent'}; `; const Header = styled.div` @@ -95,7 +95,13 @@ const Outline = ({ height, onClick, }: OutlineProps) => ( - +
{title} @@ -191,4 +197,4 @@ const DiscoverComponent = () => { ); }; -export { DesignComponent, DefineComponent, DiscoverComponent }; +export { DefineComponent, DesignComponent, DiscoverComponent }; diff --git a/src/components/SelfUnderstandPage/TestResultSection.tsx b/src/components/SelfUnderstandPage/TestResultSection.tsx index 655b638..7907ac0 100644 --- a/src/components/SelfUnderstandPage/TestResultSection.tsx +++ b/src/components/SelfUnderstandPage/TestResultSection.tsx @@ -44,7 +44,7 @@ const StyledContainer = styled.section` position: relative; background: ${({ theme }) => theme.color.white}; - padding-top: 84px; + padding-top: 81px; display: flex; flex-direction: column; diff --git a/src/components/common/Modal/BrandCardModal.tsx b/src/components/common/Modal/BrandCardModal.tsx index e2123b6..d8c35fb 100644 --- a/src/components/common/Modal/BrandCardModal.tsx +++ b/src/components/common/Modal/BrandCardModal.tsx @@ -43,7 +43,7 @@ export const BrandCardModal = ({ isOpen, onClose, onAdd }: ModalProps) => { if (!isOpen) return null; return ( - <StyledContainer isOpen={isOpen}> + <StyledContainer $isOpen={isOpen}> <Container> <Title type="text" @@ -122,7 +122,7 @@ export const BrandCardModal = ({ isOpen, onClose, onAdd }: ModalProps) => { ); }; -const StyledContainer = styled.div<{ isOpen: boolean }>` +const StyledContainer = styled.div<{ $isOpen: boolean }>` position: fixed; top: 0; left: 0; From 06ea1e994fda0260623a8cbb60028abff996b792 Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Thu, 6 Jun 2024 07:07:47 +0900 Subject: [PATCH 13/20] =?UTF-8?q?refactor:=20Define=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefineStartPage/DefineDesktopView.tsx | 77 +++--- .../DefineStartPage/DefineMobileView.tsx | 68 +++--- .../DefineTest/DefineButtonView.tsx | 229 ------------------ src/components/DefineTest/DefineChip.tsx | 186 -------------- src/components/DefineTest/DefineTestView.tsx | 80 ------ src/components/DefineTest/DefineTextView.tsx | 148 ----------- .../DefineTestPage/StartSection.tsx} | 8 +- src/components/DefineTestPage/TestSection.tsx | 179 ++++++++++++++ .../DefineTestPage/TestSectionTitle.tsx | 86 +++++++ .../DefineTestPage/ToastMessage.tsx | 76 ++++++ src/components/common/Chip/KeywordChip.tsx | 4 +- src/components/common/ProgressBar/index.tsx | 21 +- src/hooks/useSessionStorage.ts | 25 ++ src/pages/DefineTestPage.tsx | 132 ++++++++-- src/pages/RedirectPage.tsx | 2 + src/recoil/toastState.ts | 6 + src/routers/router.tsx | 8 +- src/services/TokenService.ts | 1 + 18 files changed, 570 insertions(+), 766 deletions(-) delete mode 100644 src/components/DefineTest/DefineButtonView.tsx delete mode 100644 src/components/DefineTest/DefineChip.tsx delete mode 100644 src/components/DefineTest/DefineTestView.tsx delete mode 100644 src/components/DefineTest/DefineTextView.tsx rename src/{pages/DefineStartPage.tsx => components/DefineTestPage/StartSection.tsx} (68%) create mode 100644 src/components/DefineTestPage/TestSection.tsx create mode 100644 src/components/DefineTestPage/TestSectionTitle.tsx create mode 100644 src/components/DefineTestPage/ToastMessage.tsx create mode 100644 src/hooks/useSessionStorage.ts create mode 100644 src/recoil/toastState.ts diff --git a/src/components/DefineStartPage/DefineDesktopView.tsx b/src/components/DefineStartPage/DefineDesktopView.tsx index 54e875e..c34c483 100644 --- a/src/components/DefineStartPage/DefineDesktopView.tsx +++ b/src/components/DefineStartPage/DefineDesktopView.tsx @@ -1,50 +1,42 @@ -import { useNavigate } from 'react-router-dom'; import { styled } from 'styled-components'; import backgroundImg from '@/assets/backgrounds/defineBackground.png'; import { PlainButton } from '@/components/common/Button/PlainButton'; -export const DefineDesktopView = () => { - const navigate = useNavigate(); - - const handleClick = () => { - navigate('/test/define/2'); - }; +export const DefineDesktopView = ({ onNext }: { onNext: () => void }) => { return ( - <div> - <ViewContainer> - <Styled1Container> - <TopContainer> - <TitleTextContainer> - <TitleContainer> - <br /> - 현재 당신은 어떤 사람인가요? - </TitleContainer> - <SubTitleContainer> - 문항은 <span className="highlight">총 3문항</span>으로, 홀랜드 검사 이론을 기반으로 - 구성되어 있어요. - <br /> - 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} - <span className="highlight">결과 카드</span>를 받아보세요! - </SubTitleContainer> - </TitleTextContainer> - <PlainButton variant="gray" width="376px" height="48px" onClick={handleClick}> - 테스트 시작하기 - </PlainButton> - </TopContainer> - - <Styled2Container> - <SubTextContainer> - <span className="highlight">홀랜드 검사란,</span> + <ViewContainer> + <Styled1Container> + <TopContainer> + <TitleTextContainer> + <TitleContainer> <br /> - 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, + 현재 당신은 어떤 사람인가요? + </TitleContainer> + <SubTitleContainer> + 문항은 <span className="highlight">총 3문항</span>으로, 홀랜드 검사 이론을 기반으로 + 구성되어 있어요. <br /> - 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. - </SubTextContainer> - </Styled2Container> - </Styled1Container> - </ViewContainer> - </div> + 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} + <span className="highlight">결과 카드</span>를 받아보세요! + </SubTitleContainer> + </TitleTextContainer> + <PlainButton variant="gray" width="376px" height="48px" onClick={onNext}> + 테스트 시작하기 + </PlainButton> + </TopContainer> + + <Styled2Container> + <SubTextContainer> + <span className="highlight">홀랜드 검사란,</span> + <br /> + 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, + <br /> + 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. + </SubTextContainer> + </Styled2Container> + </Styled1Container> + </ViewContainer> ); }; @@ -82,6 +74,9 @@ const TopContainer = styled.div` align-items: flex-start; gap: 48px; display: flex; + + transform: scale(0.8); + transform-origin: top left; `; const SubTextContainer = styled.div` @@ -111,11 +106,13 @@ const Styled2Container = styled.div` `; export const ViewContainer = styled.div` - height: var(--full-height); + height: 100vh; background-image: url(${backgroundImg}); background-size: cover; background-position: right; display: flex; overflow-x: auto; + + zoom: 1.25; `; diff --git a/src/components/DefineStartPage/DefineMobileView.tsx b/src/components/DefineStartPage/DefineMobileView.tsx index 2b0877a..f41dfca 100644 --- a/src/components/DefineStartPage/DefineMobileView.tsx +++ b/src/components/DefineStartPage/DefineMobileView.tsx @@ -1,45 +1,37 @@ -import { useNavigate } from 'react-router-dom'; import { styled } from 'styled-components'; import backgroundImg from '@/assets/backgrounds/defineBackground.png'; import { PlainButton } from '@/components/common/Button/PlainButton'; -export const DefineMobileView = () => { - const navigate = useNavigate(); - - const handleClick = () => { - navigate('/test/define/2'); - }; +export const DefineMobileView = ({ onNext }: { onNext: () => void }) => { return ( - <div> - <ViewContainer> - <StyledContainer> - <TopContainer> - <TitleTextContainer> - <TitleContainer>현재 당신은 어떤 사람인가요?</TitleContainer> - <SubTitleContainer> - 문항은 <span className="highlight">총 3문항</span>으로, - <br /> 홀랜드 검사 이론을 기반으로 구성되어 있어요. - <br /> - 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} - <span className="highlight">결과 카드</span>를 받아보세요! - </SubTitleContainer> - </TitleTextContainer> - <BottomContainer> - <SubTextContainer> - <span className="highlight">홀랜드 검사란,</span> - <br /> - 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, - 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. - </SubTextContainer> - <PlainButton variant="gray" width="100%" height="48px" onClick={handleClick}> - 테스트 시작하기 - </PlainButton> - </BottomContainer> - </TopContainer> - </StyledContainer> - </ViewContainer> - </div> + <ViewContainer> + <StyledContainer> + <TopContainer> + <TitleTextContainer> + <TitleContainer>현재 당신은 어떤 사람인가요?</TitleContainer> + <SubTitleContainer> + 문항은 <span className="highlight">총 3문항</span>으로, + <br /> 홀랜드 검사 이론을 기반으로 구성되어 있어요. + <br /> + 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} + <span className="highlight">결과 카드</span>를 받아보세요! + </SubTitleContainer> + </TitleTextContainer> + <BottomContainer> + <SubTextContainer> + <span className="highlight">홀랜드 검사란,</span> + <br /> + 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, + 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. + </SubTextContainer> + <PlainButton variant="gray" width="100%" height="48px" onClick={onNext}> + 테스트 시작하기 + </PlainButton> + </BottomContainer> + </TopContainer> + </StyledContainer> + </ViewContainer> ); }; @@ -129,9 +121,11 @@ const ViewContainer = styled.div` justify-content: center; align-items: center; width: 100%; - height: var(--full-height); + height: 100vh; min-height: 700px; background-image: url(${backgroundImg}); background-size: cover; background-position: center; + + zoom: 1.25; `; diff --git a/src/components/DefineTest/DefineButtonView.tsx b/src/components/DefineTest/DefineButtonView.tsx deleted file mode 100644 index 554aaeb..0000000 --- a/src/components/DefineTest/DefineButtonView.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import { useEffect, useState } from 'react'; - -import { useNavigate } from 'react-router-dom'; -import { useRecoilState } from 'recoil'; -import styled from 'styled-components'; - -import { personaAPI } from '@/apis/personaAPI'; -import { PlainButton } from '@/components/common/Button/PlainButton'; -import { loadingHandlerState } from '@/recoil/loadingHandlerState'; -import { loadingState } from '@/recoil/loadingState'; -import { userService } from '@/services/UserService'; - -interface Props { - warning?: boolean; - warningMessage?: boolean; -} - -const Container = styled.div` - position: relative; - width: 100%; - - display: flex; - flex-direction: column; - align-items: center; - gap: 15px; -`; - -const ButtonContainer = styled.div` - width: 100%; - display: flex; - gap: 15px; - - @media ${({ theme }) => theme.device.tablet} { - gap: 8px; - } - - @media ${({ theme }) => theme.device.mobile} { - gap: 8px; - } -`; - -const ChipContainer = styled.div` - position: absolute; - - top: -16px; - left: 50%; - - transform: translate(-50%, -100%); - - width: max-content; - padding: 8px 20px; - border-radius: 8px; - border: 1px solid ${({ theme }) => `${theme.color.secondary600}`}; - background: ${({ theme }) => `${theme.color.secondary50}`}; - - color: ${({ theme }) => `${theme.color.secondary600}`}; - ${({ theme }) => theme.font.desktop.label1m}; -`; - -const Text = styled.div` - color: ${({ theme }) => `${theme.color.gray400}`}; - ${({ theme }) => theme.font.desktop.label1m}; - word-wrap: break-word; -`; - -export const DefineButtonView1 = ({ warning, warningMessage }: Props) => { - const navigate = useNavigate(); - const [showWarn, setShowWarn] = useState(false); - - const handleButtonClick = () => { - navigate('/test/define/3'); - }; - - useEffect(() => { - if (warningMessage) { - setShowWarn(true); - const timer = setTimeout(() => { - setShowWarn(false); - }, 5000); - - return () => clearTimeout(timer); - } else { - setShowWarn(false); - } - }, [warningMessage]); - - return ( - <Container> - {showWarn && <ChipContainer>키워드를 5개만 선택해 주세요!</ChipContainer>} - <PlainButton - variant="gray" - height="48px" - width="100%" - onClick={handleButtonClick} - disabled={warning} - > - 다음으로 - </PlainButton> - <Text>종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요!</Text> - </Container> - ); -}; - -export const DefineButtonView2 = ({ warning, warningMessage }: Props) => { - const navigate = useNavigate(); - const [showWarn, setShowWarn] = useState(false); - - const handleButton1Click = () => { - navigate('/test/define/2'); - }; - - const handleButton2Click = () => { - navigate('/test/define/4'); - }; - - useEffect(() => { - if (warningMessage) { - setShowWarn(true); - const timer = setTimeout(() => { - setShowWarn(false); - }, 5000); - - return () => clearTimeout(timer); - } else { - setShowWarn(false); - } - }, [warningMessage]); - - return ( - <Container> - {showWarn && <ChipContainer>키워드를 5개만 선택해 주세요!</ChipContainer>} - <ButtonContainer> - <PlainButton variant="gray" height="48px" width="100%" onClick={handleButton1Click}> - 이전으로 - </PlainButton> - <PlainButton - variant="gray" - height="48px" - width="100%" - onClick={handleButton2Click} - disabled={warning} - > - 다음으로 - </PlainButton> - </ButtonContainer> - <Text>종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요!</Text> - </Container> - ); -}; - -export const DefineButtonView3 = ({ warning, warningMessage }: Props) => { - const navigate = useNavigate(); - const [showWarn, setShowWarn] = useState(false); - const [loading, setLoading] = useRecoilState(loadingState); - const [loadingHandler, setLoadingHandler] = useRecoilState(loadingHandlerState); - - const handleButton1Click = () => { - navigate('/test/define/3'); - }; - - const handleButton2Click = () => { - const selectedChips1 = JSON.parse(sessionStorage.getItem('selectedChips1') || '[]'); - const selectedChips2 = JSON.parse(sessionStorage.getItem('selectedChips2') || '[]'); - const selectedChips3 = JSON.parse(sessionStorage.getItem('selectedChips3') || '[]'); - - const requestData = { - stage_one_keywords: selectedChips1, - stage_two_keywords: selectedChips2, - stage_three_keywords: selectedChips3, - }; - - setLoading({ show: true, speed: 50 }); - - personaAPI - .registerDefine(userService.getUserState() === 'MEMBER', requestData) - .then((response) => { - const { code, message, payload } = response; - - if (code === '201') { - setLoadingHandler({ - ...loadingHandler, - handleCompleted: () => { - navigate(`/test/define/${payload.define_persona_id}`); - }, - }); - } else { - console.error('페르소나 생성 실패:', message); - setLoading({ ...loading, show: false }); - } - }) - .catch((error) => { - console.error('페르소나 생성 요청 실패:', error); - window.alert('페르소나 생성 요청 실패'); - setLoading({ ...loading, show: false }); - }); - }; - - useEffect(() => { - if (warningMessage) { - setShowWarn(true); - const timer = setTimeout(() => { - setShowWarn(false); - }, 5000); - - return () => clearTimeout(timer); - } - }, [warningMessage]); - - return ( - <Container> - {showWarn && <ChipContainer>키워드를 5개만 선택해 주세요!</ChipContainer>} - <ButtonContainer> - <PlainButton variant="gray" height="48px" width="100%" onClick={handleButton1Click}> - 이전으로 - </PlainButton> - <PlainButton - variant="primary2" - height="48px" - width="100%" - onClick={handleButton2Click} - disabled={warning} - > - 결과 확인하기 - </PlainButton> - </ButtonContainer> - <Text>종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요!</Text> - </Container> - ); -}; diff --git a/src/components/DefineTest/DefineChip.tsx b/src/components/DefineTest/DefineChip.tsx deleted file mode 100644 index ce07874..0000000 --- a/src/components/DefineTest/DefineChip.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { useState, useEffect } from 'react'; - -import styled from 'styled-components'; - -import { - DefineButtonView1, - DefineButtonView2, - DefineButtonView3, -} from '@/components/DefineTest/DefineButtonView'; -import { KeywordChip } from '@/components/common/Chip/KeywordChip'; -import { CHIP_DATA1, CHIP_DATA2, CHIP_DATA3 } from '@/constants/defineChip'; - -export const DefineChips1 = () => { - const [chipStates, setChipStates] = useState(Array(CHIP_DATA1.length).fill(1)); - const [warning, setWarning] = useState(false); - const [warningMessage, setWarningMessage] = useState(false); - - useEffect(() => { - const activeCount = chipStates.filter((state) => state === 2).length; - setWarning(activeCount < 5 || activeCount > 5); - setWarningMessage(activeCount > 5); - }, [chipStates]); - - const handleToggle = (index: number) => { - const newChipStates = [...chipStates]; - - if (newChipStates[index] === 2) { - newChipStates[index] = 1; - } else { - newChipStates[index] = 2; - } - - setChipStates(newChipStates); - - const selectedChips1 = newChipStates.reduce((selected, state, i) => { - if (state === 2) { - selected.push(CHIP_DATA1[i]); - } - return selected; - }, []); - sessionStorage.setItem('selectedChips1', JSON.stringify(selectedChips1)); - }; - - return ( - <StyledContainer> - <KeywordContainer> - {CHIP_DATA1.map((text, index) => ( - <KeywordChip - key={index} - selected={chipStates[index] !== 1} - toggleHandler={() => handleToggle(index)} - > - {text} - </KeywordChip> - ))} - </KeywordContainer> - <DefineButtonView1 warning={warning} warningMessage={warningMessage} /> - </StyledContainer> - ); -}; -export const DefineChips2 = () => { - const [chipStates, setChipStates] = useState(Array(CHIP_DATA2.length).fill(1)); - const [warning, setWarning] = useState(false); - const [warningMessage, setWarningMessage] = useState(false); - - useEffect(() => { - const activeCount = chipStates.filter((state) => state === 2).length; - setWarning(activeCount < 5 || activeCount > 5); - setWarningMessage(activeCount > 5); - }, [chipStates]); - - const handleToggle = (index: number) => { - const newChipStates = [...chipStates]; - - if (newChipStates[index] === 2) { - newChipStates[index] = 1; - } else { - newChipStates[index] = 2; - } - - setChipStates(newChipStates); - - const selectedChips = newChipStates.reduce((selected, state, i) => { - if (state === 2) { - selected.push(CHIP_DATA2[i]); - } - return selected; - }, []); - - sessionStorage.setItem('selectedChips2', JSON.stringify(selectedChips)); - }; - - return ( - <StyledContainer> - <KeywordContainer> - {CHIP_DATA2.map((text, index) => ( - <KeywordChip - key={index} - selected={chipStates[index] !== 1} - toggleHandler={() => handleToggle(index)} - > - {text} - </KeywordChip> - ))} - </KeywordContainer> - <DefineButtonView2 warning={warning} warningMessage={warningMessage} /> - </StyledContainer> - ); -}; - -export const DefineChips3 = () => { - const [chipStates, setChipStates] = useState(Array(CHIP_DATA3.length).fill(1)); - const [warning, setWarning] = useState(false); - const [warningMessage, setWarningMessage] = useState(false); - - useEffect(() => { - const activeCount = chipStates.filter((state) => state === 2).length; - setWarning(activeCount < 5 || activeCount > 5); - setWarningMessage(activeCount > 5); - }, [chipStates]); - - const handleToggle = (index: number) => { - const newChipStates = [...chipStates]; - - if (newChipStates[index] === 2) { - newChipStates[index] = 1; - } else { - newChipStates[index] = 2; - } - - setChipStates(newChipStates); - - const selectedChips = newChipStates.reduce((selected, state, i) => { - if (state === 2) { - selected.push(CHIP_DATA3[i]); - } - return selected; - }, []); - - sessionStorage.setItem('selectedChips3', JSON.stringify(selectedChips)); - }; - - return ( - <StyledContainer> - <KeywordContainer> - {CHIP_DATA3.map((text, index) => ( - <KeywordChip - key={index} - selected={chipStates[index] !== 1} - toggleHandler={() => handleToggle(index)} - > - {text} - </KeywordChip> - ))} - </KeywordContainer> - <DefineButtonView3 warning={warning} warningMessage={warningMessage} /> - </StyledContainer> - ); -}; - -const StyledContainer = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - gap: 95px; - justify-content: space-between; -`; - -const KeywordContainer = styled.div` - display: flex; - justify-content: center; - flex-wrap: wrap; - gap: 16px; - - margin-top: 52px; - - @media ${({ theme }) => theme.device.tablet} { - margin-top: 32px; - gap: 8px; - } - - @media ${({ theme }) => theme.device.mobile} { - margin-top: 32px; - gap: 8px; - } -`; diff --git a/src/components/DefineTest/DefineTestView.tsx b/src/components/DefineTest/DefineTestView.tsx deleted file mode 100644 index 901fcbb..0000000 --- a/src/components/DefineTest/DefineTestView.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import styled from 'styled-components'; - -import { DefineChips1, DefineChips2, DefineChips3 } from '@/components/DefineTest/DefineChip'; -import { - DefineTextView1, - DefineTextView2, - DefineTextView3, -} from '@/components/DefineTest/DefineTextView'; - -const StyledContainer = styled.section` - display: flex; - flex-direction: column; - - background: ${({ theme }) => `${theme.color.primary50}`}; - min-height: var(--full-height); - - padding: 118px 0 48px 0; - - @media ${({ theme }) => theme.device.tablet} { - padding: 24px; - padding-top: 100px; - } - - @media ${({ theme }) => theme.device.mobile} { - padding: 20px; - padding-top: 96px; - } -`; - -const StyledContentContainer = styled.div` - width: 632px; - height: 100%; - - margin: 0 auto; - flex-grow: 1; - - display: flex; - flex-direction: column; - - @media ${({ theme }) => theme.device.tablet} { - width: 552px; - } - - @media ${({ theme }) => theme.device.mobile} { - width: 100%; - } -`; - -export const DefineTestView1 = () => { - return ( - <StyledContainer> - <StyledContentContainer> - <DefineTextView1 /> - <DefineChips1 /> - </StyledContentContainer> - </StyledContainer> - ); -}; - -export const DefineTestView2 = () => { - return ( - <StyledContainer> - <StyledContentContainer> - <DefineTextView2 /> - <DefineChips2 /> - </StyledContentContainer> - </StyledContainer> - ); -}; - -export const DefineTestView3 = () => { - return ( - <StyledContainer> - <StyledContentContainer> - <DefineTextView3 /> - <DefineChips3 /> - </StyledContentContainer> - </StyledContainer> - ); -}; diff --git a/src/components/DefineTest/DefineTextView.tsx b/src/components/DefineTest/DefineTextView.tsx deleted file mode 100644 index f7e5e27..0000000 --- a/src/components/DefineTest/DefineTextView.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import styled from 'styled-components'; - -import ProgressBar from '@/components/common/ProgressBar'; - -const TextContainer = styled.div` - display: flex; - flex-direction: column; - gap: 24px; -`; -const InnerContainer = styled.div` - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -`; -const KeywordContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; -`; - -const KeywordTitle = styled.div` - text-align: center; - color: ${({ theme }) => `${theme.color.gray900}`}; - ${({ theme }) => theme.font.desktop.body1b}; - - @media ${({ theme }) => theme.device.tablet} { - ${({ theme }) => theme.font.mobile.body1b}; - } - - @media ${({ theme }) => theme.device.mobile} { - ${({ theme }) => theme.font.mobile.body1b}; - } -`; - -const KeywordDescription = styled.div` - text-align: center; - color: ${({ theme }) => `${theme.color.gray600}`}; - ${({ theme }) => theme.font.desktop.body2m}; - - @media ${({ theme }) => theme.device.tablet} { - ${({ theme }) => theme.font.mobile.body2m}; - } - - @media ${({ theme }) => theme.device.mobile} { - ${({ theme }) => theme.font.mobile.body2m}; - } - - .highlight { - color: ${({ theme }) => `${theme.color.primary500}`}; - } -`; - -const ProgressWrapper = styled.div` - display: flex; - gap: 4px; -`; - -const ProgressText = styled.div` - ${({ theme }) => theme.font.desktop.body1b}; - @media ${({ theme }) => theme.device.tablet && theme.device.mobile} { - ${({ theme }) => theme.font.mobile.body1b}; - } - word-wrap: break-word; -`; - -const ProgressNumber1 = styled(ProgressText)` - color: ${({ theme }) => `${theme.color.primary500}`}; -`; -const ProgressNumber2 = styled(ProgressText)` - color: ${({ theme }) => `${theme.color.gray500}`}; -`; - -export const DefineTextView1 = () => { - const currentStep = 1; - const totalSteps = 3; - - return ( - <> - <TextContainer> - <InnerContainer> - <ProgressWrapper> - <ProgressNumber1>{currentStep} </ProgressNumber1> - <ProgressNumber2>/ {totalSteps}</ProgressNumber2> - </ProgressWrapper> - <ProgressBar $currentStep={currentStep} $totalSteps={totalSteps} /> - </InnerContainer> - <KeywordContainer> - <KeywordTitle>나에게 해당되는 키워드는 무엇인가요?</KeywordTitle> - <KeywordDescription> - <span className="highlight">5개</span>의 키워드를 선택해주세요. - </KeywordDescription> - </KeywordContainer> - </TextContainer> - </> - ); -}; - -export const DefineTextView2 = () => { - const currentStep = 2; - const totalSteps = 3; - - return ( - <> - <TextContainer> - <InnerContainer> - <ProgressWrapper> - <ProgressNumber1>{currentStep} </ProgressNumber1> - <ProgressNumber2>/ {totalSteps}</ProgressNumber2> - </ProgressWrapper> - <ProgressBar $currentStep={currentStep} $totalSteps={totalSteps} /> - </InnerContainer> - <KeywordContainer> - <KeywordTitle>나에게 해당되는 키워드는 무엇인가요?</KeywordTitle> - <KeywordDescription> - <span className="highlight">5개</span>의 키워드를 선택해주세요. - </KeywordDescription> - </KeywordContainer> - </TextContainer> - </> - ); -}; - -export const DefineTextView3 = () => { - const currentStep = 3; - const totalSteps = 3; - - return ( - <> - <TextContainer> - <InnerContainer> - <ProgressWrapper> - <ProgressNumber1>{currentStep} </ProgressNumber1> - <ProgressNumber2>/ {totalSteps}</ProgressNumber2> - </ProgressWrapper> - <ProgressBar $currentStep={currentStep} $totalSteps={totalSteps} /> - </InnerContainer> - <KeywordContainer> - <KeywordTitle>나에게 해당되는 키워드는 무엇인가요?</KeywordTitle> - <KeywordDescription> - <span className="highlight">5개</span>의 키워드를 선택해주세요. - </KeywordDescription> - </KeywordContainer> - </TextContainer> - </> - ); -}; diff --git a/src/pages/DefineStartPage.tsx b/src/components/DefineTestPage/StartSection.tsx similarity index 68% rename from src/pages/DefineStartPage.tsx rename to src/components/DefineTestPage/StartSection.tsx index 0c7f653..1901d6a 100644 --- a/src/pages/DefineStartPage.tsx +++ b/src/components/DefineTestPage/StartSection.tsx @@ -1,9 +1,9 @@ -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { DefineDesktopView } from '@/components/DefineStartPage/DefineDesktopView'; import { DefineMobileView } from '@/components/DefineStartPage/DefineMobileView'; -export const DefineStartPage = () => { +export const StartSection = ({ onNext }: { onNext: () => void }) => { const [isMobile, setIsMobile] = useState(false); useEffect(() => { @@ -20,5 +20,7 @@ export const DefineStartPage = () => { }; }, []); - return <>{isMobile ? <DefineMobileView /> : <DefineDesktopView />}</>; + return ( + <>{isMobile ? <DefineMobileView onNext={onNext} /> : <DefineDesktopView onNext={onNext} />}</> + ); }; diff --git a/src/components/DefineTestPage/TestSection.tsx b/src/components/DefineTestPage/TestSection.tsx new file mode 100644 index 0000000..4d300fa --- /dev/null +++ b/src/components/DefineTestPage/TestSection.tsx @@ -0,0 +1,179 @@ +import { useRecoilState } from 'recoil'; +import styled from 'styled-components'; + +import { TestSectionTitle } from '@/components/DefineTestPage/TestSectionTitle'; +import { ToastMessage } from '@/components/DefineTestPage/ToastMessage'; +import { PlainButton } from '@/components/common/Button/PlainButton'; +import { KeywordChip } from '@/components/common/Chip/KeywordChip'; +import useSessionStorage from '@/hooks/useSessionStorage'; +import { toastState } from '@/recoil/toastState'; + +interface StepProps { + chipData: string[]; + sessionStorageKey: string; + steps: string[]; + currentStep: string; + onNext?: () => void; + onPrev?: () => void; + lastSection?: boolean; +} + +export const TestSection = ({ + chipData, + sessionStorageKey, + steps, + currentStep, + onNext, + onPrev, + lastSection = false, +}: StepProps) => { + const [toast, setToast] = useRecoilState(toastState); + const [selectedChips, setSelectedChips] = useSessionStorage<string[]>(sessionStorageKey, []); + + const handleSelect = (chip: string) => { + const newSelectedChips = [...selectedChips, chip]; + setSelectedChips(newSelectedChips); + if (newSelectedChips.length > 5) { + setToast({ ...toast, show: true }); + } else { + setToast({ isShown: false, show: false }); + } + }; + + const handleDelete = (chip: string) => { + const newSelectedChips = selectedChips.filter((c) => c !== chip); + setSelectedChips(newSelectedChips); + if (newSelectedChips.length > 5) { + setToast({ ...toast, show: true }); + } else { + setToast({ isShown: false, show: false }); + } + }; + + return ( + <StyledContainer> + <StyledContentContainer> + <TestSectionTitle steps={steps} currentStep={currentStep} /> + <StyledStepContainer> + <StyledChipContainer> + {chipData.map((chip, index) => ( + <KeywordChip + key={index} + selected={selectedChips.includes(chip)} + selectHandler={handleSelect} + deleteHandler={handleDelete} + > + {chip} + </KeywordChip> + ))} + </StyledChipContainer> + <div> + <StyledButtonContainer> + {onPrev && ( + <PlainButton height="48px" onClick={onPrev}> + 이전으로 + </PlainButton> + )} + {onNext && !lastSection && ( + <PlainButton height="48px" onClick={onNext} disabled={selectedChips.length !== 5}> + 다음으로 + </PlainButton> + )} + {onNext && lastSection && ( + <PlainButton + variant="primary2" + height="48px" + onClick={onNext} + disabled={selectedChips.length !== 5} + > + 결과 확인하기 + </PlainButton> + )} + </StyledButtonContainer> + <StyledExplanation> + 종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요! + </StyledExplanation> + </div> + <ToastMessage /> + </StyledStepContainer> + </StyledContentContainer> + </StyledContainer> + ); +}; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + background: ${({ theme }) => `${theme.color.primary50}`}; + + min-height: 100vh; + + padding: 24px; + padding-top: 100px; + + zoom: 1.25; +`; + +const StyledContentContainer = styled.div` + display: flex; + flex-direction: column; + + width: 552px; + height: 100%; + max-height: 800px; + flex: 1; + + @media ${({ theme }) => theme.device.desktop} { + width: 632px; + } +`; + +const StyledStepContainer = styled.div` + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + + position: relative; +`; + +const StyledChipContainer = styled.div` + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 16px; + + @media ${({ theme }) => theme.device.tablet} { + margin-top: 32px; + gap: 8px; + } + + @media ${({ theme }) => theme.device.mobile} { + margin-top: 32px; + gap: 8px; + } +`; + +const StyledButtonContainer = styled.div` + width: 100%; + display: flex; + gap: 15px; + + @media ${({ theme }) => theme.device.tablet} { + gap: 8px; + } + + @media ${({ theme }) => theme.device.mobile} { + gap: 8px; + } +`; + +const StyledExplanation = styled.div` + ${({ theme }) => theme.font.desktop.label1m}; + color: ${({ theme }) => theme.color.gray400}; + + margin-top: 15px; + text-align: center; +`; diff --git a/src/components/DefineTestPage/TestSectionTitle.tsx b/src/components/DefineTestPage/TestSectionTitle.tsx new file mode 100644 index 0000000..1840fd6 --- /dev/null +++ b/src/components/DefineTestPage/TestSectionTitle.tsx @@ -0,0 +1,86 @@ +import styled from 'styled-components'; + +import ProgressBar from '@/components/common/ProgressBar'; + +interface TestSectionTitleProps { + steps: string[]; + currentStep: string; +} + +export const TestSectionTitle = ({ steps, currentStep }: TestSectionTitleProps) => { + return ( + <StyledTitleContainer> + <StyledProgressBar> + <div className="title"> + <span>{steps.indexOf(currentStep)}</span> / {steps.length - 1} + </div> + <ProgressBar $currentStep={steps.indexOf(currentStep) + 1} $totalSteps={steps.length} /> + </StyledProgressBar> + <StyledTitle> + <div className="title">나에게 해당되는 키워드는 무엇인가요?</div> + <div className="subtitle"> + <span className="highlight">5개</span>의 키워드를 선택해주세요. + </div> + </StyledTitle> + </StyledTitleContainer> + ); +}; + +const StyledTitleContainer = styled.div` + display: flex; + flex-direction: column; + gap: 24px; + margin-bottom: 32px; +`; + +const StyledProgressBar = styled.div` + .title { + text-align: center; + margin-bottom: 8px; + + ${({ theme }) => theme.font.desktop.body1b}; + @media ${({ theme }) => theme.device.tablet && theme.device.mobile} { + ${({ theme }) => theme.font.mobile.body1b}; + } + color: ${({ theme }) => `${theme.color.gray500}`}; + + span { + color: ${({ theme }) => `${theme.color.primary500}`}; + } + } +`; + +const StyledTitle = styled.div` + .title { + text-align: center; + margin-bottom: 4px; + color: ${({ theme }) => `${theme.color.gray900}`}; + ${({ theme }) => theme.font.desktop.body1b}; + + @media ${({ theme }) => theme.device.tablet} { + ${({ theme }) => theme.font.mobile.body1b}; + } + + @media ${({ theme }) => theme.device.mobile} { + ${({ theme }) => theme.font.mobile.body1b}; + } + } + + .subtitle { + text-align: center; + color: ${({ theme }) => `${theme.color.gray600}`}; + ${({ theme }) => theme.font.desktop.body2m}; + + @media ${({ theme }) => theme.device.tablet} { + ${({ theme }) => theme.font.mobile.body2m}; + } + + @media ${({ theme }) => theme.device.mobile} { + ${({ theme }) => theme.font.mobile.body2m}; + } + + .highlight { + color: ${({ theme }) => `${theme.color.primary500}`}; + } + } +`; diff --git a/src/components/DefineTestPage/ToastMessage.tsx b/src/components/DefineTestPage/ToastMessage.tsx new file mode 100644 index 0000000..1fb059d --- /dev/null +++ b/src/components/DefineTestPage/ToastMessage.tsx @@ -0,0 +1,76 @@ +import { useEffect, useState } from 'react'; + +import { useRecoilState } from 'recoil'; +import styled from 'styled-components'; + +import { toastState } from '@/recoil/toastState'; + +export const ToastMessage = () => { + const [toast, setToast] = useRecoilState(toastState); + const [isClosing, setIsClosing] = useState<boolean>(false); + const [show, setShow] = useState(false); + + useEffect(() => { + console.log(toast.show, toast.isShown); + if (toast.show && !toast.isShown) { + const setShowTimeout = setTimeout(() => { + setShow(true); + }, 1); + + const setClosingTimeout = setTimeout(() => { + setIsClosing(true); + }, 4000); + + const setToastReset = setTimeout(() => { + setToast({ isShown: true, show: false }); + setIsClosing(false); + setShow(false); + }, 4300); + + return () => { + clearTimeout(setShowTimeout); + clearTimeout(setClosingTimeout); + clearTimeout(setToastReset); + }; + } + }, [toast.show]); + + if (!toast.show) return null; + + return ( + <StyledContainer className={isClosing ? 'closing show' : `${show && 'show'}`}> + <StyledText>키워드를 5개만 선택해 주세요!</StyledText> + </StyledContainer> + ); +}; + +const StyledContainer = styled.div` + opacity: 0; + &.show { + opacity: 1; + } + &.closing { + opacity: 0; + } + + transition: opacity 300ms ease-in-out; + + width: 100%; + display: flex; + justify-content: center; + + position: absolute; + left: 0; + bottom: 100px; +`; + +const StyledText = styled.div` + width: max-content; + padding: 8px 20px; + border-radius: 8px; + border: 1px solid ${({ theme }) => `${theme.color.secondary600}`}; + background: ${({ theme }) => `${theme.color.secondary50}`}; + + color: ${({ theme }) => `${theme.color.secondary600}`}; + ${({ theme }) => theme.font.desktop.label1m}; +`; diff --git a/src/components/common/Chip/KeywordChip.tsx b/src/components/common/Chip/KeywordChip.tsx index 112957d..034a6ad 100644 --- a/src/components/common/Chip/KeywordChip.tsx +++ b/src/components/common/Chip/KeywordChip.tsx @@ -5,7 +5,7 @@ interface KeywordChipProps { children: string; selectHandler?: (selectedKeyword: string) => void; deleteHandler?: (selectedKeyword: string) => void; - toggleHandler?: () => void; + toggleHandler?: (selectedKeyword: string) => void; } export const KeywordChip = ({ @@ -19,7 +19,7 @@ export const KeywordChip = ({ <StyledKeywordChip $selected={selected} onClick={() => { - toggleHandler && toggleHandler(); + toggleHandler && toggleHandler(children); if (!selected) selectHandler && selectHandler(children); else deleteHandler && deleteHandler(children); }} diff --git a/src/components/common/ProgressBar/index.tsx b/src/components/common/ProgressBar/index.tsx index a568ba5..d542aba 100644 --- a/src/components/common/ProgressBar/index.tsx +++ b/src/components/common/ProgressBar/index.tsx @@ -5,6 +5,16 @@ interface ProgressBarProps { $totalSteps: number; } +const ProgressBar = ({ $currentStep, $totalSteps }: ProgressBarProps) => { + return ( + <ProgressLongBar> + <Progress $currentStep={$currentStep} $totalSteps={$totalSteps} /> + </ProgressLongBar> + ); +}; + +export default ProgressBar; + const ProgressLongBar = styled.div` width: 100%; background-color: ${(props) => props.theme.color.gray50}; @@ -17,14 +27,5 @@ const Progress = styled.div<ProgressBarProps>` padding: 0; background-color: ${(props) => props.theme.color.primary500}; border-radius: 12px; + transition: width 0.5s ease-out; `; - -const ProgressBar = ({ $currentStep, $totalSteps }: ProgressBarProps) => { - return ( - <ProgressLongBar> - <Progress $currentStep={$currentStep} $totalSteps={$totalSteps} /> - </ProgressLongBar> - ); -}; - -export default ProgressBar; diff --git a/src/hooks/useSessionStorage.ts b/src/hooks/useSessionStorage.ts new file mode 100644 index 0000000..c9f783c --- /dev/null +++ b/src/hooks/useSessionStorage.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; + +const useSessionStorage = <T>(key: string, initialValue: T): [T, (value: T) => void] => { + const [storedValue, setStoredValue] = useState<T>(() => { + try { + const item = sessionStorage.getItem(key); + return item ? (JSON.parse(item) as T) : initialValue; + } catch (error) { + console.error('Error reading from sessionStorage', error); + return initialValue; + } + }); + + useEffect(() => { + try { + sessionStorage.setItem(key, JSON.stringify(storedValue)); + } catch (error) { + console.error('Error writing to sessionStorage', error); + } + }, [key, storedValue]); + + return [storedValue, setStoredValue]; +}; + +export default useSessionStorage; diff --git a/src/pages/DefineTestPage.tsx b/src/pages/DefineTestPage.tsx index 465925d..2b8f960 100644 --- a/src/pages/DefineTestPage.tsx +++ b/src/pages/DefineTestPage.tsx @@ -1,39 +1,121 @@ -import { useRecoilValue } from 'recoil'; +import { useEffect } from 'react'; -import { - DefineTestView1, - DefineTestView2, - DefineTestView3, -} from '@/components/DefineTest/DefineTestView'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilState } from 'recoil'; + +import { personaAPI } from '@/apis/personaAPI'; +import { StartSection } from '@/components/DefineTestPage/StartSection'; +import { TestSection } from '@/components/DefineTestPage/TestSection'; +import { CHIP_DATA1, CHIP_DATA2, CHIP_DATA3 } from '@/constants/defineChip'; +import { useFunnel } from '@/hooks/useFunnel'; import { LoadingPage } from '@/pages/LoadingPage'; +import { loadingHandlerState } from '@/recoil/loadingHandlerState'; import { loadingState } from '@/recoil/loadingState'; +import { userService } from '@/services/UserService'; -export const DefineTestPage1 = () => { - return ( - <> - <DefineTestView1 /> - </> - ); -}; +const STEPS = ['start', 'first', 'second', 'third']; +const KEY = ['selectedChips1', 'selectedChips2', 'selectedChips3']; -export const DefineTestPage2 = () => { - return ( - <> - <DefineTestView2 /> - </> - ); -}; +export const DefineTestPage = () => { + const [loading, setLoading] = useRecoilState(loadingState); + const [loadingHandler, setLoadingHandler] = useRecoilState(loadingHandlerState); + const navigate = useNavigate(); + const { Funnel, Step, setStep, currentStep } = useFunnel(STEPS[0]); + + useEffect(() => { + KEY.some((key, index) => { + const chips = JSON.parse(sessionStorage.getItem(key) || '[]'); + if (chips.length !== 5) { + setStep(STEPS[index + 1]); + return true; + } + return false; + }); + }, []); + + const prevClickHandler = (prevStep: string) => { + setStep(prevStep); + }; + + const nextClickHandler = (nextStep: string) => { + setStep(nextStep); + console.log(nextStep); + }; + + const handleSubmit = () => { + const requestData = { + stage_one_keywords: JSON.parse(sessionStorage.getItem(KEY[0]) || '[]'), + stage_two_keywords: JSON.parse(sessionStorage.getItem(KEY[1]) || '[]'), + stage_three_keywords: JSON.parse(sessionStorage.getItem(KEY[2]) || '[]'), + }; + + setLoading({ show: true, speed: 50 }); + + personaAPI + .registerDefine(userService.getUserState() === 'MEMBER', requestData) + .then((response) => { + const { code, message, payload } = response; + + if (code === '201') { + KEY.map((key) => sessionStorage.removeItem(key)); -export const DefineTestPage3 = () => { - const loading = useRecoilValue(loadingState); + setLoadingHandler({ + ...loadingHandler, + handleCompleted: () => { + navigate(`/test/define/${payload.define_persona_id}`); + }, + }); + } else { + console.error('페르소나 생성 실패:', message); + setLoading({ ...loading, show: false }); + } + }) + .catch((error) => { + console.error('페르소나 생성 요청 실패:', error); + window.alert('페르소나 생성 요청 실패'); + setLoading({ ...loading, show: false }); + }); + }; if (loading.show) { return <LoadingPage />; } return ( - <> - <DefineTestView3 /> - </> + <Funnel> + <Step name="start"> + <StartSection onNext={() => nextClickHandler(STEPS[1])} /> + </Step> + <Step name="first"> + <TestSection + chipData={CHIP_DATA1} + sessionStorageKey={KEY[0]} + steps={STEPS} + currentStep={currentStep} + onNext={() => nextClickHandler(STEPS[2])} + /> + </Step> + <Step name="second"> + <TestSection + chipData={CHIP_DATA2} + sessionStorageKey={KEY[1]} + steps={STEPS} + currentStep={currentStep} + onPrev={() => prevClickHandler(STEPS[1])} + onNext={() => nextClickHandler(STEPS[3])} + /> + </Step> + <Step name="third"> + <TestSection + chipData={CHIP_DATA3} + sessionStorageKey={KEY[2]} + steps={STEPS} + currentStep={currentStep} + onPrev={() => prevClickHandler(STEPS[2])} + onNext={handleSubmit} + lastSection + /> + </Step> + </Funnel> ); }; diff --git a/src/pages/RedirectPage.tsx b/src/pages/RedirectPage.tsx index 1d924fd..98f0f26 100644 --- a/src/pages/RedirectPage.tsx +++ b/src/pages/RedirectPage.tsx @@ -17,6 +17,8 @@ export const RedirectPage = () => { const accessToken = params.get('access_token'); const registerToken = params.get('register_token'); + sessionStorage.clear(); + if (registerToken) { tokenService.setRegisterToken(registerToken); userService.setUser({ nickname: '', is_test: false }); diff --git a/src/recoil/toastState.ts b/src/recoil/toastState.ts new file mode 100644 index 0000000..ab67442 --- /dev/null +++ b/src/recoil/toastState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const toastState = atom({ + key: 'toastState', + default: { show: false, isShown: false }, +}); diff --git a/src/routers/router.tsx b/src/routers/router.tsx index e8005da..2578a97 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -8,8 +8,7 @@ import { MainLayout } from '@/components/common/Layout/MainLayout'; import { MyPageLayout } from '@/components/common/Layout/MyPageLayout'; import { TestLayout } from '@/components/common/Layout/TestLayout'; import { DefineResultPage } from '@/pages/DefineResultPage'; -import { DefineStartPage } from '@/pages/DefineStartPage'; -import { DefineTestPage1, DefineTestPage2, DefineTestPage3 } from '@/pages/DefineTestPage'; +import { DefineTestPage } from '@/pages/DefineTestPage'; import { DesignResultPage } from '@/pages/DesignResultPage'; import { DesignStartPage } from '@/pages/DesignStartPage'; import { @@ -55,10 +54,7 @@ export const Router = () => { <Route path="test" element={<TestLayout />}> <Route path="define"> <Route index element={<Navigate to="1" replace />}></Route> - <Route path="1" element={<DefineStartPage />} /> - <Route path="2" element={<DefineTestPage1 />} /> - <Route path="3" element={<DefineTestPage2 />} /> - <Route path="4" element={<DefineTestPage3 />} /> + <Route path="1" element={<DefineTestPage />} /> <Route path=":defineId" element={<DefineResultPage />} /> </Route> <Route element={<MemberPrivateRoute />}> diff --git a/src/services/TokenService.ts b/src/services/TokenService.ts index e839953..9d8eed1 100644 --- a/src/services/TokenService.ts +++ b/src/services/TokenService.ts @@ -87,6 +87,7 @@ class TokenService { onLogout = () => { this.removeData(); + sessionStorage.clear(); window.location.href = '/'; }; } From ff0babc12d41437d4b411ca0a161428529c523fb Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Fri, 7 Jun 2024 10:56:38 +0900 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20Define=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=A4=91=20=EC=A7=84=ED=96=89=20=EC=A4=91=EC=9D=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/DefineTestPage.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/pages/DefineTestPage.tsx b/src/pages/DefineTestPage.tsx index 2b8f960..6cbe23e 100644 --- a/src/pages/DefineTestPage.tsx +++ b/src/pages/DefineTestPage.tsx @@ -23,14 +23,25 @@ export const DefineTestPage = () => { const { Funnel, Step, setStep, currentStep } = useFunnel(STEPS[0]); useEffect(() => { - KEY.some((key, index) => { + let allEmpty = true; + + const result = KEY.some((key, index) => { const chips = JSON.parse(sessionStorage.getItem(key) || '[]'); + + if (chips.length > 0) { + allEmpty = false; + } + if (chips.length !== 5) { setStep(STEPS[index + 1]); return true; } return false; }); + + if (!result || allEmpty) { + setStep(STEPS[0]); + } }, []); const prevClickHandler = (prevStep: string) => { From 6a82900a4172d2ab639fd20cfd3b2187a03f5002 Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Fri, 7 Jun 2024 10:57:00 +0900 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20Discover=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/personaAPI.ts | 11 +- .../DiscoverResultPage/ResultView.tsx | 113 +++++++++--------- .../SelfUnderstandPage/DiscoverResultView.tsx | 30 +---- src/hooks/useGetDiscoverResult.ts | 107 +++++++++++++++++ 4 files changed, 173 insertions(+), 88 deletions(-) create mode 100644 src/hooks/useGetDiscoverResult.ts diff --git a/src/apis/personaAPI.ts b/src/apis/personaAPI.ts index 4c2cb32..4552101 100644 --- a/src/apis/personaAPI.ts +++ b/src/apis/personaAPI.ts @@ -77,14 +77,13 @@ export const personaAPI = { return response.data; }, - // Discover 페르소나 키워드 전체 조회 - getDiscoverAllKeyword: async () => { - const response = await authClient.get('/api/personas/discover/all-keywords'); - return response.data; - }, - // Discover 페르소나 키워드 카테고리별 조회 getDiscoverCategoryKeyword: async (category: string) => { + if (category === 'all') { + const response = await authClient.get('/api/personas/discover/all-keywords'); + return response.data; + } + const response = await authClient.get( `/api/personas/discover/keywords?category=${CATEGORY_TYPE[category].title}` ); diff --git a/src/components/DiscoverResultPage/ResultView.tsx b/src/components/DiscoverResultPage/ResultView.tsx index 097c59c..9d43b75 100644 --- a/src/components/DiscoverResultPage/ResultView.tsx +++ b/src/components/DiscoverResultPage/ResultView.tsx @@ -1,16 +1,15 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styled, { css } from 'styled-components'; -import { personaAPI } from '@/apis/personaAPI'; -import { SummaryCard } from '@/components/DiscoverTestPage/SummaryCard'; import { CategoryButton } from '@/components/common/Button/CategoryButton'; import { PlainButton } from '@/components/common/Button/PlainButton'; +import { useGetDiscoverKeywordResult } from '@/hooks/useGetDiscoverResult'; import { userService } from '@/services/UserService'; import { moveDown, moveLeft, moveRight, moveUp } from '@/styles'; -const CATEGORY_LIST: { [key: string]: string } = { +export const DISCOVER_CATEGORY_LIST: { [key: string]: string } = { all: '전체', health: '건강', career: '커리어', @@ -20,17 +19,16 @@ const CATEGORY_LIST: { [key: string]: string } = { export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) => { const [selectedCategory, setSelectedCategory] = useState<string>('all'); - const [categoryKeywords, setCategoryKeywords] = useState<{ [key: string]: string[] }>({ - all: [], - health: [], - career: [], - love: [], - leisure: [], - }); const [summary, setSummary] = useState<{ [key: string]: string[] } | undefined>(undefined); const navigate = useNavigate(); - useEffect(() => { + const { data: allKeywords, loading: allKeywordsLoading } = useGetDiscoverKeywordResult(); + + if (allKeywordsLoading) { + return <div>Loading...</div>; + } + + /* useEffect(() => { const fetchAllKeywords = async () => { try { const response = await personaAPI.getDiscoverAllKeyword(); @@ -51,9 +49,9 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = fetchAllKeywords(); fetchSummary(); - }, []); + }, []); */ - useEffect(() => { + /*useEffect(() => { const fetchCategoryKeywords = async (category: string) => { if (categoryKeywords[category]) { try { @@ -71,7 +69,7 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = if (selectedCategory !== 'all') { fetchCategoryKeywords(selectedCategory); } - }, [selectedCategory, categoryKeywords]); + }, [selectedCategory, categoryKeywords]); */ return ( <StyledContainer $showSummary={showSummary}> @@ -84,38 +82,39 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = 은, 이런 사람이에요. </div> <StyledChipContainer> - {Object.keys(CATEGORY_LIST).map((category) => ( + {Object.keys(DISCOVER_CATEGORY_LIST).map((category) => ( <CategoryButton key={category} active={selectedCategory === category} onClick={() => setSelectedCategory(category)} > - {CATEGORY_LIST[category]} + {DISCOVER_CATEGORY_LIST[category]} </CategoryButton> ))} </StyledChipContainer> </StyledHeader> - <StyledContent> - {categoryKeywords[selectedCategory].length !== 0 ? ( - <div className="result"> - {showSummary && ( - <div className="description"> - <span className="highlight">상위 6개</span>만 보여주고 있어요! - </div> - )} - <BubbleSection $noResult={categoryKeywords[selectedCategory].length === 0}> - {categoryKeywords[selectedCategory].map((keyword, index) => ( - <StyledBubble key={keyword} className={`b${index}`} $weight={1 - 0.1 * index}> - <span>{keyword}</span> - </StyledBubble> - ))} - </BubbleSection> - {!showSummary && ( - <div className="description"> - <span className="highlight">상위 6개</span>만 보여주고 있어요! - </div> - )} - {showSummary && ( + {allKeywords && ( + <StyledContent> + {allKeywords[selectedCategory].length !== 0 ? ( + <div className="result"> + {showSummary && ( + <div className="description"> + <span className="highlight">상위 6개</span>만 보여주고 있어요! + </div> + )} + <BubbleSection $noResult={allKeywords[selectedCategory].length === 0}> + {allKeywords[selectedCategory].map((keyword, index) => ( + <StyledBubble key={keyword} className={`b${index}`} $weight={1 - 0.1 * index}> + <span>{keyword}</span> + </StyledBubble> + ))} + </BubbleSection> + {!showSummary && ( + <div className="description"> + <span className="highlight">상위 6개</span>만 보여주고 있어요! + </div> + )} + {/* {showSummary && ( <ResultSection> <div className="title">{`${userService.getUserNickname()}님과의 대화한 내용을 요약했어요!`}</div> <div className="card-container"> @@ -140,24 +139,26 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = ))} </div> </ResultSection> - )} - </div> - ) : ( - <div className="no-result"> - <NoResultText> - <div className="title"> - 아직 <span className="highlight">{CATEGORY_LIST[selectedCategory]}</span> 테스트를 - 완료하지 않았어요! - </div> - <div className="subtitle">남은 테스트를 진행해주세요.</div> - </NoResultText> - <StyledNoBubble> - <span>?</span> - </StyledNoBubble> - </div> - )} - </StyledContent> - {categoryKeywords[selectedCategory].length === 0 && ( + )} */} + </div> + ) : ( + <div className="no-result"> + <NoResultText> + <div className="title"> + 아직{' '} + <span className="highlight">{DISCOVER_CATEGORY_LIST[selectedCategory]}</span>{' '} + 테스트를 완료하지 않았어요! + </div> + <div className="subtitle">남은 테스트를 진행해주세요.</div> + </NoResultText> + <StyledNoBubble> + <span>?</span> + </StyledNoBubble> + </div> + )} + </StyledContent> + )} + {allKeywords && allKeywords[selectedCategory].length === 0 && ( <PlainButton width="376px" height="48px" diff --git a/src/components/SelfUnderstandPage/DiscoverResultView.tsx b/src/components/SelfUnderstandPage/DiscoverResultView.tsx index c97537b..16db602 100644 --- a/src/components/SelfUnderstandPage/DiscoverResultView.tsx +++ b/src/components/SelfUnderstandPage/DiscoverResultView.tsx @@ -1,35 +1,13 @@ -import { useEffect, useState } from 'react'; - -import axios from 'axios'; - -import { personaAPI } from '@/apis/personaAPI'; import { ResultView } from '@/components/DiscoverResultPage/ResultView'; import { NoResultSection } from '@/components/SelfUnderstandPage/NoResultTemplate'; -import { userService } from '@/services/UserService'; +import { useGetDiscoverResult } from '@/hooks/useGetDiscoverResult'; export const DiscoverResultView = () => { - const [isTest, setIsTest] = useState(false); - - useEffect(() => { - const fetchAllKeywords = async () => { - try { - await personaAPI.getDiscoverAllKeyword(); - setIsTest(true); - } catch (error) { - if (axios.isAxiosError(error) && error.response && error.response.status === 404) { - setIsTest(false); - } - } - }; + const { loading, error, isTest } = useGetDiscoverResult(); - if (userService.getUserState() === 'MEMBER') { - fetchAllKeywords(); - } - }, []); + if (loading || error) return null; - if (isTest) { - return <ResultView />; - } + if (isTest) return <ResultView />; return <NoResultSection tab="Discover" />; }; diff --git a/src/hooks/useGetDiscoverResult.ts b/src/hooks/useGetDiscoverResult.ts new file mode 100644 index 0000000..3cf024c --- /dev/null +++ b/src/hooks/useGetDiscoverResult.ts @@ -0,0 +1,107 @@ +import { useEffect, useState } from 'react'; + +import { personaAPI } from '@/apis/personaAPI'; +import { DISCOVER_CATEGORY_LIST } from '@/components/DiscoverResultPage/ResultView'; +import { userService } from '@/services/UserService'; + +export const useGetDiscoverResult = () => { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + const [isTest, setIsTest] = useState(false); + const userState = userService.getUserState(); + + useEffect(() => { + const fetchKeywords = async () => { + setLoading(true); + try { + const response = await personaAPI.getDiscoverCategoryKeyword('all'); + if (response.payload.keywords.length > 0) { + setIsTest(true); + } + } catch (error) { + setError(true); + } finally { + setLoading(false); + } + }; + + if (userState === 'MEMBER') { + fetchKeywords(); + } + }, []); + + return { loading, error, isTest }; +}; + +export const useGetDiscoverKeywordResult = () => { + const [data, setData] = useState<{ [key: keyof typeof DISCOVER_CATEGORY_LIST]: string[] }>({ + all: [], + health: [], + career: [], + love: [], + leisure: [], + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + + useEffect(() => { + const fetchKeywords = async () => { + setLoading(true); + try { + const responses = await Promise.allSettled( + Object.keys(DISCOVER_CATEGORY_LIST).map((category) => + personaAPI.getDiscoverCategoryKeyword(category) + ) + ); + + const updatedData: { [key: keyof typeof DISCOVER_CATEGORY_LIST]: string[] } = {}; + + responses.forEach((response, index) => { + const categoryKey = Object.keys(DISCOVER_CATEGORY_LIST)[index]; + console.log('Category:', categoryKey, 'Response:', response); + if (response.status === 'fulfilled' && categoryKey) { + updatedData[categoryKey] = response.value.payload.keywords; + } + }); + + setData((prevData) => ({ + ...prevData, + ...updatedData, + })); + } catch (error) { + setError(true); + } finally { + setLoading(false); + } + }; + + fetchKeywords(); + }, []); + + return { data, loading, error }; +}; + +export const useGetDiscoverSummary = () => { + // + const [data, setData] = useState<string[]>([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(false); + + useEffect(() => { + const fetchSummary = async () => { + setLoading(true); + try { + const response = await personaAPI.getDefaultSummary(); + setData(response.payload.summary); + } catch (error) { + setError(true); + } finally { + setLoading(false); + } + }; + + fetchSummary(); + }, []); + + return { data, loading, error }; +}; From ce74916b774ce20361dfccd14d73f5aba8bda4f3 Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Fri, 7 Jun 2024 15:58:03 +0900 Subject: [PATCH 16/20] =?UTF-8?q?fix:=20=EC=A0=84=EC=8B=9C=ED=9A=8C?= =?UTF-8?q?=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=9E=84=EC=8B=9C=20=EB=9D=BC?= =?UTF-8?q?=EC=9A=B0=ED=8C=85=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdevertieseCard.tsx | 3 +- .../OnboardingPage/SetupBranding.tsx | 3 +- .../SelfUnderstandPage/NoResultTemplate.tsx | 5 +- src/components/common/Layout/MainLayout.tsx | 3 +- .../common/Navigation/TopNavigation.tsx | 3 +- src/hooks/useGetDiscoverResult.ts | 3 +- src/pages/HomePage.tsx | 15 +++--- src/pages/LoginPage.tsx | 5 +- src/pages/OnboardingPage.tsx | 3 +- src/pages/RedirectPage.tsx | 3 +- src/routers/router.tsx | 51 ++++++++++++++++++- src/services/TokenService.ts | 3 +- vercel.json | 2 +- 13 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/components/ExperienceRecommendPage/AdevertieseCard.tsx b/src/components/ExperienceRecommendPage/AdevertieseCard.tsx index 2da6c5e..fbe2229 100644 --- a/src/components/ExperienceRecommendPage/AdevertieseCard.tsx +++ b/src/components/ExperienceRecommendPage/AdevertieseCard.tsx @@ -6,6 +6,7 @@ import Card3 from '@/assets/advertise/advertise3.png'; import Video1 from '@/assets/banner.mp4'; import { AdvertiseCarousel } from '@/components/ExperienceRecommendPage/AdvertiseCarousel'; +// TODO: /home -> / 로 변경해야함. const AdvertiseCard = () => { return ( <AdvertiseCarousel> @@ -14,7 +15,7 @@ const AdvertiseCard = () => { <video src={Video1} autoPlay muted loop playsInline /> </CardMedia> </StyledLink> - <StyledLink to="/"> + <StyledLink to="/home"> <CardMedia> <img src={Card2} alt="Card 2" /> </CardMedia> diff --git a/src/components/OnboardingPage/SetupBranding.tsx b/src/components/OnboardingPage/SetupBranding.tsx index 8b6b722..819314e 100644 --- a/src/components/OnboardingPage/SetupBranding.tsx +++ b/src/components/OnboardingPage/SetupBranding.tsx @@ -27,7 +27,8 @@ export const SetupBranding = () => { tokenService.setAccessToken(res.payload.access_token); userService.updateUserNickname(res.payload.nickname); tokenService.removeRegisterToken(); - navigate('/'); + // TODO: /home -> / 로 변경해야함. + navigate('/home'); }) .catch(() => { window.alert('다시 시도해주세요'); diff --git a/src/components/SelfUnderstandPage/NoResultTemplate.tsx b/src/components/SelfUnderstandPage/NoResultTemplate.tsx index bb1e867..46a1e71 100644 --- a/src/components/SelfUnderstandPage/NoResultTemplate.tsx +++ b/src/components/SelfUnderstandPage/NoResultTemplate.tsx @@ -21,7 +21,10 @@ export const NoResultSection = ({ tab }: NoResultSectionProps) => { <PlainButton width="352px" height="48px" - onClick={() => navigate(MY_UNDERSTAND_TAB.find((item) => item.tab === tab)?.path || '/')} + // TODO: /home -> / 로 변경해야함. + onClick={() => + navigate(MY_UNDERSTAND_TAB.find((item) => item.tab === tab)?.path || '/home') + } > 테스트 시작하기 </PlainButton> diff --git a/src/components/common/Layout/MainLayout.tsx b/src/components/common/Layout/MainLayout.tsx index a7ec5cb..4f83366 100644 --- a/src/components/common/Layout/MainLayout.tsx +++ b/src/components/common/Layout/MainLayout.tsx @@ -3,7 +3,8 @@ import { Outlet, useLocation } from 'react-router-dom'; import { Footer } from '@/components/common/Footer'; import { TopNavigation } from '@/components/common/Navigation/TopNavigation'; -const FOOTER_VISIBLE_PATHS = ['/', '/understand', '/program']; +// TODO: /home -> / 로 변경해야함. +const FOOTER_VISIBLE_PATHS = ['/home', '/understand', '/program']; export const MainLayout = () => { const location = useLocation(); diff --git a/src/components/common/Navigation/TopNavigation.tsx b/src/components/common/Navigation/TopNavigation.tsx index 96160a3..74c5487 100644 --- a/src/components/common/Navigation/TopNavigation.tsx +++ b/src/components/common/Navigation/TopNavigation.tsx @@ -19,10 +19,11 @@ export const TopNavigation = () => { const navigate = useNavigate(); const location = useLocation(); + // TODO: /home -> / 로 변경해야함. return ( <> <StyledContainer> - <Link to="/"> + <Link to="/home"> <MainLogo className="logo" /> </Link> {!MENU_VISIBLE_PATHS.includes(location.pathname) && ( diff --git a/src/hooks/useGetDiscoverResult.ts b/src/hooks/useGetDiscoverResult.ts index 3cf024c..8a19af0 100644 --- a/src/hooks/useGetDiscoverResult.ts +++ b/src/hooks/useGetDiscoverResult.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { personaAPI } from '@/apis/personaAPI'; -import { DISCOVER_CATEGORY_LIST } from '@/components/DiscoverResultPage/ResultView'; +import { DISCOVER_CATEGORY_LIST } from '@/constants/discover'; import { userService } from '@/services/UserService'; export const useGetDiscoverResult = () => { @@ -82,7 +82,6 @@ export const useGetDiscoverKeywordResult = () => { }; export const useGetDiscoverSummary = () => { - // const [data, setData] = useState<string[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 171d0ae..adc1941 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -6,12 +6,14 @@ import { TesterMemberView } from '@/components/HomePage/TesterMemberView'; import { userService } from '@/services/UserService'; export const HomePage = () => { - const [testState, setTestState] = useState('NON_MEMBER'); + const [testState, setTestState] = useState<string>(); useEffect(() => { setTestState(userService.getTestState()); }, []); + if (!testState) return <div>loading...</div>; + if (testState === 'NON_MEMBER') { return ( <div style={{ minWidth: '1280px' }}> @@ -27,9 +29,10 @@ export const HomePage = () => { </div> ); - return ( - <div style={{ minWidth: '1280px' }}> - <NonTesterMemberView /> - </div> - ); + if (testState === 'NON_TESTER_MEMBER') + return ( + <div style={{ minWidth: '1280px' }}> + <NonTesterMemberView /> + </div> + ); }; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index e3659f4..160df89 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -21,7 +21,8 @@ export const LoginPage = () => { window.addEventListener('resize', handleResize); if (user !== 'NON_MEMBER') { - navigate('/'); + // TODO: /home -> / 로 변경해야함. + navigate('/home'); } return () => { diff --git a/src/pages/OnboardingPage.tsx b/src/pages/OnboardingPage.tsx index 1abc32b..52b5f25 100644 --- a/src/pages/OnboardingPage.tsx +++ b/src/pages/OnboardingPage.tsx @@ -23,7 +23,8 @@ export const OnboardingPage = () => { useEffect(() => { if (user !== 'PRE_MEMBER') { - navigate('/'); + // TODO: /home -> / 로 변경해야함. + navigate('/home'); } }, [user]); diff --git a/src/pages/RedirectPage.tsx b/src/pages/RedirectPage.tsx index 98f0f26..dd65c23 100644 --- a/src/pages/RedirectPage.tsx +++ b/src/pages/RedirectPage.tsx @@ -28,7 +28,8 @@ export const RedirectPage = () => { if (accessToken) { tokenService.setAccessToken(accessToken); nickname && userService.setUser({ nickname, is_test: isTest === 'T' ? true : false }); - navigate('/'); + // TODO: /home -> / 로 변경해야함. + navigate('/home'); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/routers/router.tsx b/src/routers/router.tsx index 2578a97..ba789b9 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -31,7 +31,7 @@ import { SelfUnderstandPage } from '@/pages/SelfUnderstandPage'; import { MemberPrivateRoute } from '@/routers/MemberPrivateRoute'; export const Router = () => { - return ( + /* return ( <Routes> <Route element={<MainLayout />}> <Route path="/login" element={<RedirectPage />} /> @@ -77,5 +77,54 @@ export const Router = () => { </Route> <Route path="*" element={<Navigate to="/" replace />} /> </Routes> + ); */ + + return ( + <Routes> + <Route element={<MainLayout />}> + <Route path="/login" element={<RedirectPage />} /> + <Route path="/onboarding" element={<OnboardingPage />} /> + <Route path="/" element={<DefineTestPage />} /> + <Route path="/home" element={<HomePage />} /> + <Route path="/auth" element={<LoginPage />} /> + <Route path="/understand" element={<SelfUnderstandPage />} /> + <Route element={<MemberPrivateRoute />}> + <Route path="/program" element={<ExperienceRecommendPage />} /> + <Route path="/program/:type/:id" element={<ExperienceDetailPage />} /> + <Route path="mypage" element={<MyPageLayout />}> + <Route index element={<Navigate to="brand" replace />} /> + <Route path="brand" element={<BrandView />} /> + <Route path="persona" element={<PersonaView />} /> + <Route path="experience" element={<MyExperienceView />} /> + <Route path="settings" element={<SettingView />} /> + </Route> + </Route> + </Route> + <Route path="test" element={<TestLayout />}> + <Route path="define"> + <Route index element={<Navigate to="1" replace />}></Route> + <Route path="1" element={<DefineTestPage />} /> + <Route path=":defineId" element={<DefineResultPage />} /> + </Route> + <Route element={<MemberPrivateRoute />}> + <Route path="design"> + <Route index element={<Navigate to="1" replace />}></Route> + <Route path="1" element={<DesignStartPage />} /> + <Route path="2" element={<DesignTestPage1 />} /> + <Route path="3" element={<DesignTestPage2 />} /> + <Route path="4" element={<DesignTestPage3 />} /> + <Route path="5" element={<DesignTestPage4 />} /> + <Route path="6" element={<DesignTestPage5 />} /> + <Route path="result" element={<DesignResultPage />} /> + </Route> + <Route path="discover"> + <Route path="" element={<DiscoverStartPage />} /> + <Route path="start" element={<DiscoverTestPage />} /> + <Route path="result" element={<DiscoverResultPage />} /> + </Route> + </Route> + </Route> + <Route path="*" element={<Navigate to="/home" replace />} /> + </Routes> ); }; diff --git a/src/services/TokenService.ts b/src/services/TokenService.ts index 9d8eed1..984362f 100644 --- a/src/services/TokenService.ts +++ b/src/services/TokenService.ts @@ -88,7 +88,8 @@ class TokenService { onLogout = () => { this.removeData(); sessionStorage.clear(); - window.location.href = '/'; + // TODO: /home -> / 로 변경해야함. + window.location.href = '/home'; }; } diff --git a/vercel.json b/vercel.json index 3a48e56..f84a50f 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,3 @@ { - "rewrites": [{ "source": "/(.*)", "destination": "/" }] + "rewrites": [{ "source": "/(.*)", "destination": "/home" }] } From 905399c5113762bc80a3583fae10ef2d1b44c4a8 Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Fri, 7 Jun 2024 17:20:25 +0900 Subject: [PATCH 17/20] =?UTF-8?q?fix:=20Discover=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95=20(#77)=20-=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=A1=B0=ED=9A=8C=20=EC=A1=B0=EA=B1=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EC=9E=90=EA=B8=B0=EC=9D=B4=ED=95=B4=20?= =?UTF-8?q?=ED=99=88=EC=97=90=EC=84=9C=20=EC=A1=B0=ED=9A=8C=ED=95=A0=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=ED=95=98=EB=8A=94=20=EB=B2=84=ED=8A=BC=20=EC=95=88?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefineResultPage/ResultView.tsx | 21 +++++++++++-------- .../SelfUnderstandPage/DefineResultView.tsx | 2 +- .../SelfUnderstandPage/DiscoverResultView.tsx | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/DefineResultPage/ResultView.tsx b/src/components/DefineResultPage/ResultView.tsx index 425e9a3..fd498e5 100644 --- a/src/components/DefineResultPage/ResultView.tsx +++ b/src/components/DefineResultPage/ResultView.tsx @@ -8,9 +8,10 @@ import { DefineResult } from '@/types/test.type'; interface ResultViewProps { result: DefineResult; + showRetestButton?: boolean; } -export const ResultView = ({ result }: ResultViewProps) => { +export const ResultView = ({ result, showRetestButton = true }: ResultViewProps) => { const navigate = useNavigate(); return ( @@ -26,14 +27,16 @@ export const ResultView = ({ result }: ResultViewProps) => { <CardSection result={result} /> <DescriptionSection result={result} /> </StyledContent> - <StyledPlainButton - variant="primary2" - onClick={() => { - navigate('/test/define/1'); - }} - > - 다시 테스트 하기 - </StyledPlainButton> + {showRetestButton && ( + <StyledPlainButton + variant="primary2" + onClick={() => { + navigate('/test/define/1'); + }} + > + 다시 테스트 하기 + </StyledPlainButton> + )} </StyledInnerContainer> ); }; diff --git a/src/components/SelfUnderstandPage/DefineResultView.tsx b/src/components/SelfUnderstandPage/DefineResultView.tsx index 64366dd..f2cb6b7 100644 --- a/src/components/SelfUnderstandPage/DefineResultView.tsx +++ b/src/components/SelfUnderstandPage/DefineResultView.tsx @@ -22,7 +22,7 @@ export const DefineResultView = () => { if (defineResult) return ( <StyledContainer> - <ResultView result={defineResult} /> + <ResultView result={defineResult} showRetestButton={false} /> </StyledContainer> ); diff --git a/src/components/SelfUnderstandPage/DiscoverResultView.tsx b/src/components/SelfUnderstandPage/DiscoverResultView.tsx index 16db602..c7deb15 100644 --- a/src/components/SelfUnderstandPage/DiscoverResultView.tsx +++ b/src/components/SelfUnderstandPage/DiscoverResultView.tsx @@ -3,9 +3,9 @@ import { NoResultSection } from '@/components/SelfUnderstandPage/NoResultTemplat import { useGetDiscoverResult } from '@/hooks/useGetDiscoverResult'; export const DiscoverResultView = () => { - const { loading, error, isTest } = useGetDiscoverResult(); + const { loading, isTest } = useGetDiscoverResult(); - if (loading || error) return null; + if (loading) return null; if (isTest) return <ResultView />; From 40d8f2c88ac6b27057e6abf4f31ef2645171f1ad Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Fri, 7 Jun 2024 17:21:23 +0900 Subject: [PATCH 18/20] =?UTF-8?q?fix:=20Discover=20=EC=B1=84=ED=8C=85=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=A4=91=20=EC=A4=84=EB=B0=94=EA=BF=88?= =?UTF-8?q?=EC=9D=80=20=EB=8B=A4=EB=A5=B8=20=EB=A7=90=ED=92=8D=EC=84=A0?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DiscoverTestPage/SpeechBox.tsx | 2 +- src/utils/transformDataToMessages.ts | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/DiscoverTestPage/SpeechBox.tsx b/src/components/DiscoverTestPage/SpeechBox.tsx index 9104e07..66c6aee 100644 --- a/src/components/DiscoverTestPage/SpeechBox.tsx +++ b/src/components/DiscoverTestPage/SpeechBox.tsx @@ -5,7 +5,7 @@ import { ReactComponent as User } from '@/assets/icons/user.svg'; import { userService } from '@/services/UserService'; interface SpeechBoxProps { - children: string; + children: React.ReactNode; isUser?: boolean; isContinuous: boolean; isEnd: boolean; diff --git a/src/utils/transformDataToMessages.ts b/src/utils/transformDataToMessages.ts index 514b299..a866305 100644 --- a/src/utils/transformDataToMessages.ts +++ b/src/utils/transformDataToMessages.ts @@ -14,9 +14,21 @@ export const transformDataToMessages = (data: { [key: string]: ChattingStage }) Object.keys(data).forEach((stageKey) => { const stage = data[stageKey]; - messages.push({ type: 'question', text: stage.question, user: 'chatbot' }); - messages.push({ type: 'answer', text: stage.answer, user: 'user' }); - messages.push({ type: 'reaction', text: stage.reaction, user: 'chatbot' }); + + // 문제 텍스트를 \n 기준으로 나누어 처리 + stage.question.split('\n').forEach((questionPart) => { + messages.push({ type: 'question', text: questionPart, user: 'chatbot' }); + }); + + // 답변 텍스트를 \n 기준으로 나누어 처리 + stage.answer.split('\n').forEach((answerPart) => { + messages.push({ type: 'answer', text: answerPart, user: 'user' }); + }); + + // 반응 텍스트를 \n 기준으로 나누어 처리 + stage.reaction.split('\n').forEach((reactionPart) => { + messages.push({ type: 'reaction', text: reactionPart, user: 'chatbot' }); + }); }); return messages; From 980482ef7965fe8fdc73bb739effe2018fad8681 Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Sat, 8 Jun 2024 01:59:44 +0900 Subject: [PATCH 19/20] =?UTF-8?q?refactor:=20=EC=B1=97=EB=B4=87=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#7?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefineTestPage/ToastMessage.tsx | 1 - .../DiscoverResultPage/ResultView.tsx | 117 ++++++------------ .../DiscoverTestPage/ChattingBox.tsx | 73 ++++++----- .../DiscoverTestPage/RightSidebar.tsx | 50 +++++--- .../DiscoverTestPage/SummaryCard.tsx | 21 ++-- .../common/Button/CategoryButton.tsx | 2 +- src/constants/defineChip.ts | 2 +- src/constants/discover.ts | 8 ++ src/hooks/useGetDiscoverResult.ts | 13 +- src/hooks/useSummarySessionStorage.ts | 16 ++- src/pages/DefineTestPage.tsx | 1 - src/pages/DiscoverTestPage.tsx | 8 +- src/types/test.type.ts | 5 + 13 files changed, 161 insertions(+), 156 deletions(-) diff --git a/src/components/DefineTestPage/ToastMessage.tsx b/src/components/DefineTestPage/ToastMessage.tsx index 1fb059d..ef0348b 100644 --- a/src/components/DefineTestPage/ToastMessage.tsx +++ b/src/components/DefineTestPage/ToastMessage.tsx @@ -11,7 +11,6 @@ export const ToastMessage = () => { const [show, setShow] = useState(false); useEffect(() => { - console.log(toast.show, toast.isShown); if (toast.show && !toast.isShown) { const setShowTimeout = setTimeout(() => { setShow(true); diff --git a/src/components/DiscoverResultPage/ResultView.tsx b/src/components/DiscoverResultPage/ResultView.tsx index 9d43b75..da62215 100644 --- a/src/components/DiscoverResultPage/ResultView.tsx +++ b/src/components/DiscoverResultPage/ResultView.tsx @@ -3,74 +3,25 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styled, { css } from 'styled-components'; +import { SummaryCard } from '@/components/DiscoverTestPage/SummaryCard'; import { CategoryButton } from '@/components/common/Button/CategoryButton'; import { PlainButton } from '@/components/common/Button/PlainButton'; -import { useGetDiscoverKeywordResult } from '@/hooks/useGetDiscoverResult'; +import { DISCOVER_CATEGORY_LIST } from '@/constants/discover'; +import { useGetDiscoverKeywordResult, useGetDiscoverSummary } from '@/hooks/useGetDiscoverResult'; import { userService } from '@/services/UserService'; import { moveDown, moveLeft, moveRight, moveUp } from '@/styles'; -export const DISCOVER_CATEGORY_LIST: { [key: string]: string } = { - all: '전체', - health: '건강', - career: '커리어', - love: '사랑', - leisure: '여가', -}; - export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) => { const [selectedCategory, setSelectedCategory] = useState<string>('all'); - const [summary, setSummary] = useState<{ [key: string]: string[] } | undefined>(undefined); const navigate = useNavigate(); const { data: allKeywords, loading: allKeywordsLoading } = useGetDiscoverKeywordResult(); + const { data: summary, loading: summaryLoading } = useGetDiscoverSummary(); - if (allKeywordsLoading) { + if (allKeywordsLoading || summaryLoading) { return <div>Loading...</div>; } - /* useEffect(() => { - const fetchAllKeywords = async () => { - try { - const response = await personaAPI.getDiscoverAllKeyword(); - setCategoryKeywords({ ...categoryKeywords, all: response.payload.keywords }); - } catch (error) { - console.error('Failed to fetch all keywords:', error); - } - }; - - const fetchSummary = async () => { - try { - const response = await personaAPI.getDefaultSummary(); - setSummary(response.payload); - } catch (error) { - console.error('Failed to fetch summary:', error); - } - }; - - fetchAllKeywords(); - fetchSummary(); - }, []); */ - - /*useEffect(() => { - const fetchCategoryKeywords = async (category: string) => { - if (categoryKeywords[category]) { - try { - const response = await personaAPI.getDiscoverCategoryKeyword(category); - setCategoryKeywords((prev) => ({ - ...prev, - [category]: response.payload.keywords, // Assuming the payload has a keywords array - })); - } catch (error) { - console.error(`Failed to fetch keywords for category ${category}:`, error); - } - } - }; - - if (selectedCategory !== 'all') { - fetchCategoryKeywords(selectedCategory); - } - }, [selectedCategory, categoryKeywords]); */ - return ( <StyledContainer $showSummary={showSummary}> <StyledInnerContainer> @@ -114,32 +65,38 @@ export const ResultView = ({ showSummary = false }: { showSummary?: boolean }) = <span className="highlight">상위 6개</span>만 보여주고 있어요! </div> )} - {/* {showSummary && ( - <ResultSection> - <div className="title">{`${userService.getUserNickname()}님과의 대화한 내용을 요약했어요!`}</div> - <div className="card-container"> - {selectedCategory === 'all' && - summary && - Object.values(summary) - .map((arr) => (arr.length > 0 ? arr[0] : '')) - .map( - (item, index) => - item !== null && - item !== '' && ( - <SummaryCard - category={Object.keys(CATEGORY_LIST)[index + 1]} - descriptions={[item]} - /> - ) - )} - {selectedCategory !== 'all' && - summary && - summary[selectedCategory].map((item) => ( - <SummaryCard key={item} category={selectedCategory} descriptions={[item]} /> - ))} - </div> - </ResultSection> - )} */} + {showSummary && ( + <ResultSection> + <div className="title">{`${userService.getUserNickname()}님과의 대화한 내용을 요약했어요!`}</div> + <div className="card-container"> + {selectedCategory === 'all' && + summary && + Object.values(summary) + .map((arr) => (arr.length > 0 ? arr[0] : '')) + .map( + (item, index) => + item !== null && + item !== '' && ( + <SummaryCard + category={Object.keys(DISCOVER_CATEGORY_LIST)[index + 1]} + question={item.question} + answer={item.answer} + /> + ) + )} + {selectedCategory !== 'all' && + summary && + summary[selectedCategory].map((item) => ( + <SummaryCard + key={item.question} + category={selectedCategory} + question={item.question} + answer={item.answer} + /> + ))} + </div> + </ResultSection> + )} </div> ) : ( <div className="no-result"> diff --git a/src/components/DiscoverTestPage/ChattingBox.tsx b/src/components/DiscoverTestPage/ChattingBox.tsx index ed1df64..c9654de 100644 --- a/src/components/DiscoverTestPage/ChattingBox.tsx +++ b/src/components/DiscoverTestPage/ChattingBox.tsx @@ -14,6 +14,7 @@ import { DefaultInput } from '@/components/common/Input/DefaultInput'; import { ResetChatModal } from '@/components/common/Modal/ResetChatModal'; import { CATEGORY_TYPE } from '@/constants/discover'; import { useChatSessionStorage } from '@/hooks/useChatSessionStorage'; +import { DiscoverSummary } from '@/types/test.type'; import { ChattingList, transformDataToMessages } from '@/utils/transformDataToMessages'; const NOTICE = @@ -24,7 +25,10 @@ interface ChattingBoxProps { endCategory: string[]; setEndCategory: (endCategory: string[]) => void; resetSummary: (category: string) => void; - updateSummary: (category: string, updateFunction: (prevSummary: string[]) => string[]) => void; + updateSummary: ( + category: string, + updateFunction: (prevSummary: DiscoverSummary[]) => DiscoverSummary[] + ) => void; } const defaultOptions = { @@ -43,6 +47,7 @@ export const ChattingBox = ({ resetSummary, updateSummary, }: ChattingBoxProps) => { + const [initialDataLoaded, setInitialDataLoaded] = useState(false); const [activeResetModal, setActiveResetModal] = useState(false); const [resetCategory, setResetCategory] = useState<string | null>(null); // 추가된 상태 const [loading, setLoading] = useState(false); @@ -65,11 +70,17 @@ export const ChattingBox = ({ if (selectedCategory !== '' && categoryValue.questionCount < 3) { try { setLoading(true); - const res = await personaAPI.getQuestion(selectedCategory); - setChattingId(res.payload.chatting_id); + const response = await personaAPI.getQuestion(selectedCategory); + setChattingId(response.payload.chatting_id); + + const questionParts = response.payload.question.split('\n'); updateChattingList((prev) => [ ...prev, - { type: 'question', text: res.payload.question, user: 'chatbot' }, + ...questionParts.map((part: string) => ({ + type: 'question', + text: part, + user: 'chatbot', + })), ]); } catch (error) { window.alert('채팅을 불러오는데 실패했습니다.'); @@ -84,13 +95,13 @@ export const ChattingBox = ({ try { const res = await personaAPI.getDefaultChatting(selectedCategory); const transformedMessages = transformDataToMessages(res.payload); + setInitialDataLoaded(true); setChattingList(transformedMessages); + const messageCount = Object.keys(res.payload).length; setQuestionCount(messageCount); - return messageCount; } catch (error) { - console.log(error); - return 0; + null; } }; @@ -98,11 +109,20 @@ export const ChattingBox = ({ try { setLoading(true); const response = await personaAPI.postAnswer(categoryValue.chattingId, input); + + const reactionParts = response.payload.reaction.split('\n'); updateChattingList((prev: ChattingList[]) => [ ...prev, - { type: 'reaction', text: response.payload.reaction, user: 'chatbot' }, + ...reactionParts.map((part: string) => ({ + type: 'reaction', + text: part, + user: 'chatbot', + })), + ]); + updateSummary(selectedCategory, (prev) => [ + ...prev, + { question: response.payload.question, answer: response.payload.summary }, ]); - updateSummary(selectedCategory, (prev) => [...prev, response.payload.summary]); const newQuestionCount = categoryValue.questionCount + 1; setQuestionCount(newQuestionCount); @@ -133,24 +153,18 @@ export const ChattingBox = ({ }; useEffect(() => { + // 처음 진입 시, 기본 대화 불러오기 + setInitialDataLoaded(false); if (selectedCategory !== '') { - getHistory().then((res) => { - if (res !== undefined && res < 3) { - getNewQuestion(); - } - }); + getHistory(); } }, [selectedCategory]); useEffect(() => { - if ( - selectedCategory !== '' && - categoryValue.questionCount < 3 && - categoryValue.questionCount > 0 - ) { + if (initialDataLoaded && selectedCategory !== '' && categoryValue.questionCount < 3) { getNewQuestion(); } - }, [categoryValue.questionCount, selectedCategory]); + }, [categoryValue.questionCount, initialDataLoaded]); useEffect(() => { scrollToBottom(); @@ -162,6 +176,7 @@ export const ChattingBox = ({ <ResetChatModal onClose={() => { setActiveResetModal(false); + navigate(`/test/discover/start?category=${resetCategory}`); setResetCategory(null); }} onReset={() => { @@ -173,7 +188,6 @@ export const ChattingBox = ({ setChattingList([]); setActiveResetModal(false); navigate(`/test/discover/start?category=${resetCategory}`); - getNewQuestion(); // 초기화 후 새 질문 가져오기 }); }} category={resetCategory} @@ -200,6 +214,7 @@ export const ChattingBox = ({ setActiveResetModal(true); } }} + disabled={loading} > {CATEGORY_TYPE[category].title} </CategoryButton> @@ -221,9 +236,11 @@ export const ChattingBox = ({ </SpeechBox> ))} {loading && ( - <StyledLoading> - <Lottie animationData={defaultOptions.animationData} /> - </StyledLoading> + <SpeechBox isContinuous={false} isEnd={true}> + <LottieWrapper> + <Lottie animationData={defaultOptions.animationData} /> + </LottieWrapper> + </SpeechBox> )} <div ref={chatEndRef} /> {/* 스크롤 위치 조정을 위한 요소 */} </StyledChatting> @@ -309,10 +326,6 @@ const StyledNotice = styled.div` white-space: pre-wrap; `; -const StyledLoading = styled.div` - width: 60px; - padding: 10px; - margin-left: 52px; - border-radius: 0 8px 8px 8px; - background: ${({ theme }) => theme.color.white}; +const LottieWrapper = styled.div` + width: 40px; `; diff --git a/src/components/DiscoverTestPage/RightSidebar.tsx b/src/components/DiscoverTestPage/RightSidebar.tsx index b5f54d5..f93fe1e 100644 --- a/src/components/DiscoverTestPage/RightSidebar.tsx +++ b/src/components/DiscoverTestPage/RightSidebar.tsx @@ -8,26 +8,44 @@ import { SummaryCard } from '@/components/DiscoverTestPage/SummaryCard'; import Scrollbar from '@/components/Scrollbar'; import { PlainButton } from '@/components/common/Button/PlainButton'; import { NotFinishChatModal } from '@/components/common/Modal/NotFinishChatModal'; +import { CATEGORY_TYPE } from '@/constants/discover'; import { loadingHandlerState } from '@/recoil/loadingHandlerState'; import { loadingState } from '@/recoil/loadingState'; import { userService } from '@/services/UserService'; +import { DiscoverSummary } from '@/types/test.type'; interface RightSidebarProps { - summaryValue: { [key: string]: string[] }; + summaryValue: { [key: string]: DiscoverSummary[] }; endCategory: string[]; + deleteSummary: () => void; } -export const RightSidebar = ({ summaryValue, endCategory }: RightSidebarProps) => { +export const RightSidebar = ({ summaryValue, endCategory, deleteSummary }: RightSidebarProps) => { const [activeNotFinishModal, setActiveNotFinishModal] = useState(false); const [, setLoading] = useRecoilState(loadingState); const [loadingHandler, setLoadingHandler] = useRecoilState(loadingHandlerState); const navigate = useNavigate(); + const handleGoToResult = () => { + setLoading({ show: true, speed: 50 }); + setLoadingHandler({ + ...loadingHandler, + handleCompleted: () => { + deleteSummary(); + Object.keys(CATEGORY_TYPE).forEach((category) => { + window.sessionStorage.removeItem(`selpiece-discover-${category}`); + }); + window.sessionStorage.removeItem('selpiece-discover-summary'); + navigate(`/test/discover/result`); + }, + }); + }; + const handleResultButton = () => { if (endCategory.length < 4) { setActiveNotFinishModal(true); } else { - setLoading({ show: true, speed: 50 }); + handleGoToResult(); } }; @@ -40,13 +58,7 @@ export const RightSidebar = ({ summaryValue, endCategory }: RightSidebarProps) = }} onConfirm={() => { setActiveNotFinishModal(false); - setLoading({ show: true, speed: 50 }); - setLoadingHandler({ - ...loadingHandler, - handleCompleted: () => { - navigate(`/test/discover/result`); - }, - }); + handleGoToResult(); }} endCategory={endCategory} /> @@ -54,15 +66,15 @@ export const RightSidebar = ({ summaryValue, endCategory }: RightSidebarProps) = <StyledContainer> <StyledSummaryContainer> <div className="title">{userService.getUserNickname()}님의 답변을 요약중이에요!</div> - {Object.keys(summaryValue).map( - (key, index) => - summaryValue[key].length > 0 && ( - <SummaryCard - key={`${key}-${index}`} - category={key} - descriptions={summaryValue[key]} - /> - ) + {Object.keys(summaryValue).map((category) => + summaryValue[category].map((summary) => ( + <SummaryCard + key={summary.question} + category={category} + question={summary.question} + answer={summary.answer} + /> + )) )} </StyledSummaryContainer> <StyledButtonContainer> diff --git a/src/components/DiscoverTestPage/SummaryCard.tsx b/src/components/DiscoverTestPage/SummaryCard.tsx index 0c3d333..9af1acc 100644 --- a/src/components/DiscoverTestPage/SummaryCard.tsx +++ b/src/components/DiscoverTestPage/SummaryCard.tsx @@ -4,11 +4,11 @@ import { CATEGORY_TYPE } from '@/constants/discover'; interface SummaryCardProps { category: string; - title?: string; - descriptions: string[]; + question: string; + answer: string; } -export const SummaryCard = ({ category, title, descriptions }: SummaryCardProps) => { +export const SummaryCard = ({ category, question, answer }: SummaryCardProps) => { const Icon = CATEGORY_TYPE[category].icon; return ( @@ -18,13 +18,8 @@ export const SummaryCard = ({ category, title, descriptions }: SummaryCardProps) <span>{CATEGORY_TYPE[category].title}</span> </StyledHeader> <StyledContent> - {title && <div className="title">{title}</div>} - {descriptions.length > 0 && - descriptions.map((description) => ( - <div key={description} className="description"> - • {description} - </div> - ))} + {question && <div className="question">{question}</div>} + {answer && <div className="answer">{answer}</div>} </StyledContent> </StyledContainer> ); @@ -62,15 +57,15 @@ const StyledHeader = styled.div` `; const StyledContent = styled.div` - .title { + text-align: left; + .question { ${({ theme }) => theme.font.desktop.label1m}; color: ${({ theme }) => theme.color.gray800}; margin-bottom: 4px; } - .description { + .answer { ${({ theme }) => theme.font.desktop.label2}; color: ${({ theme }) => theme.color.gray500}; - text-align: left; } `; diff --git a/src/components/common/Button/CategoryButton.tsx b/src/components/common/Button/CategoryButton.tsx index 56ad491..4b53301 100644 --- a/src/components/common/Button/CategoryButton.tsx +++ b/src/components/common/Button/CategoryButton.tsx @@ -1,6 +1,6 @@ import styled, { css } from 'styled-components'; -interface CategoryButtonProps extends React.HTMLAttributes<HTMLButtonElement> { +interface CategoryButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> { done?: boolean; active?: boolean; } diff --git a/src/constants/defineChip.ts b/src/constants/defineChip.ts index 36a80f2..72a3f9f 100644 --- a/src/constants/defineChip.ts +++ b/src/constants/defineChip.ts @@ -53,7 +53,7 @@ export const CHIP_DATA3 = [ '조심성 많은', '세밀한', '계획적인', - '안정적', + '안정적인', '완고한', '책임감 있는', ]; diff --git a/src/constants/discover.ts b/src/constants/discover.ts index 7390972..0c490d3 100644 --- a/src/constants/discover.ts +++ b/src/constants/discover.ts @@ -32,3 +32,11 @@ export const CATEGORY_TYPE: DiscoverCategoryType = { icon: LeisureIcon, }, }; + +export const DISCOVER_CATEGORY_LIST: { [key: string]: string } = { + all: '전체', + health: '건강', + career: '커리어', + love: '사랑', + leisure: '여가', +}; diff --git a/src/hooks/useGetDiscoverResult.ts b/src/hooks/useGetDiscoverResult.ts index 8a19af0..a1df517 100644 --- a/src/hooks/useGetDiscoverResult.ts +++ b/src/hooks/useGetDiscoverResult.ts @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react'; import { personaAPI } from '@/apis/personaAPI'; import { DISCOVER_CATEGORY_LIST } from '@/constants/discover'; import { userService } from '@/services/UserService'; +import { DiscoverSummary } from '@/types/test.type'; export const useGetDiscoverResult = () => { const [loading, setLoading] = useState(false); @@ -58,7 +59,6 @@ export const useGetDiscoverKeywordResult = () => { responses.forEach((response, index) => { const categoryKey = Object.keys(DISCOVER_CATEGORY_LIST)[index]; - console.log('Category:', categoryKey, 'Response:', response); if (response.status === 'fulfilled' && categoryKey) { updatedData[categoryKey] = response.value.payload.keywords; } @@ -82,7 +82,14 @@ export const useGetDiscoverKeywordResult = () => { }; export const useGetDiscoverSummary = () => { - const [data, setData] = useState<string[]>([]); + const [data, setData] = useState<{ + [key: keyof typeof DISCOVER_CATEGORY_LIST]: DiscoverSummary[]; + }>({ + health: [], + career: [], + love: [], + leisure: [], + }); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); @@ -91,7 +98,7 @@ export const useGetDiscoverSummary = () => { setLoading(true); try { const response = await personaAPI.getDefaultSummary(); - setData(response.payload.summary); + setData(response.payload); } catch (error) { setError(true); } finally { diff --git a/src/hooks/useSummarySessionStorage.ts b/src/hooks/useSummarySessionStorage.ts index 15b3166..925b019 100644 --- a/src/hooks/useSummarySessionStorage.ts +++ b/src/hooks/useSummarySessionStorage.ts @@ -2,8 +2,9 @@ import { useEffect, useState } from 'react'; import { personaAPI } from '@/apis/personaAPI'; import { CategoryTypeKey } from '@/constants/discover'; +import { DiscoverSummary } from '@/types/test.type'; -const initialValue: { [key: CategoryTypeKey]: string[] } = { +const initialValue: { [key: CategoryTypeKey]: DiscoverSummary[] } = { health: [], career: [], love: [], @@ -11,10 +12,9 @@ const initialValue: { [key: CategoryTypeKey]: string[] } = { }; export const useSummarySessionStorage = () => { - const [summaryValue, setSummaryValue] = useState<{ [key: CategoryTypeKey]: string[] }>( + const [summaryValue, setSummaryValue] = useState<{ [key: CategoryTypeKey]: DiscoverSummary[] }>( initialValue ); - useEffect(() => { const fetchSummary = async () => { try { @@ -48,18 +48,24 @@ export const useSummarySessionStorage = () => { const updateSummary = ( category: CategoryTypeKey, - updateFunction: (prevSummary: string[]) => string[] + updateFunction: (prevSummary: DiscoverSummary[]) => DiscoverSummary[] ) => { setSummaryValue((prev) => { - const newValue = { ...prev, [category]: updateFunction(prev[category]) }; + const updatedSummary = updateFunction(prev[category]); + const newValue = { ...prev, [category]: updatedSummary }; window.sessionStorage.setItem('selpiece-discover-summary', JSON.stringify(newValue)); return newValue; }); }; + const deleteSummary = () => { + window.sessionStorage.removeItem('selpiece-discover-summary'); + }; + return { summaryValue, resetSummary, updateSummary, + deleteSummary, } as const; }; diff --git a/src/pages/DefineTestPage.tsx b/src/pages/DefineTestPage.tsx index 6cbe23e..8418877 100644 --- a/src/pages/DefineTestPage.tsx +++ b/src/pages/DefineTestPage.tsx @@ -50,7 +50,6 @@ export const DefineTestPage = () => { const nextClickHandler = (nextStep: string) => { setStep(nextStep); - console.log(nextStep); }; const handleSubmit = () => { diff --git a/src/pages/DiscoverTestPage.tsx b/src/pages/DiscoverTestPage.tsx index 4e4c487..ab18949 100644 --- a/src/pages/DiscoverTestPage.tsx +++ b/src/pages/DiscoverTestPage.tsx @@ -21,7 +21,7 @@ export const DiscoverTestPage = () => { const navigate = useNavigate(); const [loading] = useRecoilState(loadingState); - const { summaryValue, resetSummary, updateSummary } = useSummarySessionStorage(); + const { summaryValue, resetSummary, updateSummary, deleteSummary } = useSummarySessionStorage(); useEffect(() => { const category = categoryParams.get('category'); @@ -74,7 +74,11 @@ export const DiscoverTestPage = () => { resetSummary={resetSummary} updateSummary={updateSummary} /> - <RightSidebar summaryValue={summaryValue} endCategory={endCategory} /> + <RightSidebar + summaryValue={summaryValue} + endCategory={endCategory} + deleteSummary={deleteSummary} + /> </StyledInnerContainer> </StyledContainer> </> diff --git a/src/types/test.type.ts b/src/types/test.type.ts index e97fbda..794c51c 100644 --- a/src/types/test.type.ts +++ b/src/types/test.type.ts @@ -49,3 +49,8 @@ export interface ChattingStage { export interface ChattingData { [key: string]: ChattingStage; } + +export interface DiscoverSummary { + question: string; + answer: string; +} From 4cca9b582c67066cd759d23839b8ad7491411f0a Mon Sep 17 00:00:00 2001 From: aaminha <dks4857@gmail.com> Date: Sat, 8 Jun 2024 03:19:14 +0900 Subject: [PATCH 20/20] =?UTF-8?q?design:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=8A=94=20zoom=20=EA=B8=B0=EB=B3=B8=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefineStartPage/DefineDesktopView.tsx | 2 +- .../DefineStartPage/DefineMobileView.tsx | 2 +- src/components/DefineTestPage/TestSection.tsx | 2 +- .../DesignStartPage/DesignStartView.tsx | 3 ++- src/components/DesignTest/DesignTestView.tsx | 3 ++- .../DiscoverResultPage/ResultView.tsx | 6 ++++++ src/components/common/Layout/TestLayout.tsx | 9 ++++++-- src/components/common/Modal/DefaultModal.tsx | 21 ++++++++++++++----- src/pages/DefineResultPage.tsx | 6 ++++-- src/pages/DesignResultPage.tsx | 2 +- src/pages/DiscoverStartPage.tsx | 3 ++- src/pages/DiscoverTestPage.tsx | 3 ++- src/pages/LoadingPage.tsx | 2 +- src/routers/router.tsx | 4 +++- 14 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/components/DefineStartPage/DefineDesktopView.tsx b/src/components/DefineStartPage/DefineDesktopView.tsx index c34c483..977408e 100644 --- a/src/components/DefineStartPage/DefineDesktopView.tsx +++ b/src/components/DefineStartPage/DefineDesktopView.tsx @@ -114,5 +114,5 @@ export const ViewContainer = styled.div` display: flex; overflow-x: auto; - zoom: 1.25; + //zoom: 1.25; `; diff --git a/src/components/DefineStartPage/DefineMobileView.tsx b/src/components/DefineStartPage/DefineMobileView.tsx index f41dfca..ff16e65 100644 --- a/src/components/DefineStartPage/DefineMobileView.tsx +++ b/src/components/DefineStartPage/DefineMobileView.tsx @@ -127,5 +127,5 @@ const ViewContainer = styled.div` background-size: cover; background-position: center; - zoom: 1.25; + //zoom: 1.25; `; diff --git a/src/components/DefineTestPage/TestSection.tsx b/src/components/DefineTestPage/TestSection.tsx index 4d300fa..f8df128 100644 --- a/src/components/DefineTestPage/TestSection.tsx +++ b/src/components/DefineTestPage/TestSection.tsx @@ -113,7 +113,7 @@ const StyledContainer = styled.div` padding: 24px; padding-top: 100px; - zoom: 1.25; + //zoom: 1.25; `; const StyledContentContainer = styled.div` diff --git a/src/components/DesignStartPage/DesignStartView.tsx b/src/components/DesignStartPage/DesignStartView.tsx index 020c18a..fe54cb9 100644 --- a/src/components/DesignStartPage/DesignStartView.tsx +++ b/src/components/DesignStartPage/DesignStartView.tsx @@ -87,7 +87,8 @@ const Styled1Container = styled.div` `; export const ViewContainer = styled.div` - height: var(--full-height); + //height: var(--full-height); + height: 100vh; background-image: url(${backgroundImg}); background-size: cover; diff --git a/src/components/DesignTest/DesignTestView.tsx b/src/components/DesignTest/DesignTestView.tsx index 65920ce..af4a59c 100644 --- a/src/components/DesignTest/DesignTestView.tsx +++ b/src/components/DesignTest/DesignTestView.tsx @@ -20,7 +20,8 @@ const StyledContainer = styled.section` flex-direction: column; background: ${({ theme }) => `${theme.color.primary50}`}; - min-height: var(--full-height); + //min-height: var(--full-height); + min-height: 100vh; padding: 118px 0 48px 0; diff --git a/src/components/DiscoverResultPage/ResultView.tsx b/src/components/DiscoverResultPage/ResultView.tsx index da62215..a6fa571 100644 --- a/src/components/DiscoverResultPage/ResultView.tsx +++ b/src/components/DiscoverResultPage/ResultView.tsx @@ -139,6 +139,12 @@ const StyledContainer = styled.div<{ $showSummary: boolean }>` display: flex; justify-content: center; background: linear-gradient(180deg, rgba(255, 255, 255, 0) 53.94%, #ccb3fd 100%), #fff; + + ${({ $showSummary }) => + $showSummary && + css` + zoom: 1.25; + `}; `; const StyledInnerContainer = styled.div` diff --git a/src/components/common/Layout/TestLayout.tsx b/src/components/common/Layout/TestLayout.tsx index ae597f6..0f31bdc 100644 --- a/src/components/common/Layout/TestLayout.tsx +++ b/src/components/common/Layout/TestLayout.tsx @@ -1,12 +1,17 @@ import { Outlet } from 'react-router-dom'; +import styled from 'styled-components'; import { TestNavigation } from '@/components/common/Navigation/TestNavigation'; export const TestLayout = () => { return ( - <> + <StyledContainer> <TestNavigation /> <Outlet /> - </> + </StyledContainer> ); }; + +const StyledContainer = styled.div` + zoom: 1.25; +`; diff --git a/src/components/common/Modal/DefaultModal.tsx b/src/components/common/Modal/DefaultModal.tsx index 495447a..6ebf113 100644 --- a/src/components/common/Modal/DefaultModal.tsx +++ b/src/components/common/Modal/DefaultModal.tsx @@ -2,7 +2,8 @@ import React, { useEffect } from 'react'; -import styled from 'styled-components'; +import { useLocation } from 'react-router-dom'; +import styled, { css } from 'styled-components'; interface DefaultModalProps { width?: string; @@ -15,6 +16,8 @@ export const DefaultModal = ({ height = '312px', children, }: DefaultModalProps) => { + const location = useLocation(); + useEffect(() => { document.body.style.overflow = 'hidden'; return () => { @@ -23,7 +26,7 @@ export const DefaultModal = ({ }, []); return ( - <StyledOverlay> + <StyledOverlay $isTest={location.pathname.includes('test')}> <StyledModalContainer $width={width} $height={height}> {children} </StyledModalContainer> @@ -31,14 +34,22 @@ export const DefaultModal = ({ ); }; -const StyledOverlay = styled.div` +const StyledOverlay = styled.div<{ $isTest: boolean }>` position: fixed; top: 0; left: 0; z-index: 100; - width: var(--full-width); - height: var(--full-height); + ${({ $isTest }) => + $isTest + ? css` + width: 100vw; + height: 100vh; + ` + : css` + width: var(--full-width); + height: var(--full-height); + `}; display: flex; align-items: center; diff --git a/src/pages/DefineResultPage.tsx b/src/pages/DefineResultPage.tsx index b72e132..7eb97a1 100644 --- a/src/pages/DefineResultPage.tsx +++ b/src/pages/DefineResultPage.tsx @@ -34,13 +34,15 @@ export const DefineResultPage = () => { }; const StyledLoading = styled.div` - height: var(--full-height); + //height: var(--full-height); + height: 100vh; padding-top: 90px; padding-left: 20px; `; const StyledContainer = styled.section` - min-height: var(--full-height); + //min-height: var(--full-height); + min-height: 100vh; padding: 76px 64px 40px 64px; background: ${({ theme }) => diff --git a/src/pages/DesignResultPage.tsx b/src/pages/DesignResultPage.tsx index b6429b4..c18151c 100644 --- a/src/pages/DesignResultPage.tsx +++ b/src/pages/DesignResultPage.tsx @@ -20,7 +20,7 @@ export const DesignResultPage = () => { if (persona) return ( - <ResultView style={{ minHeight: 'var(--full-height)' }} definition={persona.definition}> + <ResultView style={{ minHeight: '100vh' }} definition={persona.definition}> <PlainButton variant="gray" width="597px" diff --git a/src/pages/DiscoverStartPage.tsx b/src/pages/DiscoverStartPage.tsx index 85a2d8a..5c5bc11 100644 --- a/src/pages/DiscoverStartPage.tsx +++ b/src/pages/DiscoverStartPage.tsx @@ -86,7 +86,8 @@ const Styled1Container = styled.div` `; export const ViewContainer = styled.div` - height: var(--full-height); + //height: var(--full-height); + height: 100vh; background-image: url(${backgroundImg}); background-size: cover; diff --git a/src/pages/DiscoverTestPage.tsx b/src/pages/DiscoverTestPage.tsx index ab18949..5032fa4 100644 --- a/src/pages/DiscoverTestPage.tsx +++ b/src/pages/DiscoverTestPage.tsx @@ -86,7 +86,8 @@ export const DiscoverTestPage = () => { }; const StyledContainer = styled.div` - height: var(--full-height); + //height: var(--full-height); + height: 100vh; `; const StyledInnerContainer = styled.div` diff --git a/src/pages/LoadingPage.tsx b/src/pages/LoadingPage.tsx index 7dd9080..bdd81cf 100644 --- a/src/pages/LoadingPage.tsx +++ b/src/pages/LoadingPage.tsx @@ -58,7 +58,7 @@ const StyledContainer = styled.div` justify-content: center; width: 100%; - height: var(--full-height); + height: 100vh; padding: 20px; padding-top: 96px; diff --git a/src/routers/router.tsx b/src/routers/router.tsx index ba789b9..8833816 100644 --- a/src/routers/router.tsx +++ b/src/routers/router.tsx @@ -84,7 +84,9 @@ export const Router = () => { <Route element={<MainLayout />}> <Route path="/login" element={<RedirectPage />} /> <Route path="/onboarding" element={<OnboardingPage />} /> - <Route path="/" element={<DefineTestPage />} /> + <Route path="/" element={<TestLayout />}> + <Route path="" element={<DefineTestPage />} /> + </Route> <Route path="/home" element={<HomePage />} /> <Route path="/auth" element={<LoginPage />} /> <Route path="/understand" element={<SelfUnderstandPage />} />