박권수

feat. Register View

......@@ -17,6 +17,7 @@
"axios": "^0.21.1",
"highcharts": "^9.2.0",
"highcharts-react-official": "^3.0.0",
"moment": "^2.29.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.2.0",
......@@ -24,12 +25,15 @@
"recoil": "^0.4.0",
"recoil-persist": "^3.0.0",
"styled-components": "^5.3.0",
"sweetalert2": "^11.1.3",
"typescript": "^4.1.2",
"validator": "^13.6.0",
"web-vitals": "^1.0.1"
},
"devDependencies": {
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.12",
"@types/validator": "^13.6.3",
"@typescript-eslint/eslint-plugin": "^4.29.1",
"@typescript-eslint/parser": "^4.29.1",
"eslint": "^7.32.0",
......@@ -2631,6 +2635,12 @@
"source-map": "^0.6.1"
}
},
"node_modules/@types/validator": {
"version": "13.6.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz",
"integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==",
"dev": true
},
"node_modules/@types/webpack": {
"version": "4.41.26",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz",
......@@ -11693,6 +11703,14 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"engines": {
"node": "*"
}
},
"node_modules/move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
......@@ -16885,6 +16903,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sweetalert2": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.1.4.tgz",
"integrity": "sha512-CTNLviF6w1wyh1ou2UY8RpZasV2RiXbO6489v+a1Swz4jrUyuFDDjMZ9tMMVFnxTMdiUGfYiJoFUniZbHSEnHw==",
"funding": {
"url": "https://sweetalert2.github.io/#donations"
}
},
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
......@@ -17811,6 +17837,14 @@
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/validator": {
"version": "13.6.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz",
"integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
......@@ -21123,6 +21157,12 @@
"source-map": "^0.6.1"
}
},
"@types/validator": {
"version": "13.6.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.3.tgz",
"integrity": "sha512-fWG42pMJOL4jKsDDZZREnXLjc3UE0R8LOJfARWYg6U966rxDT7TYejYzLnUF5cvSObGg34nd0+H2wHHU5Omdfw==",
"dev": true
},
"@types/webpack": {
"version": "4.41.26",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz",
......@@ -28254,6 +28294,11 @@
"minimist": "^1.2.5"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
......@@ -32338,6 +32383,11 @@
}
}
},
"sweetalert2": {
"version": "11.1.4",
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.1.4.tgz",
"integrity": "sha512-CTNLviF6w1wyh1ou2UY8RpZasV2RiXbO6489v+a1Swz4jrUyuFDDjMZ9tMMVFnxTMdiUGfYiJoFUniZbHSEnHw=="
},
"symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
......@@ -33054,6 +33104,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
"validator": {
"version": "13.6.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz",
"integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg=="
},
"value-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz",
......
......@@ -23,6 +23,7 @@
"styled-components": "^5.3.0",
"sweetalert2": "^11.1.3",
"typescript": "^4.1.2",
"validator": "^13.6.0",
"web-vitals": "^1.0.1"
},
"scripts": {
......@@ -52,6 +53,7 @@
"devDependencies": {
"@types/react-router-dom": "^5.1.8",
"@types/styled-components": "^5.1.12",
"@types/validator": "^13.6.3",
"@typescript-eslint/eslint-plugin": "^4.29.1",
"@typescript-eslint/parser": "^4.29.1",
"eslint": "^7.32.0",
......
......@@ -63,7 +63,7 @@ const Header = (props : HeaderProps) => {
<styled.Container>
<styled.HeaderLeftWrapper>
{
(token && token.length && props.location.pathname !== '/') || props.location.pathname === '/register' ?
(token && token.length && props.location.pathname !== '/') ?
<styled.Backbutton
onClick = {onGoBack}
>
......
......@@ -146,7 +146,7 @@ export const MedicineInfoWrapper = styled.div `
width : 100%;
overflow : scroll;
border : 1px solid;
border : none;
display : flex;
flex-direction : column;
......
import React, { useState, useEffect } from "react";
import { RouteComponentProps } from 'react-router-dom';
import { useRecoilValue, useRecoilState } from "recoil";
import { useRecoilValue } from "recoil";
import * as recoilUtil from '../../util/recoilUtil';
import validator from 'validator';
import * as Alert from '../../util/alertMessage';
import Header from '../../components/Header';
import RegisterPresenter from "./RegisterPresenter";
import { authApi } from '../../api';
import { resourceLimits } from "worker_threads";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
......@@ -15,7 +19,26 @@ interface RegisterProps extends RouteComponentProps {}
const RegisterContainer = (props : RegisterProps) => {
const [token, setToken] = useRecoilState(recoilUtil.token);
const token = useRecoilValue(recoilUtil.token);
const [registerForm, setRegisterForm] = useState<any>({
userId : '',
password : '',
passwordCheck : '',
info : {
doctorLicense : '',
hospitalNm : '',
hospitalAddr : '',
contact : '',
doctorType : '',
doctorNm : '',
},
});
const [page, setPage] = useState<number>(1);
const [error, setError] = useState<string | null>(null);
const fetchData = async() => {
if(token && token.length) {
......@@ -26,8 +49,167 @@ const RegisterContainer = (props : RegisterProps) => {
}
};
const onCancleRegister = () => {
Alert.onCheck('회원가입을 취소하시겠습니까?',
() => props.history.push('/login'),
() => null);
};
const onGoBackPage = () => {
if(page > 1) {
setPage(page - 1);
}
};
const validateRegisterForm = () => {
if(page === 1) {
if (!validator.isEmail(registerForm.userId)) {
setError('회원 가입 ID는 이메일이어야 합니다.');
} else if(registerForm.password === registerForm.password.toLowerCase()
|| !/\d/.test(registerForm.password)
) {
setError('비밀번호는 최소 8자 이상이어야 하고, 대문자 및 숫자를 1개 이상 포함하고 있어야 합니다.');
} else if(registerForm.password !== registerForm.passwordCheck) {
setError('비밀번호가 일치하지 않습니다.')
} else setError(null);
} else if(page === 2) {
if(!registerForm.info.doctorLicense.length &&
!validator.isAlphanumeric(registerForm.info.doctorLicense)) {
setError('의사 자격 번호를 입력해야 합니다.');
} else if(registerForm.info.doctorNm.length < 2) {
setError('의사 이름을 올바르게 입력해야 합니다.');
} else if(!registerForm.info.contact) {
setError('연락처를 입력해주세요.');
} else setError(null);
} else if(page === 3) {
if(!registerForm.info.doctorType.length) {
setError('전문 분야를 입력해주세요.');
} else if(!registerForm.info.hospitalNm || !registerForm.info.hospitalAddr) {
setError('올바른 병원 정보를 입력해주세요');
} else setError(null);
}
};
const onSetUserId = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
userId : e.target.value,
});
};
const onSetPassword = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
password : e.target.value,
});
};
const onSetPasswordCheck = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
passwordCheck : e.target.value,
});
};
const onSetDoctorLicense = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
doctorLicense : e.target.value,
},
});
};
const onSetHospitalNm = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
hospitalNm : e.target.value,
},
});
};
const onSetHospitalAddr = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
hospitalAddr : e.target.value,
},
});
};
const onSetContact = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
contact : e.target.value,
},
});
};
const onSetDoctorType = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
doctorType : e.target.value,
},
});
};
const onSetDoctorNm = (e : React.ChangeEvent<HTMLInputElement>) => {
setRegisterForm({
...registerForm,
info : {
...registerForm.info,
doctorNm : e.target.value,
},
});
};
const onSubmitButton = () => {
if(error) {
Alert.onError(error, () => null);
return;
}
if(page === 1) {
setPage(2);
} else if(page === 2) {
setPage(3);
} else if(page === 3) {
const onRegisterDoctor = async () => {
try {
const result = await authApi.registerDoctor(registerForm);
if(result.data === 'Created') {
Alert.onSuccess('회원가입 성공, 관리자의 승인을 대기하세요.', () => props.history.push('/login'));
}
} catch(e) {
Alert.onError(e.response.data, () => null);
}
};
Alert.onCheck('입력하신 정보로 회원가입을 진행하시겠습니까?', onRegisterDoctor, () => null);
}
};
useEffect(() => {
validateRegisterForm();
}, [registerForm, page]);
useEffect(() => {
fetchData();
validateRegisterForm();
}, []);
......@@ -35,7 +217,23 @@ const RegisterContainer = (props : RegisterProps) => {
<>
<Header {...props}/>
<RegisterPresenter
registerForm = {registerForm}
page = {page}
error = {error}
onGoBackPage = {onGoBackPage}
onCancleRegister = {onCancleRegister}
onSetUserId = {onSetUserId}
onSetPassword = {onSetPassword}
onSetPasswordCheck = {onSetPasswordCheck}
onSetDoctorLicense = {onSetDoctorLicense}
onSetHospitalNm = {onSetHospitalNm}
onSetHospitalAddr = {onSetHospitalAddr}
onSetContact = {onSetContact}
onSetDoctorType = {onSetDoctorType}
onSetDoctorNm = {onSetDoctorNm}
onSubmitButton = {onSubmitButton}
/>
</>
)
......
......@@ -3,32 +3,156 @@ import React from 'react';
import * as styled from './RegisterStyled';
const RegisterPresenter = () => {
interface RegisterProps {
registerForm : {
userId : string;
password : string;
passwordCheck : string;
info : {
doctorLicense : string;
hospitalNm : string;
hospitalAddr : string;
contact : string;
doctorType : string;
doctorNm : string;
},
};
page : number;
error : string | null;
onGoBackPage : () => void;
onCancleRegister : () => void;
onSetUserId : React.ChangeEventHandler<HTMLInputElement>;
onSetPassword : React.ChangeEventHandler<HTMLInputElement>;
onSetPasswordCheck : React.ChangeEventHandler<HTMLInputElement>;
onSetDoctorLicense : React.ChangeEventHandler<HTMLInputElement>;
onSetHospitalNm : React.ChangeEventHandler<HTMLInputElement>;
onSetHospitalAddr : React.ChangeEventHandler<HTMLInputElement>;
onSetContact : React.ChangeEventHandler<HTMLInputElement>;
onSetDoctorType : React.ChangeEventHandler<HTMLInputElement>;
onSetDoctorNm : React.ChangeEventHandler<HTMLInputElement>;
onSubmitButton : () => void;
}
const RegisterPresenter = (props : RegisterProps) => {
return (
<styled.Container>
<styled.RegisterWrapper>
<styled.RegisterBackButtonWrapper>
<styled.RegisterBackButton
onClick = {props.onCancleRegister}
>
회원가입 취소
</styled.RegisterBackButton>
{
props.page > 1 ?
<styled.RegisterBackButton
onClick = {props.onGoBackPage}
>
이전
</styled.RegisterBackButton> : null
}
</styled.RegisterBackButtonWrapper>
<styled.RegisterTitle>회원 가입</styled.RegisterTitle>
<styled.RegisterInfo>* 의사만 회원가입이 가능합니다.</styled.RegisterInfo>
<styled.RegisterInfo style = {{fontSize : 10,}}>의사 인증을 위한 정보가 요구됩니다. 해당 정보는 인증을 위한 용도로만 사용됩니다.</styled.RegisterInfo>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>이메일</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Email'
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>비밀번호</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Password'
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>비밀번호 확인</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Password Again'
/>
</styled.RegisterInputWrapper>
</styled.RegisterWrapper>
{
props.page === 1 ?
<>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>이메일</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Email'
value = {props.registerForm.userId}
onChange = {props.onSetUserId}
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>비밀번호</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Password'
value = {props.registerForm.password}
onChange = {props.onSetPassword}
type = 'password'
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>비밀번호 확인</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Password Again'
value = {props.registerForm.passwordCheck}
onChange = {props.onSetPasswordCheck}
type = 'password'
/>
</styled.RegisterInputWrapper>
</> :
props.page === 2 ?
<>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>의사 자격증 번호</styled.RegisterInputText>
<styled.RegisterInput
placeholder = "Doctor's License"
value = {props.registerForm.info.doctorLicense}
onChange = {props.onSetDoctorLicense}
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>이름</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Name'
value = {props.registerForm.info.doctorNm}
onChange = {props.onSetDoctorNm}
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>연락처</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Contact'
value = {props.registerForm.info.contact}
onChange = {props.onSetContact}
/>
</styled.RegisterInputWrapper>
</> :
props.page === 3 ?
<>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>전문 분야</styled.RegisterInputText>
<styled.RegisterInput
placeholder = "Doctor's Type"
value = {props.registerForm.info.doctorType}
onChange = {props.onSetDoctorType}
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>병원 이름</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Hospital'
value = {props.registerForm.info.hospitalNm}
onChange = {props.onSetHospitalNm}
/>
</styled.RegisterInputWrapper>
<styled.RegisterInputWrapper>
<styled.RegisterInputText>병원 주소</styled.RegisterInputText>
<styled.RegisterInput
placeholder = 'Address'
value = {props.registerForm.info.hospitalAddr}
onChange = {props.onSetHospitalAddr}
/>
</styled.RegisterInputWrapper>
</> : null
}
<styled.RegisterButtonWrapper>
<styled.RegisterButton
onClick = {props.onSubmitButton}
>
{
props.page < 3 ? '다음' : '회원 가입'
}
</styled.RegisterButton>
</styled.RegisterButtonWrapper>
</styled.RegisterWrapper>
</styled.Container>
)
};
......
import styled from 'styled-components';
export const Container = styled.div `
width : 100%;
height : 80vh;
......@@ -24,6 +25,44 @@ export const RegisterWrapper = styled.div `
box-shadow: 0px 0px 10px #a0a0a0;
`;
export const RegisterBackButtonWrapper = styled.div `
width : 100%;
border : none;
display : flex;
flex-direction : row-reverse;
align-items : center;
justify-content : space-between;
margin : 0 0 10px 0;
`;
export const RegisterBackButton = styled.div `
border : none;
border-bottom : 1px solid;
background-color : transparent;
color : #a0a0a0;
cursor : pointer;
font-size : 12px;
font-weight : 500;
letter-spacing : 1px;
padding : 1px 3px;
margin : 0px 20px;
transition : .25s all;
&:hover {
opacity : .7;
}
`;
export const RegisterTitle = styled.div `
......@@ -60,11 +99,48 @@ export const RegisterInputText = styled.div `
export const RegisterInput = styled.input `
width : 80%;
padding : 0 0 5px 0;
padding : 5px 10px;
border : none;
border-bottom : 1px solid #ddd;
font-size : 14px;
letter-spacing : 1px;
&::placeholder {
color : #ddd;
}
`;
export const RegisterButtonWrapper = styled.div `
margin : 20px 0 0 0;
width : 100%;
border : none;
display : flex;
justify-content : center;
align-items : center;
`;
export const RegisterButton = styled.button `
padding : 10px 30px;
width : 80%;
cursor : pointer;
background-color : transparent;
border : 1.2px solid;
border-radius : 3px;
color : #343434;
transition : .25s all;
&:hover {
color : #fff;
border : 1.2px solid #343434;
background-color : #343434;
}
`;
\ No newline at end of file
......
This diff could not be displayed because it is too large.