Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#274 SSR에서 토큰 탑재 자동화 및 마이페이지 리팩토링 #279

Merged
merged 7 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/apis/mypage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export const sendEmailVerification = async (email: string) => {
});
};

export const fetchMy = async () => {
export const fetchMyPageWithSSR = async () => {
const res = await authAPI<MyPage>({
method: 'get',
url: '/api/member/mypage',
});

return res;
return res.data;
};

export const fetchProfile = async () => {
Expand Down
17 changes: 17 additions & 0 deletions src/constants/queryManagement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { fetchMyPageWithSSR, sendEmailVerification } from '@apis/mypage';

const QUERY_MANAGEMENT = {
mypage: {
queryKey: 'mypage',
queryFn: fetchMyPageWithSSR,
},
};

const MUTATION_MANAGEMENT = {
email: {
mutateKey: 'sendEmail',
mutateFn: (email: string) => sendEmailVerification(email),
},
};

export { QUERY_MANAGEMENT, MUTATION_MANAGEMENT };
22 changes: 22 additions & 0 deletions src/hooks/mutation/useSendEmailMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MUTATION_MANAGEMENT } from '@constants/queryManagement';
import useInput from '@hooks/useInput';
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react';

const useSendEmailMutation = () => {
const [email, setEmail, resetEmail] = useInput('');
const [isSendVerifyEmail, setIsSendVerifyEmail] = useState<boolean>(false);

const { mutate } = useMutation({
mutationKey: [MUTATION_MANAGEMENT.email.mutateKey],
mutationFn: () => MUTATION_MANAGEMENT.email.mutateFn(email),
onMutate: () => {
alert('이메일을 보냈습니다.');
setIsSendVerifyEmail(true);
},
});

return { email, setEmail, resetEmail, isSendVerifyEmail, mutate };
};

export default useSendEmailMutation;
13 changes: 13 additions & 0 deletions src/hooks/query/useMyPageQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { QUERY_MANAGEMENT } from '@constants/queryManagement';
import { useSuspenseQuery } from '@tanstack/react-query';

const useMyPageQuery = () => {
const { data, refetch } = useSuspenseQuery({
queryKey: [QUERY_MANAGEMENT.mypage.queryKey],
queryFn: QUERY_MANAGEMENT.mypage.queryFn,
});

return { data, refetch };
};

export default useMyPageQuery;
22 changes: 22 additions & 0 deletions src/hooks/useInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useState, useCallback } from 'react';

const useInput = (initialValue: string) => {
const [inputValue, setInputValue] = useState<string>(initialValue);

const handleValue = useCallback(
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { value } = e.target;

setInputValue(value);
},
[],
);

const resetValue = useCallback(() => {
setInputValue('');
}, []);

return [inputValue, handleValue, resetValue] as const;
};

export default useInput;
133 changes: 44 additions & 89 deletions src/pages/mypage.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,16 @@
import { GetServerSidePropsContext } from 'next';
import { ChangeEvent, useState } from 'react';
import styled from '@emotion/styled';
import Image from 'next/image';
import { parse } from 'cookie';
import axios from 'axios';

import checkEmailAddressValidation from 'src/utils/checkEmailAddressValidation';
import { SERVER_URL } from '@config/index';
import { MyPage } from '@type/mypage';
import Icon from '@components/Icon';
import { fetchMy, sendEmailVerification } from '@apis/mypage';

interface Props {
data: MyPage;
}

const Mypage = (props: Props) => {
const [info, setInfo] = useState<MyPage>(props.data);

const [email, setEmail] = useState<string>('');
const [sendEmail, setSendEmail] = useState<boolean>(false);

const handleEmail = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};

const sendVerifyEmail = async () => {
if (!checkEmailAddressValidation(email)) {
alert('유효한 이메일 주소가 아닙니다! 다시 입력해주세요');
return;
}

try {
await sendEmailVerification(email);
setSendEmail(true);

alert(
'이메일 전송이 완료되었습니다.\n입력하신 이메일의 링크를 클릭하신 후 동기화 버튼을 눌러주세요!',
);
} catch (error) {
console.log(error);
}
};

const refreshMyInfo = async () => {
try {
const res = await fetchMy();
setInfo(res.data);
} catch (error) {
console.log(error);
}
};
import withAuthServerSideProps from 'src/utils/withAuthentication';
import { QueryClient, dehydrate } from '@tanstack/react-query';
import { QUERY_MANAGEMENT } from '@constants/queryManagement';
import useSendEmailMutation from '@hooks/mutation/useSendEmailMutation';
import useMyPageQuery from '@hooks/query/useMyPageQuery';

const Mypage = () => {
const { data, refetch } = useMyPageQuery();
const { email, setEmail, resetEmail, isSendVerifyEmail, mutate } = useSendEmailMutation();

return (
<Container>
Expand All @@ -60,23 +20,23 @@ const Mypage = (props: Props) => {
<Content>
<GridLabel>프로필</GridLabel>
<GridValue>
<ProfileImage src={info.profileImageUrl} alt='profile' width='50' height='50' />
<ProfileImage src={data.profileImageUrl} alt='profile' width='50' height='50' />
</GridValue>
</Content>
<Content>
<GridLabel>이름</GridLabel>
<GridValue>{info.nickName}</GridValue>
<GridValue>{data.nickName}</GridValue>
</Content>
<Content>
<GridLabel>이메일</GridLabel>
<GridValue>
<EmailForm>
{info.userEmailVerified ? (
<EmailInput disabled value={info.email} />
{data.userEmailVerified ? (
<EmailInput disabled value={data.email} />
) : (
<>
<EmailInput value={email} onChange={handleEmail} disabled={sendEmail} />
<EmailSendBtn onClick={sendVerifyEmail} disabled={sendEmail}>
<EmailInput value={email} onChange={setEmail} disabled={isSendVerifyEmail} />
<EmailSendBtn onClick={() => mutate()} disabled={isSendVerifyEmail}>
<Icon kind='sendEmail' size='28' />
</EmailSendBtn>
</>
Expand All @@ -87,13 +47,13 @@ const Mypage = (props: Props) => {
<Content>
<GridLabel>이메일 인증</GridLabel>
<GridValue>
{info.userEmailVerified ? '인증되었습니다.' : '미인증 상태입니다.'}
{data.userEmailVerified ? '인증되었습니다.' : '미인증 상태입니다.'}
</GridValue>
</Content>
<Content>
<GridLabel>정보 동기화</GridLabel>
<GridValue>
<EmailSendBtn onClick={refreshMyInfo}>
<EmailSendBtn onClick={() => refetch()}>
<Icon kind='refresh' size='28' />
</EmailSendBtn>
</GridValue>
Expand All @@ -104,38 +64,6 @@ const Mypage = (props: Props) => {
);
};

export default Mypage;

export const getServerSideProps = async (context: GetServerSidePropsContext) => {
try {
const cookies = parse(context.req.headers.cookie || 'no-cookie');

const accessToken = cookies.accessToken || undefined;

const res = await axios<MyPage>({
method: 'get',
url: SERVER_URL + '/api/member/mypage',
headers: {
Authorization: `Bearer ${accessToken}`,
},
});

return {
props: {
data: res.data,
},
};
} catch (error) {
console.log(error);
return {
redirect: {
permanent: false,
destination: '/login',
},
};
}
};

const Container = styled.div`
margin: 0 auto;
color: #020202;
Expand Down Expand Up @@ -207,3 +135,30 @@ const EmailSendBtn = styled.button`
const ProfileImage = styled(Image)`
border-radius: 50%;
`;

export default Mypage;

const fetchMyPage = async () => {
const queryClient = new QueryClient();

try {
await queryClient.prefetchQuery({
queryKey: [QUERY_MANAGEMENT.mypage.queryKey],
queryFn: QUERY_MANAGEMENT.mypage.queryFn,
});

return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
} catch (error) {
return {
redirect: {
destination: '/',
},
};
}
};

export const getServerSideProps = withAuthServerSideProps(fetchMyPage);
25 changes: 25 additions & 0 deletions src/utils/withAuthentication.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import authAPI from '@apis/authAPI';
import { parse } from 'cookie';
import { GetServerSidePropsContext } from 'next';

const withAuthServerSideProps = (getServerSidePropsFunction: () => Promise<any>) => {
return async (context: GetServerSidePropsContext) => {
const cookies = context.req.headers.cookie || '';

const accessToken = parse(cookies).accessToken;

if (!accessToken) {
throw new Error('401 Unauthorized');
}

authAPI.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;

const res = await getServerSidePropsFunction();

authAPI.defaults.headers.common['Authorization'] = '';

return res;
};
};

export default withAuthServerSideProps;