diff --git a/.env.example b/.env.example index 0b72555..507d2f7 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ VITE_API_BASE_URL=/mock VITE_YEAR=2024 VITE_UMAMI_WEBSITE_ID= +VITE_FEEDBACK_FORM_URL= +VITE_FEEDBACK_FORM_URL_PREFILL_ERROR_MESSAGE= diff --git a/package.json b/package.json index ed236b7..7654f43 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "mutative": "^1.0.8", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^4.0.13", "react-i18next": "^15.0.2", "simple-zustand-devtools": "^1.1.0", "sonner": "^1.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a62e095..ec6790f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-error-boundary: + specifier: ^4.0.13 + version: 4.0.13(react@18.3.1) react-i18next: specifier: ^15.0.2 version: 15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -3885,6 +3888,11 @@ packages: peerDependencies: react: ^18.3.1 + react-error-boundary@4.0.13: + resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} + peerDependencies: + react: '>=16.13.1' + react-i18next@15.0.2: resolution: {integrity: sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==} peerDependencies: @@ -9464,6 +9472,11 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-error-boundary@4.0.13(react@18.3.1): + dependencies: + '@babel/runtime': 7.25.7 + react: 18.3.1 + react-i18next@15.0.2(i18next@23.15.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.25.6 @@ -9505,7 +9518,7 @@ snapshots: react-textarea-autosize@8.5.3(@types/react@18.3.3)(react@18.3.1): dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.25.7 react: 18.3.1 use-composed-ref: 1.3.0(react@18.3.1) use-latest: 1.2.1(@types/react@18.3.3)(react@18.3.1) diff --git a/src/components/Error.tsx b/src/components/Error.tsx new file mode 100644 index 0000000..450bf9e --- /dev/null +++ b/src/components/Error.tsx @@ -0,0 +1,35 @@ +import { Code, Link } from '@nextui-org/react'; +import type { FallbackProps } from 'react-error-boundary'; + +import { useMount } from '../utils/mount'; + +export const Error = ({ error }: FallbackProps) => { + const errorMessage = String(error); + const prefilledFeedbackForm = `${import.meta.env.VITE_FEEDBACK_FORM_URL_PREFILL_ERROR_MESSAGE}+${errorMessage}%0ACause:+`; + + useMount(() => { + // Clear local data to reset the app + localStorage.clear(); + }); + + return ( +
+

Oops.. Something went wrong!

+

+ We're sorry, but we need to clear all saved data for this website + (including any locally stored courses and times). +

+ + Error Message:
+ {errorMessage} +
+

+ Before refreshing the page, would you like to{' '} + + send us feedback + {' '} + along with the error message to help improve our app? +

+
+ ); +}; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index b308f80..51d2bc4 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -54,7 +54,7 @@ export const Header = () => { diff --git a/src/main.tsx b/src/main.tsx index ed89f1d..4fe24f1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,10 +4,12 @@ import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import React from 'react'; import ReactDOM from 'react-dom/client'; +import { ErrorBoundary } from 'react-error-boundary'; import { mountStoreDevtool } from 'simple-zustand-devtools'; import { Toaster } from 'sonner'; import { App } from './App'; +import { Error } from './components/Error'; import { useEnrolledCourses } from './data/enrolled-courses'; import './i18n'; import './index.css'; @@ -35,12 +37,17 @@ if (import.meta.env.DEV) { ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - + + + + + + + + + , ); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index b66553a..1fdfed9 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -3,6 +3,8 @@ interface ImportMetaEnv { readonly VITE_API_BASE_URL: string; readonly VITE_YEAR: string; + readonly VITE_FEEDBACK_FORM_URL: string; + readonly VITE_FEEDBACK_FORM_URL_PREFILL_ERROR_MESSAGE: string; } interface ImportMeta {