diff --git a/.env b/.env index 933a52c..bbbb75f 100644 --- a/.env +++ b/.env @@ -5,4 +5,6 @@ VITE_COOKIE_NICKNAME = "stocodi-nickname" VITE_COOKIE_ACCESS = "member_access_token" VITE_COOKIE_REFRESH = "member_refresh_token" VITE_COOKIE_PATH = "/" -VITE_COOKIE_DOMAIN = ".localhost" \ No newline at end of file +VITE_COOKIE_DOMAIN = ".localhost" + +VITE_KAKAO_API_KEY = "048a738db78a2ca8c2451d886ffdd8bf" \ No newline at end of file diff --git a/index.html b/index.html index ad11673..e066de1 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,13 @@ Stocodi | 스토코디 + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 7c4ed13..d898e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@reduxjs/toolkit": "^2.0.1", "axios": "^1.6.2", + "dom-to-image": "^2.6.0", "react": "^18.2.0", "react-cookie": "^6.1.1", "react-dom": "^18.2.0", @@ -23,6 +24,7 @@ "storybook": "^7.6.6" }, "devDependencies": { + "@types/dom-to-image": "^2.6.7", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@typescript-eslint/eslint-plugin": "^5.61.0", @@ -3426,6 +3428,12 @@ "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.5.tgz", "integrity": "sha512-Rf3/lB9WkDfIL9eEKaSYKc+1L/rNVYBjThk22JTqQw0YozXarX8YljFAz+HCoC6h4B4KwCMsBPZHaFezwT4BNA==" }, + "node_modules/@types/dom-to-image": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/@types/dom-to-image/-/dom-to-image-2.6.7.tgz", + "integrity": "sha512-me5VbCv+fcXozblWwG13krNBvuEOm6kA5xoa4RrjDJCNFOZSWR3/QLtOXimBHk1Fisq69Gx3JtOoXtg1N1tijg==", + "dev": true + }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", @@ -5062,6 +5070,11 @@ "node": ">=6.0.0" } }, + "node_modules/dom-to-image": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/dom-to-image/-/dom-to-image-2.6.0.tgz", + "integrity": "sha512-Dt0QdaHmLpjURjU7Tnu3AgYSF2LuOmksSGsUcE6ItvJoCWTBEmiMXcqBdNSAm9+QbbwD7JMoVsuuKX6ZVQv1qA==" + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", diff --git a/package.json b/package.json index 2d52ede..86ef16d 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,9 @@ "dev": "vite", "build": "tsc && vite build", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "prettier" : "prettier --write **/*.{ts,tsx}", + "prettier": "prettier --write **/*.{ts,tsx}", "preview": "vite preview", - "start":"serve -s dist -l 3000" + "start": "serve -s dist -l 3000" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.5.1", @@ -17,6 +17,7 @@ "@fortawesome/react-fontawesome": "^0.2.0", "@reduxjs/toolkit": "^2.0.1", "axios": "^1.6.2", + "dom-to-image": "^2.6.0", "react": "^18.2.0", "react-cookie": "^6.1.1", "react-dom": "^18.2.0", @@ -27,6 +28,7 @@ "storybook": "^7.6.6" }, "devDependencies": { + "@types/dom-to-image": "^2.6.7", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@typescript-eslint/eslint-plugin": "^5.61.0", diff --git a/public/img/test-1.png b/public/img/test-1.png new file mode 100644 index 0000000..bbc001d Binary files /dev/null and b/public/img/test-1.png differ diff --git a/public/img/test-10.png b/public/img/test-10.png new file mode 100644 index 0000000..a9be2a7 Binary files /dev/null and b/public/img/test-10.png differ diff --git a/public/img/test-11.png b/public/img/test-11.png new file mode 100644 index 0000000..e09cf26 Binary files /dev/null and b/public/img/test-11.png differ diff --git a/public/img/test-12.png b/public/img/test-12.png new file mode 100644 index 0000000..bc1cb71 Binary files /dev/null and b/public/img/test-12.png differ diff --git a/public/img/test-13.png b/public/img/test-13.png new file mode 100644 index 0000000..5f2da6e Binary files /dev/null and b/public/img/test-13.png differ diff --git a/public/img/test-2.png b/public/img/test-2.png new file mode 100644 index 0000000..315d8fd Binary files /dev/null and b/public/img/test-2.png differ diff --git a/public/img/test-3.png b/public/img/test-3.png new file mode 100644 index 0000000..9afa0f2 Binary files /dev/null and b/public/img/test-3.png differ diff --git a/public/img/test-4.png b/public/img/test-4.png new file mode 100644 index 0000000..f04fb67 Binary files /dev/null and b/public/img/test-4.png differ diff --git a/public/img/test-5.png b/public/img/test-5.png new file mode 100644 index 0000000..4da0e7d Binary files /dev/null and b/public/img/test-5.png differ diff --git a/public/img/test-6.png b/public/img/test-6.png new file mode 100644 index 0000000..dc1fbd1 Binary files /dev/null and b/public/img/test-6.png differ diff --git a/public/img/test-7.png b/public/img/test-7.png new file mode 100644 index 0000000..c87f99d Binary files /dev/null and b/public/img/test-7.png differ diff --git a/public/img/test-8.png b/public/img/test-8.png new file mode 100644 index 0000000..21499ad Binary files /dev/null and b/public/img/test-8.png differ diff --git a/public/img/test-9.png b/public/img/test-9.png new file mode 100644 index 0000000..feace38 Binary files /dev/null and b/public/img/test-9.png differ diff --git a/src/api/env.ts b/src/api/env.ts index 6e9a1c4..3ac2fe6 100644 --- a/src/api/env.ts +++ b/src/api/env.ts @@ -6,3 +6,5 @@ export const COOKIE_REFRESH = (import.meta.env.VITE_PRODUCTION_COOKIE_REFRESH as export const COOKIE_PATH = (import.meta.env.VITE_PRODUCTION_COOKIE_PATH as string) || (import.meta.env.VITE_COOKIE_PATH as string); export const COOKIE_DOMAIN = (import.meta.env.VITE_PRODUCTION_COOKIE_DOMAIN as string) || (import.meta.env.VITE_COOKIE_DOMAIN as string); + +export const KAKAO_SDK = import.meta.env.VITE_KAKAO_API_KEY as string; diff --git a/src/components/test-page/AvatarSection.module.scss b/src/components/test-page/AvatarSection.module.scss index 46a9e25..4b2b427 100644 --- a/src/components/test-page/AvatarSection.module.scss +++ b/src/components/test-page/AvatarSection.module.scss @@ -39,6 +39,11 @@ } .speech_bubble { + display: flex; + flex-direction: column; + justify-content: center; + gap: 15px; + width: 100%; border-radius: 24px; @@ -47,13 +52,11 @@ background-color: #fff; h1 { - margin: 10px 0px; font-size: 18px; font-family: "SCDream"; color: #444444; } p { - margin: 10px 0px; text-align: right; font-size: 14px; font-family: "SCDream"; @@ -61,3 +64,35 @@ } } } + +@include mobile { + .avatar_section { + display: flex; + flex-direction: column; + justify-content: center; + + padding: 20px; + .speech_bubble { + height: 90px; + + padding: 10px 16px; + border-radius: 16px; + h1 { + font-size: 12px; + } + p { + font-size: 10px; + } + } + } + + .avatar { + // flex-direction: column; + gap: 15px; + + img { + width: 130px; + height: 130px; + } + } +} diff --git a/src/components/test-page/BreadCrumb.module.scss b/src/components/test-page/BreadCrumb.module.scss index 216301a..4925daa 100644 --- a/src/components/test-page/BreadCrumb.module.scss +++ b/src/components/test-page/BreadCrumb.module.scss @@ -5,7 +5,7 @@ max-width: 900px; margin: 0px auto; - padding: 20px 50px; + padding: 20px 0px; border: 2px solid #0eca80; } @@ -15,13 +15,42 @@ justify-content: center; gap: 30px; + .active { + color: #000; + } + div { font-size: 20px; font-family: "SCDream"; color: #bdbdbd; } - div:last-child { display: none; } } + +@include mobile { + .breadcrumb { + padding: 10px 0px; + background-color: #0eca80; + border-radius: 12px; + } + .breadcrumb_container { + display: flex; + justify-content: center; + gap: 30px; + + .active { + font-size: 16px; + color: #fff; + } + + div:not(.active) { + display: none; + } + + div:last-child { + display: none; + } + } +} diff --git a/src/components/test-page/BreadCrumb.tsx b/src/components/test-page/BreadCrumb.tsx index 82374e4..541b097 100644 --- a/src/components/test-page/BreadCrumb.tsx +++ b/src/components/test-page/BreadCrumb.tsx @@ -17,7 +17,7 @@ export const BreadCrumb: React.FC = ({ items, cursor }) => { {items.map((element, index) => { return ( -
{element}
+
{element}
diff --git a/src/components/test-page/LectureRedirectCard.module.scss b/src/components/test-page/LectureRedirectCard.module.scss index 0c60be0..71f0040 100644 --- a/src/components/test-page/LectureRedirectCard.module.scss +++ b/src/components/test-page/LectureRedirectCard.module.scss @@ -48,6 +48,9 @@ svg { margin: 0px 3px; } + &:hover { + cursor: pointer; + } } } @@ -61,3 +64,33 @@ object-fit: cover; } + +.lecture_redirect_link { + display: none; + + margin: 10px 0px; + + a { + span { + font-size: 16px; + font-family: "SCDream"; + text-align: right; + } + svg { + margin: 0px 10px; + } + &:hover { + color: #000; + } + } +} + +@include mobile { + .lecture_redirect_card { + display: none; + } + + .lecture_redirect_link { + display: block; + } +} diff --git a/src/components/test-page/LectureRedirectCard.tsx b/src/components/test-page/LectureRedirectCard.tsx index 4a7a8f5..8986a70 100644 --- a/src/components/test-page/LectureRedirectCard.tsx +++ b/src/components/test-page/LectureRedirectCard.tsx @@ -1,6 +1,8 @@ +import { Link, useNavigate } from "react-router-dom"; + import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import styles from "./LectureRedirectCard.module.scss"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import styles from "./LectureRedirectCard.module.scss"; export interface ILectureRedirectCard { width?: string; @@ -8,16 +10,22 @@ export interface ILectureRedirectCard { imgSrc: string; label: string; - onClick?: React.MouseEventHandler; } -export const LectureRedirectCard: React.FC = ({ width, height, imgSrc, label, onClick }) => { +export interface ILectureRedirectLink { + label: string; + link: string; +} + +export const LectureRedirectCard: React.FC = ({ width, height, imgSrc, label }) => { + const navigate = useNavigate(); + return (

{label}

- @@ -27,3 +35,14 @@ export const LectureRedirectCard: React.FC = ({ width, hei
); }; + +export const LectureRedirectLink: React.FC = ({ label, link }) => { + return ( +
+ + {label} + + +
+ ); +}; diff --git a/src/components/test-page/ProsConsContainer.module.scss b/src/components/test-page/ProsConsContainer.module.scss index 41b5169..e891db1 100644 --- a/src/components/test-page/ProsConsContainer.module.scss +++ b/src/components/test-page/ProsConsContainer.module.scss @@ -35,3 +35,17 @@ } } } + +@include mobile { + .pros_cons_container { + gap: 20px; + div { + p { + font-size: 16px; + } + h1 { + font-size: 26px; + } + } + } +} diff --git a/src/components/test-page/Question.module.scss b/src/components/test-page/Question.module.scss index 42ff5ff..6d3de20 100644 --- a/src/components/test-page/Question.module.scss +++ b/src/components/test-page/Question.module.scss @@ -18,3 +18,13 @@ margin: 10px 0px; } + +@include mobile { + .question { + margin: 40px auto; + padding: 0px 20px; + p { + font-size: 16px; + } + } +} diff --git a/src/components/test-page/Question.tsx b/src/components/test-page/Question.tsx index d5f37d3..4f57445 100644 --- a/src/components/test-page/Question.tsx +++ b/src/components/test-page/Question.tsx @@ -1,9 +1,5 @@ -import { useEffect } from "react"; -import { useDispatch } from "react-redux"; import { RadioOption } from "../../interfaces/forms/RadioButton"; import styles from "./Question.module.scss"; -import { Dispatch } from "@reduxjs/toolkit"; -import { UserQuestionActions } from "../../store/user-question-slice"; export interface IQuestion { index: number; @@ -11,27 +7,6 @@ export interface IQuestion { } export const Question: React.FC = ({ index, question }) => { - const dispatch: Dispatch = useDispatch(); - - useEffect(() => { - const options = document.querySelectorAll(`input[name=question-option-${index}]`); - - for (let optionIndex = 0; optionIndex < options.length; optionIndex++) { - options[optionIndex].addEventListener("change", () => { - if (optionIndex === 0) dispatch(UserQuestionActions.selectAnswer({ index: index, selectedAnswer: "O" })); - else if (optionIndex === 1) dispatch(UserQuestionActions.selectAnswer({ index: index, selectedAnswer: "X" })); - else if (optionIndex === 2) dispatch(UserQuestionActions.selectAnswer({ index: index, selectedAnswer: "모르겠음" })); - }); - } - - return () => { - // 이전에 체크한 RadioButton 체크해제 - for (let optionIndex = 0; optionIndex < options.length; optionIndex++) { - options[optionIndex].checked = false; - } - }; - }, [index, dispatch]); - return (

@@ -39,9 +14,9 @@ export const Question: React.FC = ({ index, question }) => {

- - - + + +
); diff --git a/src/components/test-page/ResultSummary.module.scss b/src/components/test-page/ResultSummary.module.scss index 398ac1e..5841395 100644 --- a/src/components/test-page/ResultSummary.module.scss +++ b/src/components/test-page/ResultSummary.module.scss @@ -1,7 +1,8 @@ @import "@/styles/utils"; .result_summary { - width: 486px; + max-width: 486px; + width: 100%; @include place-center; justify-content: space-between; @@ -41,19 +42,22 @@ .result_grid { display: grid; - grid-template-rows: repeat(2, 150px); - grid-template-columns: repeat(3, 150px); + grid-template-rows: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 18px; - width: fit-content; + justify-content: center; + + width: 100%; + max-width: 486px; margin: 20px auto; } .result_grid_item { position: relative; - width: 150px; - height: 150px; + width: 100%; + aspect-ratio: 1/1; border-radius: 16px; padding: 25px; @@ -83,3 +87,48 @@ bottom: 0; } } + +@include mobile { + .result_summary { + div { + @include vertical-center; + h2 { + margin: 5px 0px; + font-size: 16px; + color: #fff; + } + h1 { + margin: 5px 0px; + font-family: "SCDream"; + font-size: 30px; + color: #fff; + } + } + img { + width: 40%; + height: 40%; + } + } + + .result_grid { + grid-template-rows: repeat(3, 1fr); + grid-template-columns: repeat(2, 1fr); + gap: 18px; + + width: 100%; + + margin: 20px auto; + } + + .result_grid_item { + border-radius: 16px; + padding: 30px; + + h3 { + font-size: 18px; + } + h2 { + font-size: 26px; + } + } +} diff --git a/src/components/test-page/ResultSummary.tsx b/src/components/test-page/ResultSummary.tsx index f59b5ab..c8ee9a8 100644 --- a/src/components/test-page/ResultSummary.tsx +++ b/src/components/test-page/ResultSummary.tsx @@ -10,7 +10,7 @@ export interface IResultGridItem { icon: string; } -export const ResultSummary = ({ score }: { score: number }) => { +export const ResultSummary = ({ score }: { score: string }) => { return (
diff --git a/src/components/test-page/ShareSection.module.scss b/src/components/test-page/ShareContainer.module.scss similarity index 69% rename from src/components/test-page/ShareSection.module.scss rename to src/components/test-page/ShareContainer.module.scss index 6b52b9f..048ad2c 100644 --- a/src/components/test-page/ShareSection.module.scss +++ b/src/components/test-page/ShareContainer.module.scss @@ -65,3 +65,32 @@ cursor: pointer; } } + +@include mobile { + .share_section { + .body { + gap: 12px; + } + } + + .share_item { + .img_container { + width: 40px; + height: 40px; + + margin: 10px auto; + border-radius: 50%; + + background-color: #fff; + box-shadow: 0px 0px 10px -2px rgba(0, 0, 0, 0.2); + + img { + width: 50%; + height: 50%; + } + } + p { + font-size: 12px; + } + } +} diff --git a/src/components/test-page/ShareSection.tsx b/src/components/test-page/ShareContainer.tsx similarity index 81% rename from src/components/test-page/ShareSection.tsx rename to src/components/test-page/ShareContainer.tsx index 6e29c2a..30c29b9 100644 --- a/src/components/test-page/ShareSection.tsx +++ b/src/components/test-page/ShareContainer.tsx @@ -1,6 +1,6 @@ -import styles from "./ShareSection.module.scss"; +import styles from "./ShareContainer.module.scss"; -export interface IShareSection { +export interface IShareContainer { children: React.ReactNode; } @@ -10,7 +10,7 @@ export interface IShareItem { onClick?: React.MouseEventHandler; } -export const ShareSection: React.FC = ({ children }) => { +export const ShareContainer: React.FC = ({ children }) => { return (
diff --git a/src/constants/Result.ts b/src/constants/Result.ts new file mode 100644 index 0000000..6963a86 --- /dev/null +++ b/src/constants/Result.ts @@ -0,0 +1,83 @@ +export const GetResult = (score: number) => { + if (0 <= score && score < 50) { + return ResultLow[Math.floor(Math.random() * ResultLow.length)]; + } else if (50 <= score && score < 80) { + return ResultMid[Math.floor(Math.random() * ResultMid.length)]; + } else if (80 <= score && score < 100) { + return ResultHigh[Math.floor(Math.random() * ResultHigh.length)]; + } +}; + +export const ResultLow = [ + { + img: "/img/test-1.png", + title: "아니요 뚱인데요", + author: "뚱이", + }, + { + img: "/img/test-2.png", + title: "상상력만 발휘하면 뭐든 할 수 있다구", + author: "스폰지밥", + }, + { + img: "/img/test-3.png", + title: "노는 게 제일 좋아, 친구들 모여라", + author: "뽀로로", + }, + { + img: "/img/test-4.png", + title: "왜 사람들은 보이는 것 너머에 또 보아야 할 것이 있다는 걸 알지 못하지?", + author: "슈렉", + }, + { + img: "/img/test-5.png", + title: "사랑은 많이 양보하는 거야. 그래야 너가 사랑하는 사람을 행복하게 만들 수 있거든", + author: "곰돌이 푸", + }, +]; + +export const ResultMid = [ + { + img: "/img/test-6.png", + title: "불가능한 경우를 제외하고 남은 것은 아무리 이상하고 믿기지 않더라도 사실이기 마련이야", + author: "셜록", + }, + { + img: "/img/test-7.png", + title: "넌 대단한 마법사야, 난 책과 지혜고요", + author: "헤르미온느", + }, + { + img: "/img/test-8.png", + title: "영웅은 태어나지 않아, 다만 만들어질 뿐이지", + author: "아이언맨", + }, +]; + +export const ResultHigh = [ + { + img: "/img/test-9.png", + title: "가격이 잘못 매겨진 기회를 찾아라. 그게 바로 투자다. 어떤 기회가 가격이 잘못 매겨진 것인지 충분히 알아야 한다. 그게 가치투자다", + author: "찰리멍거", + }, + { + img: "/img/test-10.png", + title: "포트폴리오를 분산시키세요. 한 번에 모든 것을 거는 것은 위험합니다", + author: "레이 달리오", + }, + { + img: "/img/test-11.png", + title: "공부 없이 투자하는 것은 카드를 보지 않고 포커를 치는 것과 같다", + author: "피터 린치", + }, + { + img: "/img/test-12.png", + title: "잭팟을 터트렸다고 말하는 사람들을 부러워해선 안된다", + author: "워렌버핏", + }, + { + img: "/img/test-13.png", + title: "영리한 투자자의 고전적 정의는 모두가 팔고 있는 약세장에 매수해서 모두가 사고 있는 강세장에서 매도하는 사람이다", + author: "벤자민 그레이엄", + }, +]; diff --git a/src/interfaces/display/LabelContainer.module.scss b/src/interfaces/display/LabelContainer.module.scss index 120362e..be7863d 100644 --- a/src/interfaces/display/LabelContainer.module.scss +++ b/src/interfaces/display/LabelContainer.module.scss @@ -26,3 +26,14 @@ line-height: 26px; } } + +@include mobile { + .label_container { + p { + font-size: 14px; + } + div { + font-size: 14px; + } + } +} diff --git a/src/interfaces/forms/RadioButton.module.scss b/src/interfaces/forms/RadioButton.module.scss index b834ffb..4f93133 100644 --- a/src/interfaces/forms/RadioButton.module.scss +++ b/src/interfaces/forms/RadioButton.module.scss @@ -3,8 +3,8 @@ .radio_btn { position: relative; - width: 25px; - height: 25px; + width: 24px; + height: 24px; font-family: "SCDream"; @@ -37,8 +37,9 @@ p { @include vertical-center; - margin: 0px 5px; + margin: 0px 10px; font-family: "SCDream"; - font-size: 16px; + font-size: 16px !important; + vertical-align: middle; } } diff --git a/src/interfaces/forms/RadioButton.tsx b/src/interfaces/forms/RadioButton.tsx index fb226e5..e4f0914 100644 --- a/src/interfaces/forms/RadioButton.tsx +++ b/src/interfaces/forms/RadioButton.tsx @@ -7,25 +7,27 @@ import styles from "./RadioButton.module.scss"; export interface IRadioButton { name: string; + value: string; } export interface IRadioOption { name: string; + value: string; label: string; } -export const RadioButton = forwardRef(({ name }, ref) => { +export const RadioButton = forwardRef(({ name, value }, ref) => { return ( <> - + ); }); -export const RadioOption = forwardRef(({ name, label }, ref) => { +export const RadioOption = forwardRef(({ name, label, value }, ref) => { return (
- +

{label}

); diff --git a/src/layouts/TestLayout.module.scss b/src/layouts/TestLayout.module.scss index a5b192a..c3fbcb9 100644 --- a/src/layouts/TestLayout.module.scss +++ b/src/layouts/TestLayout.module.scss @@ -1,7 +1,10 @@ @import "@/styles/utils.scss"; .test_page { - @include vertical-center; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; width: 100%; } diff --git a/src/pages/test-page/QuestionPage.module.scss b/src/pages/test-page/QuestionPage.module.scss index ca796e5..6584df2 100644 --- a/src/pages/test-page/QuestionPage.module.scss +++ b/src/pages/test-page/QuestionPage.module.scss @@ -1,6 +1,8 @@ @import "@/styles/utils"; .question_page { + margin: 100px 0px; + .title { text-align: center; h1 { @@ -18,7 +20,7 @@ } .breadcrumb_wrapper { - margin: 30px 0px; + margin: 30px auto; } .btn { @@ -42,4 +44,23 @@ } @include mobile { + .question_page { + margin: 50px 0px; + .title { + h1 { + font-size: 40px; + } + p { + font-size: 14px; + } + } + .btn { + width: 160px; + height: 50px; + font-size: 18px; + } + } + .breadcrumb_wrapper { + padding: 0px 20px; + } } diff --git a/src/pages/test-page/QuestionPage.tsx b/src/pages/test-page/QuestionPage.tsx index 4a365fb..d3ab396 100644 --- a/src/pages/test-page/QuestionPage.tsx +++ b/src/pages/test-page/QuestionPage.tsx @@ -6,23 +6,46 @@ import { Question } from "../../components/test-page/Question"; import { questions, questionsPerPage } from "../../constants/Questions"; +import { useDispatch } from "react-redux"; + import styles from "./QuestionPage.module.scss"; -import { useSelector } from "react-redux"; -import { RootState } from "../../store/store"; +import { Dispatch } from "@reduxjs/toolkit"; +import { UserQuestionActions } from "../../store/user-question-slice"; export default function QuestionPage() { - const { submittedAnswer } = useSelector((state: RootState) => state.UserQuestion); - + const dispatch: Dispatch = useDispatch(); const navigate = useNavigate(); const [page, setPage] = useState(1); const onNextBtnClick = () => { - console.log(page * questionsPerPage, submittedAnswer.length); - if (page * questionsPerPage !== submittedAnswer.length) { + if (document.querySelectorAll(`input[type=radio]:checked`).length !== 5) { alert("아직 풀지 않은 문항이 있습니다!"); return; } + const startIdx = (page - 1) * questionsPerPage; + const endIdx = startIdx + questionsPerPage; + + let pageScore = 0; + + for (let i = startIdx + 1; i < endIdx + 1; i++) { + const selectedOption = document.querySelector(`input[name=question-option-${i}]:checked`); + + if (selectedOption) { + if (selectedOption.value === questions[i - 1].answer) { + pageScore += 1; + } + } + } + + dispatch(UserQuestionActions.setScore({ index: page, score: pageScore })); + + // Clear selected options on the current page + for (let i = startIdx + 1; i < endIdx + 1; i++) { + const options = document.querySelectorAll(`input[name=question-option-${i}]:checked`); + options.forEach((option) => (option.checked = false)); + } + window.scrollTo(0, 0); if (page === questions.length / questionsPerPage) navigate("/test/result"); else setPage((page) => page + 1); diff --git a/src/pages/test-page/ResultDetailPage.module.scss b/src/pages/test-page/ResultDetailPage.module.scss index 0c20bb4..f72c6b7 100644 --- a/src/pages/test-page/ResultDetailPage.module.scss +++ b/src/pages/test-page/ResultDetailPage.module.scss @@ -18,3 +18,12 @@ width: inherit; height: auto; } + +@include mobile { + .pros_cons_item { + flex-direction: column; + div { + width: 100% !important; + } + } +} diff --git a/src/pages/test-page/ResultDetailPage.tsx b/src/pages/test-page/ResultDetailPage.tsx index e2f2c52..d6d9e26 100644 --- a/src/pages/test-page/ResultDetailPage.tsx +++ b/src/pages/test-page/ResultDetailPage.tsx @@ -1,8 +1,10 @@ +import domtoimage from "dom-to-image"; + import { LabelContainer } from "../../interfaces/display/LabelContainer"; import { AvatarSection } from "../../components/test-page/AvatarSection"; -import { ShareSection, ShareItem } from "../../components/test-page/ShareSection"; +import { ShareContainer, ShareItem } from "../../components/test-page/ShareContainer"; import { ProsConsContainer } from "../../components/test-page/ProsConsContainer"; -import { LectureRedirectCard } from "../../components/test-page/LectureRedirectCard"; +import { LectureRedirectCard, LectureRedirectLink } from "../../components/test-page/LectureRedirectCard"; import resultPageStyle from "./ResultPage.module.scss"; import styles from "./ResultDetailPage.module.scss"; @@ -14,15 +16,38 @@ import shareFb from "@/assets/share-fb.png"; import shareLink from "@/assets/share-link.png"; import lectureImg from "@/assets/lecture.png"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store/store"; +import { shareKakaoLink } from "../../utils/ShareKakaoLink"; + +import { GetResult } from "../../constants/Result"; export default function ResultDetailPage() { + const { score } = useSelector((state: RootState) => state.UserQuestion); + const result = GetResult(score.reduce((prev, next) => prev + next) / 6); + + const GetCategory = (index: number) => { + switch (index) { + case 0: + return "경제기초"; + case 1: + return "은행상품"; + case 2: + return "카드와 신용"; + case 3: + return "세금"; + case 4: + return "보험"; + case 5: + return "투자"; + default: + return ""; + } + }; + return (
- +
@@ -38,24 +63,54 @@ export default function ResultDetailPage() {
- + +
- + +
- - - - - - - + + { + // eslint-disable-next-line, @typescript-eslint/no-unsafe-call + domtoimage.toJpeg(document.querySelector(`.${styles.result_page}`) as Node, { quality: 0.95 }).then(function (dataUrl) { + const link = document.createElement("a"); + link.download = "금융역량테스트 결과.jpeg"; + + link.href = dataUrl; + link.click(); + }); + }} + /> + { + console.log("click"); + shareKakaoLink("공유하기", "http://localhost:3000/test"); + }} + /> + alert("서비스 준비중입니다")} /> + alert("서비스 준비중입니다")} /> + { + window.navigator.clipboard.writeText("https://stocodi.com/test").then(() => { + alert("링크가 클립보드에 복사되었습니다"); + }); + }} + /> +
); } diff --git a/src/pages/test-page/ResultPage.module.scss b/src/pages/test-page/ResultPage.module.scss index 43d3c0f..59c92e2 100644 --- a/src/pages/test-page/ResultPage.module.scss +++ b/src/pages/test-page/ResultPage.module.scss @@ -4,8 +4,6 @@ width: 100%; max-width: 600px; - margin: 0px auto; - a { display: block; @@ -28,3 +26,15 @@ padding: 30px 0px; background-color: #fafbfd; } + +@include mobile { + .result_page { + a { + font-size: 16px; + } + } + + .result_section { + padding: 20px; + } +} diff --git a/src/pages/test-page/ResultPage.tsx b/src/pages/test-page/ResultPage.tsx index 46deab4..5e7f863 100644 --- a/src/pages/test-page/ResultPage.tsx +++ b/src/pages/test-page/ResultPage.tsx @@ -1,9 +1,12 @@ -import { useEffect, useState } from "react"; +import domtoimage from "dom-to-image"; + import { Link } from "react-router-dom"; import { AvatarSection } from "../../components/test-page/AvatarSection"; import { ResultGrid, ResultGridItem, ResultSummary } from "../../components/test-page/ResultSummary"; -import { ShareItem, ShareSection } from "../../components/test-page/ShareSection"; +import { ShareContainer, ShareItem } from "../../components/test-page/ShareContainer"; + +import { shareKakaoLink } from "../../utils/ShareKakaoLink"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -22,56 +25,35 @@ import shareKakao from "@/assets/share-kakao.png"; import shareIG from "@/assets/share-ig.png"; import shareFb from "@/assets/share-fb.png"; import shareLink from "@/assets/share-link.png"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store/store"; +import { useEffect } from "react"; +import { PostRequest } from "../../api/Request"; -export interface IScore { - type_basic: number; - type_bank: number; - type_credit: number; - type_tax: number; - type_insurance: number; - type_investment: number; -} +import { GetResult } from "../../constants/Result"; export default function ResultPage() { - const [score, setScore] = useState({ - type_basic: 0, - type_bank: 0, - type_credit: 0, - type_tax: 0, - type_insurance: 0, - type_investment: 0, - }); + const { score } = useSelector((state: RootState) => state.UserQuestion); + const result = GetResult(score.reduce((prev, next) => prev + next) / 6); useEffect(() => { - // 문제 채점 로직 - setScore({ - type_basic: 0, - type_bank: 0, - type_credit: 0, - type_tax: 0, - type_insurance: 0, - type_investment: 0, - }); - }, [score]); + PostRequest("/statistics/end", {}); + }); return (
- +
- + prev + next) / 6).toFixed(1)} /> - - - - - - + + + + + + @@ -80,13 +62,39 @@ export default function ResultPage() {
- - - - - - - + + { + domtoimage.toJpeg(document.querySelector(`.${styles.result_page}`) as Node, { quality: 0.95 }).then(function (dataUrl) { + const link = document.createElement("a"); + link.download = "금융역량테스트 결과.jpeg"; + link.href = dataUrl; + link.click(); + }); + }} + /> + { + console.log("click"); + shareKakaoLink("공유하기", "http://localhost:3000/test"); + }} + /> + alert("서비스 준비중입니다")} /> + alert("서비스 준비중입니다")} /> + { + window.navigator.clipboard.writeText("https://stocodi.com/test").then(() => { + alert("링크가 클립보드에 복사되었습니다"); + }); + }} + /> +
); } diff --git a/src/pages/test-page/TestPage.module.scss b/src/pages/test-page/TestPage.module.scss index 0c17051..3636119 100644 --- a/src/pages/test-page/TestPage.module.scss +++ b/src/pages/test-page/TestPage.module.scss @@ -51,11 +51,16 @@ text-align: center; h2 { - font-family: "Tendra"; + margin: 5px 0px; + font-family: "SCDream"; + font-weight: normal; + font-size: 16px; } h1 { + margin: 5px 0px; font-size: 3.5rem; - font-family: "SCDream"; + font-size: 60px; + font-family: "Tenada"; } } @@ -66,12 +71,13 @@ gap: 3px; width: 100%; - height: 100%; place-items: center center; place-content: center center; - margin: 30px auto; + align-items: flex-start; + + margin: 20px auto; div { width: 100%; @@ -79,10 +85,14 @@ @include place-center; - line-height: 1; - font-family: "Tenada"; - font-size: 3.5rem; - color: #fff; + span { + position: relative; + top: 4px; + + font-family: "Tenada"; + font-size: 3.5rem; + color: #fff; + } background-color: #404040; } @@ -108,22 +118,30 @@ padding: 20px 0px; h2 { - font-family: "Tendra"; - font-size: 1.2rem; + font-family: "SCDream"; + font-size: 12px; } h1 { font-size: 2.5rem; - font-family: "SCDream"; + font-family: "Tenada"; } } .grid { width: auto; height: auto; + + grid-template-columns: repeat(3, 80px); + grid-template-rows: repeat(3, 80px); gap: 0px; + + margin: 0px auto; + div { width: 90%; height: 90%; - font-size: 3rem; + span { + font-size: 3rem; + } } .img_grid { width: 90%; diff --git a/src/pages/test-page/TestPage.tsx b/src/pages/test-page/TestPage.tsx index 23bc344..4c3fd0c 100644 --- a/src/pages/test-page/TestPage.tsx +++ b/src/pages/test-page/TestPage.tsx @@ -2,6 +2,7 @@ import { useNavigate } from "react-router-dom"; import { Button } from "../../interfaces/forms/Button"; import styles from "./TestPage.module.scss"; +import { PostRequest } from "../../api/Request"; export default function TestPage() { const navigate = useNavigate(); @@ -14,23 +15,45 @@ export default function TestPage() {

금융역량테스트

-
-
+
+ +
+
+ +
-
+
+ +
-
-
-
-
+
+ +
+
+ +
+
+ +
+
+ +
-
diff --git a/src/store/user-question-slice.ts b/src/store/user-question-slice.ts index 1d8a7ba..81b9a4b 100644 --- a/src/store/user-question-slice.ts +++ b/src/store/user-question-slice.ts @@ -1,16 +1,11 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"; -export interface ISubmittedAnswer { - index: number; - selectedAnswer: "O" | "X" | "모르겠음"; -} - export interface IUserQuestion { - submittedAnswer: ISubmittedAnswer[]; + score: number[]; } export const initialState: IUserQuestion = { - submittedAnswer: [], + score: [0, 0, 0, 0, 0, 0], }; export const UserQuestionSlice = createSlice({ @@ -19,10 +14,8 @@ export const UserQuestionSlice = createSlice({ initialState: initialState, reducers: { - selectAnswer: (state, action: PayloadAction) => { - const index = state.submittedAnswer.findIndex((element) => element.index === action.payload.index); - if (index === -1) state.submittedAnswer.push(action.payload); - else state.submittedAnswer[index] = action.payload; + setScore: (state, action: PayloadAction<{ index: number; score: number }>) => { + state.score[action.payload.index - 1] += action.payload.score * 20; }, }, }); diff --git a/src/utils/ShareKakaoLink.ts b/src/utils/ShareKakaoLink.ts new file mode 100644 index 0000000..4f09002 --- /dev/null +++ b/src/utils/ShareKakaoLink.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +import { KAKAO_SDK } from "../api/env"; + +declare global { + interface Window { + Kakao: any; + } +} + +export const shareKakaoLink = (title: string, link: string): void => { + // const script = document.createElement("script"); + // script.setAttribute("src", "https://developers.kakao.com/sdk/js/kakao.js"); + // document.head.appendChild(script); + + if (window.Kakao) { + const kakao = window.Kakao; + if (!kakao.isInitialized()) { + kakao.init(KAKAO_SDK); + } + + kakao.Share.sendDefault({ + objectType: "feed", + content: { + title: title, + description: "설명", + imageUrl: "이미지 url", + link: { + mobileWebUrl: link, + webUrl: link, + }, + }, + buttons: [ + { + title: "title", + link: { + mobileWebUrl: link, + webUrl: link, + }, + }, + ], + }); + } +};