chore(admin-web): clear feedback warnings

What changed:

- Removed unused feedback router import and unused catch bindings.

- Wrapped feedback loading in useCallback to satisfy hook dependencies.

- Replaced screenshot lightbox img with unoptimized Next Image.

Warning impact:

- dashboards/admin-web feedback page: 7 warnings -> 0 warnings.

- Workspace lint warning lines: 191 -> 176 in /tmp/lint-before-agent-a.log and /tmp/lint-after-fix-agent-a.log.

Verification:

- pnpm --filter @bytelyst/admin-web typecheck

- pnpm --filter @bytelyst/admin-web test

- pnpm --filter @bytelyst/admin-web exec eslint . --ext .ts,.tsx

- pnpm lint
This commit is contained in:
Saravana Achu Mac 2026-05-04 15:58:49 -07:00
parent 021f053143
commit db4257fd76

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import Image from 'next/image';
import { useRouter } from 'next/navigation'; import { useCallback, useEffect, useState } from 'react';
import { import {
Search, Search,
MessageSquare, MessageSquare,
@ -26,12 +26,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { import {
Select, Select,
SelectContent, SelectContent,
@ -94,22 +89,22 @@ export default function FeedbackPage() {
const [lightboxOpen, setLightboxOpen] = useState(false); const [lightboxOpen, setLightboxOpen] = useState(false);
const { toast } = useToast(); const { toast } = useToast();
useEffect(() => { const fetchFeedback = useCallback(async () => {
fetchFeedback();
}, []);
async function fetchFeedback() {
try { try {
const res = await fetch('/api/feedback'); const res = await fetch('/api/feedback');
if (!res.ok) throw new Error('Failed to fetch'); if (!res.ok) throw new Error('Failed to fetch');
const data = await res.json(); const data = await res.json();
setFeedback(data.items || []); setFeedback(data.items || []);
} catch (err) { } catch {
toast({ title: 'Error', description: 'Failed to load feedback', variant: 'error' }); toast({ title: 'Error', description: 'Failed to load feedback', variant: 'error' });
} finally { } finally {
setLoading(false); setLoading(false);
} }
} }, [toast]);
useEffect(() => {
fetchFeedback();
}, [fetchFeedback]);
async function viewScreenshot(feedbackId: string) { async function viewScreenshot(feedbackId: string) {
try { try {
@ -118,7 +113,7 @@ export default function FeedbackPage() {
const data = await res.json(); const data = await res.json();
setScreenshotUrl(data.url); setScreenshotUrl(data.url);
setLightboxOpen(true); setLightboxOpen(true);
} catch (err) { } catch {
toast({ title: 'Error', description: 'Failed to load screenshot', variant: 'error' }); toast({ title: 'Error', description: 'Failed to load screenshot', variant: 'error' });
} }
} }
@ -130,7 +125,7 @@ export default function FeedbackPage() {
if (!res.ok) throw new Error('Failed to delete'); if (!res.ok) throw new Error('Failed to delete');
toast({ title: 'Success', description: 'Screenshot deleted' }); toast({ title: 'Success', description: 'Screenshot deleted' });
fetchFeedback(); fetchFeedback();
} catch (err) { } catch {
toast({ title: 'Error', description: 'Failed to delete screenshot', variant: 'error' }); toast({ title: 'Error', description: 'Failed to delete screenshot', variant: 'error' });
} }
} }
@ -148,19 +143,21 @@ export default function FeedbackPage() {
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
} catch (err) { } catch {
toast({ title: 'Error', description: 'Failed to download screenshot', variant: 'error' }); toast({ title: 'Error', description: 'Failed to download screenshot', variant: 'error' });
} }
} }
const filteredFeedback = feedback.filter((f) => { const filteredFeedback = feedback.filter(f => {
const matchesSearch = f.title.toLowerCase().includes(search.toLowerCase()) || const matchesSearch =
f.body?.toLowerCase().includes(search.toLowerCase()); f.title.toLowerCase().includes(search.toLowerCase()) ||
f.body?.toLowerCase().includes(search.toLowerCase());
const matchesType = typeFilter === 'all' || f.type === typeFilter; const matchesType = typeFilter === 'all' || f.type === typeFilter;
const matchesStatus = statusFilter === 'all' || f.status === statusFilter; const matchesStatus = statusFilter === 'all' || f.status === statusFilter;
const matchesScreenshot = screenshotFilter === 'all' || const matchesScreenshot =
(screenshotFilter === 'has' && f.screenshotBlobPath) || screenshotFilter === 'all' ||
(screenshotFilter === 'none' && !f.screenshotBlobPath); (screenshotFilter === 'has' && f.screenshotBlobPath) ||
(screenshotFilter === 'none' && !f.screenshotBlobPath);
return matchesSearch && matchesType && matchesStatus && matchesScreenshot; return matchesSearch && matchesType && matchesStatus && matchesScreenshot;
}); });
@ -169,7 +166,9 @@ export default function FeedbackPage() {
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-6">
<div> <div>
<h1 className="text-2xl font-bold">User Feedback</h1> <h1 className="text-2xl font-bold">User Feedback</h1>
<p className="text-muted-foreground">View and manage user-submitted feedback with screenshots</p> <p className="text-muted-foreground">
View and manage user-submitted feedback with screenshots
</p>
</div> </div>
</div> </div>
@ -181,7 +180,7 @@ export default function FeedbackPage() {
<Input <Input
placeholder="Search feedback..." placeholder="Search feedback..."
value={search} value={search}
onChange={(e) => setSearch(e.target.value)} onChange={e => setSearch(e.target.value)}
className="pl-9" className="pl-9"
/> />
</div> </div>
@ -246,7 +245,7 @@ export default function FeedbackPage() {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{filteredFeedback.map((f) => { {filteredFeedback.map(f => {
const TypeIcon = typeIcons[f.type]; const TypeIcon = typeIcons[f.type];
return ( return (
<TableRow key={f.id}> <TableRow key={f.id}>
@ -276,11 +275,7 @@ export default function FeedbackPage() {
<TableCell>{new Date(f.createdAt).toLocaleDateString()}</TableCell> <TableCell>{new Date(f.createdAt).toLocaleDateString()}</TableCell>
<TableCell> <TableCell>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button variant="ghost" size="sm" onClick={() => setSelectedFeedback(f)}>
variant="ghost"
size="sm"
onClick={() => setSelectedFeedback(f)}
>
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
</Button> </Button>
{f.screenshotBlobPath && ( {f.screenshotBlobPath && (
@ -312,9 +307,7 @@ export default function FeedbackPage() {
{selectedFeedback && ( {selectedFeedback && (
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Badge className={typeColors[selectedFeedback.type]}> <Badge className={typeColors[selectedFeedback.type]}>{selectedFeedback.type}</Badge>
{selectedFeedback.type}
</Badge>
<Badge className={statusColors[selectedFeedback.status]}> <Badge className={statusColors[selectedFeedback.status]}>
{selectedFeedback.status} {selectedFeedback.status}
</Badge> </Badge>
@ -327,24 +320,23 @@ export default function FeedbackPage() {
<div className="border rounded-lg p-4"> <div className="border rounded-lg p-4">
<h4 className="font-medium mb-2">Screenshot</h4> <h4 className="font-medium mb-2">Screenshot</h4>
<div className="flex gap-2"> <div className="flex gap-2">
<Button <Button variant="outline" onClick={() => viewScreenshot(selectedFeedback.id)}>
variant="outline"
onClick={() => viewScreenshot(selectedFeedback.id)}
>
<Eye className="h-4 w-4 mr-2" /> <Eye className="h-4 w-4 mr-2" />
View View
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
onClick={() => downloadScreenshot(selectedFeedback.id, selectedFeedback.screenshotContentType)} onClick={() =>
downloadScreenshot(
selectedFeedback.id,
selectedFeedback.screenshotContentType
)
}
> >
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
Download Download
</Button> </Button>
<Button <Button variant="outline" onClick={() => deleteScreenshot(selectedFeedback.id)}>
variant="outline"
onClick={() => deleteScreenshot(selectedFeedback.id)}
>
<Trash2 className="h-4 w-4 mr-2 text-red-500" /> <Trash2 className="h-4 w-4 mr-2 text-red-500" />
Delete Delete
</Button> </Button>
@ -358,19 +350,34 @@ export default function FeedbackPage() {
<h4 className="font-medium mb-2">Device Context</h4> <h4 className="font-medium mb-2">Device Context</h4>
<div className="grid grid-cols-2 gap-2 text-sm"> <div className="grid grid-cols-2 gap-2 text-sm">
{selectedFeedback.deviceContext.osVersion && ( {selectedFeedback.deviceContext.osVersion && (
<div><span className="text-muted-foreground">OS:</span> {selectedFeedback.deviceContext.osVersion}</div> <div>
<span className="text-muted-foreground">OS:</span>{' '}
{selectedFeedback.deviceContext.osVersion}
</div>
)} )}
{selectedFeedback.deviceContext.appVersion && ( {selectedFeedback.deviceContext.appVersion && (
<div><span className="text-muted-foreground">App:</span> {selectedFeedback.deviceContext.appVersion}</div> <div>
<span className="text-muted-foreground">App:</span>{' '}
{selectedFeedback.deviceContext.appVersion}
</div>
)} )}
{selectedFeedback.deviceContext.deviceModel && ( {selectedFeedback.deviceContext.deviceModel && (
<div><span className="text-muted-foreground">Device:</span> {selectedFeedback.deviceContext.deviceModel}</div> <div>
<span className="text-muted-foreground">Device:</span>{' '}
{selectedFeedback.deviceContext.deviceModel}
</div>
)} )}
{selectedFeedback.deviceContext.screenResolution && ( {selectedFeedback.deviceContext.screenResolution && (
<div><span className="text-muted-foreground">Resolution:</span> {selectedFeedback.deviceContext.screenResolution}</div> <div>
<span className="text-muted-foreground">Resolution:</span>{' '}
{selectedFeedback.deviceContext.screenResolution}
</div>
)} )}
{selectedFeedback.deviceContext.locale && ( {selectedFeedback.deviceContext.locale && (
<div><span className="text-muted-foreground">Locale:</span> {selectedFeedback.deviceContext.locale}</div> <div>
<span className="text-muted-foreground">Locale:</span>{' '}
{selectedFeedback.deviceContext.locale}
</div>
)} )}
</div> </div>
</div> </div>
@ -384,11 +391,16 @@ export default function FeedbackPage() {
<Dialog open={lightboxOpen} onOpenChange={setLightboxOpen}> <Dialog open={lightboxOpen} onOpenChange={setLightboxOpen}>
<DialogContent className="max-w-4xl p-0 overflow-hidden"> <DialogContent className="max-w-4xl p-0 overflow-hidden">
{screenshotUrl && ( {screenshotUrl && (
<img <div className="relative h-[80vh] w-full">
src={screenshotUrl} <Image
alt="Screenshot" src={screenshotUrl}
className="w-full h-auto max-h-[80vh] object-contain" alt="Screenshot"
/> fill
sizes="100vw"
unoptimized
className="object-contain"
/>
</div>
)} )}
</DialogContent> </DialogContent>
</Dialog> </Dialog>