feat(specials): add curator field
This commit is contained in:
@@ -10,6 +10,7 @@ interface Special {
|
|||||||
unlockSteps: string;
|
unlockSteps: string;
|
||||||
launchDate?: string;
|
launchDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
curator?: string;
|
||||||
_count?: {
|
_count?: {
|
||||||
songs: number;
|
songs: number;
|
||||||
};
|
};
|
||||||
@@ -66,6 +67,7 @@ export default function AdminPage() {
|
|||||||
const [newSpecialUnlockSteps, setNewSpecialUnlockSteps] = useState('[2,4,7,11,16,30,60]');
|
const [newSpecialUnlockSteps, setNewSpecialUnlockSteps] = useState('[2,4,7,11,16,30,60]');
|
||||||
const [newSpecialLaunchDate, setNewSpecialLaunchDate] = useState('');
|
const [newSpecialLaunchDate, setNewSpecialLaunchDate] = useState('');
|
||||||
const [newSpecialEndDate, setNewSpecialEndDate] = useState('');
|
const [newSpecialEndDate, setNewSpecialEndDate] = useState('');
|
||||||
|
const [newSpecialCurator, setNewSpecialCurator] = useState('');
|
||||||
|
|
||||||
const [editingSpecialId, setEditingSpecialId] = useState<number | null>(null);
|
const [editingSpecialId, setEditingSpecialId] = useState<number | null>(null);
|
||||||
const [editSpecialName, setEditSpecialName] = useState('');
|
const [editSpecialName, setEditSpecialName] = useState('');
|
||||||
@@ -73,6 +75,7 @@ export default function AdminPage() {
|
|||||||
const [editSpecialUnlockSteps, setEditSpecialUnlockSteps] = useState('[2,4,7,11,16,30,60]');
|
const [editSpecialUnlockSteps, setEditSpecialUnlockSteps] = useState('[2,4,7,11,16,30,60]');
|
||||||
const [editSpecialLaunchDate, setEditSpecialLaunchDate] = useState('');
|
const [editSpecialLaunchDate, setEditSpecialLaunchDate] = useState('');
|
||||||
const [editSpecialEndDate, setEditSpecialEndDate] = useState('');
|
const [editSpecialEndDate, setEditSpecialEndDate] = useState('');
|
||||||
|
const [editSpecialCurator, setEditSpecialCurator] = useState('');
|
||||||
|
|
||||||
// Edit state
|
// Edit state
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
@@ -186,6 +189,7 @@ export default function AdminPage() {
|
|||||||
unlockSteps: newSpecialUnlockSteps,
|
unlockSteps: newSpecialUnlockSteps,
|
||||||
launchDate: newSpecialLaunchDate || null,
|
launchDate: newSpecialLaunchDate || null,
|
||||||
endDate: newSpecialEndDate || null,
|
endDate: newSpecialEndDate || null,
|
||||||
|
curator: newSpecialCurator || null,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -194,6 +198,7 @@ export default function AdminPage() {
|
|||||||
setNewSpecialUnlockSteps('[2,4,7,11,16,30,60]');
|
setNewSpecialUnlockSteps('[2,4,7,11,16,30,60]');
|
||||||
setNewSpecialLaunchDate('');
|
setNewSpecialLaunchDate('');
|
||||||
setNewSpecialEndDate('');
|
setNewSpecialEndDate('');
|
||||||
|
setNewSpecialCurator('');
|
||||||
fetchSpecials();
|
fetchSpecials();
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to create special');
|
alert('Failed to create special');
|
||||||
@@ -275,6 +280,7 @@ export default function AdminPage() {
|
|||||||
setEditSpecialUnlockSteps(special.unlockSteps);
|
setEditSpecialUnlockSteps(special.unlockSteps);
|
||||||
setEditSpecialLaunchDate(special.launchDate ? new Date(special.launchDate).toISOString().split('T')[0] : '');
|
setEditSpecialLaunchDate(special.launchDate ? new Date(special.launchDate).toISOString().split('T')[0] : '');
|
||||||
setEditSpecialEndDate(special.endDate ? new Date(special.endDate).toISOString().split('T')[0] : '');
|
setEditSpecialEndDate(special.endDate ? new Date(special.endDate).toISOString().split('T')[0] : '');
|
||||||
|
setEditSpecialCurator(special.curator || '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveEditedSpecial = async () => {
|
const saveEditedSpecial = async () => {
|
||||||
@@ -289,6 +295,7 @@ export default function AdminPage() {
|
|||||||
unlockSteps: editSpecialUnlockSteps,
|
unlockSteps: editSpecialUnlockSteps,
|
||||||
launchDate: editSpecialLaunchDate || null,
|
launchDate: editSpecialLaunchDate || null,
|
||||||
endDate: editSpecialEndDate || null,
|
endDate: editSpecialEndDate || null,
|
||||||
|
curator: editSpecialCurator || null,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -707,6 +714,10 @@ export default function AdminPage() {
|
|||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</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' }}>
|
||||||
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>Curator</label>
|
||||||
|
<input type="text" placeholder="Curator name" value={newSpecialCurator} onChange={e => setNewSpecialCurator(e.target.value)} className="form-input" />
|
||||||
|
</div>
|
||||||
<button type="submit" className="btn-primary" style={{ height: '38px' }}>Add Special</button>
|
<button type="submit" className="btn-primary" style={{ height: '38px' }}>Add Special</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -752,6 +763,10 @@ export default function AdminPage() {
|
|||||||
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</label>
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>End Date</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' }}>
|
||||||
|
<label style={{ fontSize: '0.75rem', color: '#666' }}>Curator</label>
|
||||||
|
<input type="text" value={editSpecialCurator} onChange={e => setEditSpecialCurator(e.target.value)} className="form-input" />
|
||||||
|
</div>
|
||||||
<button onClick={saveEditedSpecial} className="btn-primary" style={{ height: '38px' }}>Save</button>
|
<button onClick={saveEditedSpecial} className="btn-primary" style={{ height: '38px' }}>Save</button>
|
||||||
<button onClick={() => setEditingSpecialId(null)} className="btn-secondary" style={{ height: '38px' }}>Cancel</button>
|
<button onClick={() => setEditingSpecialId(null)} className="btn-secondary" style={{ height: '38px' }}>Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export async function PUT(
|
|||||||
try {
|
try {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const specialId = parseInt(id);
|
const specialId = parseInt(id);
|
||||||
const { name, maxAttempts, unlockSteps, launchDate, endDate } = await request.json();
|
const { name, maxAttempts, unlockSteps, launchDate, endDate, curator } = await request.json();
|
||||||
|
|
||||||
const special = await prisma.special.update({
|
const special = await prisma.special.update({
|
||||||
where: { id: specialId },
|
where: { id: specialId },
|
||||||
@@ -53,6 +53,7 @@ export async function PUT(
|
|||||||
unlockSteps: typeof unlockSteps === 'string' ? unlockSteps : JSON.stringify(unlockSteps),
|
unlockSteps: typeof unlockSteps === 'string' ? unlockSteps : JSON.stringify(unlockSteps),
|
||||||
launchDate: launchDate ? new Date(launchDate) : null,
|
launchDate: launchDate ? new Date(launchDate) : null,
|
||||||
endDate: endDate ? new Date(endDate) : null,
|
endDate: endDate ? new Date(endDate) : null,
|
||||||
|
curator: curator || null,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export async function GET() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
const { name, maxAttempts = 7, unlockSteps = '[2,4,7,11,16,30,60]', launchDate, endDate } = await request.json();
|
const { name, maxAttempts = 7, unlockSteps = '[2,4,7,11,16,30,60]', launchDate, endDate, curator } = await request.json();
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return NextResponse.json({ error: 'Name is required' }, { status: 400 });
|
return NextResponse.json({ error: 'Name is required' }, { status: 400 });
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ export async function POST(request: Request) {
|
|||||||
unlockSteps,
|
unlockSteps,
|
||||||
launchDate: launchDate ? new Date(launchDate) : null,
|
launchDate: launchDate ? new Date(launchDate) : null,
|
||||||
endDate: endDate ? new Date(endDate) : null,
|
endDate: endDate ? new Date(endDate) : null,
|
||||||
|
curator: curator || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return NextResponse.json(special);
|
return NextResponse.json(special);
|
||||||
@@ -42,7 +43,7 @@ export async function DELETE(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function PUT(request: Request) {
|
export async function PUT(request: Request) {
|
||||||
const { id, name, maxAttempts, unlockSteps, launchDate, endDate } = await request.json();
|
const { id, name, maxAttempts, unlockSteps, launchDate, endDate, curator } = await request.json();
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return NextResponse.json({ error: 'ID required' }, { status: 400 });
|
return NextResponse.json({ error: 'ID required' }, { status: 400 });
|
||||||
}
|
}
|
||||||
@@ -54,6 +55,7 @@ export async function PUT(request: Request) {
|
|||||||
...(unlockSteps && { unlockSteps }),
|
...(unlockSteps && { unlockSteps }),
|
||||||
launchDate: launchDate ? new Date(launchDate) : null,
|
launchDate: launchDate ? new Date(launchDate) : null,
|
||||||
endDate: endDate ? new Date(endDate) : null,
|
endDate: endDate ? new Date(endDate) : null,
|
||||||
|
curator: curator || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return NextResponse.json(updated);
|
return NextResponse.json(updated);
|
||||||
|
|||||||
28
app/page.tsx
28
app/page.tsx
@@ -44,17 +44,23 @@ export default async function Home() {
|
|||||||
|
|
||||||
{/* Active Specials */}
|
{/* Active Specials */}
|
||||||
{activeSpecials.map(s => (
|
{activeSpecials.map(s => (
|
||||||
<Link
|
<div key={s.id} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||||
key={s.id}
|
<Link
|
||||||
href={`/special/${s.name}`}
|
href={`/special/${s.name}`}
|
||||||
style={{
|
style={{
|
||||||
color: '#be185d', // Pink-700
|
color: '#be185d', // Pink-700
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
fontWeight: '500'
|
fontWeight: '500'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
★ {s.name}
|
★ {s.name}
|
||||||
</Link>
|
</Link>
|
||||||
|
{s.curator && (
|
||||||
|
<span style={{ fontSize: '0.75rem', color: '#666' }}>
|
||||||
|
Curated by {s.curator}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Special" ADD COLUMN "curator" TEXT;
|
||||||
@@ -37,6 +37,7 @@ model Special {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
launchDate DateTime?
|
launchDate DateTime?
|
||||||
endDate DateTime?
|
endDate DateTime?
|
||||||
|
curator String?
|
||||||
songs SpecialSong[]
|
songs SpecialSong[]
|
||||||
puzzles DailyPuzzle[]
|
puzzles DailyPuzzle[]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user