Removes unnecessary TypeScript error suppression for window object usage in the ProjectDetails component. This change improves code clarity and maintains functionality by ensuring the project data is removed from the URL without reloading the page.
131 lines
3.8 KiB
TypeScript
131 lines
3.8 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
|
|
if (typeof window !== "undefined") {
|
|
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()) 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;
|