Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 55 additions & 33 deletions src/app/(frontend)/gallery/_components/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
import Image from 'next/image';
import Polaroid from './Polaroid';
import { PolaroidProps } from './Polaroid';
import { useEffect, useState } from 'react';
import { ReactElement } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { useGalleryImages } from '@/features/gallery/tanstack/useGalleryImages';
import { useState } from 'react';
import { useEffect } from 'react';

export default function Gallery() {
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(9);
const { data: polaroids, isLoading, error } = useGalleryImages();
const [isMobile, setIsMobile] = useState(false);

useEffect(() => {
const handleResize = (): void => {
Expand All @@ -20,9 +23,11 @@ export default function Gallery() {
} else if (window.innerWidth >= 768) {
setItemsPerPage(4); // md has 2x2 grid
} else {
setItemsPerPage(2); //sm has 1x2 grid
setItemsPerPage(9); //sm has 1x2 grid
}
setCurrentPage(1); // Reset to first page on resize

setIsMobile(window.innerWidth < 768);
};

window.addEventListener('resize', handleResize);
Expand All @@ -47,35 +52,61 @@ export default function Gallery() {
};

const handlePrevPage = (): void => {
setCurrentPage((prev) => Math.max(1, prev - 1));
setCurrentPage((prev: number) => Math.max(1, prev - 1));
};

const handleNextPage = (): void => {
setCurrentPage((prev) => Math.min(getTotalPages(), prev + 1));
setCurrentPage((prev: number) => Math.min(getTotalPages(), prev + 1));
};

const prevButton = (hidden: boolean = false): ReactElement => {
return (
<div>
<button
onClick={handlePrevPage}
className={
'relative py-2 text-[10vw] mr-[1rem] disabled:opacity-50 cursor-pointer hover:text-accent transition-colors ' +
`${currentPage === 1 ? 'invisible' : 'visible'}` +
`${hidden ? ' hidden' : ' block'}`
}
>
<ChevronLeft className="lg:w-20 lg:h-20 md:w-15 md:h-15 w-10 h-10" />
</button>
</div>
);
};

const nextButton = (hidden: boolean = false) => {
return (
<div>
<button
onClick={handleNextPage}
hidden={false}
className={
'relative py-2 pr-8 text-[10vw] ml-[1rem] cursor-pointer hover:text-accent transition-colors ' +
`${currentPage === getTotalPages() ? 'invisible' : 'visible'}` +
`${hidden ? ' hidden' : ' block'}`
}
>
<ChevronRight className="lg:w-20 lg:h-20 md:w-15 md:h-15 w-10 h-10" />
</button>
</div>
);
};

return (
<div className="relative ml-10 w-full ">
<div className="relative md:ml-10 w-full ">
{/* Gallery Board */}
<div className=" relative flex justify-center items-center">
<div>
<button
onClick={handlePrevPage}
className={
'relative py-2 text-[10vw] mr-[1rem] disabled:opacity-50 cursor-pointer hover:text-accent transition-colors ' +
`${currentPage === 1 ? 'invisible' : 'visible'}`
}
>
<ChevronLeft className="lg:w-20 lg:h-20 md:w-15 md:h-15 w-10 h-10" />
</button>
</div>

<div className="relative w-[60vw] h-[95vw] px-[5vw] py-[5vw] md:w-[80vw] md:h-[65vw]">
{prevButton(isMobile)}
<div
className={'relative w-[90vw] md:px-[5vw] md:py-[5vw] md:w-[80vw] md:h-[65vw]'}
>
<Image
src="/images/gallery/board.png"
alt="Gallery Board Background"
fill
className="object-fill"
className="hidden md:block object-fill"
draggable={false}
priority
/>
Expand All @@ -85,7 +116,7 @@ export default function Gallery() {
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 1 }}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[5vh] place-items-center relative z-10"
className="grid grid-cols-3 md:grid-cols-2 lg:grid-cols-3 grid-rows-3 md:grid-rows-2 lg:grid-rows-3 gap-x-[5vw] gap-y-[1vh] md:gap-y-[3vh] lg:gap-y-[2.5vh] place-items-center relative z-10"
>
{getCurrentItems().map((polaroid: PolaroidProps, index: number) => (
<motion.div
Expand All @@ -105,30 +136,21 @@ export default function Gallery() {
</motion.div>
</AnimatePresence>
</div>

<div>
<button
onClick={handleNextPage}
hidden={false}
className={
'relative py-2 pr-8 text-[10vw] ml-[1rem] disabled:opacity-50 cursor-pointer hover:text-accent transition-colors ' +
`${currentPage === getTotalPages() ? 'invisible' : 'visible'}`
}
>
<ChevronRight className="lg:w-20 lg:h-20 md:w-15 md:h-15 w-10 h-10" />
</button>
</div>
{nextButton(isMobile)}
</div>

{/* Page Indicator */}

<div className="flex justify-center items-center my-4">
{prevButton(!isMobile)}
{Array.from({ length: getTotalPages() }, (_, index) => (
<div
key={index}
className={`h-3 w-3 mx-1 rounded-full cursor-pointer ${currentPage === index + 1 ? 'bg-primary-red-400' : 'bg-gray-400'}`}
onClick={() => setCurrentPage(index + 1)}
/>
))}
{nextButton(!isMobile)}
</div>
</div>
);
Expand Down
80 changes: 39 additions & 41 deletions src/app/(frontend)/gallery/_components/Polaroid.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,69 @@
import Image from "next/image";
import Pin from "./Pin";
import {PIN_COLOURS, VARIATIONS } from "@/types/GalleryImageData";
import { GalleryImageData } from "@/types/GalleryImageData";
'use client';
import Image from 'next/image';
import Pin from './Pin';
import { PIN_COLOURS, VARIATIONS } from '@/types/GalleryImageData';
import { GalleryImageData } from '@/types/GalleryImageData';

// To change options for pinColour and variation, edit src/types/GalleryImageData.ts
export interface PolaroidProps extends GalleryImageData {
// Add additional props here if needed
}

const RANDOM_TRANSFORMS = [
'rotate-3 translate-x-1 translate-y-1',
'-rotate-2 -translate-x-1 translate-y-2',
'rotate-1 translate-x-2 -translate-y-1',
'-rotate-3 -translate-x-2 -translate-y-2',
'rotate-2 translate-x-1 -translate-y-1',
'-rotate-1 -translate-x-1 translate-y-1',
'rotate-1 translate-x-2 translate-y-1',
'-rotate-2 translate-x-1 -translate-y-2',
'rotate-3 -translate-x-1 translate-y-1',
] as const;

const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
year: 'numeric',
});
};

export default function Polaroid({
image = "/images/aboutus/AboutUsImage.jpg",
eventDate = "12/20/2015",
eventName = "TestEvent",
pinColour = 'red',
variation = 'small'
const getRandomInt = (min: number = -3, max: number = 3): number => {
return Math.floor(Math.random() * (max - min)) + min;
};

export default function Polaroid({
image = '/images/aboutus/AboutUsImage.jpg',
eventDate = '12/20/2015',
eventName = 'TestEvent',
pinColour = 'red',
variation = 'small',
}: PolaroidProps) {
// Calculates transform based on event name.
// This allows for the "random" transforms without using math.random. This prevents hydration issues.
const shift = RANDOM_TRANSFORMS[
eventName.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % RANDOM_TRANSFORMS.length
];
const variationCSS = // cannot create a new entry in GalleryImageData.ts in types because it creates a payload option for mobile size selection, which is not what we want this for.
window.innerWidth < 768
? 'min-w-[26.667vw] aspect-[1.213/1]'
: VARIATIONS[variation].dimensions;

const getTransformValue = window.innerWidth < 768 ? () => 0 : getRandomInt; // This is much cleaner and simpler than using a predetermined array of transforms
const rotate = getTransformValue();
const translateX = getTransformValue();
const translateY = getTransformValue();

return (
<div
className={`relative bg-white rounded-md drop-shadow-lg
${VARIATIONS[variation].dimensions}
${shift}`}
return (
<div
className={`relative bg-white rounded-md drop-shadow-lg ${variationCSS}`}
style={{
transform: `rotate(${rotate}deg) translateX(${translateX}px) translateY(${translateY}px)`, // Tailwind doesn't like dynamic stuff being simple so I just used inline styling cause its just like that. Type shi
}}
>
<Pin
className="absolute left-[45%] -top-9 md:-top-7 lg:-top-6"
hexPinColour={PIN_COLOURS[pinColour]}
<Pin
className="absolute hidden md:block left-[45%] -top-9 md:-top-7 lg:-top-6" // Hide pin in mobile layout
hexPinColour={PIN_COLOURS[pinColour]}
/>
<div className="flex flex-col p-3 h-full">
<div className="flex flex-col p-2 h-full rounded-xl">
<div className="relative w-full h-[90%]">
<Image
src={image}
alt={eventName}
fill
sizes={VARIATIONS[variation].imageSize}
sizes={`${VARIATIONS[variation].imageSize}`} // Image size on mobile doesnt really matter tbh, it works fine when I tested it.
className="object-cover"
draggable={false}
/>
</div>
<div className="mt-3 text-center text-[#2b2b2b] text-sm font-waytoon">
<p>{formatDate(eventDate)}</p>
<p className="ml-1">{eventName}</p>
<div className="mt-1 text-center text-[#2b2b2b] text-[5px] line-clamp-2 md:text-[12px] font-waytoon tracking-tightest overflow-hidden">
{/* Hide the event date in mobile layout */}
<a className="hidden md:block">{formatDate(eventDate)}</a> <a>{eventName}</a>
</div>
</div>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/app/(frontend)/gallery/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import Image from 'next/image';

export default function GalleryPage() {
return (
<div className="relative flex flex-col px-0 md:px-[8%] py-28 items-center min-h-screen min-w-full overflow-hidden container">
<div className="relative flex flex-col px-0 md:px-[6%] py-28 items-center md:min-h-screen min-w-screen overflow-hidden container">
<h2 className="text-primary-red px-[20vw]">Gallery</h2>
<div className="w-full mx-auto text-center text-primary-white flex flex-col items-center tracking-widest">
<div className="hidden md:block text-center text-primary-white items-center tracking-widest">
<p className="text-primary-white mb-2">View our event images here!</p>
</div>
<hr className="relative mx-auto mt-4 w-[1200px] h-px border-0 bg-white/50 mb-15" />
<hr className="relative mx-auto w-[300px] md:w-[80vw] h-px border-0 bg-primary-white md:bg-white/50 md:mt-4 mb-5 md:mb-15" />
<Gallery />

{/* Background star */}
<div className="absolute left-0 -z-10">
<div className="hidden md:block absolute left-0 -z-10">
<Image
src="/images/signup/background_star.png"
alt="background star red"
Expand All @@ -22,7 +22,7 @@ export default function GalleryPage() {
/>
</div>

<div className="absolute -right-70 -bottom-70 -z-10 object-contain">
<div className="hidden md:block absolute -right-70 -bottom-70 -z-10 object-contain">
<Image
src="/images/signup/background_star_white.png"
alt="background star red"
Expand Down
22 changes: 11 additions & 11 deletions src/types/GalleryImageData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ export const PIN_COLOURS = {
pink: '#FFC0CB',
teal: '#008080',
brown: '#A52A2A',
gray: '#808080'
gray: '#808080',
} as const;

export const VARIATIONS = {
small: {
dimensions: 'w-[42vw] aspect-[290/260] md:w-[27vw] lg:w-[17vw]',
imageSize: '260px'
dimensions: ' aspect-[290/260] md:w-[25vw] lg:w-[17vw]',
imageSize: '260px',
},
large: {
dimensions: 'w-[45vw] aspect-[370/320] md:w-[30vw] lg:w-[20vw]',
imageSize: '370px'
}
dimensions: 'aspect-[370/320] md:w-[27.5vw] lg:w-[20vw]',
imageSize: '370px',
},
} as const;

export type PinColour = keyof typeof PIN_COLOURS;
Expand All @@ -37,13 +37,13 @@ export interface GalleryImageData {
}

// Convert pin colors to Payload select options
export const PIN_COLOUR_OPTIONS = Object.keys(PIN_COLOURS).map(color => ({
export const PIN_COLOUR_OPTIONS = Object.keys(PIN_COLOURS).map((color) => ({
label: color.charAt(0).toUpperCase() + color.slice(1),
value: color
value: color,
}));

// Convert variations to Payload select options
export const VARIATION_OPTIONS = Object.keys(VARIATIONS).map(variation => ({
export const VARIATION_OPTIONS = Object.keys(VARIATIONS).map((variation) => ({
label: variation.charAt(0).toUpperCase() + variation.slice(1),
value: variation
}));
value: variation,
}));