diff --git a/app/Projects/[slug]/page.tsx b/app/Projects/[slug]/page.tsx index 500e1e5..86fcf0d 100644 --- a/app/Projects/[slug]/page.tsx +++ b/app/Projects/[slug]/page.tsx @@ -1,89 +1,127 @@ -// app/Projects/[slug]/page.tsx "use client"; -import {useParams, useRouter} from "next/navigation"; -import {useEffect, useState} from "react"; -import Header from "../../components/Header"; -import Footer_Back from "../../components/Footer_Back"; -import ReactMarkdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import rehypeRaw from "rehype-raw"; -import matter from "gray-matter"; +import { + useRouter, + useSearchParams, + useParams, + usePathname, +} from "next/navigation"; +import { useEffect, useState } from "react"; + +import Footer_Back from "@/app/components/Footer_Back"; +import Header from "@/app/components/Header"; +import Image from "next/image"; +import "@/app/styles/ghostContent.css"; // Import the global styles interface Project { - id: string; - title: string; - description: string; - text: string; - slug: string; - image: string; + slug: string; + id: string; + title: string; + feature_image: string; + visibility: string; + published_at: string; + updated_at: string; + html: string; + reading_time: number; + meta_description: string; } -export default function ProjectDetail() { - const params = useParams(); - const router = useRouter(); - const {slug} = params as { slug: string }; - const [project, setProject] = useState(null); - const [isVisible, setIsVisible] = useState(false); +const ProjectDetails = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const params = useParams(); + const pathname = usePathname(); + const [project, setProject] = useState(null); + const [isVisible, setIsVisible] = useState(false); - useEffect(() => { - setTimeout(() => { - setIsVisible(true); - }, 150); // Delay to start the animation - }, []); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 150); // Delay to start the animation + }, []); - useEffect(() => { - if (slug) { - fetch(`/projects/${slug}.md`) - .then((res) => res.text()) - .then((content) => { - const {data, content: markdownContent} = matter(content); - setProject({ - id: data.id, - title: data.title, - description: data.description, - text: markdownContent, - slug: slug, - image: data.image, - }); - - // Log the project view - fetch("/api/stats", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - type: "project_view", - projectId: data.id, - }), - }).catch((err) => console.error("Failed to log project view", err)); - }) - .catch(() => { - // Redirect to 404 if project not found - router.replace("/not-found"); - }); - } - }, [slug, router]); - - if (!project) { - return
Loading...
; + useEffect(() => { + const projectData = searchParams.get("project"); + if (projectData) { + setProject(JSON.parse(projectData)); + // Remove the project data from the URL without reloading the page + const url = new URL(window.location.href); + url.searchParams.delete("project"); + window.history.replaceState({}, "", url.toString()); + } else { + // Fetch project data based on slug from URL + const slug = params.slug as string; + fetchProjectData(slug); } + }, [searchParams, router, params, pathname]); + const fetchProjectData = async (slug: string) => { + try { + const response = await fetch(`/api/fetchProject?slug=${slug}`); + if (!response.ok) { + throw new Error("Failed to fetch project data"); + } + const projectData = await response.json(); + setProject(projectData.posts[0]); // Assuming the API returns an array of posts + } catch (error) { + console.error("Failed to fetch project data:", error); + } + }; + + if (!project) { return ( -
-
-
-
-
- - {project.text} - -
-
-
- +
+
+
+
+ +
); -} \ No newline at end of file + } + + const featureImageUrl = `/api/fetchImage?url=${encodeURIComponent(project.feature_image)}`; + + return ( +
+
+
+ {/* Hero Section */} +
+
+ {" "} + {/* 16:9 Aspect Ratio */} + {project.title} +
+
+
+

+ {project.title} +

+
+ + {/* Project Content */} +
+
+
+
+
+
+ +
+ ); +}; + +export default ProjectDetails; diff --git a/app/api/fetchAllProjects/route.tsx b/app/api/fetchAllProjects/route.tsx new file mode 100644 index 0000000..26eb7ea --- /dev/null +++ b/app/api/fetchAllProjects/route.tsx @@ -0,0 +1,25 @@ +import { NextResponse } from "next/server"; + +export const runtime = "nodejs"; // Force Node runtime + +const GHOST_API_URL = "http://192.168.179.31:2368"; +const GHOST_API_KEY = "067b8434f2e7f2a771dfcc45a7"; // Replace with your actual key + +export async function GET() { + try { + const response = await fetch( + `${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`, + ); + if (!response.ok) { + throw new Error(`Failed to fetch posts: ${response.statusText}`); + } + const posts = await response.json(); + return NextResponse.json(posts); + } catch (error) { + console.error("Failed to fetch posts from Ghost:", error); + return NextResponse.json( + { error: "Failed to fetch projects" }, + { status: 500 }, + ); + } +} diff --git a/app/api/fetchImage/route.tsx b/app/api/fetchImage/route.tsx new file mode 100644 index 0000000..421670a --- /dev/null +++ b/app/api/fetchImage/route.tsx @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from "next/server"; + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const url = searchParams.get("url"); + + if (!url) { + return NextResponse.json( + { error: "Missing URL parameter" }, + { status: 400 }, + ); + } + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`); + } + + const contentType = response.headers.get("content-type"); + const buffer = await response.arrayBuffer(); + + return new NextResponse(buffer, { + headers: { + "Content-Type": contentType || "application/octet-stream", + }, + }); + } catch (error) { + console.error("Failed to fetch image:", error); + return NextResponse.json( + { error: "Failed to fetch image" }, + { status: 500 }, + ); + } +} diff --git a/app/api/fetchProject/route.tsx b/app/api/fetchProject/route.tsx new file mode 100644 index 0000000..26d46aa --- /dev/null +++ b/app/api/fetchProject/route.tsx @@ -0,0 +1,32 @@ +import { NextResponse } from "next/server"; + +export const runtime = "nodejs"; // Force Node runtime + +const GHOST_API_URL = "http://192.168.179.31:2368"; +const GHOST_API_KEY = "067b8434f2e7f2a771dfcc45a7"; // Replace with your actual key + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const slug = searchParams.get("slug"); + + if (!slug) { + return NextResponse.json({ error: "Slug is required" }, { status: 400 }); + } + + try { + const response = await fetch( + `${GHOST_API_URL}/ghost/api/content/posts/slug/${slug}/?key=${GHOST_API_KEY}`, + ); + if (!response.ok) { + throw new Error(`Failed to fetch post: ${response.statusText}`); + } + const post = await response.json(); + return NextResponse.json(post); + } catch (error) { + console.error("Failed to fetch post from Ghost:", error); + return NextResponse.json( + { error: "Failed to fetch project" }, + { status: 500 }, + ); + } +} diff --git a/app/components/Hero.tsx b/app/components/Hero.tsx index cee1885..d0db5bc 100644 --- a/app/components/Hero.tsx +++ b/app/components/Hero.tsx @@ -1,48 +1,52 @@ // app/components/Hero.tsx -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import Image from "next/image"; export default function Hero() { - const [isVisible, setIsVisible] = useState(false); + const [isVisible, setIsVisible] = useState(false); - useEffect(() => { - setTimeout(() => { - setIsVisible(true); - }, 150); // Delay to start the animation - }, []); + useEffect(() => { + setTimeout(() => { + setIsVisible(true); + }, 150); // Delay to start the animation + }, []); - return ( -
-
-

- Hi, I’m Dennis -

-

- Student & Software Engineer -

-

- Based in Osnabrück, Germany -

-

- Passionate about technology, coding, and solving real-world problems. - I enjoy building innovative solutions and continuously expanding my knowledge. -

-

- Currently working on exciting projects that merge creativity with functionality. - Always eager to learn and collaborate! -

-
-
- Image of Dennis -
-
- ); -} \ No newline at end of file + return ( +
+
+

+ Hi, I’m Dennis +

+

+ Student & Software Engineer +

+

+ Based in Osnabrück, Germany +

+

+ Passionate about technology, coding, and solving real-world problems. + I enjoy building innovative solutions and continuously expanding my + knowledge. +

+

+ Currently working on exciting projects that merge creativity with + functionality. Always eager to learn and collaborate! +

+
+
+ Image of Dennis +
+
+ ); +} diff --git a/app/components/Projects.tsx b/app/components/Projects.tsx index 2233f12..4c11c97 100644 --- a/app/components/Projects.tsx +++ b/app/components/Projects.tsx @@ -1,70 +1,81 @@ -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import Link from "next/link"; interface Project { - id: string; - title: string; - description: string; - slug: string; + slug: string; + id: string; + title: string; + feature_image: string; + visibility: string; + published_at: string; + updated_at: string; + html: string; + reading_time: number; + meta_description: string; } export default function Projects() { - const [projects, setProjects] = useState([]); - const [isVisible, setIsVisible] = useState(false); + const [projects, setProjects] = useState([]); + const [isVisible, setIsVisible] = useState(false); - useEffect(() => { - const fetchProjects = async () => { - try { - const response = await fetch('/api/projects'); - try { - if (!response.ok) { - throw new Error("Failed to fetch projects"); - } - } catch (error) { - console.error("Failed to fetch projects:", error); - } - const projectsData = await response.json(); - setProjects(projectsData); - setTimeout(() => { - setIsVisible(true); - }, 250); // Delay to start the animation after Hero - } catch (error) { - console.error("Failed to fetch projects:", error); - } - }; - fetchProjects().then(r => r); - }, []); - const numberOfProjects = projects.length; + useEffect(() => { + const fetchProjects = async () => { + try { + const response = await fetch("/api/fetchAllProjects"); + if (!response.ok) { + throw new Error("Failed to fetch projects from Ghost"); + } + const projectsData = await response.json(); + setProjects(projectsData.posts); - return ( -
-

- Projects -

-
- {projects.map((project, index) => ( - -
-

- {project.title} -

-

- {project.description} -

-
- - ))} -
-

- More to come -

-

- ... -

-
+ setTimeout(() => { + setIsVisible(true); + }, 250); // Delay to start the animation after Hero + } catch (error) { + console.error("Failed to fetch projects:", error); + } + }; + fetchProjects(); + }, []); + + console.log(projects.at(0)?.feature_image); + + const numberOfProjects = projects.length; + return ( +
+

Projects

+
+ {projects.map((project, index) => ( + +
+

+ {project.title} +

+

{project.meta_description}

-
- ); -} \ No newline at end of file + + ))} +
+

More to come

+

...

+
+
+
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx index 1c3367f..156f64d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,29 +4,32 @@ import "./globals.css"; -import {Roboto} from 'next/font/google'; +import { Roboto } from "next/font/google"; import React from "react"; //import ClientCookieConsentBanner from "./components/ClientCookieConsentBanner"; - const roboto = Roboto({ - variable: '--font-roboto', - weight: '400', - subsets: ['latin'], + variable: "--font-roboto", + weight: "400", + subsets: ["latin"], }); export default function RootLayout({ - children, - }: { - children: React.ReactNode; + children, +}: { + children: React.ReactNode; }) { - - return ( - - - - {children} - - - ); -} \ No newline at end of file + return ( + + + + + + {children} + + ); +} diff --git a/app/legal-notice/page.tsx b/app/legal-notice/page.tsx index b275287..4ef79c0 100644 --- a/app/legal-notice/page.tsx +++ b/app/legal-notice/page.tsx @@ -27,11 +27,6 @@ export default function LegalNotice() { info@dki.one {" "}
- Telefon:{" "} - - +49 176 12669990 - -
Website:{" "} {" "} @@ -44,7 +39,10 @@ export default function LegalNotice() { 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. + 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.

Urheberrecht

@@ -60,7 +58,7 @@ export default function LegalNotice() { 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: 11.02.2025

+

Letzte Aktualisierung: 12.02.2025

diff --git a/app/page.tsx b/app/page.tsx index 0d44925..021a606 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,39 +9,38 @@ import Footer from "./components/Footer"; import Script from "next/script"; export default function Home() { - return ( - -
-