diff --git a/apis/hooks/mypage.ts b/apis/hooks/mypage.ts new file mode 100644 index 0000000..43bee93 --- /dev/null +++ b/apis/hooks/mypage.ts @@ -0,0 +1,103 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { + getMyInfo, + patchLogout, + patchNicknameChange, + patchPasswordChange, + patchQuitAccount, + postNicknameCheck, +} from "../mypage"; +import { useRouter } from "next/router"; +import { InputError } from "@/pages/mypage/password"; + +function useGetMyInfo() { + const { data } = useQuery({ + queryKey: ["getMyInfo"], + queryFn: getMyInfo, + }); + + return { data }; +} + +function usePatchLogout() { + const router = useRouter(); + const { mutate } = useMutation({ + mutationKey: ["patchLogout"], + mutationFn: patchLogout, + onSuccess: () => { + localStorage.removeItem("access_token"); + router.push("/main"); + }, + onError: () => router.push("/404"), + }); + + return { mutate }; +} + +function usePatchQuitAccount( + setPwError: React.Dispatch>, +) { + const router = useRouter(); + const { mutate } = useMutation({ + mutationKey: ["patchQuitAccount"], + mutationFn: (password: string) => patchQuitAccount(password), + onSuccess: () => { + localStorage.removeItem("access_token"); + router.push("/main"); + }, + onError: () => + setPwError({ status: true, text: "비밀번호가 올바르지 않습니다." }), + }); + + return { mutate }; +} + +function usePatchPasswordChange() { + const router = useRouter(); + const { mutate } = useMutation({ + mutationKey: ["patchPasswordChange"], + mutationFn: (body: { password: string; newPassword: string }) => + patchPasswordChange(body), + onSuccess: () => router.push("/mypage/password/success"), + onError: () => router.push("/404"), + }); + + return { mutate }; +} + +function usePatchNicknameChange(nickname: string) { + const router = useRouter(); + const { mutate } = useMutation({ + mutationKey: ["postNicknameCheck", nickname], + mutationFn: () => patchNicknameChange(nickname), + onSuccess: () => router.push("/mypage/nickname/success"), + onError: () => router.push("/404"), + }); + + return { mutate }; +} + +function usePostNicknameCheck( + nickname: string, + setNameError: React.Dispatch>, +) { + const { mutate } = useMutation({ + mutationKey: ["postNicknameCheck", nickname], + mutationFn: () => postNicknameCheck(nickname), + onSuccess: () => setNameError({ status: false, text: "" }), + onError: () => { + setNameError({ status: true, text: "이미 사용 중인 닉네임입니다." }); + }, + }); + + return { mutate }; +} + +export { + useGetMyInfo, + usePatchLogout, + usePatchQuitAccount, + usePatchPasswordChange, + usePatchNicknameChange, + usePostNicknameCheck, +}; diff --git a/apis/mypage.ts b/apis/mypage.ts new file mode 100644 index 0000000..c8bb74e --- /dev/null +++ b/apis/mypage.ts @@ -0,0 +1,71 @@ +import client, { ResponseBody } from "./client"; + +interface GetMyInfoResponse extends ResponseBody { + result: { + nickname: string; + level: number; + loginId: string; + isAdmin: boolean; + }; +} + +interface PatchResponse { + isSuccess: boolean; + message: string; +} + +interface QuitAccountResponseBody { + timestamp: string; + status: number; + error: string; + code: string; + message: string; +} + +async function getMyInfo(): Promise { + const { data } = await client.get(`/users/myPage`); + return data; +} + +async function patchLogout(): Promise { + const { data } = await client.patch(`/users/logout`); + return data; +} + +async function patchQuitAccount( + password: string, +): Promise { + const { data } = await client.patch(`/users/signout`, { password }); + return data; +} + +async function patchPasswordChange(body: { + password: string; + newPassword: string; +}): Promise { + const { data } = await client.patch(`/users/editPassword`, body); + return data; +} + +async function patchNicknameChange( + nickname: string, +): Promise { + const { data } = await client.patch(`/users/editNickname`, { nickname }); + return data; +} + +async function postNicknameCheck( + nickname: string, +): Promise { + const { data } = await client.post(`/users/nickname`, { nickname }); + return data; +} + +export { + getMyInfo, + patchLogout, + patchQuitAccount, + patchPasswordChange, + patchNicknameChange, + postNicknameCheck, +}; diff --git a/components/HeadFunction.tsx b/components/HeadFunction.tsx index 269cff2..30c5a3f 100644 --- a/components/HeadFunction.tsx +++ b/components/HeadFunction.tsx @@ -2,6 +2,7 @@ import { useRouter } from "next/router"; import FlexBox from "./Flexbox"; import Image from "next/image"; import Head from "next/head"; +import LeftArrowIcon from "@/public/svgs/LeftArrow.svg"; interface Props { leftIcon?: boolean; @@ -26,7 +27,7 @@ export default function HeadFunction({ className="w-5 h-5 shrink-0 items-center align-center" onClick={router.back} > - +
{title}
diff --git a/components/NavBar.tsx b/components/NavBar.tsx index ed7d0c8..5131c48 100644 --- a/components/NavBar.tsx +++ b/components/NavBar.tsx @@ -4,6 +4,7 @@ import MypageIcon from "@/public/svgs/MypageIcon.svg"; import { useRouter } from "next/router"; import NavBarItem from "./NavBarItem"; import { useEffect } from "react"; +import FlexBox from "./Flexbox"; const NavBar = () => { const router = useRouter(); @@ -18,40 +19,43 @@ const NavBar = () => { }, []); return ( -
-
- handleNavigate("/")} - iconType="home" - > - - - handleNavigate("/calendar")} - iconType="calendar" - > - - - handleNavigate("/mypage")} - iconType="mypage" - > - - + <> +
+
+ + handleNavigate("/")} + iconType="home" + > + + + handleNavigate("/calendar")} + iconType="calendar" + > + + + handleNavigate("/mypage")} + iconType="mypage" + > + + +
-
+ ); }; diff --git a/components/mypage/NicknameInput.tsx b/components/mypage/NicknameInput.tsx new file mode 100644 index 0000000..a3ef322 --- /dev/null +++ b/components/mypage/NicknameInput.tsx @@ -0,0 +1,52 @@ +import { ChangeEvent, HTMLInputTypeAttribute, useState } from "react"; + +export interface TextInputProps { + value: string; + setValue: React.Dispatch>; + isError: boolean; + errorText?: string; + isSuccess: boolean; + placeholder?: string; +} + +export default function NicknameInput({ + value, + setValue, + isError, + errorText, + isSuccess, + placeholder = "", +}: TextInputProps) { + const onChangeText = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + const borderStyle = () => { + if (isSuccess) return "border-green-600"; + else if (isError) return "border-red-500"; + return "border-transparent"; + }; + + return ( +
+
+ onChangeText(event)} + placeholder={placeholder} + /> +
+ {!isSuccess && isError && ( +
{errorText}
+ )} + {isSuccess && ( +
+ 사용 가능한 닉네임입니다. +
+ )} +
+ ); +} diff --git a/components/mypage/profile.tsx b/components/mypage/profile.tsx index 432c3b8..9b1a25f 100644 --- a/components/mypage/profile.tsx +++ b/components/mypage/profile.tsx @@ -1,9 +1,45 @@ import Image from "next/image"; import FlexBox from "../Flexbox"; import { useRouter } from "next/router"; +import RightArrowIcon from "@/public/svgs/RightArrow.svg"; +import Level1Icon from "@/public/svgs/badges/1.svg"; +import Level2Icon from "@/public/svgs/badges/2.svg"; +import Level3Icon from "@/public/svgs/badges/3.svg"; +import Level4Icon from "@/public/svgs/badges/4.svg"; +import Level5Icon from "@/public/svgs/badges/5.svg"; +import Level6Icon from "@/public/svgs/badges/6.svg"; -export default function Profile() { +interface ProfileProps { + nickname: string; + level: number; +} + +export default function Profile({ nickname, level }: ProfileProps) { const router = useRouter(); + + const returnBadge = () => { + switch (level) { + case 1: + return ; + break; + case 2: + return ; + break; + case 3: + return ; + break; + case 4: + return ; + break; + case 5: + return ; + break; + case 6: + default: + return ; + break; + } + }; return (
-
서울복지관 관리자
- +
{nickname}
+ {returnBadge()} - + ); } diff --git a/pages/mypage/admin.tsx b/pages/mypage/admin.tsx index e5d23ff..c417d37 100644 --- a/pages/mypage/admin.tsx +++ b/pages/mypage/admin.tsx @@ -7,10 +7,10 @@ const CertifyAdmin: NextPage = () => { return (
- +
wecare@gmail.com - 으로 괸련 서류를 보내주시면 + 으로 관련 서류를 보내주시면
2-3일 내에 인증이 완료됩니다.
diff --git a/pages/mypage/index.tsx b/pages/mypage/index.tsx index 38b6f5b..299810a 100644 --- a/pages/mypage/index.tsx +++ b/pages/mypage/index.tsx @@ -1,3 +1,4 @@ +import { useGetMyInfo, usePatchLogout } from "@/apis/hooks/mypage"; import Divider from "@/components/Divider"; import FlexBox from "@/components/Flexbox"; import HeadFunction from "@/components/HeadFunction"; @@ -5,48 +6,69 @@ import NavBar from "@/components/NavBar"; import Profile from "@/components/mypage/profile"; import { NextPage } from "next"; import { useRouter } from "next/router"; - -const infoList = [ - { - name: "일반설정", - path: "/mypage/setting", - }, - { - name: "관리자 인증", - path: "/mypage/admin", - }, - { - name: "개인정보 처리방침", - path: "/mypage/term", - }, - { - name: "서비스 이용약관", - path: "/mypage/service", - }, - { - name: "로그아웃", - path: "/", - }, -]; +import { ToastContainer, Zoom, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; const MyPage: NextPage = () => { const router = useRouter(); + const { data } = useGetMyInfo(); + const { mutate } = usePatchLogout(); + + const notify = () => { + toast.info("이미 관리자 인증을 완료하셨습니다.", { + position: "bottom-center", + }); + }; + + const certifyAdmin = () => { + if (data.result.isAdmin) notify(); + else router.push("/mypage/admin"); + }; + + const logout = () => { + mutate(); + }; + return (
- + - {infoList.map((info) => ( -
router.push(info.path)} - > - {info.name} -
- ))} +
router.push("/mypage/setting")} + > + 일반 설정 +
+
+ 관리자 인증 +
+
router.push("/mypage/term")} + > + 개인정보 처리방침 +
+
router.push("/mypage/service")} + > + 서비스 이용약관 +
+
+ 로그아웃 +
+
); }; diff --git a/pages/mypage/nickname/index.tsx b/pages/mypage/nickname/index.tsx new file mode 100644 index 0000000..774d1ee --- /dev/null +++ b/pages/mypage/nickname/index.tsx @@ -0,0 +1,98 @@ +import FlexBox from "@/components/Flexbox"; +import HeadFunction from "@/components/HeadFunction"; +import TextInput from "@/components/Input"; +import { NextPage } from "next"; +import { useState } from "react"; +import { InputError } from "../password"; +import { + usePatchNicknameChange, + usePostNicknameCheck, +} from "@/apis/hooks/mypage"; +import Button from "@/components/Button"; +import NicknameInput from "@/components/mypage/NicknameInput"; + +const ChangeNickName: NextPage = () => { + const [newName, setNewname] = useState(""); + const [tempName, setTempName] = useState(""); // onClickCheckBtn를 가장 마지막 실행했을 때의 닉네임 + const [nameError, setNameError] = useState({ + status: false, + text: "", + }); + + const { mutate: nameCheckMutate } = usePostNicknameCheck( + newName, + setNameError, + ); + const { mutate: nameChangeMutate } = usePatchNicknameChange(newName); + + const checkNicknameValidity = () => { + setTempName(newName); + if (newName.length > 8) { + setNameError({ + status: true, + text: "닉네임 최대 길이는 8자입니다.", + }); + return false; + } + + const regex = /^[가-힣a-zA-Z0-9\s]*$/; + if (!regex.test(newName)) { + setNameError({ + status: true, + text: "닉네임은 영어, 한글, 숫자로만 구성할 수 있습니다.", + }); + return false; + } + + return true; + }; + + const onClickCheckBtn = () => { + if (checkNicknameValidity()) nameCheckMutate(); + }; + + const onClickChangeBtn = () => { + nameChangeMutate(); + }; + + return ( +
+ +
+
새 닉네임
+ + + + +
+
+ ); +}; + +export default ChangeNickName; diff --git a/pages/mypage/nickname/success.tsx b/pages/mypage/nickname/success.tsx new file mode 100644 index 0000000..efe84e4 --- /dev/null +++ b/pages/mypage/nickname/success.tsx @@ -0,0 +1,44 @@ +import { NextPage } from "next"; +import lottie from "lottie-web"; +import { useEffect, useRef } from "react"; +import FlexBox from "@/components/Flexbox"; +import Button from "@/components/Button"; +import { useRouter } from "next/router"; + +const NicknameChangeSuccess: NextPage = () => { + const router = useRouter(); + const lottieRef = useRef(); + + useEffect(() => { + lottie.loadAnimation({ + container: lottieRef.current, + renderer: "svg", + loop: false, + autoplay: true, + animationData: require("@/public/lotties/Check.json"), + }); + }); + + return ( + +
+
+ 닉네임이 성공적으로 변경되었습니다! +
+
+
+ + ); +}; + +export default NicknameChangeSuccess; diff --git a/pages/mypage/password/index.tsx b/pages/mypage/password/index.tsx index 67ba9cc..d41df57 100644 --- a/pages/mypage/password/index.tsx +++ b/pages/mypage/password/index.tsx @@ -1,3 +1,5 @@ +import { usePatchPasswordChange } from "@/apis/hooks/mypage"; +import Button from "@/components/Button"; import FlexBox from "@/components/Flexbox"; import HeadFunction from "@/components/HeadFunction"; import TextInput from "@/components/Input"; @@ -12,6 +14,7 @@ export interface InputError { const Password: NextPage = () => { const router = useRouter(); + const { mutate } = usePatchPasswordChange(); const [password, setPassword] = useState(""); const [newPw, setNewPw] = useState(""); @@ -89,6 +92,10 @@ const Password: NextPage = () => { newPw2Error.status, ]); + const changePassword = () => { + mutate({ password, newPassword: newPw }); + }; + return (
@@ -114,17 +121,16 @@ const Password: NextPage = () => { isError={newPw2Error.status} errorText={newPw2Error.text} /> - + }`} + onClick={changePassword} + text="새 비밀번호 저장" + />
); diff --git a/pages/mypage/profile.tsx b/pages/mypage/profile.tsx index 8791e8a..9631acd 100644 --- a/pages/mypage/profile.tsx +++ b/pages/mypage/profile.tsx @@ -1,3 +1,4 @@ +import { useGetMyInfo } from "@/apis/hooks/mypage"; import Divider from "@/components/Divider"; import FlexBox from "@/components/Flexbox"; import HeadFunction from "@/components/HeadFunction"; @@ -6,9 +7,7 @@ import { useRouter } from "next/router"; const Profile: NextPage = () => { const router = useRouter(); - const navigateToPasswordChange = () => { - router.push("/mypage/password"); - }; + const { data } = useGetMyInfo(); return ( @@ -18,22 +17,32 @@ const Profile: NextPage = () => {
닉네임
-
위케어 매니저
+
{data?.result.nickname}
-
이메일 주소
-
email@wecare.com
+
아이디
+
{data?.result.loginId}
router.push("/mypage/nickname")} > - 로그아웃 + 닉네임 변경 +
+
router.push("/mypage/password")} + > + 비밀번호 변경 +
+
router.push("/mypage/quit")} + > + 회원 탈퇴하기
-
비밀번호 수정
-
회원 탈퇴하기
); diff --git a/pages/mypage/quit.tsx b/pages/mypage/quit.tsx new file mode 100644 index 0000000..2c1cb72 --- /dev/null +++ b/pages/mypage/quit.tsx @@ -0,0 +1,57 @@ +import HeadFunction from "@/components/HeadFunction"; +import TextInput from "@/components/Input"; +import { NextPage } from "next"; +import { useEffect, useState } from "react"; +import { InputError } from "./password"; +import Button from "@/components/Button"; +import FlexBox from "@/components/Flexbox"; +import { usePatchQuitAccount } from "@/apis/hooks/mypage"; + +const QuitAccount: NextPage = () => { + const [password, setPassword] = useState(""); + const [pwError, setPwError] = useState({ + status: false, + text: "", + }); + + const { mutate } = usePatchQuitAccount(setPwError); + + const onClickQuitBtn = () => { + mutate(password); + }; + + useEffect(() => { + password.length === 0 && + setPwError({ + status: false, + text: "", + }); + }, [password]); + + return ( +
+ + + +
+ ); +}; + +export default QuitAccount; diff --git a/public/svgs/LeftArrow.svg b/public/svgs/LeftArrow.svg index d12bc3d..6be9dbb 100644 --- a/public/svgs/LeftArrow.svg +++ b/public/svgs/LeftArrow.svg @@ -1,3 +1,3 @@ - + diff --git a/public/svgs/badges/1.svg b/public/svgs/badges/1.svg new file mode 100644 index 0000000..09c0c20 --- /dev/null +++ b/public/svgs/badges/1.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/svgs/badges/2.svg b/public/svgs/badges/2.svg new file mode 100644 index 0000000..532ac39 --- /dev/null +++ b/public/svgs/badges/2.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/svgs/badges/3.svg b/public/svgs/badges/3.svg index 1fc30ee..fa4c526 100644 --- a/public/svgs/badges/3.svg +++ b/public/svgs/badges/3.svg @@ -1,9 +1,9 @@ - - + + - + diff --git a/public/svgs/badges/4.svg b/public/svgs/badges/4.svg new file mode 100644 index 0000000..b79ced1 --- /dev/null +++ b/public/svgs/badges/4.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/svgs/badges/5.svg b/public/svgs/badges/5.svg new file mode 100644 index 0000000..4f86280 --- /dev/null +++ b/public/svgs/badges/5.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/svgs/badges/6.svg b/public/svgs/badges/6.svg new file mode 100644 index 0000000..7b1c33f --- /dev/null +++ b/public/svgs/badges/6.svg @@ -0,0 +1,3 @@ + + +