Aktueller Stand

This commit is contained in:
2026-01-22 20:15:38 +01:00
parent 0b53e47d4b
commit 33e2bc61e2
4 changed files with 114 additions and 45 deletions

View File

@@ -372,27 +372,33 @@ export default function App() {
return t("providerHintWebde"); return t("providerHintWebde");
}; };
const cleanupDisabled = true;
if (!isAuthenticated) { if (!isAuthenticated) {
return ( return (
<div className="app auth"> <div className="app auth">
<header className="topbar"> <header className="topbar">
<div> <div>
<p className="badge">v0.1</p> <p className="badge">v0.1</p>
<h1>{t("appName")}</h1> <h1>
<a className="brand-link" href="/">{t("appName")}</a>
</h1>
<p className="tagline">{t("tagline")}</p> <p className="tagline">{t("tagline")}</p>
</div> </div>
<div className="lang-compact" aria-label={t("language")}> <div className="lang-compact" aria-label={t("language")}>
<div className="lang-buttons"> <div className="lang-buttons">
{languages.map((lang) => ( <select
<button className="lang-select"
key={lang.code} value={activeLang}
type="button" onChange={(event) => switchLanguage(event.target.value)}
className={activeLang === lang.code ? "active" : ""} aria-label={t("language")}
onClick={() => switchLanguage(lang.code)} >
> {languages.map((lang) => (
{lang.code.toUpperCase()} <option key={lang.code} value={lang.code}>
</button> {lang.code.toUpperCase()}
))} </option>
))}
</select>
</div> </div>
</div> </div>
</header> </header>
@@ -440,23 +446,44 @@ export default function App() {
<header className="topbar"> <header className="topbar">
<div> <div>
<p className="badge">v0.1</p> <p className="badge">v0.1</p>
<h1>{t("appName")}</h1> <h1>
<p className="tagline">{tenant?.name ?? t("tenantFallback")}</p> <a
className="brand-link"
href="/"
onClick={() => setShowAdmin(false)}
>
{t("appName")}
</a>
</h1>
{user?.role === "ADMIN" && (
<p className="tagline">{tenant?.name ?? t("tenantFallback")}</p>
)}
</div> </div>
<div className="lang-compact"> <div className="lang-compact">
<span className="user-label">{user?.email ?? ""}</span> <span className="user-label">{user?.email ?? ""}</span>
<div className="lang-buttons"> <div className="lang-buttons">
{languages.map((lang) => ( {user?.role === "ADMIN" && (
<button <button
key={lang.code} className="ghost admin-toggle"
type="button" type="button"
className={activeLang === lang.code ? "active" : ""} onClick={() => setShowAdmin((prev) => !prev)}
onClick={() => switchLanguage(lang.code)}
> >
{lang.code.toUpperCase()} {showAdmin ? t("userWorkspace") : t("adminConsole")}
</button> </button>
))} )}
<button type="button" onClick={handleLogout}>{t("logout")}</button> <button className="ghost logout-button" type="button" onClick={handleLogout}>{t("logout")}</button>
<select
className="lang-select"
value={activeLang}
onChange={(event) => switchLanguage(event.target.value)}
aria-label={t("language")}
>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.code.toUpperCase()}
</option>
))}
</select>
</div> </div>
</div> </div>
</header> </header>
@@ -467,7 +494,13 @@ export default function App() {
<h2>{t("welcome")}</h2> <h2>{t("welcome")}</h2>
<p className="description">{t("description")}</p> <p className="description">{t("description")}</p>
<div className="actions"> <div className="actions">
<button className="primary" type="button" onClick={handleStartCleanup}> <button
className="primary"
type="button"
onClick={handleStartCleanup}
disabled={cleanupDisabled}
title={t("cleanupDisabled")}
>
{t("start")} {t("start")}
</button> </button>
</div> </div>
@@ -498,19 +531,6 @@ export default function App() {
</div> </div>
</section> </section>
{user?.role === "ADMIN" && (
<div className="admin-switch">
<button
className="ghost"
type="button"
onClick={() => setShowAdmin((prev) => !prev)}
>
{showAdmin ? t("userWorkspace") : t("adminConsole")}
</button>
<span className="status-badge">{t("admin")}</span>
</div>
)}
{user?.role === "ADMIN" && showAdmin ? ( {user?.role === "ADMIN" && showAdmin ? (
<section className="admin-only"> <section className="admin-only">
<div className="section-header"> <div className="section-header">
@@ -604,7 +624,13 @@ export default function App() {
</label> </label>
</div> </div>
<div className="card-actions"> <div className="card-actions">
<button className="primary" type="button" onClick={handleStartCleanup}> <button
className="primary"
type="button"
onClick={handleStartCleanup}
disabled={cleanupDisabled}
title={t("cleanupDisabled")}
>
{t("start")} {t("start")}
</button> </button>
</div> </div>

View File

@@ -70,6 +70,7 @@
"cleanupDryRun": "Dry run (keine Änderungen)", "cleanupDryRun": "Dry run (keine Änderungen)",
"cleanupUnsubscribe": "Unsubscribe aktiv", "cleanupUnsubscribe": "Unsubscribe aktiv",
"cleanupRouting": "Routing aktiv", "cleanupRouting": "Routing aktiv",
"cleanupDisabled": "Bereinigung ist noch nicht verfügbar.",
"rulesTitle": "Regeln", "rulesTitle": "Regeln",
"rulesName": "Rule Name", "rulesName": "Rule Name",
"rulesEnabled": "Rule aktiv", "rulesEnabled": "Rule aktiv",

View File

@@ -70,6 +70,7 @@
"cleanupDryRun": "Dry run (no changes)", "cleanupDryRun": "Dry run (no changes)",
"cleanupUnsubscribe": "Unsubscribe enabled", "cleanupUnsubscribe": "Unsubscribe enabled",
"cleanupRouting": "Routing enabled", "cleanupRouting": "Routing enabled",
"cleanupDisabled": "Cleanup is not available yet.",
"rulesTitle": "Rules", "rulesTitle": "Rules",
"rulesName": "Rule name", "rulesName": "Rule name",
"rulesEnabled": "Rule enabled", "rulesEnabled": "Rule enabled",

View File

@@ -115,6 +115,40 @@ h1 {
margin-bottom: 6px; margin-bottom: 6px;
} }
.brand-link {
color: inherit;
text-decoration: none;
user-select: none;
display: inline-flex;
align-items: center;
gap: 10px;
letter-spacing: 0.01em;
position: relative;
}
.brand-link:hover {
text-decoration: none;
color: var(--primary-strong);
}
.brand-link::after {
content: "";
position: absolute;
left: 0;
bottom: -6px;
width: 42%;
height: 2px;
background: linear-gradient(90deg, rgba(37, 99, 235, 0.45), rgba(37, 99, 235, 0));
border-radius: 999px;
opacity: 0.6;
transition: width 0.2s ease, opacity 0.2s ease;
}
.brand-link:hover::after {
width: 60%;
opacity: 1;
}
.tagline { .tagline {
color: var(--muted); color: var(--muted);
font-size: 16px; font-size: 16px;
@@ -135,23 +169,30 @@ h1 {
.lang-buttons { .lang-buttons {
display: flex; display: flex;
gap: 6px; gap: 6px;
flex-wrap: wrap; flex-wrap: nowrap;
align-items: center;
justify-content: flex-end;
} }
.lang-compact button { .lang-select {
border: 1px solid var(--border); border: 1px solid rgba(37, 99, 235, 0.25);
background: #fff; background: transparent;
padding: 4px 8px; padding: 2px 16px 2px 8px;
border-radius: 999px; border-radius: 10px;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
height: 22px;
width: 48px;
appearance: none;
} }
.lang-compact button.active { .admin-toggle {
background: var(--primary); border-style: dashed;
color: #fff; }
border-color: var(--primary);
.logout-button {
margin-left: auto;
} }
.hero { .hero {