messagerie

This commit is contained in:
Ladebeze66 2025-02-11 13:36:44 +01:00
parent 65e2a18cac
commit a64743770e
84 changed files with 188 additions and 66 deletions

View File

@ -3,6 +3,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { getApiUrl } from "../utils/getApiUrl"; // 🔥 Import de l'URL dynamique import { getApiUrl } from "../utils/getApiUrl"; // 🔥 Import de l'URL dynamique
import CarouselCompetences from "../components/CarouselCompetences"; // 🔥 Import du composant CarouselCompetences
export default function Page() { export default function Page() {
const [competences, setCompetences] = useState([]); // 🔥 Stocker les compétences une seule fois const [competences, setCompetences] = useState([]); // 🔥 Stocker les compétences une seule fois
@ -28,34 +29,31 @@ export default function Page() {
return ( return (
<main className="w-full p-3 mt-5 mb-5"> <main className="w-full p-3 mt-5 mb-5">
{/* Titre de la page */}
<h1 className="text-3xl mb-3 font-bold text-gray-700 text-center">Mes Compétences</h1>
{/* Affichage d'un message si aucune compétence n'est trouvée */} {/* Affichage d'un message si aucune compétence n'est trouvée */}
{competences.length === 0 ? ( {competences.length === 0 ? (
<p className="text-center text-gray-500">Aucune compétence disponible.</p> <p className="text-center text-gray-500">Aucune compétence disponible.</p>
) : ( ) : (
<div className="grid gap-7 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto"> <div className="flex flex-col gap-7 max-w-7xl mx-auto">
{competences.map((competence) => { {competences.map((competence) => {
const picture = competence.picture?.[0]; const pictures = competence.picture || [];
const imageUrl = picture?.url ? `${apiUrl}${picture.url}` : "/placeholder.jpg"; const images = pictures.map(picture => ({
url: picture.url ? `${apiUrl}${picture.url}` : "/placeholder.jpg",
alt: picture.name || "Competence image"
}));
return ( return (
<div <div
key={competence.id} key={competence.id}
className="bg-white rounded-lg shadow-md overflow-hidden w-80 h-96 flex flex-col transform transition-all duration-300 hover:scale-105 hover:shadow-xl p-4" className="bg-white/70 rounded-lg shadow-md overflow-hidden w-full h-auto flex flex-col transform transition-all duration-300 hover:scale-105 hover:shadow-xl p-4"
> >
{/* Lien vers la page de détail de la compétence */} {/* Lien vers la page de détail de la compétence */}
<Link href={`/competences/${competence.slug}`}> <Link href={`/competences/${competence.slug}`}>
<div className="overflow-hidden w-full h-48 mb-4"> <div className="overflow-hidden w-full h-64 mb-4">
<img <CarouselCompetences images={images} className="w-full h-full object-cover" />
src={imageUrl}
alt={picture?.name || "Competence image"}
className="w-full h-full object-cover"
/>
</div> </div>
<div className="flex-grow overflow-y-auto max-h-32 hide-scrollbar show-scrollbar"> <div className="flex-grow overflow-y-auto max-h-32 hide-scrollbar show-scrollbar">
<p className="font-bold text-xl mb-2">{competence.name}</p> <p className="font-orbitron-16-bold text-xl mb-2">{competence.name}</p>
<p className="text-gray-700 text-sm hover:text-base transition-all duration-200 ease-in-out"> <p className="text-gray-700 text-sm hover:text-base transition-all duration-200 ease-in-out">
{competence.description} {competence.description}
</p> </p>
@ -68,4 +66,4 @@ export default function Page() {
)} )}
</main> </main>
); );
} }

View File

@ -1,11 +1,13 @@
// Composant principal de la page des messages import { getApiUrl } from "../../utils/getApiUrl"; // 🔥 Import de l'URL dynamique
// // Composant principal de la page des messages
export default async function MessagesPage() { export default async function MessagesPage() {
// Récupération des messages depuis l'API Strapi // Récupération des messages depuis l'API Strapi
const res = await fetch("http://localhost:1337/api/messages"); const apiUrl = getApiUrl();
const res = await fetch(`${apiUrl}/api/messages`);
const { data } = await res.json(); const { data } = await res.json();
return ( return (
<div className="max-w-3xl mx-auto p-6"> <div className="bg-white/70 rounded-md max-w-3xl mx-auto p-6">
{/* Titre de la page */} {/* Titre de la page */}
<h1 className="text-3xl font-bold text-center mb-6">📬 Messages reçus</h1> <h1 className="text-3xl font-bold text-center mb-6">📬 Messages reçus</h1>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -5,6 +5,102 @@
@import url('https://fonts.googleapis.com/css2?family=Exo+2:wght@400;700&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Exo+2:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Audiowide&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Audiowide&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap');
.font-orbitron-24 {
font-family: 'Orbitron', sans-serif;
font-size: 24px;
color: #333333;
}
.font-orbitron-16 {
font-family: 'Orbitron', sans-serif;
font-size: 16px;
color: #333333;
}
.font-orbitron-12 {
font-family: 'Orbitron', sans-serif;
font-size: 12px;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 24px en gras */
.font-orbitron-24-bold {
font-family: 'Orbitron', sans-serif;
font-size: 24px;
font-weight: bold;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 24px en italique */
.font-orbitron-24-italic {
font-family: 'Orbitron', sans-serif;
font-size: 24px;
font-style: italic;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 24px en gras et italique */
.font-orbitron-24-bold-italic {
font-family: 'Orbitron', sans-serif;
font-size: 24px;
font-weight: bold;
font-style: italic;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 16px en gras */
.font-orbitron-16-bold {
font-family: 'Orbitron', sans-serif;
font-size: 16px;
font-weight: bold;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 16px en italique */
.font-orbitron-16-italic {
font-family: 'Orbitron', sans-serif;
font-size: 16px;
font-style: italic;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 16px en gras et italique */
.font-orbitron-16-bold-italic {
font-family: 'Orbitron', sans-serif;
font-size: 16px;
font-weight: bold;
font-style: italic;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 12px en gras */
.font-orbitron-12-bold {
font-family: 'Orbitron', sans-serif;
font-size: 12px;
font-weight: bold;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 12px en italique */
.font-orbitron-12-italic {
font-family: 'Orbitron', sans-serif;
font-size: 12px;
font-style: italic;
color: #333333;
}
/* Classe pour la police Orbitron avec taille 12px en gras et italique */
.font-orbitron-12-bold-italic {
font-family: 'Orbitron', sans-serif;
font-size: 12px;
font-weight: bold;
font-style: italic;
color: #333333;
}
/* Importation des styles de base, des composants et des utilitaires de Tailwind CSS */ /* Importation des styles de base, des composants et des utilitaires de Tailwind CSS */
@tailwind base; @tailwind base;
@ -98,4 +194,4 @@
.show-scrollbar:hover::-webkit-scrollbar { .show-scrollbar:hover::-webkit-scrollbar {
display: block; /* WebKit (Chrome, Safari, Edge) */ display: block; /* WebKit (Chrome, Safari, Edge) */
} }

View File

@ -54,7 +54,7 @@ export default function Carousel({ images, className }: CarouselProps) {
<div className="relative w-full max-w-6xl p-6 bg-transparent"> <div className="relative w-full max-w-6xl p-6 bg-transparent">
{/* Bouton de fermeture */} {/* Bouton de fermeture */}
<button <button
className="absolute top-6 right-6 text-white text-3xl bg-gray-900/70 p-2 rounded-full" className="absolute top-6 right-6 text-white text-l bg-gray-900/70 p-2 rounded-full"
onClick={() => setSelectedImage(null)} // Fermer au clic onClick={() => setSelectedImage(null)} // Fermer au clic
> >

View File

@ -54,7 +54,7 @@ export default function CarouselCompetences({ images, className }: CarouselProps
<div className="relative w-full max-w-6xl p-6 bg-transparent"> <div className="relative w-full max-w-6xl p-6 bg-transparent">
{/* Bouton de fermeture */} {/* Bouton de fermeture */}
<button <button
className="absolute top-6 right-6 text-white text-3xl bg-gray-900/70 p-2 rounded-full" className="absolute top-6 right-6 text-white text-l bg-gray-900/70 p-2 rounded-full"
onClick={() => setSelectedImage(null)} // Fermer au clic onClick={() => setSelectedImage(null)} // Fermer au clic
> >

View File

@ -51,14 +51,14 @@ export default function ContactForm() {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="max-w-lg mx-auto p-6 bg-white shadow-lg rounded-lg animate-fade-in" className="max-w-lg mx-auto p-6 bg-white shadow-lg rounded-lg animate-fade-in"
> >
<h2 className="text-2xl font-bold mb-4 text-center">📩 Contactez-moi</h2> <h2 className="text-2xl font-orbitron-16-bold mb-4 text-center">📩 Contactez-moi</h2>
<input <input
type="text" type="text"
placeholder="Votre nom" placeholder="Votre nom"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
className="w-full p-3 border border-gray-300 rounded mb-3 focus:outline-none focus:ring-2 focus:ring-blue-400" className="w-full p-3 border border-gray-300 font-orbitron-16-bold rounded mb-3 focus:outline-none focus:ring-2 focus:ring-blue-400"
required required
/> />
@ -67,7 +67,7 @@ export default function ContactForm() {
placeholder="Votre email" placeholder="Votre email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full p-3 border border-gray-300 rounded mb-3 focus:outline-none focus:ring-2 focus:ring-blue-400" className="w-full p-3 border border-gray-300 rounded font-orbitron-16-bold mb-3 focus:outline-none focus:ring-2 focus:ring-blue-400"
required required
/> />
@ -75,7 +75,7 @@ export default function ContactForm() {
placeholder="Votre message" placeholder="Votre message"
value={message} value={message}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
className="w-full p-3 border border-gray-300 rounded mb-3 focus:outline-none focus:ring-2 focus:ring-blue-400" className="w-full p-3 border border-gray-300 rounded mb-3 font-orbitron-16-bold focus:outline-none focus:ring-2 focus:ring-blue-400"
required required
/> />
@ -83,7 +83,7 @@ export default function ContactForm() {
type="submit" type="submit"
disabled={isLoading} // ✅ Désactive le bouton pendant l'envoi disabled={isLoading} // ✅ Désactive le bouton pendant l'envoi
className={`w-full py-3 rounded transition ${ className={`w-full py-3 rounded transition ${
isLoading ? "bg-gray-400 cursor-not-allowed" : "bg-blue-500 hover:bg-blue-600 text-white" isLoading ? "bg-gray-400 cursor-not-allowed" : "bg-blue-500 hover:bg-blue-600 text-white font-orbitron-16-bold"
}`} }`}
> >
{isLoading ? "⏳ Envoi..." : "Envoyer"} {isLoading ? "⏳ Envoi..." : "Envoyer"}

View File

@ -66,13 +66,13 @@ export default function ContentSection({ collection, slug, titleClass, contentCl
return ( return (
<div className="max-w-3xl mx-auto p-6"> <div className="max-w-3xl mx-auto p-6">
{/* Titre de la section */} {/* Titre de la section */}
<h1 className={titleClass || "text-3xl mb-6 font-bold text-gray-700"}>{name}</h1> <h1 className={titleClass || "bg-white/50 rounded-md text-3xl mb-6 font-orbitron-24-bold p-2 text-blue-700"}>{name}</h1>
{/* Carrousel réutilisable pour afficher les images */} {/* Carrousel réutilisable pour afficher les images */}
<Carousel images={images} className="w-full h-64" /> <Carousel images={images} className="w-full h-64" />
{/* Contenu en Markdown */} {/* Contenu en Markdown */}
<div className={contentClass || "bg-white/55 rounded-md p-4 shadow-md mt-6"}> <div className={contentClass || "bg-white/80 rounded-md p-4 font-orbitron-16-bold shadow-md mt-6"}>
<ReactMarkdown>{richText}</ReactMarkdown> <ReactMarkdown>{richText}</ReactMarkdown>
</div> </div>
@ -83,7 +83,7 @@ export default function ContentSection({ collection, slug, titleClass, contentCl
href={link} href={link}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-blue-500 hover:underline transition duration-300 ease-in-out transform hover:scale-105 hover:text-blue-700" className="bg-white/65 rounded-md p-1 text-red-700 hover:underline transition duration-300 ease-in-out transform hover:scale-105 font-orbitron-16-bold hover:text-blue-700"
> >
{linkText || "Voir plus/lien externe"} {linkText || "Voir plus/lien externe"}
</a> </a>

View File

@ -117,9 +117,9 @@ export default function ContentSectionCompetences({
return ( return (
<div className="max-w-3xl mx-auto p-6"> <div className="max-w-3xl mx-auto p-6">
<h1 className={titleClass || "text-3xl mb-6 font-bold text-gray-700"}>{name}</h1> <h1 className={titleClass || "bg-white/60 rounded-md p-1 text-2xl mb-6 font-orbitron-16-bold text-blue-700"}>{name}</h1>
<CarouselCompetences images={images} className="w-full h-64" /> <CarouselCompetences images={images} className="w-full h-64" />
<div className={contentClass || "mt-6 text-lg text-black-700"}> <div className={contentClass || "bg-white/70 rounded-md p-4 mt-6 text-lg font-orbitron-16-bold text-black-700"}>
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{contentWithLinks}</ReactMarkdown> <ReactMarkdown rehypePlugins={[rehypeRaw]}>{contentWithLinks}</ReactMarkdown>
</div> </div>
{selectedMot && <ModalGlossaire mot={selectedMot} onClose={() => setSelectedMot(null)} />} {selectedMot && <ModalGlossaire mot={selectedMot} onClose={() => setSelectedMot(null)} />}

View File

@ -12,14 +12,9 @@ export default function Footer() {
return ( return (
<footer className="bg-white/50 backdrop-blur rounded-lg"> <footer className="bg-white/50 backdrop-blur rounded-lg">
<div className="max-w-4xl mx-auto flex flex-col items-center py-6 text-sm text-gray-400"> <div className="max-w-4xl mx-auto flex flex-col items-center font-orbitron-12 py-6 text-sm text-gray-700">
{/* Affichage de l'année actuelle */} {/* Affichage de l'année actuelle */}
<p>&copy; {new Date().getFullYear()} Our Company.</p> <p>&copy; {new Date().getFullYear()} Gras-Calvet Fernand</p>
{/* Affichage du compteur de clics et du bouton */}
<p>
Vous avez cliqué {count} fois sur le bouton.
<button onClick={handleClick}>Click Me</button>
</p>
</div> </div>
</footer> </footer>
); );

View File

@ -1,25 +1,47 @@
import ContactForm from "../components/ContactForm"; // Importation du composant ContactForm // Importation de ContactForm et getApiUrl
import ContactForm from "../components/ContactForm";
import { getApiUrl } from "../utils/getApiUrl";
export default function ContactPage() { export default function ContactPage() {
// Définition de l'URL API dynamique
const apiUrl = getApiUrl();
return ( return (
<div className="max-w-3xl mx-auto p-6"> <div className="max-w-3xl mx-auto p-6">
{/* Titre de la page */} {/* Titre avec un cadre */}
<h1 className="text-3xl font-bold text-center mb-6">Contactez-moi</h1> <h1 className="bg-white/50 rounded-md text-3xl font-orbitron-24-bold text-center mb-6 border-b-4 border-blue-500 pb-2">
📬 Correspondance
</h1>
{/* Texte d'introduction */} {/* Texte d'introduction */}
<p className="text-lg text-center mb-4"> <p className="bg-white/70 rounded-md font-orbitron-16 text-lg text-center border-b-4 border-blue-500 pb-2 mb-4">
Vous pouvez me contacter via ce formulaire ou sur mes réseaux sociaux. Vous pouvez me contacter via ce formulaire ou sur mes réseaux sociaux.
</p> </p>
{/* Liens vers les réseaux sociaux */} {/* Liens vers les réseaux sociaux mis à jour */}
<div className="flex justify-center space-x-4 mb-6"> <div className="bg-white/80 rounded-mt flex justify-center space-x-4 mb-6">
<a href="https://linkedin.com/in/votreprofil" className="text-blue-500">LinkedIn</a> <a href="https://linkedin.com/in/votreprofil"
<a href="https://twitter.com/votreprofil" className="text-blue-500">Twitter</a> target="_blank"
<a href="mailto:votre@email.com" className="text-blue-500">Email</a> rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 font-orbitron-16-bold transition">
LinkedIn
</a>
<a href="https://www.facebook.com/ton.profil"
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:text-blue-700 font-orbitron-16-bold transition">
Facebook
</a>
<a href="mailto:grascalvet.fernand@gmail.com"
className="text-blue-500 hover:text-blue-700 font-orbitron-16-bold transition">
Email
</a>
</div> </div>
{/* Formulaire de contact */} {/* Formulaire de contact amélioré */}
<ContactForm /> <div className="bg-white/50 p-6 rounded-lg border-b-4 border-blue-500 pb-2 shadow">
<ContactForm apiUrl={apiUrl} />
</div>
</div> </div>
); );
} }

View File

@ -39,4 +39,4 @@ body {
/* Classe utilitaire pour appliquer l'animation de fondu en entrée */ /* Classe utilitaire pour appliquer l'animation de fondu en entrée */
.animate-fade-in { .animate-fade-in {
animation: fade-in 0.5s ease-out; /* Animation de 0.5s avec une courbe de transition */ animation: fade-in 0.5s ease-out; /* Animation de 0.5s avec une courbe de transition */
} }

View File

@ -12,9 +12,9 @@ export default function RootLayout({ children }) {
<div className="bg-wallpaper min-h-[100dvh] grid grid-rows-[auto_1fr_auto]"> <div className="bg-wallpaper min-h-[100dvh] grid grid-rows-[auto_1fr_auto]">
<header className="z-10 bg-white/50 backdrop-blur rounded-lg border-2 border-gray-500"> <header className="z-10 bg-white/50 backdrop-blur rounded-lg border-2 border-gray-500">
<div className="max-w-4xl mx-auto flex items-center justify-between p-4"> <div className="max-w-4xl mx-auto flex items-center justify-between p-4">
<h2 className="text-2xl font-bold">Portfolio Gras-Calvet Fernand</h2> <h2 className="text-2xl font-orbitron-24-bold-italic">Portfolio Gras-Calvet Fernand</h2>
<nav> <nav>
<ul className="flex gap-x-7 text-black-500 font-bold"> <ul className="flex gap-x-7 text-black-500 font-orbitron-16-bold">
<li><NavLink text="Accueil" path="/" /></li> <li><NavLink text="Accueil" path="/" /></li>
<li><NavLink text="Portfolio" path="/portfolio" /></li> <li><NavLink text="Portfolio" path="/portfolio" /></li>
<li><NavLink text="Compétences" path="/competences" /></li> <li><NavLink text="Compétences" path="/competences" /></li>

View File

@ -36,7 +36,7 @@ export default function HomePage() {
return ( return (
<main className="max-w-3xl w-full mx-auto flex flex-col items-center justify-center p-6 bg-white/55 rounded-lg mt-12 mb-3"> <main className="max-w-3xl w-full mx-auto flex flex-col items-center justify-center p-6 bg-white/55 rounded-lg mt-12 mb-3">
<h1 className="text-3xl font-bold text-gray-800 mb-4">{title}</h1> <h1 className="text-3xl font-orbitron-24-bold-italic text-gray-800 mb-4">{title}</h1>
{imageUrl ? ( {imageUrl ? (
<div className="relative w-64 h-64 rounded-full overflow-hidden shadow-lg border-4 border-gray-300 transition-transform duration-300 hover:scale-110 hover:rotate-3"> <div className="relative w-64 h-64 rounded-full overflow-hidden shadow-lg border-4 border-gray-300 transition-transform duration-300 hover:scale-110 hover:rotate-3">
@ -48,7 +48,7 @@ export default function HomePage() {
</div> </div>
)} )}
<div className="mt-6 text-lg text-black-700 max-w-2xl px-6 text-center"> <div className="mt-6 text-lg text-black-700 max-w-2xl font-orbitron-16-bold px-6 text-center">
<ReactMarkdown>{cv}</ReactMarkdown> <ReactMarkdown>{cv}</ReactMarkdown>
</div> </div>
</main> </main>

View File

@ -29,8 +29,7 @@ export default function Page() {
return ( return (
<main className="w-full p-3 mt-5 mb-5"> <main className="w-full p-3 mt-5 mb-5">
{/* Titre de la page */}
<h1 className="text-3xl mb-3 font-bold text-gray-700 text-center">Portfolio formation 42</h1>
{/* Grille des projets */} {/* Grille des projets */}
<div className="grid gap-7 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto"> <div className="grid gap-7 grid-cols-[repeat(auto-fit,minmax(300px,1fr))] max-w-7xl mx-auto">
@ -44,7 +43,7 @@ export default function Page() {
return ( return (
<div <div
key={project.id} key={project.id}
className="bg-white rounded-lg shadow-md overflow-hidden w-80 h-96 flex flex-col transform transition-all duration-300 hover:scale-105 hover:shadow-xl p-4" className="bg-white/80 rounded-lg shadow-md overflow-hidden w-80 h-96 flex flex-col transform transition-all duration-300 hover:scale-105 hover:shadow-xl p-4"
> >
{/* Lien vers la page de détail du projet */} {/* Lien vers la page de détail du projet */}
<Link href={`/portfolio/${project.slug}`}> <Link href={`/portfolio/${project.slug}`}>
@ -62,8 +61,8 @@ export default function Page() {
</div> </div>
<div className="flex-grow overflow-y-auto max-h-32 hide-scrollbar show-scrollbar"> <div className="flex-grow overflow-y-auto max-h-32 hide-scrollbar show-scrollbar">
<p className="font-bold text-xl mb-2">{project.name}</p> <p className="font-orbitron-16-bold text-xl mb-2">{project.name}</p>
<p className="text-gray-700 text-sm hover:text-base transition-all duration-200 ease-in-out"> <p className="text-gray-700 text-sm font-orbitron-12 hover:text-base transition-all duration-200 ease-in-out">
{project.description} {project.description}
</p> </p>
</div> </div>

View File

@ -4,24 +4,34 @@ require("dotenv").config();
console.log("🔍 Vérification NEXT_PUBLIC_API_URL:", process.env.NEXT_PUBLIC_API_URL); console.log("🔍 Vérification NEXT_PUBLIC_API_URL:", process.env.NEXT_PUBLIC_API_URL);
const API_URL = process.env.NEXT_PUBLIC_API_URL || "https://api.fernandgrascalvet.com"; // ✅ Valeur par défaut sécurisée
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
compress: false, // ❌ Désactive la compression Gzip pour éviter les erreurs IIS compress: false, // ❌ Désactive Gzip pour éviter les erreurs IIS
trailingSlash: true, trailingSlash: false, // ❌ Peut causer des erreurs avec Next.js App Router, on le désactive
// Utilisation de l'API URL dynamique pour Strapi // ✅ Empêche WebSocket HMR en HTTPS et force le polling pour éviter les erreurs
webpackDevMiddleware: (config: any) => {
config.watchOptions = {
poll: 1000, // Vérifie les changements toutes les 1 seconde
aggregateTimeout: 300,
};
return config;
},
// ✅ Rewrites pour Strapi (évite d'écrire l'URL complète dans chaque requête)
async rewrites() { async rewrites() {
return [ return [
{ {
source: "/api/:path*", source: "/api/:path*",
destination: process.env.NEXT_PUBLIC_API_URL + "/api/:path*", destination: `${API_URL}/api/:path*`, // ✅ Utilisation sécurisée de la variable d'API
}, },
]; ];
}, },
images: { images: {
domains: ["localhost", "api.fernandgrascalvet.com"], // ✅ Autorise aussi l'API en HTTPS domains: ["localhost", "api.fernandgrascalvet.com"], // ✅ Autorise les images locales et distantes
}, },
}; };