feat: ajouter des liens GitHub aux pages de projet et implémenter le composant PastilleStatut pour le suivi de l'état en ligne

This commit is contained in:
Puechberty Arthur
2026-03-31 17:10:39 +02:00
parent 13c171e466
commit 6e2147a489
19 changed files with 92 additions and 20 deletions
-12
View File
@@ -67,7 +67,6 @@
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/generator": "^7.28.6",
@@ -1562,7 +1561,6 @@
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1622,7 +1620,6 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@@ -2122,7 +2119,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2463,7 +2459,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3031,7 +3026,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3217,7 +3211,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -5390,7 +5383,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5400,7 +5392,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -6089,7 +6080,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -6252,7 +6242,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6528,7 +6517,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+1
View File
@@ -44,6 +44,7 @@ export default function BlocNoteInfo() {
]}
images={["/placeholder-blocnote.webp", "/placeholder-blocnote-2.webp", "/placeholder-blocnote-3.webp"]}
url="https://blocnote.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/blocnote"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function CalculatriceInfo() {
]}
images={["/placeholder-calculatrice.webp", "/placeholder-calculatrice-2.webp", "/placeholder-calculatrice-3.webp"]}
url="https://calculatrice.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/calculatrice"
/>
);
}
+1
View File
@@ -45,6 +45,7 @@ export default function ChronoInfo() {
]}
images={["/placeholder-chrono.webp", "/placeholder-chrono-2.webp", "/placeholder-chrono-3.webp"]}
url="https://chrono.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/chrono"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function ClockInfo() {
]}
images={["/placeholder-clock.webp", "/placeholder-clock-2.webp", "/placeholder-clock-3.webp"]}
url="https://clock.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/clock"
/>
);
}
+1
View File
@@ -45,6 +45,7 @@ export default function FormCraftInfo() {
]}
images={["/placeholder-formcraft.webp", "/placeholder-formcraft-2.webp", "/placeholder-formcraft-3.webp"]}
url="https://form.arthurp.fr/"
githubUrl="https://github.com/arthur-pbty/form"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function ImprimerSudokuInfo() {
]}
images={["/placeholder-imprimersudoku.webp", "/placeholder-imprimersudoku-2.webp", "/placeholder-imprimersudoku-3.webp"]}
url="https://imprimersudoku.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/imprimersudoku"
/>
);
}
+1
View File
@@ -44,6 +44,7 @@ export default function LazyBotInfo() {
]}
images={["/placeholder-lazybot.webp", "/placeholder-lazybot-2.webp", "/placeholder-lazybot-3.webp"]}
url="https://lazybot.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/LazyBot"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function LearnInfo() {
]}
images={["/placeholder-learn.webp", "/placeholder-learn-2.webp", "/placeholder-learn-3.webp"]}
url="https://learn.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/learn"
/>
);
}
+1
View File
@@ -44,6 +44,7 @@ export default function MoonInfo() {
]}
images={["/placeholder-moon.webp", "/placeholder-moon-2.webp", "/placeholder-moon-3.webp"]}
url="https://moon.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/moon"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function PomodoroInfo() {
]}
images={["/placeholder-pomodoro.webp", "/placeholder-pomodoro-2.webp", "/placeholder-pomodoro-3.webp"]}
url="https://pomodoro.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/pomodoro"
/>
);
}
+1
View File
@@ -44,6 +44,7 @@ export default function PortfolioInfo() {
]}
images={["/placeholder-portfolio.webp", "/placeholder-portfolio-2.webp", "/placeholder-portfolio-3.webp"]}
url="https://portfolio.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/portfolio"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function QCUInfo() {
]}
images={["/placeholder-qcu.webp", "/placeholder-qcu-2.webp", "/placeholder-qcu-3.webp"]}
url="https://qcu.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/QCM_physique"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function QRCodeInfo() {
]}
images={["/placeholder-qrcode.webp", "/placeholder-qrcode-2.webp", "/placeholder-qrcode-3.webp"]}
url="https://qrcode.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/qrcode"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function Page() {
]}
images={["/placeholder-reducelink.webp", "/placeholder-reducelink-2.webp", "/placeholder-reducelink-3.webp"]}
url="https://reducelink.arthurp.fr/"
githubUrl="https://github.com/arthur-pbty/reducelink"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function SudokuProjectPage() {
]}
images={["/placeholder-sudoku.webp", "/placeholder-sudoku-2.webp", "/placeholder-sudoku-3.webp"]}
url="https://sudoku.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/sudoku"
/>
);
}
+1
View File
@@ -43,6 +43,7 @@ export default function VisioInfo() {
]}
images={["/placeholder-visio.webp", "/placeholder-visio-2.webp", "/placeholder-visio-3.webp"]}
url="https://visio.arthurp.fr"
githubUrl="https://github.com/arthur-pbty/visio"
/>
);
}
+51
View File
@@ -0,0 +1,51 @@
"use client";
import { useEffect, useState } from "react";
interface PastilleStatutProps {
url: string;
intervalMs?: number;
}
export default function PastilleStatut({ url, intervalMs = 60000 }: PastilleStatutProps) {
const [statut, setStatut] = useState<"en-ligne" | "hors-ligne" | "inconnu">("inconnu");
useEffect(() => {
let timeout: NodeJS.Timeout;
let isMounted = true;
const check = async () => {
try {
const res = await fetch(url, { method: "HEAD", mode: "no-cors" });
if (isMounted) setStatut("en-ligne");
} catch {
if (isMounted) setStatut("hors-ligne");
}
timeout = setTimeout(check, intervalMs);
};
check();
return () => {
isMounted = false;
clearTimeout(timeout);
};
}, [url, intervalMs]);
let color = "bg-gray-400", label = "Statut inconnu";
if (statut === "en-ligne") {
color = "bg-green-500";
label = "Site en ligne";
} else if (statut === "hors-ligne") {
color = "bg-red-500";
label = "Site hors ligne";
}
return (
<span
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium text-white ${color}`}
title={label}
aria-label={label}
style={{ minWidth: 90 }}
>
<span className="w-2 h-2 rounded-full bg-white/80 mr-1" style={{ backgroundColor: color.replace("bg-", "") }} />
{label}
</span>
);
}
+25 -8
View File
@@ -1,6 +1,7 @@
import Image from "next/image";
import Link from "next/link";
import JsonLd from "@/components/JsonLd";
import PastilleStatut from "@/components/PastilleStatut";
interface FAQ {
question: string;
@@ -17,6 +18,7 @@ interface ProjectInfoPageProps {
faq?: FAQ[];
images: string[];
url: string;
githubUrl?: string;
}
export default function ProjectInfoPage({
@@ -29,6 +31,7 @@ export default function ProjectInfoPage({
faq,
images,
url,
githubUrl,
}: ProjectInfoPageProps) {
return (
<div className="min-h-screen bg-white font-sans text-zinc-900">
@@ -97,14 +100,28 @@ export default function ProjectInfoPage({
</div>
</div>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="inline-block rounded-lg bg-[#e2d6c2] px-6 py-3 text-[#5a4a2e] font-medium hover:bg-[#d6bfa3] border border-[#d6bfa3] transition-colors"
>
{"Acc\u00e9der \u00e0 l\u2019outil"}
</a>
<div className="flex gap-4 mb-2 items-center">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="inline-block rounded-lg bg-[#e2d6c2] px-6 py-3 text-[#5a4a2e] font-medium hover:bg-[#d6bfa3] border border-[#d6bfa3] transition-colors"
>
{"Acc\u00e9der \u00e0 l\u2019outil"}
</a>
{githubUrl && (
<a
href={githubUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-block rounded-lg bg-zinc-900 px-6 py-3 text-white font-medium hover:bg-zinc-700 border border-zinc-800 transition-colors"
>
{"Voir sur GitHub"}
</a>
)}
<PastilleStatut url={url} />
</div>
{longDescription && (
<section className="mt-12">