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>
);
}
+17 -2
View File
@@ -14,7 +14,8 @@ import {
User,
Menu,
Keyboard,
LayoutDashboard
LayoutDashboard,
MessageSquareQuote,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
@@ -151,7 +152,21 @@ export function Header({ user }: HeaderProps) {
{/* Right side */}
<div className="flex items-center gap-2 ml-auto">
{user && (
<Button asChild variant="outline" size="sm" className="hidden sm:inline-flex">
<Link href="/feedback">
<MessageSquareQuote className="h-4 w-4 mr-1.5" />
Feedback
</Link>
</Button>
)}
{user && (
<Button asChild variant="ghost" size="icon" className="sm:hidden" aria-label="Feedback and reviews">
<Link href="/feedback">
<MessageSquareQuote className="h-4 w-4" />
</Link>
</Button>
)}
<ThemeToggle />