From ac12e45393ead7354a7ae045676143bc03b238e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rdle=20Bot?= Date: Fri, 5 Dec 2025 20:41:38 +0100 Subject: [PATCH] Fix curator specials page: resolve redirect loop and add missing translations --- app/[locale]/curator/specials/page.tsx | 6 +- .../specials/CuratorSpecialsClient.tsx | 156 ++++++++++++++++++ messages/de.json | 2 + messages/en.json | 2 + 4 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 app/curator/specials/CuratorSpecialsClient.tsx diff --git a/app/[locale]/curator/specials/page.tsx b/app/[locale]/curator/specials/page.tsx index b605487..9ff86f3 100644 --- a/app/[locale]/curator/specials/page.tsx +++ b/app/[locale]/curator/specials/page.tsx @@ -1,7 +1,9 @@ 'use client'; -import CuratorSpecialsPage from '@/app/curator/specials/page'; +import CuratorSpecialsClient from '@/app/curator/specials/CuratorSpecialsClient'; -export default CuratorSpecialsPage; +export default function CuratorSpecialsPage() { + return ; +} diff --git a/app/curator/specials/CuratorSpecialsClient.tsx b/app/curator/specials/CuratorSpecialsClient.tsx new file mode 100644 index 0000000..c5ce683 --- /dev/null +++ b/app/curator/specials/CuratorSpecialsClient.tsx @@ -0,0 +1,156 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter, usePathname } from 'next/navigation'; +import { useLocale, useTranslations } from 'next-intl'; +import { Link } from '@/lib/navigation'; +import { getCuratorAuthHeaders } from '@/lib/curatorAuth'; +import { getLocalizedValue } from '@/lib/i18n'; + +interface CuratorSpecial { + id: number; + name: string | { de?: string; en?: string }; + songCount: number; +} + +export default function CuratorSpecialsClient() { + const router = useRouter(); + const pathname = usePathname(); + const urlLocale = pathname?.split('/')[1] as 'de' | 'en' | undefined; + const intlLocale = useLocale() as 'de' | 'en'; + const locale: 'de' | 'en' = urlLocale === 'de' || urlLocale === 'en' ? urlLocale : intlLocale; + const t = useTranslations('Curator'); + + const [specials, setSpecials] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchSpecials = async () => { + try { + setLoading(true); + const res = await fetch('/api/curator/specials', { + headers: getCuratorAuthHeaders(), + }); + if (!res.ok) { + if (res.status === 403) { + setError(t('specialForbidden')); + } else { + setError('Failed to load specials'); + } + return; + } + const data = await res.json(); + setSpecials(data); + } catch (e) { + setError('Failed to load specials'); + } finally { + setLoading(false); + } + }; + + fetchSpecials(); + }, [t]); + + if (loading) { + return ( +
+

{t('loading')}

+
+ ); + } + + if (error) { + return ( +
+

{error}

+ + {t('backToDashboard') || 'Back to Dashboard'} + +
+ ); + } + + return ( +
+
+
+

+ {t('curateSpecialsTitle') || 'Curate Specials'} +

+ + {t('backToDashboard') || 'Back to Dashboard'} + +
+
+ + {specials.length === 0 ? ( +
+

{t('noSpecialsAssigned') || 'No specials assigned to you.'}

+
+ ) : ( +
+ {specials.map((special) => ( + { + e.currentTarget.style.background = '#f3f4f6'; + e.currentTarget.style.borderColor = '#d1d5db'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.background = '#f9fafb'; + e.currentTarget.style.borderColor = '#e5e7eb'; + }} + > +
+
+

+ {getLocalizedValue(special.name, locale)} +

+

+ {special.songCount} {special.songCount === 1 ? 'song' : 'songs'} +

+
+
+
+ + ))} +
+ )} +
+ ); +} + diff --git a/messages/de.json b/messages/de.json index 8197de8..1ddb34a 100644 --- a/messages/de.json +++ b/messages/de.json @@ -279,11 +279,13 @@ "batchUpdateError": "Fehler: {error}", "batchUpdateNetworkError": "Netzwerkfehler bei der Batch-Aktualisierung", "backToDashboard": "Zurück zum Dashboard", + "loading": "Laden...", "curateSpecialsButton": "Specials kuratieren", "curateSpecialsTitle": "Deine Specials kuratieren", "curateSpecialsDescription": "Hier kannst du die Startzeiten der Songs in deinen zugewiesenen Specials für das Rätsel feinjustieren.", "noSpecialPermissions": "Dir sind keine Specials zugeordnet.", "noSpecialsInScope": "Keine Specials zum Kuratieren vorhanden.", + "noSpecialsAssigned": "Dir sind keine Specials zugeordnet.", "curateSpecialSongCount": "{count, plural, one {# Song} other {# Songs}} in diesem Special", "curateSpecialOpen": "Öffnen", "specialForbidden": "Du darfst dieses Special nicht bearbeiten.", diff --git a/messages/en.json b/messages/en.json index ab752ff..38ff8bf 100644 --- a/messages/en.json +++ b/messages/en.json @@ -279,11 +279,13 @@ "batchUpdateError": "Error: {error}", "batchUpdateNetworkError": "Network error during batch update", "backToDashboard": "Back to dashboard", + "loading": "Loading...", "curateSpecialsButton": "Curate Specials", "curateSpecialsTitle": "Curate your Specials", "curateSpecialsDescription": "Here you can fine-tune the start times of the songs in your assigned specials for the puzzle.", "noSpecialPermissions": "You do not have any specials assigned to you.", "noSpecialsInScope": "No specials available for you to curate.", + "noSpecialsAssigned": "No specials assigned to you.", "curateSpecialSongCount": "{count, plural, one {# song} other {# songs}} in this special", "curateSpecialOpen": "Open", "specialForbidden": "You are not allowed to edit this special.",