diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index a0a5cf94..68cfb9bf 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -37,7 +37,4 @@ jobs: aws-region: ${{ secrets.AWS_REGION }} - name: Deploy to S3 - run: aws s3 sync ./client/build s3://${{ secrets.DEV_AWS_S3_BUCKET }} --delete - - - name: Invalidate CloudFront Cache - run: aws cloudfront create-invalidation --distribution-id ${{secrets.DEV_AWS_DISTRIBUTION_ID}} --paths "/*" + run: aws s3 sync ./client/build s3://${{ secrets.DEV_AWS_S3_BUCKETNAME }} --delete diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 05d9c43e..3b8ef2dd 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -17,7 +17,7 @@ permissions: contents: read env: - S3_BUCKET_NAME: 004-s3-bucket + S3_BUCKET_NAME: fe-004-s3-bucket jobs: build: diff --git a/client/public/index.html b/client/public/index.html index f08253ed..2822e8a5 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -12,7 +12,7 @@ /> { ) } - const InputWrapper = styled.div` - height: 6rem; flex: 1; ` diff --git a/client/src/components/Common/Radio.tsx b/client/src/components/Common/Radio.tsx index 1643f4f9..903e8166 100644 --- a/client/src/components/Common/Radio.tsx +++ b/client/src/components/Common/Radio.tsx @@ -10,7 +10,7 @@ const Radio = (props: radioProps) => { {legend} {radioArray.map((i) => ( -
+ { onChange={onChange} /> {i.label} -
+ ))}
@@ -30,22 +30,26 @@ const Radio = (props: radioProps) => { const RadioWrapper = styled.div` display: flex; flex-wrap: wrap; - gap: 2rem; + gap: 1rem; + row-gap: 2rem; + + input[type='radio'] { + display: none; + } - input[type='radio'], input[type='radio']:checked + label { - color: var(--color-point); - accent-color: var(--color-point); + color: var(--color-white); + background-color: var(--color-primary); font-weight: 600; + transition: all 0.1s; } +` - .radio-div { - margin-top: 0.8rem; - } +const RadioInputWrapper = styled.div` + margin-top: 1rem; ` const Legend = styled.legend` - display: inline-block; margin: 0 0 0.4rem 0.4rem; font-size: 1.4rem; font-weight: 700; @@ -53,6 +57,11 @@ const Legend = styled.legend` const StyledLabel = styled.label` font-size: 1.4rem; + color: var(--color-secondary); + border: 1px solid var(--color-secondary); + border-radius: 1rem; + padding: 0.8rem 1rem; + cursor: pointer; ` export default Radio diff --git a/client/src/components/Common/Tab.tsx b/client/src/components/Common/Tab.tsx index 1774ab72..3157785a 100644 --- a/client/src/components/Common/Tab.tsx +++ b/client/src/components/Common/Tab.tsx @@ -15,7 +15,7 @@ const Tab = ({ tabItem }: TabMenus) => { }, []) return ( -
+ <> {tabItem.map((i, idx) => ( { -
+ ) } @@ -52,6 +52,7 @@ const TabLink = styled(Link)` } ` const SubpageContainer = styled.div` + width: 100%; min-height: 30rem; padding: 2.6rem; border: 1px solid var(--color-light-gray); diff --git a/client/src/components/User/ChangePwd.tsx b/client/src/components/User/ChangePwd.tsx index 61b9ce6e..36e9a386 100644 --- a/client/src/components/User/ChangePwd.tsx +++ b/client/src/components/User/ChangePwd.tsx @@ -106,38 +106,36 @@ const ChangePwd = () => { return ( <> - -
- - - - -
-
+
+ + + + +
{ ) } -const Wrapper = styled.div` - width: 63.7rem; - min-height: 40rem; - - @media ${({ theme }) => theme.device.tablet} { - width: 88vw; - } -` - const Form = styled.form` - width: 35rem; + width: 40rem; display: flex; flex-direction: column; gap: 1rem; @@ -168,6 +157,15 @@ const Form = styled.form` > button { margin-top: 0.8rem; } + + @media ${({ theme }) => theme.device.tablet} { + width: 80%; + min-width: 20rem; + } + + @media ${({ theme }) => theme.device.mobile} { + width: 100%; + } ` export default ChangePwd diff --git a/client/src/components/User/EditProfile.tsx b/client/src/components/User/EditProfile.tsx index 0b3c2e0a..e1cf092e 100644 --- a/client/src/components/User/EditProfile.tsx +++ b/client/src/components/User/EditProfile.tsx @@ -14,7 +14,7 @@ import { useSelector, useDispatch } from 'react-redux' import { RootState } from '../../store' import { getCookie } from '../../utils/Cookie' import { __editUser } from '../../store/slices/profileSlice' -import { userLogout } from '../../utils/userfunc' +import { userLogout, checkDate, checkNumber } from '../../utils/userfunc' interface noticeType { nickName: string @@ -73,6 +73,24 @@ const EditProfile = () => { const { name, value } = e.target setProfile({ ...profile, [name]: value }) + + if (name === 'birth') { + if (!checkDate(value)) { + setNotice({ ...notice, birth: '유효하지 않은 생년월일입니다.' }) + } else { + setProfile({ ...profile, birth: value }) + setNotice({ ...notice, birth: '' }) + } + } + + if (name === 'height' || name === 'weight') { + if (!checkNumber(value)) { + setNotice({ ...notice, [name]: '유효하지 않은 값입니다.' }) + } else { + setProfile({ ...profile, [name]: Number(value) }) + setNotice({ ...notice, [name]: '' }) + } + } } // 닉네임 중복 확인 @@ -118,13 +136,18 @@ const EditProfile = () => { // 프로필 수정 const updateProfile = () => { + console.log(profile) const msg = { nickName: '', weight: '', height: '', birth: '' } let isBlank = false - for (const key in profile) { - if (profile[key] === '') { - isBlank = true - } + // for (const key in profile) { + // if (profile[key] === '') { + // isBlank = true + // } + // } + + if (notice.birth !== '' || notice.weight !== '' || notice.height !== '') { + isBlank = true } if ((isActive && nameCheck === cantUse) || nameCheck !== nickName) { @@ -132,18 +155,8 @@ const EditProfile = () => { return } - if (weight <= 0 && weight >= 500) { - msg.weight = '유효하지 않은 값입니다.' - } - if (height <= 0 && height >= 300) { - msg.height = '유효하지 않은 값입니다.' - } - if (birth === '') { - msg.birth = '생년월일을 입력해주세요.' - } - if (isBlank) { - setNotice({ ...notice, ...msg }) + openModal('입력값을 확인해주세요.') } else { axios .patch( @@ -175,7 +188,7 @@ const EditProfile = () => { } // 회원탈퇴 - const deleteUser = () => { + const deleteUser = async () => { setDel(false) openModal( '회원 탈퇴가 완료되었습니다. \n그동안 라이팅을 이용해주셔서 감사합니다.' @@ -202,102 +215,95 @@ const EditProfile = () => { return ( <> - - - - - - -
- - {!isActive ? ( -
- -
- ) : ( -
- -
- )} -
- - - + + + + + +
- + {!isActive ? ( +
+ +
+ ) : ( +
+ +
+ )} +
- - - - -
+ + + + + + + + +
theme.device.tablet} { - width: 88vw; - } -` const GridContainer = styled.div` display: grid; @@ -345,6 +346,14 @@ const GridContainer = styled.div` @media ${({ theme }) => theme.device.tablet} { grid-template-columns: none; + + .flex-div { + flex-direction: column; + + button { + top: 0; + } + } } ` diff --git a/client/src/components/User/TabFrame.tsx b/client/src/components/User/TabFrame.tsx index c5d1e6f2..2f98dc3d 100644 --- a/client/src/components/User/TabFrame.tsx +++ b/client/src/components/User/TabFrame.tsx @@ -11,7 +11,7 @@ const TabFrame = ({ title, children }: propType) => {

{title}


- {children} + {children}
) } @@ -28,4 +28,8 @@ const Container = styled.div` } ` +const Wrapper = styled.div` + min-height: 40rem; +` + export default TabFrame diff --git a/client/src/pages/NotFound.tsx b/client/src/pages/NotFound.tsx index 367cd555..4e4d9894 100644 --- a/client/src/pages/NotFound.tsx +++ b/client/src/pages/NotFound.tsx @@ -62,7 +62,7 @@ const Icon = styled.span` ` const ErrorWrapper = styled.div` - width: 55srem; + width: 55rem; text-align: center; h1 { diff --git a/client/src/pages/UserFindPwd.tsx b/client/src/pages/UserFindPwd.tsx index f29668ac..9c15fdd2 100644 --- a/client/src/pages/UserFindPwd.tsx +++ b/client/src/pages/UserFindPwd.tsx @@ -269,13 +269,28 @@ const Container = styled.div` .flex-div { width: 100%; - display: flex; - align-items: flex-center; + display: grid; + grid-template-columns: 2fr 1.2fr; gap: 0.6rem; button { + width: 100%; position: relative; - top: 2rem; + top: 1.8rem; + } + } + + @media ${({ theme }) => theme.device.mobile} { + width: 100%; + padding: 2rem 1.8rem; + + .flex-div { + grid-template-columns: 1fr; + + button { + width: 100%; + position: relative; + top: 0; } } ` diff --git a/client/src/pages/UserPage.tsx b/client/src/pages/UserPage.tsx index 46b28b53..f96d581a 100644 --- a/client/src/pages/UserPage.tsx +++ b/client/src/pages/UserPage.tsx @@ -53,16 +53,19 @@ const UserPage = () => { - +
+ +
) } const Container = styled.div` + width: 100%; max-width: 88rem; ` @@ -108,7 +111,14 @@ const UserProfile = styled.div` margin-bottom: 2rem; } - @media screen and (max-width: 500px) { + @media ${({ theme }) => theme.device.mobile} { + flex-direction: column; + gap: 2rem; + + img { + width: 9rem; + height: 9rem; + } } ` diff --git a/client/src/pages/UserSignIn.tsx b/client/src/pages/UserSignIn.tsx index 547b5b4a..52391794 100644 --- a/client/src/pages/UserSignIn.tsx +++ b/client/src/pages/UserSignIn.tsx @@ -55,17 +55,13 @@ const UserSignIn = () => { setCookie('access', tokenWithNoBearer, { path: '/', - secure: true, expires: current, - sameSite: 'none', }) current.setMinutes(current.getMinutes() + 1440) setCookie('refresh', tokenForReissue, { path: '/', - secure: true, expires: current, - sameSite: 'none', }) }) .catch((error) => { @@ -151,6 +147,11 @@ const Container = styled.div` span { font-size: 1.2rem; } + + @media ${({ theme }) => theme.device.mobile} { + width: 100%; + padding: 2rem 1.8rem; + } ` const Logo = styled.div` diff --git a/client/src/pages/UserSignUp.tsx b/client/src/pages/UserSignUp.tsx index eb07f3a9..856b6d3b 100644 --- a/client/src/pages/UserSignUp.tsx +++ b/client/src/pages/UserSignUp.tsx @@ -4,13 +4,12 @@ import Input from '../components/Common/Input' import Button from '../components/Common/Button' import Radio from '../components/Common/Radio' import { genderList, activityScore } from '../utils/options' -import { checkEmail, checkPassword } from '../utils/userfunc' -import { ApiCaller } from '../utils/apiCaller' import { - dtoReqEmailCheck, - dtoReqVerifyEmail, -} from '../dto/membership/members/dtoSignup' -import { dtoResponse } from '../dto' + checkEmail, + checkPassword, + checkDate, + checkNumber, +} from '../utils/userfunc' import { debounce } from '../utils/timefunc' import axios from 'axios' import Modal from '../components/Common/Modal' @@ -28,8 +27,8 @@ interface userInfo { password: string gender: string activity: string - height: number - weight: number + height: string + weight: string birth: string } @@ -39,11 +38,15 @@ interface authentication { } interface errorType { + [key: string]: any email: string auth: string nickName: string password: string - ckPassword: string + passwordcheck: string + birth: string + weight: string + height: string } interface successType { @@ -59,8 +62,8 @@ const UserSignUp = ({ social }: Props) => { password: '', gender: 'male', activity: 'NONE_ACTIVE', - height: 0, - weight: 0, + height: '', + weight: '', birth: '', }) @@ -78,7 +81,10 @@ const UserSignUp = ({ social }: Props) => { auth: '', nickName: '', password: '', - ckPassword: '', + passwordcheck: '', + birth: '', + weight: '', + height: '', }) const [success, setSuccess] = useState({ @@ -103,17 +109,59 @@ const UserSignUp = ({ social }: Props) => { } // 사용자 입력값 핸들링 - const handleInput = (e: React.ChangeEvent) => { + const handleInput = debounce((e: React.ChangeEvent) => { const { name, value } = e.target - setValues({ ...values, [name]: value }) - } + // 인증번호 + if (name === 'ckAuth') { + setAuthNums({ ...authNums, ckAuth: value }) + } - const handleAuthNum = (e: React.ChangeEvent) => { - const { name, value } = e.target + // 신장, 체중 + if (name === 'height' || name === 'weight') { + if (!checkNumber(value)) { + setError({ ...error, [name]: '유효하지 않은 값입니다.' }) + } else { + setValues({ ...values, [name]: Number(value) }) + setError({ ...error, [name]: '' }) + } + } - setAuthNums({ ...authNums, [name]: value }) - } + // 비밀번호 + if (name === 'password') { + if (!checkPassword(value)) { + setError({ + ...error, + [name]: + '최소 8자, 영문+숫자+특수문자(!@#$%&*?) 조합으로 구성되어야 합니다.', + }) + } else { + setValues({ ...values, [name]: value }) + setError({ ...error, [name]: '' }) + } + } + + if (name === 'passwordcheck') { + setCkPassword(value) + } + + // 생년월일 + if (name === 'birth') { + if (!checkDate(value)) { + setError({ + ...error, + [name]: '유효하지 않은 생년월일입니다.', + }) + } else { + setValues({ ...values, [name]: value }) + setError({ ...error, [name]: '' }) + } + } + + if (name) { + setValues({ ...values, [name]: value }) + } + }, 300) // 인증번호 전송 const sendNumbers = async (email: string) => { @@ -247,47 +295,40 @@ const UserSignUp = ({ social }: Props) => { }) } - // 비밀번호 유효성 검사 - const isValidPassword = () => { - const msg = { password: '', ckPassword: '' } - - if (!checkPassword(password)) { - msg.password = - '최소 8자, 영문+숫자+특수문자(!@#$%&*?) 조합으로 구성되어야 합니다.' - setError({ ...error, ...msg }) - } else if (password !== ckPassword) { - msg.ckPassword = '비밀번호가 일치하지 않습니다.' - setError({ ...error, ...msg }) - } else { - setError({ ...error, ...msg }) - } - } - // 버튼 활성화를 위한 입력값 검증 const checkValues = useCallback( - debounce((values: userInfo, isConfirm: boolean, ckpwd: string) => { + debounce((values: userInfo, isConfirm: boolean) => { let isBlank = false + let isNotError = true let isNotValid = true + for (const key in error) { + if (error[key] !== '') { + isNotError = false + break + } + } + for (const key in values) { if (values[key] === '') { isBlank = true + break } } if ( !isBlank && + isNotError && isConfirm && - ckpwd === values.password && - values.height > 0 && - values.weight > 0 + values.passwordcheck === values.password ) { isNotValid = false } + console.log(values) setIsEmpty(isNotValid) - }, 700), - [] + }, 300), + [error] ) // 가입하기 - 모든 값이 유효한 경우 버튼 활성화 @@ -298,9 +339,18 @@ const UserSignUp = ({ social }: Props) => { return } + const convertedUserinfo = { + ...values, + weight: Number(values.weight), + height: Number(values.height), + } + axios - .post(`${process.env.REACT_APP_SERVER_URL}/members/signup`, values) - .then((response) => { + .post( + `${process.env.REACT_APP_SERVER_URL}/members/signup`, + convertedUserinfo + ) + .then(() => { openModal('회원가입이 완료되었습니다. \n로그인 페이지로 이동합니다.') }) .catch((error) => { @@ -311,8 +361,8 @@ const UserSignUp = ({ social }: Props) => { } useEffect(() => { - checkValues(values, isConfirm, ckPassword) - }, [values, isConfirm, ckPassword]) + checkValues(values, isConfirm) + }, [values, ckPassword, isConfirm]) return ( <> @@ -331,7 +381,7 @@ const UserSignUp = ({ social }: Props) => { disabled={isConfirm} onChange={handleInput} /> -
+
@@ -370,7 +420,7 @@ const UserSignUp = ({ social }: Props) => { success={success.nickName} onChange={handleInput} /> -
+
@@ -382,48 +432,56 @@ const UserSignUp = ({ social }: Props) => { placeholder="영문, 숫자, 특수문자를 조합하여 최소 8자 이상" error={error.password} onChange={handleInput} - onBlur={isValidPassword} /> - setCkPassword(e.target.value)} - onBlur={isValidPassword} + error={ + ckPassword !== password && ckPassword !== '' + ? '비밀번호가 일치하지 않습니다.' + : '' + } + onChange={handleInput} /> )} + +
- -
+ theme.device.mobile} { + width: 100%; + padding: 2rem 1.8rem; + } ` const Form = styled.form` display: flex; @@ -481,19 +544,36 @@ const Form = styled.form` display: flex; align-items: flex-center; gap: 0.6rem; + } + + .btn-div { + flex: 1 1 1; button { + width: 100%; position: relative; - top: 2rem; + top: 1.8rem; } } .grid-div { - width: 100%; display: grid; - grid-template-columns: repeat(2, 1fr); - row-gap: 1.6rem; + grid-template-columns: 1fr 1fr; column-gap: 0.6rem; } + + @media ${({ theme }) => theme.device.mobile} { + .flex-div { + width: 100%; + display: grid; + grid-template-columns: 2fr 1fr; + gap: 0.6rem; + } + + .grid-div { + grid-template-columns: 1fr; + row-gap: 2rem; + } + } ` export default UserSignUp diff --git a/client/src/store/hooks/useTokenCheck.tsx b/client/src/store/hooks/useTokenCheck.tsx deleted file mode 100644 index 06ec292e..00000000 --- a/client/src/store/hooks/useTokenCheck.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect } from 'react' -import { useNavigate } from 'react-router-dom' -import { userLogout } from '../../utils/userfunc' -import { getCookie } from '../../utils/Cookie' - -const useTokenCheck = () => { - const navigate = useNavigate() - - useEffect(() => { - const checkToken = () => { - if (!getCookie('access')) { - navigate('/sign-in', { replace: true }) - } - } - - checkToken() - }, []) -} - -export default useTokenCheck diff --git a/client/src/utils/userfunc.ts b/client/src/utils/userfunc.ts index cd3a0ef4..f2c62474 100644 --- a/client/src/utils/userfunc.ts +++ b/client/src/utils/userfunc.ts @@ -12,6 +12,42 @@ export const checkPassword = (password: string): boolean => { return pwdRegex.test(password) } +export const checkDate = (date: string): boolean => { + // yyyy-mm-dd + const dateRegex = /^\d{4}-\d{2}-\d{2}$/ + + if (!dateRegex.test(date)) { + return false + } + + const convertedDate = new Date(date) + convertedDate.setHours(0, 0, 0, 0) + + if (isNaN(convertedDate.getTime())) { + return false + } + + const Today = new Date() + Today.setHours(0, 0, 0, 0) + + if (Today < convertedDate) { + // 현재 날짜보다 미래를 입력했으면 false + return false + } + + return true +} + +export const checkNumber = (nums: string): boolean => { + const converted = Number(nums) + + if (typeof converted === 'number' && converted > 0 && converted < 500) { + return true + } + + return false +} + export const userLogout = () => { removeCookie('access', { path: '/' }) removeCookie('refresh', { path: '/' })