Backend (updates-api): - Add links table schema with categories, visibility, sorting - Implement full CRUD API for links (/v1/links, /v1/admin/links) - Create admin UI for links management (/admin/links) - Add navigation between Updates and Links admin pages - Add comprehensive tests for links API (7/7 passing) Frontend (Next.js): - Create API client for fetching links from updates-api - Add LinksSection component with category icons - Integrate links into homepage (between Notes and CTA) - Add i18n entries for links section (DE/EN) - ISR caching: 60s for links, 5min for updates Features: - Links organized by category (external/internal/resource/tool) - Visibility toggle (show/hide on homepage) - Sort order support - Optional icons and descriptions - Responsive grid layout (1/2/3 columns) Testing: - All existing tests pass - New links API tests cover CRUD + validation - Production build successful Phase 1 Complete: Links management ready for deployment Next: Phase 2 (Markdown editor + media upload)
102 lines
2.4 KiB
TypeScript
102 lines
2.4 KiB
TypeScript
// API client for updates-api service
|
|
const UPDATES_API_URL = process.env.UPDATES_API_URL || 'http://localhost:8080';
|
|
|
|
export type Link = {
|
|
id: number;
|
|
title: string;
|
|
url: string;
|
|
category: 'external' | 'internal' | 'resource' | 'tool';
|
|
description: string | null;
|
|
icon: string | null;
|
|
sort_order: number;
|
|
};
|
|
|
|
export type Update = {
|
|
id: number;
|
|
slug: string;
|
|
product: 'batchmaker' | 'standalone';
|
|
title: string;
|
|
summary: string;
|
|
body_markdown: string;
|
|
published_at: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
link_url: string | null;
|
|
};
|
|
|
|
/**
|
|
* Fetch visible links from the updates-api
|
|
* Used for displaying links on the homepage
|
|
*/
|
|
export async function fetchLinks(): Promise<Link[]> {
|
|
try {
|
|
const res = await fetch(`${UPDATES_API_URL}/v1/links`, {
|
|
next: { revalidate: 60 }, // ISR: Cache 60s
|
|
});
|
|
|
|
if (!res.ok) {
|
|
console.error('Failed to fetch links:', res.status, res.statusText);
|
|
return [];
|
|
}
|
|
|
|
const data = await res.json();
|
|
return data.data || [];
|
|
} catch (error) {
|
|
console.error('Error fetching links:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch recent product updates
|
|
* @param product Filter by product (optional)
|
|
* @param limit Max number of updates (default: 5)
|
|
*/
|
|
export async function fetchUpdates(
|
|
product?: 'batchmaker' | 'standalone',
|
|
limit: number = 5
|
|
): Promise<Update[]> {
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (product) params.set('product', product);
|
|
params.set('limit', String(limit));
|
|
|
|
const res = await fetch(
|
|
`${UPDATES_API_URL}/v1/updates?${params}`,
|
|
{ next: { revalidate: 300 } } // ISR: Cache 5min
|
|
);
|
|
|
|
if (!res.ok) {
|
|
console.error('Failed to fetch updates:', res.status, res.statusText);
|
|
return [];
|
|
}
|
|
|
|
const data = await res.json();
|
|
return data.data || [];
|
|
} catch (error) {
|
|
console.error('Error fetching updates:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch a single update by slug
|
|
*/
|
|
export async function fetchUpdateBySlug(slug: string): Promise<Update | null> {
|
|
try {
|
|
const res = await fetch(`${UPDATES_API_URL}/v1/updates/${slug}`, {
|
|
next: { revalidate: 300 },
|
|
});
|
|
|
|
if (!res.ok) {
|
|
return null;
|
|
}
|
|
|
|
const data = await res.json();
|
|
return data.data || null;
|
|
} catch (error) {
|
|
console.error('Error fetching update:', error);
|
|
return null;
|
|
}
|
|
}
|