diff --git a/.gitignore b/.gitignore index c4ed3e9..ec06bea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env .env.local */.idea -.idea \ No newline at end of file +.idea +node_modules/ \ No newline at end of file diff --git a/frontend/app/core/lib/api/schemas.ts b/frontend/app/core/lib/api/schemas.ts index fded646..9abfa24 100644 --- a/frontend/app/core/lib/api/schemas.ts +++ b/frontend/app/core/lib/api/schemas.ts @@ -34,6 +34,7 @@ export const ServerSchema = z.object({ user: UserSchema.nullish().catch(null), type: z.enum(["java", "bedrock"]).nullish().catch(undefined), hidden: z.boolean().nullish().catch(undefined), + forced_favicon: z.boolean().nullish().catch(false), registered_date: z.number().nullish().catch(undefined), data: z.array(ServerRecordSchema).nullish().catch(undefined), client_infos: z.any().nullish().catch(undefined), diff --git a/frontend/app/pages/admin/components/ServersTab/UpdateFaviconModal.tsx b/frontend/app/pages/admin/components/ServersTab/UpdateFaviconModal.tsx index 4cd83da..f2b6a87 100644 --- a/frontend/app/pages/admin/components/ServersTab/UpdateFaviconModal.tsx +++ b/frontend/app/pages/admin/components/ServersTab/UpdateFaviconModal.tsx @@ -3,7 +3,7 @@ import type { Server } from "@/core/lib/api" import { Button } from "@/ui/components/button" import { ImageIcon } from "lucide-react" import { useAuth } from "@clerk/react" -import { updateFavicon } from "@/core/lib/api" +import { getServerIconUrl, updateFavicon } from "@/core/lib/api" import { Dialog, DialogContent, @@ -13,6 +13,10 @@ import { DialogTitle, } from "@/ui/components/dialog" +// The icon endpoint is cached with max-age=86400, so bump a module-level version +// after every successful write to avoid previewing a stale image. +let faviconCacheVersion = 0 + interface UpdateFaviconModalProps { server: Server onSuccess: () => void @@ -24,10 +28,22 @@ interface UpdateFaviconModalProps { export function UpdateFaviconModal({ server, onSuccess, triggerToast, t, open, onOpenChange }: UpdateFaviconModalProps) { const { getToken } = useAuth() - const [favicon, setFavicon] = useState(server.last_favicon || "") + // `last_favicon` is not part of the list payload (the backend nulls it out), so the + // forced flag is the only reliable source of truth for the current state. + const hasOverride = server.forced_favicon === true + const [pendingFavicon, setPendingFavicon] = useState(null) + const [pendingRemoval, setPendingRemoval] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const preview = pendingFavicon + ?? (!pendingRemoval && hasOverride + ? `${getServerIconUrl(server.id)}?v=${faviconCacheVersion}` + : null) + + const hasChanges = pendingFavicon !== null || (pendingRemoval && hasOverride) + const canReset = !pendingRemoval && (hasOverride || pendingFavicon !== null) + const handleImageUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; @@ -43,7 +59,8 @@ export function UpdateFaviconModal({ server, onSuccess, triggerToast, t, open, o if (ctx) { ctx.drawImage(img, 0, 0, 64, 64); const dataUrl = canvas.toDataURL("image/png"); - setFavicon(dataUrl); + setPendingFavicon(dataUrl); + setPendingRemoval(false); } }; img.src = event.target?.result as string; @@ -55,14 +72,20 @@ export function UpdateFaviconModal({ server, onSuccess, triggerToast, t, open, o e.preventDefault() setError(null) + if (!hasChanges) { + onOpenChange(false) + return + } + setLoading(true) try { const token = await getToken() if (!token) return - // Pass null if empty to remove the override - const payload = favicon.trim() ? favicon.trim() : null + // Pass null when removing the override, so the backend clears it + const payload = pendingFavicon ? pendingFavicon.trim() : null const res = await updateFavicon(server.id, payload, token) if (res.success) { + faviconCacheVersion++ onOpenChange(false) onSuccess() } else { @@ -89,8 +112,8 @@ export function UpdateFaviconModal({ server, onSuccess, triggerToast, t, open, o
- {favicon ? ( - Preview + {preview ? ( + Preview ) : (
@@ -106,13 +129,16 @@ export function UpdateFaviconModal({ server, onSuccess, triggerToast, t, open, o onChange={handleImageUpload} /> - {favicon && ( - )}
- {!favicon && ( + {!preview && (

{t("adminFavicon.empty")}

@@ -121,7 +147,7 @@ export function UpdateFaviconModal({ server, onSuccess, triggerToast, t, open, o
{error &&

{error}

} -