-
+
{t("settings.account.deleteAccount")}
-
+
)}
>
diff --git a/frontend/src/components/SetupWizard.tsx b/frontend/src/components/SetupWizard.tsx
index 9b53654d..705e53fe 100644
--- a/frontend/src/components/SetupWizard.tsx
+++ b/frontend/src/components/SetupWizard.tsx
@@ -4,6 +4,7 @@ import { Check, ChevronLeft, ChevronRight, Loader2, Send } from "lucide-react";
import { api, HttpError } from "../lib/api";
import { setLanguage, type LangCode } from "../i18n";
import LanguageSwitcher from "./LanguageSwitcher";
+import { Button } from "./ui/Button";
import { inputCls } from "./ui/form";
// First-run install wizard. Shown by App.tsx while the instance is unconfigured. The one-time
@@ -239,15 +240,17 @@ export default function SetupWizard() {
placeholder={t("setup.smtp.testTo")}
className={inputCls}
/>
-
{t("setup.smtp.test")}
-
+
)}
{testState === "ok" && (
diff --git a/frontend/src/components/ui/Banner.tsx b/frontend/src/components/ui/Banner.tsx
index e175ab9b..afcba0a1 100644
--- a/frontend/src/components/ui/Banner.tsx
+++ b/frontend/src/components/ui/Banner.tsx
@@ -1,6 +1,7 @@
import type { ReactNode } from "react";
import type { LucideIcon } from "lucide-react";
import { X } from "lucide-react";
+import { Button } from "./Button";
type BannerTone = "accent" | "warning";
@@ -56,13 +57,14 @@ export default function Banner({
)}
{onDismiss && (
-
-
-
+ icon={}
+ />
)}
);
diff --git a/frontend/src/components/ui/Modal.tsx b/frontend/src/components/ui/Modal.tsx
index fb819d8b..20db87a0 100644
--- a/frontend/src/components/ui/Modal.tsx
+++ b/frontend/src/components/ui/Modal.tsx
@@ -5,6 +5,7 @@ import { X } from "lucide-react";
import { useBackToClose } from "../../lib/history";
import { pushLayer, removeLayer, isTopmost } from "../../lib/layerStack";
import { useFullscreenElement } from "../../lib/fullscreen";
+import { Button } from "./Button";
// Tabbable elements inside the dialog, in DOM order. `:not([tabindex="-1"])` drops programmatically
// focusable-only nodes; the visibility filter (offsetParent) drops display:none ones (e.g. a
@@ -141,14 +142,15 @@ export default function Modal({
{title}
-
-
-
+ icon={}
+ />
{children}
diff --git a/frontend/src/components/ui/QueryState.tsx b/frontend/src/components/ui/QueryState.tsx
index fa397957..2aa85523 100644
--- a/frontend/src/components/ui/QueryState.tsx
+++ b/frontend/src/components/ui/QueryState.tsx
@@ -1,6 +1,7 @@
import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { AlertTriangle, Loader2 } from "lucide-react";
+import { Button } from "./Button";
// Centered loading state: a spinning accent ring + an optional label. The single shared loading
// visual for a page/section's first load, so all of them read the same (and can be swapped for
@@ -48,13 +49,9 @@ export function StateMessage({
{children}
{onRetry && (
-
+
{t("common.retry")}
-
+
)}
);
diff --git a/frontend/src/components/ui/SidePanel.tsx b/frontend/src/components/ui/SidePanel.tsx
index 91cdbdee..99169f37 100644
--- a/frontend/src/components/ui/SidePanel.tsx
+++ b/frontend/src/components/ui/SidePanel.tsx
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
import { Check, Pencil, Pin, PinOff, RotateCcw } from "lucide-react";
import { useScrollFade } from "../../lib/useScrollFade";
import { useHoverFocusWithin } from "../../lib/useHoverFocusWithin";
+import { Button } from "./Button";
// The shared floating side panel — one shell for the Feed / Plex / Playlists rails so they read as
// one system, and the same peek-open model as the nav rail: unpinned it sits as a small tab docked
@@ -101,14 +102,16 @@ export default function SidePanel({
{/* Row 1 — identity only: pin, icon, title (flexes + truncates last), count, customize.
Module actions live on their own row below so the title always has room. */}