Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 1x 24x 24x 24x 5x 4x 1x 4x 4x 1x 4x 1x 3x 1x 8x 8x 1x 7x 24x 7x 7x 7x | // 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';
import { logger } from '../../services/logger.client';
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';
logger.debug(
{ activityLogId: log.activity_log_id, altText },
'[ActivityLog] Rendering avatar',
);
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>
);
};
|