v3: localization fixes, char counters & limits on all text fields, ux fixes

This commit is contained in:
khannurien
2026-04-03 19:47:37 +00:00
parent 0ce80398a4
commit a69788c15b
48 changed files with 1133 additions and 305 deletions

View File

@@ -6,9 +6,10 @@ import {
useState,
} from "react";
import { Link, useNavigate, useParams } from "react-router";
import { t } from "@lingui/core/macro"
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { API_URL } from "../config/api.ts";
import { API_URL, VALIDATION } from "../config/api.ts";
import { CountedInput } from "../components/CountedInput.tsx";
import type {
PlaylistWithDumps,
RawDump,
@@ -527,7 +528,10 @@ export function PlaylistDetail() {
};
const handleEditSave = async () => {
if (!playlistId || state.status !== "loaded") return;
if (
!playlistId || state.status !== "loaded" ||
editDescription.length > VALIDATION.PLAYLIST_DESCRIPTION_MAX
) return;
setEditSaving(true);
setEditError(null);
try {
@@ -587,7 +591,9 @@ export function PlaylistDetail() {
if (state.status === "loading") {
return (
<PageShell>
<p className="page-loading"><Trans>Loading playlist</Trans></p>
<p className="page-loading">
<Trans>Loading playlist</Trans>
</p>
</PageShell>
);
}
@@ -649,17 +655,19 @@ export function PlaylistDetail() {
{editOpen
? (
<div className="playlist-detail-title-row">
<input
type="text"
<CountedInput
className="playlist-edit-input"
value={editTitle}
onChange={(e) => setEditTitle(e.target.value)}
autoFocus
maxLength={VALIDATION.PLAYLIST_TITLE_MAX}
/>
<button
type="button"
className="btn-primary"
disabled={editSaving}
disabled={editSaving ||
editDescription.length >
VALIDATION.PLAYLIST_DESCRIPTION_MAX}
onClick={handleEditSave}
>
{editSaving ? <Trans>Saving</Trans> : <Trans>Save</Trans>}
@@ -710,6 +718,7 @@ export function PlaylistDetail() {
placeholder={t`Description (optional)`}
autoResize
rows={1}
maxLength={VALIDATION.PLAYLIST_DESCRIPTION_MAX}
/>
)
: playlist.description && (
@@ -745,7 +754,9 @@ export function PlaylistDetail() {
playlist.isPublic ? "" : " playlist-badge--private"
}`}
>
{playlist.isPublic ? <Trans>public</Trans> : <Trans>private</Trans>}
{playlist.isPublic
? <Trans>public</Trans>
: <Trans>private</Trans>}
</span>
{playlist.ownerUsername && (
<Link
@@ -765,7 +776,9 @@ export function PlaylistDetail() {
text={t`Edited ${playlist.updatedAt.toLocaleString()}`}
>
<span className="playlist-edited-label">
<Trans>edited {relativeTime(playlist.updatedAt)}</Trans>
<Trans>
edited {relativeTime(playlist.updatedAt)}
</Trans>
</span>
</Tooltip>
)}
@@ -780,13 +793,15 @@ export function PlaylistDetail() {
</div>
{visibleDumps.length === 0
? <p className="empty-state"><Trans>No dumps in this playlist yet.</Trans></p>
? (
<p className="empty-state">
<Trans>No dumps in this playlist yet.</Trans>
</p>
)
: (
<div
className="playlist-dump-list"
onDragOver={isOwner
? (e) => e.preventDefault()
: undefined}
onDragOver={isOwner ? (e) => e.preventDefault() : undefined}
>
{visibleDumps.map((dump) => {
const isActive = activeDumpIds.has(dump.id);