diff --git a/src/components/ErrorBoundary.svelte b/src/components/ErrorBoundary.svelte new file mode 100644 index 0000000..f73bbed --- /dev/null +++ b/src/components/ErrorBoundary.svelte @@ -0,0 +1,167 @@ + + +
+
+
⚠️
+

{title}

+

{message}

+ + {#if details} + + + {#if showDetails} +
+ Error Information +
{JSON.stringify(details, null, 2)}
+
+ {/if} + {/if} + +
+ + Go Home +
+
+
+ + diff --git a/src/lib/companies.ts b/src/lib/companies.ts index abe0cf7..7f09e89 100644 --- a/src/lib/companies.ts +++ b/src/lib/companies.ts @@ -1,9 +1,16 @@ import api from "./axios"; export const company = { - async fetch() {}, + async fetch(accessToken: string, id: string) { + const company = await api.get(`/v1/company/${id}`, { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + return company.data; + }, async fetchMany(accessToken: string, page: number = 1) { - const companies = await api.get("/v1/company/companies", { + const companies = await api.get("/v1/companies", { params: { page, }, diff --git a/src/lib/errorHandler.ts b/src/lib/errorHandler.ts new file mode 100644 index 0000000..aeea98f --- /dev/null +++ b/src/lib/errorHandler.ts @@ -0,0 +1,59 @@ +import { error } from "@sveltejs/kit"; + +export class ApiError extends Error { + constructor( + public statusCode: number, + public message: string, + public details?: unknown, + ) { + super(message); + this.name = "ApiError"; + } +} + +export function handleApiError(err: unknown): never { + console.error("API Error:", err); + + if (err instanceof ApiError) { + throw error(err.statusCode, { + message: err.message, + details: err.details, + }); + } + + if (err instanceof Error) { + const message = err.message || "An unexpected error occurred"; + throw error(500, { + message, + details: err.stack, + }); + } + + throw error(500, { + message: "An unexpected error occurred", + details: String(err), + }); +} + +export function isNetworkError(err: unknown): boolean { + if (err instanceof Error) { + return ( + err.message.includes("Network") || + err.message.includes("fetch") || + err.message.includes("ECONNREFUSED") + ); + } + return false; +} + +export function isUnauthorizedError(err: unknown): boolean { + return err instanceof ApiError && err.statusCode === 401; +} + +export function isForbiddenError(err: unknown): boolean { + return err instanceof ApiError && err.statusCode === 403; +} + +export function isNotFoundError(err: unknown): boolean { + return err instanceof ApiError && err.statusCode === 404; +} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 0000000..bfcfa51 --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,169 @@ + + + + Error — App + + +
+

Error

+ +
+ +
+
+
+

Oops! Something went wrong

+

+ We encountered an error while processing your request. Please try again + or contact support if the problem persists. +

+
+ + Go Home +
+
+
+
+ + + + diff --git a/src/routes/companies/+page.svelte b/src/routes/companies/+page.svelte index 24d22ff..e13951e 100644 --- a/src/routes/companies/+page.svelte +++ b/src/routes/companies/+page.svelte @@ -2,6 +2,7 @@ import { goto } from "$app/navigation"; import { company } from "$lib"; import LoadingSpinner from "$lib/../components/LoadingSpinner.svelte"; + import ErrorBoundary from "$lib/../components/ErrorBoundary.svelte"; export let data; @@ -38,6 +39,7 @@ let totalRecords = 0; let isLoading = true; let error: string | null = null; + let errorDetails: unknown = null; let searchQuery = ""; const itemsPerPage = 30; @@ -45,11 +47,13 @@ async function loadCompanies(page: number = 1) { isLoading = true; error = null; + errorDetails = null; try { - const response = await company.fetchMany( - data.session.accessToken || "", - page, - ); + if (!data.session.accessToken) { + throw new Error("No access token available. Please log in again."); + } + + const response = await company.fetchMany(data.session.accessToken, page); if (response && response.data && Array.isArray(response.data)) { companies = response.data; @@ -60,11 +64,34 @@ Math.ceil(companies.length / itemsPerPage); currentPage = page; } else { - error = "Failed to load companies"; + throw new Error( + response?.message || + "Failed to load companies: Invalid response format", + ); } } catch (err) { console.error("Failed to fetch companies:", err); - error = "Error loading companies. Please try again."; + errorDetails = err; + + if (err instanceof Error) { + if ( + err.message.includes("401") || + err.message.includes("Unauthorized") + ) { + error = "Your session has expired. Please log in again."; + } else if ( + err.message.includes("403") || + err.message.includes("Forbidden") + ) { + error = "You don't have permission to view companies."; + } else if (err.message.includes("Network")) { + error = "Network error. Please check your connection and try again."; + } else { + error = err.message || "Error loading companies. Please try again."; + } + } else { + error = "An unexpected error occurred. Please try again."; + } } finally { isLoading = false; } @@ -87,6 +114,10 @@ goto("/logout"); } + function retryLoad() { + loadCompanies(currentPage); + } + // Load companies on component mount loadCompanies(); @@ -111,7 +142,11 @@ {#if isLoading} {:else if error} -

{error}

+ {:else}

Browse all companies. Total: {totalRecords} companies

{/if} diff --git a/src/routes/company/[id]/+page.server.ts b/src/routes/company/[id]/+page.server.ts new file mode 100644 index 0000000..8498ae8 --- /dev/null +++ b/src/routes/company/[id]/+page.server.ts @@ -0,0 +1,35 @@ +import { company } from "$lib/companies"; +import type { PageServerLoad } from "./$types"; +import { error } from "@sveltejs/kit"; + +export const load: PageServerLoad = async ({ params, parent }) => { + const { session } = await parent(); + + if (!session.accessToken) { + throw error(401, "Unauthorized: Access token required"); + } + + try { + const companyData = await company.fetch(session.accessToken, params.id); + + if (!companyData) { + throw error(404, `Company with ID ${params.id} not found`); + } + + return { + company: companyData, + }; + } catch (err) { + console.error("Failed to fetch company:", err); + + if (err instanceof Error && err.message.includes("404")) { + throw error(404, `Company with ID ${params.id} not found`); + } + + if (err instanceof Error && err.message.includes("401")) { + throw error(401, "Your session has expired. Please log in again."); + } + + throw error(500, "Failed to load company details. Please try again."); + } +}; diff --git a/src/routes/company/[id]/+page.svelte b/src/routes/company/[id]/+page.svelte new file mode 100644 index 0000000..3152d20 --- /dev/null +++ b/src/routes/company/[id]/+page.svelte @@ -0,0 +1,119 @@ + + + + Company Detail — App + + +
+

Company Detail

+ +
+ +
+ {#if error} + + {:else} +
+

API Response

+
{JSON.stringify(data.company, null, 2)}
+
+ {/if} +
+ +