|
1 | 1 | 'use client'; |
2 | 2 |
|
3 | | -import React from 'react'; |
4 | | -import {Button} from "@nextui-org/react"; |
5 | | -import {useRouter} from "next/navigation"; |
| 3 | +import { useState, useEffect } from 'react'; |
| 4 | +import { Button } from "@nextui-org/react"; |
6 | 5 | import Image from 'next/image'; |
| 6 | + |
| 7 | +// components |
| 8 | +import Forbidden from '@/app/forbidden'; |
| 9 | +import Unauthorized from '@/app/unauthorized'; |
| 10 | + |
| 11 | +// resource |
7 | 12 | import gdgocIcon from "@public/src/images/GDGoC_icon.png"; |
8 | 13 |
|
9 | | -export default function Error({error, reset}) { |
10 | | - const router = useRouter(); |
11 | | - const errorCode = error?.statusCode || error?.status || 'Internal Server'; |
| 14 | +/** |
| 15 | + * @typedef {Object} ErrorProps |
| 16 | + * @property {Error} error - The error object |
| 17 | + * @property {() => void} reset - Function to reset the error state |
| 18 | + */ |
| 19 | +export default function Error({ error, reset = () => {} }) { |
| 20 | + const [countdown, setCountdown] = useState(5); |
| 21 | + const errorCode = error?.statusCode || error?.status || 500; |
| 22 | + const errorTitle = error?.title || 'Internal Server Error'; |
12 | 23 | const errorMessage = error?.message || 'Unknown Error has occurred'; |
13 | 24 |
|
| 25 | + useEffect(() => { |
| 26 | + if (process.env.NODE_ENV === 'development') { |
| 27 | + console.log(error || 'Unknown Error has occurred'); |
| 28 | + } |
| 29 | + }, []); |
| 30 | + |
| 31 | + useEffect(() => { |
| 32 | + if (countdown > 0) { |
| 33 | + const timer = setTimeout(() => setCountdown(countdown - 1), 1000); |
| 34 | + return () => clearTimeout(timer); |
| 35 | + } |
| 36 | + }, [countdown]); |
| 37 | + |
14 | 38 | const handleClick = () => { |
15 | 39 | reset(); |
16 | 40 | window.location.reload(); |
17 | 41 | }; |
18 | 42 |
|
| 43 | + // Handle specific error codes |
| 44 | + if (errorCode === 401) { |
| 45 | + return <Unauthorized />; |
| 46 | + } |
| 47 | + |
| 48 | + if (errorCode === 403) { |
| 49 | + return <Forbidden />; |
| 50 | + } |
| 51 | + |
19 | 52 | return ( |
20 | | - <div className="flex flex-col items-center justify-center min-h-screen relative"> |
21 | | - <div className="absolute inset-0 flex items-center justify-center opacity-35 pointer-events-none z-0"> |
22 | | - <Image |
23 | | - src={gdgocIcon} |
24 | | - alt="GDGoC Icon" |
25 | | - width={500} |
26 | | - height={500} |
27 | | - /> |
| 53 | + <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-b from-gray-900 to-black relative overflow-hidden"> |
| 54 | + {/* 아이콘 배경 */} |
| 55 | + <div className="absolute inset-0 flex items-center justify-center opacity-10 pointer-events-none z-0" aria-hidden="true"> |
| 56 | + <div className="relative"> |
| 57 | + <Image |
| 58 | + src={gdgocIcon} |
| 59 | + alt="GDGoC Icon" |
| 60 | + width={600} |
| 61 | + height={600} |
| 62 | + className="animate-pulse" |
| 63 | + priority |
| 64 | + /> |
| 65 | + </div> |
28 | 66 | </div> |
29 | 67 |
|
30 | | - <div className="z-10"> |
31 | | - <div className="flex flex-col items-center justify-center"> |
32 | | - <h1 className="text-3xl font-bold text-white">{errorCode} Error</h1> |
33 | | - <p className="mt-2 text-lg text-white">페이지 로드 중 에러가 발생하였습니다.</p> |
34 | | - <p className="text-lg text-white">개발팀으로 연락 바랍니다!</p> |
35 | | - {/* only at development env */} |
| 68 | + {/* 오류 박스 */} |
| 69 | + <div className="z-10 bg-gray-800/70 backdrop-blur-sm p-4 sm:p-8 rounded-xl shadow-2xl border border-red-500/20 max-w-[90%] sm:max-w-md w-full mx-4"> |
| 70 | + <div className="flex flex-col items-center justify-center text-center"> |
| 71 | + {/* 오류 아이콘 */} |
| 72 | + <div className="w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-red-500/10 flex items-center justify-center mb-4 sm:mb-6" role="img" aria-label="Error icon"> |
| 73 | + <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 sm:h-10 sm:w-10 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 74 | + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> |
| 75 | + </svg> |
| 76 | + </div> |
| 77 | + |
| 78 | + {/* 오류 내용 */} |
| 79 | + <h1 className="text-3xl sm:text-4xl font-bold text-white mb-2"> |
| 80 | + <p className="text-red-500 text-2xl sm:text-3xl md:text-4xl lg:text-5xl">{errorCode}</p> |
| 81 | + <span className="block text-white text-base sm:text-lg md:text-xl lg:text-2xl">{errorTitle}</span> |
| 82 | + </h1> |
| 83 | + <div className="w-12 sm:w-16 h-1 bg-gradient-to-r from-red-500 to-red-400 rounded-full mb-3 sm:mb-4" aria-hidden="true"></div> |
| 84 | + |
| 85 | + <div className="bg-red-500/10 py-3 px-6 rounded-lg border border-red-500/20"> |
| 86 | + <p className="text-base sm:text-lg text-red-100">페이지 로드 중 에러가 발생하였습니다.</p> |
| 87 | + <p className="text-base sm:text-lg text-red-100">Tech팀으로 연락 바랍니다!</p> |
| 88 | + </div> |
| 89 | + |
| 90 | + {/* 개발 환경에서만 표시 */} |
36 | 91 | {process.env.NODE_ENV === 'development' && ( |
37 | | - <div className="mt-4 p-4 bg-[#1f1f1f] rounded-lg max-w-md"> |
38 | | - <p className="text-yellow-500 text-sm font-mono break-words">{errorMessage}</p> |
| 92 | + <div className="mt-2 p-3 sm:p-4 bg-red-500/5 rounded-lg border border-red-500/20 max-w-[90%] sm:max-w-md w-full overflow-auto"> |
| 93 | + <p className="text-yellow-400 text-xs sm:text-sm font-mono break-words">{errorMessage}</p> |
39 | 94 | </div> |
40 | 95 | )} |
| 96 | + |
| 97 | + {/* 버튼 */} |
41 | 98 | <Button |
42 | 99 | onPress={handleClick} |
43 | | - className="mt-8 w-72 max-w-sm h-12 bg-red-500 text-white text-lg font-semibold rounded-lg" |
| 100 | + className="mt-6 sm:mt-8 w-full max-w-[280px] h-10 sm:h-12 bg-gradient-to-r from-red-500 to-red-400 hover:from-red-400 hover:to-red-500 text-white text-base sm:text-lg font-semibold rounded-lg transition-all duration-300 shadow-lg hover:shadow-red-500/30" |
| 101 | + aria-label={`다시 시도하기 ${countdown > 0 ? `(${countdown}초)` : ''}`} |
44 | 102 | > |
45 | | - 다시 시도하기 |
| 103 | + 다시 시도하기 {countdown > 0 && `(${countdown}초)`} |
46 | 104 | </Button> |
| 105 | + |
| 106 | + {/* 작은 로고 */} |
| 107 | + <div className="mt-4 sm:mt-6"> |
| 108 | + <Image |
| 109 | + src={gdgocIcon} |
| 110 | + alt="GDGoC Small Icon" |
| 111 | + width={32} |
| 112 | + height={32} |
| 113 | + className="opacity-50" |
| 114 | + /> |
| 115 | + </div> |
47 | 116 | </div> |
48 | 117 | </div> |
49 | 118 | </div> |
|
0 commit comments