diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index e0a5d6601..205258f38 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -40,6 +40,7 @@ "oauth-open": "^1.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.11", "react-grid-layout": "^1.4.2", "react-helmet-async": "^1.3.0", "react-i18next": "^13.2.2", @@ -49,7 +50,8 @@ "react-virtuoso": "^4.6.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", - "use-seconds": "^1.7.0" + "use-seconds": "^1.7.0", + "usehooks-ts": "^2.9.1" }, "devDependencies": { "@iconify/json": "^2.2.126", @@ -7289,6 +7291,17 @@ "node": ">=6" } }, + "node_modules/react-error-boundary": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz", + "integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -9072,6 +9085,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "engines": { + "node": ">=16.15.0", + "npm": ">=8" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -14210,6 +14236,14 @@ } } }, + "react-error-boundary": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz", + "integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -15443,6 +15477,12 @@ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "requires": {} }, + "usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/packages/react/package.json b/packages/react/package.json index ce33131f6..be782a4e1 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -44,6 +44,7 @@ "oauth-open": "^1.0.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.11", "react-grid-layout": "^1.4.2", "react-helmet-async": "^1.3.0", "react-i18next": "^13.2.2", @@ -53,7 +54,8 @@ "react-virtuoso": "^4.6.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", - "use-seconds": "^1.7.0" + "use-seconds": "^1.7.0", + "usehooks-ts": "^2.9.1" }, "devDependencies": { "@iconify/json": "^2.2.126", diff --git a/packages/react/src/components/common/ErrorFallback.tsx b/packages/react/src/components/common/ErrorFallback.tsx new file mode 100644 index 000000000..88fcacbb9 --- /dev/null +++ b/packages/react/src/components/common/ErrorFallback.tsx @@ -0,0 +1,62 @@ +import { FallbackProps } from "react-error-boundary"; +import { Trans, useTranslation } from "react-i18next"; +import { TwitterFeed } from "./TwitterFeed"; +import { useRouteError } from "react-router-dom"; +import { Button } from "@/shadcn/ui/button"; +import { useAuth } from "@/hooks/useAuth"; + +export function ErrorFallback(props?: FallbackProps | {}) { + const routeError = useRouteError() as Error | null; + const { t } = useTranslation(); + const { logout } = useAuth(); + + // @ts-ignore + const error = routeError ?? (props?.error as Error | null); + + return ( +
+
+

{t("Gomenasorry!")}

+

+ + There was an error retrieving content, please check{" "} + + Twitter + {" "} + or report an error through the{" "} + + Discord + + . + +

+
+ + +
+ + {error?.message} + + + {error?.stack} + + +
+
+ ); +} diff --git a/packages/react/src/components/common/TwitterFeed.tsx b/packages/react/src/components/common/TwitterFeed.tsx new file mode 100644 index 000000000..2458bc7d3 --- /dev/null +++ b/packages/react/src/components/common/TwitterFeed.tsx @@ -0,0 +1,27 @@ +import { DetailedHTMLProps, HTMLAttributes, useEffect, useRef } from "react"; +import { useScript } from "usehooks-ts"; + +declare global { + // eslint-disable-next-line + var twttr: any; +} + +const html = `Tweets by Holodex`; + +export function TwitterFeed( + props: DetailedHTMLProps, HTMLDivElement>, +) { + const ref = useRef(null); + const status = useScript("https://platform.twitter.com/widgets.js"); + + useEffect(() => { + if (status === "ready") window.twttr?.widgets.load(); + }, [status]); + + return ( +
+ ); +} diff --git a/packages/react/src/components/layout/Frame.tsx b/packages/react/src/components/layout/Frame.tsx index a5532279c..252044d02 100644 --- a/packages/react/src/components/layout/Frame.tsx +++ b/packages/react/src/components/layout/Frame.tsx @@ -16,6 +16,8 @@ import { Header } from "@/components/header/header"; import { Toaster } from "@/shadcn/ui/toaster"; import clsx from "clsx"; import { orgAtom } from "@/store/org"; +import { ErrorFallback } from "../common/ErrorFallback"; +import { ErrorBoundary } from "react-error-boundary"; export function Frame() { const location = useLocation(); @@ -54,7 +56,9 @@ export function Frame() {