feat(profile): add username + preferred_locale; wire to Inertia + middleware
- DB: users.username (unique), users.preferred_locale (default from app.locale) - Backend: validation, model fillable; share supportedLocales; SetLocaleFromUser - Frontend: profile page fields + types - Filament: SuperAdmin profile page with username/language feat(admin-nav): move Tasks to Bibliothek and add menu labels fix(tasks-table): show localized title/emotion/event type; add translated headers feat(l10n): add missing table headers for emotions and event types; normalize en/de files refactor: tidy translations for tasks/emotions/event types
This commit is contained in:
@@ -22,7 +22,7 @@ const breadcrumbs: BreadcrumbItem[] = [
|
||||
];
|
||||
|
||||
export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail: boolean; status?: string }) {
|
||||
const { auth } = usePage<SharedData>().props;
|
||||
const { auth, supportedLocales } = usePage<SharedData>().props as SharedData & { supportedLocales: string[] };
|
||||
|
||||
return (
|
||||
<AppLayout breadcrumbs={breadcrumbs}>
|
||||
@@ -74,6 +74,40 @@ export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail:
|
||||
<InputError className="mt-2" message={errors.email} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
|
||||
<Input
|
||||
id="username"
|
||||
className="mt-1 block w-full"
|
||||
defaultValue={(auth.user as any).username ?? ''}
|
||||
name="username"
|
||||
autoComplete="username"
|
||||
placeholder="Username"
|
||||
/>
|
||||
|
||||
<InputError className="mt-2" message={errors.username} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="preferred_locale">Language</Label>
|
||||
|
||||
<select
|
||||
id="preferred_locale"
|
||||
name="preferred_locale"
|
||||
defaultValue={(auth.user as any).preferred_locale ?? 'en'}
|
||||
className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{(supportedLocales ?? ['de', 'en']).map((l) => (
|
||||
<option key={l} value={l}>
|
||||
{l.toUpperCase()}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<InputError className="mt-2" message={errors.preferred_locale} />
|
||||
</div>
|
||||
|
||||
{mustVerifyEmail && auth.user.email_verified_at === null && (
|
||||
<div>
|
||||
<p className="-mt-4 text-sm text-muted-foreground">
|
||||
|
||||
3
resources/js/types/index.d.ts
vendored
3
resources/js/types/index.d.ts
vendored
@@ -27,6 +27,7 @@ export interface SharedData {
|
||||
quote: { message: string; author: string };
|
||||
auth: Auth;
|
||||
sidebarOpen: boolean;
|
||||
supportedLocales?: string[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -34,6 +35,8 @@ export interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
preferred_locale?: string;
|
||||
avatar?: string;
|
||||
email_verified_at: string | null;
|
||||
created_at: string;
|
||||
|
||||
222
resources/lang/de/admin.php
Normal file
222
resources/lang/de/admin.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'nav' => [
|
||||
'platform' => 'Plattform',
|
||||
'library' => 'Bibliothek',
|
||||
'content' => 'Inhalte',
|
||||
],
|
||||
|
||||
'common' => [
|
||||
'key' => 'Schlüssel',
|
||||
'value' => 'Wert',
|
||||
'locale' => 'Sprache',
|
||||
'german' => 'Deutsch',
|
||||
'english' => 'Englisch',
|
||||
'import' => 'Import',
|
||||
'import_csv' => 'CSV importieren',
|
||||
'download_csv_template' => 'CSV‑Vorlage herunterladen',
|
||||
'csv_file' => 'CSV‑Datei',
|
||||
'close' => 'Schließen',
|
||||
'hash' => '#',
|
||||
'slug' => 'Slug',
|
||||
'event' => 'Veranstaltung',
|
||||
'tenant' => 'Mandant',
|
||||
'uploads' => 'Uploads',
|
||||
'uploads_today' => 'Uploads heute',
|
||||
'thumb' => 'Vorschau',
|
||||
'likes' => 'Gefällt mir',
|
||||
'emotion' => 'Emotion',
|
||||
'event_type' => 'Eventtyp',
|
||||
'last_activity' => 'Letzte Aktivität',
|
||||
'credits' => 'Credits',
|
||||
'settings' => 'Einstellungen',
|
||||
'join' => 'Beitreten',
|
||||
'unnamed' => 'Ohne Namen',
|
||||
],
|
||||
|
||||
'photos' => [
|
||||
'fields' => [
|
||||
'event' => 'Veranstaltung',
|
||||
'photo' => 'Foto',
|
||||
'is_featured' => 'Hervorgehoben',
|
||||
'metadata' => 'Metadaten',
|
||||
'likes' => 'Gefällt mir',
|
||||
],
|
||||
'actions' => [
|
||||
'feature' => 'Hervorheben',
|
||||
'unfeature' => 'Hervorhebung entfernen',
|
||||
'feature_selected' => 'Auswahl hervorheben',
|
||||
'unfeature_selected' => 'Hervorhebung der Auswahl entfernen',
|
||||
],
|
||||
'table' => [
|
||||
'photo' => 'Foto',
|
||||
'event' => 'Veranstaltung',
|
||||
'likes' => 'Gefällt mir',
|
||||
],
|
||||
],
|
||||
|
||||
'events' => [
|
||||
'fields' => [
|
||||
'tenant' => 'Mandant',
|
||||
'name' => 'Eventname',
|
||||
'slug' => 'Slug',
|
||||
'date' => 'Eventdatum',
|
||||
'type' => 'Eventtyp',
|
||||
'default_locale' => 'Standardsprache',
|
||||
'is_active' => 'Aktiv',
|
||||
'settings' => 'Einstellungen',
|
||||
],
|
||||
'table' => [
|
||||
'tenant' => 'Mandant',
|
||||
'join' => 'Beitreten',
|
||||
],
|
||||
'actions' => [
|
||||
'toggle_active' => 'Aktiv umschalten',
|
||||
'join_link_qr' => 'Beitrittslink / QR',
|
||||
],
|
||||
'modal' => [
|
||||
'join_link_heading' => 'Beitrittslink der Veranstaltung',
|
||||
],
|
||||
'messages' => [
|
||||
'join_link_copied' => 'Beitrittslink kopiert',
|
||||
],
|
||||
'join_link' => [
|
||||
'link_label' => 'Beitrittslink',
|
||||
'qr_code_label' => 'QR‑Code',
|
||||
'note_html' => 'Hinweis: Der QR‑Code wird über einen externen QR‑Service generiert. Für eine selbst gehostete Lösung können wir später eine interne QR‑Generierung ergänzen.',
|
||||
],
|
||||
],
|
||||
|
||||
'legal_pages' => [
|
||||
'fields' => [
|
||||
'slug' => 'Slug',
|
||||
'title_localized' => 'Titel (de/en)',
|
||||
'content_localization' => 'Inhaltslokalisierung',
|
||||
'content_de' => 'Inhalt (Deutsch)',
|
||||
'content_en' => 'Inhalt (Englisch)',
|
||||
'is_published' => 'Veröffentlicht',
|
||||
'effective_from' => 'Gültig ab',
|
||||
'version' => 'Version',
|
||||
],
|
||||
],
|
||||
|
||||
'emotions' => [
|
||||
'sections' => [
|
||||
'content_localization' => 'Inhaltslokalisierung',
|
||||
],
|
||||
'fields' => [
|
||||
'name_de' => 'Name (Deutsch)',
|
||||
'description_de' => 'Beschreibung (Deutsch)',
|
||||
'name_en' => 'Name (Englisch)',
|
||||
'description_en' => 'Beschreibung (Englisch)',
|
||||
'icon_emoji' => 'Icon/Emoji',
|
||||
'color' => 'Farbe',
|
||||
'sort_order' => 'Sortierreihenfolge',
|
||||
'is_active' => 'Aktiv',
|
||||
'event_types' => 'Eventtypen',
|
||||
],
|
||||
'table' => [
|
||||
'name' => 'Name',
|
||||
'icon' => 'Icon',
|
||||
'color' => 'Farbe',
|
||||
'is_active' => 'Aktiv',
|
||||
'sort_order' => 'Sortierung',
|
||||
],
|
||||
'import' => [
|
||||
'heading' => 'Emotionen importieren (CSV)',
|
||||
],
|
||||
],
|
||||
|
||||
'event_types' => [
|
||||
'sections' => [
|
||||
'name_localization' => 'Namenslokalisierung',
|
||||
],
|
||||
'fields' => [
|
||||
'name_de' => 'Name (Deutsch)',
|
||||
'name_en' => 'Name (Englisch)',
|
||||
'slug' => 'Slug',
|
||||
'icon' => 'Icon',
|
||||
'settings' => 'Einstellungen',
|
||||
'emotions' => 'Emotionen',
|
||||
],
|
||||
'table' => [
|
||||
'name' => 'Name',
|
||||
'slug' => 'Slug',
|
||||
'icon' => 'Icon',
|
||||
'created_at' => 'Erstellt',
|
||||
],
|
||||
],
|
||||
|
||||
'tasks' => [
|
||||
'menu' => 'Aufgaben',
|
||||
'fields' => [
|
||||
'event_type_optional' => 'Eventtyp (optional)',
|
||||
'content_localization' => 'Inhaltslokalisierung',
|
||||
'title_de' => 'Titel (Deutsch)',
|
||||
'description_de' => 'Beschreibung (Deutsch)',
|
||||
'example_de' => 'Beispieltext (Deutsch)',
|
||||
'title_en' => 'Titel (Englisch)',
|
||||
'description_en' => 'Beschreibung (Englisch)',
|
||||
'example_en' => 'Beispieltext (Englisch)',
|
||||
'emotion' => 'Emotion',
|
||||
'event_type' => 'Eventtyp',
|
||||
'difficulty' => [
|
||||
'label' => 'Schwierigkeit',
|
||||
'easy' => 'Leicht',
|
||||
'medium' => 'Mittel',
|
||||
'hard' => 'Schwer',
|
||||
],
|
||||
],
|
||||
'table' => [
|
||||
'title' => 'Titel',
|
||||
'is_active' => 'Aktiv',
|
||||
'sort_order' => 'Sortierung',
|
||||
],
|
||||
'table' => [
|
||||
'name' => 'Name',
|
||||
'icon' => 'Icon',
|
||||
'color' => 'Farbe',
|
||||
'is_active' => 'Aktiv',
|
||||
'sort_order' => 'Sortierung',
|
||||
],
|
||||
'import' => [
|
||||
'heading' => 'Aufgaben importieren (CSV)',
|
||||
],
|
||||
],
|
||||
|
||||
'widgets' => [
|
||||
'events_active_today' => [
|
||||
'heading' => 'Heute aktive Events',
|
||||
],
|
||||
'recent_uploads' => [
|
||||
'heading' => 'Neueste Uploads',
|
||||
],
|
||||
'top_tenants_by_uploads' => [
|
||||
'heading' => 'Top‑Mandanten nach Uploads',
|
||||
],
|
||||
'uploads_per_day' => [
|
||||
'heading' => 'Uploads (14 Tage)',
|
||||
],
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
'file_not_found' => 'Datei nicht gefunden',
|
||||
'imported_rows' => ':count Zeilen importiert',
|
||||
'failed_count' => ':count fehlgeschlagen',
|
||||
],
|
||||
|
||||
'tenants' => [
|
||||
'fields' => [
|
||||
'name' => 'Mandantenname',
|
||||
'slug' => 'Slug',
|
||||
'contact_email' => 'Kontakt‑E‑Mail',
|
||||
'event_credits_balance' => 'Event‑Credits‑Kontostand',
|
||||
'features' => 'Funktionen',
|
||||
],
|
||||
],
|
||||
|
||||
'shell' => [
|
||||
'tenant_admin_title' => 'Tenant‑Admin',
|
||||
],
|
||||
];
|
||||
209
resources/lang/en/admin.php
Normal file
209
resources/lang/en/admin.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'nav' => [
|
||||
'platform' => 'Platform',
|
||||
'library' => 'Library',
|
||||
'content' => 'Content',
|
||||
],
|
||||
|
||||
'common' => [
|
||||
'key' => 'Key',
|
||||
'value' => 'Value',
|
||||
'locale' => 'Locale',
|
||||
'german' => 'German',
|
||||
'english' => 'English',
|
||||
'import' => 'Import',
|
||||
'import_csv' => 'Import CSV',
|
||||
'download_csv_template' => 'Download CSV Template',
|
||||
'csv_file' => 'CSV file',
|
||||
'close' => 'Close',
|
||||
'hash' => '#',
|
||||
'slug' => 'Slug',
|
||||
'event' => 'Event',
|
||||
'tenant' => 'Tenant',
|
||||
'uploads' => 'Uploads',
|
||||
'uploads_today' => 'Uploads today',
|
||||
'thumb' => 'Thumb',
|
||||
'likes' => 'Likes',
|
||||
'emotion' => 'Emotion',
|
||||
'event_type' => 'Event Type',
|
||||
'last_activity' => 'Last activity',
|
||||
'credits' => 'Credits',
|
||||
'settings' => 'Settings',
|
||||
'join' => 'Join',
|
||||
'unnamed' => 'Unnamed',
|
||||
],
|
||||
|
||||
'photos' => [
|
||||
'fields' => [
|
||||
'event' => 'Event',
|
||||
'photo' => 'Photo',
|
||||
'is_featured' => 'Is Featured',
|
||||
'metadata' => 'Metadata',
|
||||
'likes' => 'Likes',
|
||||
],
|
||||
'actions' => [
|
||||
'feature' => 'Feature',
|
||||
'unfeature' => 'Unfeature',
|
||||
'feature_selected' => 'Feature selected',
|
||||
'unfeature_selected' => 'Unfeature selected',
|
||||
],
|
||||
'table' => [
|
||||
'photo' => 'Photo',
|
||||
'event' => 'Event',
|
||||
'likes' => 'Likes',
|
||||
],
|
||||
],
|
||||
|
||||
'events' => [
|
||||
'fields' => [
|
||||
'tenant' => 'Tenant',
|
||||
'name' => 'Event Name',
|
||||
'slug' => 'Slug',
|
||||
'date' => 'Event Date',
|
||||
'type' => 'Event Type',
|
||||
'default_locale' => 'Default Locale',
|
||||
'is_active' => 'Is Active',
|
||||
'settings' => 'Settings',
|
||||
],
|
||||
'table' => [
|
||||
'tenant' => 'Tenant',
|
||||
'join' => 'Join',
|
||||
],
|
||||
'actions' => [
|
||||
'toggle_active' => 'Toggle Active',
|
||||
'join_link_qr' => 'Join Link / QR',
|
||||
],
|
||||
'modal' => [
|
||||
'join_link_heading' => 'Event Join Link',
|
||||
],
|
||||
'messages' => [
|
||||
'join_link_copied' => 'Join link copied',
|
||||
],
|
||||
'join_link' => [
|
||||
'link_label' => 'Join Link',
|
||||
'qr_code_label' => 'QR Code',
|
||||
'note_html' => 'Note: The QR code is generated via an external QR service. For a self-hosted option, we can add internal generation later.',
|
||||
],
|
||||
],
|
||||
|
||||
'legal_pages' => [
|
||||
'fields' => [
|
||||
'slug' => 'Slug',
|
||||
'title_localized' => 'Title (de/en)',
|
||||
'content_localization' => 'Content Localization',
|
||||
'content_de' => 'Content (German)',
|
||||
'content_en' => 'Content (English)',
|
||||
'is_published' => 'Is Published',
|
||||
'effective_from' => 'Effective From',
|
||||
'version' => 'Version',
|
||||
],
|
||||
],
|
||||
|
||||
'emotions' => [
|
||||
'sections' => [
|
||||
'content_localization' => 'Content Localization',
|
||||
],
|
||||
'fields' => [
|
||||
'name_de' => 'Name (German)',
|
||||
'description_de' => 'Description (German)',
|
||||
'name_en' => 'Name (English)',
|
||||
'description_en' => 'Description (English)',
|
||||
'icon_emoji' => 'Icon/Emoji',
|
||||
'color' => 'Color',
|
||||
'sort_order' => 'Sort Order',
|
||||
'is_active' => 'Is Active',
|
||||
'event_types' => 'Event Types',
|
||||
],
|
||||
'table' => [
|
||||
'name' => 'Name',
|
||||
'icon' => 'Icon',
|
||||
'color' => 'Color',
|
||||
'is_active' => 'Active',
|
||||
'sort_order' => 'Sort Order',
|
||||
],
|
||||
'import' => [
|
||||
'heading' => 'Import Emotions (CSV)',
|
||||
],
|
||||
],
|
||||
|
||||
'event_types' => [
|
||||
'sections' => [
|
||||
'name_localization' => 'Name Localization',
|
||||
],
|
||||
'fields' => [
|
||||
'name_de' => 'Name (German)',
|
||||
'name_en' => 'Name (English)',
|
||||
'slug' => 'Slug',
|
||||
'icon' => 'Icon',
|
||||
'settings' => 'Settings',
|
||||
'emotions' => 'Emotions',
|
||||
],
|
||||
],
|
||||
|
||||
'tasks' => [
|
||||
'menu' => 'Tasks',
|
||||
'fields' => [
|
||||
'event_type_optional' => 'Event Type (optional)',
|
||||
'content_localization' => 'Content Localization',
|
||||
'title_de' => 'Title (German)',
|
||||
'description_de' => 'Description (German)',
|
||||
'example_de' => 'Example Text (German)',
|
||||
'title_en' => 'Title (English)',
|
||||
'description_en' => 'Description (English)',
|
||||
'example_en' => 'Example Text (English)',
|
||||
'emotion' => 'Emotion',
|
||||
'event_type' => 'Event Type',
|
||||
'difficulty' => [
|
||||
'label' => 'Difficulty',
|
||||
'easy' => 'Easy',
|
||||
'medium' => 'Medium',
|
||||
'hard' => 'Hard',
|
||||
],
|
||||
],
|
||||
'table' => [
|
||||
'title' => 'Title',
|
||||
'is_active' => 'Active',
|
||||
'sort_order' => 'Sort Order',
|
||||
],
|
||||
'import' => [
|
||||
'heading' => 'Import Tasks (CSV)',
|
||||
],
|
||||
],
|
||||
|
||||
'widgets' => [
|
||||
'events_active_today' => [
|
||||
'heading' => 'Events active today',
|
||||
],
|
||||
'recent_uploads' => [
|
||||
'heading' => 'Recent uploads',
|
||||
],
|
||||
'top_tenants_by_uploads' => [
|
||||
'heading' => 'Top tenants by uploads',
|
||||
],
|
||||
'uploads_per_day' => [
|
||||
'heading' => 'Uploads (14 days)',
|
||||
],
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
'file_not_found' => 'File not found',
|
||||
'imported_rows' => 'Imported :count rows',
|
||||
'failed_count' => ':count failed',
|
||||
],
|
||||
|
||||
'tenants' => [
|
||||
'fields' => [
|
||||
'name' => 'Tenant Name',
|
||||
'slug' => 'Slug',
|
||||
'contact_email' => 'Contact Email',
|
||||
'event_credits_balance' => 'Event Credits Balance',
|
||||
'features' => 'Features',
|
||||
],
|
||||
],
|
||||
|
||||
'shell' => [
|
||||
'tenant_admin_title' => 'Tenant Admin',
|
||||
],
|
||||
];
|
||||
@@ -4,11 +4,10 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Tenant Admin</title>
|
||||
<title>{{ __('admin.shell.tenant_admin_title') }}</title>
|
||||
@vite('resources/js/admin/main.tsx')
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<div class="space-y-3">
|
||||
<div class="text-sm">Join Link</div>
|
||||
<div class="text-sm">{{ __('admin.events.join_link.link_label') }}</div>
|
||||
<div class="rounded border bg-gray-50 p-2 text-sm dark:bg-gray-900">
|
||||
<a href="{{ $link }}" target="_blank" class="underline">
|
||||
{{ $link }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-sm">QR Code</div>
|
||||
<div class="text-sm">{{ __('admin.events.join_link.qr_code_label') }}</div>
|
||||
<div class="flex items-center justify-center">
|
||||
{!! \SimpleSoftwareIO\QrCode\Facades\QrCode::size(300)->generate($link) !!}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
Hinweis: Der QR-Code wird über einen externen QR-Service generiert. Für eine selbst gehostete Lösung können wir später eine interne QR-Generierung ergänzen.
|
||||
{!! __('admin.events.join_link.note_html') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
]"
|
||||
/>
|
||||
<x-filament::button type="submit" >
|
||||
Import
|
||||
{{ __('admin.common.import') }}
|
||||
</x-filament::button>
|
||||
</x-filament-panels::form>
|
||||
</x-filament-panels::page>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
]"
|
||||
/>
|
||||
<x-filament::button type="submit" >
|
||||
Import
|
||||
{{ __('admin.common.import') }}
|
||||
</x-filament::button>
|
||||
</x-filament-panels::form>
|
||||
</x-filament-panels::page>
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fotospiel</title>
|
||||
<title>{{ config('app.name', 'Fotospiel') }}</title>
|
||||
@vite('resources/js/guest/main.tsx')
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user