Refactor project data parsing to ensure type safety by casting the project data as a string. Enhance the sitemap generation by fetching data from a dynamic API route, allowing for more accurate and up-to-date sitemap entries. Remove unused project markdown files to clean up the project structure. These changes improve code reliability and maintainability.
134 lines
4.0 KiB
TypeScript
134 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
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 {
|
|
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;
|
|
}
|
|
|
|
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(() => {
|
|
const projectData = searchParams.get("project");
|
|
if (projectData) {
|
|
setProject(JSON.parse(projectData as string));
|
|
// Remove the project data from the URL without reloading the page
|
|
// @ts-expect-error window is defined
|
|
if (typeof window !== "undefined") {
|
|
// @ts-expect-error window is defined
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.delete("project");
|
|
// @ts-expect-error window is defined
|
|
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()) as { posts: Project[] };
|
|
setProject(projectData.posts[0]);
|
|
} catch (error) {
|
|
console.error("Failed to fetch project data:", error);
|
|
}
|
|
};
|
|
|
|
if (!project) {
|
|
return (
|
|
<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 = project.feature_image
|
|
? `/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">
|
|
<div className="flex justify-center mt-14 md:mt-28 px-4 md:px-0">
|
|
{featureImageUrl && (
|
|
<div className="relative w-full max-w-4xl h-0 pb-[56.25%] rounded-2xl overflow-hidden">
|
|
<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-gray-600">
|
|
{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;
|