|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import { captureException } from "@sentry/nextjs"; |
| 4 | +import clsx from "clsx"; |
| 5 | +import Link from "next/link"; |
| 6 | +import { useEffect, useState } from "react"; |
| 7 | +import { FallbackPre } from "./markdown/styledSyntaxHighlighter"; |
| 8 | +import { Heading } from "./markdown/heading"; |
| 9 | + |
| 10 | +interface Props { |
| 11 | + className?: string; |
| 12 | + h1?: boolean; |
| 13 | + back?: boolean; |
| 14 | + error: unknown; |
| 15 | + reset?: () => void; |
| 16 | +} |
| 17 | +export function ErrorMessage({ error, reset, ...props }: Props) { |
| 18 | + const [eventId, setEventId] = useState<string>(); |
| 19 | + useEffect(() => { |
| 20 | + setEventId(captureException(error)); |
| 21 | + }, [error]); |
| 22 | + |
| 23 | + const digest = |
| 24 | + "digest" in (error as { digest: string }) |
| 25 | + ? (error as { digest: string }).digest |
| 26 | + : undefined; |
| 27 | + |
| 28 | + return ( |
| 29 | + <div |
| 30 | + className={clsx( |
| 31 | + "w-full flex flex-col items-center justify-center", |
| 32 | + props.className |
| 33 | + )} |
| 34 | + > |
| 35 | + {props.h1 && <Heading level={1}>エラー</Heading>} |
| 36 | + <p className="my-2">ページの読み込み中にエラーが発生しました。</p> |
| 37 | + <FallbackPre className="mx-0! max-w-max whitespace-pre-wrap"> |
| 38 | + {error instanceof Error ? error.message : String(error)} |
| 39 | + </FallbackPre> |
| 40 | + {digest && ( |
| 41 | + <p className="my-1 text-sm text-base-content/50">Digest: {digest}</p> |
| 42 | + )} |
| 43 | + {eventId && ( |
| 44 | + <p className="my-1 text-sm text-base-content/50">EventID: {eventId}</p> |
| 45 | + )} |
| 46 | + {eventId && ( |
| 47 | + <a |
| 48 | + className="link link-info my-2" |
| 49 | + href={`https://docs.google.com/forms/d/e/1FAIpQLSfkM2LKhUDgCdY2fGntuv75O3jaWISwKuBIu9MW3h3UD1I3sw/viewform?usp=pp_url&entry.758323891=${eventId}${digest ? "/" + digest : ""}`} |
| 50 | + target="_blank" |
| 51 | + > |
| 52 | + 問い合わせフォームで報告する |
| 53 | + </a> |
| 54 | + )} |
| 55 | + {(props.back || reset) && ( |
| 56 | + <> |
| 57 | + <div className="divider w-full self-auto!" /> |
| 58 | + <div className="flex flex-row gap-4"> |
| 59 | + {reset && ( |
| 60 | + <button |
| 61 | + className="btn btn-warning" |
| 62 | + onClick={ |
| 63 | + // Attempt to recover by trying to re-render the segment |
| 64 | + () => reset() |
| 65 | + } |
| 66 | + > |
| 67 | + やりなおす |
| 68 | + </button> |
| 69 | + )} |
| 70 | + {props.back && ( |
| 71 | + <Link href="/" className="btn btn-primary"> |
| 72 | + トップに戻る |
| 73 | + </Link> |
| 74 | + )} |
| 75 | + </div> |
| 76 | + </> |
| 77 | + )} |
| 78 | + </div> |
| 79 | + ); |
| 80 | +} |
0 commit comments