Fix brand theme application and add comprehensive translations

- Fixed theme not being reflected on gallery and admin login pages
- Created GlobalThemeProvider to apply themes globally
- Updated gallery and admin login pages to use dynamic CSS variables
- Added complete translations for all admin sections in English and German:
  - Notifications management
  - Event view and creation
  - Photo upload functionality
  - Category management
  - Archive page view
  - Analytics dashboard
  - Branding and theme settings
  - System settings
  - CMS page management
  - Email configuration
- Fixed admin photo management display issues
- Fixed photo upload category assignment
- Added password reset functionality for galleries
- Improved error handling and user feedback

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
2025-07-08 17:07:40 +02:00
co-authored by Claude
parent 2012b0bab9
commit d594d00227
79 changed files with 4570 additions and 329 deletions
+32 -20
View File
@@ -5,7 +5,7 @@ import { format, differenceInDays, parseISO } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { Card, CardContent, Input, Button, Loading } from '../components/common';
import { Card, CardContent, Input, Button, Loading, ReCaptcha } from '../components/common';
import { useGalleryAuth } from '../contexts';
import { useGalleryInfo } from '../hooks/useGallery';
import { GalleryView } from '../components/gallery';
@@ -19,6 +19,7 @@ export const GalleryPage: React.FC = () => {
const [password, setPassword] = useState('');
const [isLoggingIn, setIsLoggingIn] = useState(false);
const [loginError, setLoginError] = useState<string | null>(null);
const [recaptchaToken, setRecaptchaToken] = useState<string | null>(null);
// Fetch gallery info (public data)
const { data: galleryInfo, isLoading: isLoadingInfo, error: infoError } = useGalleryInfo(slug!, token);
@@ -55,7 +56,7 @@ export const GalleryPage: React.FC = () => {
try {
setIsLoggingIn(true);
setLoginError(null);
await login(slug!, password);
await login(slug!, password, recaptchaToken);
// Track successful password entry
analyticsService.trackGalleryEvent('password_entry', {
@@ -78,7 +79,7 @@ export const GalleryPage: React.FC = () => {
// Show loading state
if (isLoadingInfo) {
return (
<div className="min-h-screen bg-neutral-50">
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
<div className="min-h-screen flex items-center justify-center">
<Loading size="lg" text={t('gallery.loading')} />
</div>
@@ -88,14 +89,18 @@ export const GalleryPage: React.FC = () => {
// Show error state
if (infoError) {
// Check if it's an archived gallery error
const errorMessage = (infoError as any)?.response?.data?.error;
const isArchived = errorMessage?.includes('archived');
return (
<div className="min-h-screen bg-neutral-50">
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
<div className="min-h-screen flex flex-col">
{/* Logo at top */}
{settingsData?.branding_logo_url && (
<div className="p-8 text-center">
<img
src={settingsData.branding_logo_url}
src={`${import.meta.env.VITE_API_URL || 'http://localhost:3001'}${settingsData.branding_logo_url}`}
alt={settingsData.branding_company_name || 'Company Logo'}
className="h-16 w-auto object-contain mx-auto"
/>
@@ -106,9 +111,11 @@ export const GalleryPage: React.FC = () => {
<Card className="max-w-md w-full mx-4">
<CardContent className="text-center py-12">
<AlertCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold mb-2">{t('errors.galleryNotFound')}</h2>
<h2 className="text-xl font-semibold mb-2">
{t(isArchived ? 'errors.galleryArchived' : 'errors.galleryNotFound')}
</h2>
<p className="text-neutral-600">
{t('errors.galleryNotFoundMessage')}
{t(isArchived ? 'errors.galleryArchivedMessage' : 'errors.galleryNotFoundMessage')}
</p>
</CardContent>
</Card>
@@ -140,13 +147,13 @@ export const GalleryPage: React.FC = () => {
// Show expired state
if (galleryInfo?.is_expired) {
return (
<div className="min-h-screen bg-neutral-50">
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
<div className="min-h-screen flex flex-col">
{/* Logo at top */}
{settingsData?.branding_logo_url && (
<div className="p-8 text-center">
<img
src={settingsData.branding_logo_url}
src={`${import.meta.env.VITE_API_URL || 'http://localhost:3001'}${settingsData.branding_logo_url}`}
alt={settingsData.branding_company_name || 'Company Logo'}
className="h-16 w-auto object-contain mx-auto"
/>
@@ -198,26 +205,26 @@ export const GalleryPage: React.FC = () => {
// Show login form
return (
<div className="min-h-screen bg-gradient-to-br from-neutral-50 to-sand-100">
<div className="min-h-screen" style={{ backgroundColor: 'var(--color-background, #fafafa)' }}>
<div className="min-h-screen flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Logo/Header */}
<div className="text-center mb-8">
{settingsData?.branding_logo_url ? (
<img
src={settingsData.branding_logo_url}
src={`${import.meta.env.VITE_API_URL || 'http://localhost:3001'}${settingsData.branding_logo_url}`}
alt={settingsData.branding_company_name || 'Company Logo'}
className="h-20 w-auto object-contain mx-auto mb-4"
/>
) : (
<div className="inline-flex items-center justify-center w-20 h-20 bg-primary-600 rounded-2xl mb-4">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-2xl mb-4" style={{ backgroundColor: 'var(--color-primary, #5C8762)' }}>
<Camera className="w-10 h-10 text-white" />
</div>
)}
<h1 className="text-3xl font-bold text-neutral-900 mb-2">
<h1 className="text-3xl font-bold mb-2" style={{ color: 'var(--color-text, #171717)' }}>
{galleryInfo?.event_name}
</h1>
<div className="flex items-center justify-center text-neutral-600 text-sm">
<div className="flex items-center justify-center text-sm" style={{ color: 'var(--color-text, #171717)', opacity: 0.7 }}>
<Calendar className="w-4 h-4 mr-1" />
{format(parseISO(galleryInfo!.event_date), 'MMMM d, yyyy')}
</div>
@@ -256,6 +263,11 @@ export const GalleryPage: React.FC = () => {
autoFocus
/>
<ReCaptcha
onChange={setRecaptchaToken}
onExpired={() => setRecaptchaToken(null)}
/>
<Button
type="submit"
variant="primary"
@@ -277,19 +289,19 @@ export const GalleryPage: React.FC = () => {
{/* Legal Links */}
<div className="text-center mt-6">
<div className="flex items-center justify-center gap-4">
<a
href="/impressum"
<Link
to="/impressum"
className="text-xs text-neutral-500 hover:text-neutral-700 transition-colors"
>
{t('legal.impressum')}
</a>
</Link>
<span className="text-xs text-neutral-400">|</span>
<a
href="/datenschutz"
<Link
to="/datenschutz"
className="text-xs text-neutral-500 hover:text-neutral-700 transition-colors"
>
{t('legal.datenschutz')}
</a>
</Link>
</div>
</div>
</div>