diff --git a/index.html b/index.html index 6a448a2..8a78056 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - MBTips + MBTips_MBTI AI 대화 시뮬레이션
diff --git a/package-lock.json b/package-lock.json index 0e4d4d2..171617a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-ga4": "^2.1.0", + "react-helmet": "^6.1.0", "react-router-dom": "^7.1.5", "tailwindcss": "^4.0.3", "zustand": "^5.0.3" @@ -24,6 +25,7 @@ "@types/node": "^22.13.13", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", + "@types/react-helmet": "^6.1.11", "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.19.0", "eslint-config-prettier": "^10.0.1", @@ -1650,6 +1652,16 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.27.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.27.0.tgz", @@ -4512,7 +4524,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4909,7 +4920,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -4979,17 +4989,37 @@ "react": "^18.3.1" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, "node_modules/react-ga4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz", "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==", "license": "MIT" }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-refresh": { @@ -5042,6 +5072,15 @@ "react-dom": ">=18" } }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", diff --git a/package.json b/package.json index e7aa450..cfe8e2e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-ga4": "^2.1.0", + "react-helmet": "^6.1.0", "react-router-dom": "^7.1.5", "tailwindcss": "^4.0.3", "zustand": "^5.0.3" @@ -27,6 +28,7 @@ "@types/node": "^22.13.13", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", + "@types/react-helmet": "^6.1.11", "@vitejs/plugin-react": "^4.3.4", "eslint": "^9.19.0", "eslint-config-prettier": "^10.0.1", diff --git a/public/image/og_image.png b/public/image/og_image.png new file mode 100644 index 0000000..186bef3 Binary files /dev/null and b/public/image/og_image.png differ diff --git a/src/App.tsx b/src/App.tsx index f9bbcaf..d4aae6c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { useLocation } from "react-router-dom"; import { initGA, trackPageView } from "@/libs/analytics"; +import { Helmet } from "react-helmet"; import Home from "@/pages/Home"; import SelectInfo from "@/pages/SelectInfo"; import Chat from "@/pages/Chat"; @@ -102,6 +103,18 @@ const App = () => { onClose={() => setToastMessage("")} /> )} + + + + + + + + + } /> } /> diff --git a/src/pages/Chat.tsx b/src/pages/Chat.tsx index 08d8c68..c9ec822 100644 --- a/src/pages/Chat.tsx +++ b/src/pages/Chat.tsx @@ -1,13 +1,14 @@ import { useEffect, useRef, useState, ChangeEvent, KeyboardEvent } from "react"; import { useLocation } from "react-router-dom"; +import { Helmet } from "react-helmet"; +import { authInstance } from "@/api/axios"; +import { trackEvent } from "@/libs/analytics"; import IntroGuide from "@/components/IntroGuide"; import Header from "@/components/header/Header"; import ChatMessage from "@/components/ChatMessage"; import ChatActionBar from "@/components/ChatActionBar"; import TipsMenuContainer from "@/components/tips/TipsMenuContainer"; import pickMbtiImage from "@/utils/pickMbtiImage"; -import { authInstance } from "@/api/axios"; -import { trackEvent } from "@/libs/analytics"; interface Message { role: "user" | "assistant"; @@ -135,49 +136,57 @@ const Chat = () => { }; return ( -
-
- -
- - {/* 메시지 리스트 */} - {messages.map((msg, idx) => ( -
- {/* 캐릭터 아이콘 */} - {msg.role === "assistant" && ( - MBTI ICON - )} - {/* 채팅 메시지 */} -
- + <> + + + + + + +
+
+ +
+ + {/* 메시지 리스트 */} + {messages.map((msg, idx) => ( +
+ {/* 캐릭터 아이콘 */} + {msg.role === "assistant" && ( + MBTI ICON + )} + {/* 채팅 메시지 */} +
+ +
-
- ))} + ))} -
-
+
+
+ + handleSend(input)} + /> - handleSend(input)} - /> - - {isOpen && } -
+ {isOpen && } +
+ ); }; diff --git a/src/pages/ChatRecommend.tsx b/src/pages/ChatRecommend.tsx index ef94f41..523a0b4 100644 --- a/src/pages/ChatRecommend.tsx +++ b/src/pages/ChatRecommend.tsx @@ -1,4 +1,5 @@ import { useParams } from "react-router-dom"; +import { Helmet } from "react-helmet"; import { Mbti } from "@/types/mbti"; import { chatRecommend } from "@/mock/chatRecommend"; import Header from "@/components/header/Header"; @@ -18,22 +19,30 @@ const ChatRecommend = () => { } else return ; return ( -
-
-
- mbti 이미지 + + + + + + +
+
-

{title}

- {description} -
-
+
+ mbti 이미지 +

{title}

+ {description} +
+
+ ); }; diff --git a/src/pages/ChatTemperature.tsx b/src/pages/ChatTemperature.tsx index 8037eaa..14aa2d3 100644 --- a/src/pages/ChatTemperature.tsx +++ b/src/pages/ChatTemperature.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; +import { Helmet } from "react-helmet"; import instance from "@/api/axios"; import Header from "@/components/header/Header"; @@ -31,28 +32,40 @@ const ChatTemperature = () => { }, []); return ( -
-
-
- mbti 온도 이미지 + + + + + + +
+
-

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

- {temperature ? ( - - 현재까지 나눈 대화의 온도는 {temperature}도에요 - - ) : ( - - ...대화의 온도를 불러오는 중 - - )} -
-
+
+ mbti 온도 이미지 +

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

+ {temperature ? ( + + 현재까지 나눈 대화의 온도는 {temperature}도에요 + + ) : ( + + ...대화의 온도를 불러오는 중 + + )} +
+
+ ); }; diff --git a/src/pages/ChatTips.tsx b/src/pages/ChatTips.tsx index 1264cf9..b18352a 100644 --- a/src/pages/ChatTips.tsx +++ b/src/pages/ChatTips.tsx @@ -1,4 +1,5 @@ import { useParams } from "react-router-dom"; +import { Helmet } from "react-helmet"; import { Mbti } from "@/types/mbti"; import { tips } from "@/mock/tips"; import Header from "@/components/header/Header"; @@ -18,18 +19,30 @@ const ChatTips = () => { } else return ; return ( -
-
-
- mbti 이미지 + + + + + + +
+
-

{title}

- {description} -
-
+
+ mbti 이미지 +

{title}

+ {description} +
+
+ ); }; diff --git a/src/pages/Content.tsx b/src/pages/Content.tsx index 416d327..46a6be1 100644 --- a/src/pages/Content.tsx +++ b/src/pages/Content.tsx @@ -1,8 +1,9 @@ -import Header from "@/components/header/Header"; +import { Helmet } from "react-helmet"; +import { useNavigate, useParams } from "react-router-dom"; import { CONTENT_DATA } from "@/constants/CONTENT"; import { trackEvent } from "@/libs/analytics"; import React from "react"; -import { useNavigate, useParams } from "react-router-dom"; +import Header from "@/components/header/Header"; const Content = () => { const navigate = useNavigate(); @@ -33,44 +34,79 @@ const Content = () => { }; return ( -
-
+ <> + + + + + -
- {/* 상단 배너 */} -
- - - - {content.title} - -
+
+
- {/* 서브 제목 */} - {content.subTitle && ( -

- {content.subTitle} -

- )} +
+ {/* 상단 배너 */} +
+ + + + {content.title} + +
- {/* 본문 내용 */} -
- {content.content && renderContentWithLineBreaks(content.content)} -
+ {/* 서브 제목 */} + {content.subTitle && ( +

+ {content.subTitle} +

+ )} - {/* 하단 버튼 */} - + {/* 본문 내용 */} +
+ {content.content && renderContentWithLineBreaks(content.content)} +
+ + {/* 하단 버튼 */} + +
-
+ ); }; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index d3499a0..72a8ae1 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -10,6 +10,7 @@ import ChatStartButton from "@/components/button/ChatStartButton"; import Header from "@/components/header/Header"; import useAuthStore from "@/store/useAuthStore"; import ProfileContainer from "@/components/ProfileContainer"; +import { Helmet } from "react-helmet"; const Home = () => { const navigate = useNavigate(); @@ -34,37 +35,44 @@ const Home = () => { }, [isLoggedIn]); return ( -
-
-
-
- -
-
-
- -
-
- -
-
-
-
- -
-
- {isLoggedIn && virtualFreindList.length > 0 ? ( - - ) : ( - - )} -
-
-
-
+ <> + + + + + +
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ {isLoggedIn && virtualFreindList.length > 0 ? ( + + ) : ( + + )} +
+
+
+
+ ); }; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 619fbed..96a28cc 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { Helmet } from "react-helmet"; import KakaoLoginButton from "@/components/button/KakaoLoginButton"; import TermsAndPrivacy from "@/components/TermsAndPrivacy"; import TermsAndPrivacyModal from "@/components/modal/TermsAndPrivacyModal"; @@ -26,36 +27,44 @@ const Login = () => { const isTerms = isModalOpen.mode === "terms"; return ( -
- 로그인 페이지 이미지 -

- MBTI 성향 기반 -
- AI 채팅 시뮬레이션으로 -
- 대화 연습과 꿀팁 얻어가세요 -

-
- 대화 연습부터, 피드백까지 드려요 -
-
- -
-
- -
- {isOpen ? ( - isTerms ? ( - - ) : ( - - ) - ) : null} -
+ <> + + + + + + +
+ 로그인 페이지 이미지 +

+ MBTI 성향 기반 +
+ AI 채팅 시뮬레이션으로 +
+ 대화 연습과 꿀팁 얻어가세요 +

+
+ 대화 연습부터, 피드백까지 드려요 +
+
+ +
+
+ +
+ {isOpen ? ( + isTerms ? ( + + ) : ( + + ) + ) : null} +
+ ); }; diff --git a/src/pages/MbtiTestIntro.tsx b/src/pages/MbtiTestIntro.tsx index b167903..9a76258 100644 --- a/src/pages/MbtiTestIntro.tsx +++ b/src/pages/MbtiTestIntro.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom"; import Header from "@/components/header/Header"; import useLayoutSize from "@/hooks/useLayoutSize"; import trackClickEvent from "@/utils/trackClickEvent"; +import { Helmet } from "react-helmet"; const MbtiTestIntro = () => { const [name, setName] = useState(""); @@ -29,45 +30,64 @@ const MbtiTestIntro = () => { }; return ( -
-
-
- intro image + + + - - 그 사람의 mbti는 뭘까? - -

- 상대방 MBTI -
- 유추 테스트 -

-
- - + + + + +
+
+
+ intro image - - -
-
+ + + + +
+
+ ); }; diff --git a/src/pages/MbtiTestQuestions.tsx b/src/pages/MbtiTestQuestions.tsx index 41256aa..3f84422 100644 --- a/src/pages/MbtiTestQuestions.tsx +++ b/src/pages/MbtiTestQuestions.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import { Helmet } from "react-helmet"; import { trackPageView } from "@/libs/analytics"; import { TEST_QNA } from "@/constants/TEST_QNA"; import MbtiAnswerButtons from "@/components/button/MbtiAnswerButtons"; @@ -26,25 +27,44 @@ const MbtiTestQuestions = () => { const content: content = TEST_QNA[Number(currentPage) - 1]; return ( -
-
-
- - {content.number}/12 - -

- {content.question} -

- mbti 테스트 과정 이미지 + + + -
- -
-
-
+ + + + + +
+
+
+ + {content.number}/12 + +

+ {content.question} +

+ mbti 테스트 과정 이미지 +
+ +
+
+
+ ); } else return ; }; diff --git a/src/pages/MbtiTestResult.tsx b/src/pages/MbtiTestResult.tsx index 2572145..9ac2157 100644 --- a/src/pages/MbtiTestResult.tsx +++ b/src/pages/MbtiTestResult.tsx @@ -1,3 +1,5 @@ +import { useParams } from "react-router-dom"; +import { Helmet } from "react-helmet"; import { MBTI_RESULT_INFO } from "@/constants/MBTI_RESULT_INFO"; import ShareButton from "@/components/button/ShareButton"; import RestartButton from "@/components/button/RestartButton"; @@ -20,52 +22,83 @@ const MbtiTestResult = () => { if (!result) return
404 error occured
; return ( -
-
-
- mbti 테스트 결과 이미지 + + -

- {mbti?.toUpperCase()}는 이런 성향이에요! -

-
    -
  • - {result.tag[0]} -
  • -
  • - {result.tag[1]} -
  • -
-
-

좋아하는 대화 주제

-
    -
  • {result.topic[0]}
  • -
  • {result.topic[1]}
  • -
-

좋아하는 대화 태도

-
    -
  • {result.attitude[0]}
  • -
  • {result.attitude[1]}
  • + + + + + + +
    +
    +
    + mbti 테스트 결과 이미지 +

    + {mbti?.toUpperCase()}는 이런 성향이에요! +

    +
      +
    • + {result.tag[0]} +
    • +
    • + {result.tag[1]} +
    -
    -
    - -
    -
    - - -
    -
-
+
+

좋아하는 대화 주제

+
    +
  • {result.topic[0]}
  • +
  • {result.topic[1]}
  • +
+

좋아하는 대화 태도

+
    +
  • {result.attitude[0]}
  • +
  • {result.attitude[1]}
  • +
+
+
+ +
+
+ + +
+ +
+ ); }; diff --git a/src/pages/SelectInfo.tsx b/src/pages/SelectInfo.tsx index 9b32639..c7b7db1 100644 --- a/src/pages/SelectInfo.tsx +++ b/src/pages/SelectInfo.tsx @@ -1,11 +1,12 @@ import { ChangeEvent, useEffect, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import FormButton from "@/components/button/FormButton"; -import Header from "@/components/header/Header"; +import { Helmet } from "react-helmet"; import { getMBTIgroup, mapAgeToNumber } from "@/utils/helpers"; import { authInstance } from "@/api/axios"; -import ToastMessage from "@/components/ToastMessage"; import { trackEvent } from "@/libs/analytics"; +import FormButton from "@/components/button/FormButton"; +import Header from "@/components/header/Header"; +import ToastMessage from "@/components/ToastMessage"; type FastFriendResponse = { header: { @@ -227,154 +228,177 @@ const SelectInfo = () => { }; return ( -
-
- -
- {/* MBTI 선택 */} -
-

- {selectInfoTitle} -

- -
- {mbtiOptions.map((option) => ( - handleMBTISelect(option)} - > - {option} - - ))} -
-
-
+ <> + + + + + -
- -
-
-

- 정보 추가 입력 -

- - {/* 이름 입력 */} -
- - -
+
+
- {/* 나이 선택 */} -
-

- 나이 +

+ {/* MBTI 선택 */} +
+

+ {selectInfoTitle}

-
- {ageOptions.map((option) => ( + +
+ {mbtiOptions.map((option) => ( handleButtonClick(option, setAge, age)} + size="md" + selected={isMBTISelected(option)} + onClick={() => handleMBTISelect(option)} > {option} ))}
+
+ +
- {/* 성별 선택 */} -
-

- 성별 +

+
+

+ 정보 추가 입력

-
- {genderOptions.map((option) => ( - handleButtonClick(option, setGender, gender)} - > - {option} - - ))} + + {/* 이름 입력 */} +
+ +
-
- {/* 관계 선택 */} -
-

- 상대방과 나의 관계 -

-
- {relationshipOptions.map((option) => ( - - handleButtonClick(option, setRelationship, relationship) - } - > - {option} - - ))} + {/* 나이 선택 */} +
+

+ 나이 +

+
+ {ageOptions.map((option) => ( + handleButtonClick(option, setAge, age)} + > + {option} + + ))} +
-
- {/* 관심사 선택 */} -
-

- 관심사 -

-
- {interestOptions.map((option) => ( - handleInterestSelect(option)} - > - {option} - - ))} + {/* 성별 선택 */} +
+

+ 성별 +

+
+ {genderOptions.map((option) => ( + handleButtonClick(option, setGender, gender)} + > + {option} + + ))} +
+
+ + {/* 관계 선택 */} +
+

+ 상대방과 나의 관계 +

+
+ {relationshipOptions.map((option) => ( + + handleButtonClick(option, setRelationship, relationship) + } + > + {option} + + ))} +
+
+ + {/* 관심사 선택 */} +
+

+ 관심사 +

+
+ {interestOptions.map((option) => ( + handleInterestSelect(option)} + > + {option} + + ))} +
-
- {toastMessage && ( - setToastMessage(null)} - /> - )} - - {/* 대화 시작 버튼 */} - + {toastMessage && ( + setToastMessage(null)} + /> + )} + + {/* 대화 시작 버튼 */} + +
-
+ ); };