d8fb4c9565
GalleryLayout improvements: - Stack header elements vertically on mobile - Hide company branding on small screens - Make dates stack vertically - Responsive text sizes and padding - Icon-only logout button on mobile - Improved button layout with proper wrapping PhotoFilterBar improvements: - Stack search and sort vertically on mobile - Full-width sort button on mobile - Horizontally scrollable category filters - Responsive text sizes - Mobile-friendly dropdown positioning PhotoGrid improvements: - Responsive selection controls - Touch-friendly photo overlays - Larger selection checkboxes on mobile - Improved button text for small screens - Responsive gaps between photos Gallery grid CSS: - Smaller gaps on mobile devices - Maintains 2 columns on smallest screens GalleryPage login: - Responsive padding and margins - Smaller text and icon sizes on mobile - Better card spacing - Responsive form elements UserPhotoUpload modal: - Full-screen modal on mobile (slides up from bottom) - Responsive padding and text sizes - Mobile-optimized upload area - Sticky footer on mobile 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
176 lines
6.8 KiB
TypeScript
176 lines
6.8 KiB
TypeScript
import React from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { Calendar, Clock, Download, LogOut } from 'lucide-react';
|
|
import { parseISO } from 'date-fns';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useLocalizedDate } from '../../hooks/useLocalizedDate';
|
|
import { Button } from '../common';
|
|
import { DynamicFavicon } from '../common/DynamicFavicon';
|
|
|
|
interface GalleryLayoutProps {
|
|
event: {
|
|
event_name: string;
|
|
event_type?: string;
|
|
event_date?: string;
|
|
expires_at?: string;
|
|
};
|
|
brandingSettings?: {
|
|
company_name?: string;
|
|
company_tagline?: string;
|
|
support_email?: string;
|
|
footer_text?: string;
|
|
favicon_url?: string;
|
|
logo_url?: string;
|
|
};
|
|
showLogout?: boolean;
|
|
onLogout?: () => void;
|
|
showDownloadAll?: boolean;
|
|
onDownloadAll?: () => void;
|
|
isDownloading?: boolean;
|
|
headerExtra?: React.ReactNode;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export const GalleryLayout: React.FC<GalleryLayoutProps> = ({
|
|
event,
|
|
brandingSettings,
|
|
showLogout = false,
|
|
onLogout,
|
|
showDownloadAll = false,
|
|
onDownloadAll,
|
|
isDownloading = false,
|
|
headerExtra,
|
|
children,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const { format } = useLocalizedDate();
|
|
return (
|
|
<div className="min-h-screen bg-neutral-50">
|
|
{/* Dynamic Favicon */}
|
|
<DynamicFavicon />
|
|
|
|
{/* Header */}
|
|
<header className="bg-white border-b border-neutral-200 sticky top-0 z-40">
|
|
<div className="container py-3 sm:py-4">
|
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
|
<div className="flex items-start sm:items-center gap-3 sm:gap-4">
|
|
{/* Company logo */}
|
|
{brandingSettings?.logo_url && (
|
|
<div className="hidden sm:block pr-4 border-r border-neutral-200 flex-shrink-0">
|
|
<img
|
|
src={`${import.meta.env.VITE_API_URL || 'http://localhost:3001'}${brandingSettings.logo_url}`}
|
|
alt={brandingSettings.company_name || 'Company Logo'}
|
|
className="h-10 sm:h-12 w-auto object-contain"
|
|
/>
|
|
</div>
|
|
)}
|
|
{/* Company branding */}
|
|
{!brandingSettings?.logo_url && brandingSettings?.company_name && (
|
|
<div className="hidden sm:block pr-4 border-r border-neutral-200 flex-shrink-0">
|
|
<h2 className="text-base sm:text-lg font-semibold text-neutral-800">{brandingSettings.company_name}</h2>
|
|
{brandingSettings.company_tagline && (
|
|
<p className="hidden lg:block text-xs text-neutral-600">{brandingSettings.company_tagline}</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
<div className="flex-1">
|
|
<h1 className="text-xl sm:text-2xl font-bold text-neutral-900 leading-tight">{event.event_name}</h1>
|
|
{(event.event_date || event.expires_at) && (
|
|
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-4 mt-1 text-xs sm:text-sm text-neutral-600">
|
|
{event.event_date && (
|
|
<span className="flex items-center">
|
|
<Calendar className="w-3 h-3 sm:w-4 sm:h-4 mr-1" />
|
|
<span className="truncate">{format(parseISO(event.event_date), 'PP')}</span>
|
|
</span>
|
|
)}
|
|
{event.expires_at && (
|
|
<span className="flex items-center">
|
|
<Clock className="w-3 h-3 sm:w-4 sm:h-4 mr-1" />
|
|
<span className="truncate">{t('gallery.expires')} {format(parseISO(event.expires_at), 'PP')}</span>
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
|
|
{headerExtra}
|
|
{showDownloadAll && onDownloadAll && (
|
|
<Button
|
|
variant="primary"
|
|
size="md"
|
|
leftIcon={<Download className="w-4 h-4" />}
|
|
onClick={onDownloadAll}
|
|
isLoading={isDownloading}
|
|
className="flex-1 sm:flex-initial text-sm sm:text-base"
|
|
>
|
|
<span className="hidden sm:inline">{t('gallery.downloadAll')}</span>
|
|
<span className="sm:hidden">{t('common.downloadAll')}</span>
|
|
</Button>
|
|
)}
|
|
{showLogout && onLogout && (
|
|
<Button
|
|
variant="outline"
|
|
size="md"
|
|
leftIcon={<LogOut className="w-4 h-4" />}
|
|
onClick={onLogout}
|
|
className="text-sm sm:text-base"
|
|
>
|
|
<span className="hidden sm:inline">{t('common.logout')}</span>
|
|
<span className="sm:hidden"><LogOut className="w-4 h-4" /></span>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Main Content */}
|
|
<main className="container">{children}</main>
|
|
|
|
{/* Footer */}
|
|
<footer className="mt-8 sm:mt-12 py-6 sm:py-8 border-t border-neutral-200">
|
|
<div className="container text-center px-4">
|
|
{brandingSettings?.support_email && (
|
|
<p className="text-xs sm:text-sm text-neutral-600 mb-2">
|
|
{t('gallery.needHelp')}{' '}
|
|
<a
|
|
href={`mailto:${brandingSettings.support_email}`}
|
|
className="text-primary-600 hover:text-primary-700 break-all"
|
|
>
|
|
{brandingSettings.support_email}
|
|
</a>
|
|
</p>
|
|
)}
|
|
<p className="text-xs sm:text-sm text-neutral-500">
|
|
{brandingSettings?.footer_text || '© 2024 Your Company. All rights reserved.'}
|
|
</p>
|
|
{brandingSettings?.company_name && brandingSettings?.company_tagline && (
|
|
<p className="text-xs text-neutral-400 mt-2">
|
|
{brandingSettings.company_name} - {brandingSettings.company_tagline}
|
|
</p>
|
|
)}
|
|
{/* Legal Links */}
|
|
<div className="mt-4 flex items-center justify-center gap-4">
|
|
<Link
|
|
to="/impressum"
|
|
className="text-xs text-neutral-500 hover:text-neutral-700 transition-colors"
|
|
>
|
|
{t('legal.impressum')}
|
|
</Link>
|
|
<span className="text-xs text-neutral-400">|</span>
|
|
<Link
|
|
to="/datenschutz"
|
|
className="text-xs text-neutral-500 hover:text-neutral-700 transition-colors"
|
|
>
|
|
{t('legal.datenschutz')}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
GalleryLayout.displayName = 'GalleryLayout'; |