Skip to content

Commit

Permalink
Merge pull request codestates-seb#304 from shimdokite/develop
Browse files Browse the repository at this point in the history
[FE] ♻️ Signin, Signup, Profile, History 컴포넌트 및 페이지 리팩토링
  • Loading branch information
nalsae authored Oct 17, 2023
2 parents eced389 + 41bbfa0 commit 7620738
Show file tree
Hide file tree
Showing 72 changed files with 1,624 additions and 1,007 deletions.
85 changes: 85 additions & 0 deletions client/src/api/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import axios, {
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig,
} from 'axios';

import useUserStore from '@/stores/userStore';

const { setAccessToken } = useUserStore();

const accessToken =
typeof window !== 'undefined'
? JSON.parse(localStorage.getItem('user-key') as string).state.accessToken
: null;

const refreshToken =
typeof window !== 'undefined'
? JSON.parse(localStorage.getItem('user-key') as string).state.refreshToken
: null;

const instance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
Authorization: accessToken,
Refresh: refreshToken,
},
withCredentials: true,
});

// atob을 활용한 JWT 디코딩
const parseJWT = (token: string | null) => {
if (token) return JSON.parse(atob(token.split('.')[1]));
};

const authVerify = () => {
const decodedAccess = parseJWT(accessToken);
const decodedRefresh = parseJWT(refreshToken);

if (decodedAccess.exp * 1000 < Date.now()) {
return 'Access Token Expired';
}

if (decodedRefresh.exp * 1000 < Date.now()) {
return 'Refresh Token Expired';
}

return true;
};

// 응답 받기 전, 새로운 accessToke이 존재하면 바꿔주기
export const onFulfiled = async (response: AxiosResponse) => {
if (authVerify() === 'Access Token Expired') {
const { authorization: newAccessToken } = response.headers;

setAccessToken(newAccessToken);

// 타입 호환을 위해 새로운 객체를 만들어서 업데이트하기
response.config.headers = Object.assign({}, response.config.headers, {
authorization: `${newAccessToken}`,
});

return await axios(response.config);
}

return response;
};

instance.interceptors.request.use(
//! AxiosRequestConfig 대신 InternalAxiosRequestConfig를 사용하라고 하는데...
// InternalAxiosRequestConfig를 개발자가 직접 건드는게 좋은건 아니라고 하던데 흠...
async (config: InternalAxiosRequestConfig) => {
config.headers = config.headers ?? {};

if (accessToken) {
config.headers.Authorization = `${accessToken}`;
}

return config;
},
(error) => Promise.reject(error),
);

instance.interceptors.response.use(onFulfiled, (error) => {
return Promise.reject(error);
});
30 changes: 18 additions & 12 deletions client/src/app/history/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

import { motion } from 'framer-motion';

import useSignModalStore from '@/stores/signModalStore';
import useModalStore, { ModalType } from '@/stores/modalStore';

import ResignModal from '@/components/history/ResignModal';
import ConfirmModal from '@/components/history/ConfirmModal';
import SuccessedModal from '@/components/history/SuccessedModal';
import FailureModal from '@/components/history/FailureModal';
import HistoryBox from '@/components/history/HistoryBox';
import Footer from '@/components/common/Footer';
import {
ResignModal,
ConfirmModal,
SuccessedModal,
FailureModal,
HistoryBox,
} from '@/components/history';
import { Footer } from '@/components/common';

import { MOUNT_ANIMATION_VALUES } from '@/constants/values';

Expand All @@ -18,7 +20,14 @@ interface HistoryProps {
}

export default function History({ params }: HistoryProps) {
const currentState = useSignModalStore((state) => state.currentState);
const { isOpen, type } = useModalStore();

const renderModal = (type: ModalType) => {
if (type === 'ResignModal') return <ResignModal />;
if (type === 'ConfirmModal') return <ConfirmModal />;
if (type === 'SuccessedModal') return <SuccessedModal />;
if (type === 'FailureModal') return <FailureModal />;
};

return (
<>
Expand All @@ -29,10 +38,7 @@ export default function History({ params }: HistoryProps) {
className="flex flex-col justify-center items-center h-auto min-h-full pb-[343px] mx-4">
<HistoryBox paramsId={params.id} />

{currentState === 'ResignModal' && <ResignModal />}
{currentState === 'ConfirmModal' && <ConfirmModal />}
{currentState === 'FailureModal' && <FailureModal />}
{currentState === 'SuccessedModal' && <SuccessedModal />}
{isOpen && renderModal(type)}
</motion.div>
<Footer />
</>
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { getScrollTop } from '@/utils/getScrollTop';
export default function Home() {
const isClient = useClient();

const { isLogin, isGoogleLogin } = useUserStore();
const { isEmailLogin, isGoogleLogin } = useUserStore();

const handleClick = () => {
const top = getScrollTop(window.innerWidth);
Expand Down Expand Up @@ -69,7 +69,7 @@ export default function Home() {
<ServiceInfo key={index} order={index} />
))}
</div>
{!(isLogin || isGoogleLogin) && (
{!(isEmailLogin || isGoogleLogin) && (
<motion.section
initial={{ y: 100, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
Expand Down
14 changes: 8 additions & 6 deletions client/src/app/post/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import useUserStore from '@/stores/userStore';

import useEffectOnce from '@/hooks/useEffectOnce';

import {
PageTitle,
PostCountInfo,
Screws,
LoadingNotice,
ErrorMessage,
Footer,
} from '@/components/common';
import PostDeleteModal from '@/components/post/PostDeleteModal';
import PageTitle from '@/components/common/PageTitle';
import PostCountInfo from '@/components/common/PostCountInfo';
import Screws from '@/components/common/Screws';
import Comment from '@/components/post/Comment';
import CommentForm from '@/components/post/CommentForm';
import PostContent from '@/components/post/PostContent';
Expand All @@ -25,9 +30,6 @@ import PostImage from '@/components/post/PostImage';
import PostProfile from '@/components/post/PostProfile';
import HashTags from '@/components/post/HashTags';
import CommentDeleteModal from '@/components/post/CommentDeleteModal';
import LoadingNotice from '@/components/common/LoadingNotice';
import ErrorMessage from '@/components/common/ErrorMessage';
import Footer from '@/components/common/Footer';

import { CommentDataInfo, PostDataInfo } from '@/types/data';

Expand Down
43 changes: 26 additions & 17 deletions client/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,38 @@ import { notFound } from 'next/navigation';

import { motion } from 'framer-motion';

import useSignModalStore from '@/stores/signModalStore';
import useUserStore from '@/stores/userStore';
import useModalStore, { ModalType } from '@/stores/modalStore';

import ProfileBox from '@/components/profile/ProfileBox';
import ChangePasswordModal from '@/components/profile/ChangePasswordModal';
import ChangeNicknameModal from '@/components/profile/ChangeNicknameModal';
import ConfirmModal from '@/components/history/ConfirmModal';
import ResignModal from '@/components/history/ResignModal';
import FailureModal from '@/components/history/FailureModal';
import SuccessedModal from '@/components/history/SuccessedModal';
import Footer from '@/components/common/Footer';
import useEffectOnce from '@/hooks/useEffectOnce';

import { ProfileBox, ChangeProfileModal } from '@/components/profile';
import {
ResignModal,
ConfirmModal,
SuccessedModal,
FailureModal,
} from '@/components/history';
import { Footer } from '@/components/common';

import { ADMIN_USER_ID, MOUNT_ANIMATION_VALUES } from '@/constants/values';
import useEffectOnce from '@/hooks/useEffectOnce';

export default function Profile() {
const currentState = useSignModalStore((state) => state.currentState);
const { userId } = useUserStore();
const { isOpen, type } = useModalStore();

const renderModal = (type: ModalType) => {
if (type === 'ChangePasswordModal')
return <ChangeProfileModal type="password" />;
if (type === 'ChangeNicknameModal')
return <ChangeProfileModal type="nickname" />;
if (type === 'ChangeImageModal') return <ChangeProfileModal type="image" />;

if (type === 'ResignModal') return <ResignModal />;
if (type === 'ConfirmModal') return <ConfirmModal />;
if (type === 'SuccessedModal') return <SuccessedModal />;
if (type === 'FailureModal') return <FailureModal />;
};

useEffectOnce(() => {
userId === ADMIN_USER_ID && notFound();
Expand All @@ -36,12 +50,7 @@ export default function Profile() {
className="flex flex-col justify-center items-center h-auto min-h-full pb-[343px] mx-4">
<ProfileBox />

{currentState === 'ChangePasswordModal' && <ChangePasswordModal />}
{currentState === 'ChangeNicknameModal' && <ChangeNicknameModal />}
{currentState === 'ConfirmModal' && <ConfirmModal />}
{currentState === 'ResignModal' && <ResignModal />}
{currentState === 'FailureModal' && <FailureModal />}
{currentState === 'SuccessedModal' && <SuccessedModal />}
{isOpen && renderModal(type)}
</motion.div>
<Footer />
</>
Expand Down
24 changes: 15 additions & 9 deletions client/src/app/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@

import { motion } from 'framer-motion';

import useSignModalStore from '@/stores/signModalStore';
import useModalStore, { ModalType } from '@/stores/modalStore';

import SigninIntro from '@/components/signin/SigninIntro';
import FindPasswordModal from '@/components/signin/FindPasswordModal';
import SuccessedModal from '@/components/signin/SuccessedModal';
import FailureModal from '@/components/signin/FailureModal';
import {
SigninIntro,
FindPasswordModal,
SuccessedModal,
FailureModal,
} from '@/components/signin';

export default function Signin() {
const currentState = useSignModalStore((state) => state.currentState);
const { isOpen, type } = useModalStore();

const renderModal = (type: ModalType) => {
if (type === 'FindPasswordModal') return <FindPasswordModal />;
if (type === 'SuccessedModal') return <SuccessedModal />;
if (type === 'FailureModal') return <FailureModal />;
};

return (
<motion.div
Expand All @@ -20,9 +28,7 @@ export default function Signin() {
className="flex flex-col justify-center items-center h-full mx-4 pb-[40px]">
<SigninIntro />

{currentState === 'FindPasswordModal' && <FindPasswordModal />}
{currentState === 'SuccessedModal' && <SuccessedModal />}
{currentState === 'FailureModal' && <FailureModal />}
{isOpen && renderModal(type)}
</motion.div>
);
}
16 changes: 9 additions & 7 deletions client/src/app/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

import { motion } from 'framer-motion';

import useSignModalStore from '@/stores/signModalStore';
import useModalStore, { ModalType } from '@/stores/modalStore';

import AuthEmailModal from '@/components/signup/AuthEmailModal';
import FailureModal from '@/components/signup/FailureModal';
import SignupIntro from '@/components/signup/SignupIntro';
import { AuthEmailModal, FailureModal, SignupIntro } from '@/components/signup';

export default function Signup() {
const currentState = useSignModalStore((state) => state.currentState);
const { isOpen, type } = useModalStore();

const renderModal = (type: ModalType) => {
if (type === 'AuthEmailModal') return <AuthEmailModal />;
if (type === 'FailureModal') return <FailureModal />;
};

return (
<motion.div
Expand All @@ -19,8 +22,7 @@ export default function Signup() {
className="flex flex-col justify-center items-center h-full mx-4 pb-[40px]">
<SignupIntro />

{currentState === 'AuthEmailModal' && <AuthEmailModal />}
{currentState === 'Not Code' && <FailureModal />}
{isOpen && renderModal(type)}
</motion.div>
);
}
6 changes: 3 additions & 3 deletions client/src/components/board/RankBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ export default function RankBoard() {
variants={MOUNT_ANIMATION_VALUES}
initial="initial"
animate="animate"
className="pt-[60px] w-[448px] h-[268px] flex flex-col items-center bg-contain bg-center bg-no-repeat bg-[url('/assets/img/bg_board_lg.png')] drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)] scale-100 max-[604px]:w-[313px] max-[604px]:-mb-3 max-[604px]:px-6 max-[604px]:pt-[78px]">
<h2 className="text-2xl leading-6 text-brown-10 font-bold max-[604px]:text-xl">
className="pt-[64px] w-[448px] h-[268px] flex flex-col items-center bg-contain bg-center bg-no-repeat bg-[url('/assets/img/bg_board_lg.png')] drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)] scale-100 max-[604px]:w-[313px] max-[604px]:-mb-12 max-[604px]:px-6 max-[604px]:pt-[78px]">
<h2 className="text-2xl leading-6 text-brown-10 font-bold max-[604px]:text-lg">
이주의 좋아요 순위
</h2>
<div className="py-5 w-full max-w-[720px] flex flex-col items-center gap-2 text-base leading-4 text-brown-10 font-normal whitespace-nowrap overflow-x-hidden max-[604px]:text-xs max-[604px]:gap-1 max-[604px]:pt-1">
<div className="pt-7 pb-5 w-full max-w-[720px] flex flex-col items-center gap-2 text-base leading-4 text-brown-10 font-normal whitespace-nowrap overflow-x-hidden max-[604px]:text-xs max-[604px]:gap-1 max-[604px]:pt-1">
{boardRank.slice(0, 3).map((board) => {
return (
<Link
Expand Down
11 changes: 6 additions & 5 deletions client/src/components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default function Header() {
const [isProfileHover, setIsProfileHover] = useState(false);
const [isMenuHover, setIsMenuHover] = useState(false);

const { userId, isLogin, isGoogleLogin, profileImageUrl } = useUserStore();
const { userId, isEmailLogin, isGoogleLogin, profileImageUrl } =
useUserStore();
const getSigninForm = useSignStore((state) => state.getSigninForm);

const isClient = useClient();
Expand Down Expand Up @@ -72,7 +73,7 @@ export default function Header() {
<li className="max-[480px]:hidden">
<HeaderLink
location={
isClient && (isLogin || isGoogleLogin)
isClient && (isEmailLogin || isGoogleLogin)
? `/garden/${userId}`
: '/signin'
}
Expand All @@ -90,15 +91,15 @@ export default function Header() {
<li className="max-[480px]:hidden">
<HeaderLink
location={
isClient && (isLogin || isGoogleLogin)
isClient && (isEmailLogin || isGoogleLogin)
? `/leafs/${userId}`
: '/signin'
}
content="activity"
title="leafCard"
/>
</li>
{isClient && (isLogin || isGoogleLogin) ? (
{isClient && (isEmailLogin || isGoogleLogin) ? (
<li
onMouseOver={() => setIsProfileHover(true)}
onMouseLeave={() => setIsProfileHover(false)}>
Expand All @@ -109,7 +110,7 @@ export default function Header() {
width={36}
height={36}
/>
{isProfileHover && (isLogin || isGoogleLogin) && (
{isProfileHover && (isEmailLogin || isGoogleLogin) && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
Expand Down
Loading

0 comments on commit 7620738

Please sign in to comment.