김건희

[Merge] 'frontend' into 'master'

Showing 45 changed files with 1712 additions and 18 deletions
This diff is collapsed. Click to expand it.
Stack trace:
Frame Function Args
000005FF340 0018006286E (001802901B2, 0018026DE3E, 0000000005E, 000005FAEA0)
000005FF340 0018004846A (00000000000, 00000000000, 7FFE00000000, 00000001000)
000005FF340 001800484A2 (00000000000, 0000000005A, 0000000005E, 00000000000)
000005FF340 001800D3E58 (00000000000, 00100000000, 0018026D9F2, 000005FBFEC)
000005FF340 0018012C857 (00000000000, 00180223E20, 00180223E10, 000005FDDE0)
000005FF340 001800488B4 (0018030F940, 000005FDDE0, 00000000000, 00000000000)
000005FF340 0018004A01F (0007FFE0384, 00000000000, 00000000000, 00000000000)
000005FF340 001800D4E98 (00000000000, 00000000000, 00000000000, 00000000000)
000005FF5E0 7FFE062E9A1D (00180040000, 00000000001, 00000000000, 000005FF528)
000005FF5E0 7FFE0633C1E7 (7FFE0630DB00, 000007F3901, 7FFE00000001, 00000000001)
000005FF5E0 7FFE0633BF7A (000007F3900, 000005FF5E0, 000007F4270, 00000070000)
000005FF5E0 7FFE0633C000 (00000000010, 00000000000, 7FFE06401A90, 000005FF678)
00000000000 7FFE063A3C2A (00000000000, 00000000000, 00000000001, 00000000000)
00000000000 7FFE06344CDB (7FFE062D0000, 00000000000, 00000347000, 00000000000)
00000000000 7FFE06344B63 (00000000000, 00000000000, 00000000000, 00000000000)
00000000000 7FFE06344B0E (00000000000, 00000000000, 00000000000, 00000000000)
End of stack trace
This diff could not be displayed because it is too large.
......@@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.27.2",
"cors": "^2.8.5",
"express": "^4.18.1",
"http-proxy-middleware": "^2.0.6",
......@@ -13,7 +14,12 @@
"nodemon": "^2.0.16",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-redux": "^8.0.2",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"redux": "^4.1.2",
"redux-promise-middleware": "^6.1.2",
"redux-thunk": "^2.4.1",
"web-vitals": "^2.1.4"
},
"scripts": {
......@@ -39,5 +45,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"redux-devtools-extension": "^2.13.9"
}
}
......
import logo from './logo.svg';
import './App.css';
import RegisterPage from './component/views/RegisterPage/RegisterPage';
import LoginPage from './component/views/LoginPage/LoginPage';
import MainPage from './component/views/MainPage/MainPage';
import RecommandPage from './component/views/RecommandPage/RecommandPage';
import { Route, Routes } from 'react-router-dom';
import WeatherPage from './component/views/WeatherPage/WeatherPage';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div>
<Routes>
<Route exact path = "/login" element = {<LoginPage/>}/>
<Route exact path = "/" element = {<RegisterPage/>}/>
<Route exact path = "/main" element = {<MainPage/>}/>
<Route exact path = "/weather" element = {<WeatherPage/>}/>
<Route exact path = "/recommand" element = {<RecommandPage/>}/>
</Routes>
</div>
);
}
......
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { login } from "../../../modules/user";
import { address } from "../../../modules/weather";
import "../style/LoginPage.scss"
function LoginPage(props) {
const dispatch = useDispatch();
const navigate = useNavigate();
const loginResult = useSelector((state) => state.user.loginData);
const [Id, setId] = useState("");
const [Password, setPassword] = useState("");
const [checkLogin, setCheckLogin] = useState(false);
const [checkIdError, setCheckIdError] = useState(false);
const [checkPasswordError, setCheckPasswordError] = useState(false);
const [checkLoginError, setCheckLoginError] = useState(false);
const idRegex = /^(?=.*[a-zA-Z])(?=.*[0-9]).{6,14}$/;
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-?])(?=.*[0-9]).{8,25}$/;
useEffect(() => {
if (checkLogin === true) {
loginResult.then((result) => {
if (result.loginSuccess === true) {
alert('로그인에 성공하였습니다.');
navigate('/main');
}
else {
alert('로그인에 실패하였습니다.');
}
})
}
}, [loginResult])
const onIdHandler = useCallback((event) => {
setId(event.currentTarget.value);
// 아이디 유효성 검사
if (!idRegex.test(event.currentTarget.value)) {
setCheckIdError(true);
}
else {
setCheckIdError(false);
}
}, [checkIdError]);
const onPasswordHandler = useCallback((event) => {
setPassword(event.currentTarget.value);
// 비밀번호 유효성 검사
if (!passwordRegex.test(event.currentTarget.value)) {
setCheckPasswordError(true);
}
else {
setCheckPasswordError(false);
}
}, [checkPasswordError]);
const onSubmitHandler = useCallback((event) => {
event.preventDefault();
if (checkIdError || Id === "") {
setCheckLoginError(true);
}
else if (checkPasswordError || Password === "") {
setCheckLoginError(true);
}
else {
setCheckLoginError(false);
}
// login
if (!checkLoginError) {
const UserData = {
id: Id,
password: Password
};
dispatch(login(UserData));
dispatch(address());
setCheckLogin(true);
}
}, [checkIdError, checkPasswordError, Password, dispatch, loginResult]);
return (
<div id = "body">
<div className="login-box">
<h2>로그인</h2>
<div className="input-area">
<input
placeholder="아이디"
type="text"
value={Id}
onChange={onIdHandler}
/>
</div>
<div className="check-variable">
{checkIdError && <div style={{color : 'red'}}>아이디는 6자리 이상 14자리 이하 소문자와 숫자로 입력해주세요.</div>}
</div>
<div className="input-area">
<input
placeholder="비밀번호"
type="text"
value={Password}
onChange={onPasswordHandler}
/>
</div>
<div className="check-variable">
{checkPasswordError && <div style={{color : 'red'}}>알파벳과 숫자, 특수문자를 포함하여 8자리 이상 입력해주세요.</div>}
</div>
<div className="btn-area" onClick={onSubmitHandler}>
<button
className="login-btn"
>
로그인
</button>
</div>
<div className="check-variable">
{checkLoginError && <div style={{color : 'red'}}>정보를 제대로 입력해주세요.</div>}
</div>
</div>
</div>
);
}
export default LoginPage;
\ No newline at end of file
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { logout } from "../../../modules/user";
import { todayInformation, tommorrowInformation } from "../../../modules/weather";
import "../style/MainPage.scss"
function MainPage(props) {
const dispatch = useDispatch();
const navigate = useNavigate();
const addressResult = useSelector((state) => state.weather.address);
const user = useSelector((state) => state.user.loginData);
//이름, 성별, 시구동주소
const [Name, setName] = useState("");
const [Sex, setSex] = useState("");
const [CityAdd, setCityAdd] = useState("");
const [GuAdd, setGuAdd] = useState("");
const [DongAdd, setDongAdd] = useState("");
// const [checkNameError, setCheckNameError] = useState(false);
// const [checkSexError, setCheckSexError] = useState(true);
const [checkCityAddError, setCheckCityAddError] = useState(true);
const [checkGuAddError, setCheckGuAddError] = useState(true);
const [checkDongAddError, setCheckDongAddError] = useState(true);
const [checkSubmitError, setCheckSubmitError] = useState(false);
const CityAddSelectList = ["시/도 선택", "강원도", "경기도", "경상북도", "경상남도", "광주광역시", "대구광역시", "대전광역시", "부산광역시", "서울특별시", "울산광역시", "인천광역시", "전라북도", "전라남도", "제주특별자치도", "충청북도", "충청남도"];
const [GuAddSelectList, setGuAddselectList] = useState(["시/군/구 선택"]);
const [DongAddSelectList, setDongAddselectList] = useState(["읍/면/동 선택"]);
const [Time, setTime] = useState("00:00:00");
const currentTime = () => {
const date = new Date();
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
setTime(hours+" : "+minutes+" : "+seconds);
}
const startTimer = () => {
setInterval(currentTime, 1000)
}
startTimer()
useEffect(() => {
user.then((result) => {
setName(result.logData.name);
setSex(result.logData.gender);
})
}, [user])
// 시/군/구 주소
useEffect(() => {
const tempList = [];
addressResult.then((result) => {
for (let i = 0; i < result.length; i++) {
if (result[i].address1 === CityAdd) {
if (tempList[tempList.length - 1] !== result[i].address2){
tempList.push(result[i].address2);
}
}
}
tempList[0] = "시/군/구 선택";
setGuAddselectList(tempList);
setDongAddselectList(["읍/면/동 선택"]);
});
}, [CityAdd]);
// 읍/면/동 주소
useEffect(() => {
const tempList = [];
addressResult.then((result) => {
for (let i = 0; i < result.length; i++) {
if (result[i].address2 === GuAdd) {
if ((tempList[tempList.length - 1] !== result[i].address3) && tempList[0] !== result[i].address3){
tempList.push(result[i].address3);
}
}
}
tempList[0] = "읍/면/동 선택";
setDongAddselectList(tempList);
});
}, [GuAdd]);
const onCityAddhandler = useCallback((event) => {
setCityAdd(event.currentTarget.value);
if (event.currentTarget.value === "시/도 선택") {
setCheckCityAddError(true);
}
else {
setCheckCityAddError(false);
}
}, [checkCityAddError]);
const onGuAddhandler = useCallback((event) => {
setGuAdd(event.currentTarget.value);
if (event.currentTarget.value === "시/군/구 선택") {
setCheckGuAddError(true);
}
else {
setCheckGuAddError(false);
}
}, [checkGuAddError]);
const onDongAddhandler = useCallback((event) => {
setDongAdd(event.currentTarget.value);
if (event.currentTarget.value === "읍/면/동 선택") {
setCheckDongAddError(true);
}
else {
setCheckDongAddError(false);
}
}, [checkDongAddError]);
const onClickTitle = useCallback((event) => {
navigate('/main')
})
const onClickLogout = useCallback((event) => {
dispatch(logout());
navigate('/login');
})
const onClickRegister = useCallback((event) => {
navigate('/');
})
const onSubmitHandler = useCallback((event) => { //제출 전 오류 확인 함수
event.preventDefault(); //체크박스 미리 클릭 방지
if (checkCityAddError || CityAdd === "") {
setCheckSubmitError(true);
}
else if (checkGuAddError || GuAdd === "") {
setCheckSubmitError(true);
}
else if (checkDongAddError || DongAdd === "") {
setCheckSubmitError(true);
}
else {
setCheckSubmitError(false);
}
if (!checkSubmitError) {
addressResult.then((result) => {
for (let i = 0; i<result.length; i++) {
if (result[i].address1 === CityAdd && result[i].address2 === GuAdd && result[i].address3 === DongAdd) {
const dotData = {
address1 : CityAdd,
address2 : GuAdd,
address3 : DongAdd,
dotX : result[i].dotX,
dotY : result[i].dotY,
}
dispatch(todayInformation(dotData));
dispatch(tommorrowInformation(dotData));
navigate('/weather');
break;
}
}
})
}
}, [checkCityAddError, checkDongAddError, checkGuAddError, checkSubmitError, Name, Sex, CityAdd, GuAdd, DongAdd]);
return (
<>
<dir id = "header">
<dir className="header_title" onClick = {onClickTitle}>
<h1>Weather_Briefing</h1>
</dir>
<dir className="header_choice_box">
<button type="button" onClick = {onClickLogout}>Logout</button>
<button type="button" onClick = {onClickRegister}>Register</button>
</dir>
</dir>
<div id = "body">
<div className="info-box">
<p className="info">정보를 입력해주세요.</p>
<div className="main-input-area" readOnly>
<li>이름</li>
<input
placeholder={Name}
type="text"
value={Name}
/>
</div>
<hr/>
<div className="main-input-area" readOnly>
<li>성별</li>
<p>남자</p>
<input
type="radio" //라디오 버튼 타입
value = "0"
checked = {Sex === "0"}
/>
<p>여자</p>
<input
type="radio"
value = "1"
checked = {Sex === "1"}
/>
</div>
<hr/>
<div className="main-input-area">
<li>지역</li>
<div className="CityAddSelect">
<select onChange={onCityAddhandler} value={CityAdd}>
{CityAddSelectList.map((item) => (
<option value={item} key={item}>
{item}
</option>
))}
</select>
</div>
<div className="GuAddSelect">
<select onChange={onGuAddhandler} value={GuAdd}>
{GuAddSelectList.map((item) => (
<option value={item} key={item}>
{item}
</option>
))}
</select>
</div>
<div className="DongAddSelect">
<select onChange={onDongAddhandler} value={DongAdd}>
{DongAddSelectList.map((item) => (
<option value={item} key={CityAdd+GuAdd+item}>
{item}
</option>
))}
</select>
</div>
</div>
<hr/>
<div className="main-btn-area" onClick={onSubmitHandler}>
<button className="submit-btn">
날씨 정보
</button>
</div>
<div className="main-check-variable">
{checkSubmitError && <div style={{color : 'red'}}>정보를 제대로 입력해주세요.</div>}
</div>
</div>
</div>
</>
);
}
export default MainPage;
\ No newline at end of file
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { logout } from "../../../modules/user";
import "../style/RecommandPage.scss"
function RecommandPage(props) {
const clothesResult = useSelector((state) => state.clothes.clothesRecommend);
const [IsRain, setIsRain] = useState("");
const [TopPath, setTopPath] = useState('');
const [BottomPath, setBottomPath] = useState('');
const dispatch = useDispatch();
useEffect(() => {
clothesResult.then((result) => {
if (result.umbrella == 1) {
setIsRain("비 예보가 있습니다. 우산을 꼭 챙겨주세요!");
}
else {
setIsRain("비 예보가 없습니다!");
}
setTopPath(result.top);
setBottomPath(result.bottom);
})
}, [clothesResult])
const navigate = useNavigate();
const onClickLogout = useCallback((event) => {
dispatch(logout());
navigate('/login');
})
const onClickRegister = useCallback((event) => {
navigate('/');
})
const onClickTitle = useCallback((event) => {
navigate('/main')
})
return (
<>
<dir id = "header">
<dir className="header_title" onClick = {onClickTitle}>
<h1>Weather_Briefing</h1>
</dir>
<dir className="header_choice_box">
<button type="button" onClick={onClickLogout}>Logout</button>
<button type="button" onClick={onClickRegister}>Register</button>
</dir>
</dir>
<div id = "recommand_body">
<dir className="fashion_recommand">
<dir className="rainOrnot">{IsRain}</dir>
<dir className="clothes">
<dir className="Top">
<h1>TOP</h1>
<img src={TopPath} className='Top_Image' />
</dir>
<dir className="Bottom">
<h1>BOTTOM</h1>
<img src={BottomPath} className='Bottom_Image' />
</dir>
</dir>
</dir>
</div>
</>
);
};
export default RecommandPage;
import React, { useCallback, useEffect, useState } from "react";
import { register } from "../../../modules/user.js";
import "../style/RegisterPage.scss";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
function RegisterPage(props) {
const dispatch = useDispatch();
const navigate = useNavigate();
const registerResult = useSelector((state) => state.user.registerSuccess);
const [Name, setName] = useState("");
const [Sex, setSex] = useState("");
const [Id, setId] = useState("");
const [Password, setPassword] = useState("");
const [PasswordCheck, setPasswordCheck] = useState("");
const [PasswordError, setPasswordError] = useState(false);
const [checkRegister, setCheckRegister] = useState(false);
const [checkNameError, setCheckNameError] = useState(false);
const [checkSexError, setCheckSexError] = useState(true);
const [checkIdError, setCheckIdError] = useState(false);
const [checkPasswordError, setCheckPasswordError] = useState(false);
const [checkRegisterError, setCheckRegisterError] = useState(false);
const idRegex = /^(?=.*[a-zA-Z])(?=.*[0-9]).{6,14}$/;
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-?])(?=.*[0-9]).{8,25}$/;
useEffect(() => {
if (checkRegister === true) {
registerResult.then((result) => {
if (result.registerSuccess === '1') {
alert('회원 가입에 성공하였습니다.');
navigate('/login');
}
else if (result.registerSuccess === '0') {
alert('중복된 아이디가 존재합니다.');
}
else {
alert('회원 가입에 실패하였습니다.');
}
})
}
}, [registerResult])
const onIdHandler = useCallback((event) => {
setId(event.currentTarget.value);
// 아이디 유효성 검사
if (!idRegex.test(event.currentTarget.value)) {
setCheckIdError(true);
}
else {
setCheckIdError(false);
}
}, [checkIdError]);
const onNameHandler = useCallback((event) => {
setName(event.currentTarget.value);
// 이름 유효성 검사
if (event.currentTarget.value.length < 2) {
setCheckNameError(true);
}
else {
setCheckNameError(false);
}
}, [checkNameError]);
const onSexHandler = useCallback((event) => {
setSex(event.currentTarget.value);
setCheckSexError(false);
}, [checkSexError]);
const onPasswordHandler = useCallback((event) => {
setPassword(event.currentTarget.value);
// 비밀번호 유효성 검사
if (!passwordRegex.test(event.currentTarget.value)) {
setCheckPasswordError(true);
}
else {
setCheckPasswordError(false);
}
}, [checkPasswordError]);
const onClickLogIn = useCallback((event) => {
navigate('../login');
})
const onPasswordCheckHandler = useCallback((event) => {
//비밀번호를 입력할때마다 password 를 검증하는 함수
setPasswordError(event.currentTarget.value !== Password);
setPasswordCheck(event.currentTarget.value);
}, [PasswordError]);
const onSubmitHandler = useCallback((event) => {
event.preventDefault();
if (checkIdError || Id === "") {
setCheckRegisterError(true);
}
else if (checkNameError || Name === "") {
setCheckRegisterError(true);
}
else if (checkSexError || Sex === "") {
setCheckRegisterError(true);
}
else if (checkPasswordError || Password === "") {
setCheckRegisterError(true);
}
else if (Password !== PasswordCheck) {
setCheckRegisterError(true);
}
else {
setCheckRegisterError(false);
}
if (!checkRegisterError) {
const UserData = {
name: Name,
id: Id,
password: Password,
gender: Sex,
};
// 액션생성함수
dispatch(register(UserData));
setCheckRegister(true);
};
}, [checkIdError, checkNameError, checkPasswordError, checkRegisterError, checkSexError, Password, PasswordCheck, Sex, dispatch, registerResult, checkRegister]);
return (
<>
<div id="body">
<div className="register-box">
<h2>회원가입</h2>
<div className="input-area">
<input
placeholder="이름"
type="text"
value={Name}
onChange={onNameHandler}
/>
</div>
<div className="check-variable">
{checkNameError && <div style={{ color: 'red' }}>이름을 두글자 이상 입력해 주세요.</div>}
</div>
<div className="input-area">
<input
placeholder="아이디"
type="text"
value={Id}
onChange={onIdHandler}
/>
</div>
<div className="check-variable">
{checkIdError && <div style={{ color: 'red' }}>아이디는 6자리 이상 14자리 이하 소문자와 숫자로 입력해주세요.</div>}
</div>
<div className="input-area">
<input
placeholder="비밀번호"
type="text"
value={Password}
onChange={onPasswordHandler}
/>
</div>
<div className="check-variable">
{checkPasswordError && <div style={{ color: 'red' }}>알파벳과 숫자, 특수문자를 포함하여 8자리 이상 입력해주세요.</div>}
</div>
<div className="input-area">
<input
placeholder="비밀번호 재입력"
type="text"
value={PasswordCheck}
onChange={onPasswordCheckHandler}
/>
</div>
<div className="check-variable">
{PasswordError && <div style={{ color: 'red' }}>비밀번호가 일치하지 않습니다.</div>}
</div>
<div className="input-area">
<input
type="radio"
value="0"
checked={Sex === "0"}
onChange={onSexHandler}
/>
<input
type="radio"
value="1"
checked={Sex === "1"}
onChange={onSexHandler}
/>
</div>
<div className="btn-area" onClick={onSubmitHandler}>
<button
className="register-btn"
>
가입하기
</button>
</div>
</div>
{checkRegisterError && <div style={{ color: 'red' }}>정보를 제대로 입력해주세요.</div>}
</div>
</>
);
}
export default RegisterPage;
\ No newline at end of file
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { recommend } from "../../../modules/clothes";
import { logout } from "../../../modules/user";
import "../style/WeatherPage.scss"
function WeatherPage(props) {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector((state) => state.user.loginData);
const todayWeatherResult = useSelector((state) => state.weather.todayInformation);
const tommorrowWeatherResult = useSelector((state) => state.weather.tommorrowInformation);
const today = new Date();
const detailWeather = [];
let currentHour;
const today_year = today.getFullYear();
const today_month = today.getMonth();
const today_date = today.getDate();
const [todayHighTemperature, setTodayHighTemperature] = useState(-100);
const [todayLowTemperature, setTodayLowTemperature] = useState(100);
const [todayWeatherSymbol, setTodayWeatherSymbol] = useState('☀️');
const [nowWeatherSymbol, setNowWeatherSymbol] = useState('');
const [nowTemperature, setNowTemperature] = useState("");
const [tommorrowHighTemperature, setTommorrowHighTemperature] = useState(-100);
const [tommorrowLowTemperature, setTommorrowLowTemperature] = useState(100);
const [tommorrowWeatherSymbol, setTommorrowWeatherSymbol] = useState('');
const [cityAdd, setCityAdd] = useState('');
const [guAdd, setGuAdd] = useState('');
const [dongAdd, setDongAdd] = useState('');
const [Time, setTime] = useState("00:00:00");
let todayWeatherLevel = 0;
let userGender;
const currentTime = () => {
const date = new Date();
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
setTime(hours+" : "+minutes+" : "+seconds);
}
const startTimer = () => {
setInterval(currentTime, 1000)
}
startTimer()
useEffect(() => {
todayWeatherResult.then((result) => {
let highTemperature = -100;
let lowTemperature = 100;
let symbol = '';
currentHour = Time[0] + Time[1];
// 주소 설정
setCityAdd(result[24].address1);
setGuAdd(result[24].address2);
setDongAdd(result[24].address3);
for (let i = 0; i<24; i++) {
if (i === 13) {
if (result[i].weather === 0) {
todayWeatherLevel = 0;
}
else if (result[i].weather === 1) {
todayWeatherLevel = 1;
}
else if (result[i].weather === 2) {
todayWeatherLevel = 2;
}
else if (result[i].weather === 3) {
todayWeatherLevel = 3;
}
else if (result[i].weather === 4) {
todayWeatherLevel = 4;
}
}
// 세부 시간 정보
if (i > Number(currentHour)) {
if (result[i].rainPer >= 50) {
symbol = '🌧️';
setTodayWeatherSymbol('🌧️');
}
else if (i > 18 || i < 6) {
symbol = '🌙';
}
else {
symbol = '☀️';
}
const tempData = {
time : result[i].time,
temperature : result[i].temperature,
symbol : symbol,
}
detailWeather.push(tempData);
}
// 현재 시간 정보 다루는 부분
if (i === Number(currentHour)) {
if (result[i].rainPer >= 50) {
setNowWeatherSymbol('🌧️');
}
else if (i >= 18 || i < 6) {
setNowWeatherSymbol('🌙');
}
else {
setNowWeatherSymbol('☀️');
}
setNowTemperature(result[i].temperature);
}
// 하루 온도 정보 다루는 부분
if (result[i].temperature < lowTemperature) {
lowTemperature = result[i].temperature;
}
if (result[i].temperature > highTemperature) {
highTemperature = result[i].temperature;
}
}
setTodayHighTemperature(highTemperature);
setTodayLowTemperature(lowTemperature);
})
}, [todayWeatherResult, Time])
// 내일의 날씨
useEffect(() => {
tommorrowWeatherResult.then((result) => {
let highTemperature = -100;
let lowTemperature = 100;
let symbol = '☀️';
for (let i = 0; i < 24; i++) {
// symbol 설정
if (result[i].rainPer >= 50) {
symbol = '🌧️';
}
// 내일 온도 정보 다루는 부분
if (result[i].temperature < lowTemperature) {
lowTemperature = result[i].temperature;
}
if (result[i].temperature > highTemperature) {
highTemperature = result[i].temperature;
}
setTommorrowHighTemperature(highTemperature);
setTommorrowLowTemperature(lowTemperature);
setTommorrowWeatherSymbol(symbol);
}
})
}, [tommorrowWeatherResult])
// 유저 성별 정보
useEffect(() => {
user.then((result) => {
if (result.logData.gender === '0') {
userGender = 0;
}
else {
userGender = 1;
}
});
}, [user])
const onClickLogout = useCallback((event) => {
dispatch(logout());
navigate('/login');
})
const onClickRegister = useCallback((event) => {
navigate('/');
})
const onClickTitle = useCallback((event) => {
navigate('/main')
})
const onSubmitHandler = useCallback((event) => {
event.preventDefault(); //체크박스 미리 클릭 방지
let isRain = 0;
if (todayWeatherSymbol === '🌧️') {
isRain = 1;
}
const sendData = {
gender : userGender,
weather : todayWeatherLevel,
rain : isRain
}
dispatch(recommend(sendData));
navigate('/recommand');
}, [todayWeatherResult, user]);
return (
<>
<dir id = "header">
<dir className="header_title" onClick = {onClickTitle}>
<h1>Weather_Briefing</h1>
</dir>
<dir className="header_choice_box">
<button type="button" onClick = {onClickLogout}>Logout</button>
<button type="button" onClick = {onClickRegister}>Register</button>
</dir>
</dir>
<div id = "body">
<div className="address">
<p>{cityAdd} {guAdd} {dongAdd}</p>
</div>
<div className="today_weather">
<div className="days">
<h1>오늘의 날씨</h1>
<h2 id="day">{today_year} {today_month + 1} {today_date}</h2>
</div>
<div className="today_now_weather_info">
<h2>현재 온도</h2>
<h1 id="present_do">{nowWeatherSymbol} {nowTemperature}</h1>
</div>
<div className="today_weather_info">
<h2>전체 날씨</h2>
<div className="today_whole_weather">
<h1>{todayWeatherSymbol}</h1>
<div className="today_whole_weather_temperature">
<p>최고: {todayHighTemperature}</p>
<p>최저: {todayLowTemperature}</p>
</div>
</div>
</div>
</div>
<div className="tommorrow_weather">
<div className="days">
<h1>내일의 날씨</h1>
<h2 id="day">{today_year} {today_month + 1} {today_date+1}</h2>
</div>
<div className="tommorrow_weather_info">
<h2>날씨 정보</h2>
<div className="tommorrow_whole_weather">
<h1>{tommorrowWeatherSymbol}</h1>
<div className="tommorrow_temperator">
<p>최고: {tommorrowHighTemperature}</p>
<p>최저: {tommorrowLowTemperature}</p>
</div>
</div>
</div>
</div>
<div className="weather-btn-area" onClick={onSubmitHandler}>
<button className="submit-btn">
추천
</button>
</div>
</div>
</>
);
}
export default WeatherPage;
\ No newline at end of file
* {
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: rgb(245, 235, 223);
}
#body {
display: flex;
justify-content: center;
align-items: center;
border-top: 2px solid;
border-bottom: 2px solid;
.login-box {
border: 2px solid;
width: 35%;
height: 60%;
margin-top: 30px;
margin-bottom: 30px;
h2 {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
}
}
.input-area {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 5px;
input {
padding: 10px 0.1rem;
background-color: rgb(255, 255, 255);
}
}
.check-variable {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 5px;
}
.btn-area {
display: flex;
justify-content: center;
margin-top: 30px;
margin-bottom: 35px;
button {
width: 120px;
height: 40px;
font-size: large;
font-weight: bold;
background-color:rgb(255, 253, 238);
cursor: pointer;
}
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: rgb(245, 235, 223);
}
#header {
display: flex;
position: fixed;
justify-content: center;
align-items: center;
height: 15%;
width: 100%;
border-top: 2px solid;
border-bottom: 2px solid;
.header_clock {
justify-content: left;
align-items: left;
width: 10%;
height: 10%;
h1{
color:rgb(0, 0, 0);
font-size: 15px;
}
}
.header_title {
display: flex;
justify-content: center;
align-items: center;
margin-left: 300px;
margin-right: 300px;
h1 {
font-size: 50px;
font-family: 'Times New Roman', Times, serif;
color: rgb(0, 0, 0);
}
}
.header_choice_box {
display: flex;
justify-content: right;
align-items: right;
margin-right: 10x;
}
button {
width: 70px;
height: 25px;
font-size: 15px;
font-weight: bold;
cursor: pointer;
}
}
#body {
display: flex;
justify-content: center;
align-items: center;
border-top: 2px solid;
border-bottom: 2px solid;
.info-box {
border: 2px solid;
width: 55%;
height: 100%;
margin-top: 150px;
margin-bottom: 30px;
}
hr {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
margin-left: 100px;
margin-right: 100px;
}
.info{
display:flex;
font-size: 30px;
border: 2px solid rgb(225, 208, 134);
background-color: rgb(242, 235, 130);
color:rgb(255, 255, 255);
justify-content: center;
align-items: center;
margin-top: 50px;
margin-bottom: 50px;
margin-left: 30%;
margin-right: 30%;
}
.main-input-area {
display: flex;
justify-content: left;
align-items: left;
margin-top: 20px;
margin-bottom: 15px;
li{
display:flex;
font-size: 15px;
color: gray;
margin-left: 15%;
margin-right: 10%;
}
p{
display:flex;
font-size: 15px;
border: 2px dotted gray;
margin-bottom: 5px;
margin-left: 5%;
}
input {
padding: 10px 2rem;
background-color: rgb(255, 255, 255);
margin-left: 50px;
}
select {
padding: 10px 1.5rem;
margin-right: 5px;
background-color: rgb(255, 255, 255);
}
}
.main-check-variable {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 5px;
}
.main-btn-area {
display: flex;
justify-content: center;
margin-top: 60px;
margin-bottom: 10px;
button {
width: 150px;
height: 60px;
font-size: 15px;
font-weight: bold;
background-color:rgb(255, 253, 238);
cursor: pointer;
}
}
}
\ No newline at end of file
* {
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: rgb(245, 235, 223);
}
img {
width: 300px;
height: 300px;
object-fit: cover;
}
#header {
display: flex;
position: fixed;
justify-content: center;
align-items: center;
height: 15%;
width: 100%;
border-top: 2px solid;
border-bottom: 2px solid;
.header_title {
display: flex;
justify-content: center;
align-items: center;
margin-left: 300px;
margin-right: 300px;
h1 {
font-size: 50px;
font-family: 'Times New Roman', Times, serif;
color: rgb(0, 0, 0);
}
}
.header_choice_box {
display: flex;
justify-content: right;
align-items: right;
margin-right: 10x;
}
button {
width: 70px;
height: 25px;
font-size: 15px;
font-weight: bold;
cursor: pointer;
}
}
#recommand_body {
display: flex;
justify-content: center;
align-items: center;
border-top: 2px solid;
border-bottom: 2px solid;
.fashion_recommand {
border: 2px solid;
width: 50%;
height: 200%;
margin-top: 150px;
margin-bottom: 30px;
.rainOrnot {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
margin-left: 100px;
margin-right: 100px;
font-size :30px;
font-weight: bold;
}
.clothes{
display:flex;
justify-content: space-between;
align-items: space-between;
.Top{
display:flex;
flex-wrap: wrap;
flex-direction: column;
font-size: 30px;
border: 2px solid;
color:rgb(0, 0, 0);
justify-content: center;
align-items: center;
margin-top: 50px;
margin-bottom: 50px;
margin-left: 5%;
margin-right: 1%;
}
.Bottom{
display:flex;
flex-wrap: wrap;
flex-direction: column;
font-size: 30px;
border: 2px solid;
color:rgb(0, 0, 0);
justify-content: center;
align-items: center;
margin-top: 50px;
margin-bottom: 50px;
margin-left: 1%;
margin-right: 5%;
}
.Head{
display:flex;
flex-wrap: wrap;
flex-direction: column;
font-size: 30px;
border: 2px solid;
color:rgb(0, 0, 0);
justify-content: center;
align-items: center;
margin-top: 50px;
margin-bottom: 50px;
margin-left: 1%;
margin-right: 1%;
}
.Shoes{
display:flex;
flex-wrap: wrap;
flex-direction: column;
font-size: 30px;
border: 2px solid;
color:rgb(0, 0, 0);
justify-content: center;
align-items: center;
margin-top: 50px;
margin-bottom: 50px;
margin-left: 1%;
margin-right: 1%;
}
}
}
}
\ No newline at end of file
* {
margin: 0;
padding: 0;
box-sizing: border-box;
background-color: rgb(245, 235, 223);
}
#body {
display: flex;
justify-content: center;
align-items: center;
border-top: 2px solid;
border-bottom: 2px solid;
.register-box {
border: 2px solid;
width: 35%;
height: 60%;
margin-top: 30px;
margin-bottom: 30px;
h2 {
text-align: center;
margin-top: 20px;
margin-bottom: 20px;
}
}
.input-area {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 5px;
input {
padding: 10px 0.1rem;
background-color: rgb(255, 255, 255);
}
}
.check-variable {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 5px;
}
.btn-area {
display: flex;
justify-content: center;
margin-top: 30px;
margin-bottom: 35px;
button {
width: 120px;
height: 40px;
font-size: large;
font-weight: bold;
background-color:rgb(255, 253, 238);
cursor: pointer;
}
}
}
#header {
display: flex;
position: fixed;
justify-content: center;
align-items: center;
height: 15%;
width: 100%;
border-top: 2px solid;
border-bottom: 2px solid;
.header_title {
display: flex;
justify-content: center;
align-items: center;
margin-left: 300px;
margin-right: 300px;
cursor:pointer;
h1 {
font-size: 50px;
font-family: 'Times New Roman', Times, serif;
color: rgb(0, 0, 0);
}
}
.header_choice_box {
display: flex;
justify-content: right;
align-items: right;
margin-right: 10x;
}
button {
width: 70px;
height: 25px;
font-size: 15px;
font-weight: bold;
cursor: pointer;
}
}
#body {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
flex-direction: column;
border-top: 2px solid;
border-bottom: 2px solid;
.address {
display: flex;
justify-content: center;
align-items: center;
margin-top: 150px;
font-size: 30px;
}
.today_weather {
display: flex;
justify-content: center;
align-items: center;
border: 2px solid;
width: 50%;
margin-top: 30px;
margin-bottom: 10px;
.today_now_weather_info {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-right: 30px;
}
.days {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
flex-direction: column;
margin-top: 25px;
margin-bottom: 25px;
margin-left: 15px;
margin-right: 200px;
}
.today_whole_weather{
display: flex;
flex-direction: row;
}
}
.tommorrow_weather {
display: flex;
justify-content: center;
align-items: center;
border: 2px solid;
width: 50%;
margin-top: 20px;
margin-bottom: 20px;
.days {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
flex-direction: column;
margin-top: 25px;
margin-bottom: 25px;
margin-left: 15px;
margin-right: 200px;
}
.tommorrow_weather_info {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-right: 30px;
.tommorrow_whole_weather{
display: flex;
flex-direction: row;
}
}
}
.weather-btn-area {
display: flex;
justify-content: center;
margin-top: 20px;
margin-bottom: 10px;
button {
width: 150px;
height: 60px;
font-size: 15px;
font-weight: bold;
background-color:rgb(255, 253, 238);
cursor: pointer;
}
}
}
\ No newline at end of file
......@@ -2,13 +2,23 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';
import rootReducer from './modules/Index';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReduxThunk from 'redux-thunk';
const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(ReduxThunk)));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</React.StrictMode>
</BrowserRouter>
</Provider>,
);
// If you want to start measuring performance in your app, pass a function
......
import { combineReducers } from "redux";
import user from "./user.js";
import weather from "./weather";
import clothes from "./clothes";
const rootReducer = combineReducers({
user,
weather,
clothes,
})
export default rootReducer;
\ No newline at end of file
import axios from "axios";
const CLOTHES_RECOMMEND = 'weather/RECOMMEND';
export function recommend(dataToSubmit) {
const req = axios.post('http://localhost:4000/api/clothes', dataToSubmit)
.then(res => res.data);
return {
type: CLOTHES_RECOMMEND,
payload: req,
}
}
export default function (state = {}, action) {
switch (action.type) {
case CLOTHES_RECOMMEND:
return { ...state, clothesRecommend: action.payload };
break;
default:
return state;
}
}
\ No newline at end of file
import axios from 'axios';
const USER_REGISTER = 'user/REGISTER';
const USER_LOGIN = 'user/LOGIN';
const USER_LOGOUT = 'user/LOGOUT';
export function register (dataToSubmit) {
const req = axios.post('http://localhost:4000/api/register', dataToSubmit)
.then(res => res.data);
return {
type: USER_REGISTER,
payload: req,
}
};
export function login(dataToSubmit) {
const req = axios.post('http://localhost:4000/api/login', dataToSubmit)
.then(res => res.data);
return {
type: USER_LOGIN,
payload: req,
}
};
export function logout(dataToSubmit) {
const req = axios.post('http://localhost:4000/api/logout', dataToSubmit)
.then(res => res.data);
return {
type: USER_LOGOUT,
}
}
export default function (state = {}, action) {
switch (action.type) {
case USER_REGISTER:
return { ...state, registerSuccess: action.payload };
break;
case USER_LOGIN:
return { ...state, loginData: action.payload };
break;
case USER_LOGOUT:
return { ...state, loginData: action.payload };
break;
default:
return state;
}
}
\ No newline at end of file
import axios from "axios";
const WEATHER_ADDRESS = 'weather/ADDRESS';
const WEATHER_COORDINATE = 'weather/COORDINATE';
const WEATHER_TODAY_INFORMATION = 'weather/TODAY_INFORMATION';
const WEATHER_TOMMORROW_INFORMATION = 'weather/TOMMORROW_INFORMATION';
export function address() {
const req = axios.post('http://localhost:4000/api/address')
.then(res => res.data);
return {
type: WEATHER_ADDRESS,
payload: req,
}
}
export function coordinate(dataToSubmit) {
const req = axios.post('http://localhost:4000/api/cordinate', dataToSubmit)
.then(res => res.data);
return {
type: WEATHER_COORDINATE,
payload: req,
}
};
export function todayInformation(dataToSubmit) {
const req = axios.post('http://localhost:4000/api/weather', dataToSubmit)
.then(res => res.data);
return {
type: WEATHER_TODAY_INFORMATION,
payload: req,
}
};
export function tommorrowInformation(dataToSubmit) {
const req = axios.post('http://localhost:4000/api/tommorrow', dataToSubmit)
.then(res => res.data);
return {
type: WEATHER_TOMMORROW_INFORMATION,
payload: req,
}
};
export default function (state = {}, action) {
switch (action.type) {
case WEATHER_ADDRESS:
return { ...state, address: action.payload };
break;
case WEATHER_COORDINATE:
return { ...state, dot: action.payload };
break;
case WEATHER_TODAY_INFORMATION:
return { ...state, todayInformation: action.payload };
break;
case WEATHER_TOMMORROW_INFORMATION:
return { ...state, tommorrowInformation: action.payload };
break;
default:
return state;
}
}
\ No newline at end of file
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
createProxyMiddleware('/api', {
target: 'http://localhost:4000', //접속하려는 서버의 루트 URL
changeOrigin: true
})
);
};
\ No newline at end of file