refactor: rename admin routes and views to mymusic namespace

- Move /admin/* routes to /mymusic/* for clearer user-focused section naming
- Rename admin/ view templates to mymusic/ with updated paths and URLs
- Add cover art preview in song edit form with current cover thumbnail styling
- Update navigation active states to use new /mymusic* paths
- Change now-playing creator link from window.open() to direct navigation
This commit is contained in:
Brian Fertig 2026-04-12 10:29:00 -06:00
parent 561f41f0ce
commit 20bd6422a6
12 changed files with 73 additions and 57 deletions

View File

@ -436,6 +436,8 @@ a.detail-creator-card:hover {
.form-card::after { bottom: 6px; right: 6px; border-left: none; border-top: none; } .form-card::after { bottom: 6px; right: 6px; border-left: none; border-top: none; }
.stacked { display: flex; flex-direction: column; gap: 1rem; } .stacked { display: flex; flex-direction: column; gap: 1rem; }
.current-cover-wrap { margin: 0.25rem 0; }
.current-cover-thumb { width: 80px; height: 80px; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--border); }
.stacked label { .stacked label {
display: flex; flex-direction: column; gap: 0.35rem; display: flex; flex-direction: column; gap: 0.35rem;
font-size: 0.72rem; color: var(--muted); font-size: 0.72rem; color: var(--muted);

View File

@ -170,7 +170,7 @@
// Make the whole creator block a link if we have a profile slug. // Make the whole creator block a link if we have a profile slug.
if (creatorData.slug) { if (creatorData.slug) {
creatorInfo.style.cursor = 'pointer'; creatorInfo.style.cursor = 'pointer';
creatorInfo.onclick = () => { window.open('/profiles/' + creatorData.slug, '_blank'); }; creatorInfo.onclick = () => { window.location.href = '/profiles/' + creatorData.slug; };
} else { } else {
creatorInfo.style.cursor = ''; creatorInfo.style.cursor = '';
creatorInfo.onclick = null; creatorInfo.onclick = null;

View File

@ -77,8 +77,8 @@ app.use('/api', require('./routes/social')(csrfSynchronisedProtection));
app.use('/', require('./routes/public')); app.use('/', require('./routes/public'));
app.use('/', require('./routes/auth')(csrfSynchronisedProtection)); app.use('/', require('./routes/auth')(csrfSynchronisedProtection));
app.use('/account', require('./routes/account')(csrfSynchronisedProtection)); app.use('/account', require('./routes/account')(csrfSynchronisedProtection));
app.use('/admin/users', require('./routes/users-admin')(csrfSynchronisedProtection)); app.use('/mymusic/users', require('./routes/users-admin')(csrfSynchronisedProtection));
app.use('/admin', require('./routes/admin')(csrfSynchronisedProtection)); app.use('/mymusic', require('./routes/admin')(csrfSynchronisedProtection));
// 404. // 404.
app.use((req, res) => { app.use((req, res) => {

View File

@ -150,15 +150,15 @@ module.exports = function adminRoutes(csrfProtection) {
router.use(requireUser); router.use(requireUser);
// ---------- Songs ---------- // ---------- Songs ----------
router.get('/', (req, res) => res.redirect('/admin/songs')); router.get('/', (req, res) => res.redirect('/mymusic/songs'));
router.get('/songs', (req, res) => { router.get('/songs', (req, res) => {
const songList = songs.listByUser(req.session.user.id); const songList = songs.listByUser(req.session.user.id);
res.render('admin/songs', { title: 'My Music · Songs', songs: songList }); res.render('mymusic/songs', { title: 'My Music · Songs', songs: songList });
}); });
router.get('/songs/new', (req, res) => { router.get('/songs/new', (req, res) => {
res.render('admin/song_form', { title: 'New song', song: null, errors: {}, values: { visibility: 'logged_in' } }); res.render('mymusic/song_form', { title: 'New song', song: null, errors: {}, values: { visibility: 'logged_in' } });
}); });
router.post('/songs', songFields, csrfProtection, async (req, res, next) => { router.post('/songs', songFields, csrfProtection, async (req, res, next) => {
@ -170,7 +170,7 @@ module.exports = function adminRoutes(csrfProtection) {
if (!req.files || !req.files.audio || !req.files.audio[0]) errors.audio = 'Audio file is required.'; if (!req.files || !req.files.audio || !req.files.audio[0]) errors.audio = 'Audio file is required.';
if (req.files?.audio?.[0]) unlinkQuiet(req.files.audio[0].path); if (req.files?.audio?.[0]) unlinkQuiet(req.files.audio[0].path);
if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path); if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path);
return res.status(400).render('admin/song_form', { title: 'New song', song: null, errors, values: req.body }); return res.status(400).render('mymusic/song_form', { title: 'New song', song: null, errors, values: req.body });
} }
const audioFile = req.files.audio[0]; const audioFile = req.files.audio[0];
const coverFile = req.files.cover?.[0]; const coverFile = req.files.cover?.[0];
@ -212,7 +212,7 @@ module.exports = function adminRoutes(csrfProtection) {
uploadedBy: req.session.user.id, uploadedBy: req.session.user.id,
}); });
req.flash('success', 'Song uploaded. Tags were read from the file — review and edit if needed.'); req.flash('success', 'Song uploaded. Tags were read from the file — review and edit if needed.');
res.redirect('/admin/songs'); res.redirect('/mymusic/songs');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -220,13 +220,13 @@ module.exports = function adminRoutes(csrfProtection) {
const song = songs.findById(Number(req.params.id)); const song = songs.findById(Number(req.params.id));
if (!song) return next(); if (!song) return next();
if (!canManage(song, req.session.user)) return forbidden(next); if (!canManage(song, req.session.user)) return forbidden(next);
res.render('admin/song_form', { res.render('mymusic/song_form', {
title: 'Edit song', song, errors: {}, title: 'Edit song', song, errors: {},
values: { ...song }, values: { ...song },
}); });
}); });
router.post('/songs/:id', csrfProtection, (req, res, next) => { router.post('/songs/:id', songFields, csrfProtection, (req, res, next) => {
try { try {
const id = Number(req.params.id); const id = Number(req.params.id);
const existing = songs.findById(id); const existing = songs.findById(id);
@ -236,7 +236,8 @@ module.exports = function adminRoutes(csrfProtection) {
if (!parsed.success) { if (!parsed.success) {
const errors = {}; const errors = {};
for (const i of parsed.error.issues) errors[i.path[0]] = i.message; for (const i of parsed.error.issues) errors[i.path[0]] = i.message;
return res.status(400).render('admin/song_form', { title: 'Edit song', song: existing, errors, values: req.body }); if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path);
return res.status(400).render('mymusic/song_form', { title: 'Edit song', song: existing, errors, values: req.body });
} }
songs.update(id, { songs.update(id, {
title: parsed.data.title, title: parsed.data.title,
@ -246,8 +247,13 @@ module.exports = function adminRoutes(csrfProtection) {
year: parsed.data.year ?? null, year: parsed.data.year ?? null,
visibility: parsed.data.visibility, visibility: parsed.data.visibility,
}); });
const coverFile = req.files?.cover?.[0];
if (coverFile) {
if (existing.coverPath) unlinkQuiet(path.join(mediaDir, existing.coverPath));
songs.setCover(id, relMedia(coverFile, 'covers'));
}
req.flash('success', 'Song updated.'); req.flash('success', 'Song updated.');
res.redirect('/admin/songs'); res.redirect('/mymusic/songs');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -262,19 +268,19 @@ module.exports = function adminRoutes(csrfProtection) {
if (existing.audioPath) unlinkQuiet(path.join(mediaDir, existing.audioPath)); if (existing.audioPath) unlinkQuiet(path.join(mediaDir, existing.audioPath));
if (existing.coverPath) unlinkQuiet(path.join(mediaDir, existing.coverPath)); if (existing.coverPath) unlinkQuiet(path.join(mediaDir, existing.coverPath));
req.flash('success', 'Song deleted.'); req.flash('success', 'Song deleted.');
res.redirect('/admin/songs'); res.redirect('/mymusic/songs');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
// ---------- Playlists ---------- // ---------- Playlists ----------
router.get('/playlists', (req, res) => { router.get('/playlists', (req, res) => {
const playlistList = playlists.listByUser(req.session.user.id); const playlistList = playlists.listByUser(req.session.user.id);
res.render('admin/playlists', { title: 'My Music · Playlists', playlists: playlistList }); res.render('mymusic/playlists', { title: 'My Music · Playlists', playlists: playlistList });
}); });
router.get('/playlists/new', (req, res) => { router.get('/playlists/new', (req, res) => {
const allSongs = songs.listForManagement(req.session.user.id); const allSongs = songs.listForManagement(req.session.user.id);
res.render('admin/playlist_form', { title: 'New playlist', playlist: null, tracks: [], allSongs, errors: {}, values: { visibility: 'logged_in' } }); res.render('mymusic/playlist_form', { title: 'New playlist', playlist: null, tracks: [], allSongs, errors: {}, values: { visibility: 'logged_in' } });
}); });
router.post('/playlists', playlistFields, csrfProtection, (req, res, next) => { router.post('/playlists', playlistFields, csrfProtection, (req, res, next) => {
@ -286,7 +292,7 @@ module.exports = function adminRoutes(csrfProtection) {
for (const i of parsed.error.issues) errors[i.path[0]] = i.message; for (const i of parsed.error.issues) errors[i.path[0]] = i.message;
if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path); if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path);
const allSongs = songs.listForManagement(user.id); const allSongs = songs.listForManagement(user.id);
return res.status(400).render('admin/playlist_form', { title: 'New playlist', playlist: null, tracks: [], allSongs, errors, values: req.body }); return res.status(400).render('mymusic/playlist_form', { title: 'New playlist', playlist: null, tracks: [], allSongs, errors, values: req.body });
} }
const coverFile = req.files?.cover?.[0]; const coverFile = req.files?.cover?.[0];
const created = playlists.create({ const created = playlists.create({
@ -297,7 +303,7 @@ module.exports = function adminRoutes(csrfProtection) {
createdBy: req.session.user.id, createdBy: req.session.user.id,
}); });
req.flash('success', 'Playlist created.'); req.flash('success', 'Playlist created.');
res.redirect(`/admin/playlists/${created.id}/edit`); res.redirect(`/mymusic/playlists/${created.id}/edit`);
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -307,7 +313,7 @@ module.exports = function adminRoutes(csrfProtection) {
if (!playlist) return next(); if (!playlist) return next();
if (!canManage(playlist, req.session.user)) return forbidden(next); if (!canManage(playlist, req.session.user)) return forbidden(next);
const allSongs = songs.listForManagement(req.session.user.id); const allSongs = songs.listForManagement(req.session.user.id);
res.render('admin/playlist_form', { res.render('mymusic/playlist_form', {
title: 'Edit playlist', title: 'Edit playlist',
playlist, playlist,
tracks: playlists.getTracks(id), tracks: playlists.getTracks(id),
@ -330,7 +336,7 @@ module.exports = function adminRoutes(csrfProtection) {
for (const i of parsed.error.issues) errors[i.path[0]] = i.message; for (const i of parsed.error.issues) errors[i.path[0]] = i.message;
if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path); if (req.files?.cover?.[0]) unlinkQuiet(req.files.cover[0].path);
const allSongs = songs.listForManagement(user.id); const allSongs = songs.listForManagement(user.id);
return res.status(400).render('admin/playlist_form', { return res.status(400).render('mymusic/playlist_form', {
title: 'Edit playlist', playlist: existing, title: 'Edit playlist', playlist: existing,
tracks: playlists.getTracks(id), allSongs, tracks: playlists.getTracks(id), allSongs,
errors, values: req.body, errors, values: req.body,
@ -347,7 +353,7 @@ module.exports = function adminRoutes(csrfProtection) {
playlists.setCover(id, relMedia(coverFile, 'covers')); playlists.setCover(id, relMedia(coverFile, 'covers'));
} }
req.flash('success', 'Playlist updated.'); req.flash('success', 'Playlist updated.');
res.redirect(`/admin/playlists/${id}/edit`); res.redirect(`/mymusic/playlists/${id}/edit`);
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -364,7 +370,7 @@ module.exports = function adminRoutes(csrfProtection) {
if (VISIBILITY_RANK[song.visibility] > VISIBILITY_RANK[playlist.visibility]) { if (VISIBILITY_RANK[song.visibility] > VISIBILITY_RANK[playlist.visibility]) {
playlists.setVisibility(id, song.visibility); playlists.setVisibility(id, song.visibility);
} }
res.redirect(`/admin/playlists/${id}/edit`); res.redirect(`/mymusic/playlists/${id}/edit`);
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -376,7 +382,7 @@ module.exports = function adminRoutes(csrfProtection) {
if (!playlist) return next(); if (!playlist) return next();
if (!canManage(playlist, req.session.user)) return forbidden(next); if (!canManage(playlist, req.session.user)) return forbidden(next);
playlists.removeTrack(id, songId); playlists.removeTrack(id, songId);
res.redirect(`/admin/playlists/${id}/edit`); res.redirect(`/mymusic/playlists/${id}/edit`);
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -389,7 +395,7 @@ module.exports = function adminRoutes(csrfProtection) {
const order = String(req.body.order || '') const order = String(req.body.order || '')
.split(',').map((x) => Number(x.trim())).filter(Number.isFinite); .split(',').map((x) => Number(x.trim())).filter(Number.isFinite);
playlists.setOrder(id, order); playlists.setOrder(id, order);
res.redirect(`/admin/playlists/${id}/edit`); res.redirect(`/mymusic/playlists/${id}/edit`);
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -402,7 +408,7 @@ module.exports = function adminRoutes(csrfProtection) {
playlists.remove(id); playlists.remove(id);
if (existing.coverPath) unlinkQuiet(path.join(mediaDir, existing.coverPath)); if (existing.coverPath) unlinkQuiet(path.join(mediaDir, existing.coverPath));
req.flash('success', 'Playlist deleted.'); req.flash('success', 'Playlist deleted.');
res.redirect('/admin/playlists'); res.redirect('/mymusic/playlists');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });

View File

@ -15,7 +15,7 @@ module.exports = function usersAdminRoutes(csrfProtection) {
router.use(requireAdmin); router.use(requireAdmin);
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.render('admin/users', { title: 'Users', userList: users.listAllUsers() }); res.render('mymusic/users', { title: 'Users', userList: users.listAllUsers() });
}); });
router.post('/:id/disable', csrfProtection, (req, res, next) => { router.post('/:id/disable', csrfProtection, (req, res, next) => {
@ -23,11 +23,11 @@ module.exports = function usersAdminRoutes(csrfProtection) {
const id = Number(req.params.id); const id = Number(req.params.id);
if (id === req.session.user.id) { if (id === req.session.user.id) {
req.flash('error', 'You cannot disable your own account.'); req.flash('error', 'You cannot disable your own account.');
return res.redirect('/admin/users'); return res.redirect('/mymusic/users');
} }
users.setDisabled(id, true); users.setDisabled(id, true);
req.flash('success', 'Account disabled.'); req.flash('success', 'Account disabled.');
res.redirect('/admin/users'); res.redirect('/mymusic/users');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -35,7 +35,7 @@ module.exports = function usersAdminRoutes(csrfProtection) {
try { try {
users.setDisabled(Number(req.params.id), false); users.setDisabled(Number(req.params.id), false);
req.flash('success', 'Account enabled.'); req.flash('success', 'Account enabled.');
res.redirect('/admin/users'); res.redirect('/mymusic/users');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -43,7 +43,7 @@ module.exports = function usersAdminRoutes(csrfProtection) {
try { try {
users.markVerifiedAdmin(Number(req.params.id)); users.markVerifiedAdmin(Number(req.params.id));
req.flash('success', 'Email marked as verified.'); req.flash('success', 'Email marked as verified.');
res.redirect('/admin/users'); res.redirect('/mymusic/users');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -52,7 +52,7 @@ module.exports = function usersAdminRoutes(csrfProtection) {
const id = Number(req.params.id); const id = Number(req.params.id);
if (id === req.session.user.id) { if (id === req.session.user.id) {
req.flash('error', 'You cannot delete your own account.'); req.flash('error', 'You cannot delete your own account.');
return res.redirect('/admin/users'); return res.redirect('/mymusic/users');
} }
const { songs, playlists } = users.deleteUserWithContent(id); const { songs, playlists } = users.deleteUserWithContent(id);
for (const s of songs) { for (const s of songs) {
@ -63,7 +63,7 @@ module.exports = function usersAdminRoutes(csrfProtection) {
if (p.cover_path) unlinkQuiet(path.join(mediaDir, p.cover_path)); if (p.cover_path) unlinkQuiet(path.join(mediaDir, p.cover_path));
} }
req.flash('success', 'User and all their content deleted.'); req.flash('success', 'User and all their content deleted.');
res.redirect('/admin/users'); res.redirect('/mymusic/users');
} catch (err) { next(err); } } catch (err) { next(err); }
}); });

View File

@ -1,7 +1,7 @@
<h1><%= playlist ? 'Edit playlist' : 'New playlist' %></h1> <h1><%= playlist ? 'Edit playlist' : 'New playlist' %></h1>
<form method="post" <form method="post"
action="<%= playlist ? '/admin/playlists/' + playlist.id : '/admin/playlists' %>" action="<%= playlist ? '/mymusic/playlists/' + playlist.id : '/mymusic/playlists' %>"
enctype="multipart/form-data" class="stacked form-card"> enctype="multipart/form-data" class="stacked form-card">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<label> <label>
@ -35,7 +35,7 @@
</fieldset> </fieldset>
<div class="row"> <div class="row">
<button type="submit" class="btn-primary"><%= playlist ? 'Save' : 'Create' %></button> <button type="submit" class="btn-primary"><%= playlist ? 'Save' : 'Create' %></button>
<a href="/admin/playlists" class="btn-link">Cancel</a> <a href="/mymusic/playlists" class="btn-link">Cancel</a>
</div> </div>
</form> </form>
@ -56,7 +56,7 @@
</svg> </svg>
</span> </span>
<span class="track-label"><%= t.title %> — <span class="muted small"><%= t.artist %></span></span> <span class="track-label"><%= t.title %> — <span class="muted small"><%= t.artist %></span></span>
<form method="post" action="/admin/playlists/<%= playlist.id %>/remove-track" class="inline-form"> <form method="post" action="/mymusic/playlists/<%= playlist.id %>/remove-track" class="inline-form">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input type="hidden" name="song_id" value="<%= t.id %>"> <input type="hidden" name="song_id" value="<%= t.id %>">
<button type="submit" class="linklike danger">Remove</button> <button type="submit" class="linklike danger">Remove</button>
@ -64,13 +64,13 @@
</li> </li>
<% }) %> <% }) %>
</ol> </ol>
<form id="reorder-form" method="post" action="/admin/playlists/<%= playlist.id %>/reorder" style="display:none"> <form id="reorder-form" method="post" action="/mymusic/playlists/<%= playlist.id %>/reorder" style="display:none">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<input type="hidden" name="order" id="reorder-order"> <input type="hidden" name="order" id="reorder-order">
</form> </form>
<% } %> <% } %>
<form method="post" action="/admin/playlists/<%= playlist.id %>/add-track" class="inline-row"> <form method="post" action="/mymusic/playlists/<%= playlist.id %>/add-track" class="inline-row">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<select name="song_id" required> <select name="song_id" required>
<option value="">— Add a song —</option> <option value="">— Add a song —</option>

View File

@ -1,11 +1,11 @@
<div class="row-between"> <div class="row-between">
<h1>My Music · Playlists</h1> <h1>My Music · Playlists</h1>
<a href="/admin/playlists/new" class="btn-primary">New playlist</a> <a href="/mymusic/playlists/new" class="btn-primary">New playlist</a>
</div> </div>
<nav class="tabs"> <nav class="tabs">
<a href="/admin/songs">Songs</a> <a href="/mymusic/songs">Songs</a>
<a href="/admin/playlists" class="active">Playlists</a> <a href="/mymusic/playlists" class="active">Playlists</a>
</nav> </nav>
<% if (playlists.length === 0) { %> <% if (playlists.length === 0) { %>
@ -28,8 +28,8 @@
</td> </td>
<td class="muted small"><%= p.createdAt %></td> <td class="muted small"><%= p.createdAt %></td>
<td class="actions"> <td class="actions">
<a href="/admin/playlists/<%= p.id %>/edit">Edit</a> <a href="/mymusic/playlists/<%= p.id %>/edit">Edit</a>
<form method="post" action="/admin/playlists/<%= p.id %>/delete" class="inline-form" onsubmit="return confirm('Delete this playlist?');"> <form method="post" action="/mymusic/playlists/<%= p.id %>/delete" class="inline-form" onsubmit="return confirm('Delete this playlist?');">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="linklike danger">Delete</button> <button type="submit" class="linklike danger">Delete</button>
</form> </form>

View File

@ -5,8 +5,8 @@
<% } %> <% } %>
<form method="post" <form method="post"
action="<%= song ? '/admin/songs/' + song.id : '/admin/songs' %>" action="<%= song ? '/mymusic/songs/' + song.id : '/mymusic/songs' %>"
<% if (!song) { %>enctype="multipart/form-data"<% } %> enctype="multipart/form-data"
class="stacked form-card"> class="stacked form-card">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
@ -46,7 +46,15 @@
<input type="file" name="cover" accept="image/*"> <input type="file" name="cover" accept="image/*">
</label> </label>
<% } else { %> <% } else { %>
<p class="muted small">Audio and cover can't be replaced from this form yet — delete and re-upload to change them.</p> <label>
Replace cover art <span class="muted small">(optional — leave blank to keep current)</span>
<% if (song.coverPath) { %>
<div class="current-cover-wrap">
<img src="/media/<%= song.coverPath %>" alt="Current cover" class="current-cover-thumb">
</div>
<% } %>
<input type="file" name="cover" accept="image/*">
</label>
<% } %> <% } %>
<fieldset class="visibility-field"> <fieldset class="visibility-field">
@ -67,6 +75,6 @@
<div class="row"> <div class="row">
<button type="submit" class="btn-primary"><%= song ? 'Save' : 'Upload' %></button> <button type="submit" class="btn-primary"><%= song ? 'Save' : 'Upload' %></button>
<a href="/admin/songs" class="btn-link">Cancel</a> <a href="/mymusic/songs" class="btn-link">Cancel</a>
</div> </div>
</form> </form>

View File

@ -1,11 +1,11 @@
<div class="row-between"> <div class="row-between">
<h1>My Music · Songs</h1> <h1>My Music · Songs</h1>
<a href="/admin/songs/new" class="btn-primary">Upload song</a> <a href="/mymusic/songs/new" class="btn-primary">Upload song</a>
</div> </div>
<nav class="tabs"> <nav class="tabs">
<a href="/admin/songs" class="active">Songs</a> <a href="/mymusic/songs" class="active">Songs</a>
<a href="/admin/playlists">Playlists</a> <a href="/mymusic/playlists">Playlists</a>
</nav> </nav>
<% if (songs.length === 0) { %> <% if (songs.length === 0) { %>
@ -29,8 +29,8 @@
<% } %> <% } %>
</td> </td>
<td class="actions"> <td class="actions">
<a href="/admin/songs/<%= s.id %>/edit">Edit</a> <a href="/mymusic/songs/<%= s.id %>/edit">Edit</a>
<form method="post" action="/admin/songs/<%= s.id %>/delete" class="inline-form" onsubmit="return confirm('Delete this song?');"> <form method="post" action="/mymusic/songs/<%= s.id %>/delete" class="inline-form" onsubmit="return confirm('Delete this song?');">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="linklike danger">Delete</button> <button type="submit" class="linklike danger">Delete</button>
</form> </form>

View File

@ -20,24 +20,24 @@
<td><%= u.disabled ? 'Disabled' : 'Active' %></td> <td><%= u.disabled ? 'Disabled' : 'Active' %></td>
<td class="actions"> <td class="actions">
<% if (!u.emailVerified) { %> <% if (!u.emailVerified) { %>
<form method="post" action="/admin/users/<%= u.id %>/verify" class="inline-form"> <form method="post" action="/mymusic/users/<%= u.id %>/verify" class="inline-form">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="btn-small">Verify</button> <button type="submit" class="btn-small">Verify</button>
</form> </form>
<% } %> <% } %>
<% if (u.id !== user.id) { %> <% if (u.id !== user.id) { %>
<% if (u.disabled) { %> <% if (u.disabled) { %>
<form method="post" action="/admin/users/<%= u.id %>/enable" class="inline-form"> <form method="post" action="/mymusic/users/<%= u.id %>/enable" class="inline-form">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="btn-small">Enable</button> <button type="submit" class="btn-small">Enable</button>
</form> </form>
<% } else { %> <% } else { %>
<form method="post" action="/admin/users/<%= u.id %>/disable" class="inline-form"> <form method="post" action="/mymusic/users/<%= u.id %>/disable" class="inline-form">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="btn-small">Disable</button> <button type="submit" class="btn-small">Disable</button>
</form> </form>
<% } %> <% } %>
<form method="post" action="/admin/users/<%= u.id %>/delete" class="inline-form" <form method="post" action="/mymusic/users/<%= u.id %>/delete" class="inline-form"
onsubmit="return confirm('Delete <%= u.displayName.replace(/'/g, "\\'") %> and ALL their content? This cannot be undone.')"> onsubmit="return confirm('Delete <%= u.displayName.replace(/'/g, "\\'") %> and ALL their content? This cannot be undone.')">
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit" class="btn-small danger">Delete</button> <button type="submit" class="btn-small danger">Delete</button>

View File

@ -6,9 +6,9 @@
<a href="/playlists" class="<%= currentPath && currentPath.startsWith('/playlists') ? 'active' : '' %>">Playlists</a> <a href="/playlists" class="<%= currentPath && currentPath.startsWith('/playlists') ? 'active' : '' %>">Playlists</a>
<a href="/profiles" class="<%= currentPath && currentPath.startsWith('/profiles') ? 'active' : '' %>">Profiles</a> <a href="/profiles" class="<%= currentPath && currentPath.startsWith('/profiles') ? 'active' : '' %>">Profiles</a>
<% if (user) { %> <% if (user) { %>
<a href="/admin/songs" class="<%= currentPath && currentPath.startsWith('/admin') && !currentPath.startsWith('/admin/users') ? 'active' : '' %>">My Music</a> <a href="/mymusic/songs" class="<%= currentPath && currentPath.startsWith('/mymusic') && !currentPath.startsWith('/mymusic/users') ? 'active' : '' %>">My Music</a>
<% if (user.role === 'admin') { %> <% if (user.role === 'admin') { %>
<a href="/admin/users" class="<%= currentPath && currentPath.startsWith('/admin/users') ? 'active' : '' %>">Users</a> <a href="/mymusic/users" class="<%= currentPath && currentPath.startsWith('/mymusic/users') ? 'active' : '' %>">Users</a>
<% } %> <% } %>
<a href="/account">Account</a> <a href="/account">Account</a>
<form method="post" action="/logout" class="inline-form"> <form method="post" action="/logout" class="inline-form">

View File

@ -49,7 +49,7 @@
<section> <section>
<h2>Recently added <a href="/songs" class="muted small" style="font-weight:normal">(show all)</a></h2> <h2>Recently added <a href="/songs" class="muted small" style="font-weight:normal">(show all)</a></h2>
<% if (recent.length === 0) { %> <% if (recent.length === 0) { %>
<p class="muted">No songs yet. <% if (user && user.role === 'admin') { %><a href="/admin/songs/new">Upload the first one →</a><% } %></p> <p class="muted">No songs yet. <% if (user && user.role === 'admin') { %><a href="/mymusic/songs/new">Upload the first one →</a><% } %></p>
<% } else { %> <% } else { %>
<%- include('../partials/song_list', { songs: recent }) %> <%- include('../partials/song_list', { songs: recent }) %>
<% } %> <% } %>