Render unknown URLs inside the app shell and move ListeningStats to the domain types
Add catch-all routes so unknown pages 404 inside the (app) layout and unknown /api URLs return a JSON 404 instead of redirecting to the login page. Restore the full root error page.
This commit is contained in:
@@ -33,7 +33,7 @@ npm run lint:tokens # scripts/check-tokens.mjs, the design system guardrail
|
||||
|
||||
## Folder structure
|
||||
|
||||
- `src/lib/types.ts` holds the domain types (`Track`, `Album`, `Playlist`, `Mix`, `Paged<T>`). Pages and the player only ever see these, never raw API DTOs.
|
||||
- `src/lib/types.ts` holds the domain types (`Track`, `Album`, `Playlist`, `Mix`, `ListeningStats`, `Paged<T>`). Pages and the player only ever see these, never raw API DTOs.
|
||||
- `src/lib/player/` is the player: `player.svelte.ts` (state and orchestration), `queue.ts` (pure queue operations), `stream.ts`, `mediaSession.ts`, `progressClock.ts`, `listenTracker.ts`, `recentlyPlayed.ts` (merges `player.recentlyPlayed` with the server history), and `actions.ts` (`playAllOrToggle`, `playShuffled`, `isQueueCurrent`).
|
||||
- `src/lib/state/` holds reactive rune state: `panels.svelte.ts` (queue panel, create playlist modal), `history.svelte.ts` (previous page for `BackLink`), `menu.svelte.ts` (`createMenu<T>()`), `pagedList.svelte.ts` (`createPagedList`), `dialog.svelte.ts`, `liked.svelte.ts` (the user's liked tracks), `toggle.svelte.ts` (`createToggle`), and `scroll.ts` (`restoreScroll`). Nothing in `state/` imports from `components/`.
|
||||
- `src/lib/theme/` holds the theme: `accent.svelte.ts` (accent hue per track and the hue fade), `mode.svelte.ts`, `palette.ts`, `tokens.ts`, and `color.ts` (`ACCENT_HUE`). The player never imports from `theme/`: `(app)/+layout.svelte` follows `player.currentId` and calls `accent.follow`.
|
||||
@@ -51,7 +51,7 @@ npm run lint:tokens # scripts/check-tokens.mjs, the design system guardrail
|
||||
- `media/` for stateless music display components that receive props (Artwork, MediaCard, ListRow, PlayButton, and so on).
|
||||
- `forms/` for composite entity forms (AlbumForm, PlaylistForm, CoverForm, ImageDropzone).
|
||||
- `layout/` for page level scaffolding (Page, PageHeader, SectionHeading).
|
||||
- The root `+layout.svelte` only holds the globals (fonts, theme, `DialogHost`). Everything the user sees after signing in lives under `src/routes/(app)/`, including Home (`(app)/+page.svelte`); `(app)/+layout.server.ts` guards the group and loads the shell data, and `(app)/+error.svelte` renders inside the shell. Auth routes live under `src/routes/auth/`: `+page.svelte` is the login screen, and `login/`, `logout/`, `callback/` are the OIDC endpoints. The public user profile route is `src/routes/(app)/user/[id]/`, and its followers/following lists share one route, `user/[id]/[list=followList]/` (matcher in `src/params/followList.ts`).
|
||||
- The root `+layout.svelte` only holds the globals (fonts, theme, `DialogHost`). Everything the user sees after signing in lives under `src/routes/(app)/`, including Home (`(app)/+page.svelte`); `(app)/+layout.server.ts` guards the group and loads the shell data, and `(app)/+error.svelte` renders inside the shell. Unknown URLs hit the catch-all `(app)/[...rest]/` (it throws a 404) so they also get the shell (unknown `/api/...` URLs hit `api/[...rest]/`, a JSON 404); the root `+error.svelte` only covers errors outside the group or in the group layout load. Auth routes live under `src/routes/auth/`: `+page.svelte` is the login screen, and `login/`, `logout/`, `callback/` are the OIDC endpoints. The public user profile route is `src/routes/(app)/user/[id]/`, and its followers/following lists share one route, `user/[id]/[list=followList]/` (matcher in `src/params/followList.ts`).
|
||||
|
||||
## Naming conventions and glossary
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { components } from '$lib/api/schema';
|
||||
import type { Album, LikedTrack, Mix, Paged, Playlist, Track } from '$lib/types';
|
||||
import type { Album, LikedTrack, ListeningStats, Mix, Paged, Playlist, Track } from '$lib/types';
|
||||
|
||||
type TrackDto = components['schemas']['TrackApplicationResponse'];
|
||||
type AlbumDto = components['schemas']['AlbumApplicationResponse'];
|
||||
@@ -84,12 +84,6 @@ export function toPlaylist(dto: PlaylistDto): Playlist {
|
||||
|
||||
type ListeningStatsDto = components['schemas']['ListeningStatsResponse'];
|
||||
|
||||
export interface ListeningStats {
|
||||
tracksThisWeek: number;
|
||||
secondsThisWeek: number;
|
||||
streakDays: number;
|
||||
}
|
||||
|
||||
export function toCount(value: number | string | null | undefined): number {
|
||||
return Number(value ?? 0) || 0;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,12 @@ export interface Mix {
|
||||
tracks: Track[];
|
||||
}
|
||||
|
||||
export interface ListeningStats {
|
||||
tracksThisWeek: number;
|
||||
secondsThisWeek: number;
|
||||
streakDays: number;
|
||||
}
|
||||
|
||||
export interface Paged<T> {
|
||||
items: T[];
|
||||
pageNumber: number;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = () => {
|
||||
error(404, 'Página no encontrada');
|
||||
};
|
||||
@@ -1,12 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import Button from '$lib/components/ui/primitives/Button.svelte';
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-bg text-fg antialiased">
|
||||
<div class="mx-auto my-[14vh] max-w-prose-sm text-center">
|
||||
<div class="min-h-screen bg-bg px-6 text-fg antialiased">
|
||||
<div class="animate-enter mx-auto my-[14vh] max-w-prose-sm text-center">
|
||||
<h1 class="mb-2.5 text-display-3">
|
||||
{page.status === 404 ? 'Página no encontrada' : 'Algo salió mal'}
|
||||
</h1>
|
||||
<a href="/" class="text-body text-fg-2 underline">Volver al inicio</a>
|
||||
<p class="mb-6.5 text-body text-fg-2">
|
||||
{page.status === 404
|
||||
? 'Comprueba el enlace o vuelve al inicio.'
|
||||
: 'No se pudo cargar la página. Inténtalo de nuevo en unos momentos.'}
|
||||
</p>
|
||||
<Button href="/" variant="secondary">Volver al inicio</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const fallback: RequestHandler = () => json({ message: 'No encontrado.' }, { status: 404 });
|
||||
Reference in New Issue
Block a user