Design Prot Merge #1

Merged
NotS1mp merged 217 commits from design-prot into master 2026-09-21 15:48:16 +00:00
26 changed files with 1317 additions and 88 deletions
Showing only changes of commit e728b333a9 - Show all commits
@@ -2,5 +2,5 @@ namespace Musify.Api.DataTransferObjects.PlayLists;
public record CreatePlayListRequest(
string Name,
string Description,
string? Description,
Guid? PictureIntentId);
@@ -181,12 +181,8 @@ public static class PlayListEndpoints
CreatePlayListRequest request,
CancellationToken cancellationToken)
{
var description = string.IsNullOrWhiteSpace(request.Description)
? "No description was provided."
: request.Description;
var result = await mediator.Send(
new CreatePlayListCommand(currentUser.RequiredId, request.Name, description, request.PictureIntentId),
new CreatePlayListCommand(currentUser.RequiredId, request.Name, request.Description, request.PictureIntentId),
cancellationToken);
return result.ToCreatedResult(playList => $"/playlists/{playList.Id}");
@@ -16,7 +16,7 @@ namespace Musify.Application.PlayLists
public record CreatePlayListCommand(
long UserId,
string Name,
string Description,
string? Description,
Guid? PictureIntentId)
: ICommand<ErrorOr<PlayListApplicationResponse>>;
@@ -58,7 +58,7 @@ namespace Musify.Application.PlayLists
UserId = request.UserId,
Name = request.Name,
NormalizedName = request.Name.Trim().ToUpperInvariant(),
Description = request.Description,
Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(),
Pictures = pictureIntent == null ? null
: new PlayListPictures { OriginalName = pictureIntent.ObjectName }
};
+1 -2
View File
@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Musify.Domain.Abstractions;
@@ -24,7 +24,6 @@ namespace Musify.Domain.Entities
[MaxLength(50)]
public required string NormalizedName { get; set; }
[Required]
[MaxLength(256)]
public string? Description { get; set; }
@@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class MakePlayListDescriptionOptional : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "PlayLists",
type: "character varying(256)",
maxLength: 256,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256);
migrationBuilder.Sql(
"""
UPDATE "PlayLists" SET "Description" = NULL
WHERE "Description" = 'No description was provided.';
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Description",
table: "PlayLists",
type: "character varying(256)",
maxLength: 256,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true);
}
}
}
@@ -409,7 +409,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
@@ -22,7 +22,7 @@
<header
class="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-line bg-bg/85 page-x backdrop-blur-md md:hidden"
>
<a href="/" class="text-lg font-extrabold tracking-wide">Musify</a>
<a href="/" class="text-lg font-semibold tracking-wide">Musify</a>
<div class="relative" bind:this={menuRef}>
<button
@@ -1,4 +1,5 @@
<script lang="ts">
import MediaCard from './MediaCard.svelte';
import PlaylistArt from './PlaylistArt.svelte';
import Cover from './Cover.svelte';
import { hueFor } from '$lib/theme/color';
@@ -45,30 +46,25 @@
);
</script>
<a
<MediaCard
href={href ?? `/albums/${id}`}
class="group animate-enter min-w-0 text-left"
style="animation-delay:{index * 45}ms"
{title}
subtitle={meta}
{index}
oncontextmenu={onContextMenu}
>
{#if coverSrc}
<Cover
trackId={id}
src={coverSrc}
hue={hueFor(id)}
size="large"
alt={title}
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
/>
{:else}
<PlaylistArt
{trackIds}
hue={hueFor(id)}
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
/>
{/if}
<div class="mt-2.5 truncate text-fg">{title}</div>
{#if meta}
<div class="mt-0.5 truncate text-sm text-fg-3">{meta}</div>
{/if}
</a>
{#snippet art(artClass)}
{#if coverSrc}
<Cover
trackId={id}
src={coverSrc}
hue={hueFor(id)}
size="large"
alt={title}
class={artClass}
/>
{:else}
<PlaylistArt {trackIds} hue={hueFor(id)} class={artClass} />
{/if}
{/snippet}
</MediaCard>
@@ -20,7 +20,7 @@
</span>
{/if}
{#if title}
<h3 class="mt-5 text-2xl font-bold tracking-tight text-fg">{title}</h3>
<h3 class="mt-5 text-2xl font-semibold tracking-tight text-fg">{title}</h3>
{/if}
{#if description}
<p class="mt-2 text-fg-2">{description}</p>
@@ -0,0 +1,32 @@
<script lang="ts" module>
export const mediaCardArt =
'aspect-square w-full rounded-art shadow-art transition duration-300 ease-out group-hover:-translate-y-1 group-hover:shadow-art-lg';
</script>
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
href: string;
title: string;
subtitle?: string | null;
index?: number;
oncontextmenu?: (event: MouseEvent) => void;
art: Snippet<[string]>;
}
let { href, title, subtitle, index = 0, oncontextmenu, art }: Props = $props();
</script>
<a
{href}
class="group animate-enter min-w-0 text-left"
style="animation-delay:{index * 45}ms"
{oncontextmenu}
>
{@render art(mediaCardArt)}
<div class="mt-2.5 truncate text-fg transition-colors group-hover:text-accent-soft">{title}</div>
{#if subtitle}
<div class="mt-0.5 truncate text-sm text-fg-3">{subtitle}</div>
{/if}
</a>
@@ -11,7 +11,7 @@
let {
as = 'div',
min = '200px',
min = '165px',
minMobile = '150px',
class: klass = '',
children
@@ -1,4 +1,5 @@
<script lang="ts">
import MediaCard from './MediaCard.svelte';
import MixArt from './MixArt.svelte';
import { hueFor } from '$lib/theme/color';
import type { Mix } from '$lib/mixes';
@@ -13,19 +14,13 @@
const count = $derived(Number(mix.itemCount));
</script>
<a
<MediaCard
href="/mixes/{mix.id}"
class="group animate-enter min-w-0 text-left"
style="animation-delay:{index * 45}ms"
title={mix.title}
subtitle="{count} {count === 1 ? 'canción' : 'canciones'}"
{index}
>
<MixArt
items={mix.items}
hue={hueFor(mix.id)}
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
/>
<div class="mt-2.5 truncate text-fg">{mix.title}</div>
<div class="mt-0.5 truncate text-sm text-fg-3">
{count}
{count === 1 ? 'canción' : 'canciones'}
</div>
</a>
{#snippet art(artClass)}
<MixArt items={mix.items} hue={hueFor(mix.id)} class={artClass} />
{/snippet}
</MediaCard>
@@ -82,7 +82,7 @@
>
{#if title}
<div class="flex items-center justify-between">
<h2 class="text-lg font-bold tracking-tight text-fg">{title}</h2>
<h2 class="text-lg font-semibold tracking-tight text-fg">{title}</h2>
<IconButton label="Cerrar" onclick={onClose}>
<X class="h-4 w-4" />
</IconButton>
@@ -13,7 +13,7 @@
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between {klass}">
<div class="min-w-0">
<h1 class="text-2xl font-bold tracking-tight text-fg sm:text-4xl">{title}</h1>
<h1 class="text-2xl font-semibold tracking-tight text-fg sm:text-4xl">{title}</h1>
{#if subtitle}
<p class="mt-2 text-sm text-fg-2 sm:text-base">{subtitle}</p>
{/if}
@@ -1,4 +1,5 @@
<script lang="ts">
import MediaCard from './MediaCard.svelte';
import PlaylistArt from './PlaylistArt.svelte';
import { hueFor } from '$lib/theme/color';
@@ -14,20 +15,8 @@
let { id, name, description, trackIds, updatedAt, index = 0 }: Props = $props();
</script>
<a
href="/playlists/{id}"
class="group animate-enter min-w-0 text-left"
style="animation-delay:{index * 45}ms"
>
<PlaylistArt
playlistId={id}
{trackIds}
hue={hueFor(id)}
version={updatedAt}
class="aspect-square w-full rounded-art shadow-art transition group-hover:shadow-art-lg"
/>
<div class="mt-2.5 truncate text-fg">{name}</div>
{#if description}
<div class="mt-0.5 truncate text-sm text-fg-3">{description}</div>
{/if}
</a>
<MediaCard href="/playlists/{id}" title={name} subtitle={description} {index}>
{#snippet art(artClass)}
<PlaylistArt playlistId={id} {trackIds} hue={hueFor(id)} version={updatedAt} class={artClass} />
{/snippet}
</MediaCard>
@@ -11,10 +11,12 @@
</script>
{#if actions}
<div class="mb-4 flex items-center justify-between gap-4 sm:mb-6 {klass}">
<h2 class="text-xl font-bold text-fg sm:text-2xl">{title}</h2>
<div class="mb-3 flex items-baseline justify-between gap-4 sm:mb-4 {klass}">
<h2 class="text-lg font-semibold tracking-tight text-fg sm:text-xl">{title}</h2>
{@render actions()}
</div>
{:else}
<h2 class="mb-4 text-xl font-bold text-fg sm:mb-6 sm:text-2xl {klass}">{title}</h2>
<h2 class="mb-3 text-lg font-semibold tracking-tight text-fg sm:mb-4 sm:text-xl {klass}">
{title}
</h2>
{/if}
@@ -77,7 +77,7 @@
</Modal>
<Modal open={confirmingDelete} onClose={() => (confirmingDelete = false)} maxWidth="max-w-sm">
<h2 class="text-lg font-bold tracking-tight text-fg">
<h2 class="text-lg font-semibold tracking-tight text-fg">
¿Eliminar «{album.title}»?
</h2>
<p class="mt-2 text-base text-fg-3">
@@ -64,7 +64,7 @@
<div class="min-w-0 flex-1">
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Álbum</p>
<h1
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
>
{title}
</h1>
@@ -103,7 +103,7 @@
{album.isSingle ? 'Single' : album.isEp ? 'EP' : 'Álbum'} · YouTube Music
</p>
<h1
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
>
{album.title}
</h1>
@@ -85,7 +85,7 @@
<div class="min-w-0 flex-1">
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Mezcla</p>
<h1
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
>
{mix.title}
</h1>
@@ -79,7 +79,7 @@
</Modal>
<Modal open={confirmingDelete} onClose={() => (confirmingDelete = false)} maxWidth="max-w-sm">
<h2 class="text-lg font-bold tracking-tight text-fg">
<h2 class="text-lg font-semibold tracking-tight text-fg">
¿Eliminar «{playlist.name}»?
</h2>
<p class="mt-2 text-base text-fg-3">
@@ -46,7 +46,7 @@
<div class="min-w-0 flex-1">
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Lista</p>
<h1
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
class="mt-1.5 text-3xl font-semibold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
>
{name}
</h1>
@@ -141,7 +141,7 @@
>
<Check class="h-8 w-8" strokeWidth={2.5} />
</span>
<h2 class="mt-6 text-2xl font-bold tracking-tight text-fg">¡Subida!</h2>
<h2 class="mt-6 text-2xl font-semibold tracking-tight text-fg">¡Subida!</h2>
<p class="mt-3 text-fg-2">
«{publishedTitle}» se está procesando. Aparecerá lista para reproducir en tu biblioteca en
unos momentos.
+6 -5
View File
@@ -178,14 +178,14 @@
></div>
<div class="animate-enter relative flex flex-col gap-2 sm:gap-2.5">
<p class="text-sm tracking-[0.14em] text-fg-2 uppercase sm:text-base">{data.greeting}</p>
<h1 class="max-w-160 text-3xl leading-[1.05] font-extrabold text-fg sm:text-5xl">
<h1 class="max-w-160 text-3xl leading-[1.05] font-semibold text-fg sm:text-5xl">
Tu música. Sin límites.
</h1>
</div>
</section>
{#if recentlyPlayed.length > 0}
<section class="page-x pt-5 pb-3 sm:pt-6">
<section class="page-x pt-10 sm:pt-14">
<SectionHeading title="Volver a escuchar" />
<MediaGrid min="280px" minMobile="220px">
{#each recentlyPlayed as track, i (track.id)}
@@ -228,7 +228,7 @@
{/if}
{#if mixes.length > 0 || playlists.length > 0}
<section class="grid gap-x-8 gap-y-5 page-x pt-5 pb-3 sm:pt-6 lg:grid-cols-2">
<section class="grid gap-x-8 gap-y-10 page-x pt-10 sm:pt-14 lg:grid-cols-2">
{#if mixes.length > 0}
<div class="min-w-0">
<SectionHeading title="Hechas para ti" />
@@ -269,7 +269,7 @@
{/if}
{#if albums.length > 0}
<section class="page-x pt-5 pb-3 sm:pt-6">
<section class="page-x pt-10 sm:pt-14">
<SectionHeading title="Álbumes escuchados recientemente">
{#snippet actions()}
<a href="/albums" class="text-sm text-fg-3 transition hover:text-accent-soft">Ver todos</a>
@@ -290,7 +290,7 @@
</section>
{/if}
<section class="page-x pt-5 pb-10 sm:pt-6">
<section class="page-x pt-10 pb-16 sm:pt-14">
<SectionHeading title="Descubrir" />
{#if latest.length > 0}
<MediaGrid as="ul">
@@ -299,6 +299,7 @@
id={targetId(item)}
title={targetTitle(item)}
artist={targetArtist(item)}
coverShape="round"
coverSrc={targetCoverSrc(item)}
hue={hueFor(targetId(item))}
explicit={targetExplicit(item)}
+2 -2
View File
@@ -13,10 +13,10 @@
<div class="grid min-h-screen place-items-center bg-bg px-6 text-fg">
<div class="flex w-full max-w-110 flex-col items-center gap-9 text-center">
<div class="text-3xl font-extrabold tracking-tight">Musify</div>
<div class="text-3xl font-semibold tracking-tight">Musify</div>
<div>
<h1 class="text-4xl font-extrabold tracking-tight">Tu música. Sin límites.</h1>
<h1 class="text-4xl font-semibold tracking-tight">Tu música. Sin límites.</h1>
<p class="mt-3 text-base text-fg-2">Inicia sesión para continuar.</p>
</div>