diff --git a/web/package-lock.json b/web/package-lock.json
index 680eb4e..04ef3b1 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -9,7 +9,6 @@
"version": "0.0.0",
"dependencies": {
"@hookform/resolvers": "^3.9.0",
- "@lukemorales/query-key-factory": "^1.3.4",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@reduxjs/toolkit": "^2.2.6",
@@ -3008,19 +3007,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@lukemorales/query-key-factory": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/@lukemorales/query-key-factory/-/query-key-factory-1.3.4.tgz",
- "integrity": "sha512-A3frRDdkmaNNQi6mxIshsDk4chRXWoXa05US8fBo4kci/H+lVmujS6QrwQLLGIkNIRFGjMqp2uKjC4XsLdydRw==",
- "license": "MIT",
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "@tanstack/query-core": ">= 4.0.0",
- "@tanstack/react-query": ">= 4.0.0"
- }
- },
"node_modules/@mdx-js/react": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz",
diff --git a/web/package.json b/web/package.json
index d78e5ce..e36e682 100644
--- a/web/package.json
+++ b/web/package.json
@@ -19,7 +19,6 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
- "@lukemorales/query-key-factory": "^1.3.4",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@reduxjs/toolkit": "^2.2.6",
diff --git a/web/src/App.tsx b/web/src/App.tsx
index c5c2fe4..8f41f61 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -30,6 +30,15 @@ const router = createBrowserRouter([
};
},
},
+ {
+ path: 'mypage',
+ lazy: async () => {
+ const { default: MyPage } = await import('@/pages/my-page');
+ return {
+ Component: MyPage,
+ };
+ },
+ },
{
path: 'myevent',
lazy: async () => {
@@ -86,7 +95,7 @@ export default function App() {
-
+
diff --git a/web/src/assets/step1.png b/web/src/assets/step1.png
deleted file mode 100644
index a996ee5..0000000
Binary files a/web/src/assets/step1.png and /dev/null differ
diff --git a/web/src/assets/step2.png b/web/src/assets/step2.png
deleted file mode 100644
index e766aa7..0000000
Binary files a/web/src/assets/step2.png and /dev/null differ
diff --git a/web/src/assets/step3.png b/web/src/assets/step3.png
deleted file mode 100644
index cd4a36a..0000000
Binary files a/web/src/assets/step3.png and /dev/null differ
diff --git a/web/src/components/auth/sign-in.tsx b/web/src/components/auth/sign-in.tsx
index 466f002..86dd9d9 100644
--- a/web/src/components/auth/sign-in.tsx
+++ b/web/src/components/auth/sign-in.tsx
@@ -78,9 +78,9 @@ export const SignIn = () => {
-
비밀번호 찾기
+
아이디 찾기
-
아이디 찾기
+
비밀번호 찾기
회원가입
diff --git a/web/src/components/auth/sign-up1.tsx b/web/src/components/auth/sign-up1.tsx
index b1ee465..2b562d7 100644
--- a/web/src/components/auth/sign-up1.tsx
+++ b/web/src/components/auth/sign-up1.tsx
@@ -1,16 +1,16 @@
+import { AppDispatch, RootState } from '@/store';
+import { toggleCheck } from '@/store/signup/terms';
+import { body1Style, head1Style } from '@/styles/global-styles';
+import { COLORS } from '@/theme';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import { Subtitle } from '../common/Subtitle';
-import { body1Style, head1Style } from '@/styles/global-styles';
-import { COLORS } from '@/theme';
-import { toggleCheck } from '@/store/signup/terms';
-import { AppDispatch, RootState } from '@/store';
+import { useNavigate } from 'react-router-dom';
import checkRound from '../../assets/checkRound.png';
-import uncheckRound from '../../assets/uncheckRound.png';
import rightArrow from '../../assets/rigth.png';
-import { useNavigate } from 'react-router-dom';
+import uncheckRound from '../../assets/uncheckRound.png';
export const SignUp1: React.FC = () => {
const dispatch = useDispatch
();
@@ -98,7 +98,7 @@ export const SignUp1: React.FC = () => {
-
+
다음으로
@@ -183,17 +183,17 @@ const Total = styled.div`
`;
interface NextButtonProps {
- enabled: boolean;
+ $enabled: boolean;
}
const NextButton = styled.button`
width: 100%;
padding: 10px;
- background-color: ${props => (props.enabled ? COLORS.Main : COLORS.Gray3)};
+ background-color: ${props => (props.$enabled ? COLORS.Main : COLORS.Gray3)};
color: #fff;
border: none;
border-radius: 10px;
- cursor: ${props => (props.enabled ? 'pointer' : 'not-allowed')};
+ cursor: ${props => (props.$enabled ? 'pointer' : 'not-allowed')};
font-size: 16px;
font-weight: bold;
margin-top: 200px;
diff --git a/web/src/components/auth/sign-up2.tsx b/web/src/components/auth/sign-up2.tsx
index af4ace2..cf23294 100644
--- a/web/src/components/auth/sign-up2.tsx
+++ b/web/src/components/auth/sign-up2.tsx
@@ -20,11 +20,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useDispatch } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import styled from 'styled-components';
import { Subtitle } from '../common/Subtitle';
import { Label } from '../common/label';
-import { useNavigate } from 'react-router-dom';
export const SignUp2 = () => {
const dispatch = useDispatch();
@@ -171,7 +171,7 @@ export const SignUp2 = () => {
-
+
다음으로
@@ -251,17 +251,17 @@ const Confirm1 = styled.button`
`;
interface NextButtonProps {
- enabled: boolean;
+ $enabled: boolean;
}
const NextButton = styled.button`
width: 100%;
padding: 10px;
- background-color: ${props => (props.enabled ? COLORS.Main : COLORS.Gray3)};
+ background-color: ${props => (props.$enabled ? COLORS.Main : COLORS.Gray3)};
color: #fff;
border: none;
border-radius: 10px;
- cursor: ${props => (props.enabled ? 'pointer' : 'not-allowed')};
+ cursor: ${props => (props.$enabled ? 'pointer' : 'not-allowed')};
font-size: 16px;
font-weight: bold;
margin-top: 80px;
diff --git a/web/src/components/common/header/header.styles.ts b/web/src/components/common/header/header.styles.ts
index 6879145..b23d9dd 100644
--- a/web/src/components/common/header/header.styles.ts
+++ b/web/src/components/common/header/header.styles.ts
@@ -6,6 +6,7 @@ export const HeaderWrapper = styled.header`
padding: 0 1.25rem;
display: flex;
align-items: center;
+ justify-content: space-between;
h1 {
font-weight: bold;
diff --git a/web/src/components/common/header/index.tsx b/web/src/components/common/header/index.tsx
index bb13b31..c88c2b6 100644
--- a/web/src/components/common/header/index.tsx
+++ b/web/src/components/common/header/index.tsx
@@ -1,8 +1,37 @@
-import { ComponentProps } from 'react';
+import { ComponentProps, useEffect, useState } from 'react';
import { HeaderWrapper } from './header.styles';
+import styled from 'styled-components';
+import btnMypage from '@/assets/btn_mypage.png';
+import { useNavigate } from 'react-router-dom';
type HeaderProps = ComponentProps<'header'>;
export const Header = ({ children }: HeaderProps) => {
- return {children};
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const token = localStorage.getItem('token');
+ if (token) {
+ setIsLoggedIn(true);
+ }
+ }, []);
+
+ const moveMyPage = () => {
+ navigate('/mypage');
+ };
+
+ return (
+
+ {children}
+ {isLoggedIn && (
+
+ )}
+
+ );
};
+
+const MypageIcon = styled.img`
+ width: 38px;
+ cursor: pointer;
+`;
diff --git a/web/src/components/mypage/DeleteModal.tsx b/web/src/components/mypage/DeleteModal.tsx
new file mode 100644
index 0000000..592a52f
--- /dev/null
+++ b/web/src/components/mypage/DeleteModal.tsx
@@ -0,0 +1,15 @@
+import { useDeleteUser } from '@/services/queries/user.mutation';
+
+export const DeleteModal = () => {
+ const { deleteUserAccount, isDeleting } = useDeleteUser();
+
+ const handleDelete = () => {
+ deleteUserAccount().then(() => onClose());
+ };
+
+ return (
+ <>
+ 탈퇴
+ >
+ );
+};
diff --git a/web/src/components/mypage/user-info.styles.ts b/web/src/components/mypage/user-info.styles.ts
new file mode 100644
index 0000000..bdb98cc
--- /dev/null
+++ b/web/src/components/mypage/user-info.styles.ts
@@ -0,0 +1,28 @@
+import { body1Style } from '@/styles/global-styles';
+import { COLORS } from '@/theme';
+import styled from 'styled-components';
+
+export const Information = styled.div`
+ ${body1Style}
+ color: ${COLORS.Gray1};
+ border-bottom: 1px solid ${COLORS.Gray5};
+ padding: 30px 0px 30px 0px;
+`;
+
+export const BasicInfo = styled.div`
+ margin-bottom: 20px;
+`;
+
+export const InfoContainer = styled.div`
+ display: flex;
+ gap: 27px;
+`;
+export const Info = styled.p`
+ ${body1Style}
+ color: ${COLORS.Gray2};
+`;
+
+export const Id = styled.a`
+ color: ${COLORS.Gray2};
+ text-decoration: none;
+`;
diff --git a/web/src/components/mypage/user-info.tsx b/web/src/components/mypage/user-info.tsx
new file mode 100644
index 0000000..7c97570
--- /dev/null
+++ b/web/src/components/mypage/user-info.tsx
@@ -0,0 +1,28 @@
+import { UserInfoType } from '@/types/user';
+import {
+ BasicInfo,
+ Id,
+ Info,
+ InfoContainer,
+ Information,
+} from './user-info.styles';
+
+export const UserInfo = ({ user }: { user: UserInfoType }) => {
+ return (
+
+ 기본 정보
+
+ 아이디
+ {user.userId}
+
+
+ 휴대폰
+ {user.phoneNumber}
+
+
+ 이메일
+ {user.email}
+
+
+ );
+};
diff --git a/web/src/lib/query-keys.ts b/web/src/lib/query-keys.ts
index e89648d..6b36e3f 100644
--- a/web/src/lib/query-keys.ts
+++ b/web/src/lib/query-keys.ts
@@ -1,7 +1,7 @@
-import { createQueryKeyStore } from '@lukemorales/query-key-factory';
+const USER = 'user';
-export const queries = createQueryKeyStore({
+export const queries = {
user: {
- all: null,
+ DEFAULT: [USER],
},
-});
+};
diff --git a/web/src/lib/schema/auth.schema.ts b/web/src/lib/schema/auth.schema.ts
index 115f23f..a418079 100644
--- a/web/src/lib/schema/auth.schema.ts
+++ b/web/src/lib/schema/auth.schema.ts
@@ -61,3 +61,11 @@ export const signinSchema = z.object({
export type SignupOneStepType = z.infer;
export type SignupTwoStepType = z.infer;
export type SigninType = z.infer;
+export type SignupPayload = {
+ userId: string;
+ name: string;
+ password: string;
+ email: string;
+ phone: string;
+ verificationCode: string;
+};
diff --git a/web/src/pages/my-page.tsx b/web/src/pages/my-page.tsx
index cda6860..dc0cd1d 100644
--- a/web/src/pages/my-page.tsx
+++ b/web/src/pages/my-page.tsx
@@ -1,40 +1,54 @@
+import backImg from '@/assets/btn_back_black.png';
import { Subtitle } from '@/components/common/Subtitle';
+import { UserInfo } from '@/components/mypage/user-info';
+import { useDeleteUser } from '@/services/queries/user.mutation';
+import { useUserInfo } from '@/services/queries/user.queries';
import { body1Style, head2Style } from '@/styles/global-styles';
import { COLORS } from '@/theme';
+import { JSX } from 'react';
import styled from 'styled-components';
-import btn_mypage from '../assets/btn_mypage.png';
-
const MyPage = () => {
+ const { userInfo } = useUserInfo();
+
+ const { deleteUserAccount, isDeleting } = useDeleteUser();
+
+ const handleDelete = () => {
+ if (window.confirm('정말로 탈퇴하시겠습니까?')) {
+ deleteUserAccount();
+ }
+ };
+
+ // TODO: Loading 스켈레톤 추가
+ let content: JSX.Element;
+ if (userInfo.isPending) {
+ content = Loading,,,
;
+ } else if (userInfo.isFetching) {
+ content = Loading,,,
;
+ } else if (userInfo.isError) {
+ content = Error,,,
;
+ } else {
+ content = ;
+ }
return (
-
+
- 이현우
+ {userInfo.data && userInfo.data.name}
인플루언서
-
- 기본 정보
-
- 아이디
- Suppin2024
-
-
- 휴대폰
- 010-1234-5678
-
-
- 이메일
- suppin2024@naver.com
-
-
+ {/* TODO: 스켈레톤 컴포넌트 추가 */}
+ {content}
비밀번호 변경하기
- 회원 탈퇴하기
+
+ {isDeleting ? '탈퇴 중...' : '회원 탈퇴하기'}
+
+ {/* */}
버전 정보
@@ -54,7 +68,6 @@ const Container1 = styled.div`
const SubContainer = styled.div`
display: flex;
- /* justify-content: flex-start; */
align-items: flex-start;
flex-direction: column;
margin-left: 15px;
@@ -79,45 +92,24 @@ const Type = styled.div`
const Container2 = styled.div`
padding: 0px 20px;
`;
-const Information = styled.div`
- ${body1Style}
- color: ${COLORS.Gray1};
- border-bottom: 1px solid ${COLORS.Gray5};
- padding: 30px 0px 30px 0px;
-`;
-
-const BasicInfo = styled.div`
- margin-bottom: 20px;
-`;
-
-const InfoContainer = styled.div`
- display: flex;
- gap: 27px;
-
- a {
- text-decoration: none; /* 밑줄 제거 */
- }
-`;
-const Info = styled.p`
- ${body1Style}
- color: ${COLORS.Gray2};
-`;
-const Id = styled.a`
- color: ${COLORS.Gray2};
- text-decoration: none;
-`;
const Change = styled.div`
padding: 20px 0px;
border-bottom: 1px solid ${COLORS.Gray5};
${body1Style}
color: ${COLORS.Gray1};
`;
-const Leave = styled.div`
+const Leave = styled.button`
padding: 20px 0px;
- /* border-bottom: 1px solid ${COLORS.Gray5}; */
+ border: none;
+ background: none;
+ cursor: pointer;
${body1Style}
color: ${COLORS.Gray1};
+ &:disabled {
+ color: ${COLORS.Gray4};
+ cursor: not-allowed;
+ }
`;
const VersionContainer = styled.div`
border-bottom: 4px solid ${COLORS.Gray6};
diff --git a/web/src/services/apis/user.service.ts b/web/src/services/apis/user.service.ts
index 1dc1a1e..2b413e3 100644
--- a/web/src/services/apis/user.service.ts
+++ b/web/src/services/apis/user.service.ts
@@ -1,7 +1,8 @@
-import { SigninType, SignupType } from '@/lib/schema/auth.schema';
+import { SigninType, SignupPayload } from '@/lib/schema/auth.schema';
import { axiosInstance } from '@/services/axios-instance';
+import { UserResponse } from '@/types/user';
-export const signup = async (payload: SignupType) => {
+export const signup = async (payload: SignupPayload) => {
const { data } = await axiosInstance.post('/members/join', payload);
return data;
};
@@ -33,9 +34,26 @@ export const signin = async (payload: SigninType) => {
return data;
};
+// 아이디 중복 확인 (sign-up3.tsx)
export const checkUserId = async (userId: string) => {
const { data } = await axiosInstance.get('/members/checkUserId', {
params: { userId },
});
return data;
};
+
+// 회원정보 상세 조회
+export const getUserInfo = async (): Promise
=> {
+ const { data } = await axiosInstance.get('/members/me');
+ return data;
+};
+
+// 회원탈퇴
+export const deleteUser = async () => {
+ const { data } = await axiosInstance.delete('/members/delete', {
+ headers: {
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
+ },
+ });
+ return data;
+};
diff --git a/web/src/services/axios-instance.ts b/web/src/services/axios-instance.ts
index 8cf8c5b..6c7f5e9 100644
--- a/web/src/services/axios-instance.ts
+++ b/web/src/services/axios-instance.ts
@@ -7,6 +7,7 @@ export const axiosInstance = axios.create({
axiosInstance.interceptors.request.use(
config => {
+ config.headers.Authorization = `Bearer ${window.localStorage.getItem('token')}`;
return config;
},
error => {
@@ -17,6 +18,9 @@ axiosInstance.interceptors.request.use(
axiosInstance.interceptors.response.use(
response => {
+ if (response.config.url === '/members/login') {
+ window.localStorage.setItem('token', response.data.data.token);
+ }
return response;
},
error => {
diff --git a/web/src/services/queries/user.mutation.ts b/web/src/services/queries/user.mutation.ts
index 3e5c508..63d3b45 100644
--- a/web/src/services/queries/user.mutation.ts
+++ b/web/src/services/queries/user.mutation.ts
@@ -1,6 +1,7 @@
import { SigninType, SignupType } from '@/lib/schema/auth.schema';
-import { signin, signup } from '@/services/apis/user.service';
+import { deleteUser, signin, signup } from '@/services/apis/user.service';
import { useMutation } from '@tanstack/react-query';
+import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
export const useSignup = () => {
@@ -37,9 +38,14 @@ export const useSignin = () => {
}
toast.error('로그인 중 오류가 발생했습니다, 다시 시도해주세요');
},
- // onSuccess: () => {
- // console.log('로그인 성공');
- // },
+ // 로그인 성공 시 token 저장 및 콘솔 출력
+ onSuccess: data => {
+ console.log(data); // 로그인 성공 시 응답 콘솔 출력
+ if (data && data.data && data.data.token) {
+ localStorage.setItem('token', data.data.token); // token을 로컬스토리지에 저장
+ }
+ // toast.success('로그인이 정상처리 되었습니다.');
+ },
});
return {
@@ -47,3 +53,27 @@ export const useSignin = () => {
isSigninLoading,
};
};
+
+export const useDeleteUser = () => {
+ const navigate = useNavigate();
+
+ const { mutateAsync: deleteUserAccount, isPending: isDeleting } = useMutation(
+ {
+ mutationFn: async () => await deleteUser(),
+ onError: error => {
+ console.error('회원 탈퇴 에러:', error);
+ toast.error('회원 탈퇴 중 오류가 발생했습니다, 다시 시도해주세요');
+ },
+ onSuccess: () => {
+ toast.success('회원 탈퇴가 완료되었습니다.');
+ localStorage.removeItem('token');
+ navigate('/auth?authType=in');
+ },
+ }
+ );
+
+ return {
+ deleteUserAccount,
+ isDeleting,
+ };
+};
diff --git a/web/src/services/queries/user.queries.ts b/web/src/services/queries/user.queries.ts
index e69de29..a8afb74 100644
--- a/web/src/services/queries/user.queries.ts
+++ b/web/src/services/queries/user.queries.ts
@@ -0,0 +1,15 @@
+import { queries } from '@/lib/query-keys';
+import { getUserInfo } from '@/services/apis/user.service';
+import { useQuery } from '@tanstack/react-query';
+
+export const useUserInfo = () => {
+ const userInfo = useQuery({
+ queryKey: queries.user.DEFAULT,
+ queryFn: getUserInfo,
+ select: data => data.data,
+ });
+
+ return {
+ userInfo,
+ };
+};
diff --git a/web/src/types/user.ts b/web/src/types/user.ts
index e69de29..9f9d3d0 100644
--- a/web/src/types/user.ts
+++ b/web/src/types/user.ts
@@ -0,0 +1,19 @@
+export type UserResponse = {
+ code: string;
+ message: string;
+ data: {
+ userId: string;
+ name: string;
+ email: string;
+ phoneNumber: string;
+ createdAt: string;
+ };
+};
+
+export type UserInfoType = {
+ userId: string;
+ name: string;
+ email: string;
+ phoneNumber: string;
+ createdAt: string;
+};