@@ -4,99 +4,88 @@ import { Logo } from "@/components/logo";
44import { Badge } from "@/components/ui/badge" ;
55import { Button } from "@/components/ui/button" ;
66import { Card , CardContent , CardDescription , CardFooter , CardHeader , CardTitle } from "@/components/ui/card" ;
7- import { ERROR_NOT_FOUND , ERROR_NOT_IMPLEMENTED , ERROR_UNAUTHORIZED , ERROR_USER_VERIFIED } from "@/lib/apollo-errors" ;
7+ import {
8+ ERROR_FORBIDDEN ,
9+ ERROR_INVALID_INPUT ,
10+ ERROR_NOT_FOUND ,
11+ ERROR_NOT_IMPLEMENTED ,
12+ ERROR_UNAUTHORIZED ,
13+ } from "@/lib/apollo-errors" ;
814import { CombinedGraphQLErrors , CombinedProtocolErrors } from "@apollo/client" ;
9- import { AlertCircle , Code , Home , Lock , RefreshCw , Search , Shield , WifiOff } from "lucide-react" ;
15+ import { AlertCircle , Bug , Code , Home , Lock , type LucideIcon , Search , Shield } from "lucide-react" ;
1016import Link from "next/link" ;
11- import { useEffect } from "react" ;
1217
1318interface GlobalErrorProps {
1419 error : Error & { digest ?: string } ;
1520 reset : ( ) => void ;
1621}
1722
18- function getErrorInfo ( error : Error ) {
19- if ( CombinedProtocolErrors . is ( error ) ) {
20- // Network errors
21- if ( error && "statusCode" in error ) {
22- switch ( error . statusCode ) {
23- case 401 :
23+ type ErrorCommonInfo = {
24+ title : string ;
25+ description : string ;
26+ icon : LucideIcon ;
27+ } ;
28+
29+ type ErrorActionInfo = {
30+ actionName : string ;
31+ actionHref : string ;
32+ } ;
33+
34+ type ErrorInfo = ErrorCommonInfo & ( ErrorActionInfo | { actionName ?: undefined ; actionHref ?: undefined } ) ;
35+
36+ function getErrorInfo ( error : Error ) : ErrorInfo {
37+ // GraphQL errors with codes
38+ if ( CombinedGraphQLErrors . is ( error ) ) {
39+ const graphQLErrors = error . errors ;
40+
41+ if ( graphQLErrors && graphQLErrors . length > 0 ) {
42+ const firstError = graphQLErrors [ 0 ] ;
43+ const errorCode = firstError . extensions ?. code as string ;
44+
45+ switch ( errorCode ) {
46+ case ERROR_NOT_FOUND :
47+ return {
48+ title : "找不到資料" ,
49+ description : "請求的資料不存在或已被刪除。" ,
50+ icon : Search ,
51+ } ;
52+ case ERROR_UNAUTHORIZED :
2453 return {
2554 title : "未經授權" ,
26- description : "您的登入狀態已過期,請重新登入 。" ,
55+ description : "請登入後再試,或您的權限不足 。" ,
2756 icon : Lock ,
57+ actionName : "重新登入" ,
2858 actionHref : "/login" ,
2959 } ;
30- case 403 :
60+ case ERROR_FORBIDDEN :
61+ const missingScopes = firstError . message . replace ( / ^ n o s u f f i c i e n t s c o p e : / , "" ) ;
3162 return {
3263 title : "權限不足" ,
33- description : "您沒有權限執行此操作。" ,
64+ description : "您的帳號缺少權限,請聯絡管理員開通權限後重新登入:" + missingScopes ,
3465 icon : Shield ,
66+ actionName : "重新登入" ,
67+ actionHref : "/login" ,
3568 } ;
36- case 404 :
69+ case ERROR_NOT_IMPLEMENTED :
3770 return {
38- title : "找不到資源 " ,
39- description : "請求的資源不存在或已被移除 。" ,
40- icon : Search ,
71+ title : "功能未實作 " ,
72+ description : "此功能目前尚未實作,請稍後再試 。" ,
73+ icon : Code ,
4174 } ;
42- case 500 :
75+ case ERROR_INVALID_INPUT :
4376 return {
44- title : "伺服器錯誤 " ,
45- description : "伺服器發生內部錯誤,請稍後再試 。" ,
46- icon : AlertCircle ,
77+ title : "輸入無效 " ,
78+ description : "請聯絡開發者檢查 API 的輸入資料 。" ,
79+ icon : Bug ,
4780 } ;
4881 }
4982
5083 return {
51- title : "網路連線錯誤 " ,
52- description : "無法連接到伺服器,請檢查網路連線 。",
53- icon : WifiOff ,
84+ title : "GraphQL 查詢錯誤 " ,
85+ description : firstError . message || "GraphQL 查詢發生錯誤 。",
86+ icon : AlertCircle ,
5487 } ;
5588 }
56-
57- // GraphQL errors with codes
58- if ( CombinedGraphQLErrors . is ( error ) ) {
59- const graphQLErrors = error . errors ;
60-
61- if ( graphQLErrors && graphQLErrors . length > 0 ) {
62- const firstError = graphQLErrors [ 0 ] ;
63- const errorCode = firstError . extensions ?. code as string ;
64-
65- switch ( errorCode ) {
66- case ERROR_NOT_FOUND :
67- return {
68- title : "找不到資料" ,
69- description : "請求的資料不存在或已被刪除。" ,
70- icon : Search ,
71- } ;
72- case ERROR_UNAUTHORIZED :
73- return {
74- title : "未經授權" ,
75- description : "請登入後再試,或您的權限不足。" ,
76- icon : Lock ,
77- actionHref : "/login" ,
78- } ;
79- case ERROR_USER_VERIFIED :
80- return {
81- title : "帳號已驗證" ,
82- description : "此帳號已經完成驗證程序。" ,
83- icon : Shield ,
84- } ;
85- case ERROR_NOT_IMPLEMENTED :
86- return {
87- title : "功能未實作" ,
88- description : "此功能目前尚未實作,請稍後再試。" ,
89- icon : Code ,
90- } ;
91- }
92-
93- return {
94- title : "GraphQL 查詢錯誤" ,
95- description : firstError . message || "GraphQL 查詢發生錯誤。" ,
96- icon : AlertCircle ,
97- } ;
98- }
99- }
10089 }
10190
10291 // Regular JavaScript errors
@@ -107,14 +96,9 @@ function getErrorInfo(error: Error) {
10796 } ;
10897}
10998
110- export default function GlobalError ( { error, reset } : GlobalErrorProps ) {
99+ export default function GlobalError ( { error } : GlobalErrorProps ) {
111100 const errorInfo = getErrorInfo ( error ) ;
112101
113- useEffect ( ( ) => {
114- // Log error to monitoring service
115- console . error ( "Global error:" , error ) ;
116- } , [ error ] ) ;
117-
118102 return (
119103 < html >
120104 < body >
@@ -223,23 +207,14 @@ export default function GlobalError({ error, reset }: GlobalErrorProps) {
223207 sm:flex-row
224208 ` }
225209 >
226- < Button
227- onClick = { reset }
228- variant = "default"
229- className = { `flex items-center gap-2` }
230- >
231- < RefreshCw className = "size-4" />
232- 重試
233- </ Button >
234-
235210 { errorInfo . actionHref
236211 ? (
237212 < Button
238213 asChild
239214 variant = "outline"
240215 className = { `flex items-center gap-2` }
241216 >
242- < Link href = { errorInfo . actionHref } > 前往處理 </ Link >
217+ < Link href = { errorInfo . actionHref } > { errorInfo . actionName } </ Link >
243218 </ Button >
244219 )
245220 : (
@@ -270,11 +245,6 @@ export default function GlobalError({ error, reset }: GlobalErrorProps) {
270245 timeZone : "Asia/Taipei" ,
271246 } ) }
272247 </ p >
273- { error . digest && (
274- < p className = "text-red-600" >
275- 錯誤 ID:{ error . digest }
276- </ p >
277- ) }
278248 </ section >
279249 </ CardFooter >
280250 </ Card >
0 commit comments