diff --git a/app/components/About.tsx b/app/components/About.tsx index f2275cb..751e138 100644 --- a/app/components/About.tsx +++ b/app/components/About.tsx @@ -1,13 +1,13 @@ "use client"; import { useState, useEffect } from "react"; -import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb, User, BookOpen, MessageSquare, ExternalLink, ArrowRight } from "lucide-react"; +import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb, BookOpen, MessageSquare, ArrowRight, Send } from "lucide-react"; import { useLocale, useTranslations } from "next-intl"; import type { JSONContent } from "@tiptap/react"; import RichTextClient from "./RichTextClient"; import CurrentlyReading from "./CurrentlyReading"; import ReadBooks from "./ReadBooks"; -import { motion } from "framer-motion"; +import { motion, AnimatePresence } from "framer-motion"; import { TechStackCategory, Hobby } from "@/lib/directus"; import Link from "next/link"; import ActivityFeed from "./ActivityFeed"; @@ -22,21 +22,30 @@ const About = () => { const [cmsDoc, setCmsDoc] = useState(null); const [techStack, setTechStack] = useState([]); const [hobbies, setHobbies] = useState([]); + const [isActivityActive, setIsActivityActive] = useState(false); + const [cmsMessages, setCmsMessages] = useState>({}); useEffect(() => { const fetchData = async () => { try { - const [cmsRes, techRes, hobbiesRes] = await Promise.all([ + const [cmsRes, techRes, hobbiesRes, msgRes] = await Promise.all([ fetch(`/api/content/page?key=home-about&locale=${locale}`), fetch(`/api/tech-stack?locale=${locale}`), - fetch(`/api/hobbies?locale=${locale}`) + fetch(`/api/hobbies?locale=${locale}`), + fetch(`/api/messages?locale=${locale}`) ]); + const cmsData = await cmsRes.json(); if (cmsData?.content?.content) setCmsDoc(cmsData.content.content as JSONContent); + const techData = await techRes.json(); if (techData?.techStack) setTechStack(techData.techStack); + const hobbiesData = await hobbiesRes.json(); if (hobbiesData?.hobbies) setHobbies(hobbiesData.hobbies); + + const msgData = await msgRes.json(); + if (msgData?.messages) setCmsMessages(msgData.messages); } catch (error) {} }; fetchData(); @@ -56,56 +65,103 @@ const About = () => { className="md:col-span-8 bg-white dark:bg-stone-900 rounded-[3rem] p-10 md:p-16 border border-stone-200/60 dark:border-stone-800/60 shadow-sm" >
-

- {t("title")} +

+ {t("title")}.

{cmsDoc ? :

{t("p1")} {t("p2")}

}
-
-

{t("funFactTitle")}

+
+

{t("funFactTitle")}

{t("funFactBody")}

- {/* 2. Doing Right Now (Status) */} + {/* 2. Activity / Status Box */} -
-

- Doing Now +
+

+ Currently

-
- + +
+ setIsActivityActive(active)} /> + + {!isActivityActive && ( + +

+ “{cmsMessages["about.quote.idle"] || "Gerade am Planen des nächsten großen Projekts."}” +

+
+ )}
- {/* Ambient Background for Status */} -
+ {/* Design Element */} +
- {/* 3. Tech Stack */} + {/* 3. AI Chat Box (Direct Integration) */} +
+
+
+ +
+
+ {[1,2,3].map(i =>
)} +
+
+ +

Ask my AI Twin

+

Get instant answers about my stack, availability or experience.

+ + +
+
+ + {/* 4. Tech Stack */} +
{techStack.map((cat) => (
-

{cat.name}

+

{cat.name}

{cat.items?.map((item: any) => ( - + {item.name} ))} @@ -115,31 +171,7 @@ const About = () => {
- {/* 4. AI Chat Box */} - { - const chatBtn = document.querySelector('[aria-label="Open chat"]') as HTMLElement; - if (chatBtn) chatBtn.click(); - }} - > -
- -
-
-

AI Assistant

-

Have questions about my projects or experience? Ask my digital twin.

-
-
- Start Chat -
-
- - {/* 5. Reading & Hobbies Archive Row */} + {/* 5. Library Link & Hobbies */} { transition={{ delay: 0.4 }} className="md:col-span-12 grid grid-cols-1 md:grid-cols-2 gap-8" > - {/* Reading Mini-Box */} -
-
-

- Reading -

- - View Library - -
-
+
+
+
+

+ Library +

+ + View All + +
+
- {/* Hobbies Mini-Box */}
-
+
{hobbies.map((hobby) => { const Icon = iconMap[hobby.icon] || Lightbulb; return ( -
- - {hobby.title} +
+
) })}

{t("hobbiesTitle")}

-

Things that spark my curiosity outside of software engineering.

+

Curiosity beyond software engineering.

diff --git a/app/components/ActivityFeed.tsx b/app/components/ActivityFeed.tsx index 8406c54..a13e644 100644 --- a/app/components/ActivityFeed.tsx +++ b/app/components/ActivityFeed.tsx @@ -68,7 +68,7 @@ function getSafeGamingText(details: string | number | undefined, state: string | return fallback; } -export default function ActivityFeed() { +export default function ActivityFeed({ onActivityChange }: { onActivityChange?: (active: boolean) => void }) { const [data, setData] = useState(null); const [isExpanded, setIsExpanded] = useState(true); const [isMinimized, setIsMinimized] = useState(false); @@ -200,6 +200,7 @@ export default function ActivityFeed() { } setHasActivity(hasActiveActivity); + onActivityChange?.(hasActiveActivity); // Auto-expand if there's new activity and not minimized if (hasActiveActivity && !isMinimized) { diff --git a/app/legal-notice/page.tsx b/app/legal-notice/page.tsx index 877dce4..124e687 100644 --- a/app/legal-notice/page.tsx +++ b/app/legal-notice/page.tsx @@ -24,42 +24,34 @@ export default function LegalNotice() { `/api/content/page?key=${encodeURIComponent("legal-notice")}&locale=${encodeURIComponent(locale)}`, ); const data = await res.json(); - // Only use CMS content if it exists for the active locale. if (data?.content?.content && data?.content?.locale === locale) { setCmsDoc(data.content.content as JSONContent); setCmsTitle((data.content.title as string | null) ?? null); - } else { - setCmsDoc(null); - setCmsTitle(null); } - } catch { - // ignore; fallback to static content - setCmsDoc(null); - setCmsTitle(null); - } + } catch {} })(); }, [locale]); return ( -
+
-
+
- - {t("backToHome")} + + {t("backToHome")} -

- {cmsTitle || "Impressum"} +

+ Legal.

@@ -67,69 +59,34 @@ export default function LegalNotice() { initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, delay: 0.2 }} - className="glass-card p-8 rounded-2xl space-y-6" + className="bg-white dark:bg-stone-900 rounded-[3rem] p-10 md:p-16 border border-stone-200/60 dark:border-stone-800/60 shadow-sm" > {cmsDoc ? ( - +
+ +
) : ( - <> -
-

Verantwortlicher für die Inhalte dieser Website

-
-

- Name: Dennis Konkol -

-

- Adresse: Auf dem Ziegenbrink 2B, 49082 Osnabrück, Deutschland -

-

- E-Mail:{" "} - - info@dk0.dev - -

-

- Website:{" "} - - dk0.dev - -

+
+
+

Angaben gemäß § 5 TMG

+
+

Dennis Konkol

+

Auf dem Ziegenbrink 2B, 49082 Osnabrück, Deutschland

+

E-Mail: info@dk0.dev

-
-

Haftung für Links

-

- Meine Website enthält Links auf externe Websites. Ich habe keinen Einfluss auf die Inhalte dieser - Websites und kann daher keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der - Betreiber oder Anbieter der Seiten verantwortlich. Jedoch überprüfe ich die verlinkten Seiten zum - Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße. Bei Bekanntwerden von Rechtsverletzungen werde - ich derartige Links umgehend entfernen. +

+

Haftung für Inhalte

+

+ Als Diensteanbieter bin ich gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG bin ich als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen.

-
-

Urheberrecht

-

- Alle Inhalte dieser Website, einschließlich Texte, Fotos und Designs, stehen unter - Urheberrechtsschutz. Jegliche Nutzung ohne vorherige schriftliche Zustimmung des Urhebers ist - verboten. -

+
+

Last updated: 12.02.2025

- -
-

Gewährleistung

-

- Die Nutzung der Inhalte dieser Website erfolgt auf eigene Gefahr. Als Diensteanbieter kann ich keine - Gewähr übernehmen für Schäden, die entstehen können, durch den Zugriff oder die Nutzung dieser - Website. -

-
- -
-

Letzte Aktualisierung: 12.02.2025

-
- +
)}
diff --git a/app/privacy-policy/page.tsx b/app/privacy-policy/page.tsx index ee541ba..1ffebb8 100644 --- a/app/privacy-policy/page.tsx +++ b/app/privacy-policy/page.tsx @@ -15,7 +15,6 @@ export default function PrivacyPolicy() { const locale = useLocale(); const t = useTranslations("common"); const [cmsDoc, setCmsDoc] = useState(null); - const [cmsTitle, setCmsTitle] = useState(null); useEffect(() => { (async () => { @@ -24,42 +23,33 @@ export default function PrivacyPolicy() { `/api/content/page?key=${encodeURIComponent("privacy-policy")}&locale=${encodeURIComponent(locale)}`, ); const data = await res.json(); - // Only use CMS content if it exists for the active locale. if (data?.content?.content && data?.content?.locale === locale) { setCmsDoc(data.content.content as JSONContent); - setCmsTitle((data.content.title as string | null) ?? null); - } else { - setCmsDoc(null); - setCmsTitle(null); } - } catch { - // ignore; fallback to static content - setCmsDoc(null); - setCmsTitle(null); - } + } catch {} })(); }, [locale]); return ( -
+
-
+
- - - {t("backToHome")} - + + {t("backToHome")} + -

- {cmsTitle || "Datenschutzerklärung"} +

+ Privacy.

@@ -67,265 +57,29 @@ export default function PrivacyPolicy() { initial={{ opacity: 0, y: 30 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, delay: 0.2 }} - className="glass-card p-8 rounded-2xl space-y-6 text-white" + className="bg-white dark:bg-stone-900 rounded-[3rem] p-10 md:p-16 border border-stone-200/60 dark:border-stone-800/60 shadow-sm" > {cmsDoc ? ( - +
+ +
) : ( - <> -
-

- Der Schutz Ihrer persönlichen Daten ist mir wichtig. In dieser Datenschutzerklärung informiere ich Sie - über die Verarbeitung personenbezogener Daten im Rahmen meines Internet-Angebots. +

+
+

Datenschutz

+

+ Der Schutz Ihrer persönlichen Daten ist mir ein besonderes Anliegen. Ich verarbeite Ihre Daten daher ausschließlich auf Grundlage der gesetzlichen Bestimmungen (DSGVO, TMG). +

+

Kontakt mit mir

+

+ Wenn Sie per Formular auf der Website oder per E-Mail Kontakt mit mir aufnehmen, werden Ihre angegebenen Daten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei mir gespeichert. Diese Daten gebe ich nicht ohne Ihre Einwilligung weiter.

-
-

Verantwortlicher für die Datenverarbeitung

-
-

- Name: Dennis Konkol -

-

- Adresse: Auf dem Ziegenbrink 2B, 49082 Osnabrück, Deutschland -

-

- E-Mail:{" "} - - info@dk0.dev - -

-

- Website:{" "} - - dk0.dev - -

-
-

- Diese Datenschutzerklärung gilt für die Verarbeitung personenbezogener Daten durch den oben genannten - Verantwortlichen. -

+
+

Last updated: 12.02.2025

- -

- Erfassung allgemeiner Informationen beim Besuch meiner Website -

-
- Beim Zugriff auf meiner Website werden automatisch Informationen allgemeiner Natur erfasst. Diese - beinhalten unter anderem: -
    -
  • IP-Adresse (in anonymisierter Form)
  • -
  • Uhrzeit
  • -
  • Browsertyp
  • -
  • Verwendetes Betriebssystem
  • -
  • Referrer-URL (die zuvor besuchte Seite)
  • -
-
- Diese Informationen werden anonymisiert erfasst und dienen ausschließlich statistischen Auswertungen. - Rückschlüsse auf Ihre Person sind nicht möglich. Diese Daten werden verarbeitet, um: -
    -
  • die Inhalte meiner Website korrekt auszuliefern,
  • -
  • die Inhalte meiner Website zu optimieren,
  • -
  • die Systemsicherheit und -stabilität zu analysiern.
  • -
-
- -

Cookies

-

- Diese Website verwendet ein technisch notwendiges Cookie, um deine Datenschutz-Einstellungen (z.B. - Analytics/Chatbot) zu speichern. Ohne dieses Cookie wäre ein Consent-Banner bei jedem Besuch erneut - nötig. -

- -

Analyse- und Tracking-Tools

-

- Die nachfolgend beschriebene Analyse- und Tracking-Methode (im - Folgenden „Maßnahme“ genannt) basiert auf Art. 6 Abs. 1 S. 1 lit. f - DSGVO. Durch diese Maßnahme möchten ich eine benutzerfreundliche - Gestaltung sowie eine kontinuierliche Verbesserung meiner Website - sicherstellen. Diese Interessen sind im Sinne der genannten Vorschrift - als berechtigt anzusehen. -
-
- Rechtsgrundlage: Art. 6 Abs. 1 S. 1 lit. f DSGVO (berechtigtes - Interesse an der Analyse und Optimierung unserer Website). -
-
- Detaillierte Informationen zu den erhobenen Daten und deren - Verarbeitung finden Sie in den nachfolgenden Abschnitten. -
-
- Zur Analyse der Nutzung meiner Website setze ich Umami ein. Umami - speichert keine IP-Adressen oder Cookies. Alle erfassten Daten sind - anonymisiert. Da ich Umami auf meinem eigenen Server betreibe, erfolgt - keine Weitergabe an Dritte. Weitere Informationen finden Sie unter{" "} - - Umami - - . -

-

- Zusätzlich kann diese Website optionale, selbst gehostete - Nutzungsstatistiken erfassen (z.B. Seitenaufrufe, Performance-Metriken), - die erst nach deiner Einwilligung im Consent-Banner aktiviert werden. -

- -

Error Monitoring (Sentry)

-

- Um Fehler und Probleme auf dieser Website schnell zu erkennen und zu beheben, - nutze ich Sentry.io, einen Dienst zur Fehlerüberwachung. Dabei werden technische - Informationen wie Browser-Typ, Betriebssystem, URL der aufgerufenen Seite und - Fehlermeldungen an Sentry übermittelt. Diese Daten dienen ausschließlich der - Verbesserung der Website-Stabilität und werden nicht für andere Zwecke verwendet. -
-
- Anbieter: Functional Software, Inc. (Sentry), 45 Fremont Street, 8th Floor, - San Francisco, CA 94105, USA -
-
- Rechtsgrundlage: Art. 6 Abs. 1 S. 1 lit. f DSGVO (berechtigtes Interesse an - der Fehleranalyse und Systemstabilität). -
-
- Weitere Informationen: - Sentry Datenschutzerklärung - -

- -

Kontaktformular

-

- Wenn Sie das Kontaktformular nutzen, werden Ihre Angaben zur - Bearbeitung Ihrer Anfrage gespeichert. Diese Daten werden nicht an - Dritte weitergegeben und nach Erfüllung des Zwecks gelöscht.
-
- Rechtsgrundlage: Art. 6 Abs. 1 S. 1 lit. a DSGVO (Einwilligung). -

-

Chatbot

-

- Wenn du den optionalen Chatbot nutzt, werden die von dir eingegebenen - Nachrichten verarbeitet, um eine Antwort zu generieren. Die Verarbeitung - kann dabei über eine selbst gehostete Automations-/Chat-Infrastruktur - (z.B. n8n) erfolgen. Bitte gib im Chat keine sensiblen Daten ein. -
-
- Rechtsgrundlage: Art. 6 Abs. 1 S. 1 lit. a DSGVO (Einwilligung) – der - Chatbot wird erst nach Aktivierung im Consent-Banner geladen. -

-

Social Media Links

-

- Unsere Website enthält Links zu GitHub und LinkedIn. Durch das - Anklicken dieser Links gelten die Datenschutzbestimmungen der - jeweiligen Anbieter. -

-

Weitergabe von Daten

-
- Eine Weitergabe Ihrer personenbezogenen Daten erfolgt nur, wenn: -
    -
  • - Sie nach Art. 6 Abs. 1 S. 1 lit. a DSGVO ausdrücklich eingewilligt - haben, -
  • -
  • - dies zur Vertragserfüllung gemäß Art. 6 Abs. 1 S. 1 lit. b DSGVO - erforderlich ist, -
  • -
  • - eine gesetzliche Verpflichtung zur Weitergabe nach Art. 6 Abs. 1 - S. 1 lit. c DSGVO besteht oder -
  • -
  • - die Verarbeitung nach Art. 6 Abs. 1 S. 1 lit. f DSGVO zur Wahrung - berechtigter Interessen erforderlich ist. -
  • -
-
-

- Speicherdauer und Löschung -

-

- Ihre Daten werden nur solange gespeichert, wie dies für die Erfüllung - des Verarbeitungszwecks erforderlich ist. Nach Erfüllung des Zwecks - werden Ihre Daten gelöscht. -

-

Ihre Rechte

-
- Sie haben gemäß DSGVO folgende Rechte: -
    -
  • - Art. 15 DSGVO: Auskunftsrecht über Ihre von mir gespeicherten - Daten -
  • -
  • - Art. 16 DSGVO: Recht auf Berichtigung unrichtiger oder - unvollständiger Daten -
  • -
  • - Art. 17 DSGVO: Recht auf Löschung Ihrer bei mir gespeicherten - Daten (soweit keine gesetzlichen Aufbewahrungspflichten - entgegenstehen) -
  • -
  • - Art. 18 DSGVO: Recht auf Einschränkung der Verarbeitung Ihrer - Daten -
  • -
  • Art. 20 DSGVO: Recht auf Datenübertragbarkeit
  • -
  • - Art. 21 DSGVO: Widerspruchsrecht gegen die Verarbeitung Ihrer - Daten -
  • -
-
- Falls Sie eine Einwilligung erklärt haben, können Sie diese jederzeit - widerrufen. -
- Beschwerden können Sie an die zuständige Datenschutzaufsichtsbehörde - richten. Eine Liste der Datenschutzbeauftragten sowie deren - Kontaktdaten finden Sie unter:{" "} - - https://www.bfdi.bund.de/ - -
-

Datensicherheit

-

- Ich setze technische und organisatorische Maßnahmen ein, um Ihre Daten - zu schützen. Dazu gehören unter anderem die SSL-Verschlüsselung. Diese - Verschlüsselung erkennen Sie an dem Schloss-Symbol in der Adresszeile - Ihres Browsers und an der URL, die mit "https://" beginnt. -

-

Kontakt

-

- Bei Fragen zur Datenschutzerklärung kontaktieren Sie mich unter{" "} - - info@dk0.dev - {" "} - oder nutzen Sie das Kontaktformular auf meiner Website. -

-

- Änderungen der Datenschutzerklärung -

-

- Diese Datenschutzerklärung wird regelmäßig aktualisiert, um den - gesetzlichen Anforderungen zu entsprechen und neue Entwicklungen zu - berücksichtigen. Die jeweils aktuelle Datenschutzerklärung finden Sie - auf meiner Website. -

-
-

Letzte Aktualisierung: 12.02.2025

-
- +
)}
diff --git a/scripts/fix-messages-collection.js b/scripts/fix-messages-collection.js new file mode 100644 index 0000000..8d686bf --- /dev/null +++ b/scripts/fix-messages-collection.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(endpoint, method = 'POST', body = null) { + const res = await fetch(`${DIRECTUS_URL}/${endpoint}`, { + method, + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : null + }); + const data = await res.json().catch(() => ({})); + return { ok: res.ok, data }; +} + +async function fixMessagesCollection() { + console.log('🛠 Repariere "messages" Collection...'); + + // 1. Key-Feld hinzufügen (falls es fehlt) + // Wir nutzen type: string und schema: {} um eine echte Spalte zu erzeugen + const fieldRes = await api('fields/messages', 'POST', { + field: 'key', + type: 'string', + schema: { + is_nullable: false, + is_unique: true + }, + meta: { + interface: 'input', + options: { placeholder: 'z.B. hero.title' }, + required: true + } + }); + + if (fieldRes.ok) { + console.log('✅ "key" Feld erfolgreich erstellt.'); + } else { + console.log('⚠️ "key" Feld konnte nicht erstellt werden (existiert evtl schon).'); + } + + // 2. Übersetzungs-Feld in der Untertabelle reparieren + console.log('🛠 Prüfe messages_translations...'); + await api('fields/messages_translations', 'POST', { + field: 'value', + type: 'text', + schema: {}, + meta: { interface: 'input-multiline' } + }).catch(() => {}); + + console.log('✅ Fix abgeschlossen! Bitte lade Directus neu.'); +} + +fixMessagesCollection().catch(console.error); diff --git a/scripts/seed-cms-content.js b/scripts/seed-cms-content.js new file mode 100644 index 0000000..25c0f26 --- /dev/null +++ b/scripts/seed-cms-content.js @@ -0,0 +1,37 @@ +#!/usr/bin/env node +require('dotenv').config(); +const DIRECTUS_URL = process.env.DIRECTUS_URL || 'https://cms.dk0.dev'; +const DIRECTUS_TOKEN = process.env.DIRECTUS_STATIC_TOKEN; + +async function api(endpoint, method = 'POST', body = null) { + const res = await fetch(`${DIRECTUS_URL}/${endpoint}`, { + method, + headers: { 'Authorization': `Bearer ${DIRECTUS_TOKEN}`, 'Content-Type': 'application/json' }, + body: body ? JSON.stringify(body) : null + }); + return res.ok ? await res.json() : { error: true, status: res.status }; +} + +const seedData = [ + { key: 'hero.badge', de: 'Student & Self-Hoster', en: 'Student & Self-Hoster' }, + { key: 'hero.line1', de: 'Building', en: 'Building' }, + { key: 'hero.line2', de: 'Stuff.', en: 'Stuff.' }, + { key: 'about.quote.idle', de: 'Gerade am Planen des nächsten großen Projekts.', en: 'Currently planning the next big thing.' } +]; + +async function seedMessages() { + console.log('🌱 Befülle Directus mit Inhalten...'); + for (const item of seedData) { + console.log(`- Erstelle Key: ${item.key}`); + const res = await api('items/messages', 'POST', { + key: item.key, + translations: [ + { languages_code: 'de-DE', value: item.de }, + { languages_code: 'en-US', value: item.en } + ] + }); + } + console.log('✅ CMS erfolgreich befüllt!'); +} + +seedMessages().catch(console.error);