fix: resolve code quality issues and add missing i18n keys (#162, #163)

Add missing i18n translations for hero image focal point picker in both
EN and DE locales. Fix lint errors across touched files: remove unused
imports/variables, replace raw buttons with shared Button component,
eliminate inline styles, extract duplicated backend validation, and
remove dead heroImagePosition type.
This commit is contained in:
Paul Nothaft
2026-02-03 10:53:13 +01:00
parent 734868abc2
commit 329d224846
8 changed files with 48 additions and 42 deletions
@@ -1,6 +1,6 @@
import React, { useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { AuthenticatedImage } from '../common';
import { AuthenticatedImage, Button } from '../common';
interface FocalPointPickerProps {
imageUrl: string;
@@ -64,7 +64,7 @@ export const FocalPointPicker: React.FC<FocalPointPickerProps> = ({
>
<AuthenticatedImage
src={imageUrl}
alt="Hero preview"
alt={t('events.heroPreview', 'Hero preview')}
className="w-full h-full object-cover pointer-events-none"
style={{ objectPosition: `${x}% ${y}%` }}
slug={slug}
@@ -78,7 +78,7 @@ export const FocalPointPicker: React.FC<FocalPointPickerProps> = ({
{/* Outer ring (dark) for contrast on light areas */}
<div className="w-6 h-6 rounded-full border-2 border-black/50" />
{/* Inner ring (white) for contrast on dark areas */}
<div className="absolute inset-0 w-6 h-6 rounded-full border-2 border-white" style={{ margin: '1px' }} />
<div className="absolute inset-0 m-px w-6 h-6 rounded-full border-2 border-white" />
{/* Center dot */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-1.5 h-1.5 rounded-full bg-white shadow-sm" />
@@ -94,18 +94,20 @@ export const FocalPointPicker: React.FC<FocalPointPickerProps> = ({
{/* Preset buttons */}
<div className="flex gap-2 mt-2">
{presets.map((p) => (
<button
<Button
key={p.value}
type="button"
variant="outline"
size="sm"
onClick={() => onChange(p.value)}
className={`px-3 py-1 text-xs font-medium rounded-md border transition-colors ${
className={
keywordToPercent(currentValue) === p.value
? 'bg-primary-50 border-primary-300 text-primary-700'
: 'bg-white border-neutral-300 text-neutral-600 hover:bg-neutral-50'
}`}
: ''
}
>
{p.label}
</button>
</Button>
))}
</div>
</div>
@@ -111,7 +111,7 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
// Clear selection when category changes
useEffect(() => {
setSelectedPhotos(new Set());
}, [categoryId]);
}, [categoryId, setSelectedPhotos]);
const handlePhotoClick = (index: number) => {
setOpenFeedbackInitially(false);
@@ -171,7 +171,7 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
try {
await galleryService.downloadSelectedPhotos(slug, ids);
analyticsService.trackGalleryEvent('bulk_download', { gallery: slug, photo_count: ids.length });
} catch (error) {
} catch {
toastify.error(t('gallery.downloadError'));
} finally {
setSelectedPhotos(new Set());
+6
View File
@@ -863,6 +863,12 @@
"selectHeroPhoto": "Hero-Foto auswählen",
"noHeroPhotoSelected": "Kein Hero-Foto ausgewählt",
"heroPhotoSelected": "Hero-Foto ausgewählt",
"heroImageAnchor": "Hero-Bild Zuschneideposition",
"heroImageAnchorDescription": "Klicken Sie auf das Bild, um den Fokuspunkt für den Zuschnitt festzulegen.",
"heroImageAnchorTop": "Oben",
"heroImageAnchorCenter": "Mitte",
"heroImageAnchorBottom": "Unten",
"heroPreview": "Hero-Vorschau",
"noPhotosAvailable": "Keine Fotos verfügbar",
"processingRequest": "Ihre Anfrage wird verarbeitet...",
"eventTypeWedding": "Hochzeit",
+6
View File
@@ -489,6 +489,12 @@
"selectHeroPhoto": "Select Hero Photo",
"noHeroPhotoSelected": "No hero photo selected",
"heroPhotoSelected": "Hero photo selected",
"heroImageAnchor": "Hero Image Crop Position",
"heroImageAnchorDescription": "Click on the image to set the focal point for cropping.",
"heroImageAnchorTop": "Top",
"heroImageAnchorCenter": "Center",
"heroImageAnchorBottom": "Bottom",
"heroPreview": "Hero preview",
"noPhotosAvailable": "No photos available",
"processingRequest": "Processing your request...",
"eventTypeWedding": "Wedding",
@@ -89,6 +89,7 @@ const ExternalFolderPicker: React.FC<{ value: string; onChange: (p: string) => v
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => { load(currentPath || ''); }, []);
const navigateUp = () => {
@@ -295,7 +296,7 @@ export const EventDetailsPage: React.FC = () => {
const mediaTypes = useMemo(() => {
const types = new Set<'photo' | 'video'>();
photos.forEach((p: any) => {
photos.forEach((p) => {
const mediaType = (p.media_type as 'photo' | 'video' | undefined)
|| ((p.mime_type && String(p.mime_type).startsWith('video/')) || p.type === 'video' ? 'video' : 'photo');
if (mediaType === 'video' || mediaType === 'photo') {
@@ -436,7 +437,7 @@ export const EventDetailsPage: React.FC = () => {
setCurrentPresetName(event.color_theme);
}
}
} catch (e) {
} catch {
setCurrentTheme(GALLERY_THEME_PRESETS.default.config);
setCurrentPresetName('default');
}
@@ -578,7 +579,7 @@ export const EventDetailsPage: React.FC = () => {
// Update feedback settings separately
try {
await feedbackService.updateEventFeedbackSettings(id!, feedbackSettings);
} catch (error) {
} catch {
// Error already handled by mutation
}
};
@@ -869,7 +870,7 @@ export const EventDetailsPage: React.FC = () => {
{/* Hero Image Focal Point Picker (#162) */}
{editForm.hero_photo_id && (() => {
const heroPhoto = (photos || []).find((p: any) => p.id === editForm.hero_photo_id);
const heroPhoto = (photos || []).find((p) => p.id === editForm.hero_photo_id);
const heroImageUrl = heroPhoto?.thumbnail_url || heroPhoto?.url;
if (!heroImageUrl) return null;
return (
@@ -1477,7 +1478,7 @@ export const EventDetailsPage: React.FC = () => {
try {
await eventsService.resendCreationEmail(event.id);
toast.success(t('events.creationEmailResent'));
} catch (error) {
} catch {
toast.error(t('events.failedToResendEmail'));
}
}}
@@ -1653,7 +1654,7 @@ export const EventDetailsPage: React.FC = () => {
toast.info(t('events.downloadingArchive', { name: event.event_name }));
await archiveService.downloadArchive(Number(id), `${event.slug}-archive.zip`);
toast.success(t('events.downloadStarted'));
} catch (error) {
} catch {
toast.error(t('events.failedToDownloadArchive'));
}
}}
-1
View File
@@ -43,7 +43,6 @@ export interface GalleryLayoutSettings {
// Hero specific
heroImageId?: number;
heroImagePosition?: 'top' | 'center' | 'bottom';
heroOverlayOpacity?: number;
// Mosaic specific