173 lines
4.9 KiB
TypeScript
173 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import { getGlobalReplaySystem } from '@/lib/infrastructure/ErrorReplay';
|
|
import { Button } from '@/ui/Button';
|
|
import { Icon } from '@/ui/Icon';
|
|
import { IconButton } from '@/ui/IconButton';
|
|
import { Stack } from '@/ui/primitives/Stack';
|
|
import { Text } from '@/ui/Text';
|
|
import { Box, Clock, Copy, Download, Play, Trash2 } from 'lucide-react';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
interface ReplayEntry {
|
|
id: string;
|
|
timestamp: string;
|
|
error: string;
|
|
type: string;
|
|
}
|
|
|
|
export function ReplaySection() {
|
|
const [replays, setReplays] = useState<ReplayEntry[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadReplays();
|
|
}, []);
|
|
|
|
const loadReplays = () => {
|
|
const system = getGlobalReplaySystem();
|
|
const index = system.getReplayIndex();
|
|
setReplays(index);
|
|
};
|
|
|
|
const handleReplay = async (replayId: string) => {
|
|
setLoading(true);
|
|
try {
|
|
const system = getGlobalReplaySystem();
|
|
await system.replay(replayId);
|
|
} catch (error) {
|
|
console.error('Replay failed:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleExport = (replayId: string) => {
|
|
const system = getGlobalReplaySystem();
|
|
const data = system.exportReplay(replayId, 'json');
|
|
|
|
const blob = new Blob([data], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `replay_${replayId}.json`;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
};
|
|
|
|
const handleCopy = async (replayId: string) => {
|
|
const system = getGlobalReplaySystem();
|
|
const data = system.exportReplay(replayId, 'json');
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(data);
|
|
console.log('Replay data copied to clipboard');
|
|
} catch (err) {
|
|
console.error('Failed to copy:', err);
|
|
}
|
|
};
|
|
|
|
const handleDelete = (replayId: string) => {
|
|
if (confirm('Delete this replay?')) {
|
|
const system = getGlobalReplaySystem();
|
|
system.deleteReplay(replayId);
|
|
loadReplays();
|
|
}
|
|
};
|
|
|
|
const handleClearAll = () => {
|
|
if (confirm('Clear all replays?')) {
|
|
const system = getGlobalReplaySystem();
|
|
system.clearAll();
|
|
loadReplays();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Stack gap={2}>
|
|
<Box display="flex" alignItems="center" justifyContent="between">
|
|
<Text size="xs" weight="semibold" color="text-gray-400">Error Replay</Text>
|
|
<Box display="flex" gap={1}>
|
|
<IconButton
|
|
icon={Clock}
|
|
onClick={loadReplays}
|
|
variant="ghost"
|
|
size="sm"
|
|
title="Refresh"
|
|
/>
|
|
<IconButton
|
|
icon={Trash2}
|
|
onClick={handleClearAll}
|
|
variant="ghost"
|
|
size="sm"
|
|
title="Clear All"
|
|
color="rgb(239, 68, 68)"
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
|
|
{replays.length === 0 ? (
|
|
<Box textAlign="center" py={2}>
|
|
<Text size="xs" color="text-gray-500">No replays available</Text>
|
|
</Box>
|
|
) : (
|
|
<Stack gap={1}>
|
|
{replays.map((replay) => (
|
|
<Box
|
|
key={replay.id}
|
|
bg="bg-deep-graphite"
|
|
border
|
|
borderColor="border-charcoal-outline"
|
|
rounded="md"
|
|
p={2}
|
|
>
|
|
<Box mb={1}>
|
|
<Text size="xs" font="mono" weight="bold" color="text-red-400" block truncate>
|
|
{replay.type}
|
|
</Text>
|
|
<Text size="xs" color="text-gray-300" block truncate>{replay.error}</Text>
|
|
<Text size="xs" color="text-gray-500" block>
|
|
{new Date(replay.timestamp).toLocaleTimeString()}
|
|
</Text>
|
|
</Box>
|
|
<Box display="flex" gap={1} mt={1}>
|
|
<Button
|
|
variant="primary"
|
|
onClick={() => handleReplay(replay.id)}
|
|
disabled={loading}
|
|
size="sm"
|
|
icon={<Icon icon={Play} size={3} />}
|
|
>
|
|
Replay
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
onClick={() => handleExport(replay.id)}
|
|
size="sm"
|
|
icon={<Icon icon={Download} size={3} />}
|
|
>
|
|
Export
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
onClick={() => handleCopy(replay.id)}
|
|
size="sm"
|
|
icon={<Icon icon={Copy} size={3} />}
|
|
>
|
|
Copy
|
|
</Button>
|
|
<IconButton
|
|
icon={Trash2}
|
|
onClick={() => handleDelete(replay.id)}
|
|
variant="secondary"
|
|
size="sm"
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
</Stack>
|
|
)}
|
|
</Stack>
|
|
);
|
|
}
|