5 Commits

Author SHA1 Message Date
fc281504eb Update src/app/portfolio/page.tsx 2026-03-12 12:37:58 +00:00
cb70d0e5e9 Update src/app/portfolio/page.tsx 2026-03-12 12:37:12 +00:00
c1e0019a5e Merge version_7 into main
Merge version_7 into main
2026-03-12 12:26:43 +00:00
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

View File

@@ -3,9 +3,10 @@
import Link from "next/link";
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
import NavbarStyleCentered from "@/components/navbar/NavbarStyleCentered/NavbarStyleCentered";
import FeatureCardOne from "@/components/sections/feature/FeatureCardOne";
import ContactText from "@/components/sections/contact/ContactText";
import FooterBaseCard from "@/components/sections/footer/FooterBaseCard";
import { Camera, Upload, Trash2, Play } from "lucide-react";
import { Camera, Upload, Trash2, Play, Maximize2 } from "lucide-react";
import { useState } from "react";
export default function PortfolioPage() {
@@ -103,6 +104,13 @@ export default function PortfolioPage() {
setUploadedMedia((prev) => prev.filter((media) => media.id !== id));
};
const featureItems = uploadedMedia.map((media) => ({
id: media.id,
title: media.name,
description: media.type === "photo" ? "📷 Photo du projet" : "🎥 Vidéo du projet", imageSrc: media.src,
imageAlt: media.name,
}));
return (
<ThemeProvider
defaultButtonVariant="directional-hover"
@@ -126,191 +134,125 @@ export default function PortfolioPage() {
/>
</div>
{/* Media Upload and Management Section */}
<div id="media-manager" data-section="media-manager">
{/* Media Upload Section */}
<div id="media-upload" data-section="media-upload">
<div className="w-full py-20 px-4 md:px-8">
<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)]">
<Camera size={16} />
<span className="text-sm font-medium">Portfolio Projets</span>
<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 Photos & Vidéos de Projets
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 avec une galerie professionnelle.
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-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>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Upload Controls - Sidebar */}
<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>
{/* 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>
{/* 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>
</div>
<input
type="file"
multiple
accept="image/*"
onChange={(e) => handleMediaUpload(e, "photo")}
className="hidden"
aria-label="Télécharger des photos"
/>
</label>
{/* 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
type="file"
multiple
accept="video/*"
onChange={(e) => handleMediaUpload(e, "video")}
className="hidden"
aria-label="Télécharger des vidéos"
/>
</label>
{/* 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 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-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 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>
{/* Media Count */}
<div className="bg-[var(--card)] rounded-3xl p-6 border border-[var(--accent)] border-opacity-20 shadow-lg">
<div className="text-center">
<p className="text-3xl font-bold text-[var(--primary-cta)] mb-2">
{uploadedMedia.length}
</p>
<p className="text-sm text-[var(--foreground)] opacity-60">
{uploadedMedia.length === 1 ? "Média" : "Médias"} ajoutés
</p>
{uploadedMedia.length > 0 && (
<p className="text-xs text-[var(--foreground)] opacity-40 mt-3">
{uploadedMedia.filter((m) => m.type === "photo").length}📷 {uploadedMedia.filter((m) => m.type === "video").length}🎥
</p>
)}
</div>
</div>
</div>
{/* Feature Card Gallery - Takes 2 columns */}
<div className="lg:col-span-2">
{featureItems.length > 0 ? (
<FeatureCardOne
features={featureItems}
title="Vos Réalisations"
description="Galerie complète de vos projets de construction et rénovation"
tag="Projets"
animationType="slide-up"
textboxLayout="default"
useInvertedBackground={false}
gridVariant="uniform-all-items-equal"
/>
) : (
<div className="bg-[var(--card)] rounded-3xl p-12 border border-[var(--accent)] border-opacity-20 shadow-lg text-center">
<Camera size={48} className="mx-auto text-[var(--accent)] opacity-20 mb-4" />
<p className="text-[var(--foreground)] opacity-60 text-lg">
Aucun média ajouté pour le moment
</p>
<p className="text-[var(--foreground)] opacity-40 text-sm mt-2">
Commencez à ajouter des photos et vidéos de vos projets ci-contre
</p>
</div>
)}
</div>
</div>
</div>
@@ -343,4 +285,4 @@ export default function PortfolioPage() {
</div>
</ThemeProvider>
);
}
}