Enhance authentication flow with improved error handling and logging in requireAuthHook. Refine HLS stream initialization and metadata fetching in AudioPlayer to handle station changes gracefully. Improve toast notifications and autocomplete behavior in LyricSearch. Simplify RandomMsg logic and remove unused imports. Add track and album count display in MediaRequestForm and enhance artist selection. Introduce dark mode styles for tables and dialogs in RequestManagement.css. Adjust imports and ensure proper usage of requireAuthHook in index.astro and requests.astro.

This commit is contained in:
2025-09-22 11:15:24 -04:00
parent 3afc944a67
commit f177315231
8 changed files with 273 additions and 54 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, Suspense, lazy } from "react";
import React, { useState, useEffect, useRef, Suspense, lazy, useMemo } from "react";
import { toast } from "react-toastify";
import { Button } from "@mui/joy";
import { Accordion, AccordionTab } from "primereact/accordion";
@@ -62,7 +62,7 @@ export default function MediaRequestForm() {
searchArtists.lastCall = Date.now();
const res = await authFetch(
`${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent(query)}`
`${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent(query)}&group=true`
);
if (!res.ok) throw new Error("API error");
const data = await res.json();
@@ -82,9 +82,89 @@ export default function MediaRequestForm() {
? text
: text.slice(0, maxLen - 3) + '...';
const selectArtist = (artist) => {
// artist may be a grouped item or an alternate object
const value = artist.artist || artist.name || "";
setSelectedArtist(artist);
setArtistInput(value);
setArtistSuggestions([]);
// Hide autocomplete panel and blur input to prevent leftover white box.
// Use a small timeout so PrimeReact internal handlers can finish first.
try {
const ac = autoCompleteRef.current;
// Give PrimeReact a tick to finish its events, then call hide().
setTimeout(() => {
try {
if (ac && typeof ac.hide === "function") {
ac.hide();
}
// If panel still exists, hide it via style (safer than removing the node)
setTimeout(() => {
const panel = document.querySelector('.p-autocomplete-panel');
if (panel) {
panel.style.display = 'none';
panel.style.opacity = '0';
panel.style.visibility = 'hidden';
panel.style.pointerEvents = 'none';
}
}, 10);
// blur the input element if present
const inputEl = ac?.getInput ? ac.getInput() : document.querySelector('.p-autocomplete-input');
if (inputEl && typeof inputEl.blur === 'function') inputEl.blur();
} catch (innerErr) {
console.debug('selectArtist: overlay hide fallback failed', innerErr);
}
}, 0);
} catch (err) {
console.debug('selectArtist: unable to schedule overlay hide', err);
}
};
const totalAlbums = albums.length;
const totalTracks = useMemo(() =>
Object.values(tracksByAlbum).reduce((sum, arr) => (Array.isArray(arr) ? sum + arr.length : sum), 0),
[tracksByAlbum]
);
const artistItemTemplate = (artist) => {
if (!artist) return null;
return <div>{truncate(artist.artist, 58)}</div>;
const alts = artist.alternatives || artist.alternates || [];
return (
<div className="py-1">
<div className="font-medium flex items-baseline gap-2">
<span>{truncate(artist.artist || artist.name, 58)}</span>
<span className="text-xs text-neutral-400">ID: {artist.id}</span>
</div>
{alts.length > 0 && (
<div className="text-xs text-neutral-500 mt-1">
<div className="flex flex-wrap gap-2 items-center">
<span className="mr-1">Alternates:</span>
{alts.map((alt, idx) => (
<span key={alt.id || idx} className="inline-flex items-center max-w-[200px] truncate">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
selectArtist({ ...alt, artist: alt.artist || alt.name });
}}
className="ml-1 mr-1 underline hover:text-blue-600 bg-transparent border-none p-0 cursor-pointer"
>
{truncate(alt.artist || alt.name, 26)}
</button>
<span className="text-xs text-neutral-400">ID: {alt.id}</span>
{idx < alts.length - 1 ? <span className="mx-1">,</span> : null}
</span>
))}
</div>
</div>
)}
</div>
);
};
@@ -553,19 +633,25 @@ export default function MediaRequestForm() {
{type === "artist" && albums.length > 0 && (
<>
<div className="flex justify-end mb-2">
<a
href="#"
role="button"
onClick={(e) => {
e.preventDefault(); // prevent page jump
handleToggleAllAlbums();
}}
className="text-sm text-blue-600 hover:underline cursor-pointer"
>
Check / Uncheck All Albums
</a>
<div className="flex justify-between items-center mb-2">
<div className="text-sm text-neutral-600 dark:text-neutral-400">
<strong className="mr-2">Albums:</strong> {totalAlbums}
<span className="mx-3">|</span>
<strong className="mr-2">Tracks:</strong> {totalTracks}
</div>
<div>
<a
href="#"
role="button"
onClick={(e) => {
e.preventDefault(); // prevent page jump
handleToggleAllAlbums();
}}
className="text-sm text-blue-600 hover:underline cursor-pointer"
>
Check / Uncheck All Albums
</a>
</div>
</div>
<Accordion
multiple
@@ -588,7 +674,7 @@ export default function MediaRequestForm() {
<AccordionTab
key={id}
header={
<div className="flex items-center gap-2">
<div className="flex items-center gap-2 w-full">
<input
type="checkbox"
checked={allChecked}
@@ -605,6 +691,13 @@ export default function MediaRequestForm() {
{loadingAlbumId === id && <Spinner />}
</span>
<small className="ml-2 text-neutral-500 dark:text-neutral-400">({release_date})</small>
<span className="ml-auto text-xs text-neutral-500">
{typeof tracksByAlbum[id] === 'undefined' ? (
loadingAlbumId === id ? 'Loading...' : '...'
) : (
`${allTracks.length} track${allTracks.length !== 1 ? 's' : ''}`
)}
</span>
</div>
}