feat(frontend): integrate error handling into application

App.svelte:
- Import and render ErrorModal component
- Add ErrorModal to main app layout below Header

CheckIfAniListLoggedInAndLoadWatchList.svelte:
- Import error state helpers (setApiError, clearApiError)
- Wrap LoadAniListUser in try-catch with error handling
- Wrap LoadAniListWatchList in try-catch with error handling
- Update CheckIfAniListLoggedInAndLoadWatchList with error handling
- Remove old alert() calls in favor of modal system

Home.svelte:
- Import isApiDown and apiError stores
- Add conditional rendering for API down state
- Display user-friendly "API Unavailable" message when apiError is set
- Show warning icon and helpful messaging

Error handling is now fully integrated across the frontend application.
This commit is contained in:
2026-03-30 20:08:34 -04:00
parent 2c7e2d0eff
commit ae54fd20dd
3 changed files with 93 additions and 27 deletions

View File

@@ -25,6 +25,7 @@
SimklGetUserWatchlist, SimklGetUserWatchlist,
} from "../wailsjs/go/main/App"; } from "../wailsjs/go/main/App";
import { loc } from "svelte-spa-router"; import { loc } from "svelte-spa-router";
import ErrorModal from "./helperComponents/ErrorModal.svelte";
onMount(async () => { onMount(async () => {
let isAniListLoggedIn: boolean; let isAniListLoggedIn: boolean;
@@ -57,6 +58,7 @@
</script> </script>
<Header /> <Header />
<ErrorModal />
<Router <Router
routes={{ routes={{
"/": Home, "/": Home,

View File

@@ -12,6 +12,8 @@
aniListLoggedIn, aniListLoggedIn,
aniListWatchlist, aniListWatchlist,
aniListSort, aniListSort,
clearApiError,
setApiError,
} from "./GlobalVariablesAndHelperFunctions.svelte"; } from "./GlobalVariablesAndHelperFunctions.svelte";
let isAniListPrimary: boolean; let isAniListPrimary: boolean;
@@ -25,23 +27,55 @@
aniListSort.subscribe((value) => (sort = value)); aniListSort.subscribe((value) => (sort = value));
export const LoadAniListUser = async () => { export const LoadAniListUser = async () => {
await GetAniListLoggedInUser().then((user) => { try {
aniListUser.set(user); await GetAniListLoggedInUser().then((user) => {
}); aniListUser.set(user);
});
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
setApiError(
"anilist",
`Failed to load user: ${errorMsg}`,
undefined,
true,
);
throw err;
}
}; };
export const LoadAniListWatchList = async () => { export const LoadAniListWatchList = async () => {
await GetAniListUserWatchingList(page, perPage, sort).then((watchList) => { try {
const watchList = await GetAniListUserWatchingList(page, perPage, sort);
aniListWatchlist.set(watchList); aniListWatchlist.set(watchList);
}); clearApiError();
}; } catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
export const CheckIfAniListLoggedInAndLoadWatchList = async () => { setApiError(
const loggedIn = await CheckIfAniListLoggedIn(); "anilist",
if (loggedIn) { `Failed to load watch list: ${errorMsg}`,
await LoadAniListUser(); undefined,
if (isAniListPrimary) await LoadAniListWatchList(); true,
);
throw err;
}
};
export const CheckIfAniListLoggedInAndLoadWatchList = async () => {
try {
const loggedIn = await CheckIfAniListLoggedIn();
if (loggedIn) {
await LoadAniListUser();
if (isAniListPrimary) await LoadAniListWatchList();
}
aniListLoggedIn.set(loggedIn);
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);
setApiError(
"anilist",
`Authentication failed: ${errorMsg}`,
undefined,
true,
);
aniListLoggedIn.set(false);
} }
aniListLoggedIn.set(loggedIn);
}; };
</script> </script>

View File

@@ -1,25 +1,55 @@
<script lang="ts"> <script lang="ts">
import Pagination from "../helperComponents/Pagination.svelte"; import Pagination from "../helperComponents/Pagination.svelte";
import WatchList from "../helperComponents/WatchList.svelte"; import WatchList from "../helperComponents/WatchList.svelte";
import { import {
aniListLoggedIn, aniListLoggedIn,
aniListPrimary, aniListPrimary,
loading, loading,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte"; isApiDown,
import loader from '../helperFunctions/loader' apiError,
} from "../helperModules/GlobalVariablesAndHelperFunctions.svelte";
import loader from "../helperFunctions/loader";
let isAniListPrimary: boolean let isAniListPrimary: boolean;
let isAniListLoggedIn: boolean let isAniListLoggedIn: boolean;
aniListPrimary.subscribe((value) => isAniListPrimary = value) aniListPrimary.subscribe((value) => (isAniListPrimary = value));
aniListLoggedIn.subscribe((value) => isAniListLoggedIn = value) aniListLoggedIn.subscribe((value) => (isAniListLoggedIn = value));
</script> </script>
{#if isAniListLoggedIn && isAniListPrimary}
<div class="container py-10"> {#if $isApiDown}
<div class="container py-10">
<div
class="bg-yellow-50 border border-yellow-200 rounded-lg p-6 text-center"
>
<svg
class="mx-auto h-12 w-12 text-yellow-600 mb-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<h2 class="text-xl font-semibold text-yellow-900 mb-2">
API Unavailable
</h2>
<p class="text-yellow-700 mb-4">
The {$apiError?.service || "service"} is currently unavailable. The app will
remain open, and you can retry when the service is back online.
</p>
</div>
</div>
{:else if isAniListLoggedIn && isAniListPrimary}
<div class="container py-10">
<Pagination /> <Pagination />
<WatchList /> <WatchList />
<Pagination /> <Pagination />
</div> </div>
{:else} {:else}
<div use:loader={loading}></div> <div use:loader={loading}></div>
{/if} {/if}