From 42575fbf3a4587eb8538d422f1ddfe476f67c421 Mon Sep 17 00:00:00 2001 From: John Corser Date: Thu, 2 Jan 2025 10:48:39 -0500 Subject: [PATCH] improve error handling --- .github/workflows/docker-publish.yml | 6 +- package-lock.json | 31 ++++++++++ package.json | 1 + src/App.tsx | 9 ++- src/components/pages/AudioReviewPage.tsx | 11 +++- src/components/pages/FavoriteActorsPage.tsx | 11 +++- src/components/pages/LiveTvReviewPage.tsx | 18 ++++-- src/components/pages/MoviesReviewPage.tsx | 11 +++- src/components/pages/MusicVideoPage.tsx | 16 ++++- src/components/pages/OldestMoviePage.tsx | 36 ++++++----- src/components/pages/OldestShowPage.tsx | 27 +++++--- .../pages/ServerConfigurationPage.tsx | 61 ++++++++++++------- src/components/pages/ShowReviewPage.tsx | 11 +++- src/lib/cache.ts | 41 +++++++++++++ src/lib/jellyfin-api.ts | 15 +++-- src/lib/playback-reporting-queries.ts | 19 +++--- 16 files changed, 242 insertions(+), 82 deletions(-) create mode 100644 src/lib/cache.ts diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 18337f2..dc3a076 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -3,7 +3,7 @@ name: Build and Publish Docker Image on: push: tags: - - 'v*.*.*' + - "v*.*.*" jobs: docker: @@ -19,8 +19,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' - cache: 'npm' + node-version: "18" + cache: "npm" - name: Install dependencies run: npm ci diff --git a/package-lock.json b/package-lock.json index dbb245e..dae688e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "lucide-react": "^0.469.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^5.0.0", "react-router-dom": "^7.1.1", "tailwind-merge": "^2.6.0" }, @@ -291,6 +292,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -5433,6 +5446,18 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -5575,6 +5600,12 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", diff --git a/package.json b/package.json index 7df58e6..0b71175 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "lucide-react": "^0.469.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^5.0.0", "react-router-dom": "^7.1.1", "tailwind-merge": "^2.6.0" }, diff --git a/src/App.tsx b/src/App.tsx index 7fb3828..56ecde6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import "@radix-ui/themes/styles.css"; +import { ErrorBoundary } from "react-error-boundary"; import { Theme } from "@radix-ui/themes"; import { createBrowserRouter, RouterProvider, Outlet } from "react-router-dom"; import SplashPage from "./components/pages/SplashPage"; @@ -15,9 +16,11 @@ import MusicVideoPage from "./components/pages/MusicVideoPage"; // Layout component that wraps all routes function RootLayout() { return ( - - - + Something went wrong}> + + + + ); } diff --git a/src/components/pages/AudioReviewPage.tsx b/src/components/pages/AudioReviewPage.tsx index 4d0e7b5..f068810 100644 --- a/src/components/pages/AudioReviewPage.tsx +++ b/src/components/pages/AudioReviewPage.tsx @@ -5,8 +5,10 @@ import { Container, Grid, Box, Spinner, Button } from "@radix-ui/themes"; import { motion } from "framer-motion"; import { styled } from "@stitches/react"; import { useNavigate } from "react-router-dom"; +import { useErrorBoundary } from "react-error-boundary"; export default function AudioReviewPage() { + const { showBoundary } = useErrorBoundary(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); @@ -15,8 +17,13 @@ export default function AudioReviewPage() { useEffect(() => { const setup = async () => { setIsLoading(true); - setAudios(await listAudio()); - setIsLoading(false); + try { + setAudios(await listAudio()); + } catch (e) { + showBoundary(e); + } finally { + setIsLoading(false); + } }; setup(); }, []); diff --git a/src/components/pages/FavoriteActorsPage.tsx b/src/components/pages/FavoriteActorsPage.tsx index ef560a3..f792b41 100644 --- a/src/components/pages/FavoriteActorsPage.tsx +++ b/src/components/pages/FavoriteActorsPage.tsx @@ -10,8 +10,10 @@ import { useNavigate } from "react-router-dom"; import { generateGuid } from "@/lib/utils"; import { BaseItemPerson } from "@jellyfin/sdk/lib/generated-client"; import { ActorCard } from "./MoviesReviewPage/ActorCard"; +import { useErrorBoundary } from "react-error-boundary"; export default function FavoriteActorsPage() { + const { showBoundary } = useErrorBoundary(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); const [favoriteActors, setFavoriteActors] = useState< @@ -27,8 +29,13 @@ export default function FavoriteActorsPage() { useEffect(() => { const setup = async () => { setIsLoading(true); - setFavoriteActors(await listFavoriteActors()); - setIsLoading(false); + try { + setFavoriteActors(await listFavoriteActors()); + } catch (e) { + showBoundary(e); + } finally { + setIsLoading(false); + } }; setup(); }, []); diff --git a/src/components/pages/LiveTvReviewPage.tsx b/src/components/pages/LiveTvReviewPage.tsx index 04c465e..ec4db2a 100644 --- a/src/components/pages/LiveTvReviewPage.tsx +++ b/src/components/pages/LiveTvReviewPage.tsx @@ -6,6 +6,7 @@ import { styled } from "@stitches/react"; import { Card, Text, Flex } from "@radix-ui/themes"; import { formatDuration } from "@/lib/utils"; import { useNavigate } from "react-router-dom"; +import { useErrorBoundary } from "react-error-boundary"; interface ChannelCardProps { channelName: string; @@ -34,6 +35,8 @@ export function ChannelCard({ channelName, duration }: ChannelCardProps) { } export default function LiveTvReviewPage() { + const { showBoundary } = useErrorBoundary(); + const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); const [channels, setChannels] = useState([]); @@ -41,11 +44,16 @@ export default function LiveTvReviewPage() { useEffect(() => { const setup = async () => { setIsLoading(true); - const channelData = await listLiveTvChannels(); - // Sort channels by duration in descending order - channelData.sort((a, b) => b.duration - a.duration); - setChannels(channelData); - setIsLoading(false); + try { + const channelData = await listLiveTvChannels(); + // Sort channels by duration in descending order + channelData.sort((a, b) => b.duration - a.duration); + setChannels(channelData); + } catch (e) { + showBoundary(e); + } finally { + setIsLoading(false); + } }; setup(); }, []); diff --git a/src/components/pages/MoviesReviewPage.tsx b/src/components/pages/MoviesReviewPage.tsx index 1282f8d..0ad6b1b 100644 --- a/src/components/pages/MoviesReviewPage.tsx +++ b/src/components/pages/MoviesReviewPage.tsx @@ -6,8 +6,10 @@ import { motion } from "framer-motion"; import { itemVariants, Title } from "../ui/styled"; import { useNavigate } from "react-router-dom"; import { generateGuid } from "@/lib/utils"; +import { useErrorBoundary } from "react-error-boundary"; export default function MoviesReviewPage() { + const { showBoundary } = useErrorBoundary(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); const [movies, setMovies] = useState([]); @@ -15,8 +17,13 @@ export default function MoviesReviewPage() { useEffect(() => { const setup = async () => { setIsLoading(true); - setMovies(await listMovies()); - setIsLoading(false); + try { + setMovies(await listMovies()); + } catch (error) { + showBoundary(error); + } finally { + setIsLoading(false); + } }; setup(); }, []); diff --git a/src/components/pages/MusicVideoPage.tsx b/src/components/pages/MusicVideoPage.tsx index 6d26213..ead6ebe 100644 --- a/src/components/pages/MusicVideoPage.tsx +++ b/src/components/pages/MusicVideoPage.tsx @@ -1,12 +1,17 @@ import { useState, useEffect } from "react"; -import { listMusicVideos, SimpleItemDto } from "@/lib/playback-reporting-queries"; +import { + listMusicVideos, + SimpleItemDto, +} from "@/lib/playback-reporting-queries"; import { MovieCard } from "./MoviesReviewPage/MovieCard"; import { Container, Grid, Box, Spinner, Button } from "@radix-ui/themes"; import { motion } from "framer-motion"; import { styled } from "@stitches/react"; import { useNavigate } from "react-router-dom"; +import { useErrorBoundary } from "react-error-boundary"; export default function MusicVideoPage() { + const { showBoundary } = useErrorBoundary(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); @@ -15,8 +20,13 @@ export default function MusicVideoPage() { useEffect(() => { const setup = async () => { setIsLoading(true); - setMusicVideos(await listMusicVideos()); - setIsLoading(false); + try { + setMusicVideos(await listMusicVideos()); + } catch (error) { + showBoundary(error); + } finally { + setIsLoading(false); + } }; setup(); }, []); diff --git a/src/components/pages/OldestMoviePage.tsx b/src/components/pages/OldestMoviePage.tsx index 40fb304..8efa1b2 100644 --- a/src/components/pages/OldestMoviePage.tsx +++ b/src/components/pages/OldestMoviePage.tsx @@ -6,8 +6,11 @@ import { motion } from "framer-motion"; import { styled } from "@stitches/react"; import { useNavigate } from "react-router-dom"; import { Subtitle } from "../ui/styled"; +import { useErrorBoundary } from "react-error-boundary"; export default function OldestMoviePage() { + const { showBoundary } = useErrorBoundary(); + const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); const [movie, setMovie] = useState(); @@ -15,15 +18,20 @@ export default function OldestMoviePage() { useEffect(() => { const setup = async () => { setIsLoading(true); - const movies = await listMovies(); - movies.sort((a, b) => { - const aDate = new Date(a.date!); - const bDate = new Date(b.date!); - return aDate.getTime() - bDate.getTime(); - }); - const m = movies.find((s) => s)!; - setMovie(m); - setIsLoading(false); + try { + const movies = await listMovies(); + movies.sort((a, b) => { + const aDate = new Date(a.date!); + const bDate = new Date(b.date!); + return aDate.getTime() - bDate.getTime(); + }); + const m = movies.find((s) => s)!; + setMovie(m); + } catch (e) { + showBoundary(e); + } finally { + setIsLoading(false); + } }; setup(); }, []); @@ -72,7 +80,8 @@ export default function OldestMoviePage() {
- It's {new Date().getFullYear()}, but you've time traveled back to {movie?.productionYear} + It's {new Date().getFullYear()}, but you've time traveled back to{" "} + {movie?.productionYear} {movie?.name} came out on{" "} - {new Date(movie?.date ?? '').toLocaleDateString(undefined, { + {new Date(movie?.date ?? "").toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric", })}
- +