diff --git a/README.md b/README.md index 88fbb03d..6f1ddf4e 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,14 @@
-> 서비스 바로가기
( 현재 리팩토링중인 서비스입니다 :) 5월 출시를 기다려주세요! ) + + +## [💗💗💗 서비스 바로가기 💗💗💗](https://www.moodmate.site/main) + +
-

- 팀 노션 -   |   - 백로그 -   |   - figma -

diff --git a/public/sw.js.map b/public/sw.js.map index 4c8a9ed9..8ef3b84e 100644 --- a/public/sw.js.map +++ b/public/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../../../private/var/folders/j1/3yzh53wj1vx2zmdt31jlm_dc0000gn/T/71ce1223d07d16ee8de90f93ad7cc73e/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../private/var/folders/j1/3yzh53wj1vx2zmdt31jlm_dc0000gn/T/1ba5e82cd016c690c8741c074e92b13d/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-routing/registerRoute.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly as workbox_strategies_NetworkOnly} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-strategies/NetworkOnly.mjs';\nimport {clientsClaim as workbox_core_clientsClaim} from '/Users/johyewon/Desktop/Leets/MoodMate-FE/node_modules/workbox-core/clientsClaim.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\nimportScripts(\n \n);\n\n\n\n\n\n\n\nself.skipWaiting();\n\nworkbox_core_clientsClaim();\n\n\n\nworkbox_routing_registerRoute(\"/\", new workbox_strategies_NetworkFirst({ \"cacheName\":\"start-url\", plugins: [{ cacheWillUpdate: async ({ request, response, event, state }) => { if (response && response.type === 'opaqueredirect') { return new Response(response.body, { status: 200, statusText: 'OK', headers: response.headers }) } return response } }] }), 'GET');\nworkbox_routing_registerRoute(/.*/i, new workbox_strategies_NetworkOnly({ \"cacheName\":\"dev\", plugins: [] }), 'GET');\n\n\n\n\n"],"names":["importScripts","self","skipWaiting","workbox_core_clientsClaim","workbox_routing_registerRoute","workbox_strategies_NetworkFirst","plugins","cacheWillUpdate","request","response","event","state","type","Response","body","status","statusText","headers","workbox_strategies_NetworkOnly"],"mappings":"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"} \ No newline at end of file diff --git a/src/app/_atom/userinfo.ts b/src/app/_atom/userinfo.ts index 7a8a14d1..1868102d 100644 --- a/src/app/_atom/userinfo.ts +++ b/src/app/_atom/userinfo.ts @@ -35,3 +35,11 @@ export const editUserInfoState = atom({ preferMood: '', }, }) + +export const editUserNickname = atom({ + key: 'editUserNickname', + default: { + isNicknameEdit: false, + userNickname: '', + }, +}) diff --git a/src/app/_components/common/userinfo/UserNickname.tsx b/src/app/_components/common/userinfo/UserNickname.tsx index 6f818909..dbf4cc63 100644 --- a/src/app/_components/common/userinfo/UserNickname.tsx +++ b/src/app/_components/common/userinfo/UserNickname.tsx @@ -8,6 +8,8 @@ import Input from '../Input' import Loading from '../Loading' import ErrorPage from '@/(route)/error' import { useMyPageQuery } from '@/_hooks/useMypageQuery' +import { useMutation } from '@tanstack/react-query' +import { postCheckNickname } from '@/_service/mypage' interface UserNicknameProps { pageNum: string @@ -17,6 +19,8 @@ interface UserNicknameProps { const UserNickname = ({ pageNum, isEdit }: UserNicknameProps) => { const [editUserInfo, setEditUserInfoState] = useRecoilState(editUserInfoState) const route = useRouter() + const [canUseNickname, setCanUseNickname] = useState(false) + const [canNext, setCanNext] = useState(false) const [nickname, setNickname] = useRecoilState(userInfoState) const userInfo = useRecoilValue(userInfoState) const [inputValue, setInputValue] = useState( @@ -61,6 +65,7 @@ const UserNickname = ({ pageNum, isEdit }: UserNicknameProps) => { const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value.slice(0, INPUT_NICKNAME.MAX) const koeranOnly = /^[ㄱ-ㅎㅏ-ㅣ가-힣]*$/g + setCanUseNickname(false) if (!koeranOnly.test(newValue)) { alert('한글만 입력 가능합니다.') } else { @@ -69,6 +74,14 @@ const UserNickname = ({ pageNum, isEdit }: UserNicknameProps) => { } } + useEffect(() => { + if (canUseNickname && inputValue.length > 0) { + setCanNext(true) + } else { + setCanNext(false) + } + }, [inputValue, canUseNickname]) + const nextRoute = () => { if (inputValue.trim() === '') { alert('닉네임을 입력해주세요.') @@ -87,6 +100,34 @@ const UserNickname = ({ pageNum, isEdit }: UserNicknameProps) => { } } + const postUserDataMutation = useMutation({ + mutationFn: () => postCheckNickname(inputValue.trim()), + onSuccess: (data) => { + if (data.isDuplicate) { + setCanUseNickname(false) + alert('이미 사용중인 닉네임입니다.') + } else { + setCanUseNickname(true) + if (inputValue.length > 0) { + setCanNext(true) + } + alert('사용 가능한 닉네임입니다!') + } + }, + onError: () => { + setCanUseNickname(false) + alert('다시 시도해 주세요.') + }, + }) + + const checkCanUseNickname = () => { + if (inputValue.trim().length !== 0) { + postUserDataMutation.mutate() + } else { + alert('닉네임을 입력해주세요.') + } + } + const inputStyles = { defaultStyles: 'bg-lightgray', activeStyles: 'bg-primary', @@ -125,8 +166,17 @@ const UserNickname = ({ pageNum, isEdit }: UserNicknameProps) => { : inputStyles.defaultStyles }`} /> -
- {NICK_NAME_PAGE.GUIDE} +
+ +
+ {NICK_NAME_PAGE.GUIDE} +
@@ -135,11 +185,9 @@ const UserNickname = ({ pageNum, isEdit }: UserNicknameProps) => { onClick={nextRoute} buttonType="userinfo" className={`relative mb-7 rounded-md text-darkgray ${ - inputValue.length > 0 - ? buttonStyles.activeStyles - : buttonStyles.defaultStyles + canNext ? buttonStyles.activeStyles : buttonStyles.defaultStyles }`} - isActive={inputValue.trim() !== ''} + isActive={canNext} />
) diff --git a/src/app/_components/information/EditNickname.tsx b/src/app/_components/information/EditNickname.tsx new file mode 100644 index 00000000..a4ab7f58 --- /dev/null +++ b/src/app/_components/information/EditNickname.tsx @@ -0,0 +1,133 @@ +'use client' + +import { useEffect, useState } from 'react' +import Input from '../common/Input' +import { INPUT_NICKNAME, NICK_NAME_PAGE } from '@/_constants/info' +import { useRecoilState } from 'recoil' +import { editUserNickname } from '@/_atom/userinfo' +import { useMutation } from '@tanstack/react-query' +import { postCheckNickname } from '@/_service/mypage' +import { putUserNickname } from '@/_service/userinfo' +import Icons from '../common/Icons' +import { exit } from '@/_ui/IconsPath' + +interface EditNicknameProps { + userNickname: string +} + +const EditNickname = ({ userNickname }: EditNicknameProps) => { + const [inputValue, setInputValue] = useState(userNickname) + const [inputCount, setinputCount] = useState(`${userNickname.length}/5`) + const [editUserInfo, setEditUserInfoState] = useRecoilState(editUserNickname) + const [canUseNickname, setCanUseNickname] = useState(false) + + useEffect(() => { + setInputValue(userNickname) + setEditUserInfoState({ ...editUserInfo, userNickname: userNickname }) + }, []) + + const handleInputChange = (e: React.ChangeEvent) => { + const newValue = e.target.value.slice(0, INPUT_NICKNAME.MAX) + const koeranOnly = /^[ㄱ-ㅎㅏ-ㅣ가-힣]*$/g + if (!koeranOnly.test(newValue)) { + alert('한글만 입력 가능합니다.') + } else { + setCanUseNickname(false) + setInputValue(newValue) + setEditUserInfoState({ ...editUserInfo, userNickname: newValue }) + setinputCount(`${newValue.length}/${INPUT_NICKNAME.MAX}`) + } + } + + const inputStyles = { + defaultStyles: 'bg-lightgray', + activeStyles: 'bg-primary', + } + + const postUserDataMutation = useMutation({ + mutationFn: () => postCheckNickname(editUserInfo.userNickname), + onSuccess: (data) => { + if (data.isDuplicate) { + setCanUseNickname(false) + alert('이미 사용중인 닉네임입니다.') + } else { + setCanUseNickname(true) + alert('사용 가능한 닉네임입니다!') + } + }, + onError: () => { + setCanUseNickname(false) + alert('다시 시도해 주세요.') + }, + }) + + const putUserNicknameMutation = useMutation({ + mutationFn: () => putUserNickname(editUserInfo.userNickname), + onSuccess: (data) => { + window.location.reload() + }, + onError: () => alert('다시 시도해 주세요.'), + }) + + const checkCanUseNickname = () => { + postUserDataMutation.mutate() + } + + const changeNickname = () => { + putUserNicknameMutation.mutate() + } + + return ( +
+
+ + + {inputCount} + +
+
0 + ? inputStyles.activeStyles + : inputStyles.defaultStyles + }`} + /> +
+ + +
+ + setEditUserInfoState({ ...editUserInfo, isNicknameEdit: false }) + } + /> +
+
+
+ ) +} + +export default EditNickname diff --git a/src/app/_components/information/Profile.tsx b/src/app/_components/information/Profile.tsx index be5818e5..8f9edca4 100644 --- a/src/app/_components/information/Profile.tsx +++ b/src/app/_components/information/Profile.tsx @@ -1,6 +1,8 @@ import femaleImage from 'public/illustration/female/mypage/myprofile.png' import maleImage from 'public/illustration/male/mypage/myprofile.png' import Image from 'next/image' +import UserNickname from './UserNickname' +import YearDept from './YearDept' interface UserInfoProps { userGender: string @@ -16,17 +18,14 @@ const Profile = ({ }: UserInfoProps) => { const gender = userGender === 'MALE' ? maleImage : femaleImage const age = year?.toString().slice(-2) + return (
-
+
gender -

{userNickname}

-
-
-

{age}년생

-

|

-

{userDepartment}

+
+
) diff --git a/src/app/_components/information/UserNickname.tsx b/src/app/_components/information/UserNickname.tsx new file mode 100644 index 00000000..01fa1adc --- /dev/null +++ b/src/app/_components/information/UserNickname.tsx @@ -0,0 +1,41 @@ +'use cleint' + +import { useRecoilState } from 'recoil' +import EditNickname from './EditNickname' +import { editUserNickname } from '@/_atom/userinfo' +import Icons from '../common/Icons' +import { pencile } from '@/_ui/IconsPath' +import { useEffect } from 'react' + +interface UserNicknameProps { + userNickname: string +} + +const UserNickname = ({ userNickname }: UserNicknameProps) => { + const [editUserInfo, setEditUserInfoState] = useRecoilState(editUserNickname) + useEffect(() => { + setEditUserInfoState({ ...editUserInfo, isNicknameEdit: false }) + }, []) + return ( + <> + {!editUserInfo.isNicknameEdit ? ( +
+

{userNickname}

+
+ + setEditUserInfoState({ ...editUserInfo, isNicknameEdit: true }) + } + /> +
+
+ ) : ( + + )} + + ) +} + +export default UserNickname diff --git a/src/app/_components/information/YearDept.tsx b/src/app/_components/information/YearDept.tsx new file mode 100644 index 00000000..f9ba7eb9 --- /dev/null +++ b/src/app/_components/information/YearDept.tsx @@ -0,0 +1,23 @@ +'use client' + +import { useRecoilValue } from 'recoil' +import { editUserNickname } from '@/_atom/userinfo' + +interface YearDeptProps { + age: string + userDepartment: string +} +const YearDept = ({ age, userDepartment }: YearDeptProps) => { + const userInfo = useRecoilValue(editUserNickname) + return ( + !userInfo.isNicknameEdit && ( +
+

{age}년생

+

|

+

{userDepartment}

+
+ ) + ) +} + +export default YearDept diff --git a/src/app/_components/mypage/containers/MypageThirdBoxContainer.tsx b/src/app/_components/mypage/containers/MypageThirdBoxContainer.tsx index 6b26ef49..3e5d1ef8 100644 --- a/src/app/_components/mypage/containers/MypageThirdBoxContainer.tsx +++ b/src/app/_components/mypage/containers/MypageThirdBoxContainer.tsx @@ -3,22 +3,40 @@ import { logout } from '@/_ui/IconsPath' import { useState } from 'react' import ModalOutside from '@/_components/common/modal/ModalOutside' import ModalContent from '@/_components/common/modal/ModalContent' -import { MY_MODAL } from '@/_constants' +import { MY_MODAL, DELETE_MODAL } from '@/_constants' import ModalPortal from '@/_components/common/modal/ModalPortal' import { useRouter } from 'next/navigation' import Cookies from 'js-cookie' +import { useMutation } from '@tanstack/react-query' +import { deleteUser } from '@/_service/mypage' const MypageThirdBoxContainer = () => { const [openModal, setOpenModal] = useState(false) + const [openDeleteModal, setOpenDeleteModal] = useState(false) const route = useRouter() const onOpenModal = () => { Cookies.remove('accessToken') Cookies.remove('refreshToken') route.push('/login') } - const onCloseModal = () => { - setOpenModal((prev) => !prev) + const onCloseModal = (type: string) => { + if (type === 'delete') { + setOpenDeleteModal((prev) => !prev) + } else if (type === 'logout') { + setOpenModal((prev) => !prev) + } } + const deleteUserProfile = useMutation({ + mutationFn: () => deleteUser(), + onSuccess: () => { + Cookies.remove('accessToken') + Cookies.remove('refreshToken') + route.push('/login') + }, + onError: () => { + alert('다시 시도해 주세요.') + }, + }) return (

로그인

@@ -31,6 +49,15 @@ const MypageThirdBoxContainer = () => { 로그아웃

+
+ +

setOpenDeleteModal(true)} + > + 회원탈퇴 +

+
{openModal && ( { onMyPage subject={MY_MODAL} onConfirm={onOpenModal} - onCancel={onCloseModal} + onCancel={() => onCloseModal('logout')} + /> + + + )} + {openDeleteModal && ( + + setOpenDeleteModal(false)} + className="max-w-md scroll overflow-hidden bg-white w-[260px] h-[400px] px-10 rounded-[25px] shadow-sm py-10" + > + deleteUserProfile.mutate()} + onCancel={() => onCloseModal('delete')} /> diff --git a/src/app/_constants/info.ts b/src/app/_constants/info.ts index de612afd..a15a7b93 100644 --- a/src/app/_constants/info.ts +++ b/src/app/_constants/info.ts @@ -79,7 +79,7 @@ export const DEPARTMENT_LIST = [ export const NICK_NAME_PAGE = { GREETINGS1: '가입을 축하드려요!', GREETINGS2: '어떻게 불러드리면 될까요?', - WARNINGS: '닉네임은 한번 정하면 수정할 수 없어요!', + WARNINGS: '무디가 당신의 이름을 궁금해해요!', INPUTBOX: '닉네임을 입력하세요.', GUIDE: '5글자 이내로 한글만 입력해주세요.', } as const diff --git a/src/app/_constants/main.ts b/src/app/_constants/main.ts index 04c5864a..9cad8be9 100644 --- a/src/app/_constants/main.ts +++ b/src/app/_constants/main.ts @@ -41,3 +41,10 @@ export const MY_MODAL = { CONFIRM: '로그아웃', CANCEL: '취소', } as const + +export const DELETE_MODAL = { + TITLE: '회원탈퇴를 하시겠어요?', + SUB_TITLE: '신중하게 결정해주세요!', + CONFIRM: '회원탈퇴', + CANCEL: '취소', +} diff --git a/src/app/_service/mypage.ts b/src/app/_service/mypage.ts index cb42870d..1bd5d5ee 100644 --- a/src/app/_service/mypage.ts +++ b/src/app/_service/mypage.ts @@ -2,7 +2,27 @@ import api from './axios' export const myPageInfo = async () => { try { - return await api.get('/mypage').then((res) => res.data) + return await api.get('mypage').then((res) => res.data) + } catch (error) { + throw error + } +} + +export const postCheckNickname = async (nickname: string) => { + try { + const requestBody = { + userNickname: nickname, + } + const response = await api.post('mypage/nickname/check', requestBody) + return response.data + } catch (error) { + throw error + } +} + +export const deleteUser = async () => { + try { + return await api.delete('users').then((res) => res.data) } catch (error) { throw error } diff --git a/src/app/_service/userinfo.ts b/src/app/_service/userinfo.ts index f2f5a646..06bc9417 100644 --- a/src/app/_service/userinfo.ts +++ b/src/app/_service/userinfo.ts @@ -39,7 +39,18 @@ export const postEditUserData = async (editUserInfo: EditUserInfoData) => { const putEditUserInfo = async (editUserInfo: EditUserInfoData) => { try { - const response = await api.put('/mypage', editUserInfo) + const response = await api.put('mypage', editUserInfo) + return response.data + } catch (error) { + throw error + } +} + +export const putUserNickname = async (userNickname: string) => { + try { + const response = await api.put('mypage/nickname/change', { + newNickname: userNickname, + }) return response.data } catch (error) { throw error diff --git a/src/app/_ui/IconsPath.tsx b/src/app/_ui/IconsPath.tsx index 81d5f013..ea4c52eb 100644 --- a/src/app/_ui/IconsPath.tsx +++ b/src/app/_ui/IconsPath.tsx @@ -101,3 +101,10 @@ export const pencile = { height: 17, fill: '#333333', } + +export const exit = { + path: 'M15.5459 13.954C15.7572 14.1653 15.876 14.452 15.876 14.7509C15.876 15.0497 15.7572 15.3364 15.5459 15.5477C15.3346 15.7591 15.0479 15.8778 14.749 15.8778C14.4501 15.8778 14.1635 15.7591 13.9521 15.5477L7.99996 9.59367L2.0459 15.5459C1.83455 15.7572 1.54791 15.8759 1.24902 15.8759C0.950136 15.8759 0.663491 15.7572 0.452147 15.5459C0.240802 15.3345 0.12207 15.0479 0.12207 14.749C0.12207 14.4501 0.240802 14.1635 0.452147 13.9521L6.40621 7.99992L0.454022 2.04586C0.242677 1.83451 0.123945 1.54787 0.123945 1.24898C0.123945 0.950097 0.242677 0.663452 0.454022 0.452108C0.665366 0.240763 0.95201 0.122031 1.2509 0.122031C1.54978 0.122031 1.83643 0.240763 2.04777 0.452108L7.99996 6.40617L13.954 0.45117C14.1654 0.239826 14.452 0.121094 14.7509 0.121094C15.0498 0.121094 15.3364 0.239826 15.5478 0.45117C15.7591 0.662514 15.8778 0.949159 15.8778 1.24804C15.8778 1.54693 15.7591 1.83358 15.5478 2.04492L9.59371 7.99992L15.5459 13.954Z', + width: 16, + height: 16, + fill: '#333333', +}