mon-site-perso/frontend/app/projects/page.tsx

99 lines
No EOL
4.3 KiB
TypeScript

interface Project {
id: string;
created_at: number;
// ... autres propriétés
}
async function getProjects() {
const baseUrl = process.env.BACKEND_URL;
if (!baseUrl) {
throw new Error("Variable d'environnement BACKEND_URL manquante");
}
const res = await fetch(`${baseUrl}/projects/`, {
cache: "no-store",
});
if(!res.ok){
throw new Error("Erreur lors de la récupération des projets depuis l'API");
}
return res.json();
}
function toTags(technologies: unknown): string[] {
if (Array.isArray(technologies)) return technologies.filter(Boolean).map(String);
if (typeof technologies === 'string') {
// Split on common separators and the word 'et' (French 'and')
return technologies
.split(/,|;|\||\//g)
.flatMap(part => part.split(/\bet\b/i))
.flatMap(part => part.split(/\s{2,}/))
.map(s => s.trim())
.filter(s => s.length > 0);
}
return [];
}
function normalizeImageUrl(img: unknown): string | null {
if (typeof img !== 'string' || img.trim() === '') return null;
const val = img.trim();
// If already absolute (http/https or data URL), return as is
if (/^(https?:)?\/\//i.test(val) || /^data:/i.test(val)) return val;
// Otherwise, try to use as-is; backend might serve it relatively
return val;
}
export default async function ProjectsPage() {
const projects = await getProjects();
return(
<section id="projets" className="section">
<div className="container">
<h2 className="section-title">Mes projets</h2>
<ul className="projects-list">
{projects
.sort((a: Project, b: Project) => b.created_at - a.created_at)
.map((p: any) => {
const tags = toTags(p.technologies);
const href = p.link || p.url || '#';
const imgUrl = normalizeImageUrl(p.image);
return (
<li key={p.id} className="project-card">
<a href={href} target="_blank" rel="noopener noreferrer" className="project-card__link">
<div className="project-card__header" aria-hidden="true">
{p.image ? (
<div className="project-card__image" style={{ backgroundImage: `url(${p.image || ''})` }} />
) : (
<div className="project-card__image project-card__image--placeholder">
<span>{(p.name || '').slice(0, 1).toUpperCase()}</span>
</div>
)}
</div>
<div className="project-card__body">
<h3>{p.name}</h3>
<p>{p.description}</p>
</div>
<div className="project-card__footer">
<div className="tech-badges">
{tags.length > 0 ? (
tags.map((t: string, idx: number) => (
<span key={idx} className="badge badge--mono">{t}</span>
))
) : (
p.technologies ? <span className="badge badge--mono">{String(p.technologies)}</span> : null
)}
</div>
<div>
Crée le {new Date(p.created_at * 1000).toLocaleDateString()}
</div>
</div>
</a>
</li>
);
})}
</ul>
</div>
</section>
);
}