Skip to content
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
25 changes: 25 additions & 0 deletions src/components/common/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { cn } from '@/lib/utils';

interface LoadingProps {
size?: 'sm' | 'md' | 'lg' | 'xl';
className?: string;
}

const sizeClasses = {
sm: 'w-12 h-12',
md: 'w-14 h-14',
lg: 'w-16 h-16',
xl: 'w-20 h-20',
};

export function Loading({ size = 'lg', className }: LoadingProps) {
return (
<div
className={cn(
'animate-spin rounded-full border-4 border-muted border-t-primary',
sizeClasses[size],
className,
)}
/>
);
}
49 changes: 49 additions & 0 deletions src/components/common/LoadingOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useEffect, useState } from 'react';

import { useIsFetching, useIsMutating } from '@tanstack/react-query';

import { Loading } from './Loading';
import { Overlay } from './Overlay';

interface LoadingOverlayProps {
className?: string;
}

export function LoadingOverlay({ className }: LoadingOverlayProps) {
const MIN_DELAY = 200;

const [showLoading, setShowLoading] = useState(false);
const isFetching = useIsFetching();
const isMutating = useIsMutating();

const isLoading = isFetching > 0 || isMutating > 0;

useEffect(() => {
let timeout: ReturnType<typeof setTimeout> | null = null;

// 로딩중인 경우 로딩 보여줌
if (isLoading) {
setShowLoading(true);
return;
}

// 로딩 보여주고 있을 때, 최소 0.2초 이상 보여줌
if (showLoading) {
timeout = setTimeout(() => {
setShowLoading(false);
}, MIN_DELAY);
}

return () => {
if (timeout) clearTimeout(timeout);
};
}, [isLoading, showLoading]);

if (!showLoading) return null;

return (
<Overlay className={className}>
<Loading size={'lg'} />
</Overlay>
);
}
21 changes: 21 additions & 0 deletions src/components/common/Overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';

import { cn } from '@/lib/utils';

interface OverlayProps {
className?: string;
children?: React.ReactNode;
}

export function Overlay({ className, children }: OverlayProps) {
return (
<div
className={cn(
'fixed z-[60] bg-black/40 min-h-screen w-full flex items-center justify-center',
className,
)}
>
{children}
</div>
);
}
4 changes: 3 additions & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useRouter } from 'next/router';

import ErrorBoundary from '@/components/common/ErrorBoundary';
import Gnb from '@/components/common/Gnb';
import { LoadingOverlay } from '@/components/common/LoadingOverlay';
import { useInitUser } from '@/hooks/useInitUser';

import type { AppProps } from 'next/app';
Expand All @@ -26,7 +27,7 @@ export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();

const { pathname } = useRouter();
const pagesWithoutGnb = ['/signup', '/signin', '/oauth/kakao', '/oauth/signup/kakao'];
const pagesWithoutGnb = ['/signup', '/signin', '/oauth/signup/kakao'];
const hideHeader = pagesWithoutGnb.includes(pathname);
const isLanding = pathname === '/';
const is404 = pathname === '/404';
Expand All @@ -38,6 +39,7 @@ export default function App({ Component, pageProps }: AppProps) {
<meta name='viewport' content='width=device-width, initial-scale=1' />
</Head>
<QueryClientProvider client={queryClient}>
<LoadingOverlay />
<HydrationBoundary state={pageProps.dehydratedState}>
{!hideHeader && <Gnb />}
<ErrorBoundary fallback={<></>} router={router}>
Expand Down