feat(05-01): settings shell, header link, and i18n keys for dashboard phase

- Create settings/layout.tsx with SettingsSidebar and back-to-dashboard link
- Create settings/page.tsx with redirect to /settings/dashboard
- Create settings-sidebar.tsx with Widgets and Calendar nav items, aria-current
- Add Settings link in header user dropdown (gear icon, before logout)
- Add settings namespace (DE+EN) with all category and action keys
- Add widgets namespace (DE+EN) with all widget names, descriptions, error states
- Add header.settings key ("Einstellungen"/"Settings")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 11:23:47 +02:00
parent d5e1c42e64
commit 8a9c1ae6a9
6 changed files with 231 additions and 0 deletions
@@ -0,0 +1,47 @@
'use client';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { SettingsSidebar } from '@/components/settings/settings-sidebar';
/**
* Settings nested layout (D-19, D-20).
* Renders within the portal AppShell, adding its own sub-sidebar on the left.
* Back-to-dashboard link at top of content area per UI-SPEC.
*/
export default function SettingsLayout({
children,
}: {
children: React.ReactNode;
}) {
const t = useTranslations('settings');
return (
<div className="flex h-full">
<SettingsSidebar />
<div className="flex-1 overflow-y-auto p-6">
{/* Back to Dashboard link */}
<Link
href="/"
className="mb-4 inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="15 18 9 12 15 6" />
</svg>
{t('backToDashboard')}
</Link>
{children}
</div>
</div>
);
}
@@ -0,0 +1,8 @@
import { redirect } from 'next/navigation';
/**
* Settings index page — redirects to /settings/dashboard (the default category).
*/
export default function SettingsPage() {
redirect('/settings/dashboard');
}
+26
View File
@@ -1,6 +1,7 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { useSidebarStore } from '@/lib/stores/sidebar-store';
import { useAuthStore } from '@/lib/stores/auth-store';
@@ -128,6 +129,31 @@ export function Header() {
)}
</div>
{/* Settings link */}
<div className="px-2 py-1">
<Link
href="/settings"
onClick={() => setDropdownOpen(false)}
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-foreground hover:bg-muted transition-colors"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
{tHeader('settings')}
</Link>
</div>
{/* Logout button */}
<div className="px-2 py-1">
<button
@@ -0,0 +1,64 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useTranslations } from 'next-intl';
/**
* Settings sub-sidebar (D-19, D-20).
* Phase 05 categories: Dashboard > Widgets, Calendar.
* Active item uses sidebar-accent pattern with aria-current="page".
*/
export function SettingsSidebar() {
const t = useTranslations('settings');
const pathname = usePathname();
const items = [
{
label: t('categoryWidgets'),
href: '/settings/dashboard',
},
{
label: t('categoryCalendar'),
href: '/settings/dashboard/calendar',
},
];
const isActive = (href: string) => {
if (href === '/settings/dashboard') {
// Active when exactly on /settings/dashboard (not on sub-routes like /calendar)
return pathname === '/settings/dashboard';
}
return pathname.startsWith(href);
};
return (
<aside
className="flex w-[220px] shrink-0 flex-col border-r border-sidebar-border bg-sidebar"
role="navigation"
aria-label={t('navLabel')}
>
<div className="p-4">
<h2 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{t('categoryDashboard')}
</h2>
</div>
<nav className="flex flex-col gap-1 px-3">
{items.map((item) => (
<Link
key={item.href}
href={item.href}
className={`flex items-center rounded-md px-2 py-1.5 text-sm transition-colors ${
isActive(item.href)
? 'bg-sidebar-accent text-sidebar-accent-foreground font-medium'
: 'text-sidebar-foreground hover:bg-muted'
}`}
aria-current={isActive(item.href) ? 'page' : undefined}
>
{item.label}
</Link>
))}
</nav>
</aside>
);
}
+43
View File
@@ -67,6 +67,7 @@
"breadcrumb": {
"home": "Startseite"
},
"settings": "Einstellungen",
"logout": "Abmelden",
"role": {
"SUPER_ADMIN": "Super-Admin",
@@ -98,6 +99,48 @@
"empty": "Keine Widgets aktiv",
"addWidget": "Widget hinzufuegen"
},
"settings": {
"link": "Einstellungen",
"navLabel": "Einstellungen",
"backToDashboard": "Zurueck zum Dashboard",
"categoryDashboard": "Dashboard",
"categoryWidgets": "Widgets",
"categoryCalendar": "Kalender",
"sourceDeleteConfirm": "Moechten Sie diese Kalenderquelle wirklich loeschen?",
"sourceDeleteCta": "Quelle loeschen",
"providerDeleteConfirm": "Moechten Sie diesen Suchanbieter wirklich loeschen?",
"providerDeleteCta": "Anbieter loeschen",
"cancel": "Abbrechen"
},
"widgets": {
"emptyHeading": "Keine Widgets aktiv",
"emptyBody": "Klicken Sie auf Bearbeiten, um Widgets hinzuzufuegen.",
"addWidget": "Widget hinzufuegen",
"catalogTitle": "Widget hinzufuegen",
"deleteTooltip": "Widget entfernen",
"editMode": "Dashboard bearbeiten",
"saveChanges": "Aenderungen speichern",
"layoutLoadError": "Dashboard konnte nicht geladen werden. Bitte laden Sie die Seite neu.",
"widgetSaveError": "Aenderungen konnten nicht gespeichert werden. Bitte versuchen Sie es erneut.",
"clock.name": "Uhr",
"clock.description": "Zeigt die aktuelle Uhrzeit an",
"clock.dateHint": "Datum anzeigen",
"search.name": "Suchleiste",
"search.description": "Schnellsuche im Web",
"search.placeholder": "Suchen...",
"calendar.name": "Kalender",
"calendar.description": "Kommende Termine",
"calendar.emptyNoSources": "Keine Kalenderquellen konfiguriert",
"calendar.emptyNoEvents": "Keine anstehenden Termine",
"calendar.connectionSuccess": "Verbindung erfolgreich",
"calendar.connectionError": "Verbindung fehlgeschlagen. Bitte ueberpruefen Sie die URL und Zugangsdaten.",
"calendar.addSource": "Quelle hinzufuegen",
"calendar.sourceEmpty": "Keine Kalenderquellen eingerichtet. Fuegen Sie eine Quelle hinzu, um Termine anzuzeigen.",
"note.name": "Notizen",
"note.description": "Freitext-Notizen mit Markdown",
"note.defaultTitle": "Notiz",
"note.autosaveError": "Speichern fehlgeschlagen"
},
"admin": {
"users": {
"title": "Benutzerverwaltung",
+43
View File
@@ -67,6 +67,7 @@
"breadcrumb": {
"home": "Home"
},
"settings": "Settings",
"logout": "Sign Out",
"role": {
"SUPER_ADMIN": "Super Admin",
@@ -98,6 +99,48 @@
"empty": "No active widgets",
"addWidget": "Add widget"
},
"settings": {
"link": "Settings",
"navLabel": "Settings",
"backToDashboard": "Back to Dashboard",
"categoryDashboard": "Dashboard",
"categoryWidgets": "Widgets",
"categoryCalendar": "Calendar",
"sourceDeleteConfirm": "Are you sure you want to delete this calendar source?",
"sourceDeleteCta": "Delete source",
"providerDeleteConfirm": "Are you sure you want to delete this search provider?",
"providerDeleteCta": "Delete provider",
"cancel": "Cancel"
},
"widgets": {
"emptyHeading": "No active widgets",
"emptyBody": "Click edit to add widgets to your dashboard.",
"addWidget": "Add widget",
"catalogTitle": "Add widget",
"deleteTooltip": "Remove widget",
"editMode": "Edit dashboard",
"saveChanges": "Save changes",
"layoutLoadError": "Could not load dashboard. Please reload the page.",
"widgetSaveError": "Could not save changes. Please try again.",
"clock.name": "Clock",
"clock.description": "Shows the current time",
"clock.dateHint": "Show date",
"search.name": "Search",
"search.description": "Quick web search",
"search.placeholder": "Search...",
"calendar.name": "Calendar",
"calendar.description": "Upcoming events",
"calendar.emptyNoSources": "No calendar sources configured",
"calendar.emptyNoEvents": "No upcoming events",
"calendar.connectionSuccess": "Connection successful",
"calendar.connectionError": "Connection failed. Please check the URL and credentials.",
"calendar.addSource": "Add source",
"calendar.sourceEmpty": "No calendar sources configured. Add a source to display events.",
"note.name": "Notes",
"note.description": "Free-text notes with Markdown",
"note.defaultTitle": "Note",
"note.autosaveError": "Save failed"
},
"admin": {
"users": {
"title": "User Management",