Fix gallery mobile view issues
- Make logout button show only icon on mobile (no text) - Move upload button from top bar to sidebar menu on mobile - Fix top bar layout with proper structure: - Logo on left - Gallery title centered - Event date and expiration date shown below title on mobile - Improve responsive design for header elements - Ensure upload button only appears in menu when uploads are enabled 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
# TODO - Open Items Before Release
|
||||||
|
|
||||||
|
## Priority Items
|
||||||
|
|
||||||
|
- [ ] **Gallery Mobile View**
|
||||||
|
- Logout button should only show logo icon (no text)
|
||||||
|
- If photo upload is enabled, move upload button inside menu (not on top bar)
|
||||||
|
- Top bar should show: logo (left), gallery title (center), event date + expiration date
|
||||||
|
|
||||||
|
- [ ] **Gallery Preview**
|
||||||
|
- Preview should correctly reflect the selected grid layout style
|
||||||
|
- Add grid style selector above current top bar
|
||||||
|
- Selector should match the style of event template settings grid selector
|
||||||
|
|
||||||
|
- [ ] **Hero Grid Layout**
|
||||||
|
- Top bar: only menu and logout buttons
|
||||||
|
- Title + logo displayed centered on hero photo
|
||||||
|
- Event date and expiration date also on hero photo
|
||||||
|
- No logo/title in top bar
|
||||||
|
|
||||||
|
- [ ] **Logo Testing** - Test new PicPeak logos across all grid styles
|
||||||
|
|
||||||
|
- [ ] **Welcome Message**
|
||||||
|
- Add welcome message to email template when creating new event
|
||||||
|
- Use as personal message in the email
|
||||||
|
|
||||||
|
- [ ] **Gallery Upload Function**
|
||||||
|
- Fix scrolling in upload popup when multiple images selected
|
||||||
|
- Save/Cancel buttons unreachable due to incorrect scroll formatting
|
||||||
|
|
||||||
|
- [ ] **Watermarks** - Test watermark functionality, styling, and image application
|
||||||
|
|
||||||
|
- [ ] **Dashboard Activities** - Remove "show all" link from latest activities widget
|
||||||
|
|
||||||
|
- [ ] **Security Audit** - Perform security review and code audit
|
||||||
|
|
||||||
|
- [ ] **Drone CI/CD** - Update drone.yaml configuration
|
||||||
|
|
||||||
|
- [ ] **Version Management** - Implement automatic version updates on commits/builds
|
||||||
|
|
||||||
|
## Completed Items
|
||||||
|
|
||||||
|
_(Move completed items here with date)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Last updated: 2025-07-10
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import { Button } from '../common';
|
||||||
|
import { PhotoUpload } from './PhotoUpload';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
interface PhotoUploadModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
eventId: number;
|
||||||
|
onUploadComplete?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PhotoUploadModal: React.FC<PhotoUploadModalProps> = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
eventId,
|
||||||
|
onUploadComplete
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const handleUploadComplete = () => {
|
||||||
|
if (onUploadComplete) {
|
||||||
|
onUploadComplete();
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white rounded-lg shadow-xl w-full max-w-2xl flex flex-col max-h-[90vh]">
|
||||||
|
{/* Fixed Header */}
|
||||||
|
<div className="flex items-center justify-between p-6 border-b border-neutral-200">
|
||||||
|
<h2 className="text-xl font-semibold text-neutral-900">{t('events.uploadPhotos')}</h2>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={onClose}
|
||||||
|
className="!p-1"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scrollable Content */}
|
||||||
|
<div className="flex-1 overflow-y-auto p-6">
|
||||||
|
<PhotoUpload
|
||||||
|
eventId={eventId}
|
||||||
|
onUploadComplete={handleUploadComplete}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PhotoUploadModal.displayName = 'PhotoUploadModal';
|
||||||
@@ -20,3 +20,4 @@ export { ThemeCustomizerEnhanced } from './ThemeCustomizerEnhanced';
|
|||||||
export { ThemeDisplay } from './ThemeDisplay';
|
export { ThemeDisplay } from './ThemeDisplay';
|
||||||
export { ThemeEditorModal } from './ThemeEditorModal';
|
export { ThemeEditorModal } from './ThemeEditorModal';
|
||||||
export { HeroPhotoSelector } from './HeroPhotoSelector';
|
export { HeroPhotoSelector } from './HeroPhotoSelector';
|
||||||
|
export { PhotoUploadModal } from './PhotoUploadModal';
|
||||||
@@ -91,8 +91,9 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
leftIcon={<LogOut className="w-4 h-4" />}
|
leftIcon={<LogOut className="w-4 h-4" />}
|
||||||
onClick={onLogout}
|
onClick={onLogout}
|
||||||
|
className="sm:min-w-0"
|
||||||
>
|
>
|
||||||
{t('common.logout')}
|
<span className="hidden sm:inline">{t('common.logout')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -101,17 +102,12 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* For grid and hero layouts - everything in one bar */}
|
{/* For grid layout - everything in one bar */}
|
||||||
{(!isNonGridLayout || theme.galleryLayout === 'hero') && (
|
{!isNonGridLayout && theme.galleryLayout !== 'hero' && (
|
||||||
<div className="container py-3">
|
<div className="container py-3">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-2 sm:gap-4">
|
||||||
{/* Left side - Logo, Title, and Dates */}
|
{/* Left side - Logo and menu button on mobile */}
|
||||||
<div className="flex items-center gap-4 flex-1 min-w-0">
|
<div className="flex items-center gap-2 sm:gap-4 flex-shrink-0">
|
||||||
{/* Menu button */}
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
{headerExtra}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Logo - Show custom logo or fallback to PicPeak logo */}
|
{/* Logo - Show custom logo or fallback to PicPeak logo */}
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<img
|
<img
|
||||||
@@ -120,20 +116,26 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|||||||
'/picpeak-logo-transparent.png'
|
'/picpeak-logo-transparent.png'
|
||||||
}
|
}
|
||||||
alt={brandingSettings?.company_name || 'PicPeak'}
|
alt={brandingSettings?.company_name || 'PicPeak'}
|
||||||
className="h-10 sm:h-12 w-auto object-contain"
|
className="h-8 sm:h-10 lg:h-12 w-auto object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Event info */}
|
{/* Menu button on mobile */}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-shrink-0 sm:hidden">
|
||||||
|
{headerExtra}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Center - Event info */}
|
||||||
|
<div className="flex-1 min-w-0 text-center sm:text-left">
|
||||||
<h1
|
<h1
|
||||||
className="text-lg sm:text-xl lg:text-2xl font-bold text-neutral-900 leading-tight truncate"
|
className="text-base sm:text-lg lg:text-xl font-bold text-neutral-900 leading-tight truncate"
|
||||||
style={{ fontFamily: headingFontFamily }}
|
style={{ fontFamily: headingFontFamily }}
|
||||||
>
|
>
|
||||||
{event.event_name}
|
{event.event_name}
|
||||||
</h1>
|
</h1>
|
||||||
{(event.event_date || event.expires_at) && (
|
{(event.event_date || event.expires_at) && (
|
||||||
<div className="flex flex-wrap gap-x-3 gap-y-1 mt-1 text-xs sm:text-sm text-neutral-600">
|
<div className="hidden sm:flex flex-wrap gap-x-3 gap-y-1 mt-1 text-xs sm:text-sm text-neutral-600">
|
||||||
{event.event_date && (
|
{event.event_date && (
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
<Calendar className="w-3 h-3 sm:w-4 sm:h-4 mr-1 flex-shrink-0" />
|
<Calendar className="w-3 h-3 sm:w-4 sm:h-4 mr-1 flex-shrink-0" />
|
||||||
@@ -149,6 +151,71 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Right side - Action buttons */}
|
||||||
|
<div className="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||||
|
{/* Menu button on desktop */}
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
{headerExtra}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Download all button - hidden on mobile when sidebar is shown */}
|
||||||
|
{showDownloadAll && onDownloadAll && (
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<Download className="w-4 h-4" />}
|
||||||
|
onClick={onDownloadAll}
|
||||||
|
isLoading={isDownloading}
|
||||||
|
className="hidden sm:flex"
|
||||||
|
>
|
||||||
|
<span className="hidden sm:inline">{t('gallery.downloadAll')}</span>
|
||||||
|
<span className="sm:hidden">{t('common.download')}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Logout button */}
|
||||||
|
{showLogout && onLogout && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<LogOut className="w-4 h-4" />}
|
||||||
|
onClick={onLogout}
|
||||||
|
className="sm:min-w-0"
|
||||||
|
>
|
||||||
|
<span className="hidden sm:inline">{t('common.logout')}</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile dates row */}
|
||||||
|
{(event.event_date || event.expires_at) && (
|
||||||
|
<div className="flex sm:hidden justify-center gap-x-3 mt-2 text-xs text-neutral-600">
|
||||||
|
{event.event_date && (
|
||||||
|
<span className="flex items-center">
|
||||||
|
<Calendar className="w-3 h-3 mr-1 flex-shrink-0" />
|
||||||
|
<span>{format(parseISO(event.event_date), 'PP')}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{event.expires_at && (
|
||||||
|
<span className="flex items-center">
|
||||||
|
<Clock className="w-3 h-3 mr-1 flex-shrink-0" />
|
||||||
|
<span>{t('gallery.expires')} {format(parseISO(event.expires_at), 'PP')}</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* For hero layout - minimal header with just menu and logout */}
|
||||||
|
{theme.galleryLayout === 'hero' && (
|
||||||
|
<div className="container py-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{/* Left side - Menu button */}
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
{headerExtra}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right side - Action buttons */}
|
{/* Right side - Action buttons */}
|
||||||
@@ -174,8 +241,9 @@ export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
leftIcon={<LogOut className="w-4 h-4" />}
|
leftIcon={<LogOut className="w-4 h-4" />}
|
||||||
onClick={onLogout}
|
onClick={onLogout}
|
||||||
|
className="sm:min-w-0"
|
||||||
>
|
>
|
||||||
{t('common.logout')}
|
<span className="hidden sm:inline">{t('common.logout')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { X, Download, Filter, SortAsc, Search, Calendar, Type, HardDrive, Check } from 'lucide-react';
|
import { X, Download, Filter, SortAsc, Search, Calendar, Type, HardDrive, Check, Upload } from 'lucide-react';
|
||||||
import { Button } from '../common';
|
import { Button } from '../common';
|
||||||
import { PhotoCategory } from '../../types';
|
import { PhotoCategory } from '../../types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -24,6 +24,8 @@ interface GallerySidebarProps {
|
|||||||
totalPhotos: number;
|
totalPhotos: number;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
galleryLayout?: string;
|
galleryLayout?: string;
|
||||||
|
allowUploads?: boolean;
|
||||||
|
onUploadClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
||||||
@@ -45,7 +47,9 @@ export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
|||||||
photoCounts = {},
|
photoCounts = {},
|
||||||
totalPhotos,
|
totalPhotos,
|
||||||
isMobile,
|
isMobile,
|
||||||
galleryLayout
|
galleryLayout,
|
||||||
|
allowUploads,
|
||||||
|
onUploadClick
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -113,6 +117,24 @@ export const GallerySidebar: React.FC<GallerySidebarProps> = ({
|
|||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
{/* Upload Section - Only show on mobile when uploads are allowed */}
|
||||||
|
{isMobile && allowUploads && onUploadClick && (
|
||||||
|
<div className="p-4 border-b border-neutral-200">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<Upload className="w-4 h-4" />}
|
||||||
|
onClick={() => {
|
||||||
|
onUploadClick();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{t('upload.uploadPhotos')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Search Section - Hidden for carousel layout */}
|
{/* Search Section - Hidden for carousel layout */}
|
||||||
{galleryLayout !== 'carousel' && (
|
{galleryLayout !== 'carousel' && (
|
||||||
<div className="p-4 border-b border-neutral-200">
|
<div className="p-4 border-b border-neutral-200">
|
||||||
|
|||||||
@@ -307,6 +307,8 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
totalPhotos={data?.photos.length || 0}
|
totalPhotos={data?.photos.length || 0}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
galleryLayout={theme.galleryLayout}
|
galleryLayout={theme.galleryLayout}
|
||||||
|
allowUploads={event.allow_user_uploads}
|
||||||
|
onUploadClick={() => setShowUploadModal(true)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -331,7 +333,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
aria-label={t('gallery.toggleMenu')}
|
aria-label={t('gallery.toggleMenu')}
|
||||||
>
|
>
|
||||||
{t('common.menu')}
|
<span className="hidden sm:inline">{t('common.menu')}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -342,7 +344,23 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.allow_user_uploads) {
|
// Upload button only on desktop when sidebar is shown
|
||||||
|
if (event.allow_user_uploads && showSidebar && !isMobile) {
|
||||||
|
items.push(
|
||||||
|
<Button
|
||||||
|
key="upload-button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<Upload className="w-4 h-4" />}
|
||||||
|
onClick={() => setShowUploadModal(true)}
|
||||||
|
>
|
||||||
|
{t('upload.uploadPhotos')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload button for non-sidebar layouts
|
||||||
|
if (event.allow_user_uploads && !showSidebar) {
|
||||||
items.push(
|
items.push(
|
||||||
<Button
|
<Button
|
||||||
key="upload-button"
|
key="upload-button"
|
||||||
@@ -396,6 +414,8 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event }) => {
|
|||||||
showSelectionControls={!showSidebar}
|
showSelectionControls={!showSidebar}
|
||||||
eventName={event.event_name}
|
eventName={event.event_name}
|
||||||
eventLogo={brandingSettings?.logo_url}
|
eventLogo={brandingSettings?.logo_url}
|
||||||
|
eventDate={event.event_date}
|
||||||
|
expiresAt={event.expires_at}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ interface PhotoGridWithLayoutsProps {
|
|||||||
showSelectionControls?: boolean;
|
showSelectionControls?: boolean;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventLogo?: string | null;
|
eventLogo?: string | null;
|
||||||
|
eventDate?: string;
|
||||||
|
expiresAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
||||||
@@ -44,7 +46,9 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
|||||||
onToggleSelectionMode: parentToggleSelectionMode,
|
onToggleSelectionMode: parentToggleSelectionMode,
|
||||||
showSelectionControls = true,
|
showSelectionControls = true,
|
||||||
eventName,
|
eventName,
|
||||||
eventLogo
|
eventLogo,
|
||||||
|
eventDate,
|
||||||
|
expiresAt
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -160,6 +164,8 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
|||||||
onPhotoSelect: handlePhotoSelect,
|
onPhotoSelect: handlePhotoSelect,
|
||||||
eventName,
|
eventName,
|
||||||
eventLogo,
|
eventLogo,
|
||||||
|
eventDate,
|
||||||
|
expiresAt,
|
||||||
};
|
};
|
||||||
|
|
||||||
let LayoutComponent;
|
let LayoutComponent;
|
||||||
|
|||||||
@@ -113,11 +113,10 @@ export const UserPhotoUpload: React.FC<UserPhotoUploadProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-end sm:items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-end sm:items-center justify-center z-50 p-0 sm:p-4">
|
||||||
<Card className="w-full sm:max-w-2xl max-h-[100vh] sm:max-h-[90vh] overflow-hidden sm:mx-4 rounded-t-2xl sm:rounded-2xl">
|
<div className="w-full sm:max-w-2xl bg-white flex flex-col max-h-[100vh] sm:max-h-[90vh] rounded-t-2xl sm:rounded-2xl shadow-xl">
|
||||||
<CardContent className="p-0">
|
{/* Fixed Header */}
|
||||||
{/* Header */}
|
<div className="flex items-center justify-between p-4 sm:p-6 border-b border-neutral-200 flex-shrink-0">
|
||||||
<div className="flex items-center justify-between p-4 sm:p-6 border-b border-neutral-200">
|
|
||||||
<h2 className="text-lg sm:text-xl font-semibold text-neutral-900">{t('upload.uploadPhotos')}</h2>
|
<h2 className="text-lg sm:text-xl font-semibold text-neutral-900">{t('upload.uploadPhotos')}</h2>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -127,8 +126,8 @@ export const UserPhotoUpload: React.FC<UserPhotoUploadProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Scrollable Content */}
|
||||||
<div className="p-4 sm:p-6 overflow-y-auto" style={{ maxHeight: 'calc(100vh - 160px)', minHeight: '300px' }}>
|
<div className="flex-1 p-4 sm:p-6 overflow-y-auto min-h-0">
|
||||||
{/* Upload Area */}
|
{/* Upload Area */}
|
||||||
<div className="mb-4 sm:mb-6">
|
<div className="mb-4 sm:mb-6">
|
||||||
<label className="block">
|
<label className="block">
|
||||||
@@ -201,8 +200,8 @@ export const UserPhotoUpload: React.FC<UserPhotoUploadProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Fixed Footer */}
|
||||||
<div className="flex items-center justify-end gap-2 sm:gap-3 p-4 sm:p-6 border-t border-neutral-200 bg-white">
|
<div className="flex items-center justify-end gap-2 sm:gap-3 p-4 sm:p-6 border-t border-neutral-200 bg-white flex-shrink-0">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -221,8 +220,7 @@ export const UserPhotoUpload: React.FC<UserPhotoUploadProps> = ({
|
|||||||
{uploading ? t('upload.uploading') : t('common.upload')} ({files.length})
|
{uploading ? t('upload.uploading') : t('common.upload')} ({files.length})
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export interface BaseGalleryLayoutProps {
|
|||||||
onPhotoSelect?: (photoId: number) => void;
|
onPhotoSelect?: (photoId: number) => void;
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventLogo?: string | null;
|
eventLogo?: string | null;
|
||||||
|
eventDate?: string;
|
||||||
|
expiresAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseGalleryLayout<T extends BaseGalleryLayoutProps = BaseGalleryLayoutProps> extends React.Component<T> {
|
export abstract class BaseGalleryLayout<T extends BaseGalleryLayoutProps = BaseGalleryLayoutProps> extends React.Component<T> {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Download, Maximize2, Check, ChevronDown } from 'lucide-react';
|
import { Download, Maximize2, Check, ChevronDown, Calendar, Clock } from 'lucide-react';
|
||||||
|
import { parseISO } from 'date-fns';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useLocalizedDate } from '../../../hooks/useLocalizedDate';
|
||||||
import { useTheme } from '../../../contexts/ThemeContext';
|
import { useTheme } from '../../../contexts/ThemeContext';
|
||||||
import { AuthenticatedImage } from '../../common';
|
import { AuthenticatedImage } from '../../common';
|
||||||
import type { BaseGalleryLayoutProps } from './BaseGalleryLayout';
|
import type { BaseGalleryLayoutProps } from './BaseGalleryLayout';
|
||||||
@@ -8,6 +11,8 @@ import type { Photo } from '../../../types';
|
|||||||
interface HeroGalleryLayoutProps extends BaseGalleryLayoutProps {
|
interface HeroGalleryLayoutProps extends BaseGalleryLayoutProps {
|
||||||
eventName?: string;
|
eventName?: string;
|
||||||
eventLogo?: string | null;
|
eventLogo?: string | null;
|
||||||
|
eventDate?: string;
|
||||||
|
expiresAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeroGalleryLayout: React.FC<HeroGalleryLayoutProps> = ({
|
export const HeroGalleryLayout: React.FC<HeroGalleryLayoutProps> = ({
|
||||||
@@ -18,8 +23,12 @@ export const HeroGalleryLayout: React.FC<HeroGalleryLayoutProps> = ({
|
|||||||
isSelectionMode = false,
|
isSelectionMode = false,
|
||||||
onPhotoSelect,
|
onPhotoSelect,
|
||||||
eventName,
|
eventName,
|
||||||
eventLogo
|
eventLogo,
|
||||||
|
eventDate,
|
||||||
|
expiresAt
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { format } = useLocalizedDate();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [heroPhoto, setHeroPhoto] = useState<Photo | null>(null);
|
const [heroPhoto, setHeroPhoto] = useState<Photo | null>(null);
|
||||||
const gallerySettings = theme.gallerySettings || {};
|
const gallerySettings = theme.gallerySettings || {};
|
||||||
@@ -72,10 +81,28 @@ export const HeroGalleryLayout: React.FC<HeroGalleryLayoutProps> = ({
|
|||||||
|
|
||||||
{/* Event Title */}
|
{/* Event Title */}
|
||||||
{eventName && (
|
{eventName && (
|
||||||
<h1 className="text-3xl sm:text-4xl lg:text-5xl xl:text-6xl font-bold text-white drop-shadow-lg">
|
<h1 className="text-3xl sm:text-4xl lg:text-5xl xl:text-6xl font-bold text-white drop-shadow-lg mb-4">
|
||||||
{eventName}
|
{eventName}
|
||||||
</h1>
|
</h1>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Event Dates */}
|
||||||
|
{(eventDate || expiresAt) && (
|
||||||
|
<div className="flex flex-wrap items-center justify-center gap-4 sm:gap-6 text-white/90">
|
||||||
|
{eventDate && (
|
||||||
|
<span className="flex items-center text-lg sm:text-xl">
|
||||||
|
<Calendar className="w-5 h-5 sm:w-6 sm:h-6 mr-2" />
|
||||||
|
{format(parseISO(eventDate), 'PP')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{expiresAt && (
|
||||||
|
<span className="flex items-center text-lg sm:text-xl">
|
||||||
|
<Clock className="w-5 h-5 sm:w-6 sm:h-6 mr-2" />
|
||||||
|
{t('gallery.expires')} {format(parseISO(expiresAt), 'PP')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { toast } from 'react-toastify';
|
|||||||
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
|
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
|
||||||
|
|
||||||
import { Button, Input, Card, Loading } from '../../components/common';
|
import { Button, Input, Card, Loading } from '../../components/common';
|
||||||
import { PhotoUpload, EventCategoryManager, AdminPhotoGrid, AdminPhotoViewer, PhotoFilters, PasswordResetModal, ThemeDisplay, ThemeCustomizerEnhanced, ThemeEditorModal, HeroPhotoSelector } from '../../components/admin';
|
import { PhotoUpload, EventCategoryManager, AdminPhotoGrid, AdminPhotoViewer, PhotoFilters, PasswordResetModal, ThemeDisplay, ThemeCustomizerEnhanced, ThemeEditorModal, HeroPhotoSelector, PhotoUploadModal } from '../../components/admin';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { eventsService } from '../../services/events.service';
|
import { eventsService } from '../../services/events.service';
|
||||||
import { archiveService } from '../../services/archive.service';
|
import { archiveService } from '../../services/archive.service';
|
||||||
@@ -887,31 +887,18 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
{/* Photos Tab */}
|
{/* Photos Tab */}
|
||||||
{activeTab === 'photos' && (
|
{activeTab === 'photos' && (
|
||||||
<div>
|
<div>
|
||||||
{/* Photo Upload */}
|
{/* Photo Upload Modal */}
|
||||||
{showPhotoUpload && (
|
<PhotoUploadModal
|
||||||
<Card padding="md" className="mb-6">
|
isOpen={showPhotoUpload}
|
||||||
<div className="flex items-center justify-between mb-4">
|
onClose={() => setShowPhotoUpload(false)}
|
||||||
<h2 className="text-lg font-semibold text-neutral-900">{t('events.uploadPhotos')}</h2>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() => setShowPhotoUpload(false)}
|
|
||||||
>
|
|
||||||
<X className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<PhotoUpload
|
|
||||||
eventId={parseInt(id!)}
|
eventId={parseInt(id!)}
|
||||||
onUploadComplete={() => {
|
onUploadComplete={() => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['admin-event', id] });
|
queryClient.invalidateQueries({ queryKey: ['admin-event', id] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['admin-event-photos', id] });
|
queryClient.invalidateQueries({ queryKey: ['admin-event-photos', id] });
|
||||||
toast.success(t('toast.uploadSuccess'));
|
toast.success(t('toast.uploadSuccess'));
|
||||||
setShowPhotoUpload(false);
|
|
||||||
refetchPhotos();
|
refetchPhotos();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Photo Filters */}
|
{/* Photo Filters */}
|
||||||
<PhotoFilters
|
<PhotoFilters
|
||||||
@@ -926,7 +913,6 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Actions Bar */}
|
{/* Actions Bar */}
|
||||||
{!showPhotoUpload && (
|
|
||||||
<div className="mb-4 flex justify-between items-center">
|
<div className="mb-4 flex justify-between items-center">
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
@@ -937,7 +923,6 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
{t('events.uploadPhotos')}
|
{t('events.uploadPhotos')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Photo Grid */}
|
{/* Photo Grid */}
|
||||||
{photosLoading ? (
|
{photosLoading ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user