project pages created and populated automatically
This commit is contained in:
+13
-2
@@ -3,11 +3,22 @@ import WorksSection from "@/components/sections/WorksSection";
|
|||||||
import FeatureSection from "@/components/sections/FeatureSection";
|
import FeatureSection from "@/components/sections/FeatureSection";
|
||||||
import TimelineSection from "@/components/sections/AboutSection";
|
import TimelineSection from "@/components/sections/AboutSection";
|
||||||
|
|
||||||
export default function Home() {
|
import { getAllProjects } from "@/lib/projects/get-projects";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Home({ params }: Props) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const projects = getAllProjects();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className=" bg-brand-beige text-brand-black flex flex-col items-center justify-center">
|
<main className=" bg-brand-beige text-brand-black flex flex-col items-center justify-center">
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
<WorksSection />
|
<WorksSection projects={projects} locale={locale} />
|
||||||
<FeatureSection />
|
<FeatureSection />
|
||||||
<TimelineSection />
|
<TimelineSection />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||||
|
import { getAllProjects, getProjectBySlug, getProjectSlugs } from "@/lib/projects/get-projects";
|
||||||
|
import { mdxComponents } from "@/components/mdx/mdx-components";
|
||||||
|
import { ProjectHero } from "@/components/sections/ProjectHero";
|
||||||
|
import { ProjectMeta } from "@/components/sections/ProjectMeta";
|
||||||
|
import { ProjectTechnologies } from "@/components/sections/ProjectTechnologies";
|
||||||
|
import { OtherProjects } from "@/components/sections/OtherProjects";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string;
|
||||||
|
slug: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return getProjectSlugs().flatMap((slug: any) => [
|
||||||
|
{ locale: "fr", slug },
|
||||||
|
{ locale: "en", slug },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ProjectPage({ params }: Props) {
|
||||||
|
const { locale, slug } = await params;
|
||||||
|
const project = getProjectBySlug(slug);
|
||||||
|
|
||||||
|
if (!project) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherProjects = getAllProjects()
|
||||||
|
.filter((item: any) => item.slug !== slug)
|
||||||
|
.slice(0, 2)
|
||||||
|
.map((item: any) => ({
|
||||||
|
slug: item.slug,
|
||||||
|
title: item.title,
|
||||||
|
subtitle: item.subtitle,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="bg-[#f5f3ef] text-[#2b160f]">
|
||||||
|
<div className="mx-auto max-w-7xl px-6 py-6">
|
||||||
|
<ProjectHero
|
||||||
|
locale={locale}
|
||||||
|
title={project.title}
|
||||||
|
subtitle={project.subtitle}
|
||||||
|
year={project.year}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProjectMeta
|
||||||
|
year={project.year}
|
||||||
|
category={project.category}
|
||||||
|
roles={project.roles}
|
||||||
|
client={project.client}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProjectTechnologies technologies={project.technologies} />
|
||||||
|
|
||||||
|
<section className="pb-16">
|
||||||
|
<article
|
||||||
|
className="
|
||||||
|
prose prose-neutral max-w-none
|
||||||
|
prose-h2:text-6xl prose-h2:font-semibold prose-h2:text-[#2b160f]
|
||||||
|
prose-p:text-base prose-p:leading-8 prose-p:text-neutral-800
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<MDXRemote source={project.content} components={mdxComponents} />
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<OtherProjects locale={locale} projects={otherProjects} />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
};
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,47 +1,24 @@
|
|||||||
|
import { Project } from "@/lib/projects/types";
|
||||||
import { ProjectCard } from "../ui/ProjectCard";
|
import { ProjectCard } from "../ui/ProjectCard";
|
||||||
|
|
||||||
export default function WorksSection() {
|
type Props = {
|
||||||
|
projects: Project[];
|
||||||
|
locale: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function WorksSection({ projects, locale }: Props) {
|
||||||
return (
|
return (
|
||||||
<section id="works" className="w-full flex flex-col items-start justify-start">
|
<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>
|
<h2 className="text-[clamp(80px,10vw,128px)] font-serif text-brand-brown mt-6 mb-4">Travaux</h2>
|
||||||
|
|
||||||
|
{projects.map((project, index) => (
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
projectVideoPath={""}
|
key={project.slug}
|
||||||
projectName={"EVEREAST SOLUTIONS"}
|
project={project}
|
||||||
projectType={"Application web"}
|
locale={locale}
|
||||||
projectDate={"2026 - auj."}
|
isTheLast={index === projects.length - 1}
|
||||||
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 d’un 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}
|
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,62 +1,62 @@
|
|||||||
|
import Link from "next/link";
|
||||||
import { GoArrowRight } from "react-icons/go";
|
import { GoArrowRight } from "react-icons/go";
|
||||||
|
import { Project } from "@/lib/projects/types";
|
||||||
|
|
||||||
type props = {
|
type Props = {
|
||||||
projectVideoPath : String;
|
project: Project;
|
||||||
projectName : String;
|
locale: string;
|
||||||
projectType : String;
|
isTheLast?: boolean;
|
||||||
projectDate : String;
|
|
||||||
roles : String[];
|
|
||||||
projectDescription : String;
|
|
||||||
projectPageLink : String;
|
|
||||||
isTheLast? : boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectCard = ({
|
export const ProjectCard = ({
|
||||||
projectVideoPath,
|
project,
|
||||||
projectName,
|
locale,
|
||||||
projectType,
|
isTheLast = false,
|
||||||
projectDate,
|
}: Props) => {
|
||||||
roles,
|
|
||||||
projectDescription,
|
|
||||||
projectPageLink,
|
|
||||||
isTheLast = false
|
|
||||||
}: props) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full pb-12">
|
<div className="w-full pb-12">
|
||||||
{/* Top separator */}
|
|
||||||
<div className="w-full h-px bg-brand-brown/20" />
|
<div className="w-full h-px bg-brand-brown/20" />
|
||||||
|
|
||||||
{/* *** Content *** */}
|
<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="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>
|
<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>
|
||||||
|
|
||||||
{/* Left image/video */}
|
<div className="w-full lg:w-3/5 h-92.5 bg-gray-600 overflow-hidden">
|
||||||
<div className="w-full lg:w-3/5 h-92.5 bg-gray-600"></div>
|
{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>
|
||||||
|
|
||||||
{/* 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 gap-7.5">
|
||||||
<div className="flex flex-col items-start justify-center w-full">
|
<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="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">{projectName}</div>
|
<div className="text-5xl text-brand-brown font-serif uppercase">{project.title}</div>
|
||||||
<div className="text-2xl font-serif font-light">
|
<div className="text-2xl font-serif font-light">
|
||||||
<p>(
|
<p>
|
||||||
{roles.map((role, index) => (
|
(
|
||||||
role + (index < roles.length - 1 ? ", " : "")
|
{project.roles.map((role, index) => role + (index < project.roles.length - 1 ? ", " : ""))}
|
||||||
))}
|
)
|
||||||
)</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-base font-normal font-sans">{projectDescription}</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">
|
<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>
|
<p>READ</p>
|
||||||
<GoArrowRight />
|
<GoArrowRight />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
{/* Bottom separator */}
|
|
||||||
{isTheLast && <div className="w-full h-px mt-12 bg-brand-brown/20" />}
|
{isTheLast && <div className="w-full h-px mt-12 bg-brand-brown/20" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
title: "EverEast Solutions"
|
||||||
|
subtitle: "Maître d'oeuvre du web"
|
||||||
|
year: 2026
|
||||||
|
dateLabel: "2026 - auj."
|
||||||
|
category: "Application web"
|
||||||
|
client: "EverEast Solutions"
|
||||||
|
roles:
|
||||||
|
- "Co-fondateur"
|
||||||
|
- "Développeur Full-Stack"
|
||||||
|
technologies:
|
||||||
|
- "React"
|
||||||
|
- "FastAPI"
|
||||||
|
- "MariaDB"
|
||||||
|
- "Docker"
|
||||||
|
- "Cloudron"
|
||||||
|
- "Mollie"
|
||||||
|
- "Ackee"
|
||||||
|
cover: "/images/projects/evereast/cover.jpg"
|
||||||
|
preview1: "/images/projects/evereast/preview-1.jpg"
|
||||||
|
preview2: "/images/projects/evereast/preview-2.jpg"
|
||||||
|
description: "Plateforme de mise en relations clients - agences web et gestion de projet agile"
|
||||||
|
website: "https://evereast-solutions.com"
|
||||||
|
featured: true
|
||||||
|
---
|
||||||
|
|
||||||
|
## La mission
|
||||||
|
|
||||||
|
EverEast Solutions est une application web pensée pour centraliser la gestion métier,
|
||||||
|
simplifier les flux internes et proposer une expérience utilisateur sobre, rapide et claire.
|
||||||
|
|
||||||
|
Le projet a été conçu avec une architecture full-stack moderne, avec une attention
|
||||||
|
particulière portée à la maintenabilité, aux performances et au déploiement.
|
||||||
|
|
||||||
|
<ProjectsColumns>
|
||||||
|
<div>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sed
|
||||||
|
pulvinar eget in. Mi varius senectus adipiscing augue in metus.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Eget sagittis dui dictum porttitor. Urna ornare mattis pharetra sapien
|
||||||
|
elit netus eleifend ante et. Viverra nunc duis id pellentesque aliquam arcu.
|
||||||
|
</div>
|
||||||
|
</ProjectsColumns>
|
||||||
|
|
||||||
|
## Résultats
|
||||||
|
|
||||||
|
- Mise en place d’une architecture API + front séparés
|
||||||
|
- Déploiement conteneurisé avec Docker
|
||||||
|
- Intégration de paiement avec Mollie
|
||||||
|
- Suivi d’audience avec Ackee
|
||||||
|
|
||||||
|
<ProjectPreviewGrid
|
||||||
|
items={[
|
||||||
|
{ title: "Aperçu 1", image: "/images/projects/evereast/preview-1.jpg" },
|
||||||
|
{ title: "Aperçu 2", image: "/images/projects/evereast/preview-2.jpg" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProjectVisitButton href="https://evereast-solutions.com" />
|
||||||
|
|
||||||
|
## Autres informations
|
||||||
|
|
||||||
|
Le projet a été mené avec une approche pragmatique, en priorisant les besoins métier,
|
||||||
|
la qualité du code et une interface cohérente avec l’identité du client.
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import matter from "gray-matter";
|
||||||
|
import { Project, ProjectFrontmatter } from "./types";
|
||||||
|
|
||||||
|
const projectsDirectory = path.join(process.cwd(), "content/projects");
|
||||||
|
|
||||||
|
function fileNameToSlug(fileName: string) {
|
||||||
|
return fileName.replace(/\.mdx$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProjectSlugs() {
|
||||||
|
return fs
|
||||||
|
.readdirSync(projectsDirectory)
|
||||||
|
.filter((file) => file.endsWith(".mdx"))
|
||||||
|
.map(fileNameToSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProjectBySlug(slug: string): Project | null {
|
||||||
|
const realSlug = slug.replace(/\.mdx$/, "");
|
||||||
|
const fullPath = path.join(projectsDirectory, `${realSlug}.mdx`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileContents = fs.readFileSync(fullPath, "utf8");
|
||||||
|
const { data, content } = matter(fileContents);
|
||||||
|
|
||||||
|
return {
|
||||||
|
slug: realSlug,
|
||||||
|
...(data as ProjectFrontmatter),
|
||||||
|
content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllProjects(): Project[] {
|
||||||
|
return getProjectSlugs()
|
||||||
|
.map((slug) => getProjectBySlug(slug))
|
||||||
|
.filter((project): project is Project => project !== null)
|
||||||
|
.sort((a, b) => b.year - a.year);
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export type ProjectFrontmatter = {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
year: number;
|
||||||
|
category: string;
|
||||||
|
client: string;
|
||||||
|
roles: string[];
|
||||||
|
technologies: string[];
|
||||||
|
preview1?: string;
|
||||||
|
preview2?: string;
|
||||||
|
website: string;
|
||||||
|
featured: boolean;
|
||||||
|
description: string;
|
||||||
|
dateLabel: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Project = ProjectFrontmatter & {
|
||||||
|
slug: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
Generated
+1834
-10
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,9 @@
|
|||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"gray-matter": "^4.0.3",
|
||||||
"next": "16.2.3",
|
"next": "16.2.3",
|
||||||
|
"next-mdx-remote": "^6.0.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"react-icons": "^5.6.0"
|
"react-icons": "^5.6.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user