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
47 changes: 47 additions & 0 deletions public/icons/AlertCircleIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { SVGProps } from 'react';

interface AlertCircleIconProps extends SVGProps<SVGSVGElement> {
width?: number;
height?: number;
}

function AlertCircleIcon({
width = 50,
height = 50,
...props
}: AlertCircleIconProps) {
return (
<svg
width={width}
height={height}
viewBox="0 0 50 50"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M24.9994 45.8337C36.5053 45.8337 45.8327 36.5063 45.8327 25.0003C45.8327 13.4944 36.5053 4.16699 24.9994 4.16699C13.4934 4.16699 4.16602 13.4944 4.16602 25.0003C4.16602 36.5063 13.4934 45.8337 24.9994 45.8337Z"
stroke="#00A991"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M25 16.667V26.667"
stroke="#00A991"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M25 33.333H25.0208"
stroke="#00A991"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

export default AlertCircleIcon;
1 change: 1 addition & 0 deletions public/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { default as OnlineIcon } from './OnlineIcon';
export { default as MessageIcon } from './MessageIcon';
export { default as PencilIcon } from './PencilIcon';
export { default as IcCheckOnly } from './IcCheckOnly';
export { default as AlertCircleIcon } from './AlertCircleIcon';
Binary file added public/images/errorImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/app/bookclub/create/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import ErrorTemplate from '@/components/error/ErrorTemplate';

export default function BookClubCreateError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<ErrorTemplate
error={error}
reset={reset}
title="북클럽 생성 오류"
message="북클럽 생성 페이지 정보를 불러오던 중 에러가 발생했습니다."
/>
);
}
42 changes: 42 additions & 0 deletions src/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import Button from '@/components/button/Button';
import Image from 'next/image';

export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="ko">
<head></head>
<body>
<div className="flex h-screen flex-col items-center justify-center gap-4">
<Image
src="/images/errorImage.png"
alt="에러 이미지"
width={300}
height={200}
priority
/>
<h2 className="text-xl font-bold">치명적인 오류가 발생했습니다</h2>
<p className="text-gray-600">
{error.message || '서비스에 문제가 발생했습니다'}
</p>

<Button
onClick={reset}
text="다시 시도"
size="small"
fillType="solid"
themeColor="green-normal-01"
className="hover-dim"
/>
</div>
</body>
</html>
);
}
32 changes: 32 additions & 0 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Button from '@/components/button/Button';
import Link from 'next/link';
import Image from 'next/image';

export default function NotFound() {
return (
<div className="flex h-[calc(100vh-80px)] flex-col items-center justify-center gap-4">
<Image
src="/images/errorImage.png"
alt="에러 이미지"
width={300}
height={200}
priority
/>

<div className="flex flex-col items-center justify-center gap-2">
<h2 className="text-xl font-bold">페이지를 찾을 수 없습니다</h2>
<p className="text-gray-600">요청하신 페이지가 존재하지 않습니다</p>
</div>

<Link href="/">
<Button
text="홈으로 돌아가기"
size="small"
fillType="solid"
themeColor="green-normal-01"
className="hover-dim"
/>
</Link>
</div>
);
}
72 changes: 72 additions & 0 deletions src/components/error/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client';

import { Component, ReactNode, ErrorInfo, ComponentType } from 'react';

interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}

export interface FallbackProps {
error: Error | null;
resetErrorBoundary: () => void;
}

type ErrorBoundaryProps = {
FallbackComponent: ComponentType<FallbackProps>;
onReset: () => void;
children: ReactNode;
};

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);

this.state = {
hasError: false,
error: null,
};

this.resetErrorBoundary = this.resetErrorBoundary.bind(this);
}

/** 에러 상태 변경 */
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
console.log({ error, errorInfo });
}

/** 에러 상태 기본 초기화 */
resetErrorBoundary(): void {
this.props.onReset();

this.setState({
hasError: false,
error: null,
});
}

render() {
const { state, props } = this;

const { hasError, error } = state;

const { FallbackComponent, children } = props;

if (hasError && error) {
return (
<FallbackComponent
error={error}
resetErrorBoundary={this.resetErrorBoundary}
/>
);
}

return children;
}
}

export default ErrorBoundary;
32 changes: 32 additions & 0 deletions src/components/error/ErrorFallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import Button from '@/components/button/Button';
import { FallbackProps } from './ErrorBoundary';
import { AlertCircleIcon } from '../../../public/icons';

export default function ErrorFallback({
error,
resetErrorBoundary,
}: FallbackProps) {
return (
<div className="flex h-[calc(100vh-80px)] flex-col items-center gap-3">
<AlertCircleIcon className="mb-2" />
{error && (
<h2 className="text-md font-semibold">
요청을 처리하는 과정에서 오류가 발생했습니다. 다시 시도해주세요.
</h2>
)}

<Button
onClick={resetErrorBoundary}
text="다시 시도"
size="small"
fillType="solid"
themeColor="green-normal-01"
className="hover-dim"
>
다시 시도
</Button>
</div>
);
}
27 changes: 27 additions & 0 deletions src/components/error/ErrorHandlingWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { ComponentType, ReactNode, Suspense } from 'react';
import ErrorBoundary, { FallbackProps } from './ErrorBoundary';

interface ErrorHandlingWrapperProps {
children: ReactNode;
fallbackComponent: ComponentType<FallbackProps>;
suspenseFallback: ReactNode;
}

export default function ErrorHandlingWrapper({
children,
fallbackComponent: FallbackComponent,
suspenseFallback,
}: ErrorHandlingWrapperProps) {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} FallbackComponent={FallbackComponent}>
<Suspense fallback={suspenseFallback}>{children}</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
46 changes: 46 additions & 0 deletions src/components/error/ErrorTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import Button from '@/components/button/Button';
import Image from 'next/image';

interface ErrorTemplateProps {
error: Error;
reset: () => void;
title?: string;
message?: string;
children?: React.ReactNode;
}

export default function ErrorTemplate({
error,
reset,
title = '오류가 발생했습니다',
message,
children,
}: ErrorTemplateProps) {
return (
<div className="flex h-[calc(100vh-80px)] flex-col items-center justify-center gap-4">
<Image
src="/images/errorImage.png"
alt="에러 이미지"
width={300}
height={200}
/>
<h2 className="text-xl font-bold">{title}</h2>
<p className="text-gray-600">{message || error.message}</p>

<Button
onClick={reset}
text="다시 시도"
size="small"
fillType="solid"
themeColor="green-normal-01"
className="hover-dim"
>
다시 시도
</Button>

{children}
</div>
);
}
Loading
Loading