fix: resolve feedback validation issues from GitHub issue #16

- Fixed 400 Bad Request error when submitting feedback with name/email required
- Updated backend validation to properly handle empty/undefined name/email fields
- Modified frontend components to send undefined instead of empty strings when fields are not provided
- Fixed thumbnail display issue in moderation view by using correct admin API endpoints
- Updated FeedbackModerationPanel and EventFeedbackPage to display thumbnails correctly

The issue was caused by the validation logic treating empty strings differently than undefined values.
Frontend components now properly send undefined when name/email are not provided, and the backend
validation correctly handles both cases.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-02 17:23:07 +02:00
parent 67ff415840
commit f26becad1d
7 changed files with 38 additions and 21 deletions
+3 -3
View File
@@ -233,11 +233,11 @@ async function validateGuestRequirements(settings, guestData) {
errors.push('Name is required'); errors.push('Name is required');
} }
// Check for email - handle both undefined and empty strings // Check for email - handle both undefined and empty strings
const email = guestData.guest_email; const email = guestData.guest_email;
if (!email || (typeof email === 'string' && email.trim().length === 0)) { if (!email || (typeof email === 'string' && email.trim().length === 0)) {
errors.push('Valid email is required'); errors.push('Email is required');
} else if (email && !validator.isEmail(email.trim())) { } else if (email && typeof email === 'string' && !validator.isEmail(email.trim())) {
errors.push('Valid email is required'); errors.push('Valid email is required');
} }
@@ -126,11 +126,22 @@ export const FeedbackModerationPanel: React.FC<FeedbackModerationPanelProps> = (
)} )}
</span> </span>
</div> </div>
<p className="mt-1 text-sm text-neutral-700">{item.comment}</p> <p className="mt-1 text-sm text-neutral-700">{item.comment_text || item.comment}</p>
{item.photo_filename && ( {item.photo_id && (
<p className="mt-1 text-xs text-neutral-500"> <div className="mt-2 flex items-center gap-2">
{t('feedback.onPhoto', 'On photo')}: {item.photo_filename} <img
</p> src={`/api/admin/photos/${eventId}/thumbnail/${item.photo_id}`}
alt={item.filename || 'Photo'}
className="w-16 h-16 object-cover rounded"
onError={(e) => {
// Hide image if thumbnail fails to load
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
<p className="text-xs text-neutral-500">
{t('feedback.onPhoto', 'On photo')}: {item.filename || item.photo_filename || `#${item.photo_id}`}
</p>
</div>
)} )}
</div> </div>
</div> </div>
@@ -103,8 +103,8 @@ export const PhotoComments: React.FC<PhotoCommentsProps> = ({
submitCommentMutation.mutate({ submitCommentMutation.mutate({
comment_text: commentText.trim(), comment_text: commentText.trim(),
guest_name: guestName.trim(), guest_name: guestName.trim() || undefined,
guest_email: guestEmail.trim() guest_email: guestEmail.trim() || undefined
}); });
}; };
@@ -36,8 +36,8 @@ export const PhotoFavorites: React.FC<PhotoFavoritesProps> = ({
mutationFn: (data: { guest_name?: string; guest_email?: string } = {}) => mutationFn: (data: { guest_name?: string; guest_email?: string } = {}) =>
feedbackService.submitFeedback(gallerySlug, photoId, { feedbackService.submitFeedback(gallerySlug, photoId, {
feedback_type: 'favorite', feedback_type: 'favorite',
guest_name: data.guest_name, guest_name: data.guest_name || undefined,
guest_email: data.guest_email guest_email: data.guest_email || undefined
}), }),
onMutate: async () => { onMutate: async () => {
setIsSubmitting(true); setIsSubmitting(true);
@@ -36,8 +36,8 @@ export const PhotoLikes: React.FC<PhotoLikesProps> = ({
mutationFn: (data: { guest_name?: string; guest_email?: string } = {}) => mutationFn: (data: { guest_name?: string; guest_email?: string } = {}) =>
feedbackService.submitFeedback(gallerySlug, photoId, { feedbackService.submitFeedback(gallerySlug, photoId, {
feedback_type: 'like', feedback_type: 'like',
guest_name: data.guest_name, guest_name: data.guest_name || undefined,
guest_email: data.guest_email guest_email: data.guest_email || undefined
}), }),
onMutate: async () => { onMutate: async () => {
setIsSubmitting(true); setIsSubmitting(true);
@@ -42,8 +42,8 @@ export const PhotoRating: React.FC<PhotoRatingProps> = ({
feedbackService.submitFeedback(gallerySlug, photoId, { feedbackService.submitFeedback(gallerySlug, photoId, {
feedback_type: 'rating', feedback_type: 'rating',
rating: data.rating, rating: data.rating,
guest_name: data.guest_name, guest_name: data.guest_name || undefined,
guest_email: data.guest_email guest_email: data.guest_email || undefined
}), }),
onMutate: async (data) => { onMutate: async (data) => {
setIsSubmitting(true); setIsSubmitting(true);
+11 -5
View File
@@ -250,11 +250,17 @@ export const EventFeedbackPage: React.FC = () => {
{feedbackData?.feedback?.map((item: PhotoFeedback) => ( {feedbackData?.feedback?.map((item: PhotoFeedback) => (
<Card key={item.id} className="overflow-hidden"> <Card key={item.id} className="overflow-hidden">
<div className="p-4 flex items-start gap-4"> <div className="p-4 flex items-start gap-4">
<img {item.photo_id && (
src={`/thumbnails/${item.path}`} <img
alt={item.filename} src={`/api/admin/photos/${eventId}/thumbnail/${item.photo_id}`}
className="w-16 h-16 object-cover rounded" alt={item.filename || 'Photo'}
/> className="w-16 h-16 object-cover rounded"
onError={(e) => {
// Hide image if thumbnail fails to load
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
)}
<div className="flex-1"> <div className="flex-1">
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div> <div>