feat(gallery): always-visible feedback indicators on grid tiles; fallback image rendering in lightbox/hero; auto-auth from shared-link token; fix external photo resolver\n\n- GridGallery: bottom-left icons for like/rated/comment on every tile\n- Hero layout grid: added same indicators (non-intrusive icons)\n- Lightbox/Hero: add fallbackSrc to display thumbnail if original fails\n- GalleryAuth: auto-store token from /gallery/:slug/:token and hydrate event\n- Backend gallery photo route: use resolvePhotoFilePath for external-media\n\nfix(admin): move photo feedback badges to bottom-right on admin grid tiles\n\nfix(dashboard): add missing i18n keys for activity types + fallback to formatter\n\nfix(admin/feedback): correct thumbnail URL base + robust date parsing\n\nRefs: #19
This commit is contained in:
@@ -268,13 +268,13 @@ export const AdminDashboard: React.FC = () => {
|
||||
categoryName: activity.metadata?.category_name || ''
|
||||
};
|
||||
|
||||
// Check if translation exists
|
||||
const translated = t(translationKey, params);
|
||||
if (typeof translated === 'string') {
|
||||
return translated;
|
||||
// Translate; if key missing i18n returns the key string itself
|
||||
const translated = t(translationKey, params) as string;
|
||||
if (!translated || translated === translationKey) {
|
||||
// Fallback: format a readable English message
|
||||
return adminService.formatActivityMessage(activity);
|
||||
}
|
||||
// Fallback to unknown activity if translation not found
|
||||
return t('admin.activities.unknown') as string;
|
||||
return translated;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -302,4 +302,4 @@ export const AdminDashboard: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
AdminDashboard.displayName = 'AdminDashboard';
|
||||
AdminDashboard.displayName = 'AdminDashboard';
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
Trash2
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { format } from 'date-fns';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
|
||||
import { Button, Card, Loading } from '../../components/common';
|
||||
import { AdminAuthenticatedImage } from '../../components/admin/AdminAuthenticatedImage';
|
||||
@@ -254,7 +254,7 @@ export const EventFeedbackPage: React.FC = () => {
|
||||
{item.photo_id && (
|
||||
<div className="w-16 h-16 overflow-hidden rounded">
|
||||
<AdminAuthenticatedImage
|
||||
src={`/api/admin/photos/${id}/thumbnail/${item.photo_id}`}
|
||||
src={`/admin/photos/${id}/thumbnail/${item.photo_id}`}
|
||||
alt={item.filename || 'Photo'}
|
||||
className="w-16 h-16 object-cover rounded"
|
||||
/>
|
||||
@@ -290,7 +290,12 @@ export const EventFeedbackPage: React.FC = () => {
|
||||
<p className="text-sm text-neutral-700">{item.comment_text}</p>
|
||||
)}
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
{format(new Date(item.created_at), 'PPpp')}
|
||||
{(() => {
|
||||
const d = typeof item.created_at === 'string'
|
||||
? parseISO(item.created_at)
|
||||
: new Date(item.created_at);
|
||||
return isNaN(d.getTime()) ? t('common.unknownDate', 'Unknown date') : format(d, 'PPpp');
|
||||
})()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -492,7 +497,12 @@ export const EventFeedbackPage: React.FC = () => {
|
||||
<p className="text-sm text-neutral-700">{comment.comment_text}</p>
|
||||
<p className="text-xs text-neutral-500 mt-1">
|
||||
{comment.guest_name} • {comment.filename} •
|
||||
{format(new Date(comment.created_at), 'PP')}
|
||||
{(() => {
|
||||
const d = typeof comment.created_at === 'string'
|
||||
? parseISO(comment.created_at)
|
||||
: new Date(comment.created_at);
|
||||
return isNaN(d.getTime()) ? t('common.unknownDate', 'Unknown date') : format(d, 'PP');
|
||||
})()}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user