Add likes, follows and playlist visibility; redesign web-player UI

Backend:
- New Likes feature (toggle/get liked tracks) and endpoints
- User follow/unfollow, listening stats, public user profile
- Playlist visibility (public/private) support
- Various auth and DTO/response adjustments to support the above

Frontend (web-player):
- New design system pass: theme tokens/mode (light/dark), glass menu,
  tabs bar, player dock/extras/queue components, mix tiles, artist link
- New routes: Liked, public user profile (/u/[id]), likes API proxy
- Search refactor: shared SearchBox component used by TopBar and
  MobileHeader (search now reachable on mobile), live results as you
  type instead of requiring Enter, dropped the "Buscando…" quick-result
  preview
- Removed now-unused components (MobileNav, CoverBadge, MixBentoTile,
  PlayPauseOverlay, SuggestionsTable, userDirectory)
- Design handoff reference docs added under web-player/design_handoff_musify_player
  and web-player/docs/design-system-refactor-plan.md
This commit is contained in:
2026-09-14 23:42:45 +02:00
parent 389b3b9244
commit dc1ba89a6d
166 changed files with 14271 additions and 1982 deletions
+63 -1
View File
@@ -1,8 +1,44 @@
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
import { getAllowAnonymousListening } from '$lib/server/playbackConfig';
import { createApiClient } from '$lib/server/api';
import { authConfig } from '$lib/server/config';
import { LIKED_TRACKS_PAGE_SIZE } from '$lib/config';
const PUBLIC_PATHS = new Set(['/login']);
const SIDEBAR_PLAYLISTS_LIMIT = 8;
async function fetchSidebarPlaylists(
fetchFn: typeof fetch,
accessToken: string | null,
userId: string
) {
try {
const api = createApiClient({ fetch: fetchFn, accessToken: accessToken ?? undefined });
const { data } = await api.GET('/playlists/users/{userId}', {
params: {
path: { userId },
query: { pageNumber: 1, pageSize: SIDEBAR_PLAYLISTS_LIMIT }
}
});
return data?.items ?? [];
} catch {
// The sidebar shell must render even when the API is unreachable.
return [];
}
}
async function fetchLikedTracks(fetchFn: typeof fetch, accessToken: string | null) {
try {
const api = createApiClient({ fetch: fetchFn, accessToken: accessToken ?? undefined });
const { data } = await api.GET('/likes', {
params: { query: { pageNumber: 1, pageSize: LIKED_TRACKS_PAGE_SIZE } }
});
return data?.items ?? [];
} catch {
return [];
}
}
export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
const allowAnonymousListening = await getAllowAnonymousListening(fetch);
@@ -10,5 +46,31 @@ export const load: LayoutServerLoad = async ({ locals, url, fetch }) => {
if (!locals.user && !allowAnonymousListening && !PUBLIC_PATHS.has(url.pathname)) {
redirect(302, `/login?returnTo=${encodeURIComponent(url.pathname + url.search)}`);
}
return { user: locals.user, allowAnonymousListening };
const accountUrl = authConfig.issuer
? `${authConfig.issuer.replace(/\/+$/, '')}/ui/console`
: null;
if (!locals.user) {
return {
user: locals.user,
allowAnonymousListening,
sidebarPlaylists: [],
likedTracks: [],
accountUrl
};
}
const [sidebarPlaylists, likedTracks] = await Promise.all([
fetchSidebarPlaylists(fetch, locals.accessToken, locals.user.sub),
fetchLikedTracks(fetch, locals.accessToken)
]);
return {
user: locals.user,
allowAnonymousListening,
sidebarPlaylists,
likedTracks,
accountUrl
};
};