Initial commit
This commit is contained in:
commit
78f8d225ee
21173 changed files with 2907774 additions and 0 deletions
153
app/projects/[id]/page.tsx
Normal file
153
app/projects/[id]/page.tsx
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
export default function ProjectDetailPage() {
|
||||
const { id } = useParams();
|
||||
const [project, setProject] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
const url = new URL(`${process.env.NEXT_PUBLIC_API_BASE_URL}/items/projects/${id}`);
|
||||
url.searchParams.set(
|
||||
"fields",
|
||||
"submission_id,title,uploader,category,tags,p_image.filename_disk,p_image.title,p_files.directus_files_id.filename_disk"
|
||||
);
|
||||
url.searchParams.set("limit", "1");
|
||||
|
||||
fetch(url.toString())
|
||||
.then((res) => res.json())
|
||||
.then((data) => setProject(data.data))
|
||||
.catch(() => setProject(null));
|
||||
}, [id]);
|
||||
|
||||
if (!project) {
|
||||
return (
|
||||
<div className="p-6 max-w-5xl mx-auto">
|
||||
<div className="mb-4">
|
||||
<Link href="/projects" className="text-accent underline">
|
||||
← Back to Projects
|
||||
</Link>
|
||||
</div>
|
||||
<p className="text-muted-foreground">Loading project…</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const imageSrc = project.p_image?.filename_disk
|
||||
? `${process.env.NEXT_PUBLIC_ASSET_URL || "https://forms.lasereverything.net"}/assets/${project.p_image.filename_disk}`
|
||||
: null;
|
||||
|
||||
const fileList: string[] = Array.isArray(project.p_files)
|
||||
? project.p_files
|
||||
.map((f: any) => f?.directus_files_id?.filename_disk)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-5xl mx-auto">
|
||||
<style jsx global>{`
|
||||
.file-pill {
|
||||
display: inline-block;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: var(--muted);
|
||||
color: var(--foreground);
|
||||
border-radius: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
.file-pill:hover {
|
||||
background-color: #ffde59;
|
||||
color: #000;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div className="mb-4">
|
||||
<Link href="/projects" className="text-accent underline">
|
||||
← Back to Projects
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="md:col-span-1">
|
||||
<div className="border rounded overflow-hidden bg-card">
|
||||
{imageSrc ? (
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={project.p_image?.title || "Project image"}
|
||||
width={800}
|
||||
height={800}
|
||||
className="w-full h-auto object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="p-6 text-sm text-muted-foreground">No preview image</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<h3 className="text-sm font-semibold mb-2">Files</h3>
|
||||
{fileList.length > 0 ? (
|
||||
<div className="flex flex-wrap">
|
||||
{fileList.map((fname, i) => (
|
||||
<a
|
||||
key={i}
|
||||
className="file-pill"
|
||||
href={`${process.env.NEXT_PUBLIC_ASSET_URL || "https://forms.lasereverything.net"}/assets/${fname}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={fname}
|
||||
>
|
||||
{fname}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">No files attached.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<h1 className="text-2xl font-bold mb-2">{project.title}</h1>
|
||||
<p className="text-sm text-muted-foreground mb-1">
|
||||
Uploaded by: {project.uploader || "—"}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
Category: {project.category || "—"}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{Array.isArray(project.tags) && project.tags.length > 0 ? (
|
||||
project.tags.map((tag, i) => (
|
||||
<a
|
||||
key={i}
|
||||
href={`/projects?query=${encodeURIComponent(tag)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs bg-muted text-foreground rounded px-2 py-0.5 hover:bg-accent hover:text-background transition-colors"
|
||||
title={`Search for ${tag}`}
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
))
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">No tags</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Optional long description if you add one later */}
|
||||
{project.body ? (
|
||||
<div className="prose dark:prose-invert mt-6">
|
||||
<ReactMarkdown>{project.body}</ReactMarkdown>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue