diff --git a/src/App.jsx b/src/App.jsx index 9588690..1927bf6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,56 +1,81 @@ -import React from 'react'; -import { BrowserRouter as Router, Routes, Route,Navigate } from 'react-router-dom'; -import { useState } from 'react'; -import Navbar from './components/Navbar'; -import Home from './pages/Home'; -import About from './pages/About'; -import NotFound from './pages/NotFound'; -import WishlistPage from './pages/WishlistPage'; -import Magazine from './pages/Magazine'; -import SignupForm from './pages/Signup'; -import CalendarPage from './pages/CalendarPage'; -import LoginPage from './pages/login'; -import RegisterPage from './pages/register'; -import Register_success from './pages/Register_success'; -import SurveyPage from './pages/Survey'; +import React from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import Navbar from "./components/Navbar"; +import Home from "./pages/Home"; +import About from "./pages/About"; +import NotFound from "./pages/NotFound"; +import WishlistPage from "./pages/WishlistPage"; +import Magazine from "./pages/Magazine"; +import CalendarPage from "./pages/CalendarPage"; +import LoginPage from "./pages/login"; +import RegisterPage from "./pages/register"; +import Register_success from "./pages/Register_success"; +import SurveyPage from "./pages/SurveyPage"; +import { AuthProvider } from "./functions/AuthContext"; +import AuthRoute from "./pages/AuthRoute"; +import Empty from "./pages/empty"; function App() { - - const [isLoggedIn, setIsLoggedIn] = useState(false); - - // 로그아웃 처리 - const handleLogout = () => { - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - setIsLoggedIn(false); - }; - - return ( -
- - -
-
-
+ +
+ + +
+
} /> } /> - : } /> - : } /> - } /> - : } /> - } /> - } /> - }/> - }/> - + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + } /> + } /> + } + /> + } /> + } /> + +
-
- -
+ +
+ ); } -export default App; \ No newline at end of file +export default App; diff --git a/src/components/Navbar.css b/src/components/Navbar.css index 5fff52d..213d914 100644 --- a/src/components/Navbar.css +++ b/src/components/Navbar.css @@ -1,6 +1,6 @@ /* Navbar의 기본 스타일 */ .navbar { - color:white; + color: white; display: flex; justify-content: space-between; /* 좌, 우, 중앙 정렬 */ align-items: center; /* 세로 방향 중앙 정렬 */ @@ -33,7 +33,6 @@ padding-right: 540px; } - /* 로고 이미지의 스타일 */ .logo { width: 120px; /* 로고 이미지의 너비 설정 */ @@ -105,7 +104,7 @@ a { left: 0; width: 100%; padding-bottom: 30px; - overflow:hidden; + overflow: hidden; } .mobile-menu li { @@ -114,7 +113,7 @@ a { /* 모바일 화면에서 보여질 메뉴 아이콘의 스타일 */ .mobile-menu-icon { - color:#808080; + color: #808080; position: fixed; top: 5%; right: 5%; @@ -150,4 +149,4 @@ a { .mobileMenuIcon { z-index: 10; /* z-index 증가 */ -} \ No newline at end of file +} diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index f91d473..5f5d9ec 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -1,100 +1,182 @@ -import React, { useState } from 'react'; -import { Link,NavLink } from 'react-router-dom'; -import { useMediaQuery } from 'react-responsive'; +import React, { useState, useContext } from "react"; +import { NavLink } from "react-router-dom"; +import { useMediaQuery } from "react-responsive"; import "./Navbar.css"; -import UserMenu from './UserMenu'; +import UserMenu from "./UserMenu"; +import { AuthContext } from "../functions/AuthContext"; +import { Nav } from "react-bootstrap"; -// 웹 뷰에서의 css -function getLinkStyle({isActive}){ +function getLinkStyle({ isActive }) { return { - borderBottom: isActive? '3px solid #7EBFFF' : 'none', - color: isActive? '#7EBFFF' : 'black', - fontWeight: isActive? 700 : 'normal', - fontSize: '30px', - //marginLeft: '25px', - //marginRight: '25px', - } + borderBottom: isActive ? "3px solid #7EBFFF" : "none", + color: isActive ? "#7EBFFF" : "black", + fontWeight: isActive ? 700 : "normal", + fontSize: "30px", + }; } -// 모바일 및 태블릿 뷰에서의 css -function getResponsiveLinkStyle({isActive}) { +function getResponsiveLinkStyle({ isActive }) { return { - color: isActive ? '#7EBFFF' : 'black', - fontWeight: isActive ? 700 : 'normal', - fontSize: '26px', - marginLeft: '50px', - } + color: isActive ? "#7EBFFF" : "black", + fontWeight: isActive ? 700 : "normal", + fontSize: "26px", + marginLeft: "50px", + }; } -const Navbar = ({ isLoggedIn, handleLogout }) => { +const Navbar = () => { const [isMenuOpen, setMenuOpen] = useState(false); + const { isLoggedIn, logout } = useContext(AuthContext); const isDesktopOrLaptop = useMediaQuery({ - query: '(min-device-width: 1424px)', // 기존 1224였던 min-device-width를 1424로 바꿈. + query: "(min-device-width: 1424px)", }); - const isTabletOrMobile = useMediaQuery({ query: '(max-width: 1424px)' }); // 기존 1224였던 max-width를 1424로 바꿈. + const isTabletOrMobile = useMediaQuery({ query: "(max-width: 1424px)" }); const handleMenuToggle = () => { setMenuOpen(!isMenuOpen); - // body에 menu-open 클래스를 추가 또는 제거하여 스타일을 적용 - document.body.classList.toggle('menu-open', !isMenuOpen); + document.body.classList.toggle("menu-open", !isMenuOpen); }; return ( -
); } diff --git a/src/functions/AuthContext.js b/src/functions/AuthContext.js new file mode 100644 index 0000000..cbc6a3d --- /dev/null +++ b/src/functions/AuthContext.js @@ -0,0 +1,26 @@ +import React, { createContext, useState } from "react"; + +export const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + // 세션 스토리지에 accessToken이 있으면 로그인 상태로 시작 + const [isLoggedIn, setIsLoggedIn] = useState(!!sessionStorage.getItem("accessToken")); + + const login = () => { + setIsLoggedIn(true); + console.log("login status is true") + }; + + const logout = () => { + sessionStorage.removeItem("accessToken"); + sessionStorage.removeItem("refreshToken"); + setIsLoggedIn(false); + console.log("login status is false") + }; + + return ( + + {children} + + ); +}; diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/AuthRoute.jsx b/src/pages/AuthRoute.jsx new file mode 100644 index 0000000..e9b4665 --- /dev/null +++ b/src/pages/AuthRoute.jsx @@ -0,0 +1,19 @@ +import React, { useContext } from "react"; +import { Navigate } from "react-router-dom"; +import { AuthContext } from "../functions/AuthContext"; + +const AuthRoute = ({ children }) => { + const { isLoggedIn } = useContext(AuthContext); + + console.log('before'+isLoggedIn) + if (!isLoggedIn) { + // 로그인하지 않은 사용자는 로그인 페이지로 리디렉션 + return ; + } + console.log('After'+isLoggedIn) + + // 로그인한 사용자는 해당 컴포넌트를 렌더링 + return children; +}; + +export default AuthRoute; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 0ddae20..2b5d27f 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,136 +1,180 @@ -import React from 'react'; -import { useMediaQuery } from 'react-responsive'; -import {Button,Container } from '@mui/material' -import styles from './Home.module.css'; -import MainSlider from '../components/MainSlider' -import SubSlider from '../components/SubSlider'; +import React from "react"; +import { useMediaQuery } from "react-responsive"; +import { Button, Container } from "@mui/material"; +import styles from "./Home.module.css"; +import MainSlider from "../components/MainSlider"; +import SubSlider from "../components/SubSlider"; import { AwesomeButton } from "react-awesome-button"; -import 'react-awesome-button/dist/styles.css'; -import { Link} from 'react-router-dom'; -import { Card } from 'react-bootstrap'; -import Col from 'react-bootstrap/Col'; -import Row from 'react-bootstrap/Row'; - +import "react-awesome-button/dist/styles.css"; +import { Link } from "react-router-dom"; +import { Card } from "react-bootstrap"; +import Col from "react-bootstrap/Col"; +import Row from "react-bootstrap/Row"; const Home = () => { const isMobile = useMediaQuery({ maxWidth: 899 }); const isTablet = useMediaQuery({ minWidth: 900, maxWidth: 1423 }); const isDesktop = useMediaQuery({ minWidth: 1424 }); - const images = ['https://i.postimg.cc/KcKgqkbS/1.jpg','https://i.postimg.cc/T3xL3tDf/2.jpg','https://i.postimg.cc/wjktyDXJ/3.jpg','https://i.postimg.cc/VNLJj5gh/4.jpg'] + const images = [ + "https://i.postimg.cc/KcKgqkbS/1.jpg", + "https://i.postimg.cc/T3xL3tDf/2.jpg", + "https://i.postimg.cc/wjktyDXJ/3.jpg", + "https://i.postimg.cc/VNLJj5gh/4.jpg", + ]; return ( - -
- +
{isMobile && ( -
-
-

대한민국 1등 진단 앱 니모내모

-

두피 진단과 건강 솔루션을 찾아보세요

-
- - -
-

- 꾸준한 두피 관리, 섬세한 진단을 원하신다면? -
- 니모내모를 이용해 보세요! -

-

- 니모내모는 AI를 활용한 두피 진단을 제공하고, 사용자에게 맞는 탈모 방지 제품을 추천드립니다. -
- 지금 함께 시작해보실래요? -

-
-
- - -
- 지금 시작하기 -
+
+
+

+ 대한민국 1등 진단 앱 니모내모 +

+

두피 진단과 건강 솔루션을 찾아보세요

+
+ + +
+

+ 꾸준한 두피 관리, 섬세한 진단을 원하신다면? +
+ 니모내모를 이용해 보세요! +

+

+ 니모내모는 AI를 활용한 두피 진단을 제공하고, 사용자에게 맞는 + 탈모 방지 제품을 추천드립니다. +
+ 지금 함께 시작해보실래요? +

+
+
+ +
+ + {" "} + 지금 시작하기{" "} + +
)} {isTablet && (
-
-

대한민국 1등 진단 앱 니모내모

-

두피 진단과 건강 솔루션을 찾아보세요

-
- - -
-

- 꾸준한 두피 관리, 섬세한 진단을 원하신다면? -
- 니모내모를 이용해 보세요! -

-

- 니모내모는 AI를 활용한 두피 진단을 제공하고, 사용자에게 맞는 탈모 방지 제품을 추천드립니다. -
- 지금 함께 시작해보실래요? -

-
-
+
+

+ 대한민국 1등 진단 앱 니모내모 +

+

두피 진단과 건강 솔루션을 찾아보세요

+
+ + +
+

+ 꾸준한 두피 관리, 섬세한 진단을 원하신다면? +
+ 니모내모를 이용해 보세요! +

+

+ 니모내모는 AI를 활용한 두피 진단을 제공하고, 사용자에게 맞는 + 탈모 방지 제품을 추천드립니다. +
+ 지금 함께 시작해보실래요? +

+
+
- -
- 지금 시작하기 -
+ +
+ + {" "} + 지금 시작하기{" "} + +
)} {isDesktop && (
- -
-

대한민국 1등 진단 앱 니모내모

-

두피 진단과 건강 솔루션을 찾아보세요

-

- -
- - -
- -
-

- 꾸준한 두피 관리, 섬세한 진단을 원하신다면? +
+

+ 대한민국 1등 진단 앱 니모내모 +

+

두피 진단과 건강 솔루션을 찾아보세요


- 니모내모를 이용해 보세요! -

-

- 니모내모는 AI를 활용한 두피 진단을 제공하고, 사용자에게 맞는 탈모 방지 제품을 추천드립니다.
- 지금 함께 시작해보실래요? -

- - {images.map((imgSrc, idx) => ( - - - {/* 이 부분을 수정하세요 */} - - Card title - - This is a longer card with supporting text below as a natural - lead-in to additional content. This content is a little bit - longer. - - - - - ))} - -
-
+ {" "} + +
- -
- 지금 시작하기 -
+ +
+ +
+

+ 꾸준한 두피 관리, 섬세한 진단을 원하신다면? +
+ 니모내모를 이용해 보세요! +

+

+ 니모내모는 AI를 활용한 두피 진단을 제공하고, 사용자에게 맞는 + 탈모 방지 제품을 추천드립니다. +
+ 지금 함께 시작해보실래요? +

+ + {images.map((imgSrc, idx) => ( + + + {" "} + {/* 이 부분을 수정하세요 */} + + Card title + + This is a longer card with supporting text below as a + natural lead-in to additional content. This content is + a little bit longer. + + + + + ))} + +
+
+ + +
+ + {" "} + 지금 시작하기{" "} + +
)}
); -} +}; export default Home; diff --git a/src/pages/SurveyPage.jsx b/src/pages/SurveyPage.jsx new file mode 100644 index 0000000..0a76d90 --- /dev/null +++ b/src/pages/SurveyPage.jsx @@ -0,0 +1,201 @@ +import React, {useEffect, useState} from 'react'; +import { useForm, Controller } from 'react-hook-form'; +import * as yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import { Button, Modal } from 'react-bootstrap'; + +const requiredMessage = `필수 응답란입니다.`; +const warningStyle = { + color: 'red' +}; + +const schema = yup.object().shape({ + questGender: yup.string().required(requiredMessage), + questOld: yup.string().required(requiredMessage), + questUsageTerm: yup.string().required(requiredMessage), + questUsageTerm: yup.string().required(requiredMessage), + questRecommend: yup.string().required(requiredMessage), +}); + +const SurveyPage = () => { + const [showModal, setShowModal] = useState(false); + // 모달을 사용하기 위한 state + + const { control, handleSubmit, setValue, formState: {errors}, watch } = useForm({ + resolver: yupResolver(schema), + }); + // 폼을 작성하기 위한 메서드. + + const onSubmit = (data) => { + // 여기서 data에는 모든 컨트롤러의 값이 포함됩니다. + alert(JSON.stringify(data)); + }; // '제출' 버튼을 클릭했을때 발생하는 것들. + + const handleModalClose = () => setShowModal(false); + + useEffect(() => { + if (watch('questRecommend') === 'no') { + setShowModal(true); + } + }, [watch('questRecommend')]); + + const handleYesButtonClick = () => { + setValue('questRecommend', 'no'); + setShowModal(false); + }; + + const handleNoButtonClick = () => { + setValue('questRecommend', 'yes'); + setShowModal(false); + }; + + return ( +
+
+ + + 알림 + + + 정말 헤어케어 제품을 추천받지 않으시겠어요? + + + + + + + {/* 모달 정의 부분 */} + + + +

검사전 설문

+

보다 정확한 진단을 위해 필요하니 응답해주시면 감사하곘습니다. +

별 표시( * )가 있는 항목은 필수응답 항목입니다.

+ +
+

* 성별을 알려주세요.

+ ( +
+ +
+ )} + /> + {errors.questGender &&

{errors.questGender.message}

} +
+ +
+

* 연령대를 알려주세요.

+ ( +
+ +
+ )}/> + {errors.questOld &&

{errors.questOld.message}

} +
+ +
+

* 샴푸 사용빈도를 알려주세요.

+ ( +
+ +
+ )}/> + {errors.questUsageTerm &&

{errors.questUsageTerm.message}

} +
+ +
+

* 염색 주기를 알려주세요.

+ ( +
+ +
+ )}/> + {errors.questUsageTerm &&

{errors.questUsageTerm.message}

} +
+ +
+

헤어케어 제품을 추천받으시겠습니까?

+ ( +
+ + {/* 추가적인 체크 박스 옵션을 필요에 따라 추가할 수 있음 */} +
+ )} + /> + {errors.questRecommend &&

{errors.questRecommend.message}

} +
+

+ + +
+
+ ); +} + +export default SurveyPage; \ No newline at end of file diff --git a/src/pages/empty.jsx b/src/pages/empty.jsx new file mode 100644 index 0000000..0a100ab --- /dev/null +++ b/src/pages/empty.jsx @@ -0,0 +1,16 @@ +import { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import styles from './WishlistPage.module.css'; + +function Empty() { + + return ( +
+

나의 Empty

+
테스트용도
+
+ ); +} + +export default Empty; + diff --git a/src/pages/login.jsx b/src/pages/login.jsx index b5ae168..41a3f99 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.jsx @@ -1,56 +1,60 @@ -import { useState } from "react"; +import { useState, useContext } from "react"; import { useForm } from "react-hook-form"; -import { useNavigate } from 'react-router-dom'; -import styles from './loginStyles.module.css'; +import { useNavigate } from "react-router-dom"; +import styles from "./loginStyles.module.css"; import axios from "axios"; -import useSignupToken from "../functions/useSignupToken"; +import { AuthContext } from "../functions/AuthContext"; // AuthContext 임포트 -const LoginPage = ({ setIsLoggedIn, handleLogout }) => { +const LoginPage = () => { const { register, handleSubmit } = useForm(); - const [loginResult, setLoginResult] = useState(""); // 로그인 결과를 저장할 상태 - const signupToken = useSignupToken(); + const [loginResult, setLoginResult] = useState(""); // 로그인 결과를 저장할 상태 + const { login } = useContext(AuthContext); // AuthContext에서 login 함수 사용 const navigate = useNavigate(); - // axios.post('http://13.113.206.129:8081/user/'+signupToken, { ...formData}); - const onSubmit = async (formData) => { - try { - const response = await axios.post('http://13.113.206.129:8081/user/login', { ...formData }); + const onSubmit = async (formData) => { + try { + const response = await axios.post( + "http://13.113.206.129:8081/user/login", + formData + ); console.log(response.data); // 로그인 응답 데이터 확인 const { accessToken, refreshToken } = response.data; - sessionStorage.setItem('accessToken', accessToken); - sessionStorage.setItem('refreshToken', refreshToken); + sessionStorage.setItem("accessToken", accessToken); + sessionStorage.setItem("refreshToken", refreshToken); - // accessToken 유효시간 후 자동 로그아웃 - setTimeout(() => { - handleLogout(); - }, 3600000); // 토큰 유효시간 1시간 (밀리초 단위) - - setIsLoggedIn(true); // 로그인 성공 상태 업데이트 + login(); // 로그인 상태 업데이트 setLoginResult("로그인 성공!"); // 로그인 성공 메시지 설정 - navigate('/'); - - } catch (error) { - console.error('Error during login:', error); + navigate("/"); // 홈페이지로 이동 + } catch (error) { + console.error("Error during login:", error); setLoginResult("로그인 실패. 다시 시도해주세요."); // 로그인 실패 메시지 설정 - } -}; - + } + }; return (
-

로그인 페이지

+

+ 로그인 페이지 +

- - + +
{loginResult &&

{loginResult}

} {/* 로그인 결과 표시 */} -
); - -} +}; export default LoginPage; diff --git a/src/pages/loginStyles.module.css b/src/pages/loginStyles.module.css index 6307e4c..ef74731 100644 --- a/src/pages/loginStyles.module.css +++ b/src/pages/loginStyles.module.css @@ -1,3 +1,7 @@ + .title{ + text-align: center; + } + .h1 { margin-top: 80px; color: white; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3a..0000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b..0000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom';