diff --git a/__tests__/components/ArticleCard.test.tsx b/__tests__/components/ArticleCard.test.tsx new file mode 100644 index 0000000..458eb11 --- /dev/null +++ b/__tests__/components/ArticleCard.test.tsx @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest' +import { render, screen } from '@testing-library/react' +import ArticleCard from '@/components/article/ArticleCard' +import type { Article } from '@/lib/types' + +const mockArticle: Article = { + id: '1', + title: 'Frieren Season 2 Officially Announced', + slug: 'frieren-season-2-announced', + status: 'published', + content: null, + excerpt: 'The beloved series returns.', + featured_image: null, + published_at: '2026-05-28T00:00:00Z', + is_featured: false, + seo_title: null, + seo_description: null, + category: { id: '1', name: 'Anime', slug: 'anime', description: null }, + tags: [], +} + +describe('ArticleCard', () => { + it('renders the article title', () => { + render() + expect(screen.getByText('Frieren Season 2 Officially Announced')).toBeInTheDocument() + }) + + it('renders the category name', () => { + render() + expect(screen.getByText('Anime')).toBeInTheDocument() + }) + + it('links to the correct article URL', () => { + render() + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '/anime/frieren-season-2-announced') + }) + + it('renders the Read -> CTA', () => { + render() + expect(screen.getByText('Read →')).toBeInTheDocument() + }) +}) diff --git a/components/article/ArticleCard.tsx b/components/article/ArticleCard.tsx new file mode 100644 index 0000000..11fb3b7 --- /dev/null +++ b/components/article/ArticleCard.tsx @@ -0,0 +1,42 @@ +import Image from 'next/image' +import Link from 'next/link' +import { getAssetUrl } from '@/lib/directus' +import type { Article } from '@/lib/types' + +interface Props { + article: Article +} + +export default function ArticleCard({ article }: Props) { + const href = `/${article.category.slug}/${article.slug}` + + return ( + +
+ {article.featured_image ? ( + {article.title} + ) : ( +
+ )} +
+
+

+ {article.category.name} +

+

+ {article.title} +

+ Read → +
+ + ) +} diff --git a/lib/directus.ts b/lib/directus.ts index 0e70e3d..54096f6 100644 --- a/lib/directus.ts +++ b/lib/directus.ts @@ -8,12 +8,19 @@ import { } from '@directus/sdk' import type { Article, Category, SiteSettings } from './types' -const directus = createDirectus(process.env.DIRECTUS_URL!) - .with(staticToken(process.env.DIRECTUS_TOKEN!)) - .with(rest()) +let _client: ReturnType | null = null + +function getClient() { + if (!_client) { + _client = createDirectus(process.env.DIRECTUS_URL!) + .with(staticToken(process.env.DIRECTUS_TOKEN!)) + .with(rest()) + } + return _client +} export async function getAllCategories(): Promise { - return directus.request( + return getClient().request( readItems('categories', { fields: ['id', 'name', 'slug', 'description'], sort: ['name'], @@ -22,7 +29,7 @@ export async function getAllCategories(): Promise { } export async function getCategoryBySlug(slug: string): Promise { - const results = await directus.request( + const results = await getClient().request( readItems('categories', { fields: ['id', 'name', 'slug', 'description'], filter: { slug: { _eq: slug } }, @@ -42,7 +49,7 @@ export async function getArticles(options: { if (categorySlug) { filter['category'] = { slug: { _eq: categorySlug } } } - return directus.request( + return getClient().request( readItems('articles', { fields: [ 'id', 'title', 'slug', 'status', 'excerpt', 'featured_image', @@ -58,7 +65,7 @@ export async function getArticles(options: { } export async function getArticleBySlug(slug: string): Promise
{ - const results = await directus.request( + const results = await getClient().request( readItems('articles', { fields: [ 'id', 'title', 'slug', 'status', 'content', 'excerpt', @@ -79,7 +86,7 @@ export async function getArticlePathById(id: string): Promise<{ category: { slug: string } } | null> { try { - return await directus.request( + return await getClient().request( readItem('articles', id, { fields: ['slug', 'category.slug'], }) @@ -93,7 +100,7 @@ export async function getRelatedArticles( categorySlug: string, excludeSlug: string, ): Promise { - return directus.request( + return getClient().request( readItems('articles', { fields: [ 'id', 'title', 'slug', 'excerpt', 'featured_image', @@ -111,7 +118,7 @@ export async function getRelatedArticles( } export async function getSiteSettings(): Promise { - return directus.request( + return getClient().request( readSingleton('site_settings', { fields: [ 'id', 'site_name', @@ -125,7 +132,7 @@ export async function getSiteSettings(): Promise { } export async function searchArticles(query: string): Promise { - return directus.request( + return getClient().request( readItems('articles', { fields: ['id', 'title', 'slug', 'category.slug', 'category.name'], search: query,