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:
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user