diff --git a/1-team-front/src/App.jsx b/1-team-front/src/App.jsx index 17cc7f6..73ef9de 100644 --- a/1-team-front/src/App.jsx +++ b/1-team-front/src/App.jsx @@ -1,9 +1,34 @@ -import { useState } from 'react' +/* eslint-disable */ -import './App.css' +import { + createBrowserRouter, + createRoutesFromElements, + Route, + RouterProvider, +} from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from 'react-query'; + +import Auth from './pages/Auth'; +import LogIn from './components/Auth/LogIn/LogIn'; +import FindPassword from './components/Auth/FindPassword/FindPassword'; + +const queryClient = new QueryClient(); + +const routesDifinition = createRoutesFromElements( + }> + }> + }> + , +); + +const router = createBrowserRouter(routesDifinition); function App() { - return

hello

+ return ( + + + + ); } -export default App +export default App; diff --git a/1-team-front/src/components/Auth/AuthStyles.jsx b/1-team-front/src/components/Auth/AuthStyles.jsx new file mode 100644 index 0000000..9d22394 --- /dev/null +++ b/1-team-front/src/components/Auth/AuthStyles.jsx @@ -0,0 +1,86 @@ +import styled, { css } from 'styled-components'; + +const buttonType = { + cancel: css` + background-color: #2f3136; + &:hover { + background-color: #40444b; + } + `, + confirm: css` + background-color: #5865f2; + &:hover { + background-color: #4752c4; + } + `, +}; + +const CardContainer = styled.div` + padding: 40px 30px; + background-color: #36393f; + border-radius: 4px; + display: flex; + flex-direction: column; + align-items: center; + gap: 18px; + & h2 { + color: #fff; + } + & p { + font-size: 0.9rem; + } +`; + +const FormWrapper = styled.form` + display: flex; + flex-direction: column; + align-items: center; + gap: 13px; +`; + +const InputBox = styled.div` + width: 360px; + & label { + font-size: 0.8rem; + font-weight: 800; + & span { + color: #ed4245; + } + } + & input { + width: 95%; + height: 30px; + background-color: #18191c; + border-radius: 2px; + margin: 5px 0; + color: #fff; + padding: 5px 10px; + font-size: 0.9rem; + } + & p { + font-size: 0.8rem; + color: ${(props) => (props.isBlue ? '#00aff4' : '#ed4245')}; + } +`; + +const Button = styled.button` + width: 100%; + height: 40px; + border-radius: 2px; + color: #fff; + font-weight: 800; + transition: all 0.3s linear; + border: 0; + ${(props) => buttonType[props.buttonType]} +`; + +const SpanButton = styled.span` + font-size: 0.8rem; + color: #00aff4; + cursor: pointer; + &:hover { + text-decoration-line: underline; + } +`; + +export { CardContainer, FormWrapper, InputBox, Button, SpanButton }; diff --git a/1-team-front/src/components/Auth/FindPassword/FindPassword.jsx b/1-team-front/src/components/Auth/FindPassword/FindPassword.jsx new file mode 100644 index 0000000..00a0f28 --- /dev/null +++ b/1-team-front/src/components/Auth/FindPassword/FindPassword.jsx @@ -0,0 +1,52 @@ +/*eslint-disable*/ +import { useState } from 'react'; + +import { CardContainer, FormWrapper, InputBox, Button } from '../AuthStyles'; +import useAuthMutation from '../../../hooks/Auth/useAuthMutation'; +import useBlurHandler from '../../../hooks/Auth/useBlurHandler'; + +const FindPassword = () => { + const [email, setEmail] = useBlurHandler(''); + const [isError, setIsError] = useState(false); + const [isSubmit, setIsSubmit] = useState(false); + + const fetchFindPassword = useAuthMutation( + 'http://localhost:8080/member/password-reissue', + ); + + const findPasswordSubmitHandler = (event) => { + event.preventDefault(); + fetchFindPassword.mutate( + { email: email }, + { + onSuccess: () => { + setIsError(false); + setIsSubmit(true); + }, + onError: () => { + setIsSubmit(false); + setIsError(true); + }, + }, + ); + }; + return ( + +

비밀번호를 잊으셨나요 ?

+

입력하신 이메일로 비밀번호 재설정 링크를 보내드립니다.

+ + + + setEmail(event)}> + {isError &&

이메일이 유효하지 않습니다.

} + {isSubmit && ( +

입력하신 이메일로 비밀번호 재설정 링크를 보냈습니다.

+ )} +
+ +
+
+ ); +}; + +export default FindPassword; diff --git a/1-team-front/src/components/Auth/LogIn/LogIn.jsx b/1-team-front/src/components/Auth/LogIn/LogIn.jsx new file mode 100644 index 0000000..6a521a9 --- /dev/null +++ b/1-team-front/src/components/Auth/LogIn/LogIn.jsx @@ -0,0 +1,92 @@ +/* eslint-disable */ +import { useState } from 'react'; +import styled from 'styled-components'; +import { useNavigate } from 'react-router-dom'; + +import { + CardContainer, + FormWrapper, + InputBox, + Button, + SpanButton, +} from '../AuthStyles'; +import useAuthMutation from '../../../hooks/Auth/useAuthMutation'; +import useBlurHandler from '../../../hooks/Auth/useBlurHandler'; + +const FooterBox = styled.div` + display: flex; + gap: 8px; +`; + +const LogIn = () => { + const navigate = useNavigate(); + const [email, setEmail] = useBlurHandler(''); + const [password, setPassword] = useBlurHandler(''); + const [isError, setIsError] = useState(false); + + const fetchLogin = useAuthMutation('http://localhost:8080/login'); + + const loginSubmitHander = (event) => { + event.preventDefault(); + fetchLogin.mutate( + { email: email, password: password }, + { + onSuccess: (res) => { + setIsError(false); + localStorage.clear(); + localStorage.setItem('email', res.data.email); + localStorage.setItem('token', res.data.token); + navigate('/'); + }, + onError: () => { + setIsError(true); + }, + }, + ); + }; + + const findPasswordClickHandler = () => { + navigate('/auth/find-password'); + }; + + return ( + +

환영합니다 !

+ + + + setEmail(event)} + > + + + + setPassword(event)} + > + {isError &&

이메일 또는 비밀번호가 일치하지 않습니다.

} + + 비밀번호를 잊으셨나요 ? + +
+ +
+ +

계정이 필요한가요 ?

+ 가입하기 +
+
+ ); +}; + +export default LogIn; diff --git a/1-team-front/src/components/Auth/SignUp/SignUp.jsx b/1-team-front/src/components/Auth/SignUp/SignUp.jsx new file mode 100644 index 0000000..6520e2e --- /dev/null +++ b/1-team-front/src/components/Auth/SignUp/SignUp.jsx @@ -0,0 +1,157 @@ +/* eslint-disable */ + +import { useState } from 'react'; +import styled from 'styled-components'; + +const SignUpForm = styled.form` + padding: 40px 30px; + background-color: #36393f; + border-radius: 4px; + display: flex; + flex-direction: column; + align-items: center; + gap: 18px; + & h2 { + color: #fff; + } +`; + +const SignUpButton = styled.button` + width: 100%; + height: 40px; + border-radius: 2px; + color: #fff; + background-color: #5865f2; + font-weight: 800; +`; + +const InputBox = styled.div` + width: 360px; + & label { + font-size: 0.8rem; + font-weight: 800; + } + & input { + width: 95%; + height: 30px; + background-color: #18191c; + border-radius: 2px; + margin: 5px 0; + color: #fff; + padding: 5px 10px; + font-size: 0.9rem; + } + & p { + font-size: 0.8rem; + color: #ed4245; + } +`; + +function SignUp() { + const [email, setEmail] = useState({ email: '', isWarn: false }); + const [password, setPassword] = useState({ password: '', isWarn: false }); + const [passwordConfirm, setPasswordConfirm] = useState({ + passwordConfirm: '', + isWarn: false, + }); + const [nickName, setNickName] = useState({ nickName: '', isWarn: false }); + + const validateEmail = (value) => value.includes('@'); + const validatePassword = (value) => { + const check = + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,20}$/; + return check.test(value); + }; + const validatePasswordConfirm = (value) => { + return value === password.password; + }; + const validateNickName = (value) => { + return value.length !== 0; + }; + + const blurHandler = (id, value, setFn, validationFn) => { + if (validationFn(value)) return setFn({ [id]: value, isWarn: false }); + setFn((prev) => { + return { ...prev, isWarn: true }; + }); + }; + + return ( + +

계정 만들기

+ + + + blurHandler( + event.target.id, + event.target.value, + setEmail, + validateEmail, + ) + } + /> + {email.isWarn &&

이메일을 입력해 주세요.

} +
+ + + + blurHandler( + event.target.id, + event.target.value, + setPassword, + validatePassword, + ) + } + > + {password.isWarn && ( +

비밀번호는 대문자, 소문자, 숫자, 특수문자 조합을 사용해 주세요.

+ )} +
+ + + + blurHandler( + event.target.id, + event.target.value, + setPasswordConfirm, + validatePasswordConfirm, + ) + } + > + {passwordConfirm.isWarn &&

비밀번호가 일치하지 않습니다.

} +
+ + + + blurHandler( + event.target.id, + event.target.value, + setNickName, + validateNickName, + ) + } + > + {nickName.isWarn && ( +

+ 입력하지 않을경우 랜덤 별명이 발급 됩니다. +

+ )} +
+ 계속하기 +
+ ); +} + +export default SignUp; diff --git a/1-team-front/src/hooks/Auth/useAuthMutation.js b/1-team-front/src/hooks/Auth/useAuthMutation.js new file mode 100644 index 0000000..2812a09 --- /dev/null +++ b/1-team-front/src/hooks/Auth/useAuthMutation.js @@ -0,0 +1,15 @@ +/* eslint-disable */ + +import axios from 'axios'; +import { useMutation } from 'react-query'; + +const useAuthMutation = (url) => { + const { mutate, ...rest } = useMutation(async (payload) => { + const response = await axios.post(url, payload); + return response.data; + }); + + return { mutate, ...rest }; +}; + +export default useAuthMutation; diff --git a/1-team-front/src/hooks/Auth/useBlurHandler.js b/1-team-front/src/hooks/Auth/useBlurHandler.js new file mode 100644 index 0000000..84b65cc --- /dev/null +++ b/1-team-front/src/hooks/Auth/useBlurHandler.js @@ -0,0 +1,13 @@ +import { useState } from 'react'; + +const useBlurHandler = (initialValue) => { + const [inputValue, setInputValue] = useState(initialValue); + + const blurHandler = (event) => { + setInputValue(event.target.value); + }; + + return [inputValue, blurHandler]; +}; + +export default useBlurHandler; diff --git a/1-team-front/src/index.css b/1-team-front/src/index.css index 6119ad9..1373380 100644 --- a/1-team-front/src/index.css +++ b/1-team-front/src/index.css @@ -1,3 +1,9 @@ +* { + margin: 0; + border: 0; + padding: 0; +} + :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; diff --git a/1-team-front/src/pages/Auth.jsx b/1-team-front/src/pages/Auth.jsx new file mode 100644 index 0000000..67eeea2 --- /dev/null +++ b/1-team-front/src/pages/Auth.jsx @@ -0,0 +1,21 @@ +import styled from 'styled-components'; +import { Outlet } from 'react-router-dom'; + +const AuthContainer = styled.div` + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: #202225; +`; + +function Auth() { + return ( + + + + ); +} + +export default Auth;