feat(admin): server-safe StatusCard and Import hub polish
CI / check (push) Failing after 8s
CI / release (push) Skipped
CI / deploy (push) Skipped

Split OnlineUsersWidget from StatusCard, decouple ad delete button, sync badge import to ExternalTexts+WebsiteBadges, add Import section hub with cancelable SSE jobs and upload SQL option.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-08-01 15:48:21 +02:00
1 parent db957d7fb1
commit 9c4949186c
21 files changed
+576 -249

No files matched your search

@@ -15,7 +15,6 @@ import {
} from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { importBadgeFromRemote } from "@/actions/import-badges";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -25,6 +24,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { adminFetch } from "@/lib/admin-fetch";
import { cn } from "@/lib/utils";
import { BadgeEditorDialog } from "./badge-editor-dialog";
@@ -119,14 +119,32 @@ export function ImportBadgesClient() {
fetchBadges(activeSearch, newOffset, allHotels);
}
async function doImport(badge: BadgeItem) {
setImportingCode(badge.code);
try {
const result = await importBadgeFromRemote({
async function postImportBadge(badge: BadgeItem) {
const res = await adminFetch("/api/admin/import/badges", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code: badge.code,
name: badge.name,
description: badge.description,
});
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
return {
ok: false as const,
error:
(data as { error?: string }).error ||
`Failed to import ${badge.code}`,
};
}
return { ok: true as const };
}
async function doImport(badge: BadgeItem) {
setImportingCode(badge.code);
try {
const result = await postImportBadge(badge);
if (result.ok) {
toast.success(`${badge.code} imported`);
setBadges((prev) =>
@@ -140,7 +158,7 @@ export function ImportBadgesClient() {
return n;
});
} else {
toast.error(result.error || `Failed to import ${badge.code}`);
toast.error(result.error);
}
} catch {
toast.error(`Network error importing ${badge.code}`);
@@ -159,11 +177,7 @@ export function ImportBadgesClient() {
fail = 0;
for (const badge of toImport) {
try {
const result = await importBadgeFromRemote({
code: badge.code,
name: badge.name,
description: badge.description,
});
const result = await postImportBadge(badge);
if (result.ok) {
ok++;
setBadges((prev) =>