[React MBTI 만들기] #03. 결과페이지 제작 / Figma 디자인 / 테스트 컨셉 변경
[React MBTI 만들기] (scss/JavaScript/git)
#03. 결과페이지 제작 / Figma 디자인 / 테스트 컨셉 변경
목차.

00. Intro
01. 결과페이지 완성.
02. MBTI Figma 디자인 및 컨셉변경
03. 마무리
04. 최종 코드 및 깃허브
00. Intro
이번에는, React MBTI 테스트의 결과페이지 제작을 마무리하고, 배포도 다시 진행했으며 그 후 테스트 컨셉을 변경해서 스토리를 짠 다음 이를 토대로 Figma 디자인을 진행했다.
디자인하는데 재미있어서 디자인을 몇 시간 동안 한 것 같다. React MBTI 개발이 생각보다 금방 끝날 줄 알았는데, 학업과 병행하며 내가 만들고자 하는 컨셉으로 변경하려니 꽤나 시간이 소요되는 것 같다.
- 사용 기술 : React, JavaScript, scss, git/github, Figma + npm run deploy를 이용한 깃허브 배포
- 기록 툴 : Notion
- 기간 : 2024.03.12 ~ 2024.03.19
01. 결과페이지 완성.
1. 결과 사진
결과페이지를 완성했다. MBTI 결과와 함께, 저장된 결과 배열 개수에 따라 말풍선이 나오게 만들었다.
결과 사진의 경우는, 설명 말풍선이 3개이므로, 배열 안에 3개의 결과가 들어가 있는 것을 알 수 있다.

2. 결과 부분 나타낼 Object
- ESFP mbti의 설명 배열의 요소가 3개이다.
{mbti:"ESFP",contents:['사교형','','']},
3. map을 활용한 설명 전체 나타내기 코드
-> map을 사용하여, mbti 설명(content) 배열의 요소를 모두 나타내준다.
- 그 외는 '당신의 mbti는 , 0000 는요' 이런 설명 글을 직접 <div>로 추가해 주면 된다.
<div className="chatListLayout">
<div className='chatBox'>
<div>◀</div> <div> 당신의 MBTI는 {mbtiContents.mbti} 입니다. </div>
</div>
<div className='chatBox'>
<div>◀</div> <div>{mbtiContents.mbti}는요</div>
</div>
// map으로 contents 배열의 개수만큼 <div> 생성. (val : 현재 요소1개, idx : 현재 인덱스)
{mbtiContents.contents.map((val,idx)=>
<div className='chatBox' key={idx}>
<div>◀</div> <div>{val}</div>
</div>
)}
</div>
02. MBTI Figma 디자인 및 컨셉변경
이번에는 MBTI 컨셉을 바꾸고, Figma를 디자인했다.
1. 바뀐 컨셉
- 이번에는 '울산대학교 새내기 유형 MBTI' 컨셉으로 테스트 기획을 새로 했다.
- 문항이 총 12개인데 생각보다 많아서, 이게 개발보다 훨씬 오래 걸렸다. 역시 기획은 어렵다.
q: ['입시가 끝나고, 드디어 울산대학교 새내기가 되었다! 나의 기분은?'],
a:[ // 답변
{type:"I", text:"설레면서도 잘 지낼 수 있을까 걱정된다."},
{type:"E", text:"새로운 친구들을 만날 생각에 너무 기대된다."}]
위 코드블록은, 예시 내용이다.
2. MBTI Figma 디자인 ( '울산대학교 새내기 유형 MBTI' )
- Figma를 이용해서 바뀐 컨셉을 토대로, 새롭게 디자인했다.
- 그리고 새로운 기능도 추가로 디자인했다.
- 디자인하는데 재미있어서 3시간 넘게 한 것 같다. 그리고 혼자서 디자인을 하려니 어려워서, 레퍼런스를 많이 찾았다.
2-1. 바뀌기 전 MBTI 디자인 (강의자료 보고 그대로 만든 거)

2-2. 바뀐 MBTI 디자인 (Figma)


이렇게 디자인을 해봤는데, 디자인 자체는 참고해서 만들어서 깔끔하게 만들기 위해 노력했다.
그런데 너무 초록초록하기만 해서 별로 안 예쁘다는 의견이 있었다.
색상을 수정할지는 생각해 봐야겠다.
2-3. 디자인 참고 자료

그리고, 디자인을 하며 참고한 자료는 다음과 같다. (더 보기 클릭)
1. 색상 : Ai colors에서 직접 울산대학교 색 뽑아서 사용
2. 디자인
- 2. 슬기로운 대학생활 ( @Contributors)
- 3. 플라워BTI(FlowerBTI)-케이테스트 (ktestone.com)
- 4. color Test mbti
- 5. glam mbti test : 연애 능력치 테스트 | 글램 (glam.am)
- - (사실상 연애 능력치 테스트 ui를 거의 보고 만들었다 ㅎㅎ)
03. 마무리
이번에는 기능을 모두 구현하고 , 디자인까지 했다.
이제는 디자인한 걸로 만들어둔 React MBTI 테스트를 수정하고,
TypeScript를 조금씩 공부할 것이다.
왜냐하면, 목표는 TypeScript로 MBTI 만들기여서이다!
앞으로도 파이팅!
04. 최종 코드 및 깃허브
0. 깃허브 주소
Ji-ny/mbti_test at dev (github.com)
GitHub - Ji-ny/mbti_test: React 기반 mbti_test입니다.
React 기반 mbti_test입니다. Contribute to Ji-ny/mbti_test development by creating an account on GitHub.
github.com
1. App.js (더 보기 클릭하시면 코드 확인이 가능합니다.)
import { useEffect, useState } from 'react';
import './App.scss';
function App() {
/** 현재 뷰포트 높이의 1%를 계산한다. */
const setVh = () => {
const vh = window.innerHeight * 0.01; //window.innerHeight : 뷰포트의 높이를 가져온다. | 뷰포트 높이를 100분의 1로 나누어서 사용하는 것 (1%)
document.documentElement.style.setProperty('--vh', `${vh}px`); // css 변수를 만들어준다.
}
// 첫 렌더링시, 뷰포트 사이즈 계산 후 적용
useEffect(()=> {
setVh();
// 사이즈가 변경될 때, 다시 뷰포트 높이를 구한다.
function onResize(){
setVh();
}
// 이벤트 유형 , 이벤트가 발생할때 실행하는 함수.
window.addEventListener('resize', onResize);
},[]);
const [page, setPage] = useState(0); // 페이지 번호 상태
// 질문 & 답변 리스트 생성
const questionList = [
{
q: ['입시가 끝나고, 드디어 울산대학교 24학번 새내기가 되었다! 나의 기분은?'],
a:[ // 답변
{type:"I", text:"설레면서도 잘 지낼 수 있을까 걱정된다."},
{type:"E", text:"새로운 친구들을 만날 생각에 너무 기대된다."}]
},
{
q: ['수업을 듣기 위해 강의실에 왔다. 옆자리 사람에게 말을 걸어볼까? '],
a:[ // 답변
{type:"I", text:"(먼저 말 걸어주기를 기다린다.)"},
{type:"E", text:"안녕하세요? 혹시 이름이 뭐에요?"}]
},
{
q: ['새신발(새터)에 왔다! OT때 인사했던 친구가 지나가는데..?!'],
a:[ // 답변
{type:"I", text:"인사 해야말까 고민하다가 그냥 지나간다."},
{type:"E", text:"OO아 안녕!?"}]
},
{
q: ['대학교 입학식 전날! 나의 심정은?'],
a:[ // 답변
{type:"S", text:"내일 첫날이니까, 빨리 자야겠다."},
{type:"N", text:"너무 떨린다. 입학식을 시뮬레이션해본다."}]
},
{
q: ['밤새 술을 마셔서 밖에 보니 해가 뜨고 있다. 어떡하지?'],
a:[ // 답변
{type:"S", text:"이게 대학생활이지."},
{type:"N", text:"해를 보면서 감상에 젖는다."}]
},
{
q: ['예쁜(잘생긴) 이성 친구가 말을 걸어온다! 내 심정은?'],
a:[ // 답변
{type:"S", text:"말을 걸어줘서 기분이 좋다."},
{type:"N", text:"그 친구와 결혼까지 생각한다."}]
},
{
q: ['친구가 시험 공부를 제대로 못했다고 한다. 뭐라고 대답할까?'],
a:[ // 답변
{type:"F", text:"헐 어떡해ㅠㅠ 그래도 시험 잘 칠거야!"},
{type:"T", text:"어쩌다 공부 많이 못했어?"}]
},
{
q: ['친구가 "나 우리과가 잘 안 맞는것 같아,,, 전과할까?" 라고 말한다'],
a:[ // 답변
{type:"F", text:"어디가 안 맞는 것 같아?"},
{type:"T", text:"어느과로 하게?"}]
},
{
q: ['친구가 몰래 과CC를 한다고 알려준다! 나의 반응은?'],
a:[ // 답변
{type:"F", text:"와,, 진짜 낭만있다.. 나도 해보고 싶어!"},
{type:"T", text:"진짜?! 누구랑?"}]
},
{
q: ['대학교 친구들과 경주 여행을 가기로 했다. 일정을 잡아야 하는데..'],
a:[ // 답변
{type:"P", text:"숙소만 얼른 잡고 나머지는 가서 생각하자"},
{type:"J", text:"숙소와 일정 계획까지 모두 정리한다."}]
},
{
q: ['수강신청 전날! 들을 교양을 찾아야 한다.'],
a:[ // 답변
{type:"P", text:"대충 별점 높은 교양을 아무거나 잡는다."},
{type:"J", text:"강의평을 세세히 보고, 여러 교양을 알아본다."}]
},
{
q: ['지금 과제가 쌓여있다. 어떻게 할까?'],
a:[ // 답변
{type:"P", text:"과제 마감 전날에 몰아서 한다."},
{type:"J", text:"미리미리 과제를 해놓는다."}]
},
{
q: ['테스트 종료! 결과를 보러 가시겠습니까? '],
a:[ // 답변
{type:"", text:"결과 보러 가기"},
]
},
];
const [mbtiList, setMbtiList ] = useState([
{name:"I", count:0}, {name:"E", count:0}, {name:"S", count:0}, {name:"N", count:0},
{name:"F", count:0}, {name:"T", count:0}, {name:"P", count:0}, {name:"J", count:0},
]);
// 답변을 클릭했을 때 실행할 함수. (MTTI 타입 / 질문 idx)
const handleCkAnswer = (type, idx) => {
let ls = mbtiList;
for(let i = 0; i < ls.length; i++){
if (ls[i].name === type){ //mbtiList의 name이, type과 같을 경우
ls[i].count +=1; // count값을 1 늘려준다.
}
}
setMbtiList(ls); //mbtiList를 업데이트 시킨다.
setPage(page+1); // 페이지도 1 업데이트 시킨다.
//결과페이지 | idx가 질문리스트 길이만큼 됐다면
if(idx+1 === questionList.length){
// console.log("결과보기")
setMbti();
}
}
// 최종 mbti 결과를 담을 상태
const [mbtiContents, setMbtiContents] = useState([]);
// mbti 설정해주는 함수
function setMbti(){
//각 mbti의 특성
let mc = [
{mbti:"ISTP",contents:['백과사전형','','']},
{mbti:"ISFP",contents:['성인군자형','','']},
{mbti:"ISTJ",contents:['과학자형','','']},
{mbti:"ISFJ",contents:['권력형','','']},
{mbti:"INFJ",contents:['예언자형','','']},
{mbti:"INTJ",contents:['과학자형','','']},
{mbti:"INFP",contents:['잔다르크형','','']},
{mbti:"INTP",contents:['아이디어형','','']},
{mbti:"ESFP",contents:['사교형','','']},
{mbti:"ESTP",contents:['활동가형','','']},
{mbti:"ESFJ",contents:['친선도모형','','']},
{mbti:"ESTJ",contents:['사업가형','','']},
{mbti:"ENTP",contents:['발명가형','','']},
{mbti:"ENFP",contents:['스파크형','','']},
{mbti:"ENFJ",contents:['언변능숙형','','']},
{mbti:"ENTJ",contents:['지도자형','','']},
]
// I와 E를 구분한다.
let IorE= // data : mbtiList를 데표하는 변수.
mbtiList.find(function(data){return data.name === "I"}).count >
mbtiList.find(function(data){return data.name === "E"}).count ? "I" : "E";
//구분한다.
let NorS= // data : mbtiList를 데표하는 변수.
mbtiList.find(function(data){return data.name === "N"}).count >
mbtiList.find(function(data){return data.name === "S"}).count ? "N" : "S";
//구분한다.
let ForT= // data : mbtiList를 데표하는 변수.
mbtiList.find(function(data){return data.name === "F"}).count >
mbtiList.find(function(data){return data.name === "T"}).count ? "F" : "T";
//구분한다.
let JorP= // data : mbtiList를 데표하는 변수.
mbtiList.find(function(data){return data.name === "J"}).count >
mbtiList.find(function(data){return data.name === "P"}).count ? "J" : "P";
// 아래 변수에, 찾은 MBTI를 모두 합쳐준다.
let mbti = IorE+ NorS + ForT + JorP;
console.log("결과:mbti",mbti);
// 도출되는 mbti와 mc배열안에 있는 mbti 배열을 찾아서 값이 일치하는 것만 찬는다.
setMbtiContents(mc.filter((val)=>val.mbti === mbti)[0]);
}
return (
<div className="mbtiLayout">
{/* 페이지에 따라 나타내는 상태가 다르게 한다. */}
{page===0?
<div className='startPageLayout'>
<div className='startLogo'>
<div>MBTI</div>
<div>▼</div>
</div>
<div onClick={()=> setPage(1)} className='startButton' >테스트 시작하기</div>
</div>
: page <= questionList.length? // 현재 페이지가 질문 길이보다 작다면,
<div className='questionLayout'>
<div className='mbtiTitle'>
<div>MBTI 테스트</div>
<div>{`${page} / ${questionList.length}`}</div>
</div>
{questionList.map((val,idx)=> //질문 리스트 받기
<div className='questionList' key={idx} style={{display:page===idx+1? "flex" : "none"}}>
{console.log(mbtiList)}
<div className='questionItemLayout'>
<div className='profileImg'>
<div/><div/>
</div>
<div className="chatListLayout">
{val.q.map((qval, qidx)=>
<div key={qidx} className='chatBox'>
{/* 채팅 내용 */}
<div>◀</div> <div> {qval} </div>
</div>
)}
</div>
</div>
<div className='answerItemLayout'>
<div className="aChatBox">
<div>+</div> <div>#</div>
</div>
{/* 답변 내용 */}
{val.a.map((aval,aidx) =>
<div key={aidx} className="answerBox" onClick={() => handleCkAnswer(aval.type, idx )}>
{aval.text}
</div>
)}
</div>
</div>
)}
</div>
: // 현재 페이지가 질문 길이보다 크다면, (결과페이지)
<div className='questionLayout'>
<div className='mbtiTitle'>
<div>MBTI 테스트</div>
<div onClick={()=> window.location.reload()}>다시하기</div>
</div>
<div className='questionList'style={{display:"flex"}}>
<div className='questionItemLayout'>
<div className='profileImg'>
<div/><div/>
</div>
<div className="chatListLayout">
<div className='chatBox'>
<div>◀</div> <div> 당신의 MBTI는 {mbtiContents.mbti} 입니다. </div>
</div>
<div className='chatBox'>
<div>◀</div> <div>{mbtiContents.mbti}는요</div>
</div>
{mbtiContents.contents.map((val,idx)=>
<div className='chatBox' key={idx}>
<div>◀</div> <div>{val}</div>
</div>
)}
</div>
</div>
</div>
</div>
}
</div>
);
}
export default App;
2. App.scss (더보기 클릭하시면 코드 확인이 가능합니다.)
.mbtiLayout{
/*모바일 흐름장에서 볼 때 주소창과 네이게이션과의
높이는 고려하지 않고 계산이 되어서,
불필요한 스크롤이 생기기 때문이다.*/
width: 100vw; // heigth는 100vh로 쓰지 않는다.
height: calc(var(--vh,1vh)*100); // var() : Css변수를 가져오고, 값이 없을경우 기본값으로 1vh를 사용.
font-size: 14px;
display: flex;
justify-content: center;
background-color: #eee;
.startPageLayout{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #ffea35;
width: 100%;
max-width: 900px;
height: 100%;
color: #413730;
.startLogo{
display: flex;
flex-direction: column;
align-items: center;
font-size: 25px;
font-weight: 700;
//MBTI
> div:nth-child(1){
background-color: #413730;
color: #ffea35;
width: 100px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50px;
}
//▼
> div:nth-child(2){
margin-top:-15px;
}
}
.startButton{
font-size: 17px;
font-weight: 700;
margin-top: 20px;
cursor: pointer; // 마우스를 올렸을 때 커서를 손바닥 모양으로 변경
}
}
.questionLayout{
display: flex;
flex-direction: column;
background-color: #bacee0;
width: 100%;
max-width: 900%;
height: 100%;
overflow: hidden; // 스크린 없애기
// MBTI 제목, 페이지
.mbtiTitle{
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
padding:15px;
box-sizing: border-box;
font-weight: 700;
>div:nth-child(1){ //"MBTI 테스트"
display: flex;
flex:1; // 1을 줘서, 화면 비율에 따라 유연하게 늘어나거나 줄어들 수 있음을 만드는 속성
}
}
// 질문리스트
.questionList{
flex-direction: column;
height: 100%;
overflow: scroll;
scrollbar-width: none; // 스크롤바 표시는 없지만, 여전히 스크롤 가능
// background-color: #ffffff;
// border: 3px solid red;
.questionItemLayout{
display: flex;
flex:1;
margin : 10px 15px 5px 15px;
.profileImg{
display: flex;
flex-direction: column;
width: 40px;
height: 40px;
background-color: #a1b6e9;
border-radius: 15px;
margin-right: 5px;
align-items: center;
justify-content: center;
>div:nth-child(1){
background-color: #cbd6f2;
width: 8px;
height: 8px;
border-radius: 4px;
}
>div:nth-child(2){
background-color: #cbd6f2;
width: 16px;
height: 8px;
border-radius: 8px 8px 0px 0px;
margin-top: 2px;
}
}
// 상대방 채팅 박스 영역
.chatListLayout{
display: flex;
flex-direction: column;
// 채팅 하나하나 박스
.chatBox{
display: flex;
flex-direction: row;
align-items: flex-start;
max-width: 60vw;
margin: 3px 0px 12px 0px;
>div:nth-child(1){
color:#fff;
font-size: 12px;
margin-top : 8px;
}
>div:nth-child(2){
padding: 7px 10px 7px 10px;
background-color: #fff;
margin-left:-4px;
border-radius: 10px;
text-align: left;
}
}
}
}
.answerItemLayout{
width: 100%;
background-color: #fff;
padding-bottom: 10px;
display: flex;
flex-direction: column;
.aChatBox{
display: flex;
width:100%;
color: #969696;
font-size: 22px;
border-bottom: 1px solid #eee; // 아래 테두리 속성
margin-bottom: 25px;
> div:nth-child(1){
display: flex;
flex: 1;
padding:10px;
box-sizing: border-box;
}
> div:nth-child(2){
padding:10px;
}
}
.answerBox{
display: flex;
align-items: center;
justify-content: center;
height: 100px;
text-align: center;
box-shadow: 2px 2px 0px #ededed;
border:1px solid #ededed;
margin: 0px 20px 15px 20px;
border-radius: 20px;
cursor: pointer; // 포인터가 손가락 모양으로 보이도록
}
}
}
.questionList::-webkit-scrollbar{
display: none; // 스크롤바가 숨겨지지만, 스크롤 기능은 유지한다.
}
}
}
[React로 MBTI 테스트 만들기] 시리즈의 다른 게시글을 보시려면 , 아래 링크로 이동하시면 됩니다!
[React로 MBTI 만들기 시리즈] | 링크 |
#01. 리액트 프로젝트를 깃허브로 웹사이트 배포 | https://raon-2.tistory.com/48?category=1222538 |
#02. 시작/테스트/결과페이지 만들기 | https://raon-2.tistory.com/49 |