fix: multiple improvements and CI/CD updates

Frontend fixes:
- Add missing translations for chunk upload (upload.uploadingChunks, common.chunk)
- Fix photo deletion visual bug by tracking deletion state per photo
- Prevent UI confusion when deleting photos in admin grid

Backend fixes:
- Add file existence checks before deleting thumbnails
- Prevent ENOENT errors for missing thumbnail files
- Improve error handling in photo deletion

CI/CD updates:
- Remove Gitea release creation from Drone pipeline
- Simplify GitHub mirror workflow (remove history rewriting, keep file removal)
- Add clean-git-history.sh script for manual history cleanup

These changes improve the admin photo management experience and streamline
the CI/CD process for better maintainability.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-24 16:39:20 +02:00
parent fee369a503
commit bf705674d5
8 changed files with 147 additions and 199 deletions
@@ -23,7 +23,7 @@ export const AdminPhotoGrid: React.FC<AdminPhotoGridProps> = ({
const [selectedPhotos, setSelectedPhotos] = useState<Set<number>>(new Set());
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [deletingPhotoId, setDeletingPhotoId] = useState<number | null>(null);
const [deletingPhotos, setDeletingPhotos] = useState<Set<number>>(new Set());
const handlePhotoSelect = (photoId: number, e?: React.MouseEvent) => {
if (e) {
@@ -54,15 +54,18 @@ export const AdminPhotoGrid: React.FC<AdminPhotoGridProps> = ({
return;
}
setDeletingPhotoId(photo.id);
setDeletingPhotos(prev => new Set(prev).add(photo.id));
try {
await photosService.deletePhoto(eventId, photo.id);
toast.success('Photo deleted successfully');
onPhotosDeleted();
} catch (error) {
toast.error('Failed to delete photo');
} finally {
setDeletingPhotoId(null);
setDeletingPhotos(prev => {
const newSet = new Set(prev);
newSet.delete(photo.id);
return newSet;
});
}
};
@@ -75,14 +78,18 @@ export const AdminPhotoGrid: React.FC<AdminPhotoGridProps> = ({
}
setIsDeleting(true);
const selectedIds = Array.from(selectedPhotos);
setDeletingPhotos(new Set(selectedIds));
try {
await photosService.deletePhotos(eventId, Array.from(selectedPhotos));
await photosService.deletePhotos(eventId, selectedIds);
toast.success(`${count} photo${count > 1 ? 's' : ''} deleted successfully`);
setSelectedPhotos(new Set());
setIsSelectionMode(false);
onPhotosDeleted();
} catch (error) {
toast.error('Failed to delete photos');
setDeletingPhotos(new Set());
} finally {
setIsDeleting(false);
}
@@ -155,13 +162,15 @@ export const AdminPhotoGrid: React.FC<AdminPhotoGridProps> = ({
{/* Photo Grid */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4">
{photos.map((photo, index) => (
<div
key={photo.id}
className={`relative group cursor-pointer rounded-lg overflow-hidden bg-neutral-100 ${
isSelectionMode ? 'ring-2 ring-offset-2 ' + (selectedPhotos.has(photo.id) ? 'ring-primary-500' : 'ring-transparent') : ''
}`}
onClick={() => isSelectionMode ? handlePhotoSelect(photo.id) : onPhotoClick(photo, index)}
{photos.map((photo, index) => {
const isDeleting = deletingPhotos.has(photo.id);
return (
<div
key={photo.id}
className={`relative group cursor-pointer rounded-lg overflow-hidden bg-neutral-100 transition-opacity ${
isSelectionMode ? 'ring-2 ring-offset-2 ' + (selectedPhotos.has(photo.id) ? 'ring-primary-500' : 'ring-transparent') : ''
} ${isDeleting ? 'opacity-50' : ''}`}
onClick={() => !isDeleting && (isSelectionMode ? handlePhotoSelect(photo.id) : onPhotoClick(photo, index))}
>
{/* Selection Checkbox */}
{isSelectionMode && (
@@ -219,8 +228,8 @@ export const AdminPhotoGrid: React.FC<AdminPhotoGridProps> = ({
</button>
<button
onClick={(e) => handleDeleteSingle(photo, e)}
className="p-1 text-white hover:bg-white/20 rounded"
disabled={deletingPhotoId === photo.id}
className="p-1 text-white hover:bg-white/20 rounded disabled:opacity-50"
disabled={isDeleting}
>
<Trash2 className="w-3 h-3" />
</button>
@@ -238,7 +247,8 @@ export const AdminPhotoGrid: React.FC<AdminPhotoGridProps> = ({
</div>
)}
</div>
))}
);
})}
</div>
{photos.length === 0 && (
@@ -256,7 +256,7 @@ export const PhotoUpload: React.FC<PhotoUploadProps> = ({ eventId, onUploadCompl
<div className="flex justify-between text-sm text-neutral-600 mb-1">
<span>
{t('upload.uploading')}
{totalChunks > 1 && ` (${t('common.chunk') || 'Chunk'} ${currentChunk}/${totalChunks})`}
{totalChunks > 1 && ` (${t('common.chunk')} ${currentChunk}/${totalChunks})`}
</span>
<span>{uploadProgress}%</span>
</div>
@@ -268,7 +268,7 @@ export const PhotoUpload: React.FC<PhotoUploadProps> = ({ eventId, onUploadCompl
</div>
{totalChunks > 1 && (
<p className="text-xs text-neutral-500 mt-1">
{t('upload.uploadingChunks') || `Uploading ${selectedFiles.length} files in ${totalChunks} batches...`}
{t('upload.uploadingChunks', { count: selectedFiles.length, total: totalChunks })}
</p>
)}
</div>
+4 -2
View File
@@ -35,7 +35,8 @@
"days": "Tage",
"customize": "Anpassen",
"hide": "Ausblenden",
"unknown": "Unbekannt"
"unknown": "Unbekannt",
"chunk": "Teil"
},
"upload": {
"photoCategory": "Fotokategorie",
@@ -51,7 +52,8 @@
"uploadPhotos": "Fotos hochladen",
"maxFilesReached": "Maximal 500 Dateien erlaubt",
"someFilesSkipped": "Einige Dateien wurden übersprungen (500 Dateien Limit)",
"tooManyFiles": "Maximal 500 Dateien können gleichzeitig hochgeladen werden"
"tooManyFiles": "Maximal 500 Dateien können gleichzeitig hochgeladen werden",
"uploadingChunks": "Lade {{count}} Dateien in {{total}} Teilen hoch..."
},
"navigation": {
"dashboard": "Dashboard",
+4 -2
View File
@@ -35,7 +35,8 @@
"days": "days",
"customize": "Customize",
"hide": "Hide",
"unknown": "Unknown"
"unknown": "Unknown",
"chunk": "Chunk"
},
"upload": {
"photoCategory": "Photo Category",
@@ -51,7 +52,8 @@
"uploadPhotos": "Upload Photos",
"maxFilesReached": "Maximum 500 files allowed",
"someFilesSkipped": "Some files were skipped (500 file limit)",
"tooManyFiles": "Maximum 500 files can be uploaded at once"
"tooManyFiles": "Maximum 500 files can be uploaded at once",
"uploadingChunks": "Uploading {{count}} files in {{total}} batches..."
},
"navigation": {
"dashboard": "Dashboard",