From 71c4e2509f3230306caac07f3bcb64e500616f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rdle=20Bot?= Date: Sat, 6 Dec 2025 15:38:46 +0100 Subject: [PATCH] feat(admin): add danger zone buttons for resetting ratings and activations - Added reset all user ratings button to admin danger zone - Added reset all activations button to admin danger zone - Created API endpoints: /api/admin/reset-ratings and /api/admin/reset-activations - Removed old non-localized routes: /app/admin, /app/curator - Removed unused page.module.css - All admin functionality now uses localized routes (/[locale]/admin) --- app/[locale]/admin/page.tsx | 1143 +++++---- app/admin/layout.tsx | 14 - app/admin/page.tsx | 1185 --------- app/admin/specials/[id]/page.tsx | 89 - app/api/admin/reset-activations/route.ts | 23 + app/api/admin/reset-ratings/route.ts | 28 + app/curator/CuratorPageClient.tsx | 2164 ----------------- app/curator/help/CuratorHelpClient.tsx | 171 -- app/curator/help/page.tsx | 8 - app/curator/page.tsx | 11 - .../specials/CuratorSpecialsClient.tsx | 156 -- app/curator/specials/[id]/page.tsx | 177 -- app/curator/specials/page.tsx | 13 - app/page.module.css | 141 -- 14 files changed, 661 insertions(+), 4662 deletions(-) delete mode 100644 app/admin/layout.tsx delete mode 100644 app/admin/page.tsx delete mode 100644 app/admin/specials/[id]/page.tsx create mode 100644 app/api/admin/reset-activations/route.ts create mode 100644 app/api/admin/reset-ratings/route.ts delete mode 100644 app/curator/CuratorPageClient.tsx delete mode 100644 app/curator/help/CuratorHelpClient.tsx delete mode 100644 app/curator/help/page.tsx delete mode 100644 app/curator/page.tsx delete mode 100644 app/curator/specials/CuratorSpecialsClient.tsx delete mode 100644 app/curator/specials/[id]/page.tsx delete mode 100644 app/curator/specials/page.tsx delete mode 100644 app/page.module.css diff --git a/app/[locale]/admin/page.tsx b/app/[locale]/admin/page.tsx index c080b1f..6f4bdac 100644 --- a/app/[locale]/admin/page.tsx +++ b/app/[locale]/admin/page.tsx @@ -376,7 +376,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { const handleCreateSpecial = async (e: React.FormEvent) => { e.preventDefault(); if (!newSpecialName.de.trim() && !newSpecialName.en.trim()) return; - + // Validate unlock steps const unlockStepsError = validateUnlockSteps(newSpecialUnlockSteps); if (unlockStepsError) { @@ -384,7 +384,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { return; } setNewSpecialUnlockStepsError(null); - + const res = await fetch('/api/specials', { method: 'POST', headers: getAuthHeaders(), @@ -501,7 +501,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { const saveEditedSpecial = async () => { if (editingSpecialId === null) return; - + // Validate unlock steps const unlockStepsError = validateUnlockSteps(editSpecialUnlockSteps); if (unlockStepsError) { @@ -509,7 +509,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { return; } setEditSpecialUnlockStepsError(null); - + const res = await fetch('/api/specials', { method: 'PUT', headers: getAuthHeaders(), @@ -1337,224 +1337,224 @@ export default function AdminPage({ params }: { params: { locale: string } }) { {showSpecials && ( <>
-
-
- - setNewSpecialName({ ...newSpecialName, [activeTab]: e.target.value })} className="form-input" required key={`special-name-${activeTab}`} /> -
-
- - setNewSpecialSubtitle({ ...newSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" key={`special-subtitle-${activeTab}`} /> -
-
- - setNewSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} /> -
-
-
- -
- {specials.map(special => ( -
- - {special.hidden && 👁️‍🗨️} {getLocalizedValue(special.name, activeTab)} ({special._count?.songs || 0}) - - {special.subtitle && ( - +
+
+ + setNewSpecialLaunchDate(e.target.value)} className="form-input" /> +
+
+ + setNewSpecialEndDate(e.target.value)} className="form-input" /> +
+
+ + setNewSpecialCurator(e.target.value)} className="form-input" /> +
+
+ + +
+ +
+ +
+ {specials.map(special => ( +
- - {getLocalizedValue(special.subtitle, activeTab)} - - )} - - -
- ))} -
- {editingSpecialId !== null && ( -
-

{t('editSpecial')}

-
-
- - setEditSpecialName({ ...editSpecialName, [activeTab]: e.target.value })} className="form-input" key={`edit-special-name-${activeTab}`} /> -
-
- - setEditSpecialSubtitle({ ...editSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" key={`edit-special-subtitle-${activeTab}`} /> -
-
- - setEditSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} /> -
-
- - { - const value = e.target.value; - setEditSpecialUnlockSteps(value); - const error = validateUnlockSteps(value); - setEditSpecialUnlockStepsError(error); - }} - className="form-input" - title={editSpecialUnlockStepsError || undefined} - style={{ - width: '200px', - borderColor: editSpecialUnlockStepsError ? '#ef4444' : undefined - }} - /> -
-
- - setEditSpecialLaunchDate(e.target.value)} className="form-input" /> -
-
- - setEditSpecialEndDate(e.target.value)} className="form-input" /> -
-
- - setEditSpecialCurator(e.target.value)} className="form-input" /> -
-
- - -
- - + + +
+ ))}
-
- )} + {editingSpecialId !== null && ( +
+

{t('editSpecial')}

+
+
+ + setEditSpecialName({ ...editSpecialName, [activeTab]: e.target.value })} className="form-input" key={`edit-special-name-${activeTab}`} /> +
+
+ + setEditSpecialSubtitle({ ...editSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" key={`edit-special-subtitle-${activeTab}`} /> +
+
+ + setEditSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} /> +
+
+ + { + const value = e.target.value; + setEditSpecialUnlockSteps(value); + const error = validateUnlockSteps(value); + setEditSpecialUnlockStepsError(error); + }} + className="form-input" + title={editSpecialUnlockStepsError || undefined} + style={{ + width: '200px', + borderColor: editSpecialUnlockStepsError ? '#ef4444' : undefined + }} + /> +
+
+ + setEditSpecialLaunchDate(e.target.value)} className="form-input" /> +
+
+ + setEditSpecialEndDate(e.target.value)} className="form-input" /> +
+
+ + setEditSpecialCurator(e.target.value)} className="form-input" /> +
+
+ + +
+ + +
+
+ )} )} @@ -1582,161 +1582,161 @@ export default function AdminPage({ params }: { params: { locale: string } }) { {showGenres && ( <>
- setNewGenreName({ ...newGenreName, [activeTab]: e.target.value })} - placeholder={t('newGenreName')} - className="form-input" - style={{ maxWidth: '200px' }} - key={`genre-name-${activeTab}`} - /> - setNewGenreSubtitle({ ...newGenreSubtitle, [activeTab]: e.target.value })} - placeholder={t('subtitle')} - className="form-input" - style={{ maxWidth: '300px' }} - key={`genre-subtitle-${activeTab}`} - /> - - -
-
- {genres.map(genre => ( -
- {getLocalizedValue(genre.name, activeTab)} ({genre._count?.songs || 0}) - {genre.subtitle && - {getLocalizedValue(genre.subtitle, activeTab)}} - - + setNewGenreName({ ...newGenreName, [activeTab]: e.target.value })} + placeholder={t('newGenreName')} + className="form-input" + style={{ maxWidth: '200px' }} + key={`genre-name-${activeTab}`} + /> + setNewGenreSubtitle({ ...newGenreSubtitle, [activeTab]: e.target.value })} + placeholder={t('subtitle')} + className="form-input" + style={{ maxWidth: '300px' }} + key={`genre-subtitle-${activeTab}`} + /> + +
- ))} -
- {editingGenreId !== null && ( -
-

{t('editGenre')}

-
-
- - setEditGenreName({ ...editGenreName, [activeTab]: e.target.value })} className="form-input" key={`edit-genre-name-${activeTab}`} /> -
-
- - setEditGenreSubtitle({ ...editGenreSubtitle, [activeTab]: e.target.value })} className="form-input" style={{ width: '300px' }} key={`edit-genre-subtitle-${activeTab}`} /> -
-
- -
- - +
+ {genres.map(genre => ( +
+ {getLocalizedValue(genre.name, activeTab)} ({genre._count?.songs || 0}) + {genre.subtitle && - {getLocalizedValue(genre.subtitle, activeTab)}} + + +
+ ))}
-
- )} - - {/* AI Categorization */} -
- - {genres.length === 0 && ( -

- Please create at least one genre first. -

- )} -
- - {/* Categorization Results */} - {categorizationResults && ( -
-

- ✅ Categorization Complete -

-

- {categorizationResults.message} -

- {categorizationResults.results && categorizationResults.results.length > 0 && ( -
-

Updated Songs:

-
- {categorizationResults.results.map((result: any) => ( -
- {result.title} by {result.artist} -
- {result.assignedGenres.map((genre: string) => ( - - {genre} - - ))} -
-
- ))} + {editingGenreId !== null && ( +
+

{t('editGenre')}

+
+
+ + setEditGenreName({ ...editGenreName, [activeTab]: e.target.value })} className="form-input" key={`edit-genre-name-${activeTab}`} /> +
+
+ + setEditGenreSubtitle({ ...editGenreSubtitle, [activeTab]: e.target.value })} className="form-input" style={{ width: '300px' }} key={`edit-genre-subtitle-${activeTab}`} /> +
+
+ +
+ +
)} - + {genres.length === 0 && ( +

+ Please create at least one genre first. +

+ )} +
+ + {/* Categorization Results */} + {categorizationResults && ( +
- Close - -
- )} + padding: '1rem', + background: '#f0fdf4', + border: '1px solid #86efac', + borderRadius: '0.5rem' + }}> +

+ ✅ Categorization Complete +

+

+ {categorizationResults.message} +

+ {categorizationResults.results && categorizationResults.results.length > 0 && ( +
+

Updated Songs:

+
+ {categorizationResults.results.map((result: any) => ( +
+ {result.title} by {result.artist} +
+ {result.assignedGenres.map((genre: string) => ( + + {genre} + + ))} +
+
+ ))} +
+
+ )} + +
+ )} )}
@@ -1764,161 +1764,161 @@ export default function AdminPage({ params }: { params: { locale: string } }) { {showNews && ( <>
-
- setNewNewsTitle({ ...newNewsTitle, [activeTab]: e.target.value })} - placeholder={t('newsTitle')} - className="form-input" - required - key={`news-title-${activeTab}`} - /> -