feat: complete UI overhaul (liquid glass theme, i18n, FontAwesome)

This commit is contained in:
2026-06-03 22:00:37 +02:00
parent e5a9936066
commit d34d34565f
23 changed files with 732 additions and 580 deletions
+31 -40
View File
@@ -1,16 +1,18 @@
import { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
ArrowLeft,
CheckCircle2,
AlertTriangle,
Loader2,
Plug,
Trash2,
Server,
} from "lucide-react";
faArrowLeft,
faCheckCircle,
faExclamationTriangle,
faCircleNotch,
faPlug,
faTrashAlt,
faServer,
} from "@fortawesome/free-solid-svg-icons";
import { useToast } from "../lib/toast";
import { getServerStatus, type ServerStatus } from "../lib/llm";
import { useI18n } from "../lib/i18n";
const HISTORY_KEY = "melodymuse-history";
const DRAFT_KEY = "melodymuse-draft";
@@ -44,6 +46,7 @@ function readLocalDataSummary(): LocalDataSummary {
export function SettingsPage() {
const toast = useToast();
const navigate = useNavigate();
const { t } = useI18n();
const [status, setStatus] = useState<ServerStatus | null>(null);
const [statusError, setStatusError] = useState<string | null>(null);
@@ -100,7 +103,7 @@ export function SettingsPage() {
const onClearAll = () => {
if (
!window.confirm(
"Clear all MelodyMuse data from this browser? This will remove the recent generations, the in-progress draft, and the theme preference. The API key on the server is NOT affected.",
"Clear all MelodyMuse data from this browser? This will remove the recent generations, the in-progress draft, and the theme preference.",
)
)
return;
@@ -121,9 +124,9 @@ export function SettingsPage() {
className="p-2 -ml-2 rounded-md text-fg-muted hover:text-fg hover:bg-bg-hover transition-colors"
aria-label="Back"
>
<ArrowLeft className="w-5 h-5" />
<FontAwesomeIcon icon={faArrowLeft} className="w-4 h-4" />
</button>
<h1 className="text-sm font-semibold text-fg">Settings</h1>
<h1 className="text-sm font-semibold text-fg">{t.settings}</h1>
</div>
</header>
@@ -131,7 +134,7 @@ export function SettingsPage() {
<div className="card p-6 space-y-6">
<section>
<h2 className="text-sm font-semibold text-fg mb-3 flex items-center gap-2">
<Server className="w-4 h-4" />
<FontAwesomeIcon icon={faServer} className="w-4 h-4 text-accent-primary" />
Server status
</h2>
@@ -158,7 +161,7 @@ export function SettingsPage() {
</p>
)}
{statusError && !checking && (
<p className="text-xs text-rose-300 break-words">
<p className="text-xs text-rose-400 break-words">
{statusError}
</p>
)}
@@ -171,21 +174,13 @@ export function SettingsPage() {
className="btn-ghost"
>
{checking ? (
<Loader2 className="w-4 h-4 animate-spin" />
<FontAwesomeIcon icon={faCircleNotch} spin className="w-4 h-4" />
) : (
<Plug className="w-4 h-4" />
<FontAwesomeIcon icon={faPlug} className="w-4 h-4" />
)}
<span>Re-check</span>
</button>
</div>
<p className="text-xs text-fg-muted">
The LLM provider's API key lives on this server in the{" "}
<code className="px-1 py-0.5 rounded bg-bg-hover border border-border font-mono">
LLM_API_KEY
</code>{" "}
environment variable. The browser never sees it.
</p>
</div>
</section>
@@ -199,16 +194,16 @@ export function SettingsPage() {
</p>
<ul className="text-xs text-fg-muted space-y-1 mb-4">
<li>
<code className="font-mono">melodymuse-history</code> {" "}
<code className="font-mono text-accent-primary">melodymuse-history</code> {" "}
{summary.historyEntries} recent generation
{summary.historyEntries === 1 ? "" : "s"}
</li>
<li>
<code className="font-mono">melodymuse-draft</code> {" "}
<code className="font-mono text-accent-primary">melodymuse-draft</code> {" "}
{summary.hasDraft ? "saved (in-progress input)" : "empty"}
</li>
<li>
<code className="font-mono">melodymuse-theme</code> {" "}
<code className="font-mono text-accent-primary">melodymuse-theme</code> {" "}
{summary.hasTheme ? "set" : "using default"}
</li>
</ul>
@@ -219,7 +214,7 @@ export function SettingsPage() {
disabled={summary.historyEntries === 0}
className="btn-secondary"
>
<Trash2 className="w-4 h-4" />
<FontAwesomeIcon icon={faTrashAlt} className="w-4 h-4" />
Clear recent generations
</button>
<button
@@ -228,30 +223,26 @@ export function SettingsPage() {
disabled={!summary.hasDraft}
className="btn-secondary"
>
<Trash2 className="w-4 h-4" />
<FontAwesomeIcon icon={faTrashAlt} className="w-4 h-4" />
Clear in-progress draft
</button>
<button
type="button"
onClick={onClearAll}
className="btn-secondary text-rose-300 hover:text-rose-200"
className="btn-secondary text-rose-400 hover:text-rose-300"
>
<Trash2 className="w-4 h-4" />
<FontAwesomeIcon icon={faTrashAlt} className="w-4 h-4" />
Clear all local data
</button>
</div>
</section>
<p className="text-center text-xs text-fg-muted">
API key on the server · Recent generations in this browser only
</p>
<div className="text-center">
<div className="text-center pt-2">
<Link
to="/"
className="text-xs text-fg-muted hover:text-fg transition-colors"
className="text-xs font-semibold text-fg-muted hover:text-accent-primary transition-colors flex items-center justify-center gap-2"
>
Back to generator
<FontAwesomeIcon icon={faArrowLeft} /> Back to generator
</Link>
</div>
</div>
@@ -262,7 +253,7 @@ export function SettingsPage() {
function StatusDot({ checking, ok }: { checking: boolean; ok: boolean }) {
if (checking)
return <Loader2 className="w-5 h-5 text-fg-muted animate-spin shrink-0" />;
if (ok) return <CheckCircle2 className="w-5 h-5 text-emerald-400 shrink-0" />;
return <AlertTriangle className="w-5 h-5 text-rose-400 shrink-0" />;
return <FontAwesomeIcon icon={faCircleNotch} spin className="w-5 h-5 text-fg-muted shrink-0" />;
if (ok) return <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5 text-emerald-400 shrink-0" />;
return <FontAwesomeIcon icon={faExclamationTriangle} className="w-5 h-5 text-rose-400 shrink-0" />;
}