Skip to content

Commit

Permalink
Merge branch 'feat/#123' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
seoyeon5117 committed Feb 21, 2025
2 parents f423a00 + 6268a16 commit ee58f82
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 52 deletions.
13 changes: 13 additions & 0 deletions src/apis/user/patchUserDetail.api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { generateApiPath } from '@/utils/generateApiPath';
import { authApi } from '../axios-instance';
import { API_DOMAINS } from '@/constants/api';

export const patchUserDetail = async ({ data }) => {
const response = await authApi.patch(API_DOMAINS.PATCH_USER, data, {
headers: {
'Content-Type': 'application/json',
},
});

return response.data.data;
};
20 changes: 20 additions & 0 deletions src/components/common/NotificationButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { path } from '@/routes/path';
import { useNavigate } from 'react-router-dom';
import notification from '@/assets/imgs/notification.svg';
import notification_true from '@/assets/imgs/notification_true.svg';

export const NotificationButton = ({ isNotification }) => {
const navigate = useNavigate();
return (
<button
className="cursor-pointer"
onClick={() => navigate(path.notificationList)}
>
<img
src={isNotification ? notification_true : notification}
alt="notification button"
className="w-5 h-6"
/>
</button>
);
};
2 changes: 2 additions & 0 deletions src/constants/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const KAKAO_AUTH_API_URL = 'https://kauth.kakao.com/oauth/authorize';

export const API_DOMAINS = {
USER: '/users/details',
PATCH_USER: '/users/info',
MYPAGE: '/mypage',

GET_MY_POSTS: '/posts/my',
Expand Down Expand Up @@ -102,5 +103,6 @@ export const QUERY_KEYS = {
MY_POST_LIST: 'myPostList',
MY_COMMENTED_POST_LIST: 'myCommentedPostList',
MY_SCRAPED_POST_LIST: 'scrapedPostList',
USER_INFO: 'userInfo',
};

1 change: 0 additions & 1 deletion src/constants/nations.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// 더미 데이터
const Nations = [
{ name: 'Saudi Arabia', code: 'SA' },
{ name: 'Bulgaria', code: 'BG' },
Expand Down
86 changes: 64 additions & 22 deletions src/pages/my/mypage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,45 @@ import arrow from '@/assets/imgs/arrowRight.svg';
import { useNavigate } from 'react-router-dom';
import { path } from '@/routes/path';
import { removeTokens } from '@/utils/authUtils';
import { useEffect, useState } from 'react';
import { QUERY_KEYS } from '@/constants/api';
import { useQuery } from '@tanstack/react-query';
import { getUser } from '@/apis/user/userDetail.api';
import { NotificationButton } from '@/components/common/NotificationButton';
import { useCheckSchoolStatus } from '@/hooks/use-CheckSchoolStatus';
import { Loading } from '@/components/common/Loading';

export const MyPage = () => {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [university, setUniversity] = useState('');
const [status, setStatus] = useState(''); // 학교 인증 상태

// 백 연동으로 username, university 받아오기
const username = 'COTATO';
const university = 'Cotato University';
const { data: userData, isLoading: isUserDetailsLoading } = useQuery({
queryKey: [QUERY_KEYS.USER_INFO],
queryFn: () => getUser(),
});

useEffect(() => {
if (userData) {
setUsername(userData.nickname);
setUniversity(userData.universityName);
}
}, [userData]);

const { mutate: checkSchoolStatus, isLoading: isSchoolStatusLoading } =
useCheckSchoolStatus();

useEffect(() => {
checkSchoolStatus(undefined, {
onSuccess: (data) => {
setStatus(data.status);
},
onError: (error) => {
alert(error);
},
});
}, []);

const handleLogout = async () => {
await removeTokens();
Expand All @@ -18,31 +50,41 @@ export const MyPage = () => {
return (
<div className="flex h-full w-full flex-col gap-[1.125rem] p-4">
{/* 알림 영역 */}
<div className="flex h-full w-full justify-end">
<img src={notification} alt="notification button" />
<div className="flex justify-end w-full h-full">
<NotificationButton isNotification={false} />
</div>
{/* 본문 영역 */}
<div className="flex h-full w-full flex-col gap-[2.5rem]">
{/* 마이페이지 메인 버튼 */}
<div
className="flex h-full w-full items-center rounded-[.625rem] bg-primary-base px-[.75rem] py-[1.625rem]"
className="flex h-full w-full cursor-pointer items-center rounded-[.625rem] bg-primary-base px-[.75rem] py-[1.625rem]"
onClick={() => navigate(path.mypage.settings.info)}
>
<div className="flex h-full w-full flex-col justify-between gap-1 text-white">
<div className="text-pageTitle">{username}</div>
<div className="text-neutral-disabled">
{university ? university : "What's the name of your school?"}
</div>
</div>
<div className="right-0 h-full">
<img src={arrow} alt="" className="h-[1.25rem] w-[1.25rem]" />
</div>
{isUserDetailsLoading && isSchoolStatusLoading ? (
<Loading />
) : (
<>
<div className="flex flex-col justify-between w-full h-full gap-1 text-white">
<div className="text-pageTitle">{username}</div>
<div className="text-neutral-disabled">
{university
? university
: status === 'PENDING'
? 'School verification is in progress.'
: "What's the name of your school?"}
</div>
</div>
<div className="right-0 h-full">
<img src={arrow} alt="" className="h-[1.25rem] w-[1.25rem]" />
</div>
</>
)}
</div>
{/* 마이페이지 항목들 */}
<div className="flex h-full w-full flex-col gap-[1.875rem]">
<div className="flex flex-col w-full h-full gap-2">
<div className="text-neutral-border-50">Service Settings</div>
<div className="flex h-full w-full flex-col gap-2 border-t border-primary-base pt-2">
<div className="flex flex-col w-full h-full gap-2 pt-2 border-t border-primary-base">
<button
type="button"
className="text-left"
Expand All @@ -68,9 +110,9 @@ export const MyPage = () => {
</button>
</div>
</div>
<div className="flex h-full w-full flex-col gap-2">
<div className="flex flex-col w-full h-full gap-2">
<div className="text-neutral-border-50">Community</div>
<div className="flex h-full w-full flex-col gap-2 border-t border-primary-base pt-2">
<div className="flex flex-col w-full h-full gap-2 pt-2 border-t border-primary-base">
<button
type="button"
className="text-left"
Expand All @@ -94,9 +136,9 @@ export const MyPage = () => {
</button>
</div>
</div>
<div className="flex h-full w-full flex-col gap-2">
<div className="flex flex-col w-full h-full gap-2">
<div className="text-neutral-border-50">Contact Us</div>
<div className="flex h-full w-full flex-col gap-2 border-t border-primary-base pt-2">
<div className="flex flex-col w-full h-full gap-2 pt-2 border-t border-primary-base">
<button
type="button"
className="text-left"
Expand Down Expand Up @@ -132,9 +174,9 @@ export const MyPage = () => {
</button>
</div>
</div>
<div className="flex h-full w-full flex-col gap-2">
<div className="flex flex-col w-full h-full gap-2">
<div className="text-neutral-border-50">etc.</div>
<div className="flex h-full w-full flex-col gap-2 border-t border-primary-base pt-2">
<div className="flex flex-col w-full h-full gap-2 pt-2 border-t border-primary-base">
<button
type="button"
className="text-left"
Expand Down
111 changes: 82 additions & 29 deletions src/pages/my/settings/MyInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,26 @@ import { useDuplicateCheck } from '@/hooks/use-duplicateCheck';
import { useEffect, useState } from 'react';
import { postDuplicateCheck } from '@/apis/auth/duplicateCheck.api';
import { useNavigate } from 'react-router-dom';
import { useMutation, useQuery } from '@tanstack/react-query';
import { QUERY_KEYS } from '@/constants/api';
import { getUser } from '@/apis/user/userDetail.api';
import { path } from '@/routes/path';
import { useCheckSchoolStatus } from '@/hooks/use-CheckSchoolStatus';
import { patchUserDetail } from '@/apis/user/patchUserDetail.api';

export const MyInfo = () => {
const navigate = useNavigate();
const [info, setInfo] = useState({
school: '',
username: '',
language: '',
universityName: '',
nickname: '',
preferredLanguage: '',
nationality: '',
});

const [savedInfo, setSavedInfo] = useState({
school: '',
username: '',
language: '',
universityName: '',
nickname: '',
preferredLanguage: '',
nationality: '',
});

Expand All @@ -33,14 +39,46 @@ export const MyInfo = () => {
const [isLanguageSelected, setIsLanguageSelected] = useState(true);
const [showModal, setShowModal] = useState(false);

const [status, setStatus] = useState(''); // 학교 인증 상태

// 학교 인증 여부에 따라 학교 인증 버튼/학교 이름으로 보임
const { mutate: checkSchoolStatus } = useCheckSchoolStatus();

useEffect(() => {
checkSchoolStatus(undefined, {
onSuccess: (data) => {
setStatus(data.status);
},
onError: (error) => {
alert(error);
},
});
}, []);

const validateUserNameValue = (value) => {
const regex = /^[a-z0-9]{5,20}$/;
return regex.test(value);
};

const { mutate: saveUserDetails } = useMutation({
mutationFn: (user) => patchUserDetail({ data: user }),
});

const handleClickSave = () => {
// 백 연동 후 마이페이지로 이동
navigate(-1);
const data = {
nickname: info.nickname,
preferredLanguage: info.preferredLanguage,
};
saveUserDetails(data, {
onSuccess: (response) => {
console.log(response);
navigate(-1);
},
onError: (err) => {
alert(err.message);
},
});
// navigate(-1);
};

const handleBackClick = () => {
Expand All @@ -51,17 +89,17 @@ export const MyInfo = () => {
}
};

const debouncedUserName = useDebounce(info.username, 300); // 입력을 마치고 300ms 후 중복 체크를 위함
const debouncedUserName = useDebounce(info.nickname, 300); // 입력을 마치고 300ms 후 중복 체크를 위함

const handleUserNameChange = (value) => {
setInfo((prev) => ({ ...prev, username: value }));
setInfo((prev) => ({ ...prev, nickname: value }));
setIsUserNameFormatInvalid(!validateUserNameValue(value));
};

const { mutate } = useDuplicateCheck(postDuplicateCheck);
const { mutate: checkDuplicate } = useDuplicateCheck(postDuplicateCheck);

const handleDuplicateCheck = (value) => {
mutate(
checkDuplicate(
{ data: { nickname: value } },
{
onSuccess: (response) => {
Expand All @@ -72,48 +110,63 @@ export const MyInfo = () => {
};

useEffect(() => {
if (!isUserNameFormatInvalid && debouncedUserName && (info.username !== savedInfo.username)) {
if (
!isUserNameFormatInvalid &&
debouncedUserName &&
info.nickname !== savedInfo.nickname
) {
handleDuplicateCheck(debouncedUserName);
}
}, [debouncedUserName, isUserNameFormatInvalid]);

const disabled =
!info.username ||
!info.nickname ||
isUserNameFormatInvalid ||
isUserNameDuplicated ||
!isLanguageSelected ||
JSON.stringify(info) === JSON.stringify(savedInfo);

const { data: userData } = useQuery({
queryKey: [QUERY_KEYS.USER_INFO],
queryFn: () => getUser(),
});

useEffect(() => {
const userData = {
school: '홍익대학교',
username: 'cotato',
language: 'French',
nationality: 'France',
};
setInfo(userData);
setSavedInfo(userData);
}, []);
if (userData) {
setInfo(userData);
setSavedInfo(userData);
}
}, [userData]);

return (
<div className="flex flex-col w-full h-full">
<TitleHeader text="My Information" onClick={handleBackClick} />
<div className="flex flex-col w-full h-full px-4">
<div className="mb-5 mt-12 flex w-full flex-col space-y-[1.875rem]">
{info.school ? (
<DisabledInput name="School" defaultValue={info.school} />
{info.universityName ? (
<DisabledInput name="School" defaultValue={info.universityName} />
) : status === 'PENDING' ? (
<div className="text-primary-base">
School verification is in progress.
</div>
) : (
<div className="flex flex-col gap-[.625rem]">
<span className="text-primary-base">
Verify your school to access the school board!
</span>
<MainWhiteButton>Search your school</MainWhiteButton>
<MainWhiteButton
onClick={() =>
navigate(`../../../${path.signup.base}/${path.signup.school}`)
}
>
Search your school
</MainWhiteButton>
</div>
)}
<div className="flex flex-col">
<span className="text-primary-base">User name</span>
<UserNameInput
userName={info.username}
userName={info.nickname}
onChange={handleUserNameChange}
invalid={isUserNameFormatInvalid}
duplicated={isUserNameDuplicated}
Expand All @@ -123,11 +176,11 @@ export const MyInfo = () => {
<div className="flex flex-col">
<span className="text-primary-base">Language</span>
<SearchDropdown
keyword={info.language}
keyword={info.preferredLanguage}
name="Language"
placeholder="Select your nationality"
onChange={(value) =>
setInfo((prev) => ({ ...prev, language: value }))
setInfo((prev) => ({ ...prev, preferredLanguage: value }))
}
setIsSelected={setIsLanguageSelected}
selected={isLanguageSelected}
Expand Down

0 comments on commit ee58f82

Please sign in to comment.