From b545cf5ec63cc68b9a3c04d8e49131bc1911c15f Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Fri, 17 Oct 2025 01:44:34 +0100 Subject: [PATCH 01/11] UI groundwork --- app/src/lib/components/icons/Cassette.svelte | 22 +++++++ app/src/lib/components/icons/Heart.svelte | 2 + app/src/lib/components/icons/ThreeDots.svelte | 12 ++++ app/src/lib/components/icons/Vinyl.svelte | 20 ++++++ .../releases/TrackLikeButton.svelte | 7 +- .../components/releases/TracksTable.svelte | 1 + .../components/releases/TracksTableRow.svelte | 4 ++ app/src/routes/+layout.svelte | 65 ++++++++++++++++--- .../routes/listener/collections/+page.svelte | 7 ++ .../liked-tracks}/+page.server.ts | 0 .../liked-tracks}/+page.svelte | 0 app/src/routes/listener/mixtapes/+page.svelte | 7 ++ shared/styles/global.css | 5 -- 13 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 app/src/lib/components/icons/Cassette.svelte create mode 100644 app/src/lib/components/icons/ThreeDots.svelte create mode 100644 app/src/lib/components/icons/Vinyl.svelte create mode 100644 app/src/routes/listener/collections/+page.svelte rename app/src/routes/{likes => listener/liked-tracks}/+page.server.ts (100%) rename app/src/routes/{likes => listener/liked-tracks}/+page.svelte (100%) create mode 100644 app/src/routes/listener/mixtapes/+page.svelte diff --git a/app/src/lib/components/icons/Cassette.svelte b/app/src/lib/components/icons/Cassette.svelte new file mode 100644 index 0000000..f8f7370 --- /dev/null +++ b/app/src/lib/components/icons/Cassette.svelte @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/lib/components/icons/Heart.svelte b/app/src/lib/components/icons/Heart.svelte index 851d0c2..e77e09b 100644 --- a/app/src/lib/components/icons/Heart.svelte +++ b/app/src/lib/components/icons/Heart.svelte @@ -20,5 +20,7 @@ svg { width: 1em; height: 1em; + display: inline-block; + vertical-align: middle; } diff --git a/app/src/lib/components/icons/ThreeDots.svelte b/app/src/lib/components/icons/ThreeDots.svelte new file mode 100644 index 0000000..d3cf276 --- /dev/null +++ b/app/src/lib/components/icons/ThreeDots.svelte @@ -0,0 +1,12 @@ + + + diff --git a/app/src/lib/components/icons/Vinyl.svelte b/app/src/lib/components/icons/Vinyl.svelte new file mode 100644 index 0000000..92bdeb8 --- /dev/null +++ b/app/src/lib/components/icons/Vinyl.svelte @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/lib/components/releases/TrackLikeButton.svelte b/app/src/lib/components/releases/TrackLikeButton.svelte index 7332562..caff254 100644 --- a/app/src/lib/components/releases/TrackLikeButton.svelte +++ b/app/src/lib/components/releases/TrackLikeButton.svelte @@ -21,7 +21,11 @@ }; -
+ Duration + diff --git a/app/src/lib/components/releases/TracksTableRow.svelte b/app/src/lib/components/releases/TracksTableRow.svelte index 819ca72..79a9159 100644 --- a/app/src/lib/components/releases/TracksTableRow.svelte +++ b/app/src/lib/components/releases/TracksTableRow.svelte @@ -9,6 +9,7 @@ import type { Session } from '@supabase/supabase-js'; import ReleaseTrackButton from './ReleaseTrackButton.svelte'; import TrackLikeButton from './TrackLikeButton.svelte'; + import ThreeDots from '../icons/ThreeDots.svelte'; const { i = undefined, @@ -43,6 +44,9 @@ + + + diff --git a/app/src/routes/listener/collections/+page.svelte b/app/src/routes/listener/collections/+page.svelte new file mode 100644 index 0000000..4ab2c19 --- /dev/null +++ b/app/src/routes/listener/collections/+page.svelte @@ -0,0 +1,7 @@ +

Your collections

+ +
Pending
+ + + + diff --git a/app/src/routes/likes/+page.server.ts b/app/src/routes/listener/liked-tracks/+page.server.ts similarity index 100% rename from app/src/routes/likes/+page.server.ts rename to app/src/routes/listener/liked-tracks/+page.server.ts diff --git a/app/src/routes/likes/+page.svelte b/app/src/routes/listener/liked-tracks/+page.svelte similarity index 100% rename from app/src/routes/likes/+page.svelte rename to app/src/routes/listener/liked-tracks/+page.svelte diff --git a/app/src/routes/listener/mixtapes/+page.svelte b/app/src/routes/listener/mixtapes/+page.svelte new file mode 100644 index 0000000..4e48d12 --- /dev/null +++ b/app/src/routes/listener/mixtapes/+page.svelte @@ -0,0 +1,7 @@ +

Your mixtapes

+ +
Pending
+ + + + diff --git a/shared/styles/global.css b/shared/styles/global.css index bbe4a16..ff28e99 100644 --- a/shared/styles/global.css +++ b/shared/styles/global.css @@ -62,11 +62,6 @@ p a { font-weight: 500; } -svg { - width: 100%; - height: auto; -} - h1, h2, h3, From d936d8ae2043badc2a6acd295f5ef37d24a62fda Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Fri, 17 Oct 2025 10:41:15 +0100 Subject: [PATCH 02/11] Change to /me/ routing --- app/src/lib/components/releases/TrackLikeButton.svelte | 2 +- app/src/routes/+layout.svelte | 6 +++--- app/src/routes/{listener => me}/collections/+page.svelte | 0 .../routes/{listener => me}/liked-tracks/+page.server.ts | 0 app/src/routes/{listener => me}/liked-tracks/+page.svelte | 0 app/src/routes/{listener => me}/mixtapes/+page.svelte | 0 6 files changed, 4 insertions(+), 4 deletions(-) rename app/src/routes/{listener => me}/collections/+page.svelte (100%) rename app/src/routes/{listener => me}/liked-tracks/+page.server.ts (100%) rename app/src/routes/{listener => me}/liked-tracks/+page.svelte (100%) rename app/src/routes/{listener => me}/mixtapes/+page.svelte (100%) diff --git a/app/src/lib/components/releases/TrackLikeButton.svelte b/app/src/lib/components/releases/TrackLikeButton.svelte index caff254..f626c41 100644 --- a/app/src/lib/components/releases/TrackLikeButton.svelte +++ b/app/src/lib/components/releases/TrackLikeButton.svelte @@ -23,7 +23,7 @@ diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte index 8e0450a..f430b66 100644 --- a/app/src/routes/+layout.svelte +++ b/app/src/routes/+layout.svelte @@ -39,9 +39,9 @@ { section: 'Your Library', links: [ - { href: '/listener/collections', label: 'Collections' }, - { href: '/listener/mixtapes', label: 'Mixtapes' }, - { href: '/listener/liked-tracks', label: 'Liked tracks' } + { href: '/me/collections', label: 'Collections' }, + { href: '/me/mixtapes', label: 'Mixtapes' }, + { href: '/me/liked-tracks', label: 'Liked tracks' } ] }, { diff --git a/app/src/routes/listener/collections/+page.svelte b/app/src/routes/me/collections/+page.svelte similarity index 100% rename from app/src/routes/listener/collections/+page.svelte rename to app/src/routes/me/collections/+page.svelte diff --git a/app/src/routes/listener/liked-tracks/+page.server.ts b/app/src/routes/me/liked-tracks/+page.server.ts similarity index 100% rename from app/src/routes/listener/liked-tracks/+page.server.ts rename to app/src/routes/me/liked-tracks/+page.server.ts diff --git a/app/src/routes/listener/liked-tracks/+page.svelte b/app/src/routes/me/liked-tracks/+page.svelte similarity index 100% rename from app/src/routes/listener/liked-tracks/+page.svelte rename to app/src/routes/me/liked-tracks/+page.svelte diff --git a/app/src/routes/listener/mixtapes/+page.svelte b/app/src/routes/me/mixtapes/+page.svelte similarity index 100% rename from app/src/routes/listener/mixtapes/+page.svelte rename to app/src/routes/me/mixtapes/+page.svelte From 9a725fef5becd18113f1b9a3cb24554186816ffc Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Wed, 22 Oct 2025 11:34:23 +0100 Subject: [PATCH 03/11] Collections just about working --- api/src/routes/collections/[slug]/+server.ts | 38 ++++++++ .../users/[slug]/collections/+server.ts | 36 ++++++++ app/src/lib/components/icons/Albums.svelte | 20 +++++ .../components/layout/ButtonWrapper.svelte | 2 +- app/src/routes/+layout.server.ts | 5 +- app/src/routes/+layout.ts | 1 + app/src/routes/me/collections/+page.server.ts | 41 +++++++++ app/src/routes/me/collections/+page.svelte | 51 ++++++++++- app/src/routes/releases/[slug]/+page.svelte | 89 +++++++++++++++++-- shared/config/index.ts | 3 + shared/types/index.ts | 18 ++-- 11 files changed, 289 insertions(+), 15 deletions(-) create mode 100644 api/src/routes/collections/[slug]/+server.ts create mode 100644 api/src/routes/users/[slug]/collections/+server.ts create mode 100644 app/src/lib/components/icons/Albums.svelte create mode 100644 app/src/routes/me/collections/+page.server.ts diff --git a/api/src/routes/collections/[slug]/+server.ts b/api/src/routes/collections/[slug]/+server.ts new file mode 100644 index 0000000..eda089c --- /dev/null +++ b/api/src/routes/collections/[slug]/+server.ts @@ -0,0 +1,38 @@ +import { supabase } from '$lib/server/supabase'; +import { json } from '@sveltejs/kit'; +import { TABLES } from '../../../../../shared/config'; + +export async function PATCH({ request, params }) { + const collectionId = params.slug; + const { releaseId, add } = await request.json(); + + if (!releaseId) { + return json({ error: 'Missing release ID' }, { status: 400 }); + } + + if (!add) { + const { error } = await supabase + .from(TABLES.collectionReleases) + .delete() + .eq('collection_id', collectionId) + .eq('release_id', releaseId); + + if (error) { + console.error('Error removing release from collection:', error); + return json({ error: 'Failed to remove release from collection' }, { status: 500 }); + } + + return json({ success: true }); + } + + const { error } = await supabase + .from(TABLES.collectionReleases) + .insert({ collection_id: collectionId, release_id: releaseId }); + + if (error) { + console.error('Error adding release to collection:', error); + return json({ error: 'Failed to add release to collection' }, { status: 500 }); + } + + return json({ success: true }); +} diff --git a/api/src/routes/users/[slug]/collections/+server.ts b/api/src/routes/users/[slug]/collections/+server.ts new file mode 100644 index 0000000..04674b9 --- /dev/null +++ b/api/src/routes/users/[slug]/collections/+server.ts @@ -0,0 +1,36 @@ +import { supabase } from '$lib/server/supabase'; +import { json } from '@sveltejs/kit'; +import { TABLES } from '../../../../../../shared/config'; + +export async function GET({ params }) { + const userId = params.slug; + const { data: collections, error } = await supabase + .from(TABLES.collectionsHydrated) + .select('*') + .eq('user_id', userId); + + if (error) { + console.error('Error fetching collections:', error); + return json({ error: 'Failed to fetch collections' }, { status: 500 }); + } + + return json(collections); +} + +export async function POST({ params, request }) { + const userId = params.slug; + + const { name } = await request.json(); + + if (!name) { + return json({ error: 'Missing collection name' }, { status: 400 }); + } + + const { error } = await supabase.from(TABLES.collections).insert({ user_id: userId, name }); + + if (error) { + return json({ error: 'Failed to create collection' }, { status: 500 }); + } + + return json({ success: true }); +} diff --git a/app/src/lib/components/icons/Albums.svelte b/app/src/lib/components/icons/Albums.svelte new file mode 100644 index 0000000..29022db --- /dev/null +++ b/app/src/lib/components/icons/Albums.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/app/src/lib/components/layout/ButtonWrapper.svelte b/app/src/lib/components/layout/ButtonWrapper.svelte index 2932b0b..2822d38 100644 --- a/app/src/lib/components/layout/ButtonWrapper.svelte +++ b/app/src/lib/components/layout/ButtonWrapper.svelte @@ -5,7 +5,7 @@
onClickFunction()} onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { onClickFunction; diff --git a/app/src/routes/+layout.server.ts b/app/src/routes/+layout.server.ts index 1dc7416..da98032 100644 --- a/app/src/routes/+layout.server.ts +++ b/app/src/routes/+layout.server.ts @@ -1,11 +1,12 @@ import type { LayoutServerLoad } from './$types'; -import type { LikedTrackObject, UserProfile } from '../../../shared/types'; +import type { Collection, LikedTrackObject, UserProfile } from '../../../shared/types'; import { API_BASE } from '$lib/global/config'; export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies, fetch }) => { const { session, user } = await safeGetSession(); let profileData: UserProfile | null = null; + let collections: Collection[] = []; let likedTracks: LikedTrackObject[] = []; let followedIDs: string[] = []; @@ -13,6 +14,7 @@ export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cooki if (session && userId) { profileData = await fetch(`${API_BASE}/users/${userId}`).then((res) => res.json()); + collections = await fetch(`${API_BASE}/users/${userId}/collections`).then((res) => res.json()); likedTracks = await fetch(`${API_BASE}/users/${userId}/likes`).then((res) => res.json()); followedIDs = await fetch(`${API_BASE}/users/${userId}/following`).then((res) => res.json()); } @@ -21,6 +23,7 @@ export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cooki session, user, profileData, + collections, likedTracks, followedArtists: followedIDs, cookies: cookies.getAll() diff --git a/app/src/routes/+layout.ts b/app/src/routes/+layout.ts index 6a03a0d..5bb6a31 100644 --- a/app/src/routes/+layout.ts +++ b/app/src/routes/+layout.ts @@ -46,6 +46,7 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { session, user, profileData: data.profileData, + collections: data.collections, followedArtists: data.followedArtists, likedTracks: data.likedTracks }; diff --git a/app/src/routes/me/collections/+page.server.ts b/app/src/routes/me/collections/+page.server.ts new file mode 100644 index 0000000..c542d31 --- /dev/null +++ b/app/src/routes/me/collections/+page.server.ts @@ -0,0 +1,41 @@ +import { API_BASE } from '$lib/global/config'; +import type { Actions } from '@sveltejs/kit'; + +export const actions: Actions = { + createCollection: async ({ request, fetch, locals: { safeGetSession } }) => { + const { session } = await safeGetSession(); + if (session) { + const formData = await request.formData(); + const collectionName = formData.get('collectionName'); + if (collectionName) { + console.log('Creating collection with name:', collectionName); + await fetch(`${API_BASE}/users/${session.user.id}/collections`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ name: collectionName }) + }); + console.log(`Created new collection: ${collectionName}`); + } + } + }, + addOrRemoveRelease: async ({ request, fetch, locals: { safeGetSession } }) => { + const { session } = await safeGetSession(); + if (session) { + const formData = await request.formData(); + const collectionId = formData.get('collectionId'); + const releaseId = formData.get('releaseId'); + const add = formData.get('add') === 'true'; + if (collectionId && releaseId) { + await fetch(`${API_BASE}/collections/${collectionId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ releaseId, collectionId, add }) + }); + } + } + } +}; diff --git a/app/src/routes/me/collections/+page.svelte b/app/src/routes/me/collections/+page.svelte index 4ab2c19..c46570b 100644 --- a/app/src/routes/me/collections/+page.svelte +++ b/app/src/routes/me/collections/+page.svelte @@ -1,7 +1,52 @@ + + + + Your Collections · Soli + + +

Your collections

-
Pending
+{#each data.collections as collection} +
+
+

{collection.name}

+ {#if collection.description} +
{collection.description}
+ {/if} +
+ +
+{/each} + +

Create a new collection

- + + + + + - + diff --git a/app/src/routes/releases/[slug]/+page.svelte b/app/src/routes/releases/[slug]/+page.svelte index c5d5bb3..614f9a2 100644 --- a/app/src/routes/releases/[slug]/+page.svelte +++ b/app/src/routes/releases/[slug]/+page.svelte @@ -1,5 +1,10 @@ @@ -35,14 +54,55 @@
-
-

{release.title}

- +
- {release.artist_name} +

{release.title}

+ +
+ (collectionMenuOpen = !collectionMenuOpen)}> + +
+ {#if collectionMenuOpen} +
+

Add {release.title} to collection{data.collections?.length > 1 ? 's' : ''}

+ {#each data.collections as collection} +
+ + + r.id === release.id) ? 'false' : 'true'} + /> + + +
+ {/each} + +
+ {/if} + {`Cover diff --git a/shared/config/index.ts b/shared/config/index.ts index 389b552..bba432b 100644 --- a/shared/config/index.ts +++ b/shared/config/index.ts @@ -24,4 +24,7 @@ export const TABLES = { betaUsers: "beta-users", likedTracks: "liked_tracks", followedArtists: "followed_artists", + collections: "collections", + collectionReleases: "collection_releases", + collectionsHydrated: "collections_hydrated", }; diff --git a/shared/types/index.ts b/shared/types/index.ts index ed343cf..c059119 100644 --- a/shared/types/index.ts +++ b/shared/types/index.ts @@ -68,8 +68,16 @@ export interface LikedTrackObject { } export interface SearchResult { - type: 'artist' | 'release' | 'track'; - id: string; - name: string; - image_cid?: string; - } \ No newline at end of file + type: "artist" | "release" | "track"; + id: string; + name: string; + image_cid?: string; +} + +export interface Collection { + id: string; + name: string; + description?: string; + created_at: string; + releases: ReleaseHydrated[]; +} From 62c8faa7be4c54783ff7d4c1021b48c85e6067f7 Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Wed, 22 Oct 2025 13:04:35 +0100 Subject: [PATCH 04/11] Remote functions and Zod for collections It's so... beautiful --- api/src/routes/collections/+server.ts | 19 ++++++ api/src/routes/collections/[slug]/+server.ts | 22 ++++++- .../users/[slug]/collections/+server.ts | 18 ------ app/package-lock.json | 12 +++- app/package.json | 5 +- app/src/lib/components/icons/Albums.svelte | 4 +- .../releases/ReleaseCollectionsPopup.svelte | 61 ++++++++++++++++++ .../remote-functions/collections.remote.ts | 59 ++++++++++++++++++ app/src/routes/me/collections/+page.server.ts | 41 ------------ app/src/routes/me/collections/+page.svelte | 45 +++++++++----- app/src/routes/releases/[slug]/+page.svelte | 62 +++---------------- app/svelte.config.js | 12 +++- shared/types/index.ts | 2 +- 13 files changed, 224 insertions(+), 138 deletions(-) create mode 100644 api/src/routes/collections/+server.ts create mode 100644 app/src/lib/components/releases/ReleaseCollectionsPopup.svelte create mode 100644 app/src/lib/remote-functions/collections.remote.ts delete mode 100644 app/src/routes/me/collections/+page.server.ts diff --git a/api/src/routes/collections/+server.ts b/api/src/routes/collections/+server.ts new file mode 100644 index 0000000..1d16cf7 --- /dev/null +++ b/api/src/routes/collections/+server.ts @@ -0,0 +1,19 @@ +import { supabase } from "$lib/server/supabase"; +import { json } from "@sveltejs/kit"; +import { TABLES } from "../../../../shared/config"; + +export async function POST({ request }) { + const { userId, name, description } = await request.json(); + + if (!name) { + return json({ error: 'Missing collection name' }, { status: 400 }); + } + + const { error } = await supabase.from(TABLES.collections).insert({ user_id: userId, name, description }); + + if (error) { + return json({ error: 'Failed to create collection' }, { status: 500 }); + } + + return json({ success: true }); +} \ No newline at end of file diff --git a/api/src/routes/collections/[slug]/+server.ts b/api/src/routes/collections/[slug]/+server.ts index eda089c..d23773c 100644 --- a/api/src/routes/collections/[slug]/+server.ts +++ b/api/src/routes/collections/[slug]/+server.ts @@ -4,13 +4,13 @@ import { TABLES } from '../../../../../shared/config'; export async function PATCH({ request, params }) { const collectionId = params.slug; - const { releaseId, add } = await request.json(); + const { releaseId, addOrRemove } = await request.json(); if (!releaseId) { return json({ error: 'Missing release ID' }, { status: 400 }); } - if (!add) { + if (addOrRemove === 'remove') { const { error } = await supabase .from(TABLES.collectionReleases) .delete() @@ -36,3 +36,21 @@ export async function PATCH({ request, params }) { return json({ success: true }); } + +export async function DELETE({ request, params }) { + const collectionId = params.slug; + const { userId} = await request.json(); + + const { error } = await supabase + .from(TABLES.collections) + .delete() + .eq('id', collectionId) + .eq('user_id', userId); + + if (error) { + console.error('Error deleting collection:', error); + return json({ error: 'Failed to delete collection' }, { status: 500 }); + } + + return json({ success: true }); +}; \ No newline at end of file diff --git a/api/src/routes/users/[slug]/collections/+server.ts b/api/src/routes/users/[slug]/collections/+server.ts index 04674b9..31c4487 100644 --- a/api/src/routes/users/[slug]/collections/+server.ts +++ b/api/src/routes/users/[slug]/collections/+server.ts @@ -16,21 +16,3 @@ export async function GET({ params }) { return json(collections); } - -export async function POST({ params, request }) { - const userId = params.slug; - - const { name } = await request.json(); - - if (!name) { - return json({ error: 'Missing collection name' }, { status: 400 }); - } - - const { error } = await supabase.from(TABLES.collections).insert({ user_id: userId, name }); - - if (error) { - return json({ error: 'Failed to create collection' }, { status: 500 }); - } - - return json({ success: true }); -} diff --git a/app/package-lock.json b/app/package-lock.json index bc1624a..5678f0d 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.1", "dependencies": { "@supabase/ssr": "^0.7.0", - "@supabase/supabase-js": "^2.74.0" + "@supabase/supabase-js": "^2.74.0", + "zod": "^4.1.12" }, "devDependencies": { "@eslint/compat": "^1.4.0", @@ -5057,6 +5058,15 @@ "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "dev": true, "license": "MIT" + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/app/package.json b/app/package.json index 980b31b..ee3e1f9 100644 --- a/app/package.json +++ b/app/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@supabase/ssr": "^0.7.0", - "@supabase/supabase-js": "^2.74.0" + "@supabase/supabase-js": "^2.74.0", + "zod": "^4.1.12" } -} \ No newline at end of file +} diff --git a/app/src/lib/components/icons/Albums.svelte b/app/src/lib/components/icons/Albums.svelte index 29022db..030d421 100644 --- a/app/src/lib/components/icons/Albums.svelte +++ b/app/src/lib/components/icons/Albums.svelte @@ -12,8 +12,8 @@ diff --git a/app/src/lib/remote-functions/collections.remote.ts b/app/src/lib/remote-functions/collections.remote.ts new file mode 100644 index 0000000..9d1ca19 --- /dev/null +++ b/app/src/lib/remote-functions/collections.remote.ts @@ -0,0 +1,59 @@ +import { form } from '$app/server'; +import { API_BASE } from '$lib/global/config'; +import * as z from 'zod'; + +const MakeCollectionForm = z.object({ + userId: z.string(), + name: z.string().min(3).max(100), + description: z.string().max(500).optional(), +}); + +const DeleteCollectionForm = z.object({ + userId: z.string(), + collectionId: z.string(), +}); + +const AddOrRemoveReleaseForm = z.object({ + collectionId: z.string(), + releaseId: z.string(), + addOrRemove: z.enum(['add', 'remove']), +}); + +export const makeCollection = form( + MakeCollectionForm, + async ({ userId, name, description }) => { + await fetch(`${API_BASE}/collections`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ userId, name, description }) + }); + } +); + +export const deleteCollection = form( + DeleteCollectionForm, + async ({ userId, collectionId }) => { + await fetch(`${API_BASE}/collections/${collectionId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ userId }) + }); + } +); + +export const addOrRemoveRelease = form( + AddOrRemoveReleaseForm, + async ({ collectionId, releaseId, addOrRemove }) => { + await fetch(`${API_BASE}/collections/${collectionId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ releaseId, addOrRemove }) + }); + } +); \ No newline at end of file diff --git a/app/src/routes/me/collections/+page.server.ts b/app/src/routes/me/collections/+page.server.ts deleted file mode 100644 index c542d31..0000000 --- a/app/src/routes/me/collections/+page.server.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { API_BASE } from '$lib/global/config'; -import type { Actions } from '@sveltejs/kit'; - -export const actions: Actions = { - createCollection: async ({ request, fetch, locals: { safeGetSession } }) => { - const { session } = await safeGetSession(); - if (session) { - const formData = await request.formData(); - const collectionName = formData.get('collectionName'); - if (collectionName) { - console.log('Creating collection with name:', collectionName); - await fetch(`${API_BASE}/users/${session.user.id}/collections`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ name: collectionName }) - }); - console.log(`Created new collection: ${collectionName}`); - } - } - }, - addOrRemoveRelease: async ({ request, fetch, locals: { safeGetSession } }) => { - const { session } = await safeGetSession(); - if (session) { - const formData = await request.formData(); - const collectionId = formData.get('collectionId'); - const releaseId = formData.get('releaseId'); - const add = formData.get('add') === 'true'; - if (collectionId && releaseId) { - await fetch(`${API_BASE}/collections/${collectionId}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ releaseId, collectionId, add }) - }); - } - } - } -}; diff --git a/app/src/routes/me/collections/+page.svelte b/app/src/routes/me/collections/+page.svelte index c46570b..853a5f7 100644 --- a/app/src/routes/me/collections/+page.svelte +++ b/app/src/routes/me/collections/+page.svelte @@ -1,15 +1,8 @@ @@ -28,16 +21,32 @@ {/if}
+ {#if data.user} +
+ + + +
+ {/if}
{/each} -

Create a new collection

+{#if data.user} +

Create a new collection

-
- - - -
+
+ + + + +
+{/if} diff --git a/app/src/routes/releases/[slug]/+page.svelte b/app/src/routes/releases/[slug]/+page.svelte index 614f9a2..2bdd0ad 100644 --- a/app/src/routes/releases/[slug]/+page.svelte +++ b/app/src/routes/releases/[slug]/+page.svelte @@ -12,9 +12,9 @@ import ButtonWrapper from '$lib/components/layout/ButtonWrapper.svelte'; import { formatReleaseType } from '../../../../../shared/utils'; import TagsGrid from '$lib/components/tags/TagsGrid.svelte'; - import { enhance } from '$app/forms'; - import type { SubmitFunction } from '@sveltejs/kit'; import Albums from '$lib/components/icons/Albums.svelte'; + import { addOrRemoveRelease } from '$lib/remote-functions/collections.remote'; + import ReleaseCollectionsPopup from '$lib/components/releases/ReleaseCollectionsPopup.svelte'; let { data @@ -30,15 +30,6 @@ let release = $derived(data.release); let collectionMenuOpen = $state(false); - let updatingCollection = $state(false); - - const handleUpdatingCollection: SubmitFunction = () => { - updatingCollection = true; - return async ({ update }) => { - updatingCollection = false; - update(); - }; - }; @@ -68,39 +59,11 @@ {#if collectionMenuOpen} -
-

Add {release.title} to collection{data.collections?.length > 1 ? 's' : ''}

- {#each data.collections as collection} -
- - - r.id === release.id) ? 'false' : 'true'} - /> - - -
- {/each} - -
+ {/if} diff --git a/app/svelte.config.js b/app/svelte.config.js index fe96962..1338620 100644 --- a/app/svelte.config.js +++ b/app/svelte.config.js @@ -9,9 +9,17 @@ const config = { preprocess: [vitePreprocess, mdsvex({ extensions: ['.md'] })], kit: { - adapter: adapter() + adapter: adapter(), + experimental: { + remoteFunctions: true + } }, - extensions: ['.svelte', '.md'] + extensions: ['.svelte', '.md'], + compilerOptions: { + experimental: { + async: true + } + } }; export default config; diff --git a/shared/types/index.ts b/shared/types/index.ts index c059119..6161321 100644 --- a/shared/types/index.ts +++ b/shared/types/index.ts @@ -79,5 +79,5 @@ export interface Collection { name: string; description?: string; created_at: string; - releases: ReleaseHydrated[]; + releases?: ReleaseHydrated[]; } From 1027bea859c668d3fc2e1388ccbb9aa739f29d4c Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Wed, 22 Oct 2025 14:01:52 +0100 Subject: [PATCH 05/11] Remote functions and Zod for liked tracks --- .../releases/ReleaseCollectionsPopup.svelte | 16 ++-- .../releases/TrackLikeButton.svelte | 29 ++----- app/src/lib/remote-functions/auth-check.ts | 12 +++ .../remote-functions/collections.remote.ts | 75 +++++++++---------- app/src/lib/remote-functions/tracks.remote.ts | 21 ++++++ .../routes/me/liked-tracks/+page.server.ts | 25 ------- app/src/routes/releases/[slug]/+page.svelte | 7 +- 7 files changed, 88 insertions(+), 97 deletions(-) create mode 100644 app/src/lib/remote-functions/auth-check.ts create mode 100644 app/src/lib/remote-functions/tracks.remote.ts delete mode 100644 app/src/routes/me/liked-tracks/+page.server.ts diff --git a/app/src/lib/components/releases/ReleaseCollectionsPopup.svelte b/app/src/lib/components/releases/ReleaseCollectionsPopup.svelte index f2aa630..12072ad 100644 --- a/app/src/lib/components/releases/ReleaseCollectionsPopup.svelte +++ b/app/src/lib/components/releases/ReleaseCollectionsPopup.svelte @@ -1,5 +1,5 @@ -
- + + t.track.id === trackID) ? 'remove' : 'add'} /> -
diff --git a/app/src/lib/remote-functions/tracks.remote.ts b/app/src/lib/remote-functions/user.remote.ts similarity index 55% rename from app/src/lib/remote-functions/tracks.remote.ts rename to app/src/lib/remote-functions/user.remote.ts index 8ae0b8b..8e77024 100644 --- a/app/src/lib/remote-functions/tracks.remote.ts +++ b/app/src/lib/remote-functions/user.remote.ts @@ -8,6 +8,26 @@ const ToggleLikedTrackForm = z.object({ addOrRemove: z.enum(['add', 'remove']) }); +const ToggleFollowedArtistForm = z.object({ + artistID: z.string(), + addOrRemove: z.enum(['add', 'remove']) +}); + +export const toggleFollowedArtist = form( + ToggleFollowedArtistForm, + async ({ artistID, addOrRemove }) => { + const userId = requireAuth().id; + + await fetch(`${API_BASE}/users/${userId}/following`, { + method: addOrRemove === 'remove' ? 'DELETE' : 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ artistId: artistID }) + }); + } +); + export const toggleLikedTrack = form(ToggleLikedTrackForm, async ({ trackId, addOrRemove }) => { const userId = requireAuth().id; diff --git a/app/src/routes/artists/[slug]/+page.server.ts b/app/src/routes/artists/[slug]/+page.server.ts index fe18e8e..c3d277d 100644 --- a/app/src/routes/artists/[slug]/+page.server.ts +++ b/app/src/routes/artists/[slug]/+page.server.ts @@ -1,4 +1,3 @@ -import type { Actions } from '@sveltejs/kit'; import type { ArtistRaw } from '../../../../../shared/types'; import { API_BASE } from '$lib/global/config'; import { sortReleasesByDate } from '../../../../../shared/utils'; @@ -32,23 +31,3 @@ export const load = async ({ params, fetch }) => { releases: sortReleasesByDate(artistReleases) }; }; - -export const actions: Actions = { - toggleFollowedArtist: async ({ request, fetch, locals: { safeGetSession } }) => { - const { session } = await safeGetSession(); - if (session) { - const formData = await request.formData(); - const artistID = formData.get('artistID'); - const addOrRemove = formData.get('addOrRemove'); - if (artistID && addOrRemove) { - await fetch(`${API_BASE}/users/${session.user.id}/following`, { - method: addOrRemove === 'remove' ? 'DELETE' : 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ artistId: artistID }) - }); - } - } - } -}; diff --git a/app/src/routes/artists/[slug]/+page.svelte b/app/src/routes/artists/[slug]/+page.svelte index b26d2ff..7249a9b 100644 --- a/app/src/routes/artists/[slug]/+page.svelte +++ b/app/src/routes/artists/[slug]/+page.svelte @@ -2,7 +2,7 @@ import ReleaseCardGrid from '$lib/components/ReleaseCardGrid.svelte'; import type { ArtistRaw, ReleaseHydrated } from '../../../../../shared/types'; import { makeImageLink } from '$lib/utils'; - import { enhance } from '$app/forms'; + import { toggleFollowedArtist } from '$lib/remote-functions/user.remote'; let { data @@ -60,20 +60,15 @@ You are {data.followedArtists.includes(data.artist.id) ? 'following' : 'not following'} this artist. -
- + + - +
From 1a9db9a7b3f7ccbb8de89b5d5183dae3d04145ff Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Wed, 22 Oct 2025 16:15:52 +0100 Subject: [PATCH 07/11] Create +page.server.ts --- app/src/routes/+page.server.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/src/routes/+page.server.ts diff --git a/app/src/routes/+page.server.ts b/app/src/routes/+page.server.ts new file mode 100644 index 0000000..221d63b --- /dev/null +++ b/app/src/routes/+page.server.ts @@ -0,0 +1 @@ +// I don't like this but SvelteKit requires a +page.server.ts file for hooks to work \ No newline at end of file From 40facac09440e820244b22121adc77fe6892a890 Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Wed, 22 Oct 2025 17:52:11 +0100 Subject: [PATCH 08/11] Rough mixtape implementation --- api/src/routes/mixtapes/+server.ts | 21 +++++++ api/src/routes/mixtapes/[slug]/+server.ts | 22 +++++++ .../routes/users/[slug]/mixtapes/+server.ts | 18 ++++++ .../lib/components/layout/PopupWrapper.svelte | 27 ++++++++ ...p.svelte => ReleaseCollectionsMenu.svelte} | 18 +----- .../components/releases/TracksTable.svelte | 10 ++- .../components/releases/TracksTableRow.svelte | 30 ++++++++- .../remote-functions/collections.remote.ts | 2 - .../lib/remote-functions/mixtapes.remote.ts | 35 +++++++++++ app/src/routes/+layout.server.ts | 5 +- app/src/routes/+layout.ts | 3 +- app/src/routes/+page.server.ts | 2 +- app/src/routes/me/collections/+page.svelte | 4 +- app/src/routes/me/liked-tracks/+page.svelte | 3 +- app/src/routes/me/mixtapes/+page.svelte | 61 ++++++++++++++++++- app/src/routes/releases/[slug]/+page.svelte | 17 ++++-- shared/config/index.ts | 3 + shared/types/index.ts | 22 +++++++ 18 files changed, 266 insertions(+), 37 deletions(-) create mode 100644 api/src/routes/mixtapes/+server.ts create mode 100644 api/src/routes/mixtapes/[slug]/+server.ts create mode 100644 api/src/routes/users/[slug]/mixtapes/+server.ts create mode 100644 app/src/lib/components/layout/PopupWrapper.svelte rename app/src/lib/components/releases/{ReleaseCollectionsPopup.svelte => ReleaseCollectionsMenu.svelte} (75%) create mode 100644 app/src/lib/remote-functions/mixtapes.remote.ts diff --git a/api/src/routes/mixtapes/+server.ts b/api/src/routes/mixtapes/+server.ts new file mode 100644 index 0000000..a979286 --- /dev/null +++ b/api/src/routes/mixtapes/+server.ts @@ -0,0 +1,21 @@ +import { supabase } from "$lib/server/supabase"; +import { json } from "@sveltejs/kit"; +import { TABLES } from "../../../../shared/config"; + +export async function POST({ request }) { + const { userId, name, description } = await request.json(); + + if (!name) { + console.log('Mixtape name is missing'); + return json({ error: 'Missing mixtape name' }, { status: 400 }); + } + + const { error } = await supabase.from(TABLES.mixtapesHydrated).insert({ user_id: userId, name, description }); + + if (error) { + console.log('Error creating mixtape:', error); + return json({ error: 'Failed to create mixtape' }, { status: 500 }); + } + + return json({ success: true }); +} \ No newline at end of file diff --git a/api/src/routes/mixtapes/[slug]/+server.ts b/api/src/routes/mixtapes/[slug]/+server.ts new file mode 100644 index 0000000..8022d45 --- /dev/null +++ b/api/src/routes/mixtapes/[slug]/+server.ts @@ -0,0 +1,22 @@ +import { supabase } from "$lib/server/supabase"; +import { json } from "@sveltejs/kit"; +import { TABLES } from "../../../../../shared/config"; + +export async function PATCH({ request, params }) { + const mixtapeId = params.slug; + const { trackId } = await request.json(); + + if (!trackId) { + return json({ error: 'Missing track ID' }, { status: 400 }); + } + + const { error } = await supabase + .from(TABLES.mixtapeTracks) + .insert({ mixtape_id: mixtapeId, track_id: trackId }); + + if (error) { + return json({ error: 'Failed to add track to mixtape' }, { status: 500 }); + } + + return json({ success: true }); +} \ No newline at end of file diff --git a/api/src/routes/users/[slug]/mixtapes/+server.ts b/api/src/routes/users/[slug]/mixtapes/+server.ts new file mode 100644 index 0000000..2b45ce5 --- /dev/null +++ b/api/src/routes/users/[slug]/mixtapes/+server.ts @@ -0,0 +1,18 @@ +import { supabase } from '$lib/server/supabase'; +import { json } from '@sveltejs/kit'; +import { TABLES } from '../../../../../../shared/config'; + +export async function GET({ params }) { + const userId = params.slug; + const { data: mixtapes, error } = await supabase + .from(TABLES.mixtapesHydrated) + .select('*') + .eq('user_id', userId); + + if (error) { + console.error('Error fetching mixtapes:', error); + return json({ error: 'Failed to fetch mixtapes' }, { status: 500 }); + } + + return json(mixtapes); +} diff --git a/app/src/lib/components/layout/PopupWrapper.svelte b/app/src/lib/components/layout/PopupWrapper.svelte new file mode 100644 index 0000000..c1af69b --- /dev/null +++ b/app/src/lib/components/layout/PopupWrapper.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/lib/components/releases/ReleaseCollectionsPopup.svelte b/app/src/lib/components/releases/ReleaseCollectionsMenu.svelte similarity index 75% rename from app/src/lib/components/releases/ReleaseCollectionsPopup.svelte rename to app/src/lib/components/releases/ReleaseCollectionsMenu.svelte index 12072ad..df1a60e 100644 --- a/app/src/lib/components/releases/ReleaseCollectionsPopup.svelte +++ b/app/src/lib/components/releases/ReleaseCollectionsMenu.svelte @@ -4,12 +4,10 @@ let { release, - collections, - collectionMenuOpen = $bindable() + collections }: { release: ReleaseHydrated; collections: Collection[]; - collectionMenuOpen: boolean; } = $props(); @@ -37,23 +35,9 @@ {/each} - diff --git a/app/src/routes/releases/[slug]/+page.svelte b/app/src/routes/releases/[slug]/+page.svelte index 4f636ea..2fff7ad 100644 --- a/app/src/routes/releases/[slug]/+page.svelte +++ b/app/src/routes/releases/[slug]/+page.svelte @@ -2,6 +2,7 @@ import type { Collection, LikedTrackObject, + Mixtape, ReleaseHydrated, UserProfile } from '../../../../../shared/types'; @@ -13,7 +14,8 @@ import { formatReleaseType } from '../../../../../shared/utils'; import TagsGrid from '$lib/components/tags/TagsGrid.svelte'; import Albums from '$lib/components/icons/Albums.svelte'; - import ReleaseCollectionsPopup from '$lib/components/releases/ReleaseCollectionsPopup.svelte'; + import ReleaseCollectionsMenu from '$lib/components/releases/ReleaseCollectionsMenu.svelte'; + import PopupWrapper from '$lib/components/layout/PopupWrapper.svelte'; let { data @@ -24,11 +26,12 @@ profileData: UserProfile; collections: Collection[]; likedTracks: LikedTrackObject[]; + mixtapes: Mixtape[]; }; } = $props(); let release = $derived(data.release); - let collectionMenuOpen = $state(false); + let popupMenuOpen = $state(false); @@ -47,18 +50,19 @@

{release.title}

-
- (collectionMenuOpen = !collectionMenuOpen)}> + (popupMenuOpen = !popupMenuOpen)}>
- {#if collectionMenuOpen} - + {#if popupMenuOpen} + + + {/if} diff --git a/shared/config/index.ts b/shared/config/index.ts index bba432b..0f13629 100644 --- a/shared/config/index.ts +++ b/shared/config/index.ts @@ -27,4 +27,7 @@ export const TABLES = { collections: "collections", collectionReleases: "collection_releases", collectionsHydrated: "collections_hydrated", + mixtapes: "mixtapes", + mixtapeTracks: "mixtape_tracks", + mixtapesHydrated: "mixtapes_hydrated", }; diff --git a/shared/types/index.ts b/shared/types/index.ts index 6161321..8729be3 100644 --- a/shared/types/index.ts +++ b/shared/types/index.ts @@ -81,3 +81,25 @@ export interface Collection { created_at: string; releases?: ReleaseHydrated[]; } + +export interface Mixtape { + id: string; + user_id: string; + name: string; + description?: string; + is_public: boolean; + created_at: string; + updated_at: string; + tracks?: { + id: string; + title: string; + ipfs_cid: string; + duration_seconds: number; + artist: { + id: string; + name: string; + image_ipfs_cid?: string; + }; + added_at: string; + }[]; +} \ No newline at end of file From f91cf5720b83dc6a448bbfafa6606c9f481ea4cd Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Thu, 23 Oct 2025 00:38:22 +0100 Subject: [PATCH 09/11] Big ol' state rejig A step towards where I want to be but still more to do. This separates global state from props enough to make track tables far more reusable, as they should be, but the data model needs to be tidier --- .../releases/ReleaseTrackButton.svelte | 17 ++------- .../components/releases/TracksTable.svelte | 33 +++------------- .../components/releases/TracksTableRow.svelte | 28 +++++--------- app/src/lib/global/state.svelte.ts | 21 ++++++++-- app/src/routes/+layout.ts | 2 + app/src/routes/me/liked-tracks/+page.svelte | 10 +---- app/src/routes/me/mixtapes/+page.svelte | 21 +++++----- app/src/routes/releases/[slug]/+page.svelte | 13 +++---- shared/types/index.ts | 38 ++++++++++++------- 9 files changed, 78 insertions(+), 105 deletions(-) diff --git a/app/src/lib/components/releases/ReleaseTrackButton.svelte b/app/src/lib/components/releases/ReleaseTrackButton.svelte index 2c8ef13..ddb7ffb 100644 --- a/app/src/lib/components/releases/ReleaseTrackButton.svelte +++ b/app/src/lib/components/releases/ReleaseTrackButton.svelte @@ -1,21 +1,16 @@ @@ -25,13 +20,7 @@ if (track.ipfs_cid == userState.activeSong?.ipfs_cid) { userState.activeSongIsPaused = !userState.activeSongIsPaused; } else { - setActiveSong( - track, - release, - session.user.id, - userState.liveBalance ?? profileData.tokens_balance, - profileData.pay_per_stream - ); + setActiveSong(track, release, userState.id, userState.liveBalance, userState.payPerStream); userState.autoPlay = false; } }} diff --git a/app/src/lib/components/releases/TracksTable.svelte b/app/src/lib/components/releases/TracksTable.svelte index c6cb523..c9ef4c7 100644 --- a/app/src/lib/components/releases/TracksTable.svelte +++ b/app/src/lib/components/releases/TracksTable.svelte @@ -1,26 +1,12 @@ @@ -41,17 +27,8 @@ - {#each release.tracks as track, i} - + {#each tracksAndTheirReleases as { track, release }, i} + {/each} diff --git a/app/src/lib/components/releases/TracksTableRow.svelte b/app/src/lib/components/releases/TracksTableRow.svelte index b8a3369..c53cbd5 100644 --- a/app/src/lib/components/releases/TracksTableRow.svelte +++ b/app/src/lib/components/releases/TracksTableRow.svelte @@ -1,36 +1,22 @@ - Your mixtapes · Soli - + Your mixtapes · Soli +

Your mixtapes

@@ -20,15 +21,13 @@ {#if mixtape.description}
{mixtape.description}
{/if} - {#if mixtape.tracks.length > 0} - {#each mixtape.tracks as track, i} -
- {i + 1}. {track.title} by {track.artist.name} -
- {/each} - {:else} -
This mixtape has no tracks yet.
- {/if} + ({ + track, + release: { ...track.release, artist_id: track.artist.id, artist_name: track.artist.name } + }))} + showReleaseAndArtist={true} + /> {/each} diff --git a/app/src/routes/releases/[slug]/+page.svelte b/app/src/routes/releases/[slug]/+page.svelte index 2fff7ad..51f66df 100644 --- a/app/src/routes/releases/[slug]/+page.svelte +++ b/app/src/routes/releases/[slug]/+page.svelte @@ -8,7 +8,7 @@ } from '../../../../../shared/types'; import { makeImageLink } from '$lib/utils'; import type { Session } from '@supabase/supabase-js'; - import ReleaseTracks from '$lib/components/releases/TracksTable.svelte'; + import TracksTable from '$lib/components/releases/TracksTable.svelte'; import { setActiveSong, userState } from '$lib/global/state.svelte'; import ButtonWrapper from '$lib/components/layout/ButtonWrapper.svelte'; import { formatReleaseType } from '../../../../../shared/utils'; @@ -88,12 +88,11 @@ - ({ + track, + release + }))} />
diff --git a/shared/types/index.ts b/shared/types/index.ts index 8729be3..13db2c7 100644 --- a/shared/types/index.ts +++ b/shared/types/index.ts @@ -84,22 +84,32 @@ export interface Collection { export interface Mixtape { id: string; - user_id: string; + user_id: string | null; name: string; - description?: string; + description: string | null; is_public: boolean; created_at: string; updated_at: string; - tracks?: { + tracks: HydratedTrack[]; +} + +export interface HydratedTrack { + id: string; + title: string; + ipfs_cid: string; + duration_seconds: number | null; + added_at: string; + artist: { id: string; - title: string; - ipfs_cid: string; - duration_seconds: number; - artist: { - id: string; - name: string; - image_ipfs_cid?: string; - }; - added_at: string; - }[]; -} \ No newline at end of file + name: string; + image_ipfs_cid: string | null; + } | null; + release: ParentRelease | null; +} + +export interface ParentRelease { + id: string; + title: string; + artwork_ipfs_cid: string | null; + release_date: string | null; +} From d5b6cc0f73112eb41a9ab436459ac94e7cb88da8 Mon Sep 17 00:00:00 2001 From: Frederick O'Brien Date: Sun, 26 Oct 2025 13:16:51 +0000 Subject: [PATCH 10/11] Big ol' types rejig (and collection pages) --- api/package.json | 2 +- api/src/routes/artists/+server.ts | 11 +- api/src/routes/artists/[slug]/+server.ts | 8 +- .../routes/artists/[slug]/releases/+server.ts | 6 +- api/src/routes/collections/+server.ts | 28 +++-- api/src/routes/collections/[slug]/+server.ts | 20 ++- api/src/routes/mixtapes/+server.ts | 32 ++--- api/src/routes/mixtapes/[slug]/+server.ts | 48 +++++--- api/src/routes/releases/+server.ts | 7 +- api/src/routes/releases/[slug]/+server.ts | 6 +- api/src/routes/users/[slug]/+server.ts | 4 +- .../users/[slug]/collections/+server.ts | 4 +- api/src/routes/users/[slug]/likes/+server.ts | 28 +---- .../routes/users/[slug]/mixtapes/+server.ts | 2 +- app/src/lib/components/ArtistCardGrid.svelte | 4 +- app/src/lib/components/ReleaseCardGrid.svelte | 4 +- .../audio-player/AudioPlayer.svelte | 14 +-- app/src/lib/components/layout/Header.svelte | 2 +- .../lib/components/layout/SectionLink.svelte | 13 ++ .../releases/ReleaseCollectionsMenu.svelte | 4 +- .../releases/ReleaseTrackButton.svelte | 6 +- .../releases/TrackLikeButton.svelte | 6 +- .../components/releases/TracksTable.svelte | 20 ++- .../components/releases/TracksTableRow.svelte | 19 +-- .../components/search/SearchResults.svelte | 2 +- app/src/lib/global/state.svelte.ts | 18 ++- .../remote-functions/collections.remote.ts | 10 +- .../lib/remote-functions/mixtapes.remote.ts | 10 +- .../lib/remote-functions/releases.remote.ts | 11 ++ app/src/routes/+layout.server.ts | 8 +- app/src/routes/+layout.svelte | 10 +- app/src/routes/+page.ts | 6 +- app/src/routes/account/+page.svelte | 2 +- app/src/routes/artists/+page.ts | 5 +- app/src/routes/artists/[slug]/+page.server.ts | 14 +-- app/src/routes/artists/[slug]/+page.svelte | 13 +- app/src/routes/genres/+page.ts | 2 +- app/src/routes/genres/[slug]/+page.svelte | 11 +- app/src/routes/genres/[slug]/+page.ts | 2 +- app/src/routes/me/collections/+page.svelte | 17 ++- .../routes/me/collections/[slug]/+page.svelte | 29 +++++ app/src/routes/me/liked-tracks/+page.svelte | 54 +++----- app/src/routes/me/mixtapes/+page.svelte | 19 +-- .../routes/me/mixtapes/[slug]/+page.svelte | 36 ++++++ app/src/routes/releases/+page.svelte | 2 +- app/src/routes/releases/+page.ts | 2 +- app/src/routes/releases/[slug]/+page.svelte | 41 +++---- app/src/routes/releases/[slug]/+page.ts | 2 +- .../src/lib/components/ProfileSummary.svelte | 4 +- dashboard/src/lib/state.svelte.ts | 4 +- dashboard/src/routes/+layout.server.ts | 14 +-- dashboard/src/routes/+page.svelte | 16 ++- shared/config/index.ts | 9 +- shared/types/core.ts | 66 ++++++++++ shared/types/forms.ts | 2 +- shared/types/hydrated.ts | 19 +++ shared/types/index.ts | 115 ------------------ shared/utils/index.ts | 2 +- 58 files changed, 458 insertions(+), 417 deletions(-) create mode 100644 app/src/lib/components/layout/SectionLink.svelte create mode 100644 app/src/lib/remote-functions/releases.remote.ts create mode 100644 app/src/routes/me/collections/[slug]/+page.svelte create mode 100644 app/src/routes/me/mixtapes/[slug]/+page.svelte create mode 100644 shared/types/core.ts create mode 100644 shared/types/hydrated.ts delete mode 100644 shared/types/index.ts diff --git a/api/package.json b/api/package.json index c1abc25..688c4e9 100644 --- a/api/package.json +++ b/api/package.json @@ -38,4 +38,4 @@ "stripe": "^19.1.0", "music-metadata": "^11.9.0" } -} \ No newline at end of file +} diff --git a/api/src/routes/artists/+server.ts b/api/src/routes/artists/+server.ts index da637a1..a77509c 100644 --- a/api/src/routes/artists/+server.ts +++ b/api/src/routes/artists/+server.ts @@ -1,12 +1,9 @@ import { TABLES } from '../../../../shared/config'; import { handlePostgrestQuery, supabase } from '$lib/server/supabase'; -import type { ArtistRaw } from '../../../../shared/types'; +import type { Artist } from '../../../../shared/types/core'; export async function GET() { - return handlePostgrestQuery( - async () => await supabase.from(TABLES.artists).select(), - { - transform: (data) => data.sort((a, b) => a.name.localeCompare(b.name)) - } - ); + return handlePostgrestQuery(async () => await supabase.from(TABLES.artists).select(), { + transform: (data) => data.sort((a, b) => a.name.localeCompare(b.name)) + }); } diff --git a/api/src/routes/artists/[slug]/+server.ts b/api/src/routes/artists/[slug]/+server.ts index c59f719..4e58e38 100644 --- a/api/src/routes/artists/[slug]/+server.ts +++ b/api/src/routes/artists/[slug]/+server.ts @@ -1,16 +1,16 @@ import { TABLES } from '../../../../../shared/config'; import { handlePostgrestQuery, supabase } from '$lib/server/supabase'; -import type { ArtistRaw } from '../../../../../shared/types'; +import type { Artist } from '../../../../../shared/types/core'; export async function GET({ params }) { - return handlePostgrestQuery( + return handlePostgrestQuery( async () => await supabase.from(TABLES.artists).select().eq('id', params.slug).single() ); } export async function PATCH({ request, params }) { - const body: Partial = await request.json(); - return handlePostgrestQuery( + const body: Partial = await request.json(); + return handlePostgrestQuery( async () => await supabase.from(TABLES.artists).update(body).eq('id', params.slug) ); } diff --git a/api/src/routes/artists/[slug]/releases/+server.ts b/api/src/routes/artists/[slug]/releases/+server.ts index a4776bb..283799b 100644 --- a/api/src/routes/artists/[slug]/releases/+server.ts +++ b/api/src/routes/artists/[slug]/releases/+server.ts @@ -1,9 +1,9 @@ -import { TABLES } from '../../../../../../shared/config'; import { handlePostgrestQuery, supabase } from '$lib/server/supabase'; -import type { ReleaseHydrated } from '../../../../../../shared/types'; +import { TABLES } from '../../../../../../shared/config'; +import type { ReleaseHydrated } from '../../../../../../shared/types/hydrated'; export async function GET({ params }) { return handlePostgrestQuery( - async () => await supabase.from(TABLES.releasesHydrated).select().eq('artist_id', params.slug) + async () => await supabase.from(TABLES.releasesRich).select().eq('artist_id', params.slug) ); } diff --git a/api/src/routes/collections/+server.ts b/api/src/routes/collections/+server.ts index 1d16cf7..3289ba8 100644 --- a/api/src/routes/collections/+server.ts +++ b/api/src/routes/collections/+server.ts @@ -1,19 +1,21 @@ -import { supabase } from "$lib/server/supabase"; -import { json } from "@sveltejs/kit"; -import { TABLES } from "../../../../shared/config"; +import { supabase } from '$lib/server/supabase'; +import { json } from '@sveltejs/kit'; +import { TABLES } from '../../../../shared/config'; export async function POST({ request }) { - const { userId, name, description } = await request.json(); + const { userId, name, description } = await request.json(); - if (!name) { - return json({ error: 'Missing collection name' }, { status: 400 }); - } + if (!name) { + return json({ error: 'Missing collection name' }, { status: 400 }); + } - const { error } = await supabase.from(TABLES.collections).insert({ user_id: userId, name, description }); + const { error } = await supabase + .from(TABLES.collections) + .insert({ user_id: userId, name, description }); - if (error) { - return json({ error: 'Failed to create collection' }, { status: 500 }); - } + if (error) { + return json({ error: 'Failed to create collection' }, { status: 500 }); + } - return json({ success: true }); -} \ No newline at end of file + return json({ success: true }); +} diff --git a/api/src/routes/collections/[slug]/+server.ts b/api/src/routes/collections/[slug]/+server.ts index d23773c..a8320c1 100644 --- a/api/src/routes/collections/[slug]/+server.ts +++ b/api/src/routes/collections/[slug]/+server.ts @@ -2,6 +2,22 @@ import { supabase } from '$lib/server/supabase'; import { json } from '@sveltejs/kit'; import { TABLES } from '../../../../../shared/config'; +export async function GET({ params }) { + const collectionId = params.slug; + const { data: collection, error } = await supabase + .from(TABLES.collectionsRich) + .select('*') + .eq('id', collectionId) + .single(); + + if (error) { + console.error('Error fetching collection:', error); + return json({ error: 'Failed to fetch collection' }, { status: 500 }); + } + + return json(collection); +} + export async function PATCH({ request, params }) { const collectionId = params.slug; const { releaseId, addOrRemove } = await request.json(); @@ -39,7 +55,7 @@ export async function PATCH({ request, params }) { export async function DELETE({ request, params }) { const collectionId = params.slug; - const { userId} = await request.json(); + const { userId } = await request.json(); const { error } = await supabase .from(TABLES.collections) @@ -53,4 +69,4 @@ export async function DELETE({ request, params }) { } return json({ success: true }); -}; \ No newline at end of file +} diff --git a/api/src/routes/mixtapes/+server.ts b/api/src/routes/mixtapes/+server.ts index a979286..5170673 100644 --- a/api/src/routes/mixtapes/+server.ts +++ b/api/src/routes/mixtapes/+server.ts @@ -1,21 +1,23 @@ -import { supabase } from "$lib/server/supabase"; -import { json } from "@sveltejs/kit"; -import { TABLES } from "../../../../shared/config"; +import { supabase } from '$lib/server/supabase'; +import { json } from '@sveltejs/kit'; +import { TABLES } from '../../../../shared/config'; export async function POST({ request }) { - const { userId, name, description } = await request.json(); + const { userId, name, description } = await request.json(); - if (!name) { - console.log('Mixtape name is missing'); - return json({ error: 'Missing mixtape name' }, { status: 400 }); - } + if (!name) { + console.log('Mixtape name is missing'); + return json({ error: 'Missing mixtape name' }, { status: 400 }); + } - const { error } = await supabase.from(TABLES.mixtapesHydrated).insert({ user_id: userId, name, description }); + const { error } = await supabase + .from(TABLES.mixtapesRich) + .insert({ user_id: userId, name, description }); - if (error) { - console.log('Error creating mixtape:', error); - return json({ error: 'Failed to create mixtape' }, { status: 500 }); - } + if (error) { + console.log('Error creating mixtape:', error); + return json({ error: 'Failed to create mixtape' }, { status: 500 }); + } - return json({ success: true }); -} \ No newline at end of file + return json({ success: true }); +} diff --git a/api/src/routes/mixtapes/[slug]/+server.ts b/api/src/routes/mixtapes/[slug]/+server.ts index 8022d45..e3a8a77 100644 --- a/api/src/routes/mixtapes/[slug]/+server.ts +++ b/api/src/routes/mixtapes/[slug]/+server.ts @@ -1,22 +1,38 @@ -import { supabase } from "$lib/server/supabase"; -import { json } from "@sveltejs/kit"; -import { TABLES } from "../../../../../shared/config"; +import { supabase } from '$lib/server/supabase'; +import { json } from '@sveltejs/kit'; +import { TABLES } from '../../../../../shared/config'; export async function PATCH({ request, params }) { - const mixtapeId = params.slug; - const { trackId } = await request.json(); + const mixtapeId = params.slug; + const { trackId } = await request.json(); - if (!trackId) { - return json({ error: 'Missing track ID' }, { status: 400 }); - } + if (!trackId) { + return json({ error: 'Missing track ID' }, { status: 400 }); + } - const { error } = await supabase - .from(TABLES.mixtapeTracks) - .insert({ mixtape_id: mixtapeId, track_id: trackId }); + const { error } = await supabase + .from(TABLES.mixtapeTracks) + .insert({ mixtape_id: mixtapeId, track_id: trackId }); - if (error) { - return json({ error: 'Failed to add track to mixtape' }, { status: 500 }); - } + if (error) { + return json({ error: 'Failed to add track to mixtape' }, { status: 500 }); + } - return json({ success: true }); -} \ No newline at end of file + return json({ success: true }); +} + +export async function GET({ params }) { + const mixtapeId = params.slug; + const { data: mixtape, error } = await supabase + .from(TABLES.mixtapesRich) + .select('*') + .eq('id', mixtapeId) + .single(); + + if (error) { + console.error('Error fetching mixtape tracks:', error); + return json({ error: 'Failed to fetch mixtape tracks' }, { status: 500 }); + } + + return json(mixtape); +} diff --git a/api/src/routes/releases/+server.ts b/api/src/routes/releases/+server.ts index 10e6fac..bbfcf76 100644 --- a/api/src/routes/releases/+server.ts +++ b/api/src/routes/releases/+server.ts @@ -1,13 +1,14 @@ import { handlePostgrestQuery, supabase } from '$lib/server/supabase'; import { TABLES } from '../../../../shared/config'; import { sortReleasesByDate } from '../../../../shared/utils'; -import type { ReleaseHydrated, ReleaseRaw } from '../../../../shared/types'; +import type { ReleaseHydrated } from '../../../../shared/types/hydrated'; import { pinata } from '$lib/server/pinata'; import { json } from '@sveltejs/kit'; +import type { Release } from '../../../../shared/types/core'; export async function GET() { return handlePostgrestQuery( - async () => await supabase.from(TABLES.releasesHydrated).select(), + async () => await supabase.from(TABLES.releasesRich).select(), { errorMessage: 'Failed to fetch artist data', transform: sortReleasesByDate @@ -31,7 +32,7 @@ export async function POST({ request }) { .name(`'${releaseName}' cover art`) .group(import.meta.env.PINATA_ARTWORK_GROUP); - return handlePostgrestQuery( + return handlePostgrestQuery( async () => await supabase .from(TABLES.releases) diff --git a/api/src/routes/releases/[slug]/+server.ts b/api/src/routes/releases/[slug]/+server.ts index ea8c87b..8fcf1b9 100644 --- a/api/src/routes/releases/[slug]/+server.ts +++ b/api/src/routes/releases/[slug]/+server.ts @@ -1,9 +1,9 @@ -import { TABLES } from '../../../../../shared/config'; import { handlePostgrestQuery, supabase } from '$lib/server/supabase'; -import type { ReleaseHydrated } from '../../../../../shared/types'; +import { TABLES } from '../../../../../shared/config'; +import type { ReleaseHydrated } from '../../../../../shared/types/hydrated'; export async function GET({ params }) { return handlePostgrestQuery( - async () => await supabase.from(TABLES.releasesHydrated).select().eq('id', params.slug).single() + async () => await supabase.from(TABLES.releasesRich).select().eq('id', params.slug).single() ); } diff --git a/api/src/routes/users/[slug]/+server.ts b/api/src/routes/users/[slug]/+server.ts index c2e0881..a9b1780 100644 --- a/api/src/routes/users/[slug]/+server.ts +++ b/api/src/routes/users/[slug]/+server.ts @@ -1,9 +1,9 @@ import { TABLES } from '../../../../../shared/config'; import { supabase } from '$lib/server/supabase'; import { json } from '@sveltejs/kit'; -import type { UserProfile } from '../../../../../shared/types'; +import type { User } from '../../../../../shared/types/core'; -const getUser = async (id: string): Promise => { +const getUser = async (id: string): Promise => { const { data } = await supabase .from(TABLES.users) .select(`first_name, tokens_balance, pay_per_stream`) diff --git a/api/src/routes/users/[slug]/collections/+server.ts b/api/src/routes/users/[slug]/collections/+server.ts index 31c4487..cd819e9 100644 --- a/api/src/routes/users/[slug]/collections/+server.ts +++ b/api/src/routes/users/[slug]/collections/+server.ts @@ -1,11 +1,11 @@ import { supabase } from '$lib/server/supabase'; import { json } from '@sveltejs/kit'; -import { TABLES } from '../../../../../../shared/config'; +import { TABLES } from '../../../../../../shared/config/index'; export async function GET({ params }) { const userId = params.slug; const { data: collections, error } = await supabase - .from(TABLES.collectionsHydrated) + .from(TABLES.collectionsRich) .select('*') .eq('user_id', userId); diff --git a/api/src/routes/users/[slug]/likes/+server.ts b/api/src/routes/users/[slug]/likes/+server.ts index 4a77b45..3f292ac 100644 --- a/api/src/routes/users/[slug]/likes/+server.ts +++ b/api/src/routes/users/[slug]/likes/+server.ts @@ -1,7 +1,6 @@ import { TABLES } from '../../../../../../shared/config'; import { supabase } from '$lib/server/supabase'; import { json } from '@sveltejs/kit'; -import type { LikedTrackObject } from '../../../../../../shared/types'; export async function GET({ params }) { const maybeUserID = params.slug; @@ -15,9 +14,9 @@ export async function GET({ params }) { return json({ error: 'Failed to fetch liked tracks' }, { status: 500 }); } - const { data: likedTracksRaw, error: likedTracksError } = await supabase - .from(TABLES.tracks) - .select(`id, artist_id, title, ipfs_cid, duration_seconds, created_at`) + const { data: likedTracks, error: likedTracksError } = await supabase + .from('tracks_hydrated') + .select('*') .in( 'id', likedTrackIDs.map((item) => item.track_id) @@ -27,27 +26,6 @@ export async function GET({ params }) { return json({ error: 'Failed to fetch liked tracks details' }, { status: 500 }); } - const likedTracks: LikedTrackObject[] = []; - - for (const likedTrack of likedTracksRaw) { - const { data: releaseID, error: releaseIDError } = await supabase - .from(TABLES.releaseTracks) - .select('release_id') - .eq('track_id', likedTrack.id) - .limit(1) - .single(); - if (releaseIDError) { - console.error('Error fetching release ID:', releaseIDError); - continue; - } - const { data: release } = await supabase - .from(TABLES.releasesHydrated) - .select('*') - .eq('id', releaseID.release_id) - .single(); - likedTracks.push({ track: likedTrack, release }); - } - return json(likedTracks); } diff --git a/api/src/routes/users/[slug]/mixtapes/+server.ts b/api/src/routes/users/[slug]/mixtapes/+server.ts index 2b45ce5..e103ea8 100644 --- a/api/src/routes/users/[slug]/mixtapes/+server.ts +++ b/api/src/routes/users/[slug]/mixtapes/+server.ts @@ -5,7 +5,7 @@ import { TABLES } from '../../../../../../shared/config'; export async function GET({ params }) { const userId = params.slug; const { data: mixtapes, error } = await supabase - .from(TABLES.mixtapesHydrated) + .from(TABLES.mixtapesRich) .select('*') .eq('user_id', userId); diff --git a/app/src/lib/components/ArtistCardGrid.svelte b/app/src/lib/components/ArtistCardGrid.svelte index e92aed2..15f88e0 100644 --- a/app/src/lib/components/ArtistCardGrid.svelte +++ b/app/src/lib/components/ArtistCardGrid.svelte @@ -1,12 +1,12 @@ diff --git a/app/src/lib/components/ReleaseCardGrid.svelte b/app/src/lib/components/ReleaseCardGrid.svelte index 9575667..964da46 100644 --- a/app/src/lib/components/ReleaseCardGrid.svelte +++ b/app/src/lib/components/ReleaseCardGrid.svelte @@ -1,5 +1,5 @@ + + + + diff --git a/app/src/lib/components/releases/ReleaseCollectionsMenu.svelte b/app/src/lib/components/releases/ReleaseCollectionsMenu.svelte index df1a60e..f9e3a9c 100644 --- a/app/src/lib/components/releases/ReleaseCollectionsMenu.svelte +++ b/app/src/lib/components/releases/ReleaseCollectionsMenu.svelte @@ -1,13 +1,13 @@ diff --git a/app/src/lib/components/releases/ReleaseTrackButton.svelte b/app/src/lib/components/releases/ReleaseTrackButton.svelte index ddb7ffb..43a3b22 100644 --- a/app/src/lib/components/releases/ReleaseTrackButton.svelte +++ b/app/src/lib/components/releases/ReleaseTrackButton.svelte @@ -1,6 +1,6 @@ diff --git a/app/src/lib/components/releases/TrackLikeButton.svelte b/app/src/lib/components/releases/TrackLikeButton.svelte index bc9c0a9..6d44616 100644 --- a/app/src/lib/components/releases/TrackLikeButton.svelte +++ b/app/src/lib/components/releases/TrackLikeButton.svelte @@ -1,7 +1,7 @@
diff --git a/app/src/lib/components/releases/TracksTable.svelte b/app/src/lib/components/releases/TracksTable.svelte index c9ef4c7..aed08c4 100644 --- a/app/src/lib/components/releases/TracksTable.svelte +++ b/app/src/lib/components/releases/TracksTable.svelte @@ -1,12 +1,12 @@ @@ -17,7 +17,7 @@ # Track {#if showReleaseAndArtist} - Release + Release Artist {/if} Duration @@ -27,8 +27,8 @@ - {#each tracksAndTheirReleases as { track, release }, i} - + {#each tracks as track, i} + {/each} @@ -42,4 +42,12 @@ th { font-weight: 500; } + .hide-on-mobile { + display: none; + } + @media (min-width: 640px) { + .hide-on-mobile { + display: table-cell; + } + } diff --git a/app/src/lib/components/releases/TracksTableRow.svelte b/app/src/lib/components/releases/TracksTableRow.svelte index c53cbd5..f4128c6 100644 --- a/app/src/lib/components/releases/TracksTableRow.svelte +++ b/app/src/lib/components/releases/TracksTableRow.svelte @@ -1,5 +1,5 @@ @@ -12,22 +15,16 @@

Your collections

-{#each data.collections as collection} - + {/each} {#if data.user} diff --git a/app/src/routes/me/collections/[slug]/+page.svelte b/app/src/routes/me/collections/[slug]/+page.svelte new file mode 100644 index 0000000..9a7fce2 --- /dev/null +++ b/app/src/routes/me/collections/[slug]/+page.svelte @@ -0,0 +1,29 @@ + + + + {name} · Your collections · Soli + + + + + +

{name}

+ +{#if description} +
{description}
+{/if} + + +
+ + +
diff --git a/app/src/routes/me/liked-tracks/+page.svelte b/app/src/routes/me/liked-tracks/+page.svelte index 768c813..4894130 100644 --- a/app/src/routes/me/liked-tracks/+page.svelte +++ b/app/src/routes/me/liked-tracks/+page.svelte @@ -1,7 +1,19 @@ @@ -11,42 +23,4 @@

Your liked tracks

-{#if data.session} - - - - - - - - - - - - - - {#each data.likedTracks as { track, release }} - - {/each} - -
TrackReleaseArtistDuration
-{/if} - - + diff --git a/app/src/routes/me/mixtapes/+page.svelte b/app/src/routes/me/mixtapes/+page.svelte index 79454d2..9ca3a3e 100644 --- a/app/src/routes/me/mixtapes/+page.svelte +++ b/app/src/routes/me/mixtapes/+page.svelte @@ -1,5 +1,4 @@ + + + {name} · Your mixtapes · Soli + + + + + +
+
+

{name}

+ {#if description} +
{description}
+ {/if} +
+ +
+ + diff --git a/app/src/routes/releases/+page.svelte b/app/src/routes/releases/+page.svelte index 880b417..ee6e8a1 100644 --- a/app/src/routes/releases/+page.svelte +++ b/app/src/routes/releases/+page.svelte @@ -1,6 +1,6 @@ diff --git a/app/src/routes/releases/+page.ts b/app/src/routes/releases/+page.ts index 10caaea..fa34381 100644 --- a/app/src/routes/releases/+page.ts +++ b/app/src/routes/releases/+page.ts @@ -1,5 +1,5 @@ import { API_BASE } from '$lib/global/config'; -import type { ReleaseHydrated } from '../../../../shared/types'; +import type { ReleaseHydrated } from '../../../../shared/types/hydrated'; export const load = async ({ fetch }) => { const releases: ReleaseHydrated[] = await fetch(`${API_BASE}/releases`).then((res) => res.json()); diff --git a/app/src/routes/releases/[slug]/+page.svelte b/app/src/routes/releases/[slug]/+page.svelte index 51f66df..736a952 100644 --- a/app/src/routes/releases/[slug]/+page.svelte +++ b/app/src/routes/releases/[slug]/+page.svelte @@ -1,11 +1,5 @@ - {release.title} · {release.artist_name} · Soli + {release.title} · {release.artist.name} · Soli - +
(popupMenuOpen = !popupMenuOpen)}> @@ -67,7 +66,7 @@ {`Cover @@ -89,9 +88,10 @@ ({ - track, - release + tracks={release.tracks.map((track) => ({ + ...track, + release, + artist: release.artist }))} /> @@ -108,9 +108,6 @@
diff --git a/app/src/routes/me/collections/[slug]/+page.svelte b/app/src/routes/me/collections/[slug]/+page.svelte index 9a7fce2..5f43e54 100644 --- a/app/src/routes/me/collections/[slug]/+page.svelte +++ b/app/src/routes/me/collections/[slug]/+page.svelte @@ -16,14 +16,29 @@ -

{name}

+
+
+

{name}

+ {#if description} +
{description}
+ {/if} +
+ +
-{#if description} -
{description}
-{/if} - -
+ + diff --git a/app/src/routes/me/mixtapes/+page.svelte b/app/src/routes/me/mixtapes/+page.svelte index 9ca3a3e..675f282 100644 --- a/app/src/routes/me/mixtapes/+page.svelte +++ b/app/src/routes/me/mixtapes/+page.svelte @@ -3,7 +3,7 @@ let { data } = $props(); - const mixtapes = data.mixtapes; + const mixtapes = $derived(data.mixtapes); @@ -11,18 +11,24 @@ -

Your mixtapes

+
+

Your mixtapes

-{#each mixtapes as mixtape} -
- -
-{/each} + {#each mixtapes as mixtape} + +
+

{mixtape.name}

+ {#if mixtape.description} +
{mixtape.description}
+ {/if} +
{mixtape.tracks.length} track{mixtape.tracks.length !== 1 ? 's' : ''}
+
+
+ {/each} +
-{#if data.user} -

Create a new mixtape

+
+

Create a new mixtape

-{/if} +
diff --git a/app/src/routes/me/mixtapes/[slug]/+page.svelte b/app/src/routes/me/mixtapes/[slug]/+page.svelte index 9ba9c34..fcbe61b 100644 --- a/app/src/routes/me/mixtapes/[slug]/+page.svelte +++ b/app/src/routes/me/mixtapes/[slug]/+page.svelte @@ -1,12 +1,12 @@ @@ -26,7 +26,15 @@
+
+ + +
+