Skip to content
6 changes: 5 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import '@/src/styles/globals.css';
import { pretendard } from '@/src/styles/fonts';
import QueryProvider from '@/src/components/primitives/QueryProvider';
import AuthProvicder from '@/src/components/primitives/AuthProvider';
import { Suspense } from 'react';
import LoadingSpinner from '@/src/components/primitives/LoadingSpinner';

export const metadata: Metadata = {
title: 'Global Nomad',
Expand All @@ -18,7 +20,9 @@ export default function RootLayout({
<html lang='ko'>
<body className={pretendard.className}>
<QueryProvider>
<AuthProvicder>{children}</AuthProvicder>
<AuthProvicder>
<Suspense fallback={<LoadingSpinner />}>{children}</Suspense>
</AuthProvicder>
</QueryProvider>
<div id='portal' />
</body>
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/main/AllExperiencesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export default function AllExperiencesSection() {
const totalCount = allExperiences?.totalCount;
const lastPage = totalCount && Math.ceil(totalCount / size);

if (lastPage && size > lastPage) {
if (lastPage && page > lastPage) {
setPage(lastPage);
}
}, [allExperiences, size]);
}, [allExperiences, size, page]);

return (
<section className='mt-[25px] md:mt-[65px]'>
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/main/SearchResultSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ export default function SearchResultSection() {
const totalCount = resultList?.totalCount;
const lastPage = totalCount && Math.ceil(totalCount / size);

if (lastPage && size > lastPage) {
if (lastPage && page > lastPage) {
setPage(lastPage);
}
}, [size, resultList]);
}, [resultList, size, page]);

useEffect(() => {
// 검색값이 변경되면, 페이지네이션 초기화
Expand Down
29 changes: 18 additions & 11 deletions src/components/primitives/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,37 @@ import { queries } from '@/src/services/primitives/queries';
import { useTokenStore } from '@/src/store/useTokenStore';
import { useQuery } from '@tanstack/react-query';
import { usePathname, useRouter } from 'next/navigation';
import { ReactNode, useEffect } from 'react';
import { ReactNode, useEffect, useState } from 'react';

interface Props {
children: ReactNode;
}

export default function AuthProvicder({ children }: Props) {
const [isAuthChecked, setIsAuthCheck] = useState<boolean | null>(null);
const pathname = usePathname();
const router = useRouter();
const accessToken = useTokenStore((state) => state.accessToken);

// 리액트쿼리는 액세스 토큰이 있고, 유효한 액세스 토큰을 넘겨주었을 때에만 페칭을 시작함.
const { isLoading, data: userInfo } = useQuery({
const { isLoading } = useQuery({
...queries.userOptions(accessToken),
retry: false,
staleTime: Infinity,
});

// 리다이렉트 분기
useEffect(() => {
// accessToken이 undefined 이거나 리액트 쿼리 페칭중이라면 return
if (accessToken === undefined || isLoading) return;
if (!!accessToken) {
setIsAuthCheck(true);
} else {
setIsAuthCheck(false);
}
}, [accessToken]);

const isLoggingIn = !!userInfo;
// 리다이렉트 분기
useEffect(() => {
// 리액트 쿼리 페칭중이라면 return
if (isAuthChecked === null || isLoading) return;

const AUTH_PATHS = [
'/login',
Expand All @@ -44,25 +51,25 @@ export default function AuthProvicder({ children }: Props) {
);

// 로그인 상태일때
if (isLoggingIn) {
if (isAuthChecked) {
// auth 페이지(로그인/회원가입) 라우트시
if (isAuthPath) {
router.replace('/');
}
}

// 로그인 상태가 아닐 때
if (!isLoggingIn) {
if (!isAuthChecked) {
// 프로텍트 페이지 (/, /detail, /login, /signup 제외) 접속시
if (!isPublicPath && !isAuthPath) {
// 로그인페이지로 리다이렉트, 이때 params에 ?&redirect_uri=현재페이지경로 를 추가해줘서 로그인 완료시 접속시도했던 페이지로 리다이렉트되도록 처리
router.replace(`/login?redirect_path=${pathname}`);
}
}
}, [pathname, router, accessToken, isLoading, userInfo]);
}, [pathname, router, isLoading, isAuthChecked]);

// // accessToken이 undefined 이거나 리액트 쿼리 페칭중이라면 로딩 보여주기.
if (accessToken === undefined || isLoading)
// 리액트 쿼리 페칭중이라면 로딩 보여주기.
if (isLoading)
return (
<div className='flex items-center justify-center w-screen h-screen'>
<LoadingSpinner />
Expand Down
4 changes: 4 additions & 0 deletions src/components/primitives/toast/ToastContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
'use client';

import Toast from '@/src/components/primitives/toast/Toast';
import { useToastStore } from '@/src/store/useToastStore';
import { createPortal } from 'react-dom';

export default function ToastContainer() {
const toasts = useToastStore((state) => state.toasts);

if (toasts.length === 0) return;

return createPortal(
<div className='fixed top-0 left-0 right-0 h-0 flex justify-center z-50'>
{toasts.map((toast, idx) => (
Expand Down
9 changes: 2 additions & 7 deletions src/store/useTokenStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';

interface TokenState {
accessToken: null | string | undefined;
accessToken: null | string;
setAccessToken: (token: string | null) => void;
deleteAccessToken: () => void;
}
Expand All @@ -11,19 +11,14 @@ export const useTokenStore = create<TokenState>()(
persist(
(set) => {
return {
accessToken: undefined,
accessToken: null,
setAccessToken: (token) => set({ accessToken: token }),
deleteAccessToken: () => set({ accessToken: null }),
};
},
{
name: 'accessToken',
storage: createJSONStorage(() => localStorage),
onRehydrateStorage: () => (state) => {
if (state?.accessToken === undefined) {
state?.setAccessToken(null);
}
},
}
)
);