fix(feedback): address 6 bugs/gaps from systematic review
- Fix search icon positioning (add relative container) - Fix toast variant from 'destructive' to 'error' - Remove non-existent screenshotUrl fields from delete endpoint - Fix misleading blob path comment (blob stays at initial location) - Add download screenshot function and button - Add pointer-events-none to search icon
This commit is contained in:
parent
439610cbe5
commit
8614e3f0f1
@ -12,6 +12,7 @@ import {
|
||||
Trash2,
|
||||
ImageIcon,
|
||||
Loader2,
|
||||
Download,
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@ -104,7 +105,7 @@ export default function FeedbackPage() {
|
||||
const data = await res.json();
|
||||
setFeedback(data.items || []);
|
||||
} catch (err) {
|
||||
toast({ title: 'Error', description: 'Failed to load feedback', variant: 'destructive' });
|
||||
toast({ title: 'Error', description: 'Failed to load feedback', variant: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -118,7 +119,7 @@ export default function FeedbackPage() {
|
||||
setScreenshotUrl(data.url);
|
||||
setLightboxOpen(true);
|
||||
} catch (err) {
|
||||
toast({ title: 'Error', description: 'Failed to load screenshot', variant: 'destructive' });
|
||||
toast({ title: 'Error', description: 'Failed to load screenshot', variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +131,25 @@ export default function FeedbackPage() {
|
||||
toast({ title: 'Success', description: 'Screenshot deleted' });
|
||||
fetchFeedback();
|
||||
} catch (err) {
|
||||
toast({ title: 'Error', description: 'Failed to delete screenshot', variant: 'destructive' });
|
||||
toast({ title: 'Error', description: 'Failed to delete screenshot', variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadScreenshot(feedbackId: string, contentType?: string) {
|
||||
try {
|
||||
const res = await fetch(`/api/feedback/${feedbackId}/screenshot`);
|
||||
if (!res.ok) throw new Error('Failed to fetch screenshot');
|
||||
const data = await res.json();
|
||||
|
||||
// Create temporary link to download
|
||||
const link = document.createElement('a');
|
||||
link.href = data.url;
|
||||
link.download = `screenshot-${feedbackId}.${contentType?.split('/')[1] || 'png'}`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} catch (err) {
|
||||
toast({ title: 'Error', description: 'Failed to download screenshot', variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,8 +176,8 @@ export default function FeedbackPage() {
|
||||
<Card className="mb-6">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex flex-wrap gap-4">
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex-1 min-w-[200px] relative">
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground pointer-events-none" />
|
||||
<Input
|
||||
placeholder="Search feedback..."
|
||||
value={search}
|
||||
@ -313,14 +332,21 @@ export default function FeedbackPage() {
|
||||
onClick={() => viewScreenshot(selectedFeedback.id)}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-2" />
|
||||
View Screenshot
|
||||
View
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => downloadScreenshot(selectedFeedback.id, selectedFeedback.screenshotContentType)}
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Download
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => deleteScreenshot(selectedFeedback.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2 text-red-500" />
|
||||
Delete Screenshot
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -95,10 +95,9 @@ export async function feedbackRoutes(app: FastifyInstance): Promise<void> {
|
||||
throw new BadRequestError('Invalid contentType. Must be image/png, image/jpeg, or image/webp');
|
||||
}
|
||||
|
||||
// Generate blob path (feedbackId will be assigned after creation)
|
||||
// For pre-upload, we use a temp path that gets moved on feedback submission
|
||||
const tempFeedbackId = `temp_${userId}_${Date.now()}`;
|
||||
const blobPath = generateScreenshotBlobPath(productId, tempFeedbackId, body.contentType);
|
||||
// Generate blob path with user-specific prefix for organization
|
||||
// Note: Blob stays at this location - we don't move it after feedback creation
|
||||
const blobPath = generateScreenshotBlobPath(productId, userId, body.contentType);
|
||||
|
||||
// Generate SAS URL for upload (5 minutes expiry)
|
||||
const uploadUrl = await generateSasUrl(
|
||||
@ -155,10 +154,8 @@ export async function feedbackRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
|
||||
// Update feedback to remove screenshot reference
|
||||
const updated = await updateFeedback(req.params.id, productId, {
|
||||
await updateFeedback(req.params.id, productId, {
|
||||
screenshotBlobPath: undefined,
|
||||
screenshotUrl: undefined,
|
||||
screenshotUrlExpiresAt: undefined,
|
||||
screenshotContentType: undefined,
|
||||
screenshotSizeBytes: undefined,
|
||||
} as unknown as import('./types.js').UpdateFeedbackInput);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user