diff --git a/apps/docs/src/app/[locale]/(docs)/docs/error.tsx b/apps/docs/src/app/[locale]/(docs)/docs/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/(docs)/docs/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/(main)/(home)/page.tsx b/apps/docs/src/app/[locale]/(main)/(home)/page.tsx index fdbcec642..0794e36b2 100644 --- a/apps/docs/src/app/[locale]/(main)/(home)/page.tsx +++ b/apps/docs/src/app/[locale]/(main)/(home)/page.tsx @@ -7,6 +7,7 @@ import { ChevronRight } from "lucide-react"; import { Suspense } from "react"; import { AnimatedBeamHome } from "../../../../components/animated-beam/animated-beam-home"; +import { AnimatedBeamHomeSkeleton } from "../../../../components/animated-beam/animated-beam-home-skeleton"; import { AdminSection } from "./sections/admin/admin"; import { CallToActionSection } from "./sections/call-to-action"; import { PoweringBySection } from "./sections/powering-by/powering-by"; @@ -78,7 +79,7 @@ export default function HomePage() { - + }> diff --git a/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/sso/[providerId]/loading.tsx b/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/sso/[providerId]/loading.tsx new file mode 100644 index 000000000..9e5f25c4c --- /dev/null +++ b/apps/docs/src/app/[locale]/(main)/(plugins)/(vitnode-core)/login/sso/[providerId]/loading.tsx @@ -0,0 +1,10 @@ +import { Loader } from "@vitnode/core/components/ui/loader"; + +export default function Loading() { + return ( +
+ + Loading +
+ ); +} diff --git a/apps/docs/src/app/[locale]/(main)/error.tsx b/apps/docs/src/app/[locale]/(main)/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/(main)/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/admin/(auth)/error.tsx b/apps/docs/src/app/[locale]/admin/(auth)/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/admin/(auth)/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/[locale]/error.tsx b/apps/docs/src/app/[locale]/error.tsx new file mode 100644 index 000000000..f56b7d54c --- /dev/null +++ b/apps/docs/src/app/[locale]/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { + RouteErrorView, + type RouteErrorViewProps, +} from "@vitnode/core/views/error/route-error-view"; + +export default function ErrorBoundary({ error, retry }: RouteErrorViewProps) { + return ; +} diff --git a/apps/docs/src/app/global-error.tsx b/apps/docs/src/app/global-error.tsx index 786d47b66..ccb9d5ed2 100644 --- a/apps/docs/src/app/global-error.tsx +++ b/apps/docs/src/app/global-error.tsx @@ -9,6 +9,18 @@ const geist = Geist({ subsets: ["latin"], }); -export default function GlobalError() { - return ; +export default function GlobalError({ + error, + retry, +}: { + error: Error & { digest?: string }; + retry: () => void; +}) { + return ( + + ); } diff --git a/apps/docs/src/components/animated-beam/animated-beam-home-skeleton.tsx b/apps/docs/src/components/animated-beam/animated-beam-home-skeleton.tsx new file mode 100644 index 000000000..1d806011a --- /dev/null +++ b/apps/docs/src/components/animated-beam/animated-beam-home-skeleton.tsx @@ -0,0 +1,24 @@ +import { Skeleton } from "@vitnode/core/components/ui/skeleton"; + +const Row = ({ center }: { center?: boolean }) => ( +
+ + + +
+); + +export const AnimatedBeamHomeSkeleton = () => ( +
+
+ + + +
+ + Loading +
+); diff --git a/apps/docs/src/locales/@vitnode/core/pl.json b/apps/docs/src/locales/@vitnode/core/pl.json index f87acb19a..6c1886d04 100644 --- a/apps/docs/src/locales/@vitnode/core/pl.json +++ b/apps/docs/src/locales/@vitnode/core/pl.json @@ -220,6 +220,8 @@ "desc": "Przepraszamy, występują problemy techniczne po stronie serwera." }, "title": "Ups! Coś poszło nie tak.", + "try_again": "Spróbuj ponownie", + "reference": "Identyfikator błędu: {digest}", "internal_server_error": "Wewnętrzny błąd serwera.", "field_required": "To pole jest wymagane.", "field_min_length": "To pole musi mieć co najmniej {min} znaków.", @@ -409,8 +411,6 @@ "drop_here": "Upuść tutaj widżet", "empty_title": "Twój pulpit jest pusty", "empty_desc": "Dodaj widżety, aby mieć ważne dane zawsze pod ręką.", - "saved_title": "Pulpit zapisany", - "saved_desc": "Ten układ należy tylko do Ciebie - inni administratorzy mają swój własny.", "error_title": "Nie udało się zapisać pulpitu", "error_desc": "Coś poszło nie tak po drodze do serwera. Spróbuj ponownie.", "refresh_error": "Nie udało się odświeżyć tego widżetu. Odśwież stronę, aby zobaczyć zmiany.", @@ -433,8 +433,6 @@ "open": "Skonfiguruj {title}", "title": "Skonfiguruj {title}", "desc": "Te ustawienia dotyczą tylko tej karty - inni administratorzy mają swoje własne.", - "saved_title": "Ustawienia zapisane", - "saved_desc": "Karta użyje ich, gdy skończysz układać pulpit.", "error_title": "Nie udało się zapisać ustawień", "error_desc": "Coś poszło nie tak po drodze do serwera. Spróbuj ponownie.", "load_error": "Nie udało się wczytać tych ustawień. Zamknij i spróbuj ponownie." diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx index 786d47b66..ccb9d5ed2 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error copy.tsx @@ -9,6 +9,18 @@ const geist = Geist({ subsets: ["latin"], }); -export default function GlobalError() { - return ; +export default function GlobalError({ + error, + retry, +}: { + error: Error & { digest?: string }; + retry: () => void; +}) { + return ( + + ); } diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx index 786d47b66..ccb9d5ed2 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global-error.tsx @@ -9,6 +9,18 @@ const geist = Geist({ subsets: ["latin"], }); -export default function GlobalError() { - return ; +export default function GlobalError({ + error, + retry, +}: { + error: Error & { digest?: string }; + retry: () => void; +}) { + return ( + + ); } diff --git a/packages/vitnode/src/locales/en.json b/packages/vitnode/src/locales/en.json index 1d8bff561..b99b1795e 100644 --- a/packages/vitnode/src/locales/en.json +++ b/packages/vitnode/src/locales/en.json @@ -220,6 +220,8 @@ "desc": "Sorry, we're experiencing technical difficulties on our server." }, "title": "Oops! Something went wrong.", + "try_again": "Try again", + "reference": "Error reference: {digest}", "internal_server_error": "Internal server error.", "field_required": "This field is required.", "field_min_length": "This field must be at least {min} characters.", @@ -398,8 +400,6 @@ "drop_here": "Drop a widget here", "empty_title": "Your dashboard is empty", "empty_desc": "Add widgets to keep the numbers you care about one glance away.", - "saved_title": "Dashboard saved", - "saved_desc": "Your layout is yours alone - other admins keep theirs.", "error_title": "Could not save your dashboard", "error_desc": "Something went wrong on the way to the server. Please try again.", "refresh_error": "Could not reload this widget. Reload the page to see your changes.", @@ -422,8 +422,6 @@ "open": "Configure {title}", "title": "Configure {title}", "desc": "These settings apply to this card only - other admins keep their own.", - "saved_title": "Settings saved", - "saved_desc": "The card picks them up once you are done arranging the board.", "error_title": "Could not save the settings", "error_desc": "Something went wrong on the way to the server. Please try again.", "load_error": "Could not load these settings. Close this and try again." diff --git a/packages/vitnode/src/routes/main/login/sso/[providerId]/loading.tsx b/packages/vitnode/src/routes/main/login/sso/[providerId]/loading.tsx new file mode 100644 index 000000000..33c3aa582 --- /dev/null +++ b/packages/vitnode/src/routes/main/login/sso/[providerId]/loading.tsx @@ -0,0 +1,10 @@ +import { Loader } from "@/components/ui/loader"; + +export default function Loading() { + return ( +
+ + Loading +
+ ); +} diff --git a/packages/vitnode/src/views/admin/views/core/dashboard/grid/board-provider.tsx b/packages/vitnode/src/views/admin/views/core/dashboard/grid/board-provider.tsx index 576e5e199..e41b85448 100644 --- a/packages/vitnode/src/views/admin/views/core/dashboard/grid/board-provider.tsx +++ b/packages/vitnode/src/views/admin/views/core/dashboard/grid/board-provider.tsx @@ -272,7 +272,6 @@ export const DashboardBoardProvider = ({ } setIsEditing(false); - toast.success(t("saved_title"), { description: t("saved_desc") }); router.refresh(); }); }; diff --git a/packages/vitnode/src/views/admin/views/core/dashboard/grid/widget-settings-dialog.tsx b/packages/vitnode/src/views/admin/views/core/dashboard/grid/widget-settings-dialog.tsx index 37d661c02..6c0be9e87 100644 --- a/packages/vitnode/src/views/admin/views/core/dashboard/grid/widget-settings-dialog.tsx +++ b/packages/vitnode/src/views/admin/views/core/dashboard/grid/widget-settings-dialog.tsx @@ -121,14 +121,6 @@ export const WidgetSettingsDialog = ({ } setOpen(false); - toast.success(t("settings.saved_title"), { - description: t("settings.saved_desc"), - }); - - // Left until the dialog has finished closing. The card suspends - // while it is re-rendered, and a suspended render mid-animation - // strands the overlay on screen - batched into this transition it - // would also hold the close back until the new card was ready. setTimeout(onSaved, 300); } finally { resolve(); diff --git a/packages/vitnode/src/views/error/global-error-view.tsx b/packages/vitnode/src/views/error/global-error-view.tsx index a5d95df93..dea63c3a0 100644 --- a/packages/vitnode/src/views/error/global-error-view.tsx +++ b/packages/vitnode/src/views/error/global-error-view.tsx @@ -1,17 +1,35 @@ +"use client"; + import type { Metadata } from "next/dist/types"; +import { HomeIcon, RefreshCwIcon } from "lucide-react"; // eslint-disable-next-line no-restricted-imports import Link from "next/link"; +import { useTransition } from "react"; import { LogoVitNode } from "@/components/logo-vitnode"; import { ThemeProvider } from "@/components/theme-provider"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { cn } from "@/lib/utils"; export const metadata: Metadata = { title: "Error 500!", }; -export const GlobalErrorView = ({ className }: { className?: string }) => { +export interface GlobalErrorViewProps { + className?: string; + error: Error & { digest?: string }; + retry: () => void; +} + +export const GlobalErrorView = ({ + className, + error, + retry, +}: GlobalErrorViewProps) => { + const [isRetrying, startRetry] = useTransition(); + return ( @@ -22,16 +40,49 @@ export const GlobalErrorView = ({ className }: { className?: string }) => { -

+

Oops! Something went wrong.

- -

- An unexpected error occurred. Please try refreshing the page - or come back later. + +

+ An unexpected error occurred. Try again, and if it keeps + happening come back a little later.

+ + {error.digest ? ( +

+ Error reference: {error.digest} +

+ ) : null} + +
+ + + + + Back to home + +
diff --git a/packages/vitnode/src/views/error/route-error-view.tsx b/packages/vitnode/src/views/error/route-error-view.tsx new file mode 100644 index 000000000..0f20e9870 --- /dev/null +++ b/packages/vitnode/src/views/error/route-error-view.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { HomeIcon, RefreshCwIcon } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useTransition } from "react"; + +import { Button } from "@/components/ui/button"; +import { buttonVariants } from "@/components/ui/button"; +import { Link } from "@/lib/navigation"; +import { cn } from "@/lib/utils"; + +import { ErrorView } from "./error-view"; + +export interface RouteErrorViewProps { + error: Error & { digest?: string }; + retry: () => void; +} + +export const RouteErrorView = ({ error, retry }: RouteErrorViewProps) => { + const t = useTranslations("core.global"); + const [isRetrying, startRetry] = useTransition(); + + return ( + + + + + + {t("back_home")} + + + } + customDescription={ + <> + {t("errors.500.desc")} + {error.digest ? ( + + {t("errors.reference", { digest: error.digest })} + + ) : null} + + } + /> + ); +};