mirror of
https://github.com/ld3z/new-manga.git
synced 2026-11-09 20:53:45 -07:00
error improvements
This commit is contained in:
parent
3e897aeefa
commit
0277da93e5
4 changed files with 122 additions and 32 deletions
26
src/components/LoadingCard.astro
Normal file
26
src/components/LoadingCard.astro
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
---
|
||||
<div class="bg-secondary rounded-xl overflow-hidden shadow-lg h-full flex flex-col max-w-[280px] mx-auto border border-hover/30">
|
||||
<div class="relative overflow-hidden aspect-[2/3] rounded-t-xl bg-hover animate-pulse"></div>
|
||||
|
||||
<div class="p-4 md:p-5 flex-grow flex flex-col bg-secondary relative rounded-b-xl">
|
||||
<div class="absolute -top-8 left-3 right-3 bg-hover animate-pulse h-12 rounded-lg"></div>
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="bg-hover animate-pulse h-8 w-24 rounded-full"></div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{[1, 2, 3].map(() => (
|
||||
<div class="bg-hover animate-pulse h-6 w-16 rounded-full"></div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto pt-4 flex items-center justify-between">
|
||||
<div class="bg-hover animate-pulse h-4 w-32 rounded"></div>
|
||||
<div class="bg-hover animate-pulse h-4 w-20 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3,20 +3,43 @@ import ThemeToggle from '../components/ThemeToggle.astro';
|
|||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props;
|
||||
const {
|
||||
title,
|
||||
description = "Discover and track your favorite comics with ComicK-RSS"
|
||||
} = Astro.props;
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? 'https://comick-rss.vercel.app').toString();
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Astro description" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={canonicalURL} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={canonicalURL} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
</head>
|
||||
<body class="min-h-screen bg-primary text-primary">
|
||||
<ThemeToggle />
|
||||
|
|
@ -63,6 +86,7 @@ const { title } = Astro.props;
|
|||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
code {
|
||||
|
|
@ -76,4 +100,14 @@ const { title } = Astro.props;
|
|||
Courier New,
|
||||
monospace;
|
||||
}
|
||||
|
||||
/* Loading animation */
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: .5; }
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -20,12 +20,39 @@ const urlBase = "https://api.comick.fun/chapter";
|
|||
|
||||
let genreCache: Genre[] = [];
|
||||
|
||||
const RETRY_COUNT = 3;
|
||||
const RETRY_DELAY = 1000;
|
||||
|
||||
async function fetchWithRetry(url: string, options: RequestInit = {}, retries = RETRY_COUNT): Promise<Response> {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; ComicReader/1.0)',
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (retries > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
||||
return fetchWithRetry(url, options, retries - 1);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchGenres(): Promise<Genre[]> {
|
||||
if (genreCache.length > 0) return genreCache;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.comick.fun/genre');
|
||||
if (!response.ok) throw new Error(`Failed to fetch genres: ${response.status}`);
|
||||
const response = await fetchWithRetry('https://api.comick.fun/genre');
|
||||
genreCache = await response.json();
|
||||
return genreCache;
|
||||
} catch (error) {
|
||||
|
|
@ -40,21 +67,10 @@ export function getGenreNames(genreIds: number[], genres: Genre[]): string[] {
|
|||
.filter(name => name !== '');
|
||||
}
|
||||
|
||||
export async function fetchComics(language: string = 'en', contentType: string = 'sfw'): Promise<Comic[]> {
|
||||
export async function fetchComics(language: string = 'en', contentType: string = 'sfw', page: number = 1): Promise<Comic[]> {
|
||||
try {
|
||||
const apiUrl = `${urlBase}?lang=${language}&page=1&order=new&accept_erotic_content=${contentType === 'nsfw'}`;
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; ComicReader/1.0)',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API responded with status: ${response.status}`);
|
||||
}
|
||||
|
||||
const apiUrl = `${urlBase}?lang=${language}&page=${page}&order=new&accept_erotic_content=${contentType === 'nsfw'}`;
|
||||
const response = await fetchWithRetry(apiUrl);
|
||||
const data = await response.json();
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
import ComicCard from '../../components/ComicCard.astro';
|
||||
import LoadingCard from '../../components/LoadingCard.astro';
|
||||
import LanguageSelector from '../../components/LanguageSelector.astro';
|
||||
import RSSLink from '../../components/RSSLink.astro';
|
||||
import { fetchComics, isValidLanguage, isValidContentType, fetchGenres } from '../../lib/api';
|
||||
import { fetchComics, isValidLanguage, isValidContentType, fetchGenres, getLanguageName } from '../../lib/api';
|
||||
import type { Comic } from '../../lib/types';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
|
|
@ -34,9 +35,12 @@ try {
|
|||
console.error('Failed to fetch comics:', e);
|
||||
error = e instanceof Error ? e.message : 'Failed to fetch comics';
|
||||
}
|
||||
|
||||
const title = `ComicK - ${getLanguageName(lang!)} ${type!.toUpperCase()}`;
|
||||
const description = `Latest ${type!.toUpperCase()} comics in ${getLanguageName(lang!)}. Updated regularly with new chapters.`;
|
||||
---
|
||||
|
||||
<Layout title={`Comick - ${lang?.toUpperCase()} ${type?.toUpperCase()}`}>
|
||||
<Layout title={title} description={description}>
|
||||
<main class="mx-auto px-4 sm:px-6 lg:px-8 max-w-[1400px] text-primary">
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-center mb-8 sm:mb-12 bg-gradient-to-r from-accent via-accent-light to-white bg-clip-text text-transparent bg-[length:400%] bg-[0%]">
|
||||
ComicK-RSS
|
||||
|
|
@ -50,20 +54,30 @@ try {
|
|||
{error && (
|
||||
<div class="bg-red-500 text-white p-4 rounded-lg mb-8">
|
||||
<p>Error: {error}</p>
|
||||
<button
|
||||
class="mt-2 px-4 py-2 bg-white text-red-500 rounded hover:bg-red-50 transition-colors"
|
||||
onclick="window.location.reload()"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comics.length > 0 ? (
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
||||
{comics.map(comic => (
|
||||
{comics.length > 0 ? (
|
||||
comics.map(comic => (
|
||||
<ComicCard comic={comic} genres={genres} />
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
) : !error ? (
|
||||
Array(10).fill(null).map(() => (
|
||||
<LoadingCard />
|
||||
))
|
||||
) : (
|
||||
<div class="text-center bg-secondary p-6 rounded-lg">
|
||||
<div class="col-span-full text-center bg-secondary p-6 rounded-lg">
|
||||
<p class="mb-4">No comics found. Please try a different language or content type.</p>
|
||||
<p class="text-sm opacity-70">Language: {lang}, Type: {type}</p>
|
||||
<p class="text-sm opacity-70">Language: {getLanguageName(lang!)}, Type: {type!.toUpperCase()}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
Loading…
Add table
Add a link
Reference in a new issue