diff --git a/.eslintrc.build.json b/.eslintrc.build.json new file mode 100644 index 0000000..beeb9f3 --- /dev/null +++ b/.eslintrc.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["next/core-web-vitals"], + "rules": { + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "off", + "@next/next/no-img-element": "off" + } +} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index db3cdfd..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Build and Push Docker Image - -on: - workflow_run: - workflows: ["Test Code Base"] - types: - - completed - branches: - - production - - dev - - preview - -jobs: - build: - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin - - - name: Create Deployment .env File - run: | - cat > .env < .env < .env <> $GITHUB_ENV - echo "PORT=4000" >> $GITHUB_ENV - elif [[ "${{ github.event.workflow_run.head_branch }}" == "dev" ]]; then - echo "DEPLOY_ENV=dev" >> $GITHUB_ENV - echo "PORT=4001" >> $GITHUB_ENV - elif [[ "${{ github.event.workflow_run.head_branch }}" == "preview" ]]; then - echo "DEPLOY_ENV=preview" >> $GITHUB_ENV - echo "PORT=4002" >> $GITHUB_ENV - fi - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin - - - name: Pull & Deploy Docker Image - run: | - IMAGE_NAME="ghcr.io/${{ github.repository_owner }}/my-nextjs-app:${{ github.event.workflow_run.head_branch }}" - IMAGE_NAME=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]') - docker pull "$IMAGE_NAME" - CONTAINER_NAME="nextjs-$DEPLOY_ENV" - - echo "Deploying $CONTAINER_NAME" - - if [ "$(docker inspect --format='{{.State.Running}}' "$CONTAINER_NAME")" = "true" ]; then - docker stop "$CONTAINER_NAME" || true - docker rm "$CONTAINER_NAME" || true - fi - - docker run -d --name "$CONTAINER_NAME" -p $PORT:3000 "$IMAGE_NAME" - if [ "$(docker inspect --format='{{.State.Running}}' "$CONTAINER_NAME")" = "true" ]; then - echo "Deployment erfolgreich!" - else - echo "Neuer Container konnte nicht gestartet werden!" - docker logs "$CONTAINER_NAME" - exit 1 - fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index eb94ce8..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Lint Code Base - -on: - push: - branches: - - production - - dev - - preview - paths: - - 'app/**' - - 'public/**' - - 'styles/**' - - 'Dockerfile' - - 'docker-compose.yml' - - '.github/workflows/**' - - 'next.config.ts' - - 'package.json' - - 'package-lock.json' - - 'tsconfig.json' - - 'tailwind.config.ts' - pull_request: - branches: - - production - - dev - - preview - paths: - - 'app/**' - - 'public/**' - - 'styles/**' - - 'Dockerfile' - - 'docker-compose.yml' - - '.github/workflows/**' - - 'next.config.ts' - - 'package.json' - - 'package-lock.json' - - 'tsconfig.json' - - 'tailwind.config.ts' - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22.14.0 - cache: 'npm' - - - name: Install Dependencies - run: npm ci - - - name: Run ESLint - run: npm run lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 3222b2b..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Test Code Base - -on: - push: - branches: - - production - - dev - - preview - paths: - - 'app/**' - - 'public/**' - - 'styles/**' - - 'Dockerfile' - - 'docker-compose.yml' - - '.github/workflows/**' - - 'next.config.ts' - - 'package.json' - - 'package-lock.json' - - 'tsconfig.json' - - 'tailwind.config.ts' - pull_request: - branches: - - production - - dev - - preview - paths: - - 'app/**' - - 'public/**' - - 'styles/**' - - 'Dockerfile' - - 'docker-compose.yml' - - '.github/workflows/**' - - 'next.config.ts' - - 'package.json' - - 'package-lock.json' - - 'tsconfig.json' - - 'tailwind.config.ts' - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22.14.0 - cache: 'npm' - - - name: Install Dependencies - run: npm ci - - - name: Create .env File - run: | - cat > .env < +``` + +### 2. Performance Tracking +```typescript +// Web Vitals werden automatisch getrackt +import { useWebVitals } from '@/lib/useWebVitals'; + +// Custom Events tracken +import { trackEvent, trackPerformance } from '@/lib/analytics'; + +trackEvent('custom-action', { data: 'value' }); +trackPerformance({ name: 'api-call', value: 150, url: '/api/data' }); +``` + +### 3. Analytics Provider +```typescript +// Automatisches Tracking von: +// - Page Views +// - User Interactions (Klicks, Scroll, Forms) +// - Performance Metrics +// - Error Tracking + + {children} + +``` + +## Dashboard + +### Performance Dashboard +- **Live Performance-Metriken** anzeigen +- **Core Web Vitals** mit Bewertungen (Good/Needs Improvement/Poor) +- **Toggle-Button** unten rechts auf der Website +- **Real-time Updates** der Performance-Daten + +### Umami Dashboard +- **Standard Analytics** über deine Umami-Instanz +- **URL**: https://umami.denshooter.de +- **Website ID**: 1f213877-deef-4238-8df1-71a5a3bcd142 + +## Event-Typen + +### Automatische Events +- `page-view` - Seitenaufrufe +- `click` - Benutzerklicks +- `form-submit` - Formular-Übermittlungen +- `scroll-depth` - Scroll-Tiefe (25%, 50%, 75%, 90%) +- `error` - JavaScript-Fehler +- `unhandled-rejection` - Unbehandelte Promise-Rejections + +### Performance Events +- `web-vitals` - Core Web Vitals (LCP, FID, CLS, FCP, TTFB) +- `performance` - Custom Performance-Metriken +- `page-timing` - Detaillierte Page-Load-Phasen +- `api-call` - API-Response-Zeiten + +### Custom Events +- `dashboard-toggle` - Performance Dashboard ein/aus +- `interaction` - Benutzerinteraktionen + +## Datenschutz + +### Was wird NICHT gesammelt: +- ❌ IP-Adressen +- ❌ User-IDs +- ❌ E-Mail-Adressen +- ❌ Personenbezogene Daten +- ❌ Cookies + +### Was wird gesammelt: +- ✅ Anonymisierte Performance-Metriken +- ✅ Technische Browser-Informationen +- ✅ Seitenaufrufe (ohne persönliche Daten) +- ✅ Error-Logs (anonymisiert) + +## Konfiguration + +### Umami Setup +1. **Self-hosted Umami** auf deinem Server +2. **Website ID** in `layout.tsx` konfiguriert +3. **Script-URL** auf deine Umami-Instanz + +### Performance Tracking +- **Automatisch aktiviert** durch `AnalyticsProvider` +- **Web Vitals** werden automatisch gemessen +- **Custom Events** über `trackEvent()` Funktion + +## Monitoring + +### Performance-Schwellenwerte +- **LCP**: ≤ 2.5s (Good), ≤ 4s (Needs Improvement), > 4s (Poor) +- **FID**: ≤ 100ms (Good), ≤ 300ms (Needs Improvement), > 300ms (Poor) +- **CLS**: ≤ 0.1 (Good), ≤ 0.25 (Needs Improvement), > 0.25 (Poor) +- **FCP**: ≤ 1.8s (Good), ≤ 3s (Needs Improvement), > 3s (Poor) +- **TTFB**: ≤ 800ms (Good), ≤ 1.8s (Needs Improvement), > 1.8s (Poor) + +### Dashboard-Zugriff +- **Performance Dashboard**: Toggle-Button unten rechts +- **Umami Dashboard**: https://umami.denshooter.de +- **API Endpoint**: `/api/analytics` für Custom-Tracking + +## Erweiterung + +### Neue Events hinzufügen +```typescript +import { trackEvent } from '@/lib/analytics'; + +// Custom Event tracken +trackEvent('feature-usage', { + feature: 'contact-form', + success: true, + duration: 1500 +}); +``` + +### Performance-Metriken erweitern +```typescript +import { trackPerformance } from '@/lib/analytics'; + +// Custom Performance-Metrik +trackPerformance({ + name: 'component-render', + value: renderTime, + url: window.location.pathname +}); +``` + +## Troubleshooting + +### Performance Dashboard nicht sichtbar +- Prüfe Browser-Konsole auf Fehler +- Stelle sicher, dass `AnalyticsProvider` in `layout.tsx` eingebunden ist + +### Umami Events nicht sichtbar +- Prüfe Umami-Dashboard auf https://umami.denshooter.de +- Stelle sicher, dass Website ID korrekt ist +- Prüfe Browser-Netzwerk-Tab auf Umami-Requests + +### Performance-Metriken fehlen +- Prüfe Browser-Konsole auf Performance Observer Fehler +- Stelle sicher, dass `useWebVitals` Hook aktiv ist +- Teste in verschiedenen Browsern diff --git a/AUTO-DEPLOYMENT.md b/AUTO-DEPLOYMENT.md new file mode 100644 index 0000000..af592eb --- /dev/null +++ b/AUTO-DEPLOYMENT.md @@ -0,0 +1,226 @@ +# Automatisches Deployment System + +## Übersicht + +Dieses Portfolio verwendet ein **automatisches Deployment-System**, das bei jedem Git Push die Codebase prüft, den Container erstellt und startet. + +## 🚀 Deployment-Skripte + +### **1. Auto-Deploy (Vollständig)** +```bash +# Vollständiges automatisches Deployment +./scripts/auto-deploy.sh + +# Oder mit npm +npm run auto-deploy +``` + +**Was passiert:** +- ✅ Git Status prüfen und uncommitted Changes committen +- ✅ Latest Changes pullen +- ✅ ESLint Linting +- ✅ Tests ausführen +- ✅ Next.js Build +- ✅ Docker Image erstellen +- ✅ Container stoppen/starten +- ✅ Health Check +- ✅ Cleanup alter Images + +### **2. Quick-Deploy (Schnell)** +```bash +# Schnelles Deployment ohne Tests +./scripts/quick-deploy.sh + +# Oder mit npm +npm run quick-deploy +``` + +**Was passiert:** +- ✅ Docker Image erstellen +- ✅ Container stoppen/starten +- ✅ Health Check + +### **3. Manuelles Deployment** +```bash +# Manuelles Deployment mit Docker Compose +./scripts/deploy.sh + +# Oder mit npm +npm run deploy +``` + +## 🔄 Automatisches Deployment + +### **Git Hook Setup** +Das System verwendet einen Git Post-Receive Hook, der automatisch bei jedem Push ausgeführt wird: + +```bash +# Hook ist bereits konfiguriert in: +.git/hooks/post-receive +``` + +### **Wie es funktioniert:** +1. **Git Push** → Hook wird ausgelöst +2. **Auto-Deploy Script** wird ausgeführt +3. **Vollständige Pipeline** läuft automatisch +4. **Deployment** wird durchgeführt +5. **Health Check** bestätigt Erfolg + +## 📋 Deployment-Schritte + +### **Automatisches Deployment:** +```bash +# 1. Code Quality Checks +git status --porcelain +git pull origin main +npm run lint +npm run test + +# 2. Build Application +npm run build + +# 3. Docker Operations +docker build -t portfolio-app:latest . +docker tag portfolio-app:latest portfolio-app:$(date +%Y%m%d-%H%M%S) + +# 4. Deployment +docker stop portfolio-app || true +docker rm portfolio-app || true +docker run -d --name portfolio-app -p 3000:3000 portfolio-app:latest + +# 5. Health Check +curl -f http://localhost:3000/api/health + +# 6. Cleanup +docker system prune -f +``` + +## 🎯 Verwendung + +### **Für Entwicklung:** +```bash +# Schnelles Deployment während der Entwicklung +npm run quick-deploy +``` + +### **Für Production:** +```bash +# Vollständiges Deployment mit Tests +npm run auto-deploy +``` + +### **Automatisch bei Push:** +```bash +# Einfach committen und pushen +git add . +git commit -m "Update feature" +git push origin main +# → Automatisches Deployment läuft +``` + +## 📊 Monitoring + +### **Container Status:** +```bash +# Status prüfen +npm run monitor status + +# Health Check +npm run monitor health + +# Logs anzeigen +npm run monitor logs +``` + +### **Deployment Logs:** +```bash +# Deployment-Logs anzeigen +tail -f /var/log/portfolio-deploy.log + +# Git-Deployment-Logs +tail -f /var/log/git-deploy.log +``` + +## 🔧 Konfiguration + +### **Ports:** +- **Standard Port:** 3000 +- **Backup Port:** 3001 (falls 3000 belegt) + +### **Container:** +- **Name:** portfolio-app +- **Image:** portfolio-app:latest +- **Restart Policy:** unless-stopped + +### **Logs:** +- **Deployment Logs:** `/var/log/portfolio-deploy.log` +- **Git Logs:** `/var/log/git-deploy.log` + +## 🚨 Troubleshooting + +### **Deployment schlägt fehl:** +```bash +# Logs prüfen +docker logs portfolio-app + +# Container-Status prüfen +docker ps -a + +# Manuell neu starten +npm run quick-deploy +``` + +### **Port bereits belegt:** +```bash +# Ports prüfen +lsof -i :3000 + +# Anderen Port verwenden +docker run -d --name portfolio-app -p 3001:3000 portfolio-app:latest +``` + +### **Tests schlagen fehl:** +```bash +# Tests lokal ausführen +npm run test + +# Linting prüfen +npm run lint + +# Build testen +npm run build +``` + +## 📈 Features + +### **Automatische Features:** +- ✅ **Git Integration** - Automatisch bei Push +- ✅ **Code Quality** - Linting und Tests +- ✅ **Health Checks** - Automatische Verifikation +- ✅ **Rollback** - Alte Container werden gestoppt +- ✅ **Cleanup** - Alte Images werden entfernt +- ✅ **Logging** - Vollständige Deployment-Logs + +### **Sicherheits-Features:** +- ✅ **Non-root Container** +- ✅ **Resource Limits** +- ✅ **Health Monitoring** +- ✅ **Error Handling** +- ✅ **Rollback bei Fehlern** + +## 🎉 Vorteile + +1. **Automatisierung** - Keine manuellen Schritte nötig +2. **Konsistenz** - Immer gleiche Deployment-Prozesse +3. **Sicherheit** - Tests vor jedem Deployment +4. **Monitoring** - Vollständige Logs und Health Checks +5. **Schnell** - Quick-Deploy für Entwicklung +6. **Zuverlässig** - Automatische Rollbacks bei Fehlern + +## 📞 Support + +Bei Problemen: +1. **Logs prüfen:** `tail -f /var/log/portfolio-deploy.log` +2. **Container-Status:** `npm run monitor status` +3. **Health Check:** `npm run monitor health` +4. **Manueller Neustart:** `npm run quick-deploy` diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..39ebba8 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,272 @@ +# Portfolio Deployment Guide + +## Übersicht + +Dieses Portfolio verwendet ein **optimiertes CI/CD-System** mit Docker für Production-Deployment. Das System ist darauf ausgelegt, hohen Traffic zu bewältigen und automatische Tests vor dem Deployment durchzuführen. + +## 🚀 Features + +### ✅ **CI/CD Pipeline** +- **Automatische Tests** vor jedem Deployment +- **Security Scanning** mit Trivy +- **Multi-Architecture Docker Builds** (AMD64 + ARM64) +- **Health Checks** und Deployment-Verifikation +- **Automatische Cleanup** alter Images + +### ⚡ **Performance-Optimierungen** +- **Multi-Stage Docker Build** für kleinere Images +- **Nginx Load Balancer** mit Caching +- **Gzip Compression** und optimierte Headers +- **Rate Limiting** für API-Endpoints +- **Resource Limits** für Container + +### 🔒 **Sicherheit** +- **Non-root User** im Container +- **Security Headers** (HSTS, CSP, etc.) +- **SSL/TLS Termination** mit Nginx +- **Vulnerability Scanning** in CI/CD + +## 📁 Dateistruktur + +``` +├── .github/workflows/ +│ └── ci-cd.yml # CI/CD Pipeline +├── scripts/ +│ ├── deploy.sh # Deployment-Skript +│ └── monitor.sh # Monitoring-Skript +├── docker-compose.prod.yml # Production Docker Compose +├── nginx.conf # Nginx Konfiguration +├── Dockerfile # Optimiertes Dockerfile +└── env.example # Environment Template +``` + +## 🛠️ Setup + +### 1. **Environment Variables** +```bash +# Kopiere die Beispiel-Datei +cp env.example .env + +# Bearbeite die .env Datei mit deinen Werten +nano .env +``` + +### 2. **GitHub Secrets & Variables** +Konfiguriere in deinem GitHub Repository: + +**Secrets:** +- `GITHUB_TOKEN` (automatisch verfügbar) +- `GHOST_API_KEY` +- `MY_PASSWORD` +- `MY_INFO_PASSWORD` + +**Variables:** +- `NEXT_PUBLIC_BASE_URL` +- `GHOST_API_URL` +- `MY_EMAIL` +- `MY_INFO_EMAIL` + +### 3. **SSL-Zertifikate** +```bash +# Erstelle SSL-Verzeichnis +mkdir -p ssl + +# Kopiere deine SSL-Zertifikate +cp your-cert.pem ssl/cert.pem +cp your-key.pem ssl/key.pem +``` + +## 🚀 Deployment + +### **Automatisches Deployment** +Das System deployt automatisch bei Push auf den `production` Branch: + +```bash +# Code auf production Branch pushen +git push origin production +``` + +### **Manuelles Deployment** +```bash +# Lokales Deployment +./scripts/deploy.sh production + +# Oder mit npm +npm run deploy +``` + +### **Docker Commands** +```bash +# Container starten +npm run docker:compose + +# Container stoppen +npm run docker:down + +# Health Check +npm run health +``` + +## 📊 Monitoring + +### **Container Status** +```bash +# Status anzeigen +./scripts/monitor.sh status + +# Oder mit npm +npm run monitor status +``` + +### **Health Check** +```bash +# Application Health +./scripts/monitor.sh health + +# Oder direkt +curl http://localhost:3000/api/health +``` + +### **Logs anzeigen** +```bash +# Letzte 50 Zeilen +./scripts/monitor.sh logs 50 + +# Live-Logs folgen +./scripts/monitor.sh logs 100 +``` + +### **Metriken** +```bash +# Detaillierte Metriken +./scripts/monitor.sh metrics +``` + +## 🔧 Wartung + +### **Container neustarten** +```bash +./scripts/monitor.sh restart +``` + +### **Cleanup** +```bash +# Docker-Ressourcen bereinigen +./scripts/monitor.sh cleanup +``` + +### **Updates** +```bash +# Neues Image pullen und deployen +./scripts/deploy.sh production +``` + +## 📈 Performance-Tuning + +### **Nginx Optimierungen** +- **Gzip Compression** aktiviert +- **Static Asset Caching** (1 Jahr) +- **API Rate Limiting** (10 req/s) +- **Load Balancing** bereit für Skalierung + +### **Docker Optimierungen** +- **Multi-Stage Build** für kleinere Images +- **Non-root User** für Sicherheit +- **Health Checks** für automatische Recovery +- **Resource Limits** (512MB RAM, 0.5 CPU) + +### **Next.js Optimierungen** +- **Standalone Output** für Docker +- **Image Optimization** (WebP, AVIF) +- **CSS Optimization** aktiviert +- **Package Import Optimization** + +## 🚨 Troubleshooting + +### **Container startet nicht** +```bash +# Logs prüfen +./scripts/monitor.sh logs + +# Status prüfen +./scripts/monitor.sh status + +# Neustarten +./scripts/monitor.sh restart +``` + +### **Health Check schlägt fehl** +```bash +# Manueller Health Check +curl -v http://localhost:3000/api/health + +# Container-Logs prüfen +docker-compose -f docker-compose.prod.yml logs portfolio +``` + +### **Performance-Probleme** +```bash +# Resource-Usage prüfen +./scripts/monitor.sh metrics + +# Nginx-Logs prüfen +docker-compose -f docker-compose.prod.yml logs nginx +``` + +### **SSL-Probleme** +```bash +# SSL-Zertifikate prüfen +openssl x509 -in ssl/cert.pem -text -noout + +# Nginx-Konfiguration testen +docker-compose -f docker-compose.prod.yml exec nginx nginx -t +``` + +## 📋 CI/CD Pipeline + +### **Workflow-Schritte** +1. **Test** - Linting, Tests, Build +2. **Security** - Trivy Vulnerability Scan +3. **Build** - Multi-Arch Docker Image +4. **Deploy** - Automatisches Deployment + +### **Trigger** +- **Push auf `main`** - Build nur +- **Push auf `production`** - Build + Deploy +- **Pull Request** - Test + Security + +### **Monitoring** +- **GitHub Actions** - Pipeline-Status +- **Container Health** - Automatische Checks +- **Resource Usage** - Monitoring-Skript + +## 🔄 Skalierung + +### **Horizontal Scaling** +```yaml +# In nginx.conf - weitere Backend-Server hinzufügen +upstream portfolio_backend { + least_conn; + server portfolio:3000 max_fails=3 fail_timeout=30s; + server portfolio-2:3000 max_fails=3 fail_timeout=30s; + server portfolio-3:3000 max_fails=3 fail_timeout=30s; +} +``` + +### **Vertical Scaling** +```yaml +# In docker-compose.prod.yml - Resource-Limits erhöhen +deploy: + resources: + limits: + memory: 1G + cpus: '1.0' +``` + +## 📞 Support + +Bei Problemen: +1. **Logs prüfen**: `./scripts/monitor.sh logs` +2. **Status prüfen**: `./scripts/monitor.sh status` +3. **Health Check**: `./scripts/monitor.sh health` +4. **Container neustarten**: `./scripts/monitor.sh restart` diff --git a/Dockerfile b/Dockerfile index a9aeaec..93f5baa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,72 @@ -# Stage 1: Build -FROM node:current-alpine AS builder +# Multi-stage build for optimized production image +FROM node:20-alpine AS base -# Set working directory +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat WORKDIR /app -# Copy package.json and package-lock.json -COPY package*.json ./ +# Install dependencies based on the preferred package manager +COPY package.json package-lock.json* ./ +RUN npm ci --only=production && npm cache clean --force -# Install dependencies including development dependencies -RUN npm install - -# Copy the application code +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules COPY . . # Install type definitions for react-responsive-masonry and node-fetch RUN npm install --save-dev @types/react-responsive-masonry @types/node-fetch -# Build the Next.js application +# Generate Prisma client +RUN npx prisma generate + +# Build the application +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production RUN npm run build -# Stage 2: Production -FROM node:current-alpine - -# Set working directory +# Production image, copy all the files and run next +FROM base AS runner WORKDIR /app -# Copy only the necessary files from the build stage -COPY --from=builder /app/package*.json ./ -COPY --from=builder /app/.next ./.next +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Create a non-root user +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy the built application COPY --from=builder /app/public ./public -COPY --from=builder /app/.env .env -# Install only production dependencies -RUN npm install --only=production +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Copy Prisma files +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma + +# Copy environment file +COPY --from=builder /app/.env* ./ + +USER nextjs -# Expose the port the app runs on EXPOSE 3000 -# Run the app with the start script -ENTRYPOINT [ "npm", "run", "start" ] \ No newline at end of file +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:3000/api/health || exit 1 + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/README-DATABASE.md b/README-DATABASE.md new file mode 100644 index 0000000..ecb8b43 --- /dev/null +++ b/README-DATABASE.md @@ -0,0 +1,228 @@ +# 🗄️ Portfolio Database Setup + +Dieses Portfolio verwendet **PostgreSQL mit Prisma ORM** für maximale Performance und Skalierbarkeit. + +## 🚀 Warum PostgreSQL + Prisma? + +- **🏃‍♂️ Hohe Performance**: Kann tausende User gleichzeitig bedienen +- **📈 Einfache Skalierung**: Von lokal zu Cloud ohne Code-Änderungen +- **🔧 TypeScript-First**: Vollständige Type-Sicherheit und Auto-completion +- **💾 Robuste Datenbank**: ACID, Transaktionen, Indizes für optimale Performance +- **🔄 Einfache Migration**: Einfache Updates und Schema-Änderungen + +## 📋 Voraussetzungen + +- Node.js 18+ +- npm oder yarn +- PostgreSQL (wird automatisch installiert) + +## 🛠️ Schnellstart (Automatisch) + +```bash +# 1. Repository klonen +git clone +cd my_portfolio + +# 2. Automatische Datenbank-Einrichtung +npm run db:setup +``` + +Das Skript installiert automatisch: +- ✅ PostgreSQL +- ✅ Datenbank und Benutzer +- ✅ Prisma Client +- ✅ Beispieldaten +- ✅ Umgebungsvariablen + +## 🔧 Manuelle Einrichtung + +### 1. PostgreSQL installieren + +**Ubuntu/Debian:** +```bash +sudo apt-get update +sudo apt-get install postgresql postgresql-contrib +``` + +**macOS:** +```bash +brew install postgresql +brew services start postgresql +``` + +**Windows:** +- [PostgreSQL Download](https://www.postgresql.org/download/windows/) + +### 2. Datenbank einrichten + +```bash +# PostgreSQL starten +sudo systemctl start postgresql # Linux +brew services start postgresql # macOS + +# Datenbank und Benutzer erstellen +sudo -u postgres psql +CREATE DATABASE portfolio_db; +CREATE USER portfolio_user WITH PASSWORD 'portfolio_pass'; +GRANT ALL PRIVILEGES ON DATABASE portfolio_db TO portfolio_user; +ALTER USER portfolio_user WITH SUPERUSER; +\q +``` + +### 3. Umgebungsvariablen + +Erstelle `.env.local`: +```env +DATABASE_URL="postgresql://portfolio_user:portfolio_pass@localhost:5432/portfolio_db?schema=public" +NEXTAUTH_SECRET="your-secret-key-here" +NEXTAUTH_URL="http://localhost:3000" +``` + +### 4. Dependencies installieren + +```bash +npm install +npx prisma generate +npx prisma db push +npx prisma db seed +``` + +## 🎯 Verfügbare Befehle + +```bash +# Datenbank verwalten +npm run db:setup # Vollständige Einrichtung +npm run db:generate # Prisma Client generieren +npm run db:push # Schema zur Datenbank pushen +npm run db:seed # Beispieldaten einfügen +npm run db:studio # Datenbank-Interface öffnen +npm run db:reset # Datenbank zurücksetzen + +# Entwicklung +npm run dev # Entwicklungsserver starten +npm run build # Produktions-Build +npm run start # Produktions-Server starten +``` + +## 🗄️ Datenbank-Schema + +### Projects +- **Basis**: Titel, Beschreibung, Inhalt, Tags +- **Metadaten**: Kategorie, Schwierigkeit, Datum +- **Performance**: Lighthouse Score, Bundle Size, Load Time +- **Analytics**: Views, Likes, Shares +- **Erweiterte Features**: Technologien, Herausforderungen, Lektionen + +### Analytics +- **PageViews**: Seitenaufrufe mit IP und User-Agent +- **UserInteractions**: Likes, Shares, Bookmarks, Kommentare + +## 📊 Performance-Features + +- **Indizes** auf allen wichtigen Feldern +- **Pagination** für große Datenmengen +- **Caching** für häufige Abfragen +- **Optimierte Queries** mit Prisma +- **Real-time Updates** möglich + +## 🔄 Migration & Updates + +```bash +# Schema ändern +npx prisma db push + +# Bei Breaking Changes +npx prisma migrate dev --name update_schema + +# Produktion +npx prisma migrate deploy +``` + +## 🌐 Deployment + +### Lokal zu Cloud Migration + +1. **Datenbank exportieren:** +```bash +pg_dump portfolio_db > backup.sql +``` + +2. **Cloud-Datenbank einrichten** (z.B. Supabase, PlanetScale, AWS RDS) + +3. **Umgebungsvariablen aktualisieren:** +```env +DATABASE_URL="postgresql://user:pass@host:5432/db?schema=public" +``` + +4. **Schema pushen:** +```bash +npx prisma db push +``` + +## 🚨 Troubleshooting + +### PostgreSQL startet nicht +```bash +# Linux +sudo systemctl status postgresql +sudo systemctl start postgresql + +# macOS +brew services list +brew services restart postgresql +``` + +### Verbindungsfehler +```bash +# PostgreSQL Status prüfen +sudo -u postgres psql -c "SELECT version();" + +# Verbindung testen +psql -h localhost -U portfolio_user -d portfolio_db +``` + +### Prisma Fehler +```bash +# Client neu generieren +npx prisma generate + +# Datenbank zurücksetzen +npx prisma db push --force-reset +``` + +## 📈 Monitoring & Wartung + +### Datenbank-Status +```bash +# Größe prüfen +psql -U portfolio_user -d portfolio_db -c "SELECT pg_size_pretty(pg_database_size('portfolio_db'));" + +# Performance-Statistiken +psql -U portfolio_user -d portfolio_db -c "SELECT * FROM pg_stat_database;" +``` + +### Backup & Restore +```bash +# Backup erstellen +pg_dump -U portfolio_user portfolio_db > backup_$(date +%Y%m%d).sql + +# Backup wiederherstellen +psql -U portfolio_user -d portfolio_db < backup_20241201.sql +``` + +## 🎉 Nächste Schritte + +1. **Datenbank starten**: `npm run db:setup` +2. **Entwicklungsserver starten**: `npm run dev` +3. **Admin-Bereich öffnen**: http://localhost:3000/admin +4. **Projekte verwalten** und dein Portfolio erweitern! + +## 📚 Weitere Ressourcen + +- [Prisma Dokumentation](https://www.prisma.io/docs) +- [PostgreSQL Dokumentation](https://www.postgresql.org/docs/) +- [Next.js API Routes](https://nextjs.org/docs/api-routes/introduction) + +--- + +**Fragen oder Probleme?** Erstelle ein Issue oder kontaktiere mich! 🚀 diff --git a/README.md b/README.md index 9370f93..4732bc0 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,30 @@ -# Dennis Konkol's Portfolio Website +# Dennis Konkol Portfolio - Modern Dark Theme -This is a [Next.js](https://nextjs.org) project bootstrapped with [ -`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, clone the repository: - -```bash -git clone https://github.com/Denshooter/my_portfolio.git -cd my_portfolio -``` - -Then, install the dependencies: - -```bash -npm install -# or -yarn install -# or -pnpm install -# or -bun install -``` - -### Development - -To run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -### Build - -To build the application for production: - -```bash -npm run build -# or -yarn build -# or -pnpm build -# or -bun build -``` - -### Start - -To start the application in production mode: - -```bash -npm run start -# or -yarn start -# or -pnpm start -# or -bun start -``` - -### Lint - -To run the linter: - -```bash -npm run lint -# or -yarn lint -# or -pnpm lint -# or -bun lint -``` - -## Project Structure - -```plaintext -my_portfolio/ -├── .github/ -│ └── workflows/ -│ └── main.yml -├── app/ -│ ├── api/ -│ │ ├── email/ -│ │ │ └── route.tsx -│ │ ├── fetchAllProjects/ -│ │ │ └── route.tsx -│ │ ├── fetchImage/ -│ │ │ └── route.tsx -│ │ ├── fetchProject/ -│ │ │ └── route.tsx -│ │ - -i mean to give more examples how it was built what it features and how it works not how to run it, but more explanations about the project and how its deployed etc. - -Got it! Here's an updated README.md that provides more detailed explanations about the project, its features, how it was built, and how it is deployed: - -```md -# Dennis Konkol's Portfolio - -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). It showcases my projects, provides a way to contact me, and includes various other features. +Ein modernes, responsives Portfolio mit dunklem Design, coolen Animationen und einem integrierten Markdown-Editor. ## Features -- **Responsive Design**: The portfolio is fully responsive and works on all devices. -- **Project Showcase**: Displays a list of projects fetched from a Ghost CMS. -- **Contact Form**: Allows visitors to send me messages via email. -- **SEO Optimized**: Includes metadata and Open Graph tags for better SEO. -- **Dynamic Sitemap**: Automatically generates a sitemap for better search engine indexing. -- **Cookie Consent**: Includes a cookie consent banner to comply with GDPR. -- **Zero-Downtime Deployment**: Uses Docker and GitHub Actions for seamless deployments to a Raspberry Pi. +- **Dunkles Theme** mit Glassmorphism-Effekten +- **Responsive Design** für alle Geräte +- **Smooth Animationen** mit Framer Motion +- **Markdown-Editor** für Projekte +- **Admin Dashboard** für Content-Management -## Project Structure +## Technologien -```plaintext -my_portfolio/ -├── .github/ -│ └── workflows/ -│ └── main.yml -├── app/ -│ ├── api/ -│ │ ├── email/ -│ │ │ └── route.tsx -│ │ ├── fetchAllProjects/ -│ │ │ └── route.tsx -│ │ ├── fetchImage/ -│ │ │ └── route.tsx -│ │ ├── fetchProject/ -│ │ │ └── route.tsx -│ │ ├── og/ -│ │ │ └── route.tsx -│ │ ├── projects/ -│ │ │ └── route.tsx -│ │ ├── sitemap/ -│ │ │ └── route.tsx -│ ├── components/ -│ │ ├── ClientCookieConsentBanner.tsx -│ │ ├── Contact.tsx -│ │ ├── CookieConsentBanner.tsx -│ │ ├── Footer.tsx -│ │ ├── Footer_Back.tsx -│ │ ├── Header.tsx -│ │ ├── Hero.tsx -│ │ ├── Projects.tsx -│ ├── styles/ -│ │ └── ghostContent.css -│ ├── globals.css -│ ├── layout.tsx -│ ├── metadata.tsx -│ ├── not-found.tsx -│ ├── page.tsx -│ ├── privacy-policy/ -│ │ └── page.tsx -│ ├── legal-notice/ -│ │ └── page.tsx -│ ├── projects/ -│ │ └── [slug]/ -│ │ └── page.tsx -│ ├── sitemap.xml/ -│ │ └── route.tsx -│ ├── utils/ -│ │ └── send-email.tsx -├── public/ -│ ├── icons/ -│ │ ├── github.svg -│ │ ├── linkedin.svg -│ ├── images/ -│ ├── robots.txt -├── Dockerfile -├── README.md -├── next.config.ts -├── package.json -├── tailwind.config.ts -├── tsconfig.json -└── eslint.config.mjs -``` +- Next.js 15 mit App Router +- TypeScript für Type Safety +- Tailwind CSS für Styling +- Framer Motion für Animationen +- React Markdown für Content -## How It Works +## Installation -### Project Showcase +npm install +npm run dev -Projects are fetched from a Ghost CMS using the Ghost Content API. The API routes in the `app/api` directory handle -fetching all projects, fetching a single project by slug, and fetching images. +## Verwendung -### Contact Form - -The contact form allows visitors to send me messages via email. It uses the `nodemailer` package to send emails through -an SMTP server. The API route `app/api/email/route.tsx` handles the email sending logic. - -### SEO and Open Graph - -The project includes metadata and Open Graph tags to improve SEO. The `app/metadata.tsx` file defines the metadata for -the site. The `app/api/og/route.tsx` file generates dynamic Open Graph images. - -### Dynamic Sitemap - -A dynamic sitemap is generated to help search engines index the site. The `app/api/sitemap/route.tsx` file generates the -sitemap, and the `app/sitemap.xml/route.tsx` file serves it. - -### Cookie Consent - -A cookie consent banner is included to comply with GDPR. The `app/components/CookieConsentBanner.tsx` and -`app/components/ClientCookieConsentBanner.tsx` components handle the display and logic of the cookie consent banner. - -### Zero-Downtime Deployment - -The project uses Docker and GitHub Actions for zero-downtime deployments to a Raspberry Pi. The -`.github/workflows/main.yml` file defines the GitHub Actions workflow for deploying the project. The `Dockerfile` -defines the Docker image for the project. - -## Deployment - -The project is deployed using Docker and GitHub Actions. The GitHub Actions workflow is defined in -`.github/workflows/main.yml`. It builds the Docker image and deploys it to a Raspberry Pi with zero downtime. - -### Steps to Deploy - -1. **Set Up Raspberry Pi**: Ensure Docker is installed on your Raspberry Pi. -2. **Configure GitHub Secrets**: Add the necessary secrets (e.g., `GHOST_API_KEY`, `MY_EMAIL`, `MY_PASSWORD`) to your - GitHub repository. -3. **Push to GitHub**: Push your changes to the `production`, `dev`, or `preview` branches to trigger the deployment - workflow. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions -are welcome! - -## Author - -- **Dennis Konkol** - [GitHub](https://github.com/Denshooter) | [LinkedIn](https://linkedin.com/in/dkonkol) +- `/` - Homepage +- `/projects` - Alle Projekte +- `/admin` - Admin Dashboard mit Markdown-Editor diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..9132f28 --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,1526 @@ +"use client"; + +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import { + Save, + Eye, + Plus, + Edit, + Trash2, + Upload, + Bold, + Italic, + List, + Link as LinkIcon, + Image as ImageIcon, + Code, + Quote, + ArrowLeft, + ChevronDown, + ChevronRight, + Palette, + Smile, + FileText, + Settings, + Database, + BarChart3 +} from 'lucide-react'; +import Link from 'next/link'; +import ReactMarkdown from 'react-markdown'; +// API functions to replace direct Prisma usage +const apiService = { + async getAllProjects() { + const response = await fetch('/api/projects'); + if (!response.ok) throw new Error('Failed to fetch projects'); + return response.json(); + }, + + async createProject(data: any) { + const response = await fetch('/api/projects', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + if (!response.ok) throw new Error('Failed to create project'); + return response.json(); + }, + + async updateProject(id: number, data: any) { + const response = await fetch(`/api/projects/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + if (!response.ok) throw new Error('Failed to update project'); + return response.json(); + }, + + async deleteProject(id: number) { + const response = await fetch(`/api/projects/${id}`, { + method: 'DELETE' + }); + if (!response.ok) throw new Error('Failed to delete project'); + return response.json(); + } +}; +import AdminDashboard from '@/components/AdminDashboard'; +import { useToast } from '@/components/Toast'; + +interface Project { + id: number; + title: string; + description: string; + content: string; + tags: string[]; + featured: boolean; + category: string; + date: string; + github?: string; + live?: string; + published: boolean; + imageUrl?: string; + metaDescription?: string; + keywords?: string; + ogImage?: string; + schema?: Record; + // New 2.0 features + difficulty: 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert'; + timeToComplete?: string; + technologies: string[]; + challenges: string[]; + lessonsLearned: string[]; + futureImprovements: string[]; + demoVideo?: string; + screenshots: string[]; + colorScheme: string; + accessibility: boolean; + performance: { + lighthouse: number; + bundleSize: string; + loadTime: string; + }; + analytics: { + views: number; + likes: number; + shares: number; + }; +} + +const AdminPage = () => { + const [mounted, setMounted] = useState(false); + const { showProjectSaved, showProjectDeleted, showError, showWarning } = useToast(); + + useEffect(() => { + setMounted(true); + }, []); + + const [projects, setProjects] = useState([]); + + // Load projects from database on mount + useEffect(() => { + loadProjects(); + }, []); + + const loadProjects = async () => { + try { + const result = await apiService.getAllProjects(); + setProjects(result.projects); + } catch (error) { + console.error('Error loading projects from database:', error); + } + }; + + const [selectedProject, setSelectedProject] = useState(null); + + const [isPreview, setIsPreview] = useState(false); + const [isProjectsCollapsed, setIsProjectsCollapsed] = useState(false); + const [showTemplates, setShowTemplates] = useState(false); + const [formData, setFormData] = useState({ + title: '', + description: '', + content: '', + tags: '', + category: '', + featured: false, + github: '', + live: '', + published: true, + imageUrl: '', + // New 2.0 fields + difficulty: 'Intermediate' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + timeToComplete: '', + technologies: '', + challenges: '', + lessonsLearned: '', + futureImprovements: '', + demoVideo: '', + screenshots: '', + colorScheme: 'Dark', + accessibility: true, + performance: { + lighthouse: 90, + bundleSize: '50KB', + loadTime: '1.5s' + }, + analytics: { + views: 0, + likes: 0, + shares: 0 + } + }); + + const [markdownContent, setMarkdownContent] = useState(''); + + const categories = [ + "Web Development", + "Full-Stack", + "Web Application", + "Mobile App", + "Desktop App", + "API Development", + "Database Design", + "DevOps", + "UI/UX Design", + "Game Development", + "Machine Learning", + "Data Science", + "Blockchain", + "IoT", + "Cybersecurity" + ]; + + if (!mounted) { + return null; + } + + const handleSave = async () => { + if (!formData.title || !formData.description || !markdownContent || !formData.category) { + showWarning('Pflichtfelder fehlen', 'Bitte fülle alle erforderlichen Felder aus.'); + return; + } + + try { + if (selectedProject) { + // Update existing project in database + const projectData = { + title: formData.title, + description: formData.description, + content: markdownContent, + tags: formData.tags ? formData.tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [], + category: formData.category, + featured: formData.featured, + github: formData.github || undefined, + live: formData.live || undefined, + published: formData.published, + imageUrl: formData.imageUrl || undefined, + difficulty: formData.difficulty, + timeToComplete: formData.timeToComplete || undefined, + technologies: formData.technologies ? formData.technologies.split(',').map(tech => tech.trim()).filter(tech => tech) : [], + challenges: formData.challenges ? formData.challenges.split(',').map(challenge => challenge.trim()).filter(challenge => challenge) : [], + lessonsLearned: formData.lessonsLearned ? formData.lessonsLearned.split(',').map(lesson => lesson.trim()).filter(lesson => lesson) : [], + futureImprovements: formData.futureImprovements ? formData.futureImprovements.split(',').map(improvement => improvement.trim()).filter(improvement => improvement) : [], + demoVideo: formData.demoVideo || undefined, + screenshots: formData.screenshots ? formData.screenshots.split(',').map(screenshot => screenshot.trim()).filter(screenshot => screenshot) : [], + colorScheme: formData.colorScheme, + accessibility: formData.accessibility, + performance: formData.performance, + analytics: formData.analytics + }; + + await apiService.updateProject(selectedProject.id, projectData); + console.log('Project updated successfully in database:', selectedProject.id); + showProjectSaved(formData.title); + } else { + // Create new project in database + const projectData = { + title: formData.title, + description: formData.description, + content: markdownContent, + tags: formData.tags ? formData.tags.split(',').map(tag => tag.trim()).filter(tag => tag) : [], + category: formData.category, + featured: formData.featured, + github: formData.github || undefined, + live: formData.live || undefined, + published: formData.published, + imageUrl: formData.imageUrl || undefined, + date: new Date().getFullYear().toString(), + difficulty: formData.difficulty, + timeToComplete: formData.timeToComplete || undefined, + technologies: formData.technologies ? formData.technologies.split(',').map(tech => tech.trim()).filter(tech => tech) : [], + challenges: formData.challenges ? formData.challenges.split(',').map(challenge => challenge.trim()).filter(challenge => challenge) : [], + lessonsLearned: formData.lessonsLearned ? formData.lessonsLearned.split(',').map(lesson => lesson.trim()).filter(lesson => lesson) : [], + futureImprovements: formData.futureImprovements ? formData.futureImprovements.split(',').map(improvement => improvement.trim()).filter(improvement => improvement) : [], + demoVideo: formData.demoVideo || undefined, + screenshots: formData.screenshots ? formData.screenshots.split(',').map(screenshot => screenshot.trim()).filter(screenshot => screenshot) : [], + colorScheme: formData.colorScheme, + accessibility: formData.accessibility, + performance: formData.performance, + analytics: formData.analytics + }; + + await apiService.createProject(projectData); + console.log('New project created successfully in database'); + showProjectSaved(formData.title); + } + + // Reload projects from database + await loadProjects(); + resetForm(); + } catch (error) { + console.error('Error saving project:', error); + showError('Fehler beim Speichern', 'Das Projekt konnte nicht gespeichert werden. Bitte versuche es erneut.'); + } + }; + + const handleEdit = (project: Project) => { + console.log('Editing project:', project); + setSelectedProject(project); + setFormData({ + title: project.title, + description: project.description, + content: project.content, + tags: project.tags.join(', '), + category: project.category, + featured: project.featured, + github: project.github || '', + live: project.live || '', + published: project.published !== undefined ? project.published : true, + imageUrl: project.imageUrl || '', + // New 2.0 fields + difficulty: project.difficulty || 'Intermediate' as const, + timeToComplete: project.timeToComplete || '', + technologies: project.technologies ? project.technologies.join(', ') : '', + challenges: project.challenges ? project.challenges.join(', ') : '', + lessonsLearned: project.lessonsLearned ? project.lessonsLearned.join(', ') : '', + futureImprovements: project.futureImprovements ? project.futureImprovements.join(', ') : '', + demoVideo: project.demoVideo || '', + screenshots: project.screenshots ? project.screenshots.join(', ') : '', + colorScheme: project.colorScheme || 'Dark', + accessibility: project.accessibility !== undefined ? project.accessibility : true, + performance: project.performance || { + lighthouse: 90, + bundleSize: '50KB', + loadTime: '1.5s' + }, + analytics: project.analytics || { + views: 0, + likes: 0, + shares: 0 + } + }); + setMarkdownContent(project.content); + setIsPreview(false); + }; + + const handleDelete = async (projectId: number) => { + if (confirm('Are you sure you want to delete this project?')) { + try { + const project = projects.find(p => p.id === projectId); + await apiService.deleteProject(projectId); + await loadProjects(); // Reload from database + console.log('Project deleted successfully from database:', projectId); + if (project) { + showProjectDeleted(project.title); + } + } catch (error) { + console.error('Error deleting project:', error); + showError('Fehler beim Löschen', 'Das Projekt konnte nicht gelöscht werden. Bitte versuche es erneut.'); + } + } + }; + + const resetForm = () => { + console.log('Resetting form'); + setSelectedProject(null); + setFormData({ + title: '', + description: '', + content: '', + tags: '', + category: '', + featured: false, + github: '', + live: '', + published: true, + imageUrl: '', + // New 2.0 fields + difficulty: 'Intermediate' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + timeToComplete: '', + technologies: '', + challenges: '', + lessonsLearned: '', + futureImprovements: '', + demoVideo: '', + screenshots: '', + colorScheme: 'Dark', + accessibility: true, + performance: { + lighthouse: 90, + bundleSize: '50KB', + loadTime: '1.5s' + }, + analytics: { + views: 0, + likes: 0, + shares: 0 + } + }); + setMarkdownContent(''); + setIsPreview(false); + }; + + const insertMarkdown = (type: string) => { + const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement; + if (!textarea) return; + + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const text = textarea.value; + + let insertion = ''; + let cursorOffset = 0; + + switch (type) { + case 'h1': + insertion = `# ${text.substring(start, end) || 'Heading'}`; + cursorOffset = 2; + break; + case 'h2': + insertion = `## ${text.substring(start, end) || 'Heading'}`; + cursorOffset = 3; + break; + case 'bold': + insertion = `**${text.substring(start, end) || 'bold text'}**`; + cursorOffset = 2; + break; + case 'italic': + insertion = `*${text.substring(start, end) || 'italic text'}*`; + cursorOffset = 1; + break; + case 'list': + insertion = `- ${text.substring(start, end) || 'list item'}`; + cursorOffset = 2; + break; + case 'link': + insertion = `[${text.substring(start, end) || 'link text'}](url)`; + cursorOffset = 3; + break; + case 'image': + insertion = `![alt text](image-url)`; + cursorOffset = 9; + break; + case 'code': + insertion = `\`${text.substring(start, end) || 'code'}\``; + cursorOffset = 1; + break; + case 'quote': + insertion = `> ${text.substring(start, end) || 'quote text'}`; + cursorOffset = 2; + break; + case 'table': + insertion = `| Header 1 | Header 2 | Header 3 |\n|----------|----------|----------|\n| Cell 1 | Cell 2 | Cell 3 |\n| Cell 4 | Cell 5 | Cell 6 |`; + cursorOffset = 0; + break; + case 'emoji': + insertion = `😊 ${text.substring(start, end) || 'Add your text here'}`; + cursorOffset = 2; + break; + case 'codeblock': + insertion = `\`\`\`javascript\n${text.substring(start, end) || '// Your code here'}\n\`\`\``; + cursorOffset = 0; + break; + case 'highlight': + insertion = `==${text.substring(start, end) || 'highlighted text'}==`; + cursorOffset = 2; + break; + case 'spoiler': + insertion = `||${text.substring(start, end) || 'spoiler text'}||`; + cursorOffset = 2; + break; + case 'callout': + insertion = `> [!NOTE]\n> ${text.substring(start, end) || 'This is a callout box'}`; + cursorOffset = 0; + break; + } + + const newText = text.substring(0, start) + insertion + text.substring(end); + setMarkdownContent(newText); + + // Set cursor position and select the placeholder text for easy editing + setTimeout(() => { + textarea.focus(); + if (type === 'h1' || type === 'h2') { + // For headings, select the placeholder text so user can type directly + const placeholderStart = start + (type === 'h1' ? 2 : 3); + const placeholderEnd = start + insertion.length; + textarea.setSelectionRange(placeholderStart, placeholderEnd); + } else { + // For other elements, position cursor appropriately + textarea.setSelectionRange(start + insertion.length - cursorOffset, start + insertion.length - cursorOffset); + } + }, 0); + }; + + const handleProjectImageUpload = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const file = files[0]; + if (file) { + // Simulate image upload - in production you'd upload to a real service + const imageUrl = URL.createObjectURL(file); + setFormData(prev => ({ ...prev, imageUrl })); + } + }; + + const handleImageUpload = (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const file = files[0]; + if (file) { + // Create a more descriptive image URL for better organization + const imageName = file.name.replace(/\.[^/.]+$/, ""); // Remove file extension + const imageUrl = URL.createObjectURL(file); + + const textarea = document.getElementById('markdown-editor') as HTMLTextAreaElement; + if (!textarea) return; + + const start = textarea.selectionStart; + const text = textarea.value; + + // Insert image with better alt text and a newline for spacing + const insertion = `\n![${imageName}](${imageUrl})\n`; + + const newText = text.substring(0, start) + insertion + text.substring(start); + setMarkdownContent(newText); + + // Focus back to textarea and position cursor after the image + setTimeout(() => { + textarea.focus(); + const newCursorPos = start + insertion.length; + textarea.setSelectionRange(newCursorPos, newCursorPos); + }, 0); + } + }; + + // Project Templates + const projectTemplates = { + 'Web App': { + title: 'Web Application', + description: 'A modern web application with responsive design and advanced features.', + content: `# Web Application + +## 🚀 Overview +A modern web application built with cutting-edge technologies. + +## ✨ Features +- **Responsive Design**: Works perfectly on all devices +- **Modern UI/UX**: Beautiful and intuitive user interface +- **Performance**: Optimized for speed and efficiency +- **Security**: Built with security best practices + +## 🛠️ Technologies Used +- Frontend Framework +- Backend Technology +- Database +- Additional Tools + +## 📱 Screenshots +![App Screenshot](screenshot-url) + +## 🔗 Links +- [Live Demo](demo-url) +- [GitHub Repository](github-url) + +## 📈 Future Improvements +- Feature 1 +- Feature 2 +- Feature 3`, + difficulty: 'Intermediate' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + category: 'Web Application', + technologies: 'React, Node.js, MongoDB', + colorScheme: 'Modern and Clean' + }, + 'Mobile App': { + title: 'Mobile Application', + description: 'A cross-platform mobile application with native performance.', + content: `# Mobile Application + +## 📱 Overview +A cross-platform mobile application that delivers native performance. + +## ✨ Features +- **Cross-Platform**: Works on iOS and Android +- **Native Performance**: Optimized for mobile devices +- **Offline Support**: Works without internet connection +- **Push Notifications**: Keep users engaged + +## 🛠️ Technologies Used +- React Native / Flutter +- Backend API +- Database +- Cloud Services + +## 📸 Screenshots +![Mobile Screenshot](screenshot-url) + +## 🔗 Links +- [App Store](app-store-url) +- [Play Store](play-store-url) +- [GitHub Repository](github-url) + +## 📈 Future Improvements +- Feature 1 +- Feature 2 +- Feature 3`, + difficulty: 'Advanced' as 'Beginner' | 'Intermediate' | 'Advanced' | 'Expert', + category: 'Mobile App', + technologies: 'React Native, Firebase, Node.js', + colorScheme: 'Mobile-First Design' + }, + 'API Service': { + title: 'API Service', + description: 'A robust and scalable API service with comprehensive documentation.', + content: `# API Service + +## 🔌 Overview +A robust and scalable API service that powers multiple applications. + +## ✨ Features +- **RESTful Design**: Follows REST API best practices +- **Authentication**: Secure JWT-based authentication +- **Rate Limiting**: Prevents abuse and ensures stability +- **Comprehensive Docs**: Complete API documentation +- **Testing**: Extensive test coverage + +## 🛠️ Technologies Used +- Backend Framework +- Database +- Authentication +- Testing Tools + +## 📚 API Endpoints +\`\`\` +GET /api/users +POST /api/users +PUT /api/users/:id +DELETE /api/users/:id +\`\`\` + +## 🔗 Links +- [API Documentation](docs-url) +- [GitHub Repository](github-url) + +## 📈 Future Improvements +- Feature 1 +- Feature 2 +- Feature 3`, + difficulty: 'Intermediate' as const, + category: 'API Development', + technologies: 'Node.js, Express, MongoDB', + colorScheme: 'Professional and Clean' + } + }; + + const applyTemplate = (templateKey: string) => { + const template = projectTemplates[templateKey as keyof typeof projectTemplates]; + if (template) { + setFormData({ + ...formData, + title: template.title, + description: template.description, + category: template.category, + difficulty: template.difficulty, + technologies: template.technologies, + colorScheme: template.colorScheme + }); + setMarkdownContent(template.content); + setShowTemplates(false); + } + }; + + return ( +
+
+ {/* Header */} + + + + Back to Home + + +

+ Admin Dashboard +

+

+ Manage your projects with the built-in Markdown editor. Create, edit, and preview your content easily. +

+
+ + {/* Projects Toggle Button - Always Visible */} +
+ setIsProjectsCollapsed(!isProjectsCollapsed)} + className="flex items-center space-x-2 px-6 py-3 bg-gradient-to-r from-gray-700 to-gray-800 hover:from-gray-600 hover:to-gray-700 rounded-xl text-white transition-all duration-200 hover:scale-105 border border-gray-600/50 shadow-lg" + title={isProjectsCollapsed ? "Show Projects" : "Hide Projects"} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {isProjectsCollapsed ? ( + <> + + Show Projects + + ) : ( + <> + + Hide Projects + + )} + +
+ +
+ {/* Projects List */} +
+ +
+

Projects

+ +
+ +
+ {projects.map((project) => ( +
handleEdit(project)} + > +

{project.title}

+

{project.description}

+
+ {project.category} +
+ + +
+
+
+ ))} +
+
+
+ + {/* Editor */} +
+ +
+

+ {selectedProject ? 'Edit Project' : 'New Project'} +

+
+ + + + {selectedProject && ( + + )} +
+
+ + {/* Project Templates Modal */} + {showTemplates && ( + setShowTemplates(false)} + > + e.stopPropagation()} + > +
+

🚀 Project Templates

+ +
+ +
+ {Object.entries(projectTemplates).map(([key, template]) => ( + applyTemplate(key)} + > +
+
+ +
+

{template.title}

+

{template.description}

+
+ +
+
+ Difficulty: + + {template.difficulty} + +
+
+ Category: + {template.category} +
+
+ Tech: + {template.technologies} +
+
+ + +
+ ))} +
+ +
+

+ Templates provide a starting point for your projects. Customize them as needed! +

+
+
+
+ )} + + {!isPreview ? ( +
+ {/* Basic Info */} +
+
+ + setFormData({...formData, title: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Project title" + /> +
+ +
+ + +
+
+ + {/* Links */} +
+
+ + setFormData({...formData, github: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="https://github.com/username/repo" + /> +
+ +
+ + setFormData({...formData, live: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="https://demo.example.com" + /> +
+
+ + {/* Project Image */} +
+ +
+
+ {formData.imageUrl ? ( + Project preview + ) : ( +
+ + {formData.title ? formData.title.split(' ').map(word => word[0]).join('').toUpperCase() : 'P'} + +
+ )} +
+
+ + +

Upload a project image or use auto-generated initials

+
+
+
+ + {/* Advanced Project Details - 2.0 Features */} +
+

+ + Advanced Project Details +

+ + {/* Difficulty & Time */} +
+
+ + +
+ +
+ + setFormData({...formData, timeToComplete: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g., 2-3 weeks, 1 month" + /> +
+
+ + {/* Technologies & Color Scheme */} +
+
+ + setFormData({...formData, technologies: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="React, TypeScript, Node.js" + /> +
+ +
+ + setFormData({...formData, colorScheme: e.target.value})} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g., Dark theme, Light theme, Colorful" + /> +
+
+ + {/* Performance Metrics */} +
+
+ + setFormData({ + ...formData, + performance: {...formData.performance, lighthouse: parseInt(e.target.value)} + })} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="90" + /> +
+ +
+ + setFormData({ + ...formData, + performance: {...formData.performance, bundleSize: e.target.value} + })} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="45KB" + /> +
+ +
+ + setFormData({ + ...formData, + performance: {...formData.performance, loadTime: e.target.value} + })} + className="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="1.2s" + /> +
+
+ + {/* Learning & Challenges */} +
+
+ + -
- -
- - -
- - - +

Send Message

+ +
+
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ +