Files
flyer-crawler.projectium.com/src/pages/admin/ActivityLog.tsx
Torben Sorensen 46c1e56b14
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 46s
progress enforcing adr-0005
2026-01-08 21:40:20 -08:00

134 lines
4.8 KiB
TypeScript

// src/pages/admin/ActivityLog.tsx
import React from 'react';
import { ActivityLogItem } from '../../types';
import { UserProfile } from '../../types';
import { formatDistanceToNow } from 'date-fns';
import { useActivityLogQuery } from '../../hooks/queries/useActivityLogQuery';
export type ActivityLogClickHandler = (log: ActivityLogItem) => void;
interface ActivityLogProps {
userProfile: UserProfile | null;
onLogClick?: ActivityLogClickHandler;
}
const renderLogDetails = (log: ActivityLogItem, onLogClick?: ActivityLogClickHandler) => {
// With discriminated unions, we can safely access properties based on the 'action' type.
const userName = log.user_full_name || 'A user';
const isClickable = onLogClick !== undefined;
switch (log.action) {
case 'flyer_processed':
return (
<span>
A new flyer for <strong>{log.details.store_name || 'a store'}</strong> was added.
</span>
);
case 'recipe_created':
return (
<span>
{userName} added a new recipe:{' '}
<strong
onClick={isClickable ? () => onLogClick(log) : undefined}
className={isClickable ? 'text-blue-500 hover:underline cursor-pointer' : ''}
>
{log.details.recipe_name || 'Untitled Recipe'}
</strong>
.
</span>
);
case 'user_registered':
return (
<span>
<strong>{log.details.full_name || 'A new user'}</strong> just joined!
</span>
);
case 'recipe_favorited':
return (
<span>
{userName} favorited the recipe:{' '}
<strong
onClick={isClickable ? () => onLogClick(log) : undefined}
className={isClickable ? 'text-blue-500 hover:underline cursor-pointer' : ''}
>
{log.details.recipe_name || 'a recipe'}
</strong>
.
</span>
);
case 'list_shared':
return (
<span>
{userName} shared the list "
<strong
onClick={isClickable ? () => onLogClick(log) : undefined}
className={isClickable ? 'text-blue-500 hover:underline cursor-pointer' : ''}
>
{log.details.list_name || 'a shopping list'}
</strong>
" with <strong>{log.details.shared_with_name || 'another user'}</strong>.
</span>
);
default:
return <span>An unknown activity occurred.</span>;
}
};
export const ActivityLog: React.FC<ActivityLogProps> = ({ userProfile, onLogClick }) => {
// Use TanStack Query for data fetching (ADR-0005 Phase 5)
const { data: logs = [], isLoading, error } = useActivityLogQuery(20, 0);
if (!userProfile) {
return null; // Don't render the component if the user is not logged in
}
return (
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm p-4">
<h3 className="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-4">
Recent Activity
</h3>
{isLoading && <p className="text-gray-500 dark:text-gray-400">Loading activity...</p>}
{error && <p className="text-red-500">{error.message}</p>}
{!isLoading && !error && logs.length === 0 && (
<p className="text-gray-500 dark:text-gray-400">No recent activity to show.</p>
)}
<ul className="space-y-4">
{logs.map((log) => (
<li key={log.activity_log_id} className="flex items-start space-x-3">
<div className="shrink-0">
{log.user_avatar_url ? (
(() => {
const altText = log.user_full_name || 'User Avatar';
console.log(
`[ActivityLog] Rendering avatar for log ${log.activity_log_id}. Alt: "${altText}"`,
);
return (
<img className="h-8 w-8 rounded-full" src={log.user_avatar_url} alt={altText} />
);
})()
) : (
<span className="h-8 w-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center">
<svg
className="h-5 w-5 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" />
</svg>
</span>
)}
</div>
<div className="flex-1">
<p className="text-sm text-gray-700 dark:text-gray-300">
{renderLogDetails(log, onLogClick)}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
{formatDistanceToNow(new Date(log.created_at), { addSuffix: true })}
</p>
</div>
</li>
))}
</ul>
</div>
);
};