I GOT COMPANY API DATA ON THE PAGE AHHHHHHH

This commit is contained in:
2026-02-13 18:02:35 -06:00
parent 6b176196d3
commit 51db9de171
7 changed files with 600 additions and 9 deletions
+167
View File
@@ -0,0 +1,167 @@
<script lang="ts">
import { onMount } from "svelte";
interface Props {
title?: string;
message?: string;
details?: unknown;
}
let {
title = "Something went wrong",
message = "An unexpected error occurred",
details,
}: Props = $props();
let showDetails = $state(false);
onMount(() => {
if (details) {
console.error("Error details:", details);
}
});
function toggleDetails() {
showDetails = !showDetails;
}
function retry() {
location.reload();
}
</script>
<div class="error-boundary">
<div class="error-content">
<div class="error-icon">⚠️</div>
<h2>{title}</h2>
<p>{message}</p>
{#if details}
<button class="details-toggle" on:click={toggleDetails}>
{showDetails ? "Hide" : "Show"} Error Details
</button>
{#if showDetails}
<details class="error-details" open>
<summary>Error Information</summary>
<pre><code>{JSON.stringify(details, null, 2)}</code></pre>
</details>
{/if}
{/if}
<div class="error-actions">
<button class="btn btn-primary" on:click={retry}>Retry</button>
<a href="/" class="btn btn-secondary">Go Home</a>
</div>
</div>
</div>
<style>
.error-boundary {
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 8px;
margin: 2rem 0;
}
.error-content {
max-width: 600px;
text-align: center;
}
.error-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
h2 {
margin: 0 0 0.5rem;
color: #d32f2f;
font-size: 1.5rem;
}
p {
color: #666;
margin: 0 0 1.5rem;
line-height: 1.6;
}
.details-toggle {
background: none;
border: 1px solid #ffc107;
color: #856404;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
margin-bottom: 1rem;
font-size: 0.875rem;
}
.details-toggle:hover {
background: rgba(255, 193, 7, 0.1);
}
.error-details {
background: white;
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 1rem;
margin: 1rem 0;
text-align: left;
}
.error-details summary {
cursor: pointer;
color: #0066cc;
font-weight: bold;
margin-bottom: 0.5rem;
}
pre {
background: #f5f5f5;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
font-size: 0.75rem;
margin: 0;
}
.error-actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 1.5rem;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
}
.btn-primary {
background: #0066cc;
color: white;
}
.btn-primary:hover {
background: #0052a3;
}
.btn-secondary {
background: #e5e7eb;
color: #111;
}
.btn-secondary:hover {
background: #d1d5db;
}
</style>
+9 -2
View File
@@ -1,9 +1,16 @@
import api from "./axios"; import api from "./axios";
export const company = { 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) { async fetchMany(accessToken: string, page: number = 1) {
const companies = await api.get("/v1/company/companies", { const companies = await api.get("/v1/companies", {
params: { params: {
page, page,
}, },
+59
View File
@@ -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;
}
+169
View File
@@ -0,0 +1,169 @@
<script>
import { goto } from "$app/navigation";
function signOut() {
goto("/logout");
}
function goBack() {
history.back();
}
</script>
<svelte:head>
<title>Error — App</title>
</svelte:head>
<header class="header container">
<h1>Error</h1>
<nav>
<a href="/">Home</a>
<button on:click={signOut}>Sign out</button>
</nav>
</header>
<main class="container">
<section class="error-section">
<div class="error-box">
<h2>Oops! Something went wrong</h2>
<p class="error-message">
We encountered an error while processing your request. Please try again
or contact support if the problem persists.
</p>
<div class="error-actions">
<button class="btn btn-primary" on:click={goBack}>Go Back</button>
<a href="/" class="btn btn-secondary">Go Home</a>
</div>
</div>
</section>
</main>
<footer class="container">
<small>© {new Date().getFullYear()} Your App</small>
</footer>
<style>
:global(body) {
margin: 0;
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial;
background: #f7f7f8;
color: #111;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e5e7eb;
background: #fff;
}
nav {
display: flex;
gap: 0.5rem;
align-items: center;
}
nav a,
nav button {
padding: 0.5rem 1rem;
text-decoration: none;
color: #0066cc;
border: none;
background: none;
cursor: pointer;
font-size: 1rem;
}
nav a:hover,
nav button:hover {
text-decoration: underline;
}
main {
padding: 2rem 1rem;
}
.error-section {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.error-box {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 2rem;
max-width: 500px;
text-align: center;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.error-box h2 {
margin: 0 0 1rem;
color: #d32f2f;
font-size: 1.5rem;
}
.error-message {
color: #666;
margin: 1rem 0 2rem;
line-height: 1.6;
}
.error-actions {
display: flex;
gap: 1rem;
justify-content: center;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
}
.btn-primary {
background: #0066cc;
color: white;
}
.btn-primary:hover {
background: #0052a3;
}
.btn-secondary {
background: #e5e7eb;
color: #111;
}
.btn-secondary:hover {
background: #d1d5db;
}
footer {
text-align: center;
padding: 2rem 1rem;
border-top: 1px solid #e5e7eb;
color: #666;
}
</style>
+42 -7
View File
@@ -2,6 +2,7 @@
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { company } from "$lib"; import { company } from "$lib";
import LoadingSpinner from "$lib/../components/LoadingSpinner.svelte"; import LoadingSpinner from "$lib/../components/LoadingSpinner.svelte";
import ErrorBoundary from "$lib/../components/ErrorBoundary.svelte";
export let data; export let data;
@@ -38,6 +39,7 @@
let totalRecords = 0; let totalRecords = 0;
let isLoading = true; let isLoading = true;
let error: string | null = null; let error: string | null = null;
let errorDetails: unknown = null;
let searchQuery = ""; let searchQuery = "";
const itemsPerPage = 30; const itemsPerPage = 30;
@@ -45,11 +47,13 @@
async function loadCompanies(page: number = 1) { async function loadCompanies(page: number = 1) {
isLoading = true; isLoading = true;
error = null; error = null;
errorDetails = null;
try { try {
const response = await company.fetchMany( if (!data.session.accessToken) {
data.session.accessToken || "", throw new Error("No access token available. Please log in again.");
page, }
);
const response = await company.fetchMany(data.session.accessToken, page);
if (response && response.data && Array.isArray(response.data)) { if (response && response.data && Array.isArray(response.data)) {
companies = response.data; companies = response.data;
@@ -60,11 +64,34 @@
Math.ceil(companies.length / itemsPerPage); Math.ceil(companies.length / itemsPerPage);
currentPage = page; currentPage = page;
} else { } else {
error = "Failed to load companies"; throw new Error(
response?.message ||
"Failed to load companies: Invalid response format",
);
} }
} catch (err) { } catch (err) {
console.error("Failed to fetch companies:", 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 { } finally {
isLoading = false; isLoading = false;
} }
@@ -87,6 +114,10 @@
goto("/logout"); goto("/logout");
} }
function retryLoad() {
loadCompanies(currentPage);
}
// Load companies on component mount // Load companies on component mount
loadCompanies(); loadCompanies();
</script> </script>
@@ -111,7 +142,11 @@
{#if isLoading} {#if isLoading}
<LoadingSpinner loading={true} /> <LoadingSpinner loading={true} />
{:else if error} {:else if error}
<p class="error">{error}</p> <ErrorBoundary
title="Failed to Load Companies"
message={error}
details={errorDetails}
/>
{:else} {:else}
<p>Browse all companies. Total: {totalRecords} companies</p> <p>Browse all companies. Total: {totalRecords} companies</p>
{/if} {/if}
+35
View File
@@ -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.");
}
};
+119
View File
@@ -0,0 +1,119 @@
<script>
import { goto } from "$app/navigation";
import ErrorBoundary from "$lib/../components/ErrorBoundary.svelte";
export let data;
export let error;
function signOut() {
goto("/logout");
}
</script>
<svelte:head>
<title>Company Detail — App</title>
</svelte:head>
<header class="header container">
<h1>Company Detail</h1>
<nav>
<a href="/companies">Companies</a>
<a href="/">Home</a>
<button on:click={signOut}>Sign out</button>
</nav>
</header>
<main class="container">
{#if error}
<ErrorBoundary
title="Failed to Load Company"
message={error.message ||
"We couldn't load the company details. Please try again."}
details={error}
/>
{:else}
<section>
<h2>API Response</h2>
<pre><code>{JSON.stringify(data.company, null, 2)}</code></pre>
</section>
{/if}
</main>
<style>
:global(body) {
margin: 0;
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial;
background: #f7f7f8;
color: #111;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #e5e7eb;
background: #fff;
}
nav {
display: flex;
gap: 0.5rem;
align-items: center;
}
nav a,
nav button {
padding: 0.5rem 1rem;
text-decoration: none;
color: #0066cc;
border: none;
background: none;
cursor: pointer;
font-size: 1rem;
}
nav a:hover,
nav button:hover {
text-decoration: underline;
}
main {
padding: 2rem 1rem;
}
section {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 2rem;
}
h2 {
margin-top: 0;
}
pre {
background-color: #f5f5f5;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
margin: 0;
}
code {
font-family: "Courier New", monospace;
font-size: 0.875rem;
}
</style>