diff --git a/index.html b/index.html index 6e23e05..40cb487 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,9 @@ rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> + TEMP diff --git a/package-lock.json b/package-lock.json index c5414bc..c982485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fullcalendar/interaction": "^6.1.8", "@fullcalendar/react": "^6.1.8", "axios": "^1.4.0", + "date-fns": "^2.30.0", "fullcalendar": "^6.1.8", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -30,6 +31,7 @@ "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "react-datepicker": "^4.16.0", "ts-node": "^10.9.1", "typescript": "^5.0.2", "vite": "^4.4.5", @@ -2622,6 +2624,16 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@remix-run/router": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz", @@ -3461,6 +3473,12 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==", + "dev": true + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3563,6 +3581,21 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4751,6 +4784,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4994,6 +5036,23 @@ "node": ">=6.0.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -5039,6 +5098,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.16.0.tgz", + "integrity": "sha512-hNQ0PAg/LQoVbDUO/RWAdm/RYmPhN3cz7LuQ3hqbs24OSp69QCiKOJRrQ4jk1gv1jNR5oYu8SjjgfDh8q6Q1yw==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.11.8", + "classnames": "^2.2.6", + "date-fns": "^2.30.0", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.12.2", + "react-popper": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -5051,6 +5128,41 @@ "react": "^18.2.0" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "dev": true + }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "dev": true, + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, + "node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dev": true, + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-router": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz", @@ -5869,6 +5981,15 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 6d9ca5a..9b54aa8 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@fullcalendar/interaction": "^6.1.8", "@fullcalendar/react": "^6.1.8", "axios": "^1.4.0", + "date-fns": "^2.30.0", "fullcalendar": "^6.1.8", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -32,6 +33,7 @@ "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "react-datepicker": "^4.16.0", "ts-node": "^10.9.1", "typescript": "^5.0.2", "vite": "^4.4.5", diff --git a/src/api/account.ts b/src/api/account.ts index 4e6cc9d..05fc3ad 100644 --- a/src/api/account.ts +++ b/src/api/account.ts @@ -1,8 +1,13 @@ import { baseInstance, authInstance } from 'api/index' //회원가입 -- base -export const signUp = async newAccount => { - const res = await baseInstance.post('/signup', newAccount) +export const signUp = async ({ password, email, hireDate, username }) => { + const res = await baseInstance.post('/signup', { + password: password, + email: email, + hireDate: hireDate, + username: username + }) return res.data } @@ -15,26 +20,23 @@ export const signIn = async (email: string, password: string) => { return res } -//로그아웃 -- auth -- API X => **remove token** -// export const signOut = async () => { -// const res = await authInstance.post('/signin') -// return res.data -// } - //이메일 중복체크 -- base export const checkEmailAvailable = async (email: string) => { - const res = await baseInstance.post('/update', email) + const res = await baseInstance.get(`/emailCheck?email=${email}`) return res.data } //정보 수정(update) -- auth -export const updateProfile = async account => { - const res = await authInstance.post('/findPassword', account) +export const updateProfile = async (profile: string, password: string) => { + const res = await authInstance.post('/update', { + profile: profile, + password: password + }) return res.data } -//비밀번호 재설정(이메일 전송) -- auth +//비밀번호 재설정(이메일 전송) -- base export const resetPassword = async (email: string) => { - const res = await authInstance.post('/findPassword', email) + const res = await baseInstance.post('/findPassword', { email: email }) return res.data } diff --git a/src/assets/calendarIcon.png b/src/assets/calendarIcon.png new file mode 100644 index 0000000..eccb953 Binary files /dev/null and b/src/assets/calendarIcon.png differ diff --git a/src/components/form/DatePickerForm.tsx b/src/components/form/DatePickerForm.tsx new file mode 100644 index 0000000..fd5a9e3 --- /dev/null +++ b/src/components/form/DatePickerForm.tsx @@ -0,0 +1,58 @@ +import { styled } from 'styled-components' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import { TitleText } from 'components/index' +import { signupTexts } from 'constants/index' +import { ko } from 'date-fns/esm/locale' + +export const DatePickerForm = ({ date, setDate }) => { + //코드 더러움 수정 필요 + return ( + <> + + {signupTexts.hireDate} + + + + ) +} + +const DatePickerWrapper = styled.div` + position: relative; + img { + width: 25px; + height: 24px; + position: absolute; + z-index: 1; + top: 53px; + right: 40px; + cursor: pointer; + } + input { + background: #ffffff; + border: 1px solid #d9d9d9; + border-radius: 10px; + padding-left: 10px; + width: 434px; + height: 60px; + margin-bottom: 6px; + &::placeholder { + font-size: 12px; + opacity: 0.75; + } + &:focus { + outline: 1px solid ${props => props.theme.colors.primaryBlue}; + } + } +` diff --git a/src/components/form/EmailValidationForm.tsx b/src/components/form/EmailValidationForm.tsx new file mode 100644 index 0000000..42d84d9 --- /dev/null +++ b/src/components/form/EmailValidationForm.tsx @@ -0,0 +1,75 @@ +import { InputField } from 'components/index' +import { styled } from 'styled-components' +import { checkEmailAvailable } from 'api/index' +import { AxiosResponse, AxiosError } from 'axios' +import { signupTexts } from 'constants/index' + +interface EmailResponse extends AxiosResponse { + response: { + email: string + available: boolean + } +} + +export const EmailValidationForm = ({ + setEmail, + email, + title, + ph, + setIsEmailInUse +}) => { + const handleEmailCheck = async () => { + try { + const res: EmailResponse = await checkEmailAvailable(email) + // 중복되는 이메일이 없는 경우 (해당 이메일로 가입 가능) + if (email && res?.response?.available === true) { + setIsEmailInUse(true) + alert(`${signupTexts.emailAvailable}`) + } + // 중복되는 이메일이 있는 경우 (해당 이메일로 가입 불가능) + if (email && res?.response?.available === false) { + setIsEmailInUse(false) + alert(`${signupTexts.emailInUse}`) + } + } catch (err) { + if (err instanceof AxiosError) { + console.error(err) + alert(err) + } + } + } + + return ( + //코드 더러움 수정 필요 + + + + ) +} + +const ValidationFormWrapper = styled.div` + position: relative; + span { + font-size: 32px; + position: absolute; + z-index: 1; + top: 50px; + right: 36px; + cursor: pointer; + } +` diff --git a/src/components/form/InputField.tsx b/src/components/form/InputField.tsx index c4126bc..30f65f2 100644 --- a/src/components/form/InputField.tsx +++ b/src/components/form/InputField.tsx @@ -14,7 +14,7 @@ export const InputField = ({ inputRef, title, ph, fn, val, type }) => { ) } -const TitleText = styled.div` +export const TitleText = styled.div` padding: 10px; line-height: 16px; letter-spacing: -0.48px; diff --git a/src/components/form/ResetPasswordForm.tsx b/src/components/form/ResetPasswordForm.tsx index 4310e65..2c4f104 100644 --- a/src/components/form/ResetPasswordForm.tsx +++ b/src/components/form/ResetPasswordForm.tsx @@ -2,11 +2,21 @@ import { styled } from 'styled-components' import { useRef, useEffect, useState } from 'react' import { resetTexts } from 'constants/index' import { InputField } from 'components/index' +import { resetPassword } from 'api/index' export const ResetPasswordForm = () => { const [email, setEmail] = useState('') - const submitHandler = () => {} + const handlePasswordReset = async e => { + e.preventDefault() + try { + if (email) { + await resetPassword(email) + } + } catch (error) { + console.log(error) + } + } const inputRef = useRef(null) @@ -24,13 +34,15 @@ export const ResetPasswordForm = () => { > - {resetTexts.reset} + + {resetTexts.reset} + ) } diff --git a/src/components/form/SignUpForm.tsx b/src/components/form/SignUpForm.tsx index 1497c9e..706bc12 100644 --- a/src/components/form/SignUpForm.tsx +++ b/src/components/form/SignUpForm.tsx @@ -1,49 +1,106 @@ import { styled } from 'styled-components' -import { useRef, useEffect } from 'react' +import { useRef, useEffect, useState } from 'react' import { signupTexts } from 'constants/index' -import { InputField, SignInCallToAction } from 'components/index' +import { signUp } from 'api/index' +import { + InputField, + SignInCallToAction, + DatePickerForm, + EmailValidationForm +} from 'components/index' +import { dateFormatter } from 'utils/index' +import { AxiosError, AxiosResponse } from 'axios' +import { useNavigate } from 'react-router-dom' export const SignUpForm = () => { + const navigate = useNavigate() + // 유저 입력 데이터들 + const [name, setName] = useState('') + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [verification, setVerification] = useState('') + const [startDate, setStartDate] = useState(new Date()) + // 이메일 중복확인 데이터 + const [isEmailInUse, setIsEmailInUse] = useState(true) + const inputRef = useRef(null) useEffect(() => { inputRef?.current?.focus() }, []) + const handleSignUp = async e => { + e.preventDefault() + try { + // 유저 입력 데이터들 부재시 + if (!(email && password && startDate && name)) { + alert(`${signupTexts.requiredData}`) + return + } + // 이메일 중복확인 미완료시 + if (!isEmailInUse) { + alert(`${signupTexts.validationRequired}`) + return + } + const res: AxiosResponse = await signUp({ + password: password, + email: email, + hireDate: dateFormatter(startDate), + username: name + }) + // 회원가입 성공시 로그인 화면으로 이동 + if (res.status === 200) { + navigate('/') + return + } + } catch (err) { + if (err instanceof AxiosError) { + const error = e.response?.data.error.message + alert(error) + } + } + } + return ( - + setIsEmailInUse={setIsEmailInUse} + /> - {signupTexts.registerBtn} + + + {signupTexts.registerBtn} + ) diff --git a/src/components/form/index.ts b/src/components/form/index.ts index af32d58..c779303 100644 --- a/src/components/form/index.ts +++ b/src/components/form/index.ts @@ -8,3 +8,5 @@ export * from 'components/form/InputField' export * from 'components/form/SignUpCallToAction' export * from 'components/form/SignInCallToAction' export * from 'components/form/UpdateImageForm' +export * from 'components/form/DatePickerForm' +export * from 'components/form/EmailValidationForm' diff --git a/src/constants/signupTexts.ts b/src/constants/signupTexts.ts index c159062..424f856 100644 --- a/src/constants/signupTexts.ts +++ b/src/constants/signupTexts.ts @@ -1,5 +1,4 @@ export const signupTexts = { - intro: 'Welcome👀', signin: '회원가입', username: '이름', email: '이메일 주소', @@ -12,5 +11,10 @@ export const signupTexts = { usernameText: '이름을 입력해주세요.', emailText: '이메일을 입력해주세요', pwdText: '영어 대문자, 영어 소문자, 숫자, 특수문자를 모두 포함 (8글자 이상)', - pwdCheckText: '비밀번호를 다시 입력해주세요.' + pwdCheckText: '비밀번호를 다시 입력해주세요.', + hireDate: '입사날짜', + emailInUse: '이미 사용중인 이메일입니다.', + emailAvailable: '사용가능한 이메일입니다.', + requiredData: '회원기입에 실패했습니다. 입력항목을 다시 확인해주세요.', + validationRequired: '이메일 인증을 진행해주세요.' } diff --git a/src/utils/dateFormatter.ts b/src/utils/dateFormatter.ts new file mode 100644 index 0000000..70d9c59 --- /dev/null +++ b/src/utils/dateFormatter.ts @@ -0,0 +1,14 @@ +export let dateFormatter = selected => { + return ( + selected.getFullYear() + + '-' + + (selected.getMonth() + 1 < 9 + ? '0' + (selected.getMonth() + 1) + : selected.getMonth() + 1) + + '-' + + (selected.getDate() < 9 ? '0' + selected.getDate() : selected.getDate()) + + 'T' + + '09:00:00' + + 'Z' + ) +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..f4fe8d6 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export * from 'utils/dateFormatter' diff --git a/tsconfig.json b/tsconfig.json index 2ab7ceb..ff87f95 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -34,7 +34,8 @@ "api/*": ["src/api/*"], "styles/*": ["src/styles/*"], "assets/*": ["src/assets/*"], - "constants/*": ["src/constants/*"] + "constants/*": ["src/constants/*"], + "utils/*": ["src/utils/*"] } }, "include": [