
주제 - 동영상 강의 시청 시, 딥러닝 기반으로 졸음 감지를 통한 학습효율증진 서비스 :러닝메이트
Hot Topic!
1. 어떻게 사용자의 집중도를 평가할 것인가
- 졸음 인지는?
- 낮은 자세로 필기하는 상태라면, 졸음 인지로 오해의 소지는?
-> 그 외로 정확하게 사용자의 상태와 자세를 판단할 수 있을 건인가
- 실시간으로 졸음 감지 결과를 어떻게 클라우드로 연결할 것인가
- server와 DB를 어떻게 연결할 것인가
(참고로 나는 백엔드&프론트 엔드)
1. ERD 구상 - 간략한 틀만 잡기
우리팀은 DB를 MySQL 사용하기로 했다. 나는 다루기 쉬운 MySQL Workbench로 구상할 것이다.
사실 사용자와 사용자의 집중도의 개체 생성이 가장 중요하다고 생각한다.
처음에는 account(회원 정보)와 member(사용자 정보)를 개체를 사용했더니 남이 보기에는 둘이 같은 거 아닌가? 헷갈릴 것 같다는 생각이 들었다. 확실하게 해주기 위해 이름을 변경하였다.
(수정전)

(수정후)

딱히 더 한 건 없지만 개체 추가 및 제목 수정하였다.
개체는 나중에 무한 생성(?)의 가능성이 존재하기에 간략하게만 틀을 잡아 봤다.
2. rest API 서버 구축 맛보기
서버와 DB의 연결고리를 rest API로 생각하고는 있다. rest API을 간단하게 설명하자면 HTTP URL을 통해 리소스에 접근 및 명시하고, http 메소드를 사용하여 CRUD을 맵핑시킵니다. 주의할 사항이 rest API의 설계할 때 지켜야 할 규칙들이 있다. 겨울방학동안 더 공부해야 할 것 같다. 다운로드만 받았지 감이 잡히지 않는다.
아래 사진은 rest API 서버 구축을 위해 spring initializr과 intellij를 사용한 내용이다.

처음에는 spring을 생각했으나, 딥러닝 파트를 python을 이용하기도 하고 시간내에 효율적으로 사용할 수 있는 것은 django라고 생각되어 선택되었다.
<Mac os를 위한 설치 방법 - 터미널 이용>
Step 1. Python설치 여부
python3 --version
미설치 시, brew install python3
Step 2. virtualenvwrapper 툴 설치
sudo pip install virtualenvwrapper
Step 3. 디렉토리 deskto으로 설정
cd Desktop
Step 4. 가상환경 이름 설정
virtualenv mydjango
Step 5. 현재 작업 디렉토리 변경
cd mydjango
Step 6. 가상환경 활성화
source bin/activate
Step 7. django 설치 및 확인
sudo pip install django==3.0.1
python -m django --version
Step 8. django 프로젝트 생성
django-admin startproject project01
Step 9. 서버 구동
python manage.py runserver
Step 10. http://127.0.0.1:8000 사이트 접속 및 성공적인 설치 여부 확인
나는 아직 프로젝트가 처음이라 백앤드와 프론트앤드를 동시에 할 수 없어서 하나씩 해나가기로 했다. 백앤드 보다는 접근성이 쉬운 프론트앤드 파트를 맡기로 하였다. 그리하여, React Js를 사용할 것이다.
npm -v
Step 3
바탕화면에 새 폴더 생성
Step 4
vsCode에서 새성한 새 폴더 열고, 새 터미널 열기
Step 5
npx create-react-app 프로젝트 이름
cd 프로젝트이름
npm start
그렇다면, react 로고와 함게 http://localhost:3000 페이지가 뜰 것이다.
성공!!!!!!!!!!
이제는 정말 프로젝트를 만들어볼 것이다.
우리 프로젝트에서는 실제 개발환경을 실행하려면 아래와 같은 명령어를 실행해야 한다.
frontend : npm run dev
backend : python manage.py runserver
react Js는 Javascript, html, CSS를 사용하여 이루어진다.
페이스북에서 개발한 라이브러리며, 대규모 웹 애플리케이션에서 데이터의 변화에 따라 동적인 UI를 만드는데 사용한다. 가상의 DOM을 유지하여 실제의 DOM과의 차이점을 계산하여 애플리케이션의 성능을 향상시킨다. 이는 실제 DOM에 직접 접근하여 변경하는 것보다 효율적이다.
1) 디렉토리 및 파일 형성(src에)
어떤 것을 구현할 것인지 큰 틀을 생각하여 디렉토리를 만들어준다.
예를 들어, 서비스 페이지(로그인 페이지, 회원가입 페이지, 홈페이지, 상세페이지 등)와 같은 Page 디렉토리를 만든다. 또한, To-do-list를 관리하기 위한 Redux 모듈을 사용한다면 Store, Action, Reducer 등과 같은 파일이 필요하기 때문에 TodoRedux 디렉토리를 만들면 된다.
2) JSX 파일
해당 디렉토리에 맞게 파일을 만들면 되는데, 나는 js 파일 보다는 jsx 파일을 만들 것이다. JSX 파일은 직관적인 코드 작성이 가능해서 UI 코드를 쉽게 수정할 수 있다. React는 컴포넌트 형식으로 이루어지기 때문에, JSX를 사용하면 컴포넌트의 출력을 직접 정희하여 컴포넌트 구조가 더 명확해진다. 이와 같은 여러 가지 이유로 나는 JSX 파일을 사용할 것이다.
- Tailwind CSS
다들 일반적으로 각 컴포넌트에 class를 넣어 따로 만든 CSS파일에 해당 class를 통해 UI를 디자인한다.
하지만 우리 프로젝트는 Tailwind CSS를 사용함으로써 별도의 CSS 스타일을 작성하지 않아 파일 형성의 번거로움을 없앴다. HTML 파일 내에 직접 클래스을 추가하여 효율적으로 스타일을 적용할 수 있게 했다.

나는 깔끔한 디자인으로 UI를 하고 싶어서 여러 버전과 수정을 통해 위의 화면이 최종 버전이 되었다.
*위에서 디렉토리와 파일을 생성에 관하여 말한대로 같이, Page 디렉토리를 만들어 Login.jsx 파일을 형성하였다.
import React from 'react';
import {IoLogoGoogleplus} from 'react-icons/io';
import {IoLogoFacebook, IoLogoTwitter} from "react-icons/io5";
import { useHistory } from 'react-router-dom';
import {MdTimer} from "react-icons/md";
import Loginerror from "../Page/Loginerror.jsx";
function Login() {
const history = useHistory();
const handlepage = () => {
history.push('/signup');
}
return (
<div>
<div className="flex items-center justify-center text-center text-5sm font-bold text-sky-400 my-5">
<h1 className="inline-flex items-center">LEARNING MATE</h1>
<MdTimer className="inline-block "/>
</div>
<div className="mx-auto max-w-md">
{/*로그인 모듈*/}
<div className="bg-white shadow-lg p-6 rounded-lg w-full ">
<div className="text-3xl font-bold text-center">
<h2>Welcome back!</h2>
</div>
<div className="text-center mb-4">
<span className="text-xs text-black tracking-wider mb-4 text-center">
or use your account
</span>
</div>
<div>
<Loginerror/>
</div>
</div>
{/*회원가입 모듈*/}
<div className="bg-white p-6 rounded-lg shadow-lg mt-4 w-full">
<div className="text-black text-3xl font-bold mb-4 text-center">
<h2>Hello, Friend!</h2>
</div>
<div>
<div className="text-black text-sm text-center">
Do you want to create a new account?
</div>
<div className="p-5 text-center">
<button
className="relative bg-gray-400 font-bold py-2 px-4 rounded w-21 h-10"
onClick={handlepage}
>
<span className="relative z-10 text-white focus:outline-none focus:shadow-outline hover:bg-transparent hover:text-black transition duration-300">Sign up</span>
<span
className="absolute top-0 left-0 w-full h-full bg-black opacity-0 transition duration-300"></span>
</button>
</div>
</div>
</div>
</div>
</div>
);
}
export default Login;
Login.jsx는 HTML을 사용하여 화면 UI만 구성하였다.
❗️하지만!
사용자가 로그인할 때, 사용자의 이메일 혹은 비밀번호를 잘못 작성하거나 서버에서 오류가 나는 여러 오류의 상황이 발생을 알려주는 로그인 오류 모달을 생성하였다.
import React, {useEffect, useState} from "react";
import axios from "axios";
import {useHistory} from 'react-router-dom';
import EmailorPwdError from "../components/EmailorPwdError.jsx";
function Loginerror() {
const history = useHistory();
//로그인 요소
const [email, setEmail] = useState('');
const [pwd, setPwd] = useState('');
//이메일 혹은 비밀번호 잘못 입력 시, 오류 모달창
const [errormodal, setErrormodal] = useState(false);
const handlesignin = async () => {
console.log("로그인 성공");
try {
await axios
.get("http://127.0.0.1:8000/api/login/", {
params: {
signin_email: email,
signin_pwd: pwd,
},
})
.then((response) => {
// 로그인 성공 -> 메인 페이지로 이동
console.log(response.data);
history.push({
pathname: "/",
state: { email: email },
});
})
.catch((error) => {
// 로그인 실패 -> 다시 시도하도록 구성(todo)
console.error("Error:", error);
alert("이메일 혹은 비밀번호를 잘못입력하셨습니다.");
setErrorModal(true);
});
} catch (error) {
console.log(error);
}
};
const modalClose = () => {
setErrorModal(false);
setErrorMsg("");
};
return (
<div>
<form>
<label htmlFor='signin_email'></label>
<input
type='email'
id="signin_email"
className="bg-gray-200 border-none px-4 py-3 my-2 w-full"
name="signin_email"
value={email}
placeholder="Email"
onChange={(e) => setEmail(e.target.value)}
required
/>
<br/>
<label htmlFor='signin_pwd'></label>
<input
className="bg-gray-200 border-none px-4 py-3 my-2 w-full"
type='password'
id="signin_pwd"
name='signin_pwd'
value={pwd}
placeholder="Password"
onChange={(e) => setPwd(e.target.value)}
required
/>
<br/>
<div className="text-center">
<button
type="button"
onClick={handlesignin}
className="relative bg-sky-300 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline hover:bg-transparent hover:text-black transition duration-300"
>
<span className="relative z-10">Sign in</span>
<span
className="absolute top-0 left-0 w-full h-full bg-purple-500 opacity-0 transition duration-300"></span>
</button>
</div>
</form>
<div>
<EmailorPwdError isOpen={errormodal} onClose={() => setErrormodal(false)}/>
</div>
</div>
);
}
export default Loginerror;
*이건 페이지 형성이 아니라 오류 모달이기 때문에 Component디렉토리를 만들어서 파일을 형성하였다.
이메일 입력필드에는 이메일 형식만, 패스워드 입력필드에는 사용자가 지정한 패스워드만 가능하다.
☕️ 이메일 형식이 아니거나 잘못된 패스워드를 입력 시, 아래의 사진과 같이 오류 모달창이 뜬다.
-> 💠 모달창으로 인해 사용자는 올바른 이메일과 패스워드를 다시 작성하여 서비스를 이용할 수 있다.

앞서 로그인 페이지에서 Sign up 버튼을 누르면 회원가입 페이지롤 연결된다.
(Login.jsx 파일에서 sign up을 누르면 Signup.jsx파일로 연결)
const handlepage = () => {
history.push('/signup');
}
<button
className="relative bg-gray-400 font-bold py-2 px-4 rounded w-21 h-10"
onClick={handlepage}
>
회원가입 페이지 파일
*Page 디렉토리 - Sign.jsx 파일 형성
import {useCallback, useState, useRouter} from "react";
import axios from "axios";
import {useHistory} from 'react-router-dom';
import React from "react";
import Errormodal from "../components/Errormodal.jsx";
function Signup() {
const history = useHistory();
//회원가입 요소 입력
const [id, setId] = useState('');
const [signuppwd, setSignuppwd] = useState('');
const [pwdconf, setPwdconf] = useState('');
const [name, setName] = useState('');
const [tel, setTel] = useState('');
const [signupemail, setSignupemail] = useState('');
//회원가입 요소 유효성 검사
const [isid, setIsid] = useState(false);
const [issignuppwd, setIssignuppwd] = useState(false);
const [ispwdconf, setIspwdconf] = useState(false);
const [isname, setIsname] = useState(false);
const [istel, setIstel] = useState(false);
const [issignupemail, setIssignupemail] = useState(false);
//오류메세지
const [idmsg, setIdmsg] = useState("");
const [signuppwdmsg, setSignuppwdmsg] = useState("");
const [pwdconfmsg, setPwdconfmsg] = useState("");
const [namemsg, setNamemsg] = useState("");
const [telmsg, setTelmsg] = useState("");
const [signupemailmsg, setSignupemailmsg] = useState("");
//오류 모달
const [modal, setModal] = useState(false);
//회원가입 폼 제출 시, 실행 및 로그인 페이지로 연결
//DB필드는 맞춰서 알아서 변경 필요
const onSubmit = async (e) => {
e.preventDefault();
try {
const result = await axios.post('http://127.0.0.1:8000/api/join/', {
user_login_id: id,
user_password: signuppwd,
user_name: name,
user_phone_number: tel,
user_email: signupemail
});
console.log("response", result);
if (result.status === 200) {
history.push('/login');
}
} catch (error) {
console.error(error);
if (error.response && error.response.status === 500) {
setModal(true);
}
}
}
//아이디
const handleId = () => {
if (!id) {
setIdmsg("아이디 입력해주세요.");
setIsid(true);
} else {
setIdmsg("");
setIsid(false);
console.log("아이디 입력 성공");
}
}
//비밀번호
const handleSignuppwd = () => {
if (!signuppwd) {
setSignuppwdmsg("비밀번호를 입력해주세요.");
setIssignuppwd(true);
} else {
setSignuppwdmsg("");
setIssignuppwd(false);
console.log("비밀번호 입력 성공");
}
}
//비밀번호 재입력
const handleSignuppwdconf = (e) => {
setPwdconf(e.target.value);
//비밀번호 재입력 필드가 공백일 때, 오류메세지 실행 하지 않음.
if (!e.target.value) {
setPwdconfmsg("");
setIspwdconf(false);
return;
}
if (signuppwd !== pwdconf) {
setPwdconfmsg("비밀번호가 일치하지 않습니다.");
setIspwdconf(true);
} else {
setPwdconfmsg("비밀번호가 일치합니다.");
setIspwdconf(false);
}
}
//이름
const handleName = (e) => {
if (!name) {
setNamemsg("이름을 입력해주세요.");
setIsname(true);
} else {
setNamemsg("");
setIsname(false);
console.log("이름 입력 성공");
}
}
//전화번호:000-0000-0000 형식으로 유효성 검사
const handleTel = (e) => {
const telsize = e.target.value;
const telvaild = /^\d{3}-\d{4}-\d{4}$/.test(e.target.value);
if (!telvaild || telsize.length !== 13) {
setTelmsg("000-0000-0000으로 입력해주세요.");
setIstel(true);
} else {
setTelmsg("");
setIstel(false);
console.log("전화번호 입력 성공");
}
}
//이메일
const handleSignupemail = (e) => {
if (!e.target.value) {
setSignupemailmsg("이메일을 입력해주세요.");
setIssignupemail(true);
} else {
const emailvaild = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(e.target.value);
if (!emailvaild) {
setSignupemailmsg("이메일 형식으로 입력해주세요.");
setIssignupemail(true);
} else {
setSignupemailmsg("");
setIssignupemail(false);
console.log("이메일 입력 성공");
}
}
}
return (
<div>
<div className="bg-white">
<header className="text-black text-center text-3xl signup-title py-6 font-bold sans-serif mt-8">
<h1>Let's be friends!</h1>
</header>
<div className="text-center bg-white">
<div className="mx-auto shadow-md w-80 p-16 mb-10 bg-white">
<form action='http://localhost:8000/api/join/' method='post' onSubmit={onSubmit}>
<label htmlFor="id"></label>
<div className="text-left text-black">
ID
<input
id="id"
className="rounded-lg"
type="text"
placeholder="아이디 입력"
value={id}
onChange={(e) => setId(e.target.value)}
onBlur={handleId}
required
/>
</div>
{idmsg && <p className="text-red-500 text-xs text-left">{idmsg}</p>}
<label htmlFor="password"></label>
<div className="text-left text-black ">
PASSWORD
<input
className="rounded-lg"
id="password"
type="password"
placeholder="비밀번호 입력"
value={signuppwd}
onBlur={handleSignuppwd}
onChange={(e) => setSignuppwd(e.target.value)}
required
/>
</div>
{signuppwdmsg && <p className="text-red-500 text-xs text-left">{signuppwdmsg}</p>}
<label htmlFor="passwordConfirm"></label>
<div className="text-left text-black ">
PASSWORD CONFRIM
<input
id="passwordConfirm"
className="rounded-lg"
type="password"
placeholder="비밀번호 재입력"
value={pwdconf}
onBlur={handleSignuppwdconf}
onChange={(e) => setPwdconf(e.target.value)}
required
/>
</div>
{pwdconfmsg && <p className="text-red-500 text-xs text-left">{pwdconfmsg}</p>}
<label htmlFor="name"></label>
<div className="text-left text-black ">
NAME
<input
id="name"
className="rounded-lg"
type="name"
placeholder="이름"
value={name}
onChange={(e) => setName(e.target.value)}
required
onBlur={handleName}
/>
</div>
{namemsg && <p className="text-red-500 text-xs text-left">{namemsg}</p>}
<label htmlFor="tel"></label>
<div className="text-left text-black ">
TEL
<input
id="tel"
className="rounded-lg"
type="tel"
placeholder="전화번호 입력"
value={tel}
onBlur={handleTel}
onChange={(e) => setTel(e.target.value)}
required
/>
</div>
{telmsg && <p className="text-red-500 text-xs text-left">{telmsg}</p>}
<label htmlFor="email"></label>
<div className="text-left text-black ">
EMAIL
<input
id="email"
className="rounded-lg"
type="email"
placeholder="이메일 입력"
value={signupemail}
onBlur={handleSignupemail}
onChange={(e) => {
setSignupemail(e.target.value)
}}
required
/>
</div>
{signupemailmsg && <p className="text-red-500 text-xs text-left">{signupemailmsg}</p>}
<div className="mt-4">
<button
className="text-3sm font-bold text-blue-900 transition-all duration-300 ease-in-out hover:shadow-inner shadow-xl hover:shadow-outline hover:translate-x-1 hover:translate-y-1 "
type="submit">가입하기
</button>
</div>
</form>
<div>
<Errormodal isOpen={modal} onClose={() => setModal(false)}/>
</div>
</div>
</div>
</div>
</div>
);
}
export default Signup;

ID와 PASSWORD는 사용자가 원하는 대로 작성하면 된다.
PASSWORD CONFIRM을 통해 사용자가 비밀번호를 올바르게 작성했는지 재확인을 한다. 다르게 작성한다면, 빨간 글씨로 다르게 썼다는 걸 알 수 있다.
TEL은 000-0000-0000 형식으로 작성해야 한다는 것을 빨간 글씨로 또 알려줄 것이다.
EMAIL은 이메일 형식에 맞게 작성해야 한다. 
위와 같이 잘못된 형식으로 작성했다면, 빨간 글씨로 재확인 할 수 있다.
회원가입 또한 오류 모달을 만들었다.
필드 아래의 빨간 글씨의 오류 메세지를 미쳐 확인하지 못 하고 회원가입 버튼을 누를 수 있다. 혹은 서버에 문제가 발생할 수 있다.
-> 이것을 사용자가 오류의 원인 파악 및 오류 해결을 통해 정상적인 회원가입이 가능하므로 오류 모달을 생성하였다.

올바른 형식으로 회원가입을 성공했다면, 로그인 페이지로 바로 연결된다.
다시 로그인을 통해 홈페이지에 입장할 수 있다.
*Page 디렉토리 - Home.jsx파일
import React, {useState, useEffect} from "react";
import {CgProfile} from "react-icons/cg";
import axios from "axios";
import {Link, useHistory} from "react-router-dom";
import {Provider} from "react-redux";
import {FaHeartCirclePlus} from "react-icons/fa6";
import {useLocation} from "react-router-dom";
import PracticeCam from "../components/PracticeCam.jsx";
function Home() {
const [user, setUser] = useState("로그인 필요");
const [study, setStudy] = useState([]);
//로그인 버튼 시, 로그인 페이지로 전환
const history = useHistory();
const [stream, setStream] = useState(false);
const [isNear, setIsNear] = React.useState(false);
useEffect(() => {
fetchData();
}, [graphActive]);
//백엔드 코드(사용자 정보 가져오기)
const fetchData = async () => {
try {
if (location.state && location.state.email) {
// 서버에서 받은 응답 데이터에서 사용자 이메일을 가져옴
const email = location.state.email;
console.log(email);
// study_todo 가져오기 위한 axios
const response = await axios.get("http://127.0.0.1:8000/api/study/", {
params: {
email: email,
},
});
setUser(response.data.user);
console.log(response.data.feeds);
await setStudy(response.data.feeds);
setisLoading(false);
} else {
// 로그인 필요한 경우
setUser("로그인 필요");
//setStudy([]);
setisLoading(true);
if (!location.state || !location.state.email) {
history.push("/login");
}
}
} catch (error) {
console.log(error);
}
};
const handletologin = () => {
history.push("/login");
};
const MenuBtn = () => {
return (
<nav className="menu" style={{textAlign: "center"}}>
<div>
<Link to="/" className="m-5 outline-none custom-btn btn-1 text-xl">
홈
</Link>
<Link
to="/focus-analysis"
className="m-5 outline-none custom-btn btn-1 text-xl">
집중도 분석
</Link>
<Link
to="/my-page"
className="m-5 outline-none custom-btn btn-1 text-xl">
마이페이지
</Link>
</div>
</nav>
);
};
return (
<div>
{/*머리*/}
<div className="flex justify-between items-center">
<div className="text-5xl font-bold ml-6">
<h1>Learning Mate</h1>
</div>
<div style={{textAlign: "center", margin: "10px", marginTop: "30px"}}>
<div
className="items-center"
style={{marginLeft: "auto", marginRight: "auto", width: "50%"}}>
<CgProfile className="text-3xl text-left"/>
</div>
<div onClick={handletologin}>{user}</div>
</div>
</div>
<hr/>
{/*메뉴바*/}
<div className="p-2 bg-sky-300 text-white font-bold">
<MenuBtn/>
{/*{user}*/}
</div>
<hr/>
</div>
</div>
);
}
export default Home;

우리 서비스르 로고를 맨 왼쪽에 배치하였다.
맨 오른쪽에는 미니프로필을 생성하여 로그인이 잘 생성되었다면, 자신의 이메일이 뜰 것이다.
아니라면, 로그인 필요가 뜰 것이다. 로그인 필요 버튼을 누르면 로그인 페이지로 이동하게 된다.
const handletologin = () => {
history.push("/login");
};
메뉴바를 생성하고 클릭할 때, 새로운 페이지를 형성하기 보다는 네이버처럼 밑에 컴포넌트만 다르게 페이지가 형성되게 디자인을 하였다.
React-Router의 Link 컴포넌트를 사용하여 애플리케이션 내의 다른 페이지로 내비게이션 링크를 구성하였습니다.
사용자가 클릭할 수 있는 여러 링크를 포함하고 있으며, 각 링크는 특정 경로로 라우팅 됩니다.
이때 발생하는 문제는 미니프로필이다. 홈페에지는 사용자의 정보가 정상적으로 연결되는데, 집중도 분석 페에지랑 마이페이지 메뉴 버튼을 눌렀을 때 사용자의 정보가 그대로 이어지기 않기 때문에 데베 설정이 필요하다.
우리의 서비스가 자동 스톱워치가 주기능이기 때문에 총 공부시간이 제일 잘 보였으면 했다. 그래서 맨 위에 더 큰 글씨로 텍스트를 위치시켰다.
스톱워치가 밀리초단위로 설정되어 바로 옆 스톱워치 제어 버튼이 빠르게 흔들린다. 대부분 밀리초 보다는 시간, 분, 초로 시간을 확인하기 때문에 변경해줬다.
// 시간,분,초
return (
<div className="timer">
<span className="digits">
{("0" + Math.floor((props.time / 3600000) % 24)).slice(-2)}:
</span>
<span className="digits">
{("0" + Math.floor((props.time / 60000) % 60)).slice(-2)}:
</span>
<span className="digits">
{("0" + Math.floor((props.time / 1000) % 60)).slice(-2)}
</span>
</div>
);
To-do-list를 좀 더 귀엽게 만들고 싶어서 아이콘을 각 텍스트 옆에 아이콘을 넣었다. 아이콘은 React-icon을 사용하였다.
-> 구글에 React-icon을 검색하면, 사용하고 싶은 아이콘을 선택하면 그에 맞는 코드가 나온다.
import만 잘 넣어주면 아이콘을 잘 사용할 수 있다.
홈페이지 디자인 원래 어두운 색깔 위주로 UI를 만들었는데, 너무 눈에 잘 들어오지 않는 것 같아 밝은 색상으로 변경하였다.
사용자가 쉽게 사용할 수 있도록, 각 컴포넌트를 쉽게 알아낼 수 있도록 하기 위해 현제 홈페에지 버전이 탄생하였다.
*Page 디렉토리 - Focus.jsx

사용자의 집중도 분석 결과값을 저장하여 과거의 기록을 볼 수 있는 집중도 페이지를 형성하였다.
사용자가 보고 싶은 날짜를 선택하기 위해 React-datePicker 라이브러리를 사용하였다.
*react-datepicker 다운로드 방법
npm install react-datepicker date-fns
npm install --save-dev @types/react-datepicker
CMD창에 frontend 경로를 가져와 위의 명령어 두 개를 통해 다운로드하면 된다.
*Page 디렉토리 - Mypage.jsx파일
마이페이지는 사용자의 정보를 볼 수 있게 만들려고 한다.
이러한 여러 페이지를 사용자에게 렌더링하기 위해 APP.jsx 파일을 통해 나타냈다.
import './App.css';
import Login from "./Page/Login";
import {BrowserRouter as Router, Switch, Route} from "react-router-dom";
import Signup from "./Page/Signup";
import Home from "./Page/Home.jsx"
import Focus from "./Page/Focus.jsx";
import Mypage from "./Page/Mypage.jsx";
function App() {
return (
<Router>
<Switch>
<Route path="/login">
<Login/>
</Route>
<Route path="/signup">
<Signup/>
</Route>
<Route path="/focus-analysis">
<Focus/>
</Route>
<Route path="/my-page">
<Mypage/>
</Route>
<Route path="/">
<Home/>
</Route>
</Switch>
</Router>
);
}
export default App;
❗️렌더링 하고 싶은 페이지를 꼭 경로에 맞게 파일을 import해야 한다.
라우터 설정
1. BrowserRouter:
BrowserRouter 컴포넌트를 사용하여 HTML5의 History API를 활용해 UI를 URL과 동기화합니다. 이 컴포넌트는 라우팅 컨텍스트를 제공하는 래퍼로, 라우팅 정보를 내부적으로 관리하면서 URL 변경에 따라 적절한 컴포넌트를 렌더링하게 해줍니다.
2. Switch:
Switch 컴포넌트는 URL과 일치하는 첫 번째 Route 컴포넌트만 렌더링하여 여러 라우트가 동시에 렌더링되는 것을 방지하고, 한 번에 하나의 라우트만 활성화되도록 관리합니다.
=> React Router 구조를 선택한 이유는 URL을 통해 에플리케이션의 다양한 페이지를 직접 방문할 수 있고, URL 변경을 감지하여 관련 컴포넌트를 동적으로 렌더링이 가능하기 때문이다.
사용자의 경험을 향상시키고 페이지 로딩 시간을 줄이는 장점을 가지고 있다.