Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 30 additions & 11 deletions apps/web/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
const API_BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000';

export async function apiFetch<T>(endpoint: string): Promise<T> {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
headers: { 'Content-Type': 'application/json' },
});
export class ApiError extends Error {
status?: number;

if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(
(error as Record<string, string>)?.message ?? `Request failed: ${response.status}`
);
constructor(message: string, status?: number) {
super(message);
this.name = 'ApiError';
this.status = status;
}

return response.json() as Promise<T>;
}

export async function apiFetch<T>(endpoint: string): Promise<T> {
try {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
headers: { 'Content-Type': 'application/json' },
});

if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new ApiError(
(error as Record<string, string>)?.message ?? `Request failed: ${response.status}`,
response.status
);
}

return response.json() as Promise<T>;
} catch (error) {
if (error instanceof ApiError) {
throw error;
}

throw new ApiError('Unable to connect to the server');
}
}
19 changes: 13 additions & 6 deletions apps/web/src/pages/CardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import type { PublicCard } from '../shared';
import { apiFetch } from '../lib/api';
import { ApiError, apiFetch } from '../lib/api';
import './CardPage.css';

function getPlatformColor(platform: string): string {
Expand Down Expand Up @@ -30,9 +30,12 @@ export default function CardPage() {
setCard(data);
setError(null);
})
.catch(() => {
.catch((error) => {
setCard(null);
setError('Card not found');
setError(error instanceof ApiError && error.status === 404
? 'Card not found'
: 'Unable to load card. Please try again later.'
);
})
.finally(() => setLoading(false));
}, [id]);
Expand All @@ -42,7 +45,7 @@ export default function CardPage() {
if (card) {
document.title = `${card.title} | ${card.owner.displayName}`;
} else if (error) {
document.title = 'Card Not Found | DevCard';
document.title = `${error} | DevCard`;
}
}, [card, error]);

Expand All @@ -67,8 +70,12 @@ export default function CardPage() {
<div className="card-wrapper">
<div className="error-glass glass">
<div className="error-emoji">😕</div>
<h1>Card not found</h1>
<p>This DevCard doesn't exist or has been removed.</p>
<h1>{error}</h1>
<p>
{error === 'Card not found'
? "This DevCard doesn't exist or has been removed."
: 'There was a problem connecting to the server.'}
</p>
<Link to="/" className="btn-primary">Return Home</Link>
</div>
</div>
Expand Down
19 changes: 13 additions & 6 deletions apps/web/src/pages/ProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import { PLATFORMS, getProfileUrl } from '../shared';
import type { PublicProfile } from '../shared';
import { apiFetch } from '../lib/api';
import { ApiError, apiFetch } from '../lib/api';
import './ProfilePage.css';

const platformColors: Record<string, string> = {
Expand Down Expand Up @@ -34,9 +34,12 @@ export default function ProfilePage() {
setProfile(data);
setError(null);
})
.catch(() => {
.catch((error) => {
setProfile(null);
setError('User not found');
setError(error instanceof ApiError && error.status === 404
? 'User not found'
: 'Unable to load profile. Please try again later.'
);
})
.finally(() => setLoading(false));
}, [username]);
Expand All @@ -63,7 +66,7 @@ export default function ProfilePage() {
if (profile) {
document.title = `${profile.displayName} | DevCard`;
} else if (error) {
document.title = 'User Not Found | DevCard';
document.title = `${error} | DevCard`;
}
}, [profile, error]);

Expand Down Expand Up @@ -92,8 +95,12 @@ export default function ProfilePage() {
<main className="profile-container loaded">
<div className="error-glass glass">
<div className="error-emoji">😕</div>
<h1>Profile not found</h1>
<p>This DevCard has vanished into the digital void.</p>
<h1>{error}</h1>
<p>
{error === 'User not found'
? 'This DevCard has vanished into the digital void.'
: 'There was a problem connecting to the server.'}
</p>
<Link to="/" className="btn-primary">Return Home</Link>
</div>
</main>
Expand Down