feat(feedback): add user feedback/review system with admin management and hardened image upload validation

This commit is contained in:
Yusuf İpek
2026-02-24 16:05:11 +03:00
parent 4083651025
commit afa1529873
15 changed files with 1725 additions and 34 deletions
@@ -0,0 +1,88 @@
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Loader2, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog';
interface DeleteFeedbackButtonProps {
feedbackId: string;
feedbackTitle: string;
redirectTo?: string;
size?: 'default' | 'sm';
variant?: 'outline' | 'destructive';
}
export function DeleteFeedbackButton({
feedbackId,
feedbackTitle,
redirectTo,
size = 'sm',
variant = 'outline',
}: DeleteFeedbackButtonProps) {
const router = useRouter();
const [isDeleting, setIsDeleting] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleDelete = async () => {
setIsDeleting(true);
setError(null);
try {
const response = await fetch(`/api/admin/feedback/${feedbackId}`, {
method: 'DELETE',
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
setError((data as { error?: string }).error || 'Failed to delete feedback');
return;
}
if (redirectTo) {
router.push(redirectTo);
} else {
router.refresh();
}
} catch {
setError('Failed to delete feedback');
} finally {
setIsDeleting(false);
}
};
return (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={variant} size={size} disabled={isDeleting}>
{isDeleting ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" /> : <Trash2 className="mr-1.5 h-4 w-4" />}
Delete
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete this feedback?</AlertDialogTitle>
<AlertDialogDescription>
This will permanently delete &quot;{feedbackTitle}&quot;. This action cannot be undone.
</AlertDialogDescription>
{error && <p className="mt-2 text-xs text-destructive">{error}</p>}
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>Cancel</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={handleDelete} disabled={isDeleting}>
{isDeleting ? 'Deleting...' : 'Delete'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}