4 Commits

Author SHA1 Message Date
f67a04ed63 Update src/app/portfolio/page.tsx 2026-03-12 12:26:39 +00:00
0dcfa33f04 Merge version_6 into main
Merge version_6 into main
2026-03-12 12:10:49 +00:00
7da2934daa Update src/app/portfolio/page.tsx 2026-03-12 12:10:45 +00:00
882b5fb286 Merge version_5 into main
Merge version_5 into main
2026-03-12 12:06:18 +00:00

View File

@@ -3,10 +3,9 @@
import Link from "next/link";
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarStyleCentered from "@/components/navbar/NavbarStyleCentered/NavbarStyleCentered";
import TestimonialCardOne from "@/components/sections/testimonial/TestimonialCardOne";
import ContactText from "@/components/sections/contact/ContactText";
import FooterBaseCard from "@/components/sections/footer/FooterBaseCard";
import { Camera, Upload, Trash2 } from "lucide-react";
import { Camera, Upload, Trash2, Play, Maximize2 } from "lucide-react";
import { useState } from "react";
export default function PortfolioPage() {
@@ -57,6 +56,8 @@ export default function PortfolioPage() {
>([]);
const [uploadError, setUploadError] = useState("");
const [uploadSuccess, setUploadSuccess] = useState(false);
const [selectedMedia, setSelectedMedia] = useState<string | null>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const handleMediaUpload = (
e: React.ChangeEvent<HTMLInputElement>,
@@ -102,8 +103,13 @@ export default function PortfolioPage() {
const handleDeleteMedia = (id: string) => {
setUploadedMedia((prev) => prev.filter((media) => media.id !== id));
if (selectedMedia === id) {
setSelectedMedia(null);
}
};
const selectedMediaItem = uploadedMedia.find((m) => m.id === selectedMedia);
return (
<ThemeProvider
defaultButtonVariant="directional-hover"
@@ -127,74 +133,104 @@ export default function PortfolioPage() {
/>
</div>
{/* Portfolio Projects Section */}
<div id="portfolio" data-section="portfolio">
<TestimonialCardOne
title="Nos Réalisations"
description="Découvrez tous nos projets réussis et les transformations que nous avons accomplies pour nos clients en Belgique. Nos réalisations incluent des photos et vidéos de chantiers."
tag="Portfolio Complet"
tagIcon={Camera}
testimonials={[
{
id: "1", name: "Rénovation Complète", role: "Maison - Bruxelles", company: "2024", rating: 5,
imageSrc: "http://img.b2bpic.net/free-photo/young-people-watching-smartphone-office_23-2147668943.jpg?_wi=2", imageAlt: "Rénovation complète intérieur maison bruxelles"},
{
id: "2", name: "Extension Résidentielle", role: "Addition 50m² - Liège", company: "2024", rating: 5,
videoSrc: "https://www.youtube.com/embed/dQw4w9WgXcQ?_wi=2", videoAriaLabel: "Vidéo extension résidentielle Liège"},
{
id: "3", name: "Terrasse Extérieure", role: "Piscine & Terrasse - Namur", company: "2023", rating: 5,
imageSrc: "http://img.b2bpic.net/free-photo/outdoor-swimming-pool_1203-2669.jpg?_wi=2", imageAlt: "Terrasse piscine extérieur namur belgique"},
{
id: "4", name: "Construction Neuve", role: "Maison moderne 150m² - Charleroi", company: "2023", rating: 5,
videoSrc: "https://www.youtube.com/embed/jNQXAC9IVRw?_wi=2", videoAriaLabel: "Vidéo construction maison neuve Charleroi"},
{
id: "5", name: "Rénovation Toiture", role: "Toiture complète - Anvers", company: "2023", rating: 5,
imageSrc: "http://img.b2bpic.net/free-photo/man-working-outdoors-high-angle_23-2149714277.jpg?_wi=2", imageAlt: "rénovation toiture ardoise anvers belgique"},
{
id: "6", name: "Aménagement Extérieur", role: "Jardin paysager - Mons", company: "2023", rating: 5,
imageSrc: "http://img.b2bpic.net/free-photo/table-chair-with-white-umbrella-outdoor-patio_74190-1917.jpg?_wi=2", imageAlt: "jardin paysager aménagement mons belgique"},
]}
gridVariant="three-columns-all-equal-width"
animationType="slide-up"
textboxLayout="default"
useInvertedBackground={false}
tagAnimation="slide-up"
/>
</div>
{/* Media Upload and Management Section */}
<div id="media-manager" data-section="media-manager">
{/* Large Format Media Gallery Section */}
<div id="media-gallery" data-section="media-gallery">
<div className="w-full py-20 px-4 md:px-8">
<div className="max-w-6xl mx-auto">
<div className="max-w-7xl mx-auto">
{/* Header */}
<div className="text-center mb-16">
<div className="inline-flex items-center gap-2 mb-4 px-4 py-2 rounded-full bg-[var(--accent)] text-[var(--background)]">
<Upload size={16} />
<span className="text-sm font-medium">Gestion des Médias</span>
<Camera size={16} />
<span className="text-sm font-medium">Galerie Projets Complétés</span>
</div>
<h2 className="text-3xl md:text-5xl font-bold mb-4 text-[var(--foreground)]">
Galerie des Projets Réalisés
Photos & Vidéos de Nos Réalisations
</h2>
<p className="text-lg text-[var(--foreground)] opacity-75 max-w-2xl mx-auto">
Téléchargez et gérez les photos et vidéos de vos projets complétés. Présentez votre portefeuille aux clients potentiels.
Découvrez nos projets complétés en grand format. Visualisez la qualité de notre travail et les transformations que nous avons accomplies pour nos clients.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{/* Upload Section */}
<div className="md:col-span-1 space-y-6">
{/* Photo Upload */}
<div className="bg-[var(--card)] rounded-2xl p-8 border border-[var(--accent)] border-opacity-20">
<h3 className="text-lg font-bold mb-4 text-[var(--foreground)] flex items-center gap-2">
<Camera size={20} />
Télécharger des Photos
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Large Format Viewer - Takes 2 columns on desktop */}
<div className="lg:col-span-2">
<div className="bg-[var(--card)] rounded-3xl p-8 border border-[var(--accent)] border-opacity-20 shadow-lg overflow-hidden">
{selectedMediaItem ? (
<div className="relative">
{/* Large Format Display */}
<div className="relative w-full bg-[var(--background)] rounded-2xl overflow-hidden">
<div className="relative w-full" style={{ paddingBottom: "75%" }}>
{selectedMediaItem.type === "photo" ? (
<img
src={selectedMediaItem.src}
alt={selectedMediaItem.name}
className="absolute inset-0 w-full h-full object-cover"
/>
) : (
<video
src={selectedMediaItem.src}
controls
className="absolute inset-0 w-full h-full object-contain bg-black"
aria-label={selectedMediaItem.name}
/>
)}
</div>
</div>
{/* Media Info */}
<div className="mt-6 p-6 bg-[var(--background)] rounded-xl border border-[var(--accent)] border-opacity-20">
<div className="flex items-start justify-between mb-4">
<div>
<div className="flex items-center gap-2 mb-2">
<span className="px-3 py-1 bg-[var(--primary-cta)] text-[var(--primary-cta-text)] text-xs font-semibold rounded-full">
{selectedMediaItem.type === "photo" ? "📷 Photo" : "🎥 Vidéo"}
</span>
</div>
<h3 className="text-xl font-bold text-[var(--foreground)]">
{selectedMediaItem.name}
</h3>
<p className="text-sm text-[var(--foreground)] opacity-60 mt-1">
{selectedMediaItem.type === "photo" ? "Photo de projet" : "Vidéo de projet"}
</p>
</div>
<button
onClick={() => handleDeleteMedia(selectedMediaItem.id)}
className="p-2 bg-red-500 hover:bg-red-600 text-white rounded-lg transition-colors"
aria-label="Supprimer ce média"
>
<Trash2 size={20} />
</button>
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center" style={{ minHeight: "600px" }}>
<div className="text-center">
<Camera size={64} className="mx-auto text-[var(--accent)] opacity-20 mb-4" />
<p className="text-[var(--foreground)] opacity-60 text-lg">
Sélectionnez une photo ou vidéo à partir de la liste ci-dessous
</p>
</div>
</div>
)}
</div>
</div>
{/* Sidebar - Media List and Upload Controls */}
<div className="lg:col-span-1 space-y-6">
{/* Upload Controls */}
<div className="bg-[var(--card)] rounded-3xl p-6 border border-[var(--accent)] border-opacity-20 shadow-lg space-y-4">
<h3 className="text-lg font-bold text-[var(--foreground)] flex items-center gap-2">
<Upload size={20} />
Ajouter Médias
</h3>
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-[var(--accent)] border-opacity-40 rounded-lg cursor-pointer hover:border-opacity-60 transition-all">
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<Upload size={24} className="text-[var(--primary-cta)] mb-2" />
<p className="text-sm text-[var(--foreground)] opacity-75 text-center">
Cliquez pour télécharger<br />
<span className="text-xs">(JPG, PNG, WebP)</span>
{/* Photo Upload */}
<label className="flex flex-col items-center justify-center w-full h-24 border-2 border-dashed border-[var(--accent)] border-opacity-40 rounded-xl cursor-pointer hover:border-opacity-60 transition-all bg-[var(--background)] bg-opacity-50">
<div className="flex flex-col items-center justify-center">
<Camera size={20} className="text-[var(--primary-cta)] mb-1" />
<p className="text-xs font-medium text-[var(--foreground)] text-center">
Ajouter Photos
</p>
</div>
<input
@@ -206,20 +242,13 @@ export default function PortfolioPage() {
aria-label="Télécharger des photos"
/>
</label>
</div>
{/* Video Upload */}
<div className="bg-[var(--card)] rounded-2xl p-8 border border-[var(--accent)] border-opacity-20">
<h3 className="text-lg font-bold mb-4 text-[var(--foreground)] flex items-center gap-2">
<Camera size={20} />
Télécharger des Vidéos
</h3>
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-[var(--accent)] border-opacity-40 rounded-lg cursor-pointer hover:border-opacity-60 transition-all">
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<Upload size={24} className="text-[var(--primary-cta)] mb-2" />
<p className="text-sm text-[var(--foreground)] opacity-75 text-center">
Cliquez pour télécharger<br />
<span className="text-xs">(MP4, WebM, OGG)</span>
{/* Video Upload */}
<label className="flex flex-col items-center justify-center w-full h-24 border-2 border-dashed border-[var(--accent)] border-opacity-40 rounded-xl cursor-pointer hover:border-opacity-60 transition-all bg-[var(--background)] bg-opacity-50">
<div className="flex flex-col items-center justify-center">
<Play size={20} className="text-[var(--primary-cta)] mb-1" />
<p className="text-xs font-medium text-[var(--foreground)] text-center">
Ajouter Vidéos
</p>
</div>
<input
@@ -231,75 +260,76 @@ export default function PortfolioPage() {
aria-label="Télécharger des vidéos"
/>
</label>
{/* Feedback Messages */}
{uploadError && (
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
<p className="text-red-800 text-xs">{uploadError}</p>
</div>
)}
{uploadSuccess && (
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
<p className="text-green-800 text-xs font-semibold"> Uploadé avec succès!</p>
</div>
)}
</div>
{/* Feedback Messages */}
{uploadError && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-800 text-sm">{uploadError}</p>
{/* Media List */}
<div className="bg-[var(--card)] rounded-3xl p-6 border border-[var(--accent)] border-opacity-20 shadow-lg">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-bold text-[var(--foreground)]">
Galerie ({uploadedMedia.length})
</h3>
{uploadedMedia.length > 0 && (
<span className="text-xs text-[var(--foreground)] opacity-60 bg-[var(--background)] px-2 py-1 rounded">
{uploadedMedia.filter((m) => m.type === "photo").length}📷 {uploadedMedia.filter((m) => m.type === "video").length}🎥
</span>
)}
</div>
)}
{uploadSuccess && (
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<p className="text-green-800 text-sm font-semibold"> Fichier uploadé avec succès!</p>
</div>
)}
</div>
{/* Media Gallery */}
<div className="md:col-span-2">
<div className="bg-[var(--card)] rounded-2xl p-8 border border-[var(--accent)] border-opacity-20">
<h3 className="text-2xl font-bold mb-6 text-[var(--foreground)]">
Galerie ({uploadedMedia.length})
</h3>
{uploadedMedia.length === 0 ? (
<div className="text-center py-12">
<Camera size={48} className="mx-auto text-[var(--accent)] opacity-30 mb-4" />
<p className="text-[var(--foreground)] opacity-60">
Aucun média léchargé. Commencez par ajouter des photos ou des vidéos de vos projets.
<Camera size={32} className="mx-auto text-[var(--accent)] opacity-20 mb-2" />
<p className="text-sm text-[var(--foreground)] opacity-60">
Aucun média ajou
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-2 max-h-96 overflow-y-auto">
{uploadedMedia.map((media) => (
<div
<button
key={media.id}
className="relative group rounded-lg overflow-hidden border border-[var(--accent)] border-opacity-20"
onClick={() => setSelectedMedia(media.id)}
className={`w-full flex items-start gap-3 p-3 rounded-lg border transition-all ${
selectedMedia === media.id
? "bg-[var(--primary-cta)] bg-opacity-10 border-[var(--primary-cta)]"
: "border-[var(--accent)] border-opacity-20 hover:border-opacity-40"
}`}
>
{media.type === "photo" ? (
<img
src={media.src}
alt={media.name}
className="w-full h-48 object-cover group-hover:scale-105 transition-transform duration-300"
/>
) : (
<video
src={media.src}
controls
className="w-full h-48 object-cover bg-black"
aria-label={media.name}
/>
)}
<div className="absolute inset-0 bg-black opacity-0 group-hover:opacity-40 transition-opacity duration-300" />
{/* Delete Button */}
<button
onClick={() => handleDeleteMedia(media.id)}
className="absolute top-2 right-2 p-2 bg-red-500 hover:bg-red-600 text-white rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300"
aria-label={`Supprimer ${media.name}`}
>
<Trash2 size={16} />
</button>
{/* File Info */}
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent p-4 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<p className="text-sm font-semibold truncate">{media.name}</p>
<p className="text-xs opacity-75">
{/* Thumbnail */}
<div className="w-12 h-12 rounded overflow-hidden flex-shrink-0 bg-[var(--background)]">
{media.type === "photo" ? (
<img
src={media.src}
alt={media.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-black bg-opacity-20">
<Play size={16} className="text-white" />
</div>
)}
</div>
{/* Info */}
<div className="min-w-0 flex-1 text-left">
<p className="text-xs font-semibold text-[var(--foreground)] truncate">
{media.name}
</p>
<p className="text-xs text-[var(--foreground)] opacity-60">
{media.type === "photo" ? "Photo" : "Vidéo"}
</p>
</div>
</div>
</button>
))}
</div>
)}