diff --git a/public/icon/twitter.svg b/public/icon/twitter.svg index c4081ad..5f77001 100644 --- a/public/icon/twitter.svg +++ b/public/icon/twitter.svg @@ -1 +1,15 @@ - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/src/App.tsx b/src/App.tsx index 01ec187..683a5af 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,17 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom"; +import { initGA, trackPageView } from "@/libs/analytics"; import Home from "@/pages/Home"; import SelectInfo from "@/pages/SelectInfo"; import Chat from "@/pages/Chat"; import ChatRecommend from "@/pages/ChatRecommend"; import ChatTips from "@/pages/ChatTips"; -import ChatTemporature from "@/pages/ChatTemporature"; +import ChatTemperature from "@/pages/ChatTemperature"; import Content from "@/pages/Content"; import Login from "@/pages/Login"; import MyInfo from "@/pages/MyInfo"; @@ -18,9 +19,10 @@ import KaKaoLogin from "@/pages/KaKaoLogin"; import MbtiTestIntro from "@/pages/MbtiTestIntro"; import MbtiTestQuestions from "@/pages/MbtiTestQuestions"; import MbtiTestResult from "@/pages/MbtiTestResult"; -import CenteredLayout from "@/components/CenteredLayout"; -import { initGA, trackPageView } from "@/libs/analytics"; import Error from "@/pages/Error"; +import CenteredLayout from "@/components/CenteredLayout"; +import ToastMessage from "@/components/ToastMessage"; +import useAuthStore from "@/store/useAuthStore"; const PageTracker = () => { const location = useLocation(); @@ -69,26 +71,46 @@ const PageTracker = () => { }; const App = () => { + const { logout } = useAuthStore(); + const [toastMessage, setToastMessage] = useState(""); + const storageAuth = localStorage.getItem("auth-storage"); + const parsedAuth = storageAuth ? JSON.parse(storageAuth).state : null; + + const checkSession = () => { + const expirationTime = new Date( + new Date(parsedAuth.loginTime).getTime() + 24 * 60 * 60 * 1000 + ); + const now = new Date(); + if (now > expirationTime) { + setToastMessage("로그인 세션이 만료되었습니다."); + logout(); + } + }; + useEffect(() => { initGA(); + if (parsedAuth) checkSession(); }, []); return ( + {toastMessage && ( + setToastMessage("")} + /> + )} } /> } /> } /> + } /> + } /> } - /> - } /> - } + path="/chat-temperature/:conversationId" + element={} /> } /> } /> diff --git a/src/api/axios.ts b/src/api/axios.ts index 8e6c154..392b431 100644 --- a/src/api/axios.ts +++ b/src/api/axios.ts @@ -3,7 +3,7 @@ import useAuthStore from "@/store/useAuthStore"; const instance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, - timeout: 10000, + timeout: 100000, headers: { "Content-Type": "application/json" } @@ -12,7 +12,7 @@ const instance = axios.create({ // 인증 절차가 필요한 API는 authInstance로 HTTP요청 const authInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, - timeout: 10000, + timeout: 100000, headers: { "Content-Type": "application/json" } diff --git a/src/components/button/TwitterShareButton.tsx b/src/components/button/TwitterShareButton.tsx index 432f2f0..daa49c0 100644 --- a/src/components/button/TwitterShareButton.tsx +++ b/src/components/button/TwitterShareButton.tsx @@ -6,8 +6,7 @@ const TwitterShareButton = ({ title }: { title: string }) => { href={`https://twitter.com/intent/tweet?text=${title}&url=${currentUrl}`} className="flex flex-col items-center gap-1" > - 트위터 아이콘 -

트위터

+ 트위터 아이콘 ); }; diff --git a/src/components/button/UrlCopyButton.tsx b/src/components/button/UrlCopyButton.tsx index f174e38..f5640f8 100644 --- a/src/components/button/UrlCopyButton.tsx +++ b/src/components/button/UrlCopyButton.tsx @@ -1,23 +1,36 @@ -const UrlCopyButton = ({currentUrl} : {currentUrl : string}) => { - const handleCopy = () => { - navigator.clipboard - .writeText(currentUrl) - .then(() => { - alert("URL이 복사되었습니다!"); // toast로 바꾸어야 함 -> 4.10 정준영 - }) - .catch((err) => { - console.error("URL 복사 실패:", err); - }); - }; - - return ( +import ToastMessage from "@/components/ToastMessage"; +import { useState } from "react"; + +const UrlCopyButton = ({ currentUrl }: { currentUrl: string }) => { + const [toastMessage, setToastMessage] = useState(""); + + const handleCopy = () => { + navigator.clipboard + .writeText(currentUrl) + .then(() => { + setToastMessage("URL을 복사했습니다."); + }) + .catch((err) => { + console.error("URL 복사 실패:", err); + }); + }; + + return ( + <> - ); - }; - - export default UrlCopyButton; \ No newline at end of file + {toastMessage && ( + setToastMessage("")} + /> + )} + + ); +}; + +export default UrlCopyButton; diff --git a/src/components/tips/TipsMenu.tsx b/src/components/tips/TipsMenu.tsx index 5a3bc25..a2bdd59 100644 --- a/src/components/tips/TipsMenu.tsx +++ b/src/components/tips/TipsMenu.tsx @@ -2,9 +2,13 @@ import trackClickEvent from "@/utils/trackClickEvent"; import { Link } from "react-router-dom"; const TipsMenu = ({ - mode + mode, + mbti, + conversationId }: { - mode: "topic" | "conversation" | "temporature"; + mode: "topic" | "conversation" | "temperature"; + mbti?: string; + conversationId?: string; }) => { let text = ""; let tagElement = ""; @@ -16,19 +20,19 @@ const TipsMenu = ({ text = "대화 주제 추천"; tagElement = "대화 주제 추천"; imageUrl = "/icon/starbubble.svg"; - href = "/chat-recommend"; + href = `/chat-recommend/${mbti}`; break; case "conversation": text = "대화 꿀팁"; tagElement = "대화 꿀팁"; imageUrl = "/icon/lightbulb.svg"; - href = "/chat-tips"; + href = `/chat-tips/${mbti}`; break; - case "temporature": + case "temperature": text = "현재 대화의 온도 측정하기"; tagElement = "대화의 온도"; imageUrl = "/icon/thermometer.svg"; - href = "/chat-temporature"; + href = `/chat-temperature/${conversationId}`; break; default: return; diff --git a/src/components/tips/TipsMenuContainer.tsx b/src/components/tips/TipsMenuContainer.tsx index 4a70d98..3f5eebb 100644 --- a/src/components/tips/TipsMenuContainer.tsx +++ b/src/components/tips/TipsMenuContainer.tsx @@ -1,11 +1,17 @@ import TipsMenu from "@/components/tips/TipsMenu"; -const TipsMenuContainer = () => { +const TipsMenuContainer = ({ + conversationId, + mbti +}: { + conversationId: string; + mbti: string; +}) => { return ( <> - - - + + + ); }; diff --git a/src/index.css b/src/index.css index 6b64713..311dbdd 100644 --- a/src/index.css +++ b/src/index.css @@ -30,6 +30,7 @@ body { main { @media screen and (min-width: 360px) { + background-color: white; font-size: 14px; width: 360px; } diff --git a/src/mock/chatRecommend.ts b/src/mock/chatRecommend.ts new file mode 100644 index 0000000..977f83e --- /dev/null +++ b/src/mock/chatRecommend.ts @@ -0,0 +1,165 @@ +export const chatRecommend: Record< + string, + { title: string; description: string } +> = { + INTJ: { + title: "INTJ (용의주도한 전략가)와 대화 주제 추천", + description: `INTJ는 미래지향적이고 전략적으로 사고해요. 현실의 비효율을 개선하거나, 지적인 주제에서 깊이 있게 탐구하는 걸 좋아해요 + + 1. "앞으로 10년 후엔 어떤 직업이 뜰까?" + 2. "너만의 문제 해결 방식이 있어?" + 3. "최적의 루틴이 있다면 어떤 구조일까?" + 4. "세상이 돌아가는 방식 중 비효율적이라 생각하는 건?" + 5. "인공지능 시대에 인간의 역할은 뭘까?"` + }, + INTP: { + title: "INTP (논리적인 사색가)와 대화 주제 추천", + description: `INTP는 철학적이고 복잡한 논리 구조에 매료돼요. 상상력과 논리가 교차하는 질문이면 한도 끝도 없이 빠져들어요 + + 1. "세상에서 가장 논리적으로 이상한 건 뭐라고 생각해?" + 2. "의식이란 과연 존재하는 걸까?" + 3. "가짜 기억이 실제 감정보다 의미 있을 수 있을까?" + 4. "우리가 사는 이 세계가 시뮬레이션이라면?" + 5. "시간이 역행한다면 사회는 어떻게 바뀔까?"` + }, + ENTJ: { + title: "ENTJ (결단력 있는 리더)와 대화 주제 추천", + description: `ENTJ는 성과와 성장, 그리고 리더십에 관심이 많아요. 논리적인 목표 설정과 시스템화된 사고방식이 대화에서 살아나요. + + 1. "효율을 극대화하려면 가장 먼저 바꿔야 할 건?" + 2. "최고의 조직문화란 뭘까?" + 3. "리더는 타고나는 걸까, 만들어지는 걸까?" + 4. "네가 생각하는 성공 공식은?" + 5. "투자하고 싶은 사업 아이템 있어?"` + }, + ENTP: { + title: "ENTP (창의적인 도전가)와 대화 주제 추천", + description: `ENTP는 반항적이고 새로운 관점에 목말라 있어요. 토론과 아이디어 싸움에서 에너지를 얻는 편이라 엉뚱하지만 깊은 주제가 매력을 느껴요. + + 1. "만약 세상에 법이 없다면 무슨 일이 벌어질까?" + 2. "모든 정보를 다 공유하면 더 나은 사회가 될까?" + 3. "너무 급진적인 변화가 필요한 분야는?" + 4. "요즘 너만 알고 있는 신기한 서비스 있어?" + 5. "가장 말도 안 되는 아이디어 하나 말해줘!"` + }, + INFJ: { + title: "INFJ (통찰력 있는 조언자)와 대화 주제 추천", + description: `INFJ는 의미와 깊이를 추구하는 편이에요. 감정적이되 매우 지적이라, 인생·사람·철학을 주제로 깊은 대화를 좋아해요. + + 1. "사람들의 무의식적인 패턴은 어떻게 알 수 있을까?" + 2. "운명과 선택 중 어느 쪽이 더 중요하다고 생각해?" + 3. "내면의 성장을 위해 가장 중요한 건 뭘까?" + 4. "사람에게 진짜 위로가 되는 건 뭐라고 봐?" + 5. "넌 어떤 세상을 꿈꾸고 있어?"` + }, + INFP: { + title: "INFP (순수한 이상주의자)와 대화 주제 추천", + description: `INFP는 감정과 가치에 따라 움직여요. 진심 어린 감정 공유나 이상적인 이야기, 따뜻한 주제를 좋아해요. + + 1. "넌 어떤 가치를 지킬 때 제일 나답다고 느껴?" + 2. "가장 기억에 남는 감정은 어떤 거였어?" + 3. "이 세상이 널 이해하지 못할 때 어떻게 해?" + 4. "넌 어떤 이야기에 쉽게 울어?" + 5. "네가 만들고 싶은 세상은 어떤 모습이야?"` + }, + ENFJ: { + title: "ENFJ (따뜻한 리더)와 대화 주제 추천", + description: `ENFJ는 대인관계의 조화와 성장에 관심이 많아요. 사람 사이의 정서적 연결에 대한 대화를 즐겨요. + + 1. "사람들이 서로 더 잘 이해하려면 어떻게 해야 할까?" + 2. "진짜 소통이란 어떤 거라고 생각해?" + 3. "네가 도와주고 싶은 사람은 어떤 사람이야?" + 4. "사람을 이끄는 힘은 어디서 나올까?" + 5. "넌 어떤 상황에서 타인을 가장 잘 이해해?"` + }, + ENFP: { + title: "ENFP (열정적인 탐험가)와 대화 주제 추천", + description: `ENFP는 자유롭고 창의적인 에너지를 가진 타입이에요. 대화는 진지하되 재밌게, 영감을 주는 이야기를 좋아해요. + + 1. "하고 싶은 게 너무 많을 땐 어떻게 정해?" + 2. "네가 가장 몰입했던 순간은?" + 3. "자유롭다는 느낌은 언제 가장 강해?" + 4. "새로운 아이디어가 떠오를 땐 어때?" + 5. "내가 생각 못한 미친 아이디어 하나만 말해줘!"` + }, + ISTJ: { + title: "ISTJ (신중한 관리자)와 대화 주제 추천", + description: `ISTJ는 신뢰성과 논리, 경험 중심 사고를 중시해요. 실질적이고 명확한 주제가 안정감을 줘요. + + 1. "네가 가장 신뢰하는 원칙은 뭐야?" + 2. "효율적인 하루 루틴은 어떻게 구성돼?" + 3. "너무 감정적인 결정은 위험하다고 생각해?" + 4. "왜 일의 기준을 그렇게 중요하게 여겨?" + 5. "지금까지 배운 인생 교훈 하나 말해줄래?"` + }, + ISFJ: { + title: "ISFJ (헌신적인 수호자)와 대화 주제 추천", + description: `ISFJ는 배려와 조화를 중요시해요. 감정 표현은 조심스럽지만 따뜻한 주제엔 마음을 열기 쉬워요. + + 1. "넌 어떻게 주변 사람을 챙기고 있어?" + 2. "누군가에게 고마웠던 기억 있어?" + 3. "작은 배려가 큰 의미가 됐던 적은?" + 4. "넌 언제 마음이 가장 따뜻해져?" + 5. "네가 소중히 여기는 전통은 뭐야?"` + }, + ESTJ: { + title: "ESTJ (현실적인 관리자)와 대화 주제 추천", + description: `ESTJ는 목표지향적이고 조직적인 사람이에요. 실질적이고 일 중심의 대화를 즐겨요. + + 1. "실행력 좋은 사람의 공통점은 뭐라고 생각해?" + 2. "성과를 내기 위해 가장 중요한 건 뭐야?" + 3. "팀을 이끄는 방법 중 최고의 방식은?" + 4. "네가 본 가장 일 잘하는 사람은 어떤 사람이었어?" + 5. "결정을 빨리 내리는 기준은 뭐야?"` + }, + ESFJ: { + title: "ESFJ (다정한 조직자)와 대화 주제 추천", + description: `ESFJ는 관계 중심적이고 감정 조화를 중요시해요. 사람 사이의 연결과 공감에 관련된 이야기를 좋아해요. + + 1. "넌 어떤 상황에서 가장 공감하게 돼?" + 2. "주변 사람들 기분 잘 읽는 편이야?" + 3. "친구랑 갈등 생기면 어떻게 풀어?" + 4. "모두가 함께 잘 지내려면 뭘 해야 할까?" + 5. "감정 표현은 왜 중요하다고 생각해?"` + }, + ISTP: { + title: "ISTP (과묵한 장인)과 대화 주제 추천", + description: `ISTP는 분석적이면서도 실용적인 성향이라, 구체적이고 ‘쓸모 있는’ 대화를 좋아해요. + + 1. "요즘 푹 빠진 도구나 기술 있어?" + 2. "손으로 무언가 만들 때 어떤 기분이야?" + 3. "문제가 생기면 보통 어떻게 접근해?" + 4. "고장 난 물건 고치는 거 좋아해?" + 5. "한번쯤 해보고 싶은 실험적인 취미 있어?"` + }, + ISFP: { + title: "ISFP (조용한 예술가)와 대화 주제 추천", + description: `ISFP는 감성적이고 감각 중심적이에요. 표현적이고 잔잔한 감정을 나누는 대화를 편하게 느껴요. + + 1. "요즘 감성 터졌던 순간 있었어?" + 2. "네가 아름답다고 느끼는 건 뭐야?" + 3. "사람에게 감동 받았던 순간은?" + 4. "네 감정을 표현하는 방법은?" + 5. "네가 좋아하는 예술 작품은 뭐야?"` + }, + ESTP: { + title: "ESTP (에너지 넘치는 모험가)와 대화 주제 추천", + description: `ESTP는 즉흥적이고 활동적인 걸 좋아해서, 실시간 반응이 오고 가는 신나는 주제가 잘 맞아요. + + 1. "가장 재밌었던 즉흥 여행은?" + 2. "위험했지만 스릴 있었던 경험 있어?" + 3. "승부욕 불타올랐던 순간?" + 4. "요즘 재밌게 하는 활동이나 운동은?" + 5. "만약 하루 뭐든 할 수 있다면 뭘 해볼래?"` + }, + ESFP: { + title: "ESFP (인생을 즐기는 연예인)과 대화 주제 추천", + description: `ESFP는 밝고 에너지 넘쳐요. 즐거움, 사람, 즉흥성 관련 대화에 잘 반응하고 분위기를 업시켜줘요. + + 1. "최근에 제일 재밌었던 파티는?" + 2. "기분 좋게 사람들과 어울릴 때는 언제야?" + 3. "좋아하는 노래로 플레이리스트 하나 짜줘!" + 4. "넌 어떻게 주변을 즐겁게 만들어?" + 5. "지금 당장 하고 싶은 거 3가지!"` + } +}; diff --git a/src/mock/tips.ts b/src/mock/tips.ts new file mode 100644 index 0000000..bb776ad --- /dev/null +++ b/src/mock/tips.ts @@ -0,0 +1,159 @@ +export const tips: Record = { + INTJ: { + title: "INTJ (용의주도한 전략가)", + description: `INTJ는 독립적이고 전략적인 사고를 중시해서, 비효율적이거나 맥락 없는 대화를 싫어해요. + 1. 논리적으로 말하기 – 감정보다는 구조와 근거가 있는 말을 선호해. + 2. 쓸데없는 소리는 줄이기 – small talk보다 실속 있는 이야기 좋아해. + 3. 미래 지향적인 주제 던지기 – 목표, 비전, 전략 이야기엔 눈빛이 반짝! + 4. 지적인 도전 환영하기 – 반박이나 다른 시각도 논리적이면 좋아함. + 5. 결정권 존중하기 – 자기 기준이 명확하니, 무시받는 느낌은 싫어해.` + }, + INTP: { + title: "INTP (논리적인 사색가)", + description: `INTP는 지적인 호기심이 강하고 자유로운 사고를 중시해요. 감정에 너무 치우친 대화는 금방 피곤해져요. + + 1. “왜 그렇게 생각해?” 자주 물어보기 – 개념 설명할 기회를 좋아해. + 2. 논쟁해도 감정 안 섞기 – 논리는 논리, 감정은 감정! 구분 중요함. + 3. 아이디어 존중하기 – 엉뚱한 얘기도 무시하지 말고 재미있게 받아줘. + 4. 답 없는 질문 던지기 – 철학적 질문 좋아함. “자아란 무엇인가?” 같은. + 5. 혼자만의 시간도 존중하기 – 과하게 밀어붙이면 부담스러워져.` + }, + ENTJ: { + title: "ENTJ (결단력 있는 리더)", + description: `ENTJ는 목표 중심적이고 리더 기질이 강해서, 생산적인 대화를 선호하고 감정적인 접근에 거리를 두는 편이에요. + + 1. 주제 중심적으로 말하기 – 핵심만 콕콕! 잡담보단 생산적인 대화 선호. + 2. 효율성과 논리 강조하기 – “그게 왜 낫다고 생각해?”에 근거를 줘야 함. + 3. 주도권 다툼 피하기 – 자기 결정에 간섭받는 걸 싫어해. + 4. 자기계발, 성과 이야기하면 잘 통해 – 동기부여나 성취에 대한 대화에 반응 좋아. + 5. 시간 낭비처럼 느끼지 않게 – 너무 감정적이거나 돌려 말하는 건 피하기.` + }, + ENTP: { + title: "ENTP (아이디어 뿜뿜형 도전가)", + description: `ENTP는 대화가 아이디어 배틀처럼 흘러가는 걸 즐겨요. 틀에 박힌 얘기보다 신박한 접근을 좋아해요. + 1. 제한 두지 말고 대화 열어두기 – “근데 이건 어때?” “그럼 이런 경우는?” 좋아함. + 2. 논리적이되 너무 틀에 박히지 않게 – 자유롭게 생각 튀는 걸 좋아해. + 3. 즉흥적 반응도 즐김 – 리액션 좋으면 더 신나함. + 4. 이견에 강함 – 토론 좋아해서 반대 의견도 논리적으로 말하면 좋아해. + 5. 질문 많이 하기 – 생각 펼칠 기회를 자주 주면 대화가 팡팡 터짐.` + }, + INFJ: { + title: "INFJ (통찰력 있는 조언자)", + description: `INFJ는 깊이 있고 의미 있는 연결을 중요시해요. 피상적인 말은 오히려 신뢰를 떨어뜨릴 수 있어요. + 1. 깊은 감정, 의미 묻기 – “넌 그때 어떤 감정이었어?” 같은 질문 좋아해. + 2. 진심 어린 말투 – 말투에서 진정성을 느껴야 마음을 열어. + 3. 과도한 개그보단 섬세한 공감 – 분위기 흐리는 유머는 오히려 차가워짐. + 4. 직설적인 충고보단 우회적인 표현 – 배려 섞인 언어 좋아함. + 5. 시간 들여 천천히 다가가기 – 겉과 속이 다른 편이라 쉽게 열지 않음.` + }, + INFP: { + title: "INFP (감성 충만한 이상주의자)", + description: `INFP는 자신만의 세계관과 감정이 뚜렷해요. 무시당하는 느낌이나 감정 무시당하는 걸 힘들어해요. + + 1. 감정 중심의 대화 선호 – “넌 어떻게 느꼈어?” 질문에 반응 잘함. + 2. 판단보단 공감 – “그랬구나, 힘들었겠다” 같은 말이 위로가 됨. + 3. 자율성 존중해주기 – 설득보단 스스로 마음을 열 기회를 줘야 해. + 4. 서정적인 표현 좋아함 – 감성적인 대화 톤이 잘 맞아. + 5. 비판은 조심조심 – 정곡을 찌르는 말에 쉽게 상처받을 수 있어.` + }, + ENFJ: { + title: "ENFJ (사람 좋아하는 카리스마)", + description: `ENFJ는 대인관계에서 에너지를 받고, 감정적 피드백을 중요하게 여겨요. 진심을 주면 진심으로 돌아와요. + + 1. “넌 어떻게 생각해?” 질문 자주 던지기 – 상대에게 관심 많은 성향이야. + 2. 감정 잘 읽는 편이니 솔직하게 표현하기 – 억지로 숨기면 다 느껴버림. + 3. 공감 섞인 리액션 – “와~ 진짜 그랬겠네” 같은 리액션 좋아해. + 4. 관계 중심 이야기 좋아함 – 팀워크, 리더십, 인간관계 등. + 5. 감사와 칭찬은 확실하게 – 표현을 많이 하고, 받는 것도 중요하게 여겨.` + }, + ENFP: { + title: "ENFP (열정 넘치는 자유인)", + description: `ENFP는 에너지 넘치고 감정 표현이 풍부해서, 억누르거나 통제하는 대화보단 열린 흐름을 선호해요. + + 1. 경청보다 리액션! – “헐 진짜?”, “그거 완전 너 스타일이네!” 리액션이 중요해. + 2. 새로운 경험 물어보기 – “최근 제일 신났던 일 뭐야?” 같은 질문 좋아해. + 3. 감정 표현에 거리낌 없음 – 같이 웃고 울어주면 금방 가까워져. + 4. 자유롭게 이야기 흐르게 두기 – 주제 뛰어넘어도 그냥 따라가 주기. + 5. 기분 존중해주기 – 감정 기복이 있으니 강요보단 분위기 타기.` + }, + ISTJ: { + title: "ISTJ (현실적인 관리자)", + description: `ISTJ는 안정과 신뢰를 중시해서, 예상 불가한 감정 중심 대화에 쉽게 피곤해해요. + + 1. 사실 기반 대화 선호 – 구체적이고 실질적인 정보에 반응해. + 2. 계획적이고 질서 있는 이야기 전개 – 말에 체계가 있어야 편함. + 3. 약속과 신뢰 중요시 – 말보다 행동이 중요해. + 4. 감정 과잉 피하기 – 감정으로 휘몰아치는 대화는 부담스러움. + 5. 천천히 신뢰 쌓기 – 믿음을 주면 진국으로 다가와.` + }, + ISFJ: { + title: "ISFJ (조용한 배려자)", + description: `ISFJ는 관계를 유지하고 싶은 마음이 강해요. 단, 섬세한 감정선이 있어서 거칠게 접근하면 금방 거리 둬요. + + 1. 따뜻한 분위기 유지 – 공격적인 대화는 쉽게 닫히게 해. + 2. 예의와 배려 표현 중요 – 사소한 존중 표현에 민감해. + 3. 조용히 들어주는 태도에 익숙함 – 말이 적다고 무시하면 안 돼. + 4. 감사 표현 자주 하기 – “덕분에…” “고마워!” 이런 말 좋아해. + 5. 천천히 마음 여는 스타일 – 조급하면 오히려 멀어짐.` + }, + ESTJ: { + title: "ESTJ (정리정돈형 현실주의자)", + description: `ESTJ는 현실적이고 목표지향적이에요. 지나친 감성적 접근은 비효율적으로 느껴질 수 있어요. + + 1. 핵심만 콕 집어 말하기 – 장황한 말 싫어함. + 2. 논리적이고 사실 기반 접근 – “그게 맞는 이유는?” 설명해줘야 함. + 3. 일, 목표 관련 대화 선호 – 구체적인 계획과 성과 이야기 좋아함. + 4. 감정보다 해결책 제시 – 위로보단 “그래서 어떻게 할 건데?” 스타일. + 5. 빠른 피드백 선호 – 기다리는 거 싫어해. 바로 반응해주기.` + }, + ESFJ: { + title: "ESFJ (사람 챙기는 따뜻한 리더)", + description: `ESFJ는 조화로운 분위기를 중시해요. 무례하거나 차가운 말은 관계에 큰 영향을 줘요. + + 1. 예의 있고 따뜻한 말투 – 말투 하나에 마음 열고 닫음. + 2. 감정 공감 자주 해주기 – “그 마음 이해돼” 한 마디가 효과 만점. + 3. 주변 사람 이야기 좋아함 – 인간관계 이야기 잘 통함. + 4. 칭찬 아끼지 않기 – “네가 있어서 분위기가 좋아” 같은 말 굿! + 5. 함께 하려는 의도 보여주기 – “같이 하자”, “같이 생각해보자” 좋아함.` + }, + ISFP: { + title: "ISFP (예술 감성 충만한 잔잔한 자유인)", + description: `ISFP는 내면이 섬세하고, 자유로운 감성을 추구해서 부담 없는 자연스러운 대화에 편안함을 느껴요. + + 1. 감정 존중해주기 – 겉으로는 조용해도 속으론 감정이 깊은 편이야. + 2. 편안한 분위기 만들기 – 긴장하거나 평가받는 느낌 싫어해. + 3. 강요 없이 의견 들어주기 – “나는 이렇게 생각해” 식으로 말하면 더 열려. + 4. 감각적인 주제에 관심 많음 – 음악, 미술, 공간 등 취향 얘기 좋아함. + 5. 말보단 행동으로 소통 – 말 많지 않아도 따뜻하게 표현할 줄 아는 스타일.` + }, + ISTP: { + title: "ISTP (논리적인 장인, 말보다 행동형)", + description: `ISTP는 실용적이고 독립적인 성향이라, 감정 중심 대화보다 구조적이고 해결 중심적인 대화를 선호해요. + + 1. 핵심 위주로 간결하게 말하기 – 불필요한 수다는 피곤함! + 2. 문제 해결형 대화 선호 – “이거 어떻게 고치지?” 같이 실용적인 얘기 좋아해. + 3. 감정보단 사실 기반 말하기 – 감정적인 얘기엔 공감이 느릴 수 있어. + 4. 취미/기술 중심 이야기 잘 통해 – 기계, 스포츠, 도전적인 취미 좋아해. + 5. 혼자 있는 시간 존중해주기 – 갑작스러운 감정공세는 거리감만 생겨.` + }, + ESFP: { + title: "ESFP (즉흥적이고 유쾌한 감각러)", + description: `ESFP는 사람들과 즉흥적으로 즐기는 걸 좋아하고, 분위기를 타는 감정형이에요. 긍정적인 에너지가 핵심! + + 1. 리액션 풍부하게 하기 – “헐 진짜?” 같은 반응에 에너지 받음. + 2. 지금 이 순간에 집중하는 이야기 – “지금 제일 재밌는 거 뭐야?” 같은 질문 좋아함. + 3. 칭찬은 과하게 해도 좋아함 – 눈에 띄는 센스, 스타일 등도 언급해줘. + 4. 감정 표현 자유롭게 하기 – 즐거움, 슬픔 다 잘 나누는 타입. + 5. 너무 무거운 주제는 가볍게 풀기 – 너무 진지하게 가면 흥미를 잃어버려.` + }, + ESTP: { + title: "ESTP (액션파 현실주의자)", + description: `ESTP는 에너지 넘치고 현실적인 행동파에요. 말보다 행동, 고민보단 해결! 빠르고 직설적인 스타일이 잘 맞아요. + + 1. 지루하면 바로 딴 데 봄 – 흥미로운 소재로 빠르게 접근하기! + 2. 농담이나 위트 있는 대화 잘 통해 – 긴장 푸는 유머에 강함. + 3. 경험 위주의 대화 선호 – "그거 실제로 해봤어?" 같은 이야기 좋아함. + 4. 즉각적인 반응 중요 – 너무 뜸 들이면 흥미를 잃어버려. + 5. 결론 없는 대화 피하기 – 실질적이고 결과 중심적인 대화를 선호해.` + } +}; diff --git a/src/pages/Chat.tsx b/src/pages/Chat.tsx index ee87ba3..8d5bd3c 100644 --- a/src/pages/Chat.tsx +++ b/src/pages/Chat.tsx @@ -36,7 +36,7 @@ const Chat = () => { const [isOpen, setIsOpen] = useState(false); const bottomRef = useRef(null); - const chatTitle = mode === "fastFriend" ? `${mbti}와 대화` : `${name}과 대화`; + const chatTitle = name ? `${name}과 대화` : `${mbti}와 대화`; const assistantImgUrl = pickMbtiImage(mbti); const storageKey = `chatMessages_${id}`; @@ -176,7 +176,7 @@ const Chat = () => { onSend={() => handleSend(input)} /> - {isOpen && } + {isOpen && } ); }; diff --git a/src/pages/ChatRecommend.tsx b/src/pages/ChatRecommend.tsx index 3c1e04b..ef94f41 100644 --- a/src/pages/ChatRecommend.tsx +++ b/src/pages/ChatRecommend.tsx @@ -1,5 +1,40 @@ +import { useParams } from "react-router-dom"; +import { Mbti } from "@/types/mbti"; +import { chatRecommend } from "@/mock/chatRecommend"; +import Header from "@/components/header/Header"; +import pickMbtiImage from "@/utils/pickMbtiImage"; +import Error from "@/pages/Error"; + const ChatRecommend = () => { - return
대화 주제 추천 페이지!
; + const { mbti } = useParams(); + let title = ""; + let description = ""; + let mbtiImage = ""; + + if (mbti) { + title = chatRecommend[mbti].title; + description = chatRecommend[mbti].description; + mbtiImage = pickMbtiImage(mbti as Mbti); + } else return ; + + return ( +
+
+
+ mbti 이미지 +

{title}

+ {description} +
+
+ ); }; export default ChatRecommend; diff --git a/src/pages/ChatTemperature.tsx b/src/pages/ChatTemperature.tsx new file mode 100644 index 0000000..abd74a9 --- /dev/null +++ b/src/pages/ChatTemperature.tsx @@ -0,0 +1,59 @@ +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import instance from "@/api/axios"; +import Header from "@/components/header/Header"; + +interface TemperatureResponse { + header: { + code: number; + message: string; + }; + data: string; +} + +const ChatTemperature = () => { + const { conversationId } = useParams(); + const [temperature, setTemperature] = useState(""); + + useEffect(() => { + const getTemporature = async () => { + try { + const res = await instance.get( + `/api/addition/temperature/${conversationId}` + ); + setTemperature(res.data.data); + } catch (error) { + console.error("온도 데이터를 가져오는 중 문제가 발생했습니다:", error); + } + }; + + getTemporature(); + }, []); + + return ( +
+
+
+ mbti 온도 이미지 +

+ 방금까지 나눈 대화로 온도를 측정했어요! +

+ {temperature ? ( + + 현재까지 나눈 대화의 온도는 {temperature}도에요 + + ) : ( + + ...대화의 온도를 불러오는 중 + + )} +
+
+ ); +}; + +export default ChatTemperature; diff --git a/src/pages/ChatTemporature.tsx b/src/pages/ChatTemporature.tsx deleted file mode 100644 index 498244c..0000000 --- a/src/pages/ChatTemporature.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const ChatTemporature = () => { - return
대화 온도 측정 페이지!
; -}; - -export default ChatTemporature; diff --git a/src/pages/ChatTips.tsx b/src/pages/ChatTips.tsx index 41678e2..1264cf9 100644 --- a/src/pages/ChatTips.tsx +++ b/src/pages/ChatTips.tsx @@ -1,5 +1,36 @@ +import { useParams } from "react-router-dom"; +import { Mbti } from "@/types/mbti"; +import { tips } from "@/mock/tips"; +import Header from "@/components/header/Header"; +import pickMbtiImage from "@/utils/pickMbtiImage"; +import Error from "@/pages/Error"; + const ChatTips = () => { - return
대화 꿀팁 페이지!
; + const { mbti } = useParams(); + let title = ""; + let description = ""; + let mbtiImage = ""; + + if (mbti) { + title = tips[mbti].title; + description = tips[mbti].description; + mbtiImage = pickMbtiImage(mbti as Mbti); + } else return ; + + return ( +
+
+
+ mbti 이미지 +

{title}

+ {description} +
+
+ ); }; export default ChatTips; diff --git a/src/pages/Content.tsx b/src/pages/Content.tsx index 3862dfb..416d327 100644 --- a/src/pages/Content.tsx +++ b/src/pages/Content.tsx @@ -19,7 +19,7 @@ const Content = () => { element: "대화 시작하기" }); - navigate("/select-info", { state: "fastFriend" }); + navigate("/select-info", { state: { type: "fastFriend" } }); }; const renderContentWithLineBreaks = (text: string) => { diff --git a/src/pages/SelectInfo.tsx b/src/pages/SelectInfo.tsx index b1db917..9b32639 100644 --- a/src/pages/SelectInfo.tsx +++ b/src/pages/SelectInfo.tsx @@ -216,7 +216,8 @@ const SelectInfo = () => { state: { mbti, mode: type, - id: responseData + id: responseData, + name } }); } diff --git a/src/store/useAuthStore.ts b/src/store/useAuthStore.ts index 3bd3cca..835dc89 100644 --- a/src/store/useAuthStore.ts +++ b/src/store/useAuthStore.ts @@ -5,27 +5,34 @@ import instance from "@/api/axios"; interface AuthStore { isLoggedIn: boolean; accessToken: string | null; + loginTime: string | null; login: (code: string) => Promise<{ ok: boolean }>; logout: () => void; } +interface LoginResponse { + data: string; +} + const useAuthStore = create( persist( (set) => ({ isLoggedIn: false, accessToken: null, + loginTime: null, login: async (code: string) => { try { const requestURI = - // 아래 코드는 백엔드팀에서 작업해주시면 동일한 uri로 바뀔 예정 -> 4.24 정준영 import.meta.env.MODE === "production" ? `/api/kakao/login?code=${code}` : `/api/kakao/login?code=${code}&redirectUrl=https://localhost:5173/kakao-login`; - const res = await instance.get(requestURI); + const res = await instance.get(requestURI); + const currentTime = new Date().toISOString(); set({ isLoggedIn: true, - accessToken: res.data.data as string + accessToken: res.data.data, + loginTime: currentTime }); return { ok: true @@ -37,8 +44,10 @@ const useAuthStore = create( logout: () => { set({ isLoggedIn: false, - accessToken: null + accessToken: null, + loginTime: null }); + window.location.href = "/"; return { ok: true }; } }), diff --git a/vite.config.ts b/vite.config.ts index da11d29..72847d0 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -54,6 +54,10 @@ export default defineConfig(({ mode }: { mode: string }) => { { find: "@/libs", replacement: path.resolve(__dirname, "src/libs") + }, + { + find: "@/mock", + replacement: path.resolve(__dirname, "src/mock") } ] }