diff --git a/app/admin/dsoc/projects/[id]/edit/page.tsx b/app/admin/dsoc/projects/[id]/edit/page.tsx index 1fc40fc..d993d04 100644 --- a/app/admin/dsoc/projects/[id]/edit/page.tsx +++ b/app/admin/dsoc/projects/[id]/edit/page.tsx @@ -17,6 +17,8 @@ import { } from "lucide-react"; import "../../../../../dsoc/styles.css"; +const GALLERY_MAX = 5; + interface MentorOption { _id: string; name: string; @@ -210,14 +212,28 @@ export default function EditProjectPage() { if (files.length === 0) return; setGalleryError(''); + + const remaining = GALLERY_MAX - formData.gallery.length; + if (remaining <= 0) { + setGalleryError(`Gallery is full. Remove an image to add a new one. Max ${GALLERY_MAX}.`); + e.target.value = ''; + return; + } + + const accepted = files.slice(0, remaining); + const dropped = files.length - accepted.length; + setGalleryUploading(true); try { - const uploaded = await Promise.all(files.map(uploadImageToCloudinary)); + const uploaded = await Promise.all(accepted.map(uploadImageToCloudinary)); setFormData((current) => ({ ...current, - gallery: [...current.gallery, ...uploaded], + gallery: [...current.gallery, ...uploaded].slice(0, GALLERY_MAX), })); + if (dropped > 0) { + setGalleryError(`Only added ${accepted.length}; gallery is capped at ${GALLERY_MAX} images.`); + } } catch (err) { console.error('Gallery upload failed:', err); setGalleryError(err instanceof Error ? err.message : 'Failed to upload one or more images'); @@ -415,17 +431,17 @@ export default function EditProjectPage() {
- Optional. Shown on the project detail page below the cover. + Optional. Shown on the project detail page as a slider. Up to {GALLERY_MAX} images.
= GALLERY_MAX} className="neo-brutal-input" /> {galleryUploading && ( diff --git a/app/admin/dsoc/projects/new/page.tsx b/app/admin/dsoc/projects/new/page.tsx index 7277822..cea2e5e 100644 --- a/app/admin/dsoc/projects/new/page.tsx +++ b/app/admin/dsoc/projects/new/page.tsx @@ -15,6 +15,8 @@ import { } from "lucide-react"; import "../../../../dsoc/styles.css"; +const GALLERY_MAX = 5; + interface MentorOption { _id: string; name: string; @@ -164,14 +166,28 @@ export default function NewProjectPage() { if (files.length === 0) return; setGalleryError(''); + + const remaining = GALLERY_MAX - formData.gallery.length; + if (remaining <= 0) { + setGalleryError(`Gallery is full. Remove an image to add a new one. Max ${GALLERY_MAX}.`); + e.target.value = ''; + return; + } + + const accepted = files.slice(0, remaining); + const dropped = files.length - accepted.length; + setGalleryUploading(true); try { - const uploaded = await Promise.all(files.map(uploadImageToCloudinary)); + const uploaded = await Promise.all(accepted.map(uploadImageToCloudinary)); setFormData((current) => ({ ...current, - gallery: [...current.gallery, ...uploaded], + gallery: [...current.gallery, ...uploaded].slice(0, GALLERY_MAX), })); + if (dropped > 0) { + setGalleryError(`Only added ${accepted.length}; gallery is capped at ${GALLERY_MAX} images.`); + } } catch (err) { console.error('Gallery upload failed:', err); setGalleryError(err instanceof Error ? err.message : 'Failed to upload one or more images'); @@ -339,17 +355,17 @@ export default function NewProjectPage() {- Optional. Shown on the project detail page below the cover. You can add multiple at once. + Optional. Shown on the project detail page as a slider. Up to {GALLERY_MAX} images.
= GALLERY_MAX} className="neo-brutal-input" /> {galleryUploading && ( diff --git a/app/dsoc/projects/[id]/gallery.css b/app/dsoc/projects/[id]/gallery.css new file mode 100644 index 0000000..9977f78 --- /dev/null +++ b/app/dsoc/projects/[id]/gallery.css @@ -0,0 +1,58 @@ +.dsoc-gallery-slider .swiper { + position: relative; +} + +.dsoc-gallery-slider .swiper-button-prev, +.dsoc-gallery-slider .swiper-button-next { + color: var(--dsoc-dark); + background: #fff; + border: 4px solid var(--dsoc-dark); + width: 44px; + height: 44px; + border-radius: 0; + box-shadow: 4px 4px 0 var(--dsoc-dark); + transition: transform 0.1s ease; +} + +.dsoc-gallery-slider .swiper-button-prev:hover, +.dsoc-gallery-slider .swiper-button-next:hover { + transform: translate(2px, 2px); + box-shadow: 2px 2px 0 var(--dsoc-dark); +} + +.dsoc-gallery-slider .swiper-button-prev::after, +.dsoc-gallery-slider .swiper-button-next::after { + font-size: 18px; + font-weight: 900; +} + +.dsoc-gallery-slider .swiper-pagination { + position: relative; + bottom: auto; + padding: 10px 0 8px; + background: var(--dsoc-dark); +} + +.dsoc-gallery-slider .swiper-pagination-bullet { + background: #fff; + opacity: 0.5; + width: 10px; + height: 10px; +} + +.dsoc-gallery-slider .swiper-pagination-bullet-active { + background: var(--dsoc-accent); + opacity: 1; +} + +@media (max-width: 640px) { + .dsoc-gallery-slider .swiper-button-prev, + .dsoc-gallery-slider .swiper-button-next { + width: 36px; + height: 36px; + } + .dsoc-gallery-slider .swiper-button-prev::after, + .dsoc-gallery-slider .swiper-button-next::after { + font-size: 14px; + } +} diff --git a/app/dsoc/projects/[id]/page.tsx b/app/dsoc/projects/[id]/page.tsx index 9470563..0a0da6c 100644 --- a/app/dsoc/projects/[id]/page.tsx +++ b/app/dsoc/projects/[id]/page.tsx @@ -20,7 +20,13 @@ import { MessageCircle, X } from "lucide-react"; +import { Swiper, SwiperSlide } from "swiper/react"; +import { Navigation, Pagination, Keyboard } from "swiper/modules"; +import "swiper/css"; +import "swiper/css/navigation"; +import "swiper/css/pagination"; import "../../styles.css"; +import "./gallery.css"; import DSOCNavbar from "../../components/DSOCNavbar"; interface Mentor { @@ -544,24 +550,36 @@ export default function ProjectDetailPage({ params }: { params: Promise<{ id: st