diff --git a/README.md b/README.md
old mode 100644
new mode 100755
diff --git a/app/components/Nav.jsx b/app/components/Nav.jsx
new file mode 100644
index 0000000..f33aa04
--- /dev/null
+++ b/app/components/Nav.jsx
@@ -0,0 +1,26 @@
+import Link from "next/link";
+
+export default function Nav() {
+ return (
+
+
+ Deligraph
+
+
+
+ -
+ Portfolio
+
+ -
+ Projets Clients Side
+
+ -
+ Projets Server Side
+
+ -
+ Contact
+
+
+
+ );
+}
diff --git a/app/components/PortfolioGridServerSide.jsx b/app/components/PortfolioGridServerSide.jsx
new file mode 100644
index 0000000..71f79e9
--- /dev/null
+++ b/app/components/PortfolioGridServerSide.jsx
@@ -0,0 +1,35 @@
+export default async function PortfolioGridServerSide({ apiUrl = "https://deligraph.com/wp-json/wp/v2/portfolio" }) {
+ let posts = [];
+ let error = null;
+
+ try {
+ posts = await fetchPortfolioPosts();
+ } catch (err) {
+ error = err.message;
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {posts.map((post) => (
+
+
+ {post.title.rendered}
+
+
+ {post.content.rendered}
+
+
+ ))}
+
+ );
+}
diff --git a/app/components/PostGridClientSide.jsx b/app/components/PostGridClientSide.jsx
new file mode 100644
index 0000000..f63c7e8
--- /dev/null
+++ b/app/components/PostGridClientSide.jsx
@@ -0,0 +1,66 @@
+"use client";
+import { useState, useEffect } from "react";
+
+export default function PostGridClientSide({ apiUrl = "https://deligraph.com/wp-json/wp/v2/portfolio" }) {
+ const [posts, setPosts] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchPosts = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const response = await fetch(apiUrl);
+
+ if (!response.ok) {
+ throw new Error(`Erreur HTTP: ${response.status}`);
+ }
+
+ const data = await response.json();
+ setPosts(data);
+ } catch (err) {
+ setError(err.message);
+ console.error("Erreur lors du fetch:", err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchPosts();
+ }, [apiUrl]);
+
+ if (loading) {
+ return (
+
+
Chargement des posts...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+ console.log(posts);
+
+ return (
+
+ {posts.map((post) => (
+
+
+ {post.title.rendered}
+
+
+ {post.content.rendered}
+
+
+ ))}
+
+ );
+}
diff --git a/app/components/PostGridServerSide.jsx b/app/components/PostGridServerSide.jsx
new file mode 100644
index 0000000..6560daf
--- /dev/null
+++ b/app/components/PostGridServerSide.jsx
@@ -0,0 +1,66 @@
+import Link from "next/link";
+import Image from "next/image";
+import { fetchPortfolioPosts } from "../utils/useWordpress";
+
+export default async function PortfolioGridServerSide() {
+ let posts = [];
+ let error = null;
+
+ try {
+ // fetchPortfolioPosts récupère automatiquement les images de couverture
+ const result = await fetchPortfolioPosts({
+ perPage: 10,
+ fetchOptions: {
+ next: { revalidate: 3600 }, // Revalide toutes les heures
+ },
+ });
+ posts = result.posts;
+ } catch (err) {
+ error = err.message;
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {posts.map((post) => {
+ return (
+
+
+ {post.featuredImageUrl && (
+
+
+
+ )}
+
+
+ {post.title.rendered}
+
+
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/app/contact/page.jsx b/app/contact/page.jsx
new file mode 100644
index 0000000..295087c
--- /dev/null
+++ b/app/contact/page.jsx
@@ -0,0 +1,30 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+
+export default function Home() {
+ return (
+
+
+
+ {" "}
+ Deligraph
+
+
+
+ -
+ Projets Clients Side
+
+ -
+ Projets Server Side
+
+ -
+ Contact
+
+
+
+ prooooojets
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
index a2dc41e..3e3ad80 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,26 +1,42 @@
@import "tailwindcss";
:root {
- --background: #ffffff;
- --foreground: #171717;
+ --background: #ffffff;
+ --foreground: #171717;
}
@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --font-sans: var(--font-geist-sans);
- --font-mono: var(--font-geist-mono);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-geist-sans);
+ --font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
}
body {
- background: var(--background);
- color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+
+ &:has(main[data-theme="dark"]) {
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+ }
+ &:has(main[data-theme="light"]) {
+ --background: white;
+ --foreground: #000;
+
+ background: var(--background);
+ color: var(--foreground);
+ font-family: Arial, Helvetica, sans-serif;
+ }
+ background: white;
+ color: #000;
}
diff --git a/app/page.tsx b/app/page.tsx
index 295f8fd..df8a0e5 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,65 +1,55 @@
+"use client";
+import Link from "next/link";
+import { useState, useEffect } from "react";
import Image from "next/image";
export default function Home() {
- return (
-
-
-
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
-
-
- );
+ const [theme, setTheme] = useState<"light" | "dark">("light");
+
+ useEffect(() => {
+ // Vérifier les préférences système uniquement côté client
+ const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
+ const initialTheme = isDarkMode ? "dark" : "light";
+ setTheme(initialTheme);
+ document.documentElement.classList.toggle("dark", initialTheme === "dark");
+ }, []);
+
+ const toggleTheme = () => {
+ const newTheme = theme === "light" ? "dark" : "light";
+ setTheme(newTheme);
+ document.documentElement.classList.toggle("dark", newTheme === "dark");
+ };
+ return (
+
+
+
+ Deligraph
+
+
+
+ -
+ Projets Clients Side
+
+ -
+ Projets Server Side
+
+ -
+ Contact
+
+
+
+
+
+ );
}
diff --git a/app/projects-clients-side/page.jsx b/app/projects-clients-side/page.jsx
new file mode 100644
index 0000000..2972e63
--- /dev/null
+++ b/app/projects-clients-side/page.jsx
@@ -0,0 +1,13 @@
+import Link from "next/link";
+import PostGridClientSide from "../components/PostGridClientSide";
+import Nav from "../components/Nav";
+
+export default function Projects() {
+ return (
+
+
+ Projets
+
+
+ );
+}
diff --git a/app/projects-server-side/page.jsx b/app/projects-server-side/page.jsx
new file mode 100644
index 0000000..f7fa55d
--- /dev/null
+++ b/app/projects-server-side/page.jsx
@@ -0,0 +1,13 @@
+import Link from "next/link";
+import PostGridServerSide from "../components/PostGridServerSide";
+import Nav from "../components/Nav";
+
+export default function Projects() {
+ return (
+
+
+ Projets
+
+
+ );
+}
diff --git a/app/projects/[slug]/page.jsx b/app/projects/[slug]/page.jsx
new file mode 100644
index 0000000..fc3745d
--- /dev/null
+++ b/app/projects/[slug]/page.jsx
@@ -0,0 +1,74 @@
+import { notFound } from "next/navigation";
+import Image from "next/image";
+import Link from "next/link";
+import Nav from "../../components/Nav";
+import { fetchPortfolioPostBySlug, fetchMedia } from "../../utils/useWordpress";
+
+export default async function ProjectDetail({ params }) {
+ const { slug } = params;
+
+ if (!slug) {
+ notFound();
+ }
+
+ let post = null;
+ let featuredImageUrl = null;
+
+ try {
+ // Récupérer le post par son slug
+ post = await fetchPortfolioPostBySlug(slug, {
+ next: { revalidate: 3600 }, // Revalide toutes les heures
+ });
+
+ if (!post) {
+ notFound();
+ }
+
+ // Récupérer l'image featured si elle existe
+ if (post.featured_media) {
+ featuredImageUrl = await fetchMedia(post.featured_media, {
+ next: { revalidate: 3600 },
+ });
+ }
+ } catch (error) {
+ console.error("Erreur lors de la récupération du projet:", error);
+ notFound();
+ }
+
+ return (
+
+
+
+
+ ← Retour aux projets
+
+
+ {featuredImageUrl && (
+
+
+
+ )}
+
+
+ {post.title?.rendered || "Sans titre"}
+
+
+
+
+
+ );
+}
diff --git a/app/projects/not-found.jsx b/app/projects/not-found.jsx
new file mode 100644
index 0000000..eab033e
--- /dev/null
+++ b/app/projects/not-found.jsx
@@ -0,0 +1,21 @@
+import Link from "next/link";
+
+export default function NotFound() {
+ return (
+
+
+
+ 404 - Projet non trouvé
+
+
+ Le projet que vous recherchez n'existe pas ou a été supprimé.
+
+
+ Retour aux projets
+
+
+
+ );
+}
diff --git a/app/projects/page.jsx b/app/projects/page.jsx
new file mode 100644
index 0000000..654a1f2
--- /dev/null
+++ b/app/projects/page.jsx
@@ -0,0 +1,12 @@
+import PortfolioGridServerSide from "../components/PostGridServerSide";
+import Nav from "../components/Nav";
+
+export default function Projects() {
+ return (
+
+
+ Portfolio
+
+
+ );
+}
diff --git a/app/utils/useWordpress.js b/app/utils/useWordpress.js
new file mode 100644
index 0000000..6b8b3f7
--- /dev/null
+++ b/app/utils/useWordpress.js
@@ -0,0 +1,281 @@
+/**
+ * Fonctions utilitaires pour récupérer des données depuis WordPress REST API
+ * Compatible avec Next.js (client et serveur)
+ */
+
+const WORDPRESS_API_BASE = "https://deligraph.com/wp-json/wp/v2";
+const PORTFOLIO_ENDPOINT = `${WORDPRESS_API_BASE}/portfolio`;
+
+/**
+ * Construit une URL avec des paramètres de requête
+ * @param {string} baseUrl - URL de base
+ * @param {Object} params - Paramètres de requête
+ * @returns {string} URL complète avec paramètres
+ */
+function buildUrl(baseUrl, params = {}) {
+ const url = new URL(baseUrl);
+ Object.keys(params).forEach((key) => {
+ if (params[key] !== undefined && params[key] !== null) {
+ url.searchParams.append(key, params[key]);
+ }
+ });
+ return url.toString();
+}
+
+/**
+ * Récupère les informations d'un média WordPress par son ID
+ * @param {number} mediaId - ID du média WordPress
+ * @param {Object} fetchOptions - Options supplémentaires pour fetch
+ * @returns {Promise} URL de l'image ou null si non trouvé
+ */
+export async function fetchMedia(mediaId, fetchOptions = {}) {
+ if (!mediaId) {
+ return null;
+ }
+
+ const url = `${WORDPRESS_API_BASE}/media/${mediaId}`;
+
+ try {
+ const response = await fetch(url, {
+ ...fetchOptions,
+ });
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return null;
+ }
+ throw new Error(`Erreur HTTP: ${response.status} - ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ // Retourner l'URL source de l'image
+ return data.source_url || null;
+ } catch (error) {
+ console.error(`Erreur lors de la récupération du média ID "${mediaId}":`, error);
+ return null; // Retourner null au lieu de throw pour éviter de casser le rendu
+ }
+}
+
+/**
+ * Récupère les posts de type portfolio depuis WordPress
+ * @param {Object} options - Options de récupération
+ * @param {number} options.perPage - Nombre de posts par page (défaut: 10)
+ * @param {number} options.page - Numéro de page (défaut: 1)
+ * @param {string} options.search - Terme de recherche
+ * @param {number} options.categories - ID de catégorie
+ * @param {string} options.order - Ordre de tri (asc/desc, défaut: desc)
+ * @param {string} options.orderby - Champ de tri (date, title, etc., défaut: date)
+ * @param {Object} options.fetchOptions - Options supplémentaires pour fetch (utile pour revalidate côté serveur)
+ * @returns {Promise} Tableau de posts portfolio
+ */
+export async function fetchPortfolioPosts(options = {}) {
+ const {
+ perPage = 10,
+ page = 1,
+ search,
+ categories,
+ order = "desc",
+ orderby = "date",
+ fetchOptions = {},
+ } = options;
+
+ const params = {
+ per_page: perPage,
+ page: page,
+ order: order,
+ orderby: orderby,
+ };
+
+ if (search) {
+ params.search = search;
+ }
+
+ if (categories) {
+ params.categories = categories;
+ }
+
+ const url = buildUrl(PORTFOLIO_ENDPOINT, params);
+
+ try {
+ const response = await fetch(url, {
+ ...fetchOptions,
+ });
+
+ if (!response.ok) {
+ throw new Error(`Erreur HTTP: ${response.status} - ${response.statusText}`);
+ }
+
+ const data = await response.json();
+
+ // Récupérer les images de couverture pour chaque post en parallèle
+ const postsWithImages = await Promise.all(
+ data.map(async (post) => {
+ if (post.featured_media) {
+ try {
+ const mediaUrl = await fetchMedia(post.featured_media, fetchOptions);
+ return { ...post, featuredImageUrl: mediaUrl };
+ } catch (error) {
+ console.error(`Erreur lors de la récupération de l'image pour le post ${post.id}:`, error);
+ return { ...post, featuredImageUrl: null };
+ }
+ }
+ return { ...post, featuredImageUrl: null };
+ }),
+ );
+
+ return {
+ posts: postsWithImages,
+ totalPages: parseInt(response.headers.get("X-WP-TotalPages") || "1", 10),
+ total: parseInt(response.headers.get("X-WP-Total") || "0", 10),
+ };
+ } catch (error) {
+ console.error("Erreur lors de la récupération des posts portfolio:", error);
+ throw error;
+ }
+}
+
+/**
+ * Récupère un post portfolio spécifique par son slug
+ * @param {string} slug - Slug du post
+ * @param {Object} fetchOptions - Options supplémentaires pour fetch
+ * @returns {Promise