Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<title>Prunerr</title>
<script>
// Prevent flash of wrong theme (FOUC) - runs before CSS loads
document.documentElement.classList.toggle(
'dark',
localStorage.theme === 'dark' ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
);
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&family=JetBrains+Mono:wght@400;500&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
Expand Down
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.12.0",
"react-theme-switch-animation": "^1.0.0",
"tailwind-merge": "^3.4.0"
},
"devDependencies": {
Expand Down
84 changes: 69 additions & 15 deletions client/src/components/ActivityLog/ActivityLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type { ActivityLogEntry, ActivityFilters } from '@/types';

// Event type configuration
const EVENT_CONFIG: Record<string, { icon: typeof Activity; colorClass: string; label: string }> = {
scan: { icon: PlayCircle, colorClass: 'text-accent-400', label: 'Scan' },
scan: { icon: PlayCircle, colorClass: 'text-accent-text', label: 'Scan' },
deletion: { icon: Trash2, colorClass: 'text-ruby-400', label: 'Deletion' },
rule_match: { icon: ListFilter, colorClass: 'text-amber-400', label: 'Rule Match' },
protection: { icon: Shield, colorClass: 'text-emerald-400', label: 'Protection' },
Expand Down Expand Up @@ -92,7 +92,7 @@ export default function ActivityLog() {
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-2xl font-bold text-white">Activity Log</h1>
<h1 className="text-2xl font-bold text-surface-50">Activity Log</h1>
<p className="text-surface-400 mt-1">System events and actions</p>
</div>

Expand All @@ -106,7 +106,7 @@ export default function ActivityLog() {
<span>Scan events are hidden by default.</span>
<button
onClick={() => setSelectedEventTypes([])}
className="text-accent-400 hover:text-accent-300 font-medium ml-1"
className="text-accent-text hover:text-accent-300 font-medium ml-1"
>
Show all events
</button>
Expand Down Expand Up @@ -144,8 +144,8 @@ export default function ActivityLog() {
</div>

{/* Event Type Filters */}
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-surface-400 mr-2">Event:</span>
<div className="flex items-center gap-2 overflow-x-auto no-scrollbar">
<span className="text-sm text-surface-400 mr-2 flex-shrink-0">Event:</span>
{EVENT_TYPES.map((type) => {
const config = EVENT_CONFIG[type];
const isSelected = selectedEventTypes.includes(type);
Expand All @@ -154,10 +154,10 @@ export default function ActivityLog() {
key={type}
onClick={() => toggleEventType(type)}
className={cn(
'px-3 py-1.5 rounded-lg text-xs font-medium transition-all',
'px-3 py-1.5 rounded-lg text-xs font-medium transition-all flex-shrink-0 whitespace-nowrap',
'border flex items-center gap-1.5',
isSelected
? 'bg-surface-700 border-surface-600 text-white'
? 'bg-surface-700 border-surface-600 text-surface-50'
: 'bg-surface-800/50 border-surface-700/50 text-surface-400 hover:text-surface-200 hover:bg-surface-800'
)}
>
Expand All @@ -169,8 +169,8 @@ export default function ActivityLog() {
</div>

{/* Actor Type Filters */}
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-surface-400 mr-2">Actor:</span>
<div className="flex items-center gap-2 overflow-x-auto no-scrollbar">
<span className="text-sm text-surface-400 mr-2 flex-shrink-0">Actor:</span>
{ACTOR_TYPES.map((type) => {
const config = ACTOR_CONFIG[type];
const isSelected = selectedActorTypes.includes(type);
Expand All @@ -179,10 +179,10 @@ export default function ActivityLog() {
key={type}
onClick={() => toggleActorType(type)}
className={cn(
'px-3 py-1.5 rounded-lg text-xs font-medium transition-all',
'px-3 py-1.5 rounded-lg text-xs font-medium transition-all flex-shrink-0 whitespace-nowrap',
'border flex items-center gap-1.5',
isSelected
? 'bg-surface-700 border-surface-600 text-white'
? 'bg-surface-700 border-surface-600 text-surface-50'
: 'bg-surface-800/50 border-surface-700/50 text-surface-400 hover:text-surface-200 hover:bg-surface-800'
)}
>
Expand Down Expand Up @@ -211,7 +211,14 @@ export default function ActivityLog() {
/>
) : data && data.items.length > 0 ? (
<Card>
<div className="overflow-x-auto">
{/* Mobile card layout */}
<div className="sm:hidden divide-y divide-surface-800/50">
{data.items.map((item) => (
<ActivityCard key={item.id} item={item} />
))}
</div>
{/* Desktop table layout */}
<div className="hidden sm:block overflow-x-auto">
<table className="table">
<thead>
<tr>
Expand Down Expand Up @@ -262,7 +269,7 @@ export default function ActivityLog() {

{/* Pagination */}
{data && data.total > 20 && (
<div className="flex items-center justify-between">
<div className="flex flex-col sm:flex-row items-center gap-2 sm:justify-between">
<p className="text-sm text-surface-400">
Showing {(page - 1) * 20 + 1} to{' '}
{Math.min(page * 20, data.total)} of {data.total} activities
Expand Down Expand Up @@ -294,6 +301,53 @@ export default function ActivityLog() {
);
}

function ActivityCard({ item }: { item: ActivityLogEntry }) {
const eventConfig = EVENT_CONFIG[item.eventType] || {
icon: Activity,
colorClass: 'text-surface-400',
label: item.eventType,
};
const actorConfig = ACTOR_CONFIG[item.actorType] || {
variant: 'default' as const,
label: item.actorType,
};

const EventIcon = eventConfig.icon;

return (
<div className="px-4 py-3 space-y-2">
<div className="flex items-start gap-3">
<div className={cn('p-2 rounded-lg bg-surface-800/50 flex-shrink-0')}>
<EventIcon className={cn('w-4 h-4', eventConfig.colorClass)} />
</div>
<div className="min-w-0 flex-1">
<p className="font-medium text-surface-50 text-sm">{item.action}</p>
{item.targetTitle && (
<p className="text-sm text-surface-300 mt-0.5">
{item.targetId ? (
<Link
to={item.targetType === 'collection' ? `/collections/${item.targetId}` : `/library/${item.targetId}`}
className="hover:text-accent-text-hover transition-colors"
>
{item.targetTitle}
</Link>
) : (
item.targetTitle
)}
</p>
)}
<div className="flex items-center gap-2 mt-1.5">
<Badge variant={actorConfig.variant} size="sm">
{actorConfig.label}
</Badge>
<span className="text-xs text-surface-500">{formatRelativeTime(item.createdAt)}</span>
</div>
</div>
</div>
</div>
);
}

function ActivityRow({ item }: { item: ActivityLogEntry }) {
const eventConfig = EVENT_CONFIG[item.eventType] || {
icon: Activity,
Expand All @@ -316,7 +370,7 @@ function ActivityRow({ item }: { item: ActivityLogEntry }) {
<EventIcon className={cn('w-4 h-4', eventConfig.colorClass)} />
</div>
<div className="min-w-0">
<p className="font-medium text-white text-sm">
<p className="font-medium text-surface-50 text-sm">
{item.action}
</p>
</div>
Expand All @@ -340,7 +394,7 @@ function ActivityRow({ item }: { item: ActivityLogEntry }) {
{item.targetTitle && item.targetId ? (
<Link
to={item.targetType === 'collection' ? `/collections/${item.targetId}` : `/library/${item.targetId}`}
className="hover:text-accent-400 transition-colors"
className="hover:text-accent-text-hover transition-colors"
>
{item.targetTitle}
</Link>
Expand Down
10 changes: 5 additions & 5 deletions client/src/components/Collections/CollectionDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,15 @@ export default function CollectionDetail() {
<button
type="button"
onClick={() => navigate('/collections')}
className="inline-flex items-center gap-2 text-sm text-surface-400 hover:text-white transition-colors mb-4"
className="inline-flex items-center gap-2 text-sm text-surface-400 hover:text-surface-50 transition-colors mb-4"
>
<ArrowLeft className="w-4 h-4" />
Back to Collections
</button>

<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
<div>
<h1 className="text-4xl font-display font-bold text-white tracking-tight">
<h1 className="text-4xl font-display font-bold text-surface-50 tracking-tight">
{collection.title}
</h1>
{collection.overview && (
Expand Down Expand Up @@ -312,7 +312,7 @@ export default function CollectionDetail() {
/>
</div>
<div>
<h3 className="text-sm font-display font-semibold text-white">
<h3 className="text-sm font-display font-semibold text-surface-50">
Collection Protection
</h3>
<p className="text-xs text-surface-500 mt-0.5">
Expand Down Expand Up @@ -381,7 +381,7 @@ export default function CollectionDetail() {
<Trash2 className="w-5 h-5 text-red-400" />
</div>
<div>
<h3 className="text-sm font-display font-semibold text-white">
<h3 className="text-sm font-display font-semibold text-surface-50">
Queue for Deletion
</h3>
<p className="text-xs text-surface-500 mt-0.5">
Expand Down Expand Up @@ -500,7 +500,7 @@ export default function CollectionDetail() {
<div className="px-1">
<Card>
<div className="px-6 py-4 border-b border-surface-700/50">
<h2 className="text-base font-display font-semibold text-white">
<h2 className="text-base font-display font-semibold text-surface-50">
Items ({items?.length ?? 0})
</h2>
</div>
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/Collections/Collections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function CollectionCard({
{/* Info */}
<div className="flex-1 min-w-0 flex flex-col justify-between py-0.5">
<div>
<h3 className="text-sm font-display font-semibold text-white truncate">
<h3 className="text-sm font-display font-semibold text-surface-50 truncate">
{collection.title}
</h3>
{collection.overview && (
Expand Down Expand Up @@ -149,11 +149,11 @@ export default function Collections() {
<div className="relative px-8 py-10">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
<div>
<p className="text-sm font-medium text-accent-400 mb-2 flex items-center gap-2">
<p className="text-sm font-medium text-accent-text mb-2 flex items-center gap-2">
<Layers className="w-4 h-4" />
Media Groups
</p>
<h1 className="text-4xl font-display font-bold text-white tracking-tight">
<h1 className="text-4xl font-display font-bold text-surface-50 tracking-tight">
Collections
</h1>
<p className="text-surface-400 mt-2 max-w-lg">
Expand Down
Loading