fix: compact filter popover; feat: i18n (de/en)
Build & Push Docker Image / build (push) Successful in 2m31s
Build & Push Docker Image / build (push) Successful in 2m31s
This commit is contained in:
@@ -27,8 +27,10 @@ import { useToast } from "@/hooks/use-toast";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||
import { Trash2, RotateCcw, Search, ShieldAlert, Trash as TrashIcon, RefreshCcw } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Trash() {
|
||||
const { t } = useTranslation();
|
||||
const { user, isAdmin, hasFeature, isLoading: authLoading } = useAuth();
|
||||
const { toast } = useToast();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -139,10 +141,10 @@ export default function Trash() {
|
||||
<Layout>
|
||||
<div className="flex flex-col items-center justify-center py-20 gap-4">
|
||||
<ShieldAlert className="w-12 h-12 text-muted-foreground" />
|
||||
<h2 className="text-2xl font-bold">Trash requires a higher tier</h2>
|
||||
<p className="text-muted-foreground">The trash is available to Premium and Enterprise users.</p>
|
||||
<h2 className="text-2xl font-bold">{t("trash.requiresPremium")}</h2>
|
||||
<p className="text-muted-foreground">{t("trash.requiresPremiumSub")}</p>
|
||||
<Button variant="outline" asChild>
|
||||
<a href="/tools">Back to tools</a>
|
||||
<a href="/tools">{t("common.backToTools")}</a>
|
||||
</Button>
|
||||
</div>
|
||||
</Layout>
|
||||
@@ -155,16 +157,16 @@ export default function Trash() {
|
||||
<Layout>
|
||||
<div className="space-y-6 pb-10">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2">Trash</h1>
|
||||
<p className="text-muted-foreground">Deleted tools are kept here until they are restored or permanently removed.</p>
|
||||
<h1 className="text-3xl font-bold tracking-tight mb-2">{t("trash.title")}</h1>
|
||||
<p className="text-muted-foreground">{t("trash.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between gap-4 flex-wrap">
|
||||
<div className="space-y-1.5">
|
||||
<CardTitle>Trashed Tools</CardTitle>
|
||||
<CardTitle>{t("trash.trashedTools")}</CardTitle>
|
||||
<CardDescription>
|
||||
{selectedIds.length > 0 ? `${selectedIds.length} selected` : `${trashed.length} tool(s) in trash`}
|
||||
{selectedIds.length > 0 ? t("trash.selected", { count: selectedIds.length }) : t("trash.toolsInTrash", { count: trashed.length })}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
@@ -172,7 +174,7 @@ export default function Trash() {
|
||||
<Search className="w-4 h-4 absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
||||
<Input
|
||||
className="pl-8 w-56"
|
||||
placeholder="Search trash…"
|
||||
placeholder={`${t("common.search")}…`}
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
/>
|
||||
@@ -183,7 +185,7 @@ export default function Trash() {
|
||||
disabled={selectedIds.length === 0 || restore.isPending}
|
||||
onClick={() => handleRestore(selectedIds)}
|
||||
>
|
||||
<RotateCcw className="w-4 h-4 mr-2" /> Restore ({selectedIds.length})
|
||||
<RotateCcw className="w-4 h-4 mr-2" /> {t("trash.restore")} ({selectedIds.length})
|
||||
</Button>
|
||||
{isAdmin && (
|
||||
<>
|
||||
@@ -193,7 +195,7 @@ export default function Trash() {
|
||||
disabled={selectedIds.length === 0 || deletePermanent.isPending}
|
||||
onClick={() => setConfirmDelete(true)}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" /> Delete permanently ({selectedIds.length})
|
||||
<TrashIcon className="w-4 h-4 mr-2" /> {t("trash.deletePermanently")} ({selectedIds.length})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -201,7 +203,7 @@ export default function Trash() {
|
||||
disabled={trashed.length === 0 || empty.isPending}
|
||||
onClick={() => setConfirmEmpty(true)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 mr-2" /> Empty trash
|
||||
<Trash2 className="w-4 h-4 mr-2" /> {t("trash.emptyTrash")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -215,7 +217,7 @@ export default function Trash() {
|
||||
) : trashed.length === 0 ? (
|
||||
<div className="text-sm text-muted-foreground py-8 text-center flex flex-col items-center gap-2">
|
||||
<RefreshCcw className="w-6 h-6 text-muted-foreground/50" />
|
||||
{search ? "No tools match your search." : "The trash is empty."}
|
||||
{search ? t("trash.noMatch") : t("trash.empty")}
|
||||
</div>
|
||||
) : (
|
||||
<Table>
|
||||
@@ -230,8 +232,8 @@ export default function Trash() {
|
||||
</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Category</TableHead>
|
||||
<TableHead>Deleted at</TableHead>
|
||||
<TableHead>Deleted by</TableHead>
|
||||
<TableHead>{t("trash.deletedAt")}</TableHead>
|
||||
<TableHead>{t("trash.deletedBy")}</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -294,7 +296,7 @@ export default function Trash() {
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
onClick={() => handleDeletePermanent(selectedIds)}
|
||||
>
|
||||
Delete permanently
|
||||
{t("trash.deletePermanently")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -314,7 +316,7 @@ export default function Trash() {
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
onClick={handleEmpty}
|
||||
>
|
||||
Empty trash
|
||||
{t("trash.emptyTrash")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
Reference in New Issue
Block a user