project pages created and populated automatically

This commit is contained in:
2026-04-16 10:15:14 +02:00
parent 82e6e3dc01
commit 5e1368f790
17 changed files with 2320 additions and 101 deletions
+31
View File
@@ -0,0 +1,31 @@
type PreviewItem = {
title: string;
image: string;
};
type props = {
items?: PreviewItem[];
};
export function ProjectPreviewGrid({ items = [] }: props) {
return (
<div className="grid gap-6 md:grid-cols-2">
{items.map((item) => (
<div
key={item.title}
className="rounded-2xl overflow-hidden bg-[#2b160f] text-white min-h-[260px] flex items-center justify-center"
>
{item.image ? (
<img
src={item.image}
alt={item.title}
className="h-full w-full object-cover"
/>
) : (
<span className="text-2xl">{item.title}</span>
)}
</div>
))}
</div>
);
}
+18
View File
@@ -0,0 +1,18 @@
type VisitButtonProps = {
href: string;
};
export function ProjectVisitButton({ href }: VisitButtonProps) {
return (
<div className="flex justify-center">
<a
href={href}
target="_blank"
rel="noreferrer"
className="rounded-full bg-[#2b160f] px-4 py-2 text-white text-sm"
>
Visiter le site
</a>
</div>
);
}
+11
View File
@@ -0,0 +1,11 @@
type props = {
children: React.ReactNode;
};
export function ProjectsColumns({ children }: props) {
return (
<div className="grid gap-6 md:grid-cols-2">
{children}
</div>
);
}
+9
View File
@@ -0,0 +1,9 @@
import { ProjectsColumns } from "@/components/mdx/ProjectsColumns";
import { ProjectPreviewGrid } from "@/components/mdx/ProjectPreviewGrid";
import { ProjectVisitButton } from "@/components/mdx/ProjectVisitButton";
export const mdxComponents = {
ProjectsColumns: ProjectsColumns,
ProjectPreviewGrid,
ProjectVisitButton,
};
+40
View File
@@ -0,0 +1,40 @@
import Link from "next/link";
type Props = {
locale: string;
projects: {
slug: string;
title: string;
subtitle: string;
}[];
};
export function OtherProjects({ locale, projects }: Props) {
if (!projects.length) return null;
return (
<section className="pb-20 pt-8">
<h2 className="mb-10 text-center text-5xl font-semibold">
Autres projets
</h2>
<div className="grid gap-6 md:grid-cols-2">
{projects.map((project) => (
<Link
key={project.slug}
href={`/${locale}/works/${project.slug}`}
className="group block"
>
<div className="mb-3 text-2xl font-medium">{project.title}</div>
<div className="flex min-h-[220px] items-center justify-center rounded-[22px] bg-white text-4xl shadow-sm transition group-hover:-translate-y-1">
{project.title}
</div>
<p className="mt-3 text-sm text-neutral-500">{project.subtitle}</p>
</Link>
))}
</div>
</section>
);
}
+32
View File
@@ -0,0 +1,32 @@
import Link from "next/link";
type Props = {
title: string;
subtitle: string;
year: number;
locale: string;
};
export function ProjectHero({ title, subtitle, year, locale }: Props) {
return (
<section className="rounded-[28px] bg-[#2b160f] px-6 py-16 text-white md:px-12 md:py-28">
<Link
href={`/${locale}/works`}
className="mb-10 inline-flex text-sm text-white/80 hover:text-white"
>
Retour aux projets
</Link>
<div className="mx-auto flex min-h-[360px] max-w-4xl flex-col items-center justify-center text-center">
<h1 className="text-5xl font-semibold md:text-7xl">{title}</h1>
<p className="mt-4 text-lg text-white/80 md:text-2xl">{subtitle}</p>
<div className="mt-8 flex items-center gap-4 text-sm text-white/70">
<span className="h-px w-16 bg-white/30" />
<span>{year}</span>
<span className="h-px w-16 bg-white/30" />
</div>
</div>
</section>
);
}
+38
View File
@@ -0,0 +1,38 @@
type Props = {
year: number;
category: string;
roles: string[];
client: string;
};
function Item({
label,
children,
}: {
label: string;
children: React.ReactNode;
}) {
return (
<div className="space-y-2">
<p className="text-sm text-neutral-500">{label}</p>
<div className="text-base text-neutral-900">{children}</div>
</div>
);
}
export function ProjectMeta({ year, category, roles, client }: Props) {
return (
<section className="grid gap-8 py-10 md:grid-cols-4">
<Item label="Année">{year}</Item>
<Item label="Catégorie">{category}</Item>
<Item label="Rôles">
<div className="flex flex-col gap-1">
{roles.map((role) => (
<span key={role}>{role}</span>
))}
</div>
</Item>
<Item label="Client">{client}</Item>
</section>
);
}
@@ -0,0 +1,22 @@
type Props = {
technologies: string[];
};
export function ProjectTechnologies({ technologies }: Props) {
return (
<section className="pb-16">
<p className="mb-4 text-sm text-neutral-500">Technologies</p>
<div className="flex flex-wrap gap-3">
{technologies.map((tech) => (
<span
key={tech}
className="rounded-full bg-[#2b160f] px-4 py-2 text-sm text-white"
>
{tech}
</span>
))}
</div>
</section>
);
}
+16 -39
View File
@@ -1,47 +1,24 @@
import { Project } from "@/lib/projects/types";
import { ProjectCard } from "../ui/ProjectCard";
export default function WorksSection() {
type Props = {
projects: Project[];
locale: string;
};
export default function WorksSection({ projects, locale }: Props) {
return (
<section id="works" className="w-full flex flex-col items-start justify-start">
<h2 className="text-[clamp(80px,10vw,128px)] font-serif text-brand-brown mt-6 mb-4">Travaux</h2>
<ProjectCard
projectVideoPath={""}
projectName={"EVEREAST SOLUTIONS"}
projectType={"Application web"}
projectDate={"2026 - auj."}
roles={["Co-fondateur", "développeur full-stack"]}
projectDescription={"Plateforme de mise en relations clients - agences web et gestion de projet agile"}
projectPageLink={"/"}
/>
<ProjectCard
projectVideoPath={""}
projectName={"THREADBASE"}
projectType={"Application macOS"}
projectDate={"2026 - auj."}
roles={["Développeur full-stack"]}
projectDescription={"Application macOS qui sauvegarde les fenêtres, les notes mentales et le contexte Git dun projet pour en faciliter la réouverture."}
projectPageLink={""}
/>
<ProjectCard
projectVideoPath={""}
projectName={"GGCG"}
projectType={"Application web"}
projectDate={"2026 - auj."}
roles={["Développeur full-stack"]}
projectDescription={"Une vue unifiée et élégante de toute votre activité de développement, centralisant les contributions de toutes vos forges Git dans une seule heatmap interactive."}
projectPageLink={""}
/>
<ProjectCard
projectVideoPath={""}
projectName={"MG DEV"}
projectType={"Site web"}
projectDate={"2026"}
roles={["Développeur full-stack"]}
projectDescription={"Un site web pour présenter mon profil, mes compétences et mes projets de développement."}
projectPageLink={""}
isTheLast={true}
/>
{projects.map((project, index) => (
<ProjectCard
key={project.slug}
project={project}
locale={locale}
isTheLast={index === projects.length - 1}
/>
))}
</section>
);
}
}
+49 -49
View File
@@ -1,63 +1,63 @@
import Link from "next/link";
import { GoArrowRight } from "react-icons/go";
import { Project } from "@/lib/projects/types";
type props = {
projectVideoPath : String;
projectName : String;
projectType : String;
projectDate : String;
roles : String[];
projectDescription : String;
projectPageLink : String;
isTheLast? : boolean;
type Props = {
project: Project;
locale: string;
isTheLast?: boolean;
};
export const ProjectCard = ({
projectVideoPath,
projectName,
projectType,
projectDate,
roles,
projectDescription,
projectPageLink,
isTheLast = false
}: props) => {
export const ProjectCard = ({
project,
locale,
isTheLast = false,
}: Props) => {
return (
<div className="w-full pb-12">
{/* Top separator */}
<div className="w-full h-px bg-brand-brown/20" />
{/* *** Content *** */}
<div className="flex flex-col gap-4 lg:flex-row lg:justify-between lg:items-center lg:gap-14 relative">
<div className="text-5xl font-serif text-brand-brown/40 mt-6 font-semibold lg:absolute lg:top-1 lg:right-1 ">{projectDate}</div>
{/* Left image/video */}
<div className="w-full lg:w-3/5 h-92.5 bg-gray-600"></div>
{/* Texts */}
<div className="flex flex-col items-start justify-center w-full gap-7.5">
<div className="flex flex-col items-start justify-center w-full">
<div className="p-1.5 bg-brand-brown rounded-px text-brand-beige font-sans">{projectType}</div>
<div className="text-5xl text-brand-brown font-serif uppercase">{projectName}</div>
<div className="text-2xl font-serif font-light">
<p>(
{roles.map((role, index) => (
role + (index < roles.length - 1 ? ", " : "")
))}
)</p>
<Link href={`/${locale}/works/${project.slug}`} className="group block">
<div className="flex flex-col gap-4 lg:flex-row lg:justify-between lg:items-center lg:gap-14 relative">
<div className="text-5xl font-serif text-brand-brown/40 mt-6 font-semibold lg:absolute lg:top-1 lg:right-1">
{project.dateLabel ?? project.year}
</div>
<div className="w-full lg:w-3/5 h-92.5 bg-gray-600 overflow-hidden">
{project.preview1 ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={project.preview1}
alt={project.title}
className="w-full h-full object-cover transition duration-500 group-hover:scale-[1.02]"
/>
) : null}
</div>
<div className="flex flex-col items-start justify-center w-full gap-7.5">
<div className="flex flex-col items-start justify-center w-full">
<div className="p-1.5 bg-brand-brown rounded-px text-brand-beige font-sans">{project.category}</div>
<div className="text-5xl text-brand-brown font-serif uppercase">{project.title}</div>
<div className="text-2xl font-serif font-light">
<p>
(
{project.roles.map((role, index) => role + (index < project.roles.length - 1 ? ", " : ""))}
)
</p>
</div>
</div>
<div className="text-base font-normal font-sans">{project.description ?? project.subtitle}</div>
<div className="border-b text-base flex flex-row justify-center items-center gap-0.5 transition group-hover:gap-1.5">
<p>READ</p>
<GoArrowRight />
</div>
</div>
<div className="text-base font-normal font-sans">{projectDescription}</div>
<div className="border-b text-base flex flex-row justify-center items-center gap-0.5">
<p>READ</p>
<GoArrowRight />
</div>
</div>
</div>
{/* Bottom separator */}
</Link>
{isTheLast && <div className="w-full h-px mt-12 bg-brand-brown/20" />}
</div>
);
};
};