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/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/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/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 4132aaf..0000000 Binary files a/src/assets/backgrounds/setting.png and /dev/null differ diff --git a/src/assets/cards/piece/connector.png b/src/assets/cards/piece/connector.png new file mode 100644 index 0000000..8e3ace3 Binary files /dev/null and b/src/assets/cards/piece/connector.png differ diff --git a/src/assets/cards/piece/creator.png b/src/assets/cards/piece/creator.png new file mode 100644 index 0000000..855478e Binary files /dev/null and b/src/assets/cards/piece/creator.png differ diff --git a/src/assets/cards/piece/encourager.png b/src/assets/cards/piece/encourager.png new file mode 100644 index 0000000..41fcd06 Binary files /dev/null and b/src/assets/cards/piece/encourager.png differ diff --git a/src/assets/cards/piece/innovator.png b/src/assets/cards/piece/innovator.png new file mode 100644 index 0000000..44e5739 Binary files /dev/null and b/src/assets/cards/piece/innovator.png differ diff --git a/src/assets/cards/piece/insighter.png b/src/assets/cards/piece/insighter.png new file mode 100644 index 0000000..d8b6ba9 Binary files /dev/null and b/src/assets/cards/piece/insighter.png differ diff --git a/src/assets/cards/piece/inventor.png b/src/assets/cards/piece/inventor.png new file mode 100644 index 0000000..1d100e9 Binary files /dev/null and b/src/assets/cards/piece/inventor.png differ diff --git a/src/assets/cards/piece/organizer.png b/src/assets/cards/piece/organizer.png new file mode 100644 index 0000000..553a566 Binary files /dev/null and b/src/assets/cards/piece/organizer.png differ diff --git a/src/assets/cards/piece/projector.png b/src/assets/cards/piece/projector.png new file mode 100644 index 0000000..30bfc4a Binary files /dev/null and b/src/assets/cards/piece/projector.png differ 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/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/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) => { - { - navigate('/test/define/1'); - }} - > - 다시 테스트 하기 - + {showRetestButton && ( + { + navigate('/test/define/1'); + }} + > + 다시 테스트 하기 + + )} ); }; diff --git a/src/components/DefineStartPage/DefineDesktopView.tsx b/src/components/DefineStartPage/DefineDesktopView.tsx index c7f502f..977408e 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 ( -
- - - - - -
- 현재 당신은 어떤 사람인가요? -
- - 문항은 총 3문항으로, 홀랜드 검사 이론을 기반으로 - 구성되어 있어요. -
- 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} - 결과 카드를 받아보세요! -
-
- - 테스트 시작하기 - -
- - - - 홀랜드 검사란, + + + + +
- 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, + 현재 당신은 어떤 사람인가요? +
+ + 문항은 총 3문항으로, 홀랜드 검사 이론을 기반으로 + 구성되어 있어요.
- 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. -
-
-
-
-
+ 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} + 결과 카드를 받아보세요! + + + + 테스트 시작하기 + + + + + + 홀랜드 검사란, +
+ 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, +
+ 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. +
+
+ + ); }; @@ -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` @@ -118,4 +113,6 @@ export const ViewContainer = styled.div` 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 be9634a..ff16e65 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 ( -
- - - - - 현재 당신은 어떤 사람인가요? - - 문항은 총 3문항으로, -
홀랜드 검사 이론을 기반으로 구성되어 있어요. -
- 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} - 결과 카드를 받아보세요! -
-
- - - 홀랜드 검사란, -
- 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, - 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. -
- - 테스트 시작하기 - -
-
-
-
-
+ + + + + 현재 당신은 어떤 사람인가요? + + 문항은 총 3문항으로, +
홀랜드 검사 이론을 기반으로 구성되어 있어요. +
+ 정의하기 테스트를 통해 나의 조각 유형을 도출하고,{' '} + 결과 카드를 받아보세요! +
+
+ + + 홀랜드 검사란, +
+ 성격 유형과 커리어의 특성을 6개의 유형으로 분류하여 육각형으로 보여주는 검사로, + 셀피스의 정의하기 테스트는 해당 이론에서 착안하여 고안되었습니다. +
+ + 테스트 시작하기 + +
+
+
+
); }; @@ -134,4 +126,6 @@ const ViewContainer = styled.div` 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 ( - - {showWarn && 키워드를 5개만 선택해 주세요!} - - 다음으로 - - 종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요! - - ); -}; - -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 ( - - {showWarn && 키워드를 5개만 선택해 주세요!} - - - 이전으로 - - - 다음으로 - - - 종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요! - - ); -}; - -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 ( - - {showWarn && 키워드를 5개만 선택해 주세요!} - - - 이전으로 - - - 결과 확인하기 - - - 종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요! - - ); -}; 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 ( - - - {CHIP_DATA1.map((text, index) => ( - handleToggle(index)} - > - {text} - - ))} - - - - ); -}; -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 ( - - - {CHIP_DATA2.map((text, index) => ( - handleToggle(index)} - > - {text} - - ))} - - - - ); -}; - -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 ( - - - {CHIP_DATA3.map((text, index) => ( - handleToggle(index)} - > - {text} - - ))} - - - - ); -}; - -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 cd820c8..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: 100vh; - - 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 ( - - - - - - - ); -}; - -export const DefineTestView2 = () => { - return ( - - - - - - - ); -}; - -export const DefineTestView3 = () => { - return ( - - - - - - - ); -}; 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 ( - <> - - - - {currentStep} - / {totalSteps} - - - - - 나에게 해당되는 키워드는 무엇인가요? - - 5개의 키워드를 선택해주세요. - - - - - ); -}; - -export const DefineTextView2 = () => { - const currentStep = 2; - const totalSteps = 3; - - return ( - <> - - - - {currentStep} - / {totalSteps} - - - - - 나에게 해당되는 키워드는 무엇인가요? - - 5개의 키워드를 선택해주세요. - - - - - ); -}; - -export const DefineTextView3 = () => { - const currentStep = 3; - const totalSteps = 3; - - return ( - <> - - - - {currentStep} - / {totalSteps} - - - - - 나에게 해당되는 키워드는 무엇인가요? - - 5개의 키워드를 선택해주세요. - - - - - ); -}; 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 ? : }; + return ( + <>{isMobile ? : } + ); }; diff --git a/src/components/DefineTestPage/TestSection.tsx b/src/components/DefineTestPage/TestSection.tsx new file mode 100644 index 0000000..f8df128 --- /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(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 ( + + + + + + {chipData.map((chip, index) => ( + + {chip} + + ))} + +
+ + {onPrev && ( + + 이전으로 + + )} + {onNext && !lastSection && ( + + 다음으로 + + )} + {onNext && lastSection && ( + + 결과 확인하기 + + )} + + + 종료하기를 누르면 해당 단계부터 이어서 검사를 진행할 수 있어요! + +
+ +
+
+
+ ); +}; + +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 ( + + +
+ {steps.indexOf(currentStep)} / {steps.length - 1} +
+ +
+ +
나에게 해당되는 키워드는 무엇인가요?
+
+ 5개의 키워드를 선택해주세요. +
+
+
+ ); +}; + +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..ef0348b --- /dev/null +++ b/src/components/DefineTestPage/ToastMessage.tsx @@ -0,0 +1,75 @@ +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(false); + const [show, setShow] = useState(false); + + useEffect(() => { + 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 ( + + 키워드를 5개만 선택해 주세요! + + ); +}; + +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/DesignStartPage/DesignStartView.tsx b/src/components/DesignStartPage/DesignStartView.tsx index f508d93..fe54cb9 100644 --- a/src/components/DesignStartPage/DesignStartView.tsx +++ b/src/components/DesignStartPage/DesignStartView.tsx @@ -87,6 +87,7 @@ const Styled1Container = styled.div` `; export const ViewContainer = styled.div` + //height: var(--full-height); height: 100vh; background-image: url(${backgroundImg}); diff --git a/src/components/DesignTest/DesignTestView.tsx b/src/components/DesignTest/DesignTestView.tsx index 5c90007..af4a59c 100644 --- a/src/components/DesignTest/DesignTestView.tsx +++ b/src/components/DesignTest/DesignTestView.tsx @@ -20,6 +20,7 @@ const StyledContainer = styled.section` flex-direction: column; background: ${({ theme }) => `${theme.color.primary50}`}; + //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 new file mode 100644 index 0000000..a6fa571 --- /dev/null +++ b/src/components/DiscoverResultPage/ResultView.tsx @@ -0,0 +1,370 @@ +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 { 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 ResultView = ({ showSummary = false }: { showSummary?: boolean }) => { + const [selectedCategory, setSelectedCategory] = useState('all'); + const navigate = useNavigate(); + + const { data: allKeywords, loading: allKeywordsLoading } = useGetDiscoverKeywordResult(); + const { data: summary, loading: summaryLoading } = useGetDiscoverSummary(); + + if (allKeywordsLoading || summaryLoading) { + return
Loading...
; + } + + return ( + + + +
이해하기 테스트의 결과에요!
+
+ 셀피스에서 분석한 지금까지의{' '} + {userService.getUserNickname()}님 + 은, 이런 사람이에요. +
+ + {Object.keys(DISCOVER_CATEGORY_LIST).map((category) => ( + setSelectedCategory(category)} + > + {DISCOVER_CATEGORY_LIST[category]} + + ))} + +
+ {allKeywords && ( + + {allKeywords[selectedCategory].length !== 0 ? ( +
+ {showSummary && ( +
+ 상위 6개만 보여주고 있어요! +
+ )} + + {allKeywords[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) => ( + + ))} +
+
+ )} +
+ ) : ( +
+ +
+ 아직{' '} + {DISCOVER_CATEGORY_LIST[selectedCategory]}{' '} + 테스트를 완료하지 않았어요! +
+
남은 테스트를 진행해주세요.
+
+ + ? + +
+ )} +
+ )} + {allKeywords && allKeywords[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; + + ${({ $showSummary }) => + $showSummary && + css` + zoom: 1.25; + `}; +`; + +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/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(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 = ({ { 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} @@ -221,9 +236,11 @@ export const ChattingBox = ({ ))} {loading && ( - - - + + + + + )}
{/* 스크롤 위치 조정을 위한 요소 */} @@ -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) =
{userService.getUserNickname()}님의 답변을 요약중이에요!
- {Object.keys(summaryValue).map( - (key, index) => - summaryValue[key].length > 0 && ( - - ) + {Object.keys(summaryValue).map((category) => + summaryValue[category].map((summary) => ( + + )) )}
diff --git a/src/components/DiscoverTestPage/SpeechBox.tsx b/src/components/DiscoverTestPage/SpeechBox.tsx index b7ceb1a..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; @@ -20,7 +20,7 @@ export const SpeechBox = ({ children, isUser = false, isContinuous, isEnd }: Spe
) : isUser ? ( - + ) : ( profile 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) {CATEGORY_TYPE[category].title} - {title &&
{title}
} - {descriptions.length > 0 && - descriptions.map((description) => ( -
- • {description} -
- ))} + {question &&
{question}
} + {answer &&
{answer}
}
); @@ -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/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 ( @@ -14,7 +15,7 @@ const AdvertiseCard = () => {