diff --git a/src/App.tsx b/src/App.tsx index f421c99a..cf024e1d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,8 +13,8 @@ import ChangePassword from 'pages/Auth/FindIDPassword/ChangePassword'; import { Suspense } from 'react'; import SearchDetails from 'pages/SearchDetails'; import FollowPage from 'pages/Follow'; -import Setting from 'pages/Setting/UserSetting'; -import IdChange from 'pages/Setting/UserSetting/IdChange'; +import Setting from 'pages/Setting'; +import IdChange from 'pages/Setting/Mobile/IdChange'; import AuthRoute from 'components/common/AuthRoute'; import Withdrawal from 'pages/Setting/Withdrawal'; import Inquiry from 'pages/Inquiry'; @@ -40,11 +40,11 @@ export default function App(): JSX.Element { } /> }> - } /> - } /> }> } /> } /> + } /> + } /> } /> } /> diff --git a/src/api/follow/entity.ts b/src/api/follow/entity.ts index bcf58154..3771bd14 100644 --- a/src/api/follow/entity.ts +++ b/src/api/follow/entity.ts @@ -1,4 +1,4 @@ -import { User } from 'api/user/entity'; +import { EmailUser } from 'api/user/entity'; import { FollowerInfo } from 'pages/Follow/static/entity'; export interface FollowListParams { @@ -27,8 +27,8 @@ export interface SentOrReceivedFollowParams { export interface SentOrReceivedFollowResponse { content: { id: number; - follower: User; - user: User; + follower: EmailUser; + user: EmailUser; }[]; empty: boolean; last: boolean; diff --git a/src/api/user/entity.ts b/src/api/user/entity.ts index 4095d60c..c08b954c 100644 --- a/src/api/user/entity.ts +++ b/src/api/user/entity.ts @@ -25,7 +25,7 @@ export interface ModifyParams { email?: string; } -export interface User { +export interface EmailUser { account: string; nickname: string; email: string; @@ -42,6 +42,14 @@ export interface User { }; } +export interface SNSUser { + id: number, + nickname: string, + email: string, +} + +export type User = EmailUser | SNSUser; + export interface SendRegisterEmailParams { email: string; } @@ -62,3 +70,7 @@ export interface FindPasswordParams { export interface ChangePasswordParams { password: string } + +export interface CheckPasswordParams { + password: string +} diff --git a/src/api/user/index.ts b/src/api/user/index.ts index e6d0abc7..497a9798 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -5,15 +5,17 @@ import { LoginResponse, ModifyParams, RegisterParams, - User, + EmailUser, SendRegisterEmailParams, SendFindEmailParams, GetAccountParams, FindPasswordParams, + User, + CheckPasswordParams, } from './entity'; import userApi from './userApiClient'; -export const register = (param: RegisterParams) => userApi.post('/', param); +export const register = (param: RegisterParams) => userApi.post('/', param); export const checkIdDuplicate = (param: CheckIdDuplicateParams) => userApi.get(`/exists?account=${param.account}`); @@ -39,3 +41,5 @@ export const findPassowrd = (param: FindPasswordParams) => userApi.post('/passwo code: param.code, email: param.email, }); + +export const checkPassword = (param: CheckPasswordParams) => userApi.post(`/check-password?password=${param.password}`); diff --git a/src/assets/svg/setting/version-check.svg b/src/assets/svg/setting/version-check.svg new file mode 100644 index 00000000..ea3fba6b --- /dev/null +++ b/src/assets/svg/setting/version-check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/Follow/components/SearchPage.tsx b/src/pages/Follow/components/SearchPage.tsx index f9ac0c93..3865cbfd 100644 --- a/src/pages/Follow/components/SearchPage.tsx +++ b/src/pages/Follow/components/SearchPage.tsx @@ -5,12 +5,18 @@ import FollowList from './FollowList'; export default function SearchPage({ data }: SearchPageInfo) { const auth = useAuth(); const myFriends: FollowerInfo[] = data.filter((follower) => follower.followedType === 'FOLLOWED'); - const newFriends: FollowerInfo[] = data.filter((follower) => follower.followedType === 'NONE').filter((follower) => follower.account !== auth?.account); + let newFriends: FollowerInfo[]; + if (auth && 'account' in auth) { + newFriends = data.filter((follower) => follower.followedType === 'NONE').filter((follower) => follower.account !== auth?.account); + return ( +
+ + +
+ ); + } return ( -
- - -
+
오류입니다.
); } diff --git a/src/pages/MyPage/components/Information/index.tsx b/src/pages/MyPage/components/Information/index.tsx index d69ceab8..0c221efb 100644 --- a/src/pages/MyPage/components/Information/index.tsx +++ b/src/pages/MyPage/components/Information/index.tsx @@ -1,22 +1,22 @@ import defaultImage from 'assets/images/follow/default-image.png'; import option from 'assets/svg/mypage/option.svg'; import { Link } from 'react-router-dom'; -import { User } from 'api/user/entity'; +import { EmailUser } from 'api/user/entity'; import styles from './Information.module.scss'; -type Profile = User & { +type Profile = EmailUser & { profileImage?: { url: string }, }; interface InformationProps { - openModal:(url:string | undefined) => void, + openModal: (url: string | undefined) => void, profile: Profile followerNumber?: number } -export default function Information({ openModal, followerNumber, profile }:InformationProps) { +export default function Information({ openModal, followerNumber, profile }: InformationProps) { return (
diff --git a/src/pages/MyPage/hooks/useMyProfile.ts b/src/pages/MyPage/hooks/useMyProfile.ts index 9cfc2453..2ddd975a 100644 --- a/src/pages/MyPage/hooks/useMyProfile.ts +++ b/src/pages/MyPage/hooks/useMyProfile.ts @@ -1,9 +1,9 @@ import { getFollwers } from 'api/mypage'; import { getMe } from 'api/user'; -import { User } from 'api/user/entity'; +import { EmailUser } from 'api/user/entity'; import { useQuery } from 'react-query'; -type Profile = User & { +type Profile = EmailUser & { profileImage?: { url: string }, @@ -11,8 +11,8 @@ type Profile = User & { const useMyProfile = () => { const { data: profileData, isLoading } = useQuery('profile', getMe); const { data: followers } = useQuery('myFollowers', getFollwers); - const profile:Profile | null = profileData ? profileData.data : null; - const getTotal = () => (profileData ? profileData.data.userCountResponse.reviewCount : 0); + const profile:Profile | null = profileData ? profileData.data as EmailUser : null; + const getTotal = () => (profileData && 'account' in profileData.data ? profileData.data.userCountResponse.reviewCount : 0); const followerNumber = followers?.data.content.length; return { profile, isLoading, getTotal, followerNumber, diff --git a/src/pages/Setting/Mobile/IdChage.module.scss b/src/pages/Setting/Mobile/IdChage.module.scss new file mode 100644 index 00000000..748a43f2 --- /dev/null +++ b/src/pages/Setting/Mobile/IdChage.module.scss @@ -0,0 +1,125 @@ +@use "src/utils/styles/mediaQuery" as media; + +.blindBox { + align-items: center; + justify-content: center; + + &__button { + position: absolute; + background-color: transparent; + border: none; + margin-top: 5px; + margin-left: -35px; + width: 16px; + height: 16px; + cursor: pointer; + } +} + +.layout { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 5vh; + width: 100vw; + min-height: 100vh; +} + +.back { + width: 270px; + padding-bottom: 30px; + padding-top: 16px; + padding-left: 50px; + display: flex; + justify-content: flex-start; + align-items: center; + gap: 20px; + margin-right: 120px; +} + +.page { + display: flex; + flex-direction: column; + + &__quote { + font-size: 18px; + font-weight: 500; + width: 240px; + line-height: 22px; + } + + &__image { + padding-right: 5px; + } + + &__error { + display: flex; + align-items: center; + color: #ff7f23; + height: 50px; + font-size: 12px; + } + + &__caution { + display: flex; + align-items: center; + } +} + +.form { + display: flex; + flex-direction: column; + margin-bottom: 12px; + + &__space { + gap: 80px !important; + } + + &__center { + display: flex; + flex-direction: column; + justify-content: center; + padding-bottom: 23px; + } + + &__label { + padding-left: 10px; + padding-bottom: 5px; + + &--paddingTop { + padding-left: 10px; + padding-bottom: 5px; + padding-top: 5px; + } + } + + &__input { + border: 1px solid #eeeeee; + border-radius: 100px; + width: 220px; + height: 30px; + padding-left: 15px; + padding-right: 15px; + margin-bottom: 10px; + background-color: #eeeeee; + + &--error { + border-radius: 18px; + border: 1px solid #ff7f23; + } + } + + &__submit { + border: none; + border-radius: 100px; + width: 250px; + height: 40px; + color: white; + background-color: #c4c4c4; + + &--active { + background-color: #ff7f23; + cursor: pointer; + } + } +} diff --git a/src/pages/Setting/Mobile/IdChange.tsx b/src/pages/Setting/Mobile/IdChange.tsx new file mode 100644 index 00000000..130d7106 --- /dev/null +++ b/src/pages/Setting/Mobile/IdChange.tsx @@ -0,0 +1,146 @@ +import cn from 'utils/ts/classNames'; +import PreviousButton from 'components/PreviousButton/PreviousButton'; +import { ReactComponent as ShowIcon } from 'assets/svg/auth/pw-show.svg'; +import { ReactComponent as BlindIcon } from 'assets/svg/auth/pw-blind.svg'; +import { useAuth } from 'store/auth'; +import useModifyPassword from 'pages/Setting/hook/useModifyPassword'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import styles from './IdChage.module.scss'; +import MobileCommonModal from './MobileCommonModal'; +import PasswordSuccessModal from '../PC/PasswordSuccessModal'; +import { correctError, currentError, typeError } from '../static/setting'; + +// const PATTERN = /^.*(?=^.{2,16}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$/; // 비밀번호 형식 패턴 + +export default function IdChange(): JSX.Element { + const auth = useAuth(); + const [isCurrentBlind, , , changeCurrentBlind] = useBooleanState(false); + const [isNewBlind, changeNewBlind] = useBooleanState(false); + const [isNewCheckBlind, changeNewCheckBlind] = useBooleanState(false); + const { + current, + newPassword, + check, + handleCheckInput, + handleCurrentInput, + handleNewPasswordInput, + modifyPassword, + isShowError, + message, + isShowModal, + setIsShowError, + } = useModifyPassword(); + return ( +
+
+ + 비밀번호 변경 +
+
+
+
+
+
현재 비밀번호
+ + + + + +
새 비밀번호
+ + + + + +
비밀번호 확인
+ + + + +
+ +
+
+ {isShowError && ( + {message} + )} + {isShowModal && 재설정된 비밀번호로 다시 로그인해주세요. } +
+ ); +} diff --git a/src/pages/Setting/Mobile/MobileCommonModal.module.scss b/src/pages/Setting/Mobile/MobileCommonModal.module.scss new file mode 100644 index 00000000..3bb56d58 --- /dev/null +++ b/src/pages/Setting/Mobile/MobileCommonModal.module.scss @@ -0,0 +1,35 @@ +.container { + position: absolute; + bottom: 8vh; + left: calc((100vw - 90vw) / 2); +} + +.modal { + width: 90vw; + height: 12vh; + background-color: #222222; + opacity: 0.8; + border-radius: 3px; + color: white; + display: flex; + align-items: center; + justify-content: space-around; + font-size: 14px; + padding: 20px 0 20px 20px; + box-sizing: border-box; + + &__content { + width: 70%; + opacity: 1; + } + + &__close { + color: #ff7f23; + opacity: 1; + background-color: transparent; + border-color: transparent; + font-size: 15px; + cursor: pointer; + width: 30%; + } +} diff --git a/src/pages/Setting/Mobile/MobileCommonModal.tsx b/src/pages/Setting/Mobile/MobileCommonModal.tsx new file mode 100644 index 00000000..96df6618 --- /dev/null +++ b/src/pages/Setting/Mobile/MobileCommonModal.tsx @@ -0,0 +1,27 @@ +import { createPortal } from 'react-dom'; +import style from './MobileCommonModal.module.scss'; + +interface Props { + children: React.ReactNode; + setIsShowError: (value: boolean) => void +} + +export default function MobileCommonModal({ children, setIsShowError }: Props) { + const root = document.body; + return createPortal( +
+
+
+
{children}
+ +
+
, + root, + ); +} diff --git a/src/pages/Setting/Mobile/MobileLogoutModal.tsx b/src/pages/Setting/Mobile/MobileLogoutModal.tsx new file mode 100644 index 00000000..88cf8972 --- /dev/null +++ b/src/pages/Setting/Mobile/MobileLogoutModal.tsx @@ -0,0 +1,41 @@ +import { createPortal } from 'react-dom'; +import { Link } from 'react-router-dom'; +import { useClearAuth } from 'store/auth'; +import { ReactComponent as Favicon } from 'assets/svg/common/favicon.svg'; +import style from 'pages/Setting/PC/PasswordSuccessModal.module.scss'; +import cn from 'utils/ts/classNames'; + +interface Props { + children: React.ReactNode; + setIsShowModal: (value: boolean) => void; +} +export default function MobileLogoutModal({ children, setIsShowModal }: Props) { + const root = document.body; + const clearAuth = useClearAuth(); + return createPortal( +
+
+
+ +
+ 로그아웃을 진행하실건가요? +
+
{children}
+
+ + +
+ +
+
, + root, + ); +} diff --git a/src/pages/Setting/UserSetting/Setting.module.scss b/src/pages/Setting/Mobile/Setting.module.scss similarity index 89% rename from src/pages/Setting/UserSetting/Setting.module.scss rename to src/pages/Setting/Mobile/Setting.module.scss index 34c57e0a..21c35346 100644 --- a/src/pages/Setting/UserSetting/Setting.module.scss +++ b/src/pages/Setting/Mobile/Setting.module.scss @@ -26,6 +26,7 @@ $back: white; font-size: 16px; font-weight: bold; font-weight: 500; + padding-right: 30px; } } @@ -42,13 +43,7 @@ $back: white; color: $title; } - &__id { - display: flex; - font-size: 14px; - color: $title; - } - - &__contents { + &__content { display: flex; flex-direction: row; margin: 0 auto 14px 16px; @@ -89,7 +84,7 @@ $back: white; color: $title; } - &__contents { + &__content { display: flex; flex-direction: row; margin: 0 auto 12.5px 16px; @@ -102,6 +97,7 @@ $back: white; display: flex; background-color: $back; border-style: none; + cursor: pointer; } &__app-version { @@ -109,6 +105,16 @@ $back: white; font-size: 14px; color: $title; } + + &__text { + display: flex; + align-items: center; + gap: 10px; + } + + &__version { + cursor: pointer; + } } .link { @@ -125,19 +131,20 @@ $back: white; &__log-out { color: $default; display: flex; - font-size: 15px; + font-size: 16px; font-weight: bold; margin: 0 auto 10px 16px; border: none; background-color: $back; text-decoration: underline; + text-decoration-color: $back; cursor: pointer; } &__delete-account { font-size: 14px; margin: 0 auto 42px 16px; - color: $default; + color: #666666; text-decoration-line: none; } } diff --git a/src/pages/Setting/UserSetting/index.tsx b/src/pages/Setting/Mobile/index.tsx similarity index 59% rename from src/pages/Setting/UserSetting/index.tsx rename to src/pages/Setting/Mobile/index.tsx index 9655b310..32b5d45e 100644 --- a/src/pages/Setting/UserSetting/index.tsx +++ b/src/pages/Setting/Mobile/index.tsx @@ -1,13 +1,17 @@ import { ReactComponent as ArrowRight } from 'assets/svg/setting/arrow-right.svg'; import { ReactComponent as Move } from 'assets/svg/setting/movement.svg'; import { Link } from 'react-router-dom'; -import { useAuth, useClearAuth } from 'store/auth'; import PreviousButton from 'components/PreviousButton/PreviousButton'; +import { ReactComponent as Version } from 'assets/svg/setting/version-check.svg'; import styles from './Setting.module.scss'; +import MobileCommonModal from './MobileCommonModal'; +import useModifyPassword from '../hook/useModifyPassword'; +import MobileLogoutModal from './MobileLogoutModal'; -export default function Setting() { - const auth = useAuth(); - const clearAuth = useClearAuth(); +export default function MobileSetting() { + const { + setIsShowError, isShowError, setIsShowModal, isShowModal, + } = useModifyPassword(); return (
@@ -18,18 +22,7 @@ export default function Setting() {
계정 관리
-
-
아이디 변경
-
-
{auth?.account}
- - - -
-
-
+
비밀번호 변경
-
+
개인정보 이용방침
@@ -48,7 +41,7 @@ export default function Setting() {
서비스
-
+
공지사항
-
+
문의하기
-
-
앱 버전
+
+
+ 앱 버전 + setIsShowError(true)} className={styles.service__version} /> +
현재 1.2.0 / 최신 1.2.0
- -
로그아웃
- +
탈퇴하기
+ {isShowError && ( + + 앱 버전이 달라요. +
+ 더 나은 서비스 경험을 위해 앱스토어에서 +
+ 업데이트를 해주세요. + +
+ )} + {isShowModal && ( + + 쩝쩝박사 로그아웃을 진행하시면 + 로그인 페이지로 돌아갑니다. + + )}
); } diff --git a/src/pages/Setting/PC/LogoutModal.tsx b/src/pages/Setting/PC/LogoutModal.tsx new file mode 100644 index 00000000..5dd0b1e6 --- /dev/null +++ b/src/pages/Setting/PC/LogoutModal.tsx @@ -0,0 +1,28 @@ +import { createPortal } from 'react-dom'; +import { Link } from 'react-router-dom'; +import { useClearAuth } from 'store/auth'; +import { ReactComponent as Favicon } from 'assets/svg/common/favicon.svg'; +import style from './PasswordSuccessModal.module.scss'; + +interface Props { + children: React.ReactNode; +} +export default function LogoutModal({ children }: Props) { + const root = document.body; + const clearAuth = useClearAuth(); + return createPortal( +
+
+
+ + +
+ 로그아웃을 진행하실건가요? +
+
{children}
+ +
+
, + root, + ); +} diff --git a/src/pages/Setting/PC/ManageAccount.tsx b/src/pages/Setting/PC/ManageAccount.tsx new file mode 100644 index 00000000..16cffb21 --- /dev/null +++ b/src/pages/Setting/PC/ManageAccount.tsx @@ -0,0 +1,144 @@ +import { ReactComponent as ShowIcon } from 'assets/svg/auth/pw-show.svg'; +import { ReactComponent as BlindIcon } from 'assets/svg/auth/pw-blind.svg'; +import { useAuth } from 'store/auth'; +import { ReactComponent as ErrorIcon } from 'assets/svg/auth/error.svg'; +import cn from 'utils/ts/classNames'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import { User } from 'api/user/entity'; +import style from './index.module.scss'; +import useModifyPassword from '../hook/useModifyPassword'; +import PasswordSuccessModal from './PasswordSuccessModal'; +import { correctError, currentError, typeError } from '../static/setting'; + +export default function ManageAccount() { + const [isCurrentBlind, , , changeCurrentBlind] = useBooleanState(false); + const [isNewBlind, changeNewBlind] = useBooleanState(false); + const [isNewCheckBlind, changeNewCheckBlind] = useBooleanState(false); + const { + current, + newPassword, + check, + handleCheckInput, + handleCurrentInput, + handleNewPasswordInput, + modifyPassword, + isShowError, + message, + isShowModal, + } = useModifyPassword(); + + const isEmailLogin = (auth: User | null) => { + if (auth && 'account' in auth) { + return auth.account; + } + + return 'SNSLogin'; + }; + const auth = useAuth(); + + return ( +
+
계정
+
+ 이메일 + +
+
+ 아이디 + +
+
비밀번호 변경
+
+ 현재 비밀번호 + + + + +
+
+ 새 비밀번호 + + + + +
+
+ 새 비밀번호 확인 + + + + +
+ {isShowError && ( +
+ + + {message} + +
+ )} + + {isShowModal && 변경된 비밀번호로 로그인 해주세요.} +
+ ); +} diff --git a/src/pages/Setting/PC/PasswordSuccessModal.module.scss b/src/pages/Setting/PC/PasswordSuccessModal.module.scss new file mode 100644 index 00000000..3fbe1795 --- /dev/null +++ b/src/pages/Setting/PC/PasswordSuccessModal.module.scss @@ -0,0 +1,90 @@ +.container { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + width: 100vw; +} + +.overay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: rgb(0 0 0 / 35%); + height: 100vh; + width: 100vw; +} + +.modal { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + background-color: white; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 320px; + height: 200px; + border-radius: 20px; + padding: 15px; + padding-bottom: 0.5px; + + &__container { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + } + + &__title { + font-size: 18px; + font-weight: 700; + color: #ff7f23; + } + + &__content { + text-align: center; + font-size: 12px; + padding-bottom: 10px; + } + + &__button { + border: none; + border-radius: 20px; + background-color: #ff7f23; + text-align: center; + width: 146px; + height: 44px; + color: white; + cursor: pointer; + margin-bottom: 15px; + + &--cancel { + background-color: #c4c4c4; + color: white; + border: none; + border-radius: 20px; + width: 146px; + height: 44px; + margin-bottom: 15px; + } + } + + &__close { + position: absolute; + top: 16px; + right: 22px; + font-size: 15px; + color: #c4c4c4; + background-color: transparent; + border: none; + + &:hover { + cursor: pointer; + } + } +} diff --git a/src/pages/Setting/PC/PasswordSuccessModal.tsx b/src/pages/Setting/PC/PasswordSuccessModal.tsx new file mode 100644 index 00000000..f390c1cd --- /dev/null +++ b/src/pages/Setting/PC/PasswordSuccessModal.tsx @@ -0,0 +1,28 @@ +import { createPortal } from 'react-dom'; +import { Link } from 'react-router-dom'; +import { useClearAuth } from 'store/auth'; +import { ReactComponent as Favicon } from 'assets/svg/common/favicon.svg'; +import style from './PasswordSuccessModal.module.scss'; + +interface Props { + children: React.ReactNode; +} +export default function PasswordSuccessModal({ children }: Props) { + const root = document.body; + const clearAuth = useClearAuth(); + return createPortal( +
+
+
+ + +
+ 비밀번호 변경을 완료했습니다. +
+
{children}
+ +
+
, + root, + ); +} diff --git a/src/pages/Setting/PC/Service.module.scss b/src/pages/Setting/PC/Service.module.scss new file mode 100644 index 00000000..3488bda1 --- /dev/null +++ b/src/pages/Setting/PC/Service.module.scss @@ -0,0 +1,65 @@ +$default: #666666; +$title: #ff7f23; +$back: white; + +.service { + font-weight: 500; + display: flex; + white-space: nowrap; + flex-direction: column; + margin: 60px; + + &__text { + font-size: 18px; + color: $default; + } + + &__container { + display: flex; + flex-direction: column; + left: 796px; + margin-bottom: 50px; + } + + &__content { + display: flex; + flex-direction: row; + margin: 0 auto 12.5px 16px; + justify-content: space-between; + width: 328px; + height: 20px; + } + + &__announcement { + display: flex; + background-color: $back; + border-style: none; + } +} + +.bottom { + display: flex; + flex-direction: column; + margin-top: 120px; + margin-right: 290px; + + &__log-out { + color: $default; + display: flex; + font-size: 18px; + font-weight: bold; + margin: 0 auto 10px 16px; + border: none; + background-color: $back; + cursor: pointer; + text-decoration: underline; + text-decoration-color: $back; + } + + &__delete-account { + font-size: 16px; + margin: 0 auto 42px 16px; + color: $default; + text-decoration-line: underline; + } +} diff --git a/src/pages/Setting/PC/Service.tsx b/src/pages/Setting/PC/Service.tsx new file mode 100644 index 00000000..1b2203e5 --- /dev/null +++ b/src/pages/Setting/PC/Service.tsx @@ -0,0 +1,45 @@ +import { Link } from 'react-router-dom'; +import { ReactComponent as Move } from 'assets/svg/setting/movement.svg'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import styles from './Service.module.scss'; +import LogoutModal from './LogoutModal'; + +export default function Service() { + const [isShowModal, setIsShowModal] = useBooleanState(false); + return ( +
+
+
+
공지사항
+ + + +
+
+
문의하기
+ + + +
+
+
+ + {isShowModal && 로그인 페이지로 돌아갑니다.} + +
탈퇴하기
+ +
+
+ ); +} diff --git a/src/pages/Setting/PC/index.module.scss b/src/pages/Setting/PC/index.module.scss new file mode 100644 index 00000000..dab6b17b --- /dev/null +++ b/src/pages/Setting/PC/index.module.scss @@ -0,0 +1,163 @@ +$title: #ff7f23; + +.formContainer { + padding-right: 100px; + padding-bottom: 40px; + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.selectContainer { + display: flex; + flex-direction: column; + gap: 15px; + align-items: flex-start; +} + +.select { + display: flex; + justify-content: center; + align-items: center; + + &__mark { + width: 8px; + height: 8px; + border-radius: 100%; + border: none; + background-color: $title; + opacity: 0; + + &--appear { + opacity: 1; + } + } + + &__button { + border: none; + background-color: transparent; + flex-direction: column; + align-items: flex-end; + margin-left: 10px; + font-size: 18px; + padding-left: 10px; + color: #c4c4c4; + + &--selected { + color: $title; + } + } +} + +.container { + justify-content: center; + align-items: center; + display: flex; + height: calc(100vh - 80px); +} + +.setting { + position: relative; + bottom: 17vh; + padding-bottom: 90px; + right: 100px; + + &__title { + font-size: 32px; + color: $title; + font-weight: 700; + margin-bottom: 70px; + } +} + +.title { + font-weight: 700; + font-size: 18px; + margin-top: 30px; + margin-bottom: 30px; + margin-right: 283.6px; +} + +.account { + margin-top: 20px; + + &__input { + width: 252px; + height: 30px; + border-radius: 18px; + border: none; + background-color: #eeeeee; + margin-bottom: 20px; + margin-left: 20px; + padding-left: 10px; + } +} + +.passwordContainer { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.password { + width: 252px; + height: 30px; + border-radius: 18px; + border: 1px solid #c4c4c4; + margin-bottom: 20px; + margin-left: 20px; + padding-left: 10px; + + &__error { + border-radius: 18px; + border: 1px solid $title; + } +} + +.button { + top: calc(688px - 80px); + width: 106px; + height: 29px; + border: 1px solid $title; + background-color: white; + border-radius: 20px; + color: $title; + font-weight: 600px; + box-shadow: 2px 3px 12px 1px #0000001a; + margin-top: 7px; + cursor: pointer; +} + +.blindBox { + align-items: center; + justify-content: center; + + &__button { + position: absolute; + background-color: transparent; + border: none; + margin-top: 5px; + margin-left: -35px; + width: 16px; + height: 16px; + cursor: pointer; + } +} + +.errorMessageBox { + display: flex; + justify-content: flex-end; + gap: 5px; +} + +.errorMessage { + color: $title; + font-size: 14px; + text-align: right; +} + +.copyright { + position: absolute; + bottom: 50px; + width: 331.621px; +} diff --git a/src/pages/Setting/PC/index.tsx b/src/pages/Setting/PC/index.tsx new file mode 100644 index 00000000..bbac1c2d --- /dev/null +++ b/src/pages/Setting/PC/index.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import cn from 'utils/ts/classNames'; +import Copyright from 'components/Auth/Copyright'; +import ManageAccount from './ManageAccount'; +import Service from './Service'; +import style from './index.module.scss'; + +export default function PcSetting() { + const [isAccount, setIsAccount] = useState(true); + return ( +
+
+
설정
+
+
+
+ +
+
+
+ +
+
+
+ {isAccount ? : } +
+ +
+
+ ); +} diff --git a/src/pages/Setting/UserSetting/IdChange.tsx b/src/pages/Setting/UserSetting/IdChange.tsx deleted file mode 100644 index 5476df06..00000000 --- a/src/pages/Setting/UserSetting/IdChange.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Link } from 'react-router-dom'; - -export default function IdChange(): JSX.Element { - return ( -
- 설정으로 돌아가기~~ -
idChange화면 입니당~~
-
- ); -} diff --git a/src/pages/Setting/Withdrawal/index.tsx b/src/pages/Setting/Withdrawal/index.tsx index ff22f084..794a6354 100644 --- a/src/pages/Setting/Withdrawal/index.tsx +++ b/src/pages/Setting/Withdrawal/index.tsx @@ -6,17 +6,19 @@ import { useState } from 'react'; import { createPortal } from 'react-dom'; import styles from './Withdrawal.module.scss'; import WithdrawalModal from './components/WithdrawalModal'; +import useWithDrawal from './useWithdrawal'; export default function Withdrawal() { const auth = useAuth(); const [isCheck, setIsCheck] = useState(0); const [modal, open] = useBooleanState(false); + const deleteAccount = useWithDrawal(); const checked = (event: React.ChangeEvent) => { if (event.target.checked) { setIsCheck(isCheck + 1); } else { setIsCheck(isCheck - 1); } }; - const handleSubmit = (event:React.FormEvent) => { + const handleSubmit = (event: React.FormEvent) => { withdrawUser(); event.preventDefault(); }; @@ -73,7 +75,7 @@ export default function Withdrawal() {
  • 추후 같은 계정으로 재가입해도 작성한 내역은 복구되지 않아요.
  • - diff --git a/src/pages/Setting/Withdrawal/useWithdrawal.ts b/src/pages/Setting/Withdrawal/useWithdrawal.ts new file mode 100644 index 00000000..db760418 --- /dev/null +++ b/src/pages/Setting/Withdrawal/useWithdrawal.ts @@ -0,0 +1,10 @@ +import { useMutation } from 'react-query'; +import { withdrawUser } from 'api/user'; + +const useWithDrawal = () => { + const { mutate: deleteAccount } = useMutation('withdrawUser', () => withdrawUser()); + + return deleteAccount; +}; + +export default useWithDrawal; diff --git a/src/pages/Setting/hook/useModifyPassword.ts b/src/pages/Setting/hook/useModifyPassword.ts new file mode 100644 index 00000000..65411163 --- /dev/null +++ b/src/pages/Setting/hook/useModifyPassword.ts @@ -0,0 +1,76 @@ +import { checkPassword, modify } from 'api/user'; +import { useState } from 'react'; +import { + PATTERN, currentError, typeError, correctError, +} from 'pages/Setting/static/setting'; +import useBooleanState from 'utils/hooks/useBooleanState'; +import usePasswordState from './usePasswordState'; + +const useModifyPassword = () => { + const [isShowError, , , ,setIsShowError] = useBooleanState(false); + const [isShowModal, , , ,setIsShowModal] = useBooleanState(false); + const [message, setMessage] = useState(); + const { + current, + handleCurrentInput, + newPassword, + handleNewPasswordInput, + check, + handleCheckInput, + } = usePasswordState(); + + const typeCheck = (pw: string) => { + if (!PATTERN.test(pw)) { + setMessage(typeError); + setIsShowError(true); + return false; + } + return true; + }; + + const passwordCheck = async () => { + try { + typeCheck(current); + await checkPassword({ password: current }); + return true; + } catch (e) { + setMessage(currentError); + setIsShowError(true); + return false; + } + }; + + const passwordCorrectCheck = () => { + if (newPassword !== check) { + setMessage(correctError); + setIsShowError(true); + return false; + } + return true; + }; + + const modifyPassword = async () => { + const nextStep = await passwordCheck(); + if (nextStep && typeCheck(newPassword) && passwordCorrectCheck()) { + await modify({ password: newPassword }); + setIsShowModal(true); + } + }; + + return { + current, + handleCurrentInput, + newPassword, + handleNewPasswordInput, + check, + handleCheckInput, + modifyPassword, + isShowError, + message, + isShowModal, + setIsShowError, + setIsShowModal, + }; +}; + +export default useModifyPassword; diff --git a/src/pages/Setting/hook/usePasswordState.ts b/src/pages/Setting/hook/usePasswordState.ts new file mode 100644 index 00000000..f49509e6 --- /dev/null +++ b/src/pages/Setting/hook/usePasswordState.ts @@ -0,0 +1,28 @@ +import { useState } from 'react'; + +const usePasswordState = () => { + const [current, setCurrent] = useState(''); + const [newPassword, setnewPassword] = useState(''); + const [check, setCheck] = useState(''); + + const handleCurrentInput = (e: React.ChangeEvent) => { + setCurrent(e.target.value); + }; + const handleNewPasswordInput = (e: React.ChangeEvent) => { + setnewPassword(e.target.value); + }; + const handleCheckInput = (e: React.ChangeEvent) => { + setCheck(e.target.value); + }; + + return { + current, + handleCurrentInput, + newPassword, + handleNewPasswordInput, + check, + handleCheckInput, + }; +}; + +export default usePasswordState; diff --git a/src/pages/Setting/index.tsx b/src/pages/Setting/index.tsx new file mode 100644 index 00000000..2bc04fba --- /dev/null +++ b/src/pages/Setting/index.tsx @@ -0,0 +1,12 @@ +import useMediaQuery from 'utils/hooks/useMediaQuery'; +import MobileSetting from './Mobile'; +import PcSetting from './PC'; + +export default function Setting() { + const { isMobile } = useMediaQuery(); + return ( +
    + {isMobile ? : } +
    + ); +} diff --git a/src/pages/Setting/static/setting.ts b/src/pages/Setting/static/setting.ts deleted file mode 100644 index f0ec38af..00000000 --- a/src/pages/Setting/static/setting.ts +++ /dev/null @@ -1,5 +0,0 @@ -const SETTING_TEXT = { - requiredLogin: '로그인이 필요한 서비스입니다.', -}; - -export default SETTING_TEXT; diff --git a/src/pages/Setting/static/setting.tsx b/src/pages/Setting/static/setting.tsx new file mode 100644 index 00000000..9af59040 --- /dev/null +++ b/src/pages/Setting/static/setting.tsx @@ -0,0 +1,27 @@ +const SETTING_TEXT = { + requiredLogin: '로그인이 필요한 서비스입니다.', +}; + +export const PATTERN = /^.*(?=^.{2,16}$)(?=.*\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&+=]).*$/; // 비밀번호 형식 패턴 + +export const currentError = [ + + 현재 비밀번호가 일치하지 않습니다. + , +]; + +export const typeError = [ + + 비밀번호는 문자, 숫자, 특수문자를 포함한 +
    + 8~16 자리로 이루어져야 합니다. +
    , +]; + +export const correctError = [ + + 새 비밀번호가 일치하지 않습니다. + , +]; + +export default SETTING_TEXT; diff --git a/src/store/auth.ts b/src/store/auth.ts index a9e5a1a8..62290c9d 100644 --- a/src/store/auth.ts +++ b/src/store/auth.ts @@ -2,8 +2,9 @@ import { getMe } from 'api/user'; import { refreshAccessToken } from 'api/user/userApiClient'; import { atom, useAtomValue, useSetAtom } from 'jotai'; import { atomWithDefault } from 'jotai/utils'; +import { User } from 'api/user/entity'; -const getAuth = async () => { +const getAuth = async (): Promise< User | null> => { const token = { access: sessionStorage.getItem('accessToken'), refresh: localStorage.getItem('refreshToken'), diff --git a/yarn.lock b/yarn.lock index 30080dde..b5f1492b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1890,16 +1890,6 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express-serve-static-core@^4.17.33": - version "4.17.36" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz#baa9022119bdc05a4adfe740ffc97b5f9360e545" - integrity sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - "@types/express@*", "@types/express@^4.17.13": version "4.17.13" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz" @@ -1910,16 +1900,6 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/express@^4.17.17": - version "4.17.17" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.17.tgz#01d5437f6ef9cfa8668e616e13c2f2ac9a491ae4" - integrity sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/geojson@*": version "7946.0.10" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" @@ -2083,14 +2063,6 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== -"@types/send@*": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" - integrity sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - "@types/serve-index@^1.9.1": version "1.9.1" resolved "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.1.tgz"