Admin-Seite lokalisiert: Übersetzungen hinzugefügt und URLs angepasst
- Admin-Namespace zu de.json und en.json hinzugefügt - Alle UI-Texte in der Admin-Seite mit useTranslations lokalisiert - Link-Komponente von next-intl verwendet für korrekte Locale-URLs - Buttons, Labels, Formulare und Tabellen-Header übersetzt
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { useTranslations, useLocale } from 'next-intl';
|
||||||
|
import { Link } from '@/lib/navigation';
|
||||||
import { getLocalizedValue } from '@/lib/i18n';
|
import { getLocalizedValue } from '@/lib/i18n';
|
||||||
|
|
||||||
|
|
||||||
@@ -70,6 +72,8 @@ type SortField = 'id' | 'title' | 'artist' | 'createdAt' | 'releaseYear' | 'acti
|
|||||||
type SortDirection = 'asc' | 'desc';
|
type SortDirection = 'asc' | 'desc';
|
||||||
|
|
||||||
export default function AdminPage({ params }: { params: { locale: string } }) {
|
export default function AdminPage({ params }: { params: { locale: string } }) {
|
||||||
|
const t = useTranslations('Admin');
|
||||||
|
const locale = useLocale();
|
||||||
const [activeTab, setActiveTab] = useState<'de' | 'en'>('de');
|
const [activeTab, setActiveTab] = useState<'de' | 'en'>('de');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
@@ -194,7 +198,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
fetchSpecials();
|
fetchSpecials();
|
||||||
fetchNews();
|
fetchNews();
|
||||||
} else {
|
} else {
|
||||||
alert('Wrong password');
|
alert(t('wrongPassword'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1020,16 +1024,16 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return (
|
return (
|
||||||
<div className="container" style={{ justifyContent: 'center' }}>
|
<div className="container" style={{ justifyContent: 'center' }}>
|
||||||
<h1 className="title" style={{ marginBottom: '1rem', fontSize: '1.5rem' }}>Admin Login</h1>
|
<h1 className="title" style={{ marginBottom: '1rem', fontSize: '1.5rem' }}>{t('login')}</h1>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={e => setPassword(e.target.value)}
|
onChange={e => setPassword(e.target.value)}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ marginBottom: '1rem', maxWidth: '300px' }}
|
style={{ marginBottom: '1rem', maxWidth: '300px' }}
|
||||||
placeholder="Password"
|
placeholder={t('password')}
|
||||||
/>
|
/>
|
||||||
<button onClick={handleLogin} className="btn-primary">Login</button>
|
<button onClick={handleLogin} className="btn-primary">{t('loginButton')}</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1037,11 +1041,16 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
return (
|
return (
|
||||||
<div className="admin-container">
|
<div className="admin-container">
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
|
||||||
<h1 className="title" style={{ margin: 0 }}>Hördle Admin Dashboard</h1>
|
<h1 className="title" style={{ margin: 0 }}>{t('title')}</h1>
|
||||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||||
<div style={{ display: 'flex', background: '#e5e7eb', borderRadius: '0.25rem', padding: '0.25rem' }}>
|
<div style={{ display: 'flex', background: '#e5e7eb', borderRadius: '0.25rem', padding: '0.25rem' }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('de')}
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setActiveTab('de');
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
padding: '0.25rem 0.75rem',
|
padding: '0.25rem 0.75rem',
|
||||||
background: activeTab === 'de' ? 'white' : 'transparent',
|
background: activeTab === 'de' ? 'white' : 'transparent',
|
||||||
@@ -1055,7 +1064,12 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
DE
|
DE
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('en')}
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setActiveTab('en');
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
padding: '0.25rem 0.75rem',
|
padding: '0.25rem 0.75rem',
|
||||||
background: activeTab === 'en' ? 'white' : 'transparent',
|
background: activeTab === 'en' ? 'white' : 'transparent',
|
||||||
@@ -1082,7 +1096,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
fontSize: '0.9rem'
|
fontSize: '0.9rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
🚪 Logout
|
🚪 {t('logout')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1091,7 +1105,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
||||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
||||||
Manage Specials
|
{t('manageSpecials')}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSpecials(!showSpecials)}
|
onClick={() => setShowSpecials(!showSpecials)}
|
||||||
@@ -1104,42 +1118,42 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
fontSize: '0.875rem'
|
fontSize: '0.875rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showSpecials ? '▼ Hide' : '▶ Show'}
|
{showSpecials ? t('hide') : t('show')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showSpecials && (
|
{showSpecials && (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={handleCreateSpecial} style={{ marginBottom: '1rem' }}>
|
<form onSubmit={handleCreateSpecial} style={{ marginBottom: '1rem' }} key={`special-form-${activeTab}`}>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Name</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('name')}</label>
|
||||||
<input type="text" placeholder="Special name" value={newSpecialName[activeTab]} onChange={e => setNewSpecialName({ ...newSpecialName, [activeTab]: e.target.value })} className="form-input" required />
|
<input type="text" placeholder={t('name')} value={newSpecialName[activeTab] || ''} onChange={e => setNewSpecialName({ ...newSpecialName, [activeTab]: e.target.value })} className="form-input" required key={`special-name-${activeTab}`} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Subtitle</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('subtitle')}</label>
|
||||||
<input type="text" placeholder="Subtitle" value={newSpecialSubtitle[activeTab]} onChange={e => setNewSpecialSubtitle({ ...newSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" />
|
<input type="text" placeholder={t('subtitle')} value={newSpecialSubtitle[activeTab] || ''} onChange={e => setNewSpecialSubtitle({ ...newSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" key={`special-subtitle-${activeTab}`} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Max Attempts</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('maxAttempts')}</label>
|
||||||
<input type="number" placeholder="Max attempts" value={newSpecialMaxAttempts} onChange={e => setNewSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} />
|
<input type="number" placeholder={t('maxAttempts')} value={newSpecialMaxAttempts} onChange={e => setNewSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Unlock Steps</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('unlockSteps')}</label>
|
||||||
<input type="text" placeholder="Unlock steps JSON" value={newSpecialUnlockSteps} onChange={e => setNewSpecialUnlockSteps(e.target.value)} className="form-input" style={{ width: '200px' }} />
|
<input type="text" placeholder={t('unlockSteps')} value={newSpecialUnlockSteps} onChange={e => setNewSpecialUnlockSteps(e.target.value)} className="form-input" style={{ width: '200px' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Launch Date</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('launchDate')}</label>
|
||||||
<input type="date" value={newSpecialLaunchDate} onChange={e => setNewSpecialLaunchDate(e.target.value)} className="form-input" />
|
<input type="date" value={newSpecialLaunchDate} onChange={e => setNewSpecialLaunchDate(e.target.value)} className="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('endDate')}</label>
|
||||||
<input type="date" value={newSpecialEndDate} onChange={e => setNewSpecialEndDate(e.target.value)} className="form-input" />
|
<input type="date" value={newSpecialEndDate} onChange={e => setNewSpecialEndDate(e.target.value)} className="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Curator</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('curator')}</label>
|
||||||
<input type="text" placeholder="Curator name" value={newSpecialCurator} onChange={e => setNewSpecialCurator(e.target.value)} className="form-input" />
|
<input type="text" placeholder={t('curator')} value={newSpecialCurator} onChange={e => setNewSpecialCurator(e.target.value)} className="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="btn-primary" style={{ height: '38px' }}>Add Special</button>
|
<button type="submit" className="btn-primary" style={{ height: '38px' }}>{t('addSpecial')}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
||||||
@@ -1155,46 +1169,46 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
}}>
|
}}>
|
||||||
<span>{getLocalizedValue(special.name, activeTab)} ({special._count?.songs || 0})</span>
|
<span>{getLocalizedValue(special.name, activeTab)} ({special._count?.songs || 0})</span>
|
||||||
{special.subtitle && <span style={{ fontSize: '0.75rem', color: '#666', marginLeft: '0.25rem' }}>- {getLocalizedValue(special.subtitle, activeTab)}</span>}
|
{special.subtitle && <span style={{ fontSize: '0.75rem', color: '#666', marginLeft: '0.25rem' }}>- {getLocalizedValue(special.subtitle, activeTab)}</span>}
|
||||||
<a href={`/admin/specials/${special.id}`} className="btn-primary" style={{ marginRight: '0.5rem', textDecoration: 'none' }}>Curate</a>
|
<Link href={`/admin/specials/${special.id}`} className="btn-primary" style={{ marginRight: '0.5rem', textDecoration: 'none' }}>{t('curate')}</Link>
|
||||||
<button onClick={() => startEditSpecial(special)} className="btn-secondary" style={{ marginRight: '0.5rem' }}>Edit</button>
|
<button onClick={() => startEditSpecial(special)} className="btn-secondary" style={{ marginRight: '0.5rem' }}>{t('edit')}</button>
|
||||||
<button onClick={() => handleDeleteSpecial(special.id)} className="btn-danger">Delete</button>
|
<button onClick={() => handleDeleteSpecial(special.id)} className="btn-danger">{t('delete')}</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{editingSpecialId !== null && (
|
{editingSpecialId !== null && (
|
||||||
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f9fafb', borderRadius: '0.5rem' }}>
|
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f9fafb', borderRadius: '0.5rem' }}>
|
||||||
<h3>Edit Special</h3>
|
<h3>{t('editSpecial')}</h3>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Name</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('name')}</label>
|
||||||
<input type="text" value={editSpecialName[activeTab]} onChange={e => setEditSpecialName({ ...editSpecialName, [activeTab]: e.target.value })} className="form-input" />
|
<input type="text" value={editSpecialName[activeTab] || ''} onChange={e => setEditSpecialName({ ...editSpecialName, [activeTab]: e.target.value })} className="form-input" key={`edit-special-name-${activeTab}`} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Subtitle</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('subtitle')}</label>
|
||||||
<input type="text" value={editSpecialSubtitle[activeTab]} onChange={e => setEditSpecialSubtitle({ ...editSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" />
|
<input type="text" value={editSpecialSubtitle[activeTab] || ''} onChange={e => setEditSpecialSubtitle({ ...editSpecialSubtitle, [activeTab]: e.target.value })} className="form-input" key={`edit-special-subtitle-${activeTab}`} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Max Attempts</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('maxAttempts')}</label>
|
||||||
<input type="number" value={editSpecialMaxAttempts} onChange={e => setEditSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} />
|
<input type="number" value={editSpecialMaxAttempts} onChange={e => setEditSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Unlock Steps</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('unlockSteps')}</label>
|
||||||
<input type="text" value={editSpecialUnlockSteps} onChange={e => setEditSpecialUnlockSteps(e.target.value)} className="form-input" style={{ width: '200px' }} />
|
<input type="text" value={editSpecialUnlockSteps} onChange={e => setEditSpecialUnlockSteps(e.target.value)} className="form-input" style={{ width: '200px' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Launch Date</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('launchDate')}</label>
|
||||||
<input type="date" value={editSpecialLaunchDate} onChange={e => setEditSpecialLaunchDate(e.target.value)} className="form-input" />
|
<input type="date" value={editSpecialLaunchDate} onChange={e => setEditSpecialLaunchDate(e.target.value)} className="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('endDate')}</label>
|
||||||
<input type="date" value={editSpecialEndDate} onChange={e => setEditSpecialEndDate(e.target.value)} className="form-input" />
|
<input type="date" value={editSpecialEndDate} onChange={e => setEditSpecialEndDate(e.target.value)} className="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Curator</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('curator')}</label>
|
||||||
<input type="text" value={editSpecialCurator} onChange={e => setEditSpecialCurator(e.target.value)} className="form-input" />
|
<input type="text" value={editSpecialCurator} onChange={e => setEditSpecialCurator(e.target.value)} className="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<button onClick={saveEditedSpecial} className="btn-primary" style={{ height: '38px' }}>Save</button>
|
<button onClick={saveEditedSpecial} className="btn-primary" style={{ height: '38px' }}>{t('save')}</button>
|
||||||
<button onClick={() => setEditingSpecialId(null)} className="btn-secondary" style={{ height: '38px' }}>Cancel</button>
|
<button onClick={() => setEditingSpecialId(null)} className="btn-secondary" style={{ height: '38px' }}>{t('cancel')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1206,7 +1220,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
||||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
||||||
Manage Genres
|
{t('manageGenres')}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowGenres(!showGenres)}
|
onClick={() => setShowGenres(!showGenres)}
|
||||||
@@ -1219,7 +1233,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
fontSize: '0.875rem'
|
fontSize: '0.875rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showGenres ? '▼ Hide' : '▶ Show'}
|
{showGenres ? t('hide') : t('show')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showGenres && (
|
{showGenres && (
|
||||||
@@ -1227,19 +1241,21 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', marginBottom: '1rem', alignItems: 'center' }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newGenreName[activeTab]}
|
value={newGenreName[activeTab] || ''}
|
||||||
onChange={e => setNewGenreName({ ...newGenreName, [activeTab]: e.target.value })}
|
onChange={e => setNewGenreName({ ...newGenreName, [activeTab]: e.target.value })}
|
||||||
placeholder="New Genre Name"
|
placeholder={t('newGenreName')}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ maxWidth: '200px' }}
|
style={{ maxWidth: '200px' }}
|
||||||
|
key={`genre-name-${activeTab}`}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newGenreSubtitle[activeTab]}
|
value={newGenreSubtitle[activeTab] || ''}
|
||||||
onChange={e => setNewGenreSubtitle({ ...newGenreSubtitle, [activeTab]: e.target.value })}
|
onChange={e => setNewGenreSubtitle({ ...newGenreSubtitle, [activeTab]: e.target.value })}
|
||||||
placeholder="Subtitle"
|
placeholder={t('subtitle')}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ maxWidth: '300px' }}
|
style={{ maxWidth: '300px' }}
|
||||||
|
key={`genre-subtitle-${activeTab}`}
|
||||||
/>
|
/>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', fontSize: '0.875rem', cursor: 'pointer' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', fontSize: '0.875rem', cursor: 'pointer' }}>
|
||||||
<input
|
<input
|
||||||
@@ -1247,9 +1263,9 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
checked={newGenreActive}
|
checked={newGenreActive}
|
||||||
onChange={e => setNewGenreActive(e.target.checked)}
|
onChange={e => setNewGenreActive(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
Active
|
{t('active')}
|
||||||
</label>
|
</label>
|
||||||
<button onClick={createGenre} className="btn-primary">Add Genre</button>
|
<button onClick={createGenre} className="btn-primary">{t('addGenre')}</button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
||||||
{genres.map(genre => (
|
{genres.map(genre => (
|
||||||
@@ -1265,22 +1281,22 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
}}>
|
}}>
|
||||||
<span>{getLocalizedValue(genre.name, activeTab)} ({genre._count?.songs || 0})</span>
|
<span>{getLocalizedValue(genre.name, activeTab)} ({genre._count?.songs || 0})</span>
|
||||||
{genre.subtitle && <span style={{ fontSize: '0.75rem', color: '#666' }}>- {getLocalizedValue(genre.subtitle, activeTab)}</span>}
|
{genre.subtitle && <span style={{ fontSize: '0.75rem', color: '#666' }}>- {getLocalizedValue(genre.subtitle, activeTab)}</span>}
|
||||||
<button onClick={() => startEditGenre(genre)} className="btn-secondary" style={{ padding: '0.1rem 0.5rem', fontSize: '0.75rem' }}>Edit</button>
|
<button onClick={() => startEditGenre(genre)} className="btn-secondary" style={{ padding: '0.1rem 0.5rem', fontSize: '0.75rem' }}>{t('edit')}</button>
|
||||||
<button onClick={() => deleteGenre(genre.id)} className="btn-danger" style={{ padding: '0.1rem 0.5rem', fontSize: '0.75rem' }}>×</button>
|
<button onClick={() => deleteGenre(genre.id)} className="btn-danger" style={{ padding: '0.1rem 0.5rem', fontSize: '0.75rem' }}>×</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{editingGenreId !== null && (
|
{editingGenreId !== null && (
|
||||||
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f9fafb', borderRadius: '0.5rem' }}>
|
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f9fafb', borderRadius: '0.5rem' }}>
|
||||||
<h3>Edit Genre</h3>
|
<h3>{t('editGenre')}</h3>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', alignItems: 'flex-end' }}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Name</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('name')}</label>
|
||||||
<input type="text" value={editGenreName[activeTab]} onChange={e => setEditGenreName({ ...editGenreName, [activeTab]: e.target.value })} className="form-input" />
|
<input type="text" value={editGenreName[activeTab] || ''} onChange={e => setEditGenreName({ ...editGenreName, [activeTab]: e.target.value })} className="form-input" key={`edit-genre-name-${activeTab}`} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Subtitle</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>{t('subtitle')}</label>
|
||||||
<input type="text" value={editGenreSubtitle[activeTab]} onChange={e => setEditGenreSubtitle({ ...editGenreSubtitle, [activeTab]: e.target.value })} className="form-input" style={{ width: '300px' }} />
|
<input type="text" value={editGenreSubtitle[activeTab] || ''} onChange={e => setEditGenreSubtitle({ ...editGenreSubtitle, [activeTab]: e.target.value })} className="form-input" style={{ width: '300px' }} key={`edit-genre-subtitle-${activeTab}`} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', paddingBottom: '0.5rem' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'flex-end', paddingBottom: '0.5rem' }}>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', fontSize: '0.875rem', cursor: 'pointer' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', fontSize: '0.875rem', cursor: 'pointer' }}>
|
||||||
@@ -1289,11 +1305,11 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
checked={editGenreActive}
|
checked={editGenreActive}
|
||||||
onChange={e => setEditGenreActive(e.target.checked)}
|
onChange={e => setEditGenreActive(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
Active
|
{t('active')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={saveEditedGenre} className="btn-primary">Save</button>
|
<button onClick={saveEditedGenre} className="btn-primary">{t('save')}</button>
|
||||||
<button onClick={() => setEditingGenreId(null)} className="btn-secondary">Cancel</button>
|
<button onClick={() => setEditingGenreId(null)} className="btn-secondary">{t('cancel')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1386,7 +1402,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
||||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
||||||
Manage News & Announcements
|
{t('manageNews')}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowNews(!showNews)}
|
onClick={() => setShowNews(!showNews)}
|
||||||
@@ -1399,7 +1415,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
fontSize: '0.875rem'
|
fontSize: '0.875rem'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{showNews ? '▼ Hide' : '▶ Show'}
|
{showNews ? t('hide') : t('show')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showNews && (
|
{showNews && (
|
||||||
@@ -1408,27 +1424,29 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newNewsTitle[activeTab]}
|
value={newNewsTitle[activeTab] || ''}
|
||||||
onChange={e => setNewNewsTitle({ ...newNewsTitle, [activeTab]: e.target.value })}
|
onChange={e => setNewNewsTitle({ ...newNewsTitle, [activeTab]: e.target.value })}
|
||||||
placeholder="News Title"
|
placeholder={t('newsTitle')}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
required
|
required
|
||||||
|
key={`news-title-${activeTab}`}
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
value={newNewsContent[activeTab]}
|
value={newNewsContent[activeTab] || ''}
|
||||||
onChange={e => setNewNewsContent({ ...newNewsContent, [activeTab]: e.target.value })}
|
onChange={e => setNewNewsContent({ ...newNewsContent, [activeTab]: e.target.value })}
|
||||||
placeholder="Content (Markdown supported)"
|
placeholder={t('content')}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
rows={4}
|
rows={4}
|
||||||
required
|
required
|
||||||
style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}
|
style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}
|
||||||
|
key={`news-content-${activeTab}`}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newNewsAuthor}
|
value={newNewsAuthor}
|
||||||
onChange={e => setNewNewsAuthor(e.target.value)}
|
onChange={e => setNewNewsAuthor(e.target.value)}
|
||||||
placeholder="Author (optional)"
|
placeholder={t('author')}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ maxWidth: '200px' }}
|
style={{ maxWidth: '200px' }}
|
||||||
/>
|
/>
|
||||||
@@ -1438,7 +1456,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ maxWidth: '200px' }}
|
style={{ maxWidth: '200px' }}
|
||||||
>
|
>
|
||||||
<option value="">No Special Link</option>
|
<option value="">{t('noSpecialLink')}</option>
|
||||||
{specials.map(s => (
|
{specials.map(s => (
|
||||||
<option key={s.id} value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
|
<option key={s.id} value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
|
||||||
))}
|
))}
|
||||||
@@ -1449,9 +1467,9 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
checked={newNewsFeatured}
|
checked={newNewsFeatured}
|
||||||
onChange={e => setNewNewsFeatured(e.target.checked)}
|
onChange={e => setNewNewsFeatured(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
Featured
|
{t('featured')}
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" className="btn-primary">Add News</button>
|
<button type="submit" className="btn-primary">{t('addNews')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -1468,23 +1486,25 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={editNewsTitle[activeTab]}
|
value={editNewsTitle[activeTab] || ''}
|
||||||
onChange={e => setEditNewsTitle({ ...editNewsTitle, [activeTab]: e.target.value })}
|
onChange={e => setEditNewsTitle({ ...editNewsTitle, [activeTab]: e.target.value })}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
|
key={`edit-news-title-${activeTab}`}
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
value={editNewsContent[activeTab]}
|
value={editNewsContent[activeTab] || ''}
|
||||||
onChange={e => setEditNewsContent({ ...editNewsContent, [activeTab]: e.target.value })}
|
onChange={e => setEditNewsContent({ ...editNewsContent, [activeTab]: e.target.value })}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
rows={4}
|
rows={4}
|
||||||
style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}
|
style={{ fontFamily: 'monospace', fontSize: '0.875rem' }}
|
||||||
|
key={`edit-news-content-${activeTab}`}
|
||||||
/>
|
/>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={editNewsAuthor}
|
value={editNewsAuthor}
|
||||||
onChange={e => setEditNewsAuthor(e.target.value)}
|
onChange={e => setEditNewsAuthor(e.target.value)}
|
||||||
placeholder="Author"
|
placeholder={t('author')}
|
||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ maxWidth: '200px' }}
|
style={{ maxWidth: '200px' }}
|
||||||
/>
|
/>
|
||||||
@@ -1494,7 +1514,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
className="form-input"
|
className="form-input"
|
||||||
style={{ maxWidth: '200px' }}
|
style={{ maxWidth: '200px' }}
|
||||||
>
|
>
|
||||||
<option value="">No Special Link</option>
|
<option value="">{t('noSpecialLink')}</option>
|
||||||
{specials.map(s => (
|
{specials.map(s => (
|
||||||
<option key={s.id} value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
|
<option key={s.id} value={s.id}>{getLocalizedValue(s.name, activeTab)}</option>
|
||||||
))}
|
))}
|
||||||
@@ -1505,10 +1525,10 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
checked={editNewsFeatured}
|
checked={editNewsFeatured}
|
||||||
onChange={e => setEditNewsFeatured(e.target.checked)}
|
onChange={e => setEditNewsFeatured(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
Featured
|
{t('featured')}
|
||||||
</label>
|
</label>
|
||||||
<button onClick={saveEditedNews} className="btn-primary">Save</button>
|
<button onClick={saveEditedNews} className="btn-primary">{t('save')}</button>
|
||||||
<button onClick={() => setEditingNewsId(null)} className="btn-secondary">Cancel</button>
|
<button onClick={() => setEditingNewsId(null)} className="btn-secondary">{t('cancel')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -1543,7 +1563,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '0.25rem', marginLeft: '1rem' }}>
|
<div style={{ display: 'flex', gap: '0.25rem', marginLeft: '1rem' }}>
|
||||||
<button onClick={() => startEditNews(newsItem)} className="btn-secondary" style={{ padding: '0.25rem 0.5rem', fontSize: '0.75rem' }}>Edit</button>
|
<button onClick={() => startEditNews(newsItem)} className="btn-secondary" style={{ padding: '0.25rem 0.5rem', fontSize: '0.75rem' }}>Edit</button>
|
||||||
<button onClick={() => handleDeleteNews(newsItem.id)} className="btn-danger" style={{ padding: '0.25rem 0.5rem', fontSize: '0.75rem' }}>Delete</button>
|
<button onClick={() => handleDeleteNews(newsItem.id)} className="btn-danger" style={{ padding: '0.25rem 0.5rem', fontSize: '0.75rem' }}>{t('delete')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -1552,7 +1572,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
))}
|
))}
|
||||||
{news.length === 0 && (
|
{news.length === 0 && (
|
||||||
<p style={{ color: '#666', fontSize: '0.875rem', textAlign: 'center', padding: '1rem' }}>
|
<p style={{ color: '#666', fontSize: '0.875rem', textAlign: 'center', padding: '1rem' }}>
|
||||||
No news items yet. Create one above!
|
{t('noNewsItems')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1561,7 +1581,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
||||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', marginBottom: '1rem' }}>Upload Songs</h2>
|
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', marginBottom: '1rem' }}>{t('uploadSongs')}</h2>
|
||||||
<form onSubmit={handleBatchUpload}>
|
<form onSubmit={handleBatchUpload}>
|
||||||
{/* Drag & Drop Zone */}
|
{/* Drag & Drop Zone */}
|
||||||
<div
|
<div
|
||||||
@@ -1713,7 +1733,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<div className="admin-card">
|
<div className="admin-card">
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
|
||||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', margin: 0 }}>
|
||||||
Today's Daily Puzzles
|
{t('todaysPuzzles')}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDailyPuzzles(!showDailyPuzzles)}
|
onClick={() => setShowDailyPuzzles(!showDailyPuzzles)}
|
||||||
@@ -1730,15 +1750,15 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{showDailyPuzzles && (dailyPuzzles.length === 0 ? (
|
{showDailyPuzzles && (dailyPuzzles.length === 0 ? (
|
||||||
<p style={{ color: '#6b7280' }}>No daily puzzles found for today.</p>
|
<p style={{ color: '#6b7280' }}>{t('noPuzzlesToday')}</p>
|
||||||
) : (
|
) : (
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ borderBottom: '2px solid #e5e7eb' }}>
|
<tr style={{ borderBottom: '2px solid #e5e7eb' }}>
|
||||||
<th style={{ padding: '0.75rem', textAlign: 'left', fontWeight: 'bold' }}>Category</th>
|
<th style={{ padding: '0.75rem', textAlign: 'left', fontWeight: 'bold' }}>{t('category')}</th>
|
||||||
<th style={{ padding: '0.75rem', textAlign: 'left', fontWeight: 'bold' }}>Song</th>
|
<th style={{ padding: '0.75rem', textAlign: 'left', fontWeight: 'bold' }}>{t('song')}</th>
|
||||||
<th style={{ padding: '0.75rem', textAlign: 'left', fontWeight: 'bold' }}>Artist</th>
|
<th style={{ padding: '0.75rem', textAlign: 'left', fontWeight: 'bold' }}>{t('artist')}</th>
|
||||||
<th style={{ padding: '0.75rem', textAlign: 'center', fontWeight: 'bold' }}>Actions</th>
|
<th style={{ padding: '0.75rem', textAlign: 'center', fontWeight: 'bold' }}>{t('actions')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -1770,7 +1790,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleDeletePuzzle(puzzle.id)}
|
onClick={() => handleDeletePuzzle(puzzle.id)}
|
||||||
style={{ fontSize: '1.25rem', cursor: 'pointer', border: 'none', background: 'none' }}
|
style={{ fontSize: '1.25rem', cursor: 'pointer', border: 'none', background: 'none' }}
|
||||||
title="Delete"
|
title={t('deletePuzzle')}
|
||||||
>
|
>
|
||||||
🗑️
|
🗑️
|
||||||
</button>
|
</button>
|
||||||
@@ -2118,7 +2138,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(song.id, song.title)}
|
onClick={() => handleDelete(song.id, song.title)}
|
||||||
style={{ fontSize: '1.25rem', cursor: 'pointer', border: 'none', background: 'none' }}
|
style={{ fontSize: '1.25rem', cursor: 'pointer', border: 'none', background: 'none' }}
|
||||||
title="Delete"
|
title={t('deletePuzzle')}
|
||||||
>
|
>
|
||||||
🗑️
|
🗑️
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -104,5 +104,52 @@
|
|||||||
"globalTooltip": "Ein zufälliger Song aus der gesamten Sammlung",
|
"globalTooltip": "Ein zufälliger Song aus der gesamten Sammlung",
|
||||||
"comingSoon": "Demnächst",
|
"comingSoon": "Demnächst",
|
||||||
"curatedBy": "Kuratiert von"
|
"curatedBy": "Kuratiert von"
|
||||||
|
},
|
||||||
|
"Admin": {
|
||||||
|
"title": "Hördle Admin Dashboard",
|
||||||
|
"login": "Admin Login",
|
||||||
|
"password": "Passwort",
|
||||||
|
"loginButton": "Login",
|
||||||
|
"logout": "Abmelden",
|
||||||
|
"manageSpecials": "Specials verwalten",
|
||||||
|
"manageGenres": "Genres verwalten",
|
||||||
|
"manageNews": "News & Ankündigungen verwalten",
|
||||||
|
"uploadSongs": "Songs hochladen",
|
||||||
|
"todaysPuzzles": "Heutige tägliche Rätsel",
|
||||||
|
"show": "▶ Anzeigen",
|
||||||
|
"hide": "▼ Ausblenden",
|
||||||
|
"addSpecial": "Special hinzufügen",
|
||||||
|
"addGenre": "Genre hinzufügen",
|
||||||
|
"addNews": "News hinzufügen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"save": "Speichern",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"curate": "Kurieren",
|
||||||
|
"name": "Name",
|
||||||
|
"subtitle": "Untertitel",
|
||||||
|
"maxAttempts": "Max. Versuche",
|
||||||
|
"unlockSteps": "Freischalt-Schritte",
|
||||||
|
"launchDate": "Startdatum",
|
||||||
|
"endDate": "Enddatum",
|
||||||
|
"curator": "Kurator",
|
||||||
|
"active": "Aktiv",
|
||||||
|
"newGenreName": "Neuer Genre-Name",
|
||||||
|
"editSpecial": "Special bearbeiten",
|
||||||
|
"editGenre": "Genre bearbeiten",
|
||||||
|
"editNews": "News bearbeiten",
|
||||||
|
"newsTitle": "News-Titel",
|
||||||
|
"content": "Inhalt (Markdown unterstützt)",
|
||||||
|
"author": "Autor (optional)",
|
||||||
|
"featured": "Hervorgehoben",
|
||||||
|
"noSpecialLink": "Kein Special-Link",
|
||||||
|
"noNewsItems": "Noch keine News-Einträge. Erstelle einen oben!",
|
||||||
|
"noPuzzlesToday": "Keine täglichen Rätsel für heute gefunden.",
|
||||||
|
"category": "Kategorie",
|
||||||
|
"song": "Song",
|
||||||
|
"artist": "Interpret",
|
||||||
|
"actions": "Aktionen",
|
||||||
|
"deletePuzzle": "Löschen",
|
||||||
|
"wrongPassword": "Falsches Passwort"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,5 +104,52 @@
|
|||||||
"globalTooltip": "A random song from the entire collection",
|
"globalTooltip": "A random song from the entire collection",
|
||||||
"comingSoon": "Coming soon",
|
"comingSoon": "Coming soon",
|
||||||
"curatedBy": "Curated by"
|
"curatedBy": "Curated by"
|
||||||
|
},
|
||||||
|
"Admin": {
|
||||||
|
"title": "Hördle Admin Dashboard",
|
||||||
|
"login": "Admin Login",
|
||||||
|
"password": "Password",
|
||||||
|
"loginButton": "Login",
|
||||||
|
"logout": "Logout",
|
||||||
|
"manageSpecials": "Manage Specials",
|
||||||
|
"manageGenres": "Manage Genres",
|
||||||
|
"manageNews": "Manage News & Announcements",
|
||||||
|
"uploadSongs": "Upload Songs",
|
||||||
|
"todaysPuzzles": "Today's Daily Puzzles",
|
||||||
|
"show": "▶ Show",
|
||||||
|
"hide": "▼ Hide",
|
||||||
|
"addSpecial": "Add Special",
|
||||||
|
"addGenre": "Add Genre",
|
||||||
|
"addNews": "Add News",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"curate": "Curate",
|
||||||
|
"name": "Name",
|
||||||
|
"subtitle": "Subtitle",
|
||||||
|
"maxAttempts": "Max Attempts",
|
||||||
|
"unlockSteps": "Unlock Steps",
|
||||||
|
"launchDate": "Launch Date",
|
||||||
|
"endDate": "End Date",
|
||||||
|
"curator": "Curator",
|
||||||
|
"active": "Active",
|
||||||
|
"newGenreName": "New Genre Name",
|
||||||
|
"editSpecial": "Edit Special",
|
||||||
|
"editGenre": "Edit Genre",
|
||||||
|
"editNews": "Edit News",
|
||||||
|
"newsTitle": "News Title",
|
||||||
|
"content": "Content (Markdown supported)",
|
||||||
|
"author": "Author (optional)",
|
||||||
|
"featured": "Featured",
|
||||||
|
"noSpecialLink": "No Special Link",
|
||||||
|
"noNewsItems": "No news items yet. Create one above!",
|
||||||
|
"noPuzzlesToday": "No daily puzzles found for today.",
|
||||||
|
"category": "Category",
|
||||||
|
"song": "Song",
|
||||||
|
"artist": "Artist",
|
||||||
|
"actions": "Actions",
|
||||||
|
"deletePuzzle": "Delete",
|
||||||
|
"wrongPassword": "Wrong password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user