diff --git a/app/[locale]/snippets/page.tsx b/app/[locale]/snippets/page.tsx index a419bb7..d62e4d7 100644 --- a/app/[locale]/snippets/page.tsx +++ b/app/[locale]/snippets/page.tsx @@ -1,7 +1,7 @@ import React from "react"; import { getSnippets } from "@/lib/directus"; -import { Terminal, ArrowLeft, Code } from "lucide-react"; +import { Terminal, ArrowLeft } from "lucide-react"; import Link from "next/link"; import SnippetsClient from "./SnippetsClient"; diff --git a/app/__tests__/not-found.test.tsx b/app/__tests__/not-found.test.tsx index ea88fb7..edc715e 100644 --- a/app/__tests__/not-found.test.tsx +++ b/app/__tests__/not-found.test.tsx @@ -18,6 +18,7 @@ jest.mock('next-intl', () => ({ describe('NotFound', () => { it('renders the 404 page with the new design text', () => { render(); - expect(screen.getByText("Lost in the Liquid.")).toBeInTheDocument(); + expect(screen.getByText(/Page not/i)).toBeInTheDocument(); + expect(screen.getByText(/Found/i)).toBeInTheDocument(); }); }); diff --git a/app/api/snippets/route.ts b/app/api/snippets/route.ts index c1fa7ab..c1ed4e3 100644 --- a/app/api/snippets/route.ts +++ b/app/api/snippets/route.ts @@ -12,7 +12,7 @@ export async function GET(request: NextRequest) { return NextResponse.json({ snippets: snippets || [] }); - } catch (error) { + } catch (_error) { return NextResponse.json({ error: 'Failed to fetch snippets' }, { status: 500 }); } } diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx index 706ed41..8f798cb 100644 --- a/app/components/Contact.tsx +++ b/app/components/Contact.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { motion } from "framer-motion"; -import { Mail, MapPin, Send, Github, Linkedin, ExternalLink } from "lucide-react"; +import { Mail, MapPin, Send, Github, Linkedin } from "lucide-react"; import { useToast } from "@/components/Toast"; import { useLocale, useTranslations } from "next-intl"; import type { JSONContent } from "@tiptap/react"; @@ -152,20 +152,6 @@ const Contact = () => { validateForm(); }; - const contactInfo = [ - { - icon: Mail, - title: tInfo("email"), - value: "contact@dk0.dev", - href: "mailto:contact@dk0.dev", - }, - { - icon: MapPin, - title: tInfo("location"), - value: tInfo("locationValue"), - }, - ]; - return (
{ return { ...actual, NextResponse: { - json: (data: Record, init?: unknown) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + json: (data: Record, init?: any) => { // Use global Response from whatwg-fetch + // eslint-disable-next-line @typescript-eslint/no-explicit-any const res = new (global as any).Response(JSON.stringify(data), init); res.headers.set('Content-Type', 'application/json'); return res; diff --git a/lib/directus.ts b/lib/directus.ts index 5044e0d..2b698a5 100644 --- a/lib/directus.ts +++ b/lib/directus.ts @@ -130,7 +130,7 @@ export async function getMessages(locale: string): Promise t.languages_code?.code === directusLocale); return translation?.value || null; - } catch (error) { + } catch (_error) { return null; } } @@ -279,13 +279,6 @@ const fallbackTechStackData: Record = { - frontend: 'Globe', - backend: 'Server', - tools: 'Wrench', - security: 'Shield' -}; - const categoryNames: Record> = { 'en-US': { frontend: 'Frontend & Mobile', @@ -413,7 +406,7 @@ export async function getTechStack(locale: string): Promise ({ + items: itemsToUse.map((item: TechStackItem) => ({ id: item.id, name: item.name, url: item.url, @@ -424,8 +417,8 @@ export async function getTechStack(locale: string): Promise { { body: { query } } ); - const hobbies = (result as any)?.hobbies; + interface HobbiesResult { + hobbies: Array<{ + id: string; + key: string; + icon: string; + translations?: Array<{ + title?: string; + description?: string; + }>; + }>; + } + const hobbies = (result as HobbiesResult | null)?.hobbies; if (!hobbies || hobbies.length === 0) { return null; } - return hobbies.map((hobby: any) => ({ + return hobbies.map((hobby) => ({ id: hobby.id, key: hobby.key, icon: hobby.icon, @@ -533,15 +537,30 @@ export async function getBookReviews(locale: string): Promise; + }>; + } + const reviews = (result as BookReviewsResult | null)?.book_reviews; if (!reviews || reviews.length === 0) { return null; } - return reviews.map((item: any) => { + return reviews.map((item) => { // Filter die passende Übersetzung im Code const translation = item.translations?.find( - (t: any) => t.languages_code?.code === directusLocale + (t) => t.languages_code?.code === directusLocale ) || item.translations?.[0]; // Fallback auf die erste Übersetzung falls locale nicht passt return { @@ -682,19 +701,52 @@ export async function getProjects( { body: { query } } ); - const projects = (result as any)?.projects; + interface ProjectsResult { + projects: Array<{ + id: string; + slug: string; + category?: string; + difficulty?: string; + tags?: string[] | string; + technologies?: string[] | string; + challenges?: string; + lessons_learned?: string; + future_improvements?: string; + github?: string; + live?: string; + image_url?: string; + demo_video?: string; + performance_metrics?: string; + screenshots?: string[] | string; + date_created?: string; + date_updated?: string; + featured?: boolean | number; + status?: string; + translations?: Array<{ + title?: string; + description?: string; + content?: string; + meta_description?: string; + keywords?: string; + button_live_label?: string; + button_github_label?: string; + languages_code?: { code: string }; + }>; + }>; + } + const projects = (result as ProjectsResult | null)?.projects; if (!projects || projects.length === 0) { return null; } - return projects.map((proj: any) => { + return projects.map((proj) => { const trans = - proj.translations?.find((t: any) => t.languages_code?.code === directusLocale) || + proj.translations?.find((t) => t.languages_code?.code === directusLocale) || proj.translations?.[0] || {}; // Parse JSON string fields if needed - const parseTags = (tags: any) => { + const parseTags = (tags: string[] | string | undefined): string[] => { if (!tags) return []; if (Array.isArray(tags)) return tags; if (typeof tags === 'string') { @@ -706,7 +758,7 @@ export async function getProjects( } return []; }; - + return { id: proj.id, slug: proj.slug, @@ -734,8 +786,8 @@ export async function getProjects( updated_at: proj.date_updated }; }); - } catch (error) { - console.error(`Failed to fetch projects (${locale}):`, error); + } catch (_error) { + console.error(`Failed to fetch projects (${locale}):`, _error); return null; } } @@ -797,19 +849,51 @@ export async function getProjectBySlug( { body: { query } } ); - const projects = (result as any)?.projects; + interface ProjectResult { + projects: Array<{ + id: string; + slug: string; + category?: string; + difficulty?: string; + tags?: string[] | string; + technologies?: string[] | string; + challenges?: string; + lessons_learned?: string; + future_improvements?: string; + github?: string; + live?: string; + image_url?: string; + demo_video?: string; + screenshots?: string[] | string; + date_created?: string; + date_updated?: string; + featured?: boolean | number; + status?: string; + translations?: Array<{ + title?: string; + description?: string; + content?: string; + meta_description?: string; + keywords?: string; + button_live_label?: string; + button_github_label?: string; + languages_code?: { code: string }; + }>; + }>; + } + const projects = (result as ProjectResult | null)?.projects; if (!projects || projects.length === 0) { return null; } const proj = projects[0]; const trans = - proj.translations?.find((t: any) => t.languages_code?.code === directusLocale) || + proj.translations?.find((t) => t.languages_code?.code === directusLocale) || proj.translations?.[0] || {}; // Parse JSON string fields if needed - const parseTags = (tags: any) => { + const parseTags = (tags: string[] | string | undefined): string[] => { if (!tags) return []; if (Array.isArray(tags)) return tags; if (typeof tags === 'string') { @@ -821,7 +905,7 @@ export async function getProjectBySlug( } return []; }; - + return { id: proj.id, slug: proj.slug, @@ -847,8 +931,8 @@ export async function getProjectBySlug( created_at: proj.date_created, updated_at: proj.date_updated }; - } catch (error) { - console.error(`Failed to fetch project by slug ${slug} (${locale}):`, error); + } catch (_error) { + console.error(`Failed to fetch project by slug ${slug} (${locale}):`, _error); return null; } } @@ -895,14 +979,17 @@ export async function getSnippets(limit = 10, featured?: boolean): Promise(); +const cache = new Map(); -function setCached(key: string, value: any, ttlSeconds = 300) { +function setCached(key: string, value: unknown, ttlSeconds = 300) { cache.set(key, { value, expires: Date.now() + ttlSeconds * 1000 }); } -function getCached(key: string): any | null { +function getCached(key: string): unknown | null { const hit = cache.get(key); if (!hit) return null; if (Date.now() > hit.expires) { @@ -38,7 +38,7 @@ export async function getLocalizedMessage( ): Promise { const cacheKey = `msg:${key}:${locale}`; const cached = getCached(cacheKey); - if (cached !== null) return cached; + if (cached !== null) return cached as string; // Try Directus with requested locale const dbValue = await getMessage(key, locale); @@ -84,11 +84,11 @@ export async function getLocalizedMessage( export async function getLocalizedContent( slug: string, locale: string -): Promise { +): Promise { const cacheKey = `page:${slug}:${locale}`; const cached = getCached(cacheKey); - if (cached !== null) return cached; - if (cached === null && cache.has(cacheKey)) return null; // Already checked, not found + if (cached !== null) return cached as ContentPage; + if (cache.has(cacheKey)) return null; // Already checked, not found // Try Directus with requested locale const dbPage = await getContentPage(slug, locale); @@ -115,14 +115,18 @@ export async function getLocalizedContent( * Helper: Get nested value from object * Example: "nav.home" → obj.nav.home */ -function getNestedValue(obj: any, path: string): any { +function getNestedValue(obj: Record, path: string): string | null { const keys = path.split('.'); - let value = obj; + let value: unknown = obj; for (const key of keys) { - value = value?.[key]; + if (value && typeof value === 'object' && key in value) { + value = (value as Record)[key]; + } else { + return null; + } if (value === undefined) return null; } - return value; + return typeof value === 'string' ? value : null; } /** diff --git a/scripts/setup-snippets.js b/scripts/setup-snippets.js index ea55b19..7a64791 100644 --- a/scripts/setup-snippets.js +++ b/scripts/setup-snippets.js @@ -1,4 +1,5 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ const fetch = require('node-fetch'); require('dotenv').config(); @@ -19,7 +20,7 @@ async function setupSnippets() { schema: { name: 'snippets' } }) }); - } catch (e) {} + } catch (_e) {} // 2. Add Fields const fields = [ @@ -39,7 +40,7 @@ async function setupSnippets() { headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(f) }); - } catch (e) {} + } catch (_e) {} } // 3. Add Example Data @@ -69,7 +70,7 @@ async function setupSnippets() { headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(s) }); - } catch (e) {} + } catch (_e) {} } console.log('✅ Snippets setup complete!'); diff --git a/scripts/update-hobbies.js b/scripts/update-hobbies.js index 72269df..a61c7f3 100644 --- a/scripts/update-hobbies.js +++ b/scripts/update-hobbies.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ const fetch = require('node-fetch'); require('dotenv').config(); @@ -136,8 +137,8 @@ async function syncHobbies() { }) }); } - } catch (e) { - console.error(`Failed to sync ${hobby.key}:`, e.message); + } catch (_e) { + console.error(`Failed to sync ${hobby.key}:`, _e.message); } } @@ -154,7 +155,7 @@ async function syncHobbies() { headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}` } }); } - } catch (e) {} + } catch (_e) {} console.log('✅ Done!'); }