Merge version_6 into main #9

Merged
bender merged 1 commits from version_6 into main 2026-03-12 12:10:49 +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 } from "lucide-react";
import { useState } from "react";
export default function PortfolioPage() {
@@ -127,180 +126,188 @@ 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">
<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">Portfolio Projets</span>
</div>
<h2 className="text-3xl md:text-5xl font-bold mb-4 text-[var(--foreground)]">
Galerie des Projets Réalisés
Galerie Photos & Vidéos de Projets
</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.
Téléchargez et gérez les photos et vidéos de vos projets complétés. Présentez votre portefeuille aux clients potentiels avec une galerie professionnelle.
</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
</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>
</p>
</div>
<input
type="file"
multiple
accept="image/*"
onChange={(e) => handleMediaUpload(e, "photo")}
className="hidden"
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>
</p>
</div>
<input
type="file"
multiple
accept="video/*"
onChange={(e) => handleMediaUpload(e, "video")}
className="hidden"
aria-label="Télécharger des vidéos"
/>
</label>
</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>
</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 téléchargé. Commencez par ajouter des photos ou des vidéos de vos projets.
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{uploadedMedia.map((media) => (
<div
key={media.id}
className="relative group rounded-lg overflow-hidden border border-[var(--accent)] border-opacity-20"
>
{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">
{media.type === "photo" ? "Photo" : "Vidéo"}
</p>
</div>
<div className="grid md:grid-cols-1 gap-8">
{/* Large Media Gallery Frame */}
<div className="bg-[var(--card)] rounded-3xl p-8 md:p-12 border border-[var(--accent)] border-opacity-20 shadow-lg">
<div className="space-y-8">
{/* Upload Controls */}
<div className="grid md:grid-cols-2 gap-6">
{/* Photo Upload */}
<div>
<h3 className="text-lg font-bold mb-4 text-[var(--foreground)] flex items-center gap-2">
<Camera size={20} />
Télécharger des Photos
</h3>
<label className="flex flex-col items-center justify-center w-full h-40 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 pt-5 pb-6">
<Upload size={28} className="text-[var(--primary-cta)] mb-2" />
<p className="text-sm font-medium text-[var(--foreground)] text-center">
Cliquez pour télécharger<br />
<span className="text-xs opacity-75">(JPG, PNG, WebP - Max 50MB)</span>
</p>
</div>
))}
<input
type="file"
multiple
accept="image/*"
onChange={(e) => handleMediaUpload(e, "photo")}
className="hidden"
aria-label="Télécharger des photos"
/>
</label>
</div>
{/* Video Upload */}
<div>
<h3 className="text-lg font-bold mb-4 text-[var(--foreground)] flex items-center gap-2">
<Play size={20} />
Télécharger des Vidéos
</h3>
<label className="flex flex-col items-center justify-center w-full h-40 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 pt-5 pb-6">
<Upload size={28} className="text-[var(--primary-cta)] mb-2" />
<p className="text-sm font-medium text-[var(--foreground)] text-center">
Cliquez pour télécharger<br />
<span className="text-xs opacity-75">(MP4, WebM, OGG - Max 50MB)</span>
</p>
</div>
<input
type="file"
multiple
accept="video/*"
onChange={(e) => handleMediaUpload(e, "video")}
className="hidden"
aria-label="Télécharger des vidéos"
/>
</label>
</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>
</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>
)}
{/* Media Gallery Display */}
<div>
<div className="flex items-center justify-between mb-6">
<h3 className="text-2xl font-bold text-[var(--foreground)]">
Galerie des Projets ({uploadedMedia.length})
</h3>
{uploadedMedia.length > 0 && (
<span className="text-sm text-[var(--foreground)] opacity-60">
{uploadedMedia.filter(m => m.type === 'photo').length} photos, {uploadedMedia.filter(m => m.type === 'video').length} vidéos
</span>
)}
</div>
{uploadedMedia.length === 0 ? (
<div className="text-center py-24 bg-[var(--background)] bg-opacity-50 rounded-xl border border-[var(--accent)] border-opacity-20">
<Camera size={56} className="mx-auto text-[var(--accent)] opacity-30 mb-4" />
<p className="text-[var(--foreground)] opacity-60 text-lg">
Aucun média téléchargé pour le moment.<br />
<span className="text-sm">Commencez par ajouter des photos ou des vidéos de vos projets réussis.</span>
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{uploadedMedia.map((media) => (
<div
key={media.id}
className="relative group rounded-xl overflow-hidden border border-[var(--accent)] border-opacity-20 shadow-md hover:shadow-lg transition-all duration-300"
>
{/* Media Preview */}
<div className="relative w-full h-56 bg-[var(--background)]">
{media.type === "photo" ? (
<img
src={media.src}
alt={media.name}
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"
/>
) : (
<div className="w-full h-full flex items-center justify-center bg-black bg-opacity-20">
<video
src={media.src}
controls
className="w-full h-full object-contain"
aria-label={media.name}
/>
</div>
)}
{/* Overlay */}
<div className="absolute inset-0 bg-black opacity-0 group-hover:opacity-50 transition-opacity duration-300" />
{/* Type Badge */}
<div className="absolute top-3 left-3 px-3 py-1 bg-[var(--primary-cta)] text-[var(--primary-cta-text)] text-xs font-semibold rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-300">
{media.type === "photo" ? "📷 Photo" : "🎥 Vidéo"}
</div>
{/* Delete Button */}
<button
onClick={() => handleDeleteMedia(media.id)}
className="absolute top-3 right-3 p-2 bg-red-500 hover:bg-red-600 text-white rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 hover:scale-110"
aria-label={`Supprimer ${media.name}`}
>
<Trash2 size={16} />
</button>
</div>
{/* File Info Footer */}
<div className="bg-[var(--background)] border-t border-[var(--accent)] border-opacity-20 p-4">
<p className="text-sm font-semibold text-[var(--foreground)] truncate mb-1">
{media.name}
</p>
<p className="text-xs text-[var(--foreground)] opacity-60">
{media.type === "photo" ? "Photo de projet" : "Vidéo de projet"}
</p>
</div>
</div>
))}
</div>
)}
</div>
{/* Gallery Statistics */}
{uploadedMedia.length > 0 && (
<div className="grid grid-cols-3 gap-4 pt-8 border-t border-[var(--accent)] border-opacity-20">
<div className="text-center p-4 bg-[var(--background)] bg-opacity-50 rounded-lg">
<p className="text-2xl font-bold text-[var(--primary-cta)]">{uploadedMedia.length}</p>
<p className="text-xs text-[var(--foreground)] opacity-60 mt-1">Total Médias</p>
</div>
<div className="text-center p-4 bg-[var(--background)] bg-opacity-50 rounded-lg">
<p className="text-2xl font-bold text-[var(--primary-cta)]">{uploadedMedia.filter(m => m.type === 'photo').length}</p>
<p className="text-xs text-[var(--foreground)] opacity-60 mt-1">Photos</p>
</div>
<div className="text-center p-4 bg-[var(--background)] bg-opacity-50 rounded-lg">
<p className="text-2xl font-bold text-[var(--primary-cta)]">{uploadedMedia.filter(m => m.type === 'video').length}</p>
<p className="text-xs text-[var(--foreground)] opacity-60 mt-1">Vidéos</p>
</div>
</div>
)}
</div>
@@ -336,4 +343,4 @@ export default function PortfolioPage() {
</div>
</ThemeProvider>
);
}
}