feat(specials): add launch and end dates for scheduling
This commit is contained in:
@@ -8,6 +8,8 @@ interface Special {
|
||||
name: string;
|
||||
maxAttempts: number;
|
||||
unlockSteps: string;
|
||||
launchDate?: string;
|
||||
endDate?: string;
|
||||
_count?: {
|
||||
songs: number;
|
||||
};
|
||||
@@ -62,10 +64,15 @@ export default function AdminPage() {
|
||||
const [newSpecialName, setNewSpecialName] = useState('');
|
||||
const [newSpecialMaxAttempts, setNewSpecialMaxAttempts] = useState(7);
|
||||
const [newSpecialUnlockSteps, setNewSpecialUnlockSteps] = useState('[2,4,7,11,16,30,60]');
|
||||
const [newSpecialLaunchDate, setNewSpecialLaunchDate] = useState('');
|
||||
const [newSpecialEndDate, setNewSpecialEndDate] = useState('');
|
||||
|
||||
const [editingSpecialId, setEditingSpecialId] = useState<number | null>(null);
|
||||
const [editSpecialName, setEditSpecialName] = useState('');
|
||||
const [editSpecialMaxAttempts, setEditSpecialMaxAttempts] = useState(7);
|
||||
const [editSpecialUnlockSteps, setEditSpecialUnlockSteps] = useState('[2,4,7,11,16,30,60]');
|
||||
const [editSpecialLaunchDate, setEditSpecialLaunchDate] = useState('');
|
||||
const [editSpecialEndDate, setEditSpecialEndDate] = useState('');
|
||||
|
||||
// Edit state
|
||||
const [editingId, setEditingId] = useState<number | null>(null);
|
||||
@@ -177,12 +184,16 @@ export default function AdminPage() {
|
||||
name: newSpecialName,
|
||||
maxAttempts: newSpecialMaxAttempts,
|
||||
unlockSteps: newSpecialUnlockSteps,
|
||||
launchDate: newSpecialLaunchDate || null,
|
||||
endDate: newSpecialEndDate || null,
|
||||
}),
|
||||
});
|
||||
if (res.ok) {
|
||||
setNewSpecialName('');
|
||||
setNewSpecialMaxAttempts(7);
|
||||
setNewSpecialUnlockSteps('[2,4,7,11,16,30,60]');
|
||||
setNewSpecialLaunchDate('');
|
||||
setNewSpecialEndDate('');
|
||||
fetchSpecials();
|
||||
} else {
|
||||
alert('Failed to create special');
|
||||
@@ -262,6 +273,8 @@ export default function AdminPage() {
|
||||
setEditSpecialName(special.name);
|
||||
setEditSpecialMaxAttempts(special.maxAttempts);
|
||||
setEditSpecialUnlockSteps(special.unlockSteps);
|
||||
setEditSpecialLaunchDate(special.launchDate ? new Date(special.launchDate).toISOString().split('T')[0] : '');
|
||||
setEditSpecialEndDate(special.endDate ? new Date(special.endDate).toISOString().split('T')[0] : '');
|
||||
};
|
||||
|
||||
const saveEditedSpecial = async () => {
|
||||
@@ -274,6 +287,8 @@ export default function AdminPage() {
|
||||
name: editSpecialName,
|
||||
maxAttempts: editSpecialMaxAttempts,
|
||||
unlockSteps: editSpecialUnlockSteps,
|
||||
launchDate: editSpecialLaunchDate || null,
|
||||
endDate: editSpecialEndDate || null,
|
||||
}),
|
||||
});
|
||||
if (res.ok) {
|
||||
@@ -671,11 +686,28 @@ export default function AdminPage() {
|
||||
<div className="admin-card" style={{ marginBottom: '2rem' }}>
|
||||
<h2 style={{ fontSize: '1.25rem', fontWeight: 'bold', marginBottom: '1rem' }}>Manage Specials</h2>
|
||||
<form onSubmit={handleCreateSpecial} style={{ marginBottom: '1rem' }}>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<input type="text" placeholder="Special name" value={newSpecialName} onChange={e => setNewSpecialName(e.target.value)} className="form-input" required />
|
||||
<input type="number" placeholder="Max attempts" value={newSpecialMaxAttempts} onChange={e => setNewSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} />
|
||||
<input type="text" placeholder="Unlock steps JSON" value={newSpecialUnlockSteps} onChange={e => setNewSpecialUnlockSteps(e.target.value)} className="form-input" />
|
||||
<button type="submit" className="btn-primary">Add Special</button>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Name</label>
|
||||
<input type="text" placeholder="Special name" value={newSpecialName} onChange={e => setNewSpecialName(e.target.value)} className="form-input" required />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Max Attempts</label>
|
||||
<input type="number" placeholder="Max attempts" value={newSpecialMaxAttempts} onChange={e => setNewSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Unlock Steps</label>
|
||||
<input type="text" placeholder="Unlock steps JSON" value={newSpecialUnlockSteps} onChange={e => setNewSpecialUnlockSteps(e.target.value)} className="form-input" style={{ width: '200px' }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Launch Date</label>
|
||||
<input type="date" value={newSpecialLaunchDate} onChange={e => setNewSpecialLaunchDate(e.target.value)} className="form-input" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</label>
|
||||
<input type="date" value={newSpecialEndDate} onChange={e => setNewSpecialEndDate(e.target.value)} className="form-input" />
|
||||
</div>
|
||||
<button type="submit" className="btn-primary" style={{ height: '38px' }}>Add Special</button>
|
||||
</div>
|
||||
</form>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
|
||||
@@ -691,6 +723,7 @@ export default function AdminPage() {
|
||||
}}>
|
||||
<span>{special.name} ({special._count?.songs || 0})</span>
|
||||
<a href={`/admin/specials/${special.id}`} className="btn-primary" style={{ marginRight: '0.5rem', textDecoration: 'none' }}>Curate</a>
|
||||
<button onClick={() => startEditSpecial(special)} className="btn-secondary" style={{ marginRight: '0.5rem' }}>Edit</button>
|
||||
<button onClick={() => handleDeleteSpecial(special.id)} className="btn-danger">Delete</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -698,12 +731,29 @@ export default function AdminPage() {
|
||||
{editingSpecialId !== null && (
|
||||
<div style={{ marginTop: '1rem', padding: '1rem', background: '#f9fafb', borderRadius: '0.5rem' }}>
|
||||
<h3>Edit Special</h3>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<input type="text" value={editSpecialName} onChange={e => setEditSpecialName(e.target.value)} className="form-input" />
|
||||
<input type="number" value={editSpecialMaxAttempts} onChange={e => setEditSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} />
|
||||
<input type="text" value={editSpecialUnlockSteps} onChange={e => setEditSpecialUnlockSteps(e.target.value)} className="form-input" />
|
||||
<button onClick={saveEditedSpecial} className="btn-primary">Save</button>
|
||||
<button onClick={() => setEditingSpecialId(null)} className="btn-secondary">Cancel</button>
|
||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Name</label>
|
||||
<input type="text" value={editSpecialName} onChange={e => setEditSpecialName(e.target.value)} className="form-input" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Max Attempts</label>
|
||||
<input type="number" value={editSpecialMaxAttempts} onChange={e => setEditSpecialMaxAttempts(Number(e.target.value))} className="form-input" min={1} style={{ width: '80px' }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Unlock Steps</label>
|
||||
<input type="text" value={editSpecialUnlockSteps} onChange={e => setEditSpecialUnlockSteps(e.target.value)} className="form-input" style={{ width: '200px' }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>Launch Date</label>
|
||||
<input type="date" value={editSpecialLaunchDate} onChange={e => setEditSpecialLaunchDate(e.target.value)} className="form-input" />
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</label>
|
||||
<input type="date" value={editSpecialEndDate} onChange={e => setEditSpecialEndDate(e.target.value)} className="form-input" />
|
||||
</div>
|
||||
<button onClick={saveEditedSpecial} className="btn-primary" style={{ height: '38px' }}>Save</button>
|
||||
<button onClick={() => setEditingSpecialId(null)} className="btn-secondary" style={{ height: '38px' }}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user