feat: update dependencies and enhance privacy policy
Replace "@vercel/analytics" with "@tryghost/content-api" and add "node-fetch" to dependencies. Remove "@vercel/speed-insights" to streamline the package. Update robots.txt to dis access to "/legal-notice" and "/privacy-policy". Change <p> tags to <div> in the Privacy Policy for better structure. Update the last modified date in the Legal Notice. Add a new API route for fetching images with error handling for missing URL parameters.
This commit is contained in:
@@ -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<Project | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const ProjectDetails = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const params = useParams();
|
||||
const pathname = usePathname();
|
||||
const [project, setProject] = useState<Project | null>(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 <div className="p-10 text-center">Loading...</div>;
|
||||
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 (
|
||||
<div className={`min-h-screen flex flex-col bg-radiant ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<Header/>
|
||||
<div className="flex-grow p-10 pt-24">
|
||||
<div
|
||||
className="flex flex-col p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl">
|
||||
<div className="mt-4 text-gray-600 dark:text-gray-300 markdown">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>
|
||||
{project.text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer_Back/>
|
||||
<div className="min-h-screen flex flex-col bg-radiant">
|
||||
<Header />
|
||||
<div className="flex-grow flex items-center justify-center">
|
||||
<div className="loader ease-linear rounded-full border-8 border-t-8 border-gray-200 h-32 w-32"></div>
|
||||
</div>
|
||||
<Footer_Back />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const featureImageUrl = `/api/fetchImage?url=${encodeURIComponent(project.feature_image)}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`min-h-screen flex flex-col bg-radiant ${isVisible ? "animate-fly-in" : "opacity-0"}`}
|
||||
>
|
||||
<Header />
|
||||
<div className="flex-grow">
|
||||
{/* Hero Section */}
|
||||
<div className="flex justify-center md:mt-28 px-4 md:px-0">
|
||||
<div className="relative w-full max-w-4xl h-0 pb-[56.25%] rounded-2xl overflow-hidden">
|
||||
{" "}
|
||||
{/* 16:9 Aspect Ratio */}
|
||||
<Image
|
||||
src={featureImageUrl}
|
||||
alt={project.title}
|
||||
fill
|
||||
style={{ objectFit: "cover" }}
|
||||
className="rounded-2xl"
|
||||
priority={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-center mt-4">
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-white">
|
||||
{project.title}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Project Content */}
|
||||
<div className="p-10 pt-12">
|
||||
<div className="flex flex-col p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl">
|
||||
<div
|
||||
className="content mt-4 text-gray-600 text-lg leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: project.html }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer_Back />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectDetails;
|
||||
|
||||
25
app/api/fetchAllProjects/route.tsx
Normal file
25
app/api/fetchAllProjects/route.tsx
Normal file
@@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
35
app/api/fetchImage/route.tsx
Normal file
35
app/api/fetchImage/route.tsx
Normal file
@@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
32
app/api/fetchProject/route.tsx
Normal file
32
app/api/fetchProject/route.tsx
Normal file
@@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<div id="about"
|
||||
className={`flex flex-col md:flex-row items-center justify-center pt-16 pb-16 px-6 text-gray-700 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<div
|
||||
className="flex flex-col items-center p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl max-w-lg text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-900">
|
||||
Hi, I’m Dennis
|
||||
</h1>
|
||||
<h2 className="mt-2 text-xl md:text-2xl font-semibold text-gray-700">
|
||||
Student & Software Engineer
|
||||
</h2>
|
||||
<h3 className="mt-1 text-lg md:text-xl text-gray-600">
|
||||
Based in Osnabrück, Germany
|
||||
</h3>
|
||||
<p className="mt-6 text-gray-800 text-lg leading-relaxed">
|
||||
Passionate about technology, coding, and solving real-world problems.
|
||||
I enjoy building innovative solutions and continuously expanding my knowledge.
|
||||
</p>
|
||||
<p className="mt-4 text-gray-700 text-base">
|
||||
Currently working on exciting projects that merge creativity with functionality.
|
||||
Always eager to learn and collaborate!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex mt-8 md:mt-0 md:ml-12">
|
||||
<Image
|
||||
src="/images/me.jpg"
|
||||
alt="Image of Dennis"
|
||||
width={400}
|
||||
height={400}
|
||||
className="rounded-2xl shadow-lg shadow-gray-700 object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
id="about"
|
||||
className={`flex flex-col md:flex-row items-center justify-center pt-16 pb-16 px-6 text-gray-700 ${isVisible ? "animate-fly-in" : "opacity-0"}`}
|
||||
>
|
||||
<div className="flex flex-col items-center p-8 bg-gradient-to-br from-white/60 to-white/30 backdrop-blur-lg rounded-2xl shadow-xl max-w-lg text-center">
|
||||
<h1 className="text-4xl md:text-5xl font-extrabold text-gray-900">
|
||||
Hi, I’m Dennis
|
||||
</h1>
|
||||
<h2 className="mt-2 text-xl md:text-2xl font-semibold text-gray-700">
|
||||
Student & Software Engineer
|
||||
</h2>
|
||||
<h3 className="mt-1 text-lg md:text-xl text-gray-600">
|
||||
Based in Osnabrück, Germany
|
||||
</h3>
|
||||
<p className="mt-6 text-gray-800 text-lg leading-relaxed">
|
||||
Passionate about technology, coding, and solving real-world problems.
|
||||
I enjoy building innovative solutions and continuously expanding my
|
||||
knowledge.
|
||||
</p>
|
||||
<p className="mt-4 text-gray-700 text-base">
|
||||
Currently working on exciting projects that merge creativity with
|
||||
functionality. Always eager to learn and collaborate!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex mt-8 md:mt-0 md:ml-12">
|
||||
<Image
|
||||
src="/images/me.jpg"
|
||||
alt="Image of Dennis"
|
||||
width={400}
|
||||
height={400}
|
||||
className="rounded-2xl shadow-lg shadow-gray-700 object-cover"
|
||||
priority={true}
|
||||
style={{ width: "auto", height: "400px" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<Project[]>([]);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
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 (
|
||||
<section id="projects" className={`p-10 ${isVisible ? 'animate-fly-in' : 'opacity-0'}`}>
|
||||
<h2 className="text-3xl font-bold text-center text-gray-800">
|
||||
Projects
|
||||
</h2>
|
||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{projects.map((project, index) => (
|
||||
<Link key={project.id} href={`/Projects/${project.slug}`} className="cursor-pointer">
|
||||
<div className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`}
|
||||
style={{animationDelay: `${index * 0.1}s`}}>
|
||||
<h3 className="text-2xl font-bold text-gray-800">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-gray-500">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<div className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`}
|
||||
style={{animationDelay: `${(numberOfProjects + 1) * 0.1}s`}}>
|
||||
<h3 className="text-2xl font-bold text-gray-800">
|
||||
More to come
|
||||
</h3>
|
||||
<p className="mt-2 text-gray-500">
|
||||
...
|
||||
</p>
|
||||
</div>
|
||||
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 (
|
||||
<section
|
||||
id="projects"
|
||||
className={`p-10 ${isVisible ? "animate-fly-in" : "opacity-0"}`}
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-center text-gray-800">Projects</h2>
|
||||
<div className="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{projects.map((project, index) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
href={{
|
||||
pathname: `/Projects/${project.slug}`,
|
||||
query: { project: JSON.stringify(project) },
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`}
|
||||
style={{ animationDelay: `${index * 0.1}s` }}
|
||||
>
|
||||
<h3 className="text-2xl font-bold text-gray-800">
|
||||
{project.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-gray-500">{project.meta_description}</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
</Link>
|
||||
))}
|
||||
<div
|
||||
className={`p-4 border shadow-lg bg-white/45 rounded-2xl animate-fly-in`}
|
||||
style={{ animationDelay: `${(numberOfProjects + 1) * 0.1}s` }}
|
||||
>
|
||||
<h3 className="text-2xl font-bold text-gray-800">More to come</h3>
|
||||
<p className="mt-2 text-gray-500">...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<html lang="en">
|
||||
<script defer src="https://umami.denshooter.de/script.js" data-website-id="1f213877-deef-4238-8df1-71a5a3bcd142"></script>
|
||||
<body className={roboto.variable}>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script
|
||||
defer
|
||||
src="https://umami.denshooter.de/script.js"
|
||||
data-website-id="1f213877-deef-4238-8df1-71a5a3bcd142"
|
||||
></script>
|
||||
<meta charSet="utf-8" />
|
||||
</head>
|
||||
<body className={roboto.variable}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ export default function LegalNotice() {
|
||||
info@dki.one
|
||||
</Link>{" "}
|
||||
<br />
|
||||
<strong>Telefon:</strong>{" "}
|
||||
<Link href={"tel:+4917612669990"} className="transition-underline">
|
||||
+49 176 12669990
|
||||
</Link>
|
||||
<br />
|
||||
<strong>Website:</strong>{" "}
|
||||
<Link href={"https://www.dki.one"} className="transition-underline">
|
||||
{" "}
|
||||
@@ -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.
|
||||
</p>
|
||||
|
||||
<h2 className="text-2xl font-semibold mt-6">Urheberrecht</h2>
|
||||
@@ -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.
|
||||
</p>
|
||||
<p className="font-semibold mt-6">Letzte Aktualisierung: 11.02.2025</p>
|
||||
<p className="font-semibold mt-6">Letzte Aktualisierung: 12.02.2025</p>
|
||||
</main>
|
||||
<Footer_Back />
|
||||
</div>
|
||||
|
||||
71
app/page.tsx
71
app/page.tsx
@@ -9,39 +9,38 @@ import Footer from "./components/Footer";
|
||||
import Script from "next/script";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
<div className="min-h-screen flex flex-col bg-radiant-animated">
|
||||
<Script
|
||||
id={"structured-data"}
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"name": "Dennis Konkol",
|
||||
"url": "https://dki.one",
|
||||
"jobTitle": "Software Engineer",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": "Osnabrück",
|
||||
"addressCountry": "Germany",
|
||||
},
|
||||
"sameAs": [
|
||||
"https://github.com/Denshooter",
|
||||
"https://linkedin.com/in/dkonkol",
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Header/>
|
||||
<div className="h-10"></div>
|
||||
<main>
|
||||
<Hero/>
|
||||
<Projects/>
|
||||
<Contact/>
|
||||
<Footer/>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col bg-radiant-animated">
|
||||
<Script
|
||||
id={"structured-data"}
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
name: "Dennis Konkol",
|
||||
url: "https://dki.one",
|
||||
jobTitle: "Software Engineer",
|
||||
address: {
|
||||
"@type": "PostalAddress",
|
||||
addressLocality: "Osnabrück",
|
||||
addressCountry: "Germany",
|
||||
},
|
||||
sameAs: [
|
||||
"https://github.com/Denshooter",
|
||||
"https://linkedin.com/in/dkonkol",
|
||||
],
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Header />
|
||||
<div className="h-10"></div>
|
||||
<main>
|
||||
<Hero />
|
||||
<Projects />
|
||||
<Contact />
|
||||
<Footer />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,11 +39,6 @@ export default function PrivacyPolicy() {
|
||||
info@dki.one
|
||||
</Link>{" "}
|
||||
<br />
|
||||
<strong>Telefon:</strong>{" "}
|
||||
<Link className="transition-underline" href={"tel:+4917612669990"}>
|
||||
+49 176 12669990
|
||||
</Link>
|
||||
<br />
|
||||
<strong>Website:</strong>{" "}
|
||||
<Link className="transition-underline" href={"https://www.dki.one"}>
|
||||
{" "}
|
||||
@@ -57,7 +52,7 @@ export default function PrivacyPolicy() {
|
||||
<h2 className="text-2xl font-semibold mt-6">
|
||||
Erfassung allgemeiner Informationen beim Besuch meiner Website
|
||||
</h2>
|
||||
<p className="mt-2">
|
||||
<div className="mt-2">
|
||||
Beim Zugriff auf meiner Website werden automatisch Informationen
|
||||
allgemeiner Natur erfasst. Diese beinhalten unter anderem:
|
||||
<ul className="list-disc list-inside mt-2">
|
||||
@@ -76,7 +71,7 @@ export default function PrivacyPolicy() {
|
||||
<li>die Inhalte meiner Website zu optimieren,</li>
|
||||
<li>die Systemsicherheit und -stabilität zu analysiern.</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold mt-6">Cookies</h2>
|
||||
<p className="mt-2">
|
||||
Meine Website verwendet keine Cookies. Daher ist kein
|
||||
@@ -129,7 +124,7 @@ export default function PrivacyPolicy() {
|
||||
jeweiligen Anbieter.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Weitergabe von Daten</h2>
|
||||
<p className="mt-2">
|
||||
<div className="mt-2">
|
||||
Eine Weitergabe Ihrer personenbezogenen Daten erfolgt nur, wenn:
|
||||
<ul className="list-disc list-inside mt-2">
|
||||
<li>
|
||||
@@ -149,7 +144,7 @@ export default function PrivacyPolicy() {
|
||||
berechtigter Interessen erforderlich ist.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold mt-6">
|
||||
Speicherdauer und Löschung
|
||||
</h2>
|
||||
@@ -159,7 +154,7 @@ export default function PrivacyPolicy() {
|
||||
werden Ihre Daten gelöscht.
|
||||
</p>
|
||||
<h2 className="text-2xl font-semibold mt-6">Ihre Rechte</h2>
|
||||
<p className="mt-2">
|
||||
<div className="mt-2">
|
||||
Sie haben gemäß DSGVO folgende Rechte:
|
||||
<ul className="list-disc list-inside mt-2">
|
||||
<li>
|
||||
@@ -198,7 +193,7 @@ export default function PrivacyPolicy() {
|
||||
>
|
||||
https://www.bfdi.bund.de/
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="text-2xl font-semibold mt-6">Datensicherheit</h2>
|
||||
<p className="mt-2">
|
||||
Ich setze technische und organisatorische Maßnahmen ein, um Ihre Daten
|
||||
@@ -226,7 +221,7 @@ export default function PrivacyPolicy() {
|
||||
berücksichtigen. Die jeweils aktuelle Datenschutzerklärung finden Sie
|
||||
auf meiner Website.
|
||||
</p>
|
||||
<p className="mt-6 font-bold">Letzte Aktualisierung: 11.02.2025</p>
|
||||
<p className="mt-6 font-bold">Letzte Aktualisierung: 12.02.2025</p>
|
||||
</main>
|
||||
<Footer_Back />
|
||||
</div>
|
||||
|
||||
70
app/styles/ghostContent.css
Normal file
70
app/styles/ghostContent.css
Normal file
@@ -0,0 +1,70 @@
|
||||
/* ghostContent.css */
|
||||
|
||||
.content {
|
||||
font-family: "Arial", sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content h1,
|
||||
.content h2,
|
||||
.content h3,
|
||||
.content h4,
|
||||
.content h5,
|
||||
.content h6 {
|
||||
color: #222;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.content p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.content ul,
|
||||
.content ol {
|
||||
margin: 1em 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.content a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 1em;
|
||||
color: #666;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.content pre {
|
||||
background: #f5f5f5;
|
||||
padding: 1em;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.loader {
|
||||
border-top-color: #3498db;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user