diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9ffb51c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,155 @@ +# CLAUDE.md - Portfolio Project Guide + +## Project Overview + +Personal portfolio website for Dennis Konkol (dk0.dev). Built with Next.js 15 (App Router), TypeScript, Tailwind CSS, and Framer Motion. Uses a "liquid" design system with soft gradient colors and glassmorphism effects. + +## Tech Stack + +- **Framework**: Next.js 15 (App Router), TypeScript 5.9 +- **Styling**: Tailwind CSS 3.4 with custom `liquid-*` color tokens +- **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, REST/GraphQL, optional) +- **Automation**: n8n webhooks (status, chat, hardcover, image generation) +- **i18n**: next-intl (EN + DE), message files in `messages/` +- **Monitoring**: Sentry +- **Deployment**: Docker + Nginx, CI via Gitea Actions + +## Commands + +```bash +npm run dev # Full dev environment (Docker + Next.js) +npm run dev:simple # Next.js only (no Docker) +npm run dev:next # Plain Next.js dev server +npm run build # Production build +npm run lint # ESLint +npm run test # Jest unit tests +npm run test:e2e # Playwright E2E tests +``` + +## Project Structure + +``` +app/ + [locale]/ # i18n routes (en, de) + page.tsx # Homepage (hero, about, projects, contact) + projects/ # Project listing + detail pages + api/ # API routes + book-reviews/ # Book reviews from Directus CMS + content/ # CMS content pages + hobbies/ # Hobbies from Directus + n8n/ # n8n webhook proxies + hardcover/ # Currently reading (Hardcover API via n8n) + status/ # Activity status (coding, music, gaming) + chat/ # AI chatbot + generate-image/ # AI image generation + projects/ # Projects API (PostgreSQL + Directus fallback) + tech-stack/ # Tech stack from Directus + components/ # React components + About.tsx # About section (tech stack, hobbies, books) + CurrentlyReading.tsx # Currently reading widget (n8n/Hardcover) + ReadBooks.tsx # Read books with ratings (Directus CMS) + Projects.tsx # Featured projects section + Hero.tsx # Hero section + Contact.tsx # Contact form +lib/ + directus.ts # Directus GraphQL client (no SDK) + auth.ts # Auth utilities + rate limiting +prisma/ + schema.prisma # Database schema +messages/ + en.json # English translations + de.json # German translations +docs/ # Documentation +``` + +## Architecture Patterns + +### Data Source Hierarchy (Fallback Chain) +1. Directus CMS (if configured via `DIRECTUS_STATIC_TOKEN`) +2. PostgreSQL (for projects, analytics) +3. JSON files (`messages/*.json`) +4. Hardcoded defaults +5. Display key itself as last resort + +All external data sources fail gracefully - the site never crashes if Directus, PostgreSQL, n8n, or Redis are unavailable. + +### CMS Integration (Directus) +- REST/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 translation system (M2O to `languages`) +- Locale mapping: `en` -> `en-US`, `de` -> `de-DE` + +### n8n Integration +- Webhook base URL: `N8N_WEBHOOK_URL` env var +- Auth via `N8N_SECRET_TOKEN` and/or `N8N_API_KEY` headers +- All n8n endpoints have rate limiting and timeout protection (10s) +- Hardcover data cached for 5 minutes + +### Component Patterns +- Client components with `"use client"` for interactive/data-fetching parts +- `useEffect` for data loading on mount +- `useTranslations` from next-intl for i18n +- Framer Motion `variants` pattern with `staggerContainer` + `fadeInUp` +- Gradient cards with `liquid-*` color tokens and `backdrop-blur-sm` + +## Design System + +Custom Tailwind colors prefixed with `liquid-`: +- `liquid-sky`, `liquid-mint`, `liquid-lavender`, `liquid-pink` +- `liquid-rose`, `liquid-peach`, `liquid-coral`, `liquid-teal`, `liquid-lime` + +Cards use gradient backgrounds (`bg-gradient-to-br from-liquid-*/15 via-liquid-*/10 to-liquid-*/15`) with `border-2` and `rounded-xl`. + +## Key Environment Variables + +```bash +# Required for CMS +DIRECTUS_URL=https://cms.dk0.dev +DIRECTUS_STATIC_TOKEN=... + +# Required for n8n features +N8N_WEBHOOK_URL=https://n8n.dk0.dev +N8N_SECRET_TOKEN=... +N8N_API_KEY=... + +# Database +DATABASE_URL=postgresql://... + +# Optional +REDIS_URL=redis://... +SENTRY_DSN=... +``` + +## Conventions + +- Language: Code in English, user-facing text via i18n (EN + DE) +- Commit messages: Conventional Commits (`feat:`, `fix:`, `chore:`) +- Components: PascalCase files in `app/components/` +- API routes: kebab-case directories in `app/api/` +- CMS data always has a static fallback - never rely solely on Directus +- Error logging: Only in `development` mode (`process.env.NODE_ENV === "development"`) +- No emojis in code unless explicitly requested + +## Common Tasks + +### Adding a new CMS-managed section +1. Define the GraphQL query + types in `lib/directus.ts` +2. Create an API route in `app/api//route.ts` +3. Create a component in `app/components/.tsx` +4. Add i18n keys to `messages/en.json` and `messages/de.json` +5. Integrate into the parent component (usually `About.tsx`) + +### Adding i18n strings +1. Add keys to `messages/en.json` and `messages/de.json` +2. Access via `useTranslations("key.path")` in client components +3. Or `getTranslations("key.path")` in server components + +### Working with Directus collections +- All queries go through `directusRequest()` in `lib/directus.ts` +- Uses GraphQL endpoint (`/graphql`) +- 2-second timeout, graceful null fallback +- Translations filtered by `languages_code.code` matching Directus locale diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..1b6355a --- /dev/null +++ b/TODO.md @@ -0,0 +1,51 @@ +# TODO - Portfolio Roadmap + +## Book Reviews (Neu) + +- [ ] **Directus Collection erstellen**: `book_reviews` mit Feldern: + - `status` (draft/published) + - `book_title` (String) + - `book_author` (String) + - `book_image` (String, URL zum Cover) + - `rating` (Integer, 1-5) + - `hardcover_id` (String, optional) + - `finished_at` (Datetime, optional) + - Translations: `review` (Text) + `languages_code` (FK) +- [ ] **n8n Workflow**: Automatisch Directus-Entwurf erstellen wenn Buch auf Hardcover als "gelesen" markiert wird +- [ ] **Hardcover GraphQL Query** für gelesene Bücher: `status_id: {_eq: 3}` (Read) +- [ ] **Erste Testdaten**: 2-3 gelesene Bücher mit Rating + Kommentar in Directus anlegen + +## Directus CMS + +- [ ] Messages Collection: `messages` mit key + translations (ersetzt `messages/*.json`) +- [ ] Projects vollständig zu Directus migrieren (`node scripts/migrate-projects-to-directus.js`) +- [ ] Directus Webhooks einrichten: On-Demand ISR Revalidation bei Content-Änderungen +- [ ] Directus Roles: Public Read Token, Admin Write + +## n8n Integrationen + +- [ ] Hardcover "Read Books" Webhook: Gelesene Bücher automatisch in Directus importieren +- [ ] Spotify Now Playing verbessern: Album-Art Caching +- [ ] Discord Rich Presence: Gaming-Status automatisch aktualisieren + +## Frontend + +- [ ] Dark Mode Support (Theme Toggle) +- [ ] Blog/Artikel Sektion (Directus-basiert) +- [ ] Projekt-Detail Seite: Bildergalerie/Lightbox +- [ ] Performance: Bilder auf Next.js `` umstellen (statt ``) +- [ ] SEO: Structured Data (JSON-LD) für Projekte + +## Testing & Qualität + +- [ ] Jest Tests für neue API-Routes (`book-reviews`, `hobbies`, `tech-stack`) +- [ ] Playwright E2E: Book Reviews Sektion testen +- [ ] Lighthouse Score > 95 auf allen Seiten sicherstellen +- [ ] Accessibility Audit (WCAG 2.1 AA) + +## DevOps + +- [ ] Staging Environment aufräumen und dokumentieren +- [ ] GitHub Actions Migration (von Gitea Actions) +- [ ] Docker Image Size optimieren (Multi-Stage Build prüfen) +- [ ] Health Check Endpoint erweitern: Directus + n8n Connectivity diff --git a/app/components/ReadBooks.tsx b/app/components/ReadBooks.tsx index d99c0d6..1a8756c 100644 --- a/app/components/ReadBooks.tsx +++ b/app/components/ReadBooks.tsx @@ -133,7 +133,7 @@ const ReadBooks = () => { transition={{ duration: 0.5, delay: 0.2 + index * 0.1 }} className="flex-shrink-0" > -
+