misc
This commit is contained in:
		@@ -14,7 +14,6 @@ const MediaRequestForm = lazy(() => import('./TRip/MediaRequestForm.jsx'));
 | 
				
			|||||||
const RequestManagement = lazy(() => import('./TRip/RequestManagement.jsx'));
 | 
					const RequestManagement = lazy(() => import('./TRip/RequestManagement.jsx'));
 | 
				
			||||||
const Player = lazy(() => import('./AudioPlayer.jsx'));
 | 
					const Player = lazy(() => import('./AudioPlayer.jsx'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function Root({ child }) {
 | 
					export default function Root({ child }) {
 | 
				
			||||||
  window.toast = toast;
 | 
					  window.toast = toast;
 | 
				
			||||||
  const theme = document.documentElement.getAttribute("data-theme")
 | 
					  const theme = document.documentElement.getAttribute("data-theme")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,6 @@ const STATIONS = {
 | 
				
			|||||||
  rock: { label: "Rock" },
 | 
					  rock: { label: "Rock" },
 | 
				
			||||||
  rap: { label: "Rap" },
 | 
					  rap: { label: "Rap" },
 | 
				
			||||||
  electronic: { label: "Electronic" },
 | 
					  electronic: { label: "Electronic" },
 | 
				
			||||||
  classical: { label: "Classical" },
 | 
					 | 
				
			||||||
  pop: { label: "Pop" },
 | 
					  pop: { label: "Pop" },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,8 +83,8 @@ export default function Player() {
 | 
				
			|||||||
      const hls = new Hls({
 | 
					      const hls = new Hls({
 | 
				
			||||||
        lowLatencyMode: true,
 | 
					        lowLatencyMode: true,
 | 
				
			||||||
        abrEnabled: false,
 | 
					        abrEnabled: false,
 | 
				
			||||||
        liveSyncDuration: 2.5,               // seconds behind live edge target
 | 
					        liveSyncDuration: 1.0,               // seconds behind live edge target
 | 
				
			||||||
        liveMaxLatencyDuration: 3.5,        // max allowed latency before catchup
 | 
					        liveMaxLatencyDuration: 2.0,        // max allowed latency before catchup
 | 
				
			||||||
        liveCatchUpPlaybackRate: 1.05,     // playback speed when catching up
 | 
					        liveCatchUpPlaybackRate: 1.05,     // playback speed when catching up
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -111,8 +110,6 @@ export default function Player() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // Update elapsed time smoothly
 | 
					  // Update elapsed time smoothly
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (!isPlaying) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const intervalId = setInterval(() => {
 | 
					    const intervalId = setInterval(() => {
 | 
				
			||||||
      const now = Date.now();
 | 
					      const now = Date.now();
 | 
				
			||||||
      const deltaSec = (now - lastUpdateTimestamp.current) / 1000;
 | 
					      const deltaSec = (now - lastUpdateTimestamp.current) / 1000;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
import { metaData, API_URL } from "../config";
 | 
					import { metaData, ENVIRONMENT } from "../config";
 | 
				
			||||||
import RandomMsg from "../components/RandomMsg";
 | 
					import RandomMsg from "../components/RandomMsg";
 | 
				
			||||||
import { buildTime, buildNumber } from '../utils/buildTime.js';
 | 
					import { buildTime, buildNumber } from '../utils/buildTime.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const YEAR = new Date().getFullYear();
 | 
					const YEAR = new Date().getFullYear();
 | 
				
			||||||
var ENVIRONMENT = (Astro.url.hostname === "localhost") ? "Dev": "Prod";
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import { Button } from "@mui/joy";
 | 
				
			|||||||
import { Accordion, AccordionTab } from "primereact/accordion";
 | 
					import { Accordion, AccordionTab } from "primereact/accordion";
 | 
				
			||||||
import { AutoComplete } from "primereact/autocomplete";
 | 
					import { AutoComplete } from "primereact/autocomplete";
 | 
				
			||||||
import BreadcrumbNav from "./BreadcrumbNav";
 | 
					import BreadcrumbNav from "./BreadcrumbNav";
 | 
				
			||||||
import { API_URL } from "@/config";
 | 
					import { API_URL, ENVIRONMENT } from "@/config";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function MediaRequestForm() {
 | 
					export default function MediaRequestForm() {
 | 
				
			||||||
    const [type, setType] = useState("artist");
 | 
					    const [type, setType] = useState("artist");
 | 
				
			||||||
@@ -25,6 +25,8 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
    const debounceTimeout = useRef(null);
 | 
					    const debounceTimeout = useRef(null);
 | 
				
			||||||
    const autoCompleteRef = useRef(null);
 | 
					    const autoCompleteRef = useRef(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // Helper for delays
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Helper fetch wrapper that includes cookies automatically
 | 
					    // Helper fetch wrapper that includes cookies automatically
 | 
				
			||||||
    const authFetch = async (url, options = {}) => {
 | 
					    const authFetch = async (url, options = {}) => {
 | 
				
			||||||
        const opts = {
 | 
					        const opts = {
 | 
				
			||||||
@@ -55,10 +57,17 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        debounceTimeout.current = setTimeout(async () => {
 | 
					        debounceTimeout.current = setTimeout(async () => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
 | 
					                // Ensure at least 600ms between actual requests
 | 
				
			||||||
 | 
					                const now = Date.now();
 | 
				
			||||||
 | 
					                if (!searchArtists.lastCall) searchArtists.lastCall = 0;
 | 
				
			||||||
 | 
					                const elapsed = now - searchArtists.lastCall;
 | 
				
			||||||
 | 
					                const minDelay = 600; // ms
 | 
				
			||||||
 | 
					                if (elapsed < minDelay) await delay(minDelay - elapsed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                searchArtists.lastCall = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const res = await authFetch(
 | 
					                const res = await authFetch(
 | 
				
			||||||
                    `${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent(
 | 
					                    `${API_URL}/trip/get_artists_by_name?artist=${encodeURIComponent(query)}`
 | 
				
			||||||
                        query
 | 
					 | 
				
			||||||
                    )}`,
 | 
					 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
                if (!res.ok) throw new Error("API error");
 | 
					                if (!res.ok) throw new Error("API error");
 | 
				
			||||||
                const data = await res.json();
 | 
					                const data = await res.json();
 | 
				
			||||||
@@ -67,9 +76,11 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
                toast.error("Failed to fetch artist suggestions.");
 | 
					                toast.error("Failed to fetch artist suggestions.");
 | 
				
			||||||
                setArtistSuggestions([]);
 | 
					                setArtistSuggestions([]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }, 300);
 | 
					        }, 500); // debounce 500ms
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //helpers for string truncation
 | 
					    //helpers for string truncation
 | 
				
			||||||
    const truncate = (text, maxLen) =>
 | 
					    const truncate = (text, maxLen) =>
 | 
				
			||||||
        maxLen <= 3
 | 
					        maxLen <= 3
 | 
				
			||||||
@@ -205,18 +216,23 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
        if (type !== "artist" || albums.length === 0) return;
 | 
					        if (type !== "artist" || albums.length === 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let isCancelled = false;
 | 
					        let isCancelled = false;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        const albumsToFetch = albums.filter((a) => !tracksByAlbum[a.id]);
 | 
					        const albumsToFetch = albums.filter((a) => !tracksByAlbum[a.id]);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (albumsToFetch.length === 0) return;
 | 
					        if (albumsToFetch.length === 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const fetchTracksSequentially = async () => {
 | 
					        const fetchTracksSequentially = async () => {
 | 
				
			||||||
 | 
					            const minDelay = 600; // ms between API requests
 | 
				
			||||||
            for (const album of albumsToFetch) {
 | 
					            for (const album of albumsToFetch) {
 | 
				
			||||||
                if (isCancelled) break;
 | 
					                if (isCancelled) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                setLoadingAlbumId(album.id);
 | 
					                setLoadingAlbumId(album.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
 | 
					                    const now = Date.now();
 | 
				
			||||||
 | 
					                    if (!fetchTracksSequentially.lastCall) fetchTracksSequentially.lastCall = 0;
 | 
				
			||||||
 | 
					                    const elapsed = now - fetchTracksSequentially.lastCall;
 | 
				
			||||||
 | 
					                    if (elapsed < minDelay) await delay(minDelay - elapsed);
 | 
				
			||||||
 | 
					                    fetchTracksSequentially.lastCall = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    const res = await authFetch(`${API_URL}/trip/get_tracks_by_album_id/${album.id}`);
 | 
					                    const res = await authFetch(`${API_URL}/trip/get_tracks_by_album_id/${album.id}`);
 | 
				
			||||||
                    if (!res.ok) throw new Error("API error");
 | 
					                    if (!res.ok) throw new Error("API error");
 | 
				
			||||||
                    const data = await res.json();
 | 
					                    const data = await res.json();
 | 
				
			||||||
@@ -299,7 +315,12 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
        try {
 | 
					        try {
 | 
				
			||||||
            // Example: simulate submission delay
 | 
					            // Example: simulate submission delay
 | 
				
			||||||
            await new Promise((resolve) => setTimeout(resolve, 1500));
 | 
					            await new Promise((resolve) => setTimeout(resolve, 1500));
 | 
				
			||||||
            toast.success("Request submitted!");
 | 
					            const allSelectedIds = Object.values(selectedTracks)
 | 
				
			||||||
 | 
					                .filter(arr => Array.isArray(arr)) // skip null entries
 | 
				
			||||||
 | 
					                .flat();
 | 
				
			||||||
 | 
					            toast.success(`Request submitted! (${allSelectedIds.length} tracks)`);
 | 
				
			||||||
 | 
					            console.debug("Requested: ", selectedTracks);
 | 
				
			||||||
 | 
					            console.debug("Flattened: ", allSelectedIds);
 | 
				
			||||||
        } catch (err) {
 | 
					        } catch (err) {
 | 
				
			||||||
            toast.error("Failed to submit request.");
 | 
					            toast.error("Failed to submit request.");
 | 
				
			||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
@@ -310,6 +331,7 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="max-w-3xl mx-auto my-10 p-6 rounded-xl shadow-md bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700">
 | 
					        <div className="max-w-3xl mx-auto my-10 p-6 rounded-xl shadow-md bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-100 border border-neutral-200 dark:border-neutral-700">
 | 
				
			||||||
            <style>{`
 | 
					            <style>{`
 | 
				
			||||||
 | 
					                        /* Accordion tab backgrounds & text */
 | 
				
			||||||
                        .p-accordion-tab {
 | 
					                        .p-accordion-tab {
 | 
				
			||||||
                            background-color: #ffffff;
 | 
					                            background-color: #ffffff;
 | 
				
			||||||
                            color: #000000;
 | 
					                            color: #000000;
 | 
				
			||||||
@@ -318,14 +340,65 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
                            background-color: #1e1e1e;
 | 
					                            background-color: #1e1e1e;
 | 
				
			||||||
                            color: #ffffff;
 | 
					                            color: #ffffff;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Accordion header link */
 | 
				
			||||||
 | 
					                        .p-accordion-header .p-accordion-header-link {
 | 
				
			||||||
 | 
					                            background-color: #f9f9f9;
 | 
				
			||||||
 | 
					                            color: #000000;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                        [data-theme="dark"] .p-accordion-header .p-accordion-header-link {
 | 
					                        [data-theme="dark"] .p-accordion-header .p-accordion-header-link {
 | 
				
			||||||
                            background-color: #1e1e1e !important;
 | 
					                            background-color: #1e1e1e !important;
 | 
				
			||||||
                            color: #ffffff !important;
 | 
					                            color: #ffffff !important;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Accordion content panel */
 | 
				
			||||||
 | 
					                        .p-accordion-content {
 | 
				
			||||||
 | 
					                            background-color: #fafafa;
 | 
				
			||||||
 | 
					                            color: #000000;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                        [data-theme="dark"] .p-accordion-content {
 | 
					                        [data-theme="dark"] .p-accordion-content {
 | 
				
			||||||
                            background-color: #2a2a2a;
 | 
					                            background-color: #2a2a2a;
 | 
				
			||||||
                            color: #ffffff;
 | 
					                            color: #ffffff;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Track list UL/LI styling */
 | 
				
			||||||
 | 
					                        .p-accordion-content ul {
 | 
				
			||||||
 | 
					                            padding-left: 0;
 | 
				
			||||||
 | 
					                            margin: 0;
 | 
				
			||||||
 | 
					                            list-style: none;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        [data-theme="dark"] .p-accordion-content ul {
 | 
				
			||||||
 | 
					                            color: #ffffff;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Track items */
 | 
				
			||||||
 | 
					                        .p-accordion-content li {
 | 
				
			||||||
 | 
					                            background-color: #fff;
 | 
				
			||||||
 | 
					                            border-bottom: 1px solid #e5e5e5;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        [data-theme="dark"] .p-accordion-content li {
 | 
				
			||||||
 | 
					                            background-color: #2a2a2a;
 | 
				
			||||||
 | 
					                            border-bottom: 1px solid #444;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Checkboxes inside track list */
 | 
				
			||||||
 | 
					                        .p-accordion-content input[type="checkbox"] {
 | 
				
			||||||
 | 
					                            accent-color: #1d4ed8; /* optional for consistent dark mode styling */
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Loading spinner (optional darker style) */
 | 
				
			||||||
 | 
					                        [data-theme="dark"] .animate-spin {
 | 
				
			||||||
 | 
					                            border-color: #555;
 | 
				
			||||||
 | 
					                            border-top-color: #1d4ed8;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        /* Small text like audio quality, duration, version */
 | 
				
			||||||
 | 
					                        .p-accordion-content span {
 | 
				
			||||||
 | 
					                            color: #555;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        [data-theme="dark"] .p-accordion-content span {
 | 
				
			||||||
 | 
					                            color: #aaa;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
        `}</style>
 | 
					        `}</style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <BreadcrumbNav currentPage="request" />
 | 
					            <BreadcrumbNav currentPage="request" />
 | 
				
			||||||
@@ -369,6 +442,7 @@ export default function MediaRequestForm() {
 | 
				
			|||||||
                        field="artist"
 | 
					                        field="artist"
 | 
				
			||||||
                        completeMethod={searchArtists}
 | 
					                        completeMethod={searchArtists}
 | 
				
			||||||
                        onChange={handleArtistChange}
 | 
					                        onChange={handleArtistChange}
 | 
				
			||||||
 | 
					                        minLength={3}
 | 
				
			||||||
                        placeholder="Artist"
 | 
					                        placeholder="Artist"
 | 
				
			||||||
                        dropdown
 | 
					                        dropdown
 | 
				
			||||||
                        className="w-full"
 | 
					                        className="w-full"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,3 +14,4 @@ export const socialLinks = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const MAJOR_VERSION = "0.0"
 | 
					export const MAJOR_VERSION = "0.0"
 | 
				
			||||||
export const RELEASE_FLAG = "Alpha";
 | 
					export const RELEASE_FLAG = "Alpha";
 | 
				
			||||||
 | 
					export const ENVIRONMENT = import.meta.env.DEV ? "Dev" : "Prod";
 | 
				
			||||||
@@ -1,8 +1,9 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
import MediaRequestForm from "@/components/qs2/MediaRequestForm"
 | 
					import MediaRequestForm from "@/components/TRip/MediaRequestForm"
 | 
				
			||||||
import Base from "@/layouts/Base.astro";
 | 
					import Base from "@/layouts/Base.astro";
 | 
				
			||||||
import Root from "@/components/AppLayout.jsx";
 | 
					import Root from "@/components/AppLayout.jsx";
 | 
				
			||||||
import { verifyToken } from "@/utils/jwt";
 | 
					import { verifyToken } from "@/utils/jwt";
 | 
				
			||||||
 | 
					import { ENVIRONMENT } from "@/config";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const token = Astro.cookies.get("access_token")?.value;
 | 
					const token = Astro.cookies.get("access_token")?.value;
 | 
				
			||||||
let user = null;
 | 
					let user = null;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,16 @@ import MediaRequestForm from "@/components/TRip/MediaRequestForm"
 | 
				
			|||||||
import Base from "@/layouts/Base.astro";
 | 
					import Base from "@/layouts/Base.astro";
 | 
				
			||||||
import Root from "@/components/AppLayout.jsx";
 | 
					import Root from "@/components/AppLayout.jsx";
 | 
				
			||||||
import { verifyToken } from "@/utils/jwt";
 | 
					import { verifyToken } from "@/utils/jwt";
 | 
				
			||||||
 | 
					import { ENVIRONMENT } from "@/config";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const token = Astro.cookies.get("access_token")?.value;
 | 
					const token = Astro.cookies.get("access_token")?.value;
 | 
				
			||||||
let user = null;
 | 
					let user = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try {
 | 
					try {
 | 
				
			||||||
 | 
					  const IS_DEV = (ENVIRONMENT==="Dev");
 | 
				
			||||||
  if (token) {
 | 
					  if (token) {
 | 
				
			||||||
    user = verifyToken(token);
 | 
					    user = verifyToken(token);
 | 
				
			||||||
    if (user) {
 | 
					    if (IS_DEV) {
 | 
				
			||||||
      console.log("Verified!", user);
 | 
					      console.log("Verified!", user);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw Error("Authentication required");
 | 
					      throw Error("Authentication required");
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user