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,
|
Trash2,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Download,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
@ -104,7 +105,7 @@ export default function FeedbackPage() {
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setFeedback(data.items || []);
|
setFeedback(data.items || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({ title: 'Error', description: 'Failed to load feedback', variant: 'destructive' });
|
toast({ title: 'Error', description: 'Failed to load feedback', variant: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -118,7 +119,7 @@ export default function FeedbackPage() {
|
|||||||
setScreenshotUrl(data.url);
|
setScreenshotUrl(data.url);
|
||||||
setLightboxOpen(true);
|
setLightboxOpen(true);
|
||||||
} catch (err) {
|
} 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' });
|
toast({ title: 'Success', description: 'Screenshot deleted' });
|
||||||
fetchFeedback();
|
fetchFeedback();
|
||||||
} catch (err) {
|
} 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">
|
<Card className="mb-6">
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="flex flex-wrap gap-4">
|
<div className="flex flex-wrap gap-4">
|
||||||
<div className="flex-1 min-w-[200px]">
|
<div className="flex-1 min-w-[200px] relative">
|
||||||
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground pointer-events-none" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search feedback..."
|
placeholder="Search feedback..."
|
||||||
value={search}
|
value={search}
|
||||||
@ -313,14 +332,21 @@ export default function FeedbackPage() {
|
|||||||
onClick={() => viewScreenshot(selectedFeedback.id)}
|
onClick={() => viewScreenshot(selectedFeedback.id)}
|
||||||
>
|
>
|
||||||
<Eye className="h-4 w-4 mr-2" />
|
<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>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => deleteScreenshot(selectedFeedback.id)}
|
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 Screenshot
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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');
|
throw new BadRequestError('Invalid contentType. Must be image/png, image/jpeg, or image/webp');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate blob path (feedbackId will be assigned after creation)
|
// Generate blob path with user-specific prefix for organization
|
||||||
// For pre-upload, we use a temp path that gets moved on feedback submission
|
// Note: Blob stays at this location - we don't move it after feedback creation
|
||||||
const tempFeedbackId = `temp_${userId}_${Date.now()}`;
|
const blobPath = generateScreenshotBlobPath(productId, userId, body.contentType);
|
||||||
const blobPath = generateScreenshotBlobPath(productId, tempFeedbackId, body.contentType);
|
|
||||||
|
|
||||||
// Generate SAS URL for upload (5 minutes expiry)
|
// Generate SAS URL for upload (5 minutes expiry)
|
||||||
const uploadUrl = await generateSasUrl(
|
const uploadUrl = await generateSasUrl(
|
||||||
@ -155,10 +154,8 @@ export async function feedbackRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update feedback to remove screenshot reference
|
// Update feedback to remove screenshot reference
|
||||||
const updated = await updateFeedback(req.params.id, productId, {
|
await updateFeedback(req.params.id, productId, {
|
||||||
screenshotBlobPath: undefined,
|
screenshotBlobPath: undefined,
|
||||||
screenshotUrl: undefined,
|
|
||||||
screenshotUrlExpiresAt: undefined,
|
|
||||||
screenshotContentType: undefined,
|
screenshotContentType: undefined,
|
||||||
screenshotSizeBytes: undefined,
|
screenshotSizeBytes: undefined,
|
||||||
} as unknown as import('./types.js').UpdateFeedbackInput);
|
} as unknown as import('./types.js').UpdateFeedbackInput);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user