From a5dba298f30b13b8161c819afedb804fc56d92b8 Mon Sep 17 00:00:00 2001 From: denshooter Date: Mon, 16 Feb 2026 12:31:40 +0100 Subject: [PATCH] feat: major UI/UX overhaul, snippets system, and performance fixes --- .github/copilot-instructions.md | 211 ++++++++++++ GEMINI.md | 34 ++ SESSION_SUMMARY.md | 42 +++ app/[locale]/projects/[slug]/page.tsx | 4 +- app/[locale]/projects/page.tsx | 15 +- app/[locale]/snippets/SnippetsClient.tsx | 109 +++++++ app/[locale]/snippets/page.tsx | 41 +++ app/__tests__/api/book-reviews.test.tsx | 4 +- app/__tests__/api/hobbies.test.tsx | 4 +- app/__tests__/api/tech-stack.test.tsx | 4 +- .../components/ActivityFeed.test.tsx | 3 +- .../components/CurrentlyReading.test.tsx | 16 +- app/__tests__/components/Hero.test.tsx | 10 +- app/_ui/HomePage.tsx | 6 + app/_ui/ProjectDetailClient.tsx | 4 +- app/_ui/ProjectsPageClient.tsx | 3 +- app/api/i18n/[namespace]/route.ts | 15 +- app/api/messages/route.ts | 2 +- app/api/projects/route.ts | 66 ++-- app/api/snippets/route.ts | 18 + app/components/About.tsx | 231 +++++++++++-- app/components/ActivityFeed.tsx | 190 +++++++---- app/components/BentoChat.tsx | 12 +- app/components/ClientProviders.tsx | 19 +- app/components/ClientWrappers.tsx | 10 +- app/components/Contact.tsx | 308 +++++++----------- app/components/Footer.tsx | 10 +- app/components/Grain.tsx | 30 ++ app/components/Hero.tsx | 36 +- app/components/Projects.tsx | 2 +- app/globals.css | 14 + app/layout.tsx | 1 + app/not-found.tsx | 173 +++++----- docs/DESIGN_OVERHAUL_LOG.md | 33 ++ jest.setup.ts | 13 +- lib/directus.ts | 142 +++++++- messages/de.json | 7 + messages/en.json | 7 + next.config.ts | 20 +- scripts/setup-snippets.js | 78 +++++ scripts/update-hobbies.js | 162 +++++++++ 41 files changed, 1610 insertions(+), 499 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 GEMINI.md create mode 100644 SESSION_SUMMARY.md create mode 100644 app/[locale]/snippets/SnippetsClient.tsx create mode 100644 app/[locale]/snippets/page.tsx create mode 100644 app/api/snippets/route.ts create mode 100644 app/components/Grain.tsx create mode 100644 docs/DESIGN_OVERHAUL_LOG.md create mode 100644 scripts/setup-snippets.js create mode 100644 scripts/update-hobbies.js diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..3975569 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,211 @@ +# Portfolio Project Instructions + +This is Dennis Konkol's personal portfolio (dk0.dev) - a Next.js 15 portfolio with Directus CMS integration, n8n automation, and a "liquid" design system. + +## Build, Test, and Lint + +### Development +```bash +npm run dev # Full dev environment (Docker + Next.js) +npm run dev:simple # Next.js only (no Docker dependencies) +npm run dev:next # Plain Next.js dev server +``` + +### Build & Deploy +```bash +npm run build # Production build (standalone mode) +npm run start # Start production server +``` + +### Testing +```bash +# Unit tests (Jest) +npm run test # Run all unit tests +npm run test:watch # Watch mode +npm run test:coverage # With coverage report + +# E2E tests (Playwright) +npm run test:e2e # Run all E2E tests +npm run test:e2e:ui # Interactive UI mode +npm run test:critical # Critical paths only +npm run test:hydration # Hydration tests only +``` + +### Linting +```bash +npm run lint # Run ESLint +npm run lint:fix # Auto-fix issues +``` + +### Database (Prisma) +```bash +npm run db:generate # Generate Prisma client +npm run db:push # Push schema to database +npm run db:studio # Open Prisma Studio +npm run db:seed # Seed database +``` + +## Architecture Overview + +### Tech Stack +- **Framework**: Next.js 15 (App Router), TypeScript 5.9 +- **Styling**: Tailwind CSS 3.4 with custom `liquid-*` color tokens +- **Theming**: next-themes for dark mode (system/light/dark) +- **Animations**: Framer Motion 12 +- **3D**: Three.js + React Three Fiber (shader gradient background) +- **Database**: PostgreSQL via Prisma ORM +- **Cache**: Redis (optional) +- **CMS**: Directus (self-hosted, GraphQL, optional) +- **Automation**: n8n webhooks (status, chat, hardcover, image generation) +- **i18n**: next-intl (EN + DE) +- **Monitoring**: Sentry +- **Deployment**: Docker (standalone mode) + Nginx + +### Key Directories +``` +app/ + [locale]/ # i18n routes (en, de) + page.tsx # Homepage sections + projects/ # Project listing + detail pages + api/ # API routes + book-reviews/ # Book reviews from Directus + hobbies/ # Hobbies from Directus + n8n/ # n8n webhook proxies + projects/ # Projects (PostgreSQL + Directus) + tech-stack/ # Tech stack from Directus + components/ # React components +lib/ + directus.ts # Directus GraphQL client (no SDK) + auth.ts # Auth + rate limiting + translations-loader.ts # i18n loaders for server components +prisma/ + schema.prisma # Database schema +messages/ + en.json # English translations + de.json # German translations +``` + +### Data Source Fallback Chain +The architecture prioritizes resilience with this fallback hierarchy: +1. **Directus CMS** (if `DIRECTUS_STATIC_TOKEN` configured) +2. **PostgreSQL** (for projects, analytics) +3. **JSON files** (`messages/*.json`) +4. **Hardcoded defaults** +5. **Display key itself** (last resort) + +**Critical**: The site never crashes if external services (Directus, PostgreSQL, n8n, Redis) are unavailable. All API routes return graceful fallbacks. + +### CMS Integration (Directus) +- GraphQL calls via `lib/directus.ts` (no Directus SDK) +- Collections: `tech_stack_categories`, `tech_stack_items`, `hobbies`, `content_pages`, `projects`, `book_reviews` +- Translations use Directus native system (M2O to `languages`) +- Locale mapping: `en` → `en-US`, `de` → `de-DE` +- API routes export `runtime='nodejs'`, `dynamic='force-dynamic'` and include a `source` field in JSON responses (`directus|fallback|error`) + +### n8n Integration +- Webhook base URL: `N8N_WEBHOOK_URL` env var +- Auth via `N8N_SECRET_TOKEN` and/or `N8N_API_KEY` headers +- All endpoints have rate limiting and 10s timeout protection +- Hardcover reading data cached for 5 minutes + +## Key Conventions + +### i18n (Internationalization) +- **Supported locales**: `en` (English), `de` (German) +- **Primary source**: Static JSON files in `messages/en.json` and `messages/de.json` +- **Optional override**: Directus CMS `messages` collection +- **Server components**: Use `getHeroTranslations()`, `getNavTranslations()`, etc. from `lib/translations-loader.ts` +- **Client components**: Use `useTranslations("key.path")` from next-intl +- **Locale mapping**: Middleware defines `["en", "de"]` which must match `app/[locale]/layout.tsx` + +### Component Patterns +- **Client components**: Mark with `"use client"` for interactive/data-fetching parts +- **Data loading**: Use `useEffect` for client-side fetching on mount +- **Animations**: Framer Motion `variants` pattern with `staggerContainer` + `fadeInUp` +- **Loading states**: Every async component needs a matching Skeleton component + +### Design System ("Liquid Editorial Bento") +- **Core palette**: Cream (`#fdfcf8`), Stone (`#0c0a09`), Emerald (`#10b981`) +- **Custom colors**: Prefixed with `liquid-*` (sky, mint, lavender, pink, rose, peach, coral, teal, lime) +- **Card style**: Gradient backgrounds (`bg-gradient-to-br from-liquid-*/15 via-liquid-*/10 to-liquid-*/15`) +- **Glassmorphism**: Use `backdrop-blur-sm` with `border-2` and `rounded-xl` +- **Typography**: Headlines uppercase, tracking-tighter, with accent point at end +- **Layout**: Bento Grid for new features (no floating overlays) + +### File Naming +- **Components**: PascalCase in `app/components/` (e.g., `About.tsx`) +- **API routes**: kebab-case directories in `app/api/` (e.g., `book-reviews/`) +- **Lib utilities**: kebab-case in `lib/` (e.g., `email-obfuscate.ts`) + +### Code Style +- **Language**: Code in English, user-facing text via i18n +- **TypeScript**: No `any` types - use interfaces from `lib/directus.ts` or `app/_ui/` +- **Error handling**: All API calls must catch errors with fallbacks +- **Error logging**: Only in development mode (`process.env.NODE_ENV === "development"`) +- **Commit messages**: Conventional Commits (`feat:`, `fix:`, `chore:`) +- **No emojis**: Unless explicitly requested + +### Testing Notes +- **Jest environment**: JSDOM with mocks for `window.matchMedia` and `IntersectionObserver` +- **Playwright**: Uses plain Next.js dev server (no Docker) with `NODE_ENV=development` to avoid Edge runtime issues +- **Transform**: ESM modules (react-markdown, remark-*, etc.) are transformed via `transformIgnorePatterns` +- **After UI changes**: Run `npm run test` to verify no regressions + +### Docker & Deployment +- **Standalone mode**: `next.config.ts` uses `output: "standalone"` for optimized Docker builds +- **Branches**: `dev` → staging, `production` → live +- **CI/CD**: Gitea Actions (`.gitea/workflows/`) +- **Verify Docker builds**: Always test Docker builds after changes to `next.config.ts` or dependencies + +## Common Tasks + +### Adding a CMS-managed section +1. Define GraphQL query + types in `lib/directus.ts` +2. Create API route in `app/api//route.ts` with `runtime='nodejs'` and `dynamic='force-dynamic'` +3. Create component in `app/components/.tsx` +4. Add i18n keys to `messages/en.json` and `messages/de.json` +5. Integrate into parent component + +### Adding i18n strings +1. Add keys to both `messages/en.json` and `messages/de.json` +2. Use `useTranslations("key.path")` in client components +3. Use `getTranslations("key.path")` in server components + +### Working with Directus +- All queries go through `directusRequest()` in `lib/directus.ts` +- Uses GraphQL endpoint (`/graphql`) with 2s timeout +- Returns `null` on failure (graceful degradation) +- Translations filtered by `languages_code.code` matching Directus locale + +## Environment Variables + +### Required for CMS +```bash +DIRECTUS_URL=https://cms.dk0.dev +DIRECTUS_STATIC_TOKEN=... +``` + +### Required for n8n features +```bash +N8N_WEBHOOK_URL=https://n8n.dk0.dev +N8N_SECRET_TOKEN=... +N8N_API_KEY=... +``` + +### Database & Cache +```bash +DATABASE_URL=postgresql://... +REDIS_URL=redis://... +``` + +### Optional +```bash +SENTRY_DSN=... +NEXT_PUBLIC_BASE_URL=https://dk0.dev +``` + +## Documentation References +- Operations guide: `docs/OPERATIONS.md` +- Locale system: `docs/LOCALE_SYSTEM.md` +- CMS guide: `docs/CMS_GUIDE.md` +- Testing & deployment: `docs/TESTING_AND_DEPLOYMENT.md` diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..74cd468 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,34 @@ +# Gemini CLI: Project Context & Engineering Mandates + +## Project Identity +- **Name:** Dennis Konkol Portfolio (dk0.dev) +- **Aesthetic:** "Liquid Editorial Bento" (Premium, minimalistisch, hoch-typografisch). +- **Core Palette:** Creme (`#fdfcf8`), Stone (`#0c0a09`), Emerald (`#10b981`), Sky, Purple. + +## Tech Stack +- **Framework:** Next.js 15 (App Router), Tailwind CSS 3.4. +- **CMS:** Directus (primär für Texte, Hobbies, Tech-Stack, Projekte). +- **Database:** PostgreSQL (Prisma) als lokaler Cache/Mirror für Projekte. +- **Animations:** Framer Motion (bevorzugt für alle Übergänge). +- **i18n:** `next-intl` (Locales: `en`, `de`). + +## Engineering Guidelines (Mandates) + +### 1. UI Components +- **Bento Grid:** Neue Features sollten immer in das bestehende Grid integriert werden. Keine schwebenden Overlays. +- **Skeletons:** Jede asynchrone Komponente benötigt einen passenden `Skeleton` Ladezustand. +- **Typography:** Headlines immer uppercase, tracking-tighter, mit Akzent-Punkt am Ende. + +### 2. Implementation Rules +- **TypeScript:** Keine `any`. Nutze bestehende Interfaces in `lib/directus.ts` oder `app/_ui/`. +- **Resilience:** Alle API-Calls müssen Fehler abfangen und sinnvolle Fallbacks (oder Skeletons) anzeigen. +- **Next.js Standalone:** Das Projekt nutzt den `standalone` Build-Mode. Docker-Builds müssen immer verifiziert werden. + +### 3. Agent Instructions +- **Codebase Investigator:** Nutze dieses Tool für Architektur-Fragen. +- **Testing:** Führe `npm run test` nach UI-Änderungen aus. Achte auf JSDOM-Einschränkungen (Mocking von `window.matchMedia` und `IntersectionObserver`). +- **CMS First:** Texte sollten nach Möglichkeit aus der `messages` Collection in Directus kommen, nicht hartcodiert werden. + +## Current State +- **Branch:** `dev` (pushed) +- **Status:** Design Overhaul abgeschlossen, Build stabil, Docker verifiziert. diff --git a/SESSION_SUMMARY.md b/SESSION_SUMMARY.md new file mode 100644 index 0000000..5e6e2c6 --- /dev/null +++ b/SESSION_SUMMARY.md @@ -0,0 +1,42 @@ +# Session Summary - February 16, 2026 + +## 🛡️ Security & Technical Fixes +- **CSP Improvements:** Added `images.unsplash.com`, `*.dk0.dev`, and `localhost` to `img-src` and `connect-src`. +- **Worker Support:** Enabled `worker-src 'self' blob:;` for dynamic features. +- **Source Map Suppression:** Configured Webpack to ignore 404 errors for `framer-motion` and `LayoutGroupContext` source maps in development. +- **Project Filtering:** Unified the projects API to use Directus as the "Single Source of Truth," strictly enforcing the `published` status. + +## 🎨 UI/UX Enhancements (Liquid Editorial Bento) +- **Hero Section:** + - Stabilized the hero photo (removed floating animation). + - Fixed edge-clipping by increasing the border/padding. + - Removed redundant social buttons for a cleaner entry. +- **Activity Feed:** + - Full localization (DE/EN). + - Added a rotating cycle of CS-related quotes (Dijkstra, etc.) including CMS quotes. + - Redesigned Music UI with Spotify-themed branding (`#1DB954`), improved contrast, and animated frequency bars. +- **Contact Area:** + - Redesigned into a unified "Connect" Bento box. + - High-typography list style for Email, GitHub, LinkedIn, and Location. +- **Hobbies:** + - Added personalized descriptions reflecting interests like Analog Photography, Astronomy, and Traveling. + - Switched to a 4-column layout for better spatial balance. + +## 🚀 New Features +- **Snippets System ("The Lab"):** + - New Directus collection and API endpoint for technical notes. + - Interactive Bento-modals with code syntax highlighting and copy-to-clipboard functionality. + - Dedicated `/snippets` overview page. + - Implemented "Featured" logic to control visibility on the home page. +- **Redesigned 404 Page:** + - Completely rebuilt in the Editorial Bento style with clear navigation paths. +- **Visual Finish:** + - Added a subtle, animated CSS-based Grain/Noise overlay. + - Implemented smooth Page Transitions using Framer Motion. + +## 💻 Hardware Setup ("My Gear") +- Added a dedicated Bento card showing current dev setup: + - MacBook Pro M4 Pro (24GB RAM). + - PC: Ryzen 7 3800XT / RTX 3080. + - Server: IONOS Cloud & Raspberry Pi 4. + - Dual MSI 164Hz Curved Monitors. diff --git a/app/[locale]/projects/[slug]/page.tsx b/app/[locale]/projects/[slug]/page.tsx index 91aeeca..6416294 100644 --- a/app/[locale]/projects/[slug]/page.tsx +++ b/app/[locale]/projects/[slug]/page.tsx @@ -3,7 +3,7 @@ import ProjectDetailClient from "@/app/_ui/ProjectDetailClient"; import { notFound } from "next/navigation"; import type { Metadata } from "next"; import { getLanguageAlternates, toAbsoluteUrl } from "@/lib/seo"; -import { getProjectBySlug, Project } from "@/lib/directus"; +import { getProjectBySlug } from "@/lib/directus"; import { ProjectDetailData } from "@/app/_ui/ProjectDetailClient"; export const revalidate = 300; @@ -83,7 +83,7 @@ export default async function ProjectPage({ if (directusProject) { projectData = { ...directusProject, - id: parseInt(directusProject.id) || 0, + id: typeof directusProject.id === 'string' ? (parseInt(directusProject.id) || 0) : directusProject.id, } as ProjectDetailData; } } diff --git a/app/[locale]/projects/page.tsx b/app/[locale]/projects/page.tsx index 04c077b..179164c 100644 --- a/app/[locale]/projects/page.tsx +++ b/app/[locale]/projects/page.tsx @@ -46,29 +46,34 @@ export default async function ProjectsPage({ if (fetched) { directusProjects = fetched.map(p => ({ ...p, - id: parseInt(p.id) || 0, + id: typeof p.id === 'string' ? (parseInt(p.id) || 0) : p.id, })) as ProjectListItem[]; } } catch (err) { console.error("Directus projects fetch failed:", err); } - const localizedDb = dbProjects.map((p) => { + const localizedDb: ProjectListItem[] = dbProjects.map((p) => { const trPreferred = p.translations?.find((t) => t.locale === locale && (t?.title || t?.description)); const trDefault = p.translations?.find( (t) => t.locale === p.defaultLocale && (t?.title || t?.description), ); const tr = trPreferred ?? trDefault; - const { translations: _translations, ...rest } = p; return { - ...rest, + id: p.id, + slug: p.slug, title: tr?.title ?? p.title, description: tr?.description ?? p.description, + tags: p.tags, + category: p.category, + date: p.date, + createdAt: p.createdAt.toISOString(), + imageUrl: p.imageUrl, }; }); // Merge projects, prioritizing DB ones if slugs match - const allProjects: any[] = [...localizedDb]; + const allProjects: ProjectListItem[] = [...localizedDb]; const dbSlugs = new Set(localizedDb.map(p => p.slug)); for (const dp of directusProjects) { diff --git a/app/[locale]/snippets/SnippetsClient.tsx b/app/[locale]/snippets/SnippetsClient.tsx new file mode 100644 index 0000000..869fc03 --- /dev/null +++ b/app/[locale]/snippets/SnippetsClient.tsx @@ -0,0 +1,109 @@ + +"use client"; + +import React, { useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { Snippet } from "@/lib/directus"; +import { X, Copy, Check, Hash } from "lucide-react"; + +export default function SnippetsClient({ initialSnippets }: { initialSnippets: Snippet[] }) { + const [selectedSnippet, setSelectedSnippet] = useState(null); + const [copied, setCopied] = useState(false); + + const copyToClipboard = (code: string) => { + navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <> +
+ {initialSnippets.map((s, i) => ( + setSelectedSnippet(s)} + className="text-left bg-white dark:bg-stone-900 rounded-[2.5rem] p-10 border border-stone-200/60 dark:border-stone-800/60 shadow-sm hover:shadow-xl hover:border-liquid-purple/40 transition-all group" + > +
+
+ +
+ {s.category} +
+

{s.title}

+

+ {s.description} +

+
+ ))} +
+ + {/* Snippet Modal */} + + {selectedSnippet && ( +
+ setSelectedSnippet(null)} + className="absolute inset-0 bg-stone-950/60 backdrop-blur-md" + /> + +
+
+
+

{selectedSnippet.category}

+

{selectedSnippet.title}

+
+ +
+ +

+ {selectedSnippet.description} +

+ +
+
+ +
+
+                    {selectedSnippet.code}
+                  
+
+
+
+ +
+
+
+ )} +
+ + ); +} diff --git a/app/[locale]/snippets/page.tsx b/app/[locale]/snippets/page.tsx new file mode 100644 index 0000000..a419bb7 --- /dev/null +++ b/app/[locale]/snippets/page.tsx @@ -0,0 +1,41 @@ + +import React from "react"; +import { getSnippets } from "@/lib/directus"; +import { Terminal, ArrowLeft, Code } from "lucide-react"; +import Link from "next/link"; +import SnippetsClient from "./SnippetsClient"; + +export default async function SnippetsPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + const snippets = await getSnippets(100) || []; + + return ( +
+
+ + + Back to Portfolio + + +
+
+
+ +
+

+ The Lab. +

+
+

+ A collection of technical snippets, configurations, and mental notes from my daily building process. +

+
+ + +
+
+ ); +} diff --git a/app/__tests__/api/book-reviews.test.tsx b/app/__tests__/api/book-reviews.test.tsx index 0b26cdd..77bac20 100644 --- a/app/__tests__/api/book-reviews.test.tsx +++ b/app/__tests__/api/book-reviews.test.tsx @@ -1,4 +1,4 @@ -import { NextResponse } from "next/server"; +import { NextResponse, NextRequest } from "next/server"; import { GET } from "@/app/api/book-reviews/route"; // Mock the route handler module @@ -12,7 +12,7 @@ describe("GET /api/book-reviews", () => { NextResponse.json({ bookReviews: [{ id: 1, book_title: "Test" }] }) ); - const response = await GET({} as any); + const response = await GET({} as NextRequest); const data = await response.json(); expect(response.status).toBe(200); expect(data.bookReviews).toHaveLength(1); diff --git a/app/__tests__/api/hobbies.test.tsx b/app/__tests__/api/hobbies.test.tsx index 656813f..482026d 100644 --- a/app/__tests__/api/hobbies.test.tsx +++ b/app/__tests__/api/hobbies.test.tsx @@ -1,4 +1,4 @@ -import { NextResponse } from "next/server"; +import { NextResponse, NextRequest } from "next/server"; import { GET } from "@/app/api/hobbies/route"; // Mock the route handler module @@ -12,7 +12,7 @@ describe("GET /api/hobbies", () => { NextResponse.json({ hobbies: [{ id: 1, title: "Gaming" }] }) ); - const response = await GET({} as any); + const response = await GET({} as NextRequest); const data = await response.json(); expect(response.status).toBe(200); expect(data.hobbies).toHaveLength(1); diff --git a/app/__tests__/api/tech-stack.test.tsx b/app/__tests__/api/tech-stack.test.tsx index 6587c94..22aa9dd 100644 --- a/app/__tests__/api/tech-stack.test.tsx +++ b/app/__tests__/api/tech-stack.test.tsx @@ -1,4 +1,4 @@ -import { NextResponse } from "next/server"; +import { NextResponse, NextRequest } from "next/server"; import { GET } from "@/app/api/tech-stack/route"; // Mock the route handler module @@ -12,7 +12,7 @@ describe("GET /api/tech-stack", () => { NextResponse.json({ techStack: [{ id: 1, name: "Frontend" }] }) ); - const response = await GET({} as any); + const response = await GET({} as NextRequest); const data = await response.json(); expect(response.status).toBe(200); expect(data.techStack).toHaveLength(1); diff --git a/app/__tests__/components/ActivityFeed.test.tsx b/app/__tests__/components/ActivityFeed.test.tsx index 6ee4d13..a8f33e3 100644 --- a/app/__tests__/components/ActivityFeed.test.tsx +++ b/app/__tests__/components/ActivityFeed.test.tsx @@ -64,7 +64,8 @@ describe('ActivityFeed NaN Handling', () => { // In the actual code, we use String(data.gaming.name || '') // If data.gaming.name is NaN, (NaN || '') evaluates to '' because NaN is falsy - const nanName = String(NaN || ''); + const nanValue = NaN; + const nanName = String(nanValue || ''); expect(nanName).toBe(''); // NaN is falsy, so it falls back to '' expect(typeof nanName).toBe('string'); }); diff --git a/app/__tests__/components/CurrentlyReading.test.tsx b/app/__tests__/components/CurrentlyReading.test.tsx index b488c8a..8a1eb1e 100644 --- a/app/__tests__/components/CurrentlyReading.test.tsx +++ b/app/__tests__/components/CurrentlyReading.test.tsx @@ -11,7 +11,7 @@ jest.mock("next-intl", () => ({ // Mock next/image jest.mock("next/image", () => ({ __esModule: true, - default: (props: any) => , + default: (props: React.ImgHTMLAttributes) => {props.alt, })); describe("CurrentlyReading Component", () => { @@ -28,19 +28,17 @@ describe("CurrentlyReading Component", () => { it("renders a book when data is fetched", async () => { const mockBooks = [ { - id: "1", - book_title: "Test Book", - book_author: "Test Author", - book_image: "/test.jpg", - status: "reading", - rating: 5, - progress: 50 + title: "Test Book", + authors: ["Test Author"], + image: "/test.jpg", + progress: 50, + startedAt: "2024-01-01" }, ]; (global.fetch as jest.Mock).mockResolvedValue({ ok: true, - json: async () => ({ hardcover: mockBooks }), + json: async () => ({ currentlyReading: mockBooks }), }); render(); diff --git a/app/__tests__/components/Hero.test.tsx b/app/__tests__/components/Hero.test.tsx index f6c4bff..5f540b7 100644 --- a/app/__tests__/components/Hero.test.tsx +++ b/app/__tests__/components/Hero.test.tsx @@ -14,9 +14,17 @@ jest.mock('next-intl', () => ({ })); // Mock next/image +interface ImageProps { + src: string; + alt: string; + fill?: boolean; + priority?: boolean; + [key: string]: unknown; +} + jest.mock('next/image', () => ({ __esModule: true, - default: ({ src, alt, fill, priority, ...props }: any) => ( + default: ({ src, alt, fill, priority, ...props }: ImageProps) => ( {alt} { + // Force scroll to top on mount to prevent starting at lower sections + window.scrollTo(0, 0); + }, []); + return (