Optimized frontend

This commit is contained in:
Xoconoch
2025-08-05 12:12:14 -06:00
parent 9b8cb025b2
commit 7f8f634348
5 changed files with 117 additions and 28 deletions

2
app.py
View File

@@ -167,7 +167,7 @@ def create_app():
app = FastAPI( app = FastAPI(
title="Spotizerr API", title="Spotizerr API",
description="Music download service API", description="Music download service API",
version="1.0.0", version="3.0.0",
lifespan=lifespan, lifespan=lifespan,
redirect_slashes=True # Enable automatic trailing slash redirects redirect_slashes=True # Enable automatic trailing slash redirects
) )

View File

@@ -1,7 +1,7 @@
{ {
"name": "spotizerr-ui", "name": "spotizerr-ui",
"private": true, "private": true,
"version": "0.0.0", "version": "3.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,16 +1,18 @@
import { createRouter, createRootRoute, createRoute } from "@tanstack/react-router"; import { createRouter, createRootRoute, createRoute, lazyRouteComponent } from "@tanstack/react-router";
import Root from "./routes/root"; import Root from "./routes/root";
import { Album } from "./routes/album";
import { Artist } from "./routes/artist";
import { Track } from "./routes/track";
import { Home } from "./routes/home";
import { Config } from "./routes/config";
import { Playlist } from "./routes/playlist";
import { History } from "./routes/history";
import { Watchlist } from "./routes/watchlist";
import apiClient from "./lib/api-client"; import apiClient from "./lib/api-client";
import type { SearchResult } from "./types/spotify"; import type { SearchResult } from "./types/spotify";
// Lazy load route components for code splitting
const Album = lazyRouteComponent(() => import("./routes/album").then(m => ({ default: m.Album })));
const Artist = lazyRouteComponent(() => import("./routes/artist").then(m => ({ default: m.Artist })));
const Track = lazyRouteComponent(() => import("./routes/track").then(m => ({ default: m.Track })));
const Home = lazyRouteComponent(() => import("./routes/home").then(m => ({ default: m.Home })));
const Config = lazyRouteComponent(() => import("./routes/config").then(m => ({ default: m.Config })));
const Playlist = lazyRouteComponent(() => import("./routes/playlist").then(m => ({ default: m.Playlist })));
const History = lazyRouteComponent(() => import("./routes/history").then(m => ({ default: m.History })));
const Watchlist = lazyRouteComponent(() => import("./routes/watchlist").then(m => ({ default: m.Watchlist })));
const rootRoute = createRootRoute({ const rootRoute = createRootRoute({
component: Root, component: Root,
}); });

View File

@@ -1,17 +1,26 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef, Suspense, lazy } from "react";
import { useSearch } from "@tanstack/react-router"; import { useSearch } from "@tanstack/react-router";
import { GeneralTab } from "../components/config/GeneralTab";
import { DownloadsTab } from "../components/config/DownloadsTab";
import { FormattingTab } from "../components/config/FormattingTab";
import { AccountsTab } from "../components/config/AccountsTab";
import { WatchTab } from "../components/config/WatchTab";
import { ServerTab } from "../components/config/ServerTab";
import { UserManagementTab } from "../components/config/UserManagementTab";
import { ProfileTab } from "../components/config/ProfileTab";
import { useSettings } from "../contexts/settings-context"; import { useSettings } from "../contexts/settings-context";
import { useAuth } from "../contexts/auth-context"; import { useAuth } from "../contexts/auth-context";
import { LoginScreen } from "../components/auth/LoginScreen"; import { LoginScreen } from "../components/auth/LoginScreen";
// Lazy load config tab components for better code splitting
const GeneralTab = lazy(() => import("../components/config/GeneralTab").then(m => ({ default: m.GeneralTab })));
const DownloadsTab = lazy(() => import("../components/config/DownloadsTab").then(m => ({ default: m.DownloadsTab })));
const FormattingTab = lazy(() => import("../components/config/FormattingTab").then(m => ({ default: m.FormattingTab })));
const AccountsTab = lazy(() => import("../components/config/AccountsTab").then(m => ({ default: m.AccountsTab })));
const WatchTab = lazy(() => import("../components/config/WatchTab").then(m => ({ default: m.WatchTab })));
const ServerTab = lazy(() => import("../components/config/ServerTab").then(m => ({ default: m.ServerTab })));
const UserManagementTab = lazy(() => import("../components/config/UserManagementTab").then(m => ({ default: m.UserManagementTab })));
const ProfileTab = lazy(() => import("../components/config/ProfileTab").then(m => ({ default: m.ProfileTab })));
// Loading component for tab transitions
const TabLoading = () => (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
</div>
);
const ConfigComponent = () => { const ConfigComponent = () => {
const { tab } = useSearch({ from: "/config" }); const { tab } = useSearch({ from: "/config" });
const { user, isAuthenticated, authEnabled, isLoading: authLoading } = useAuth(); const { user, isAuthenticated, authEnabled, isLoading: authLoading } = useAuth();
@@ -102,11 +111,19 @@ const ConfigComponent = () => {
const renderTabContent = () => { const renderTabContent = () => {
// User management and profile don't need config data // User management and profile don't need config data
if (activeTab === "user-management") { if (activeTab === "user-management") {
return <UserManagementTab />; return (
<Suspense fallback={<TabLoading />}>
<UserManagementTab />
</Suspense>
);
} }
if (activeTab === "profile") { if (activeTab === "profile") {
return <ProfileTab />; return (
<Suspense fallback={<TabLoading />}>
<ProfileTab />
</Suspense>
);
} }
if (isLoading) return <div className="text-center py-12"><p className="text-content-muted dark:text-content-muted-dark">Loading configuration...</p></div>; if (isLoading) return <div className="text-center py-12"><p className="text-content-muted dark:text-content-muted-dark">Loading configuration...</p></div>;
@@ -114,17 +131,41 @@ const ConfigComponent = () => {
switch (activeTab) { switch (activeTab) {
case "general": case "general":
return <GeneralTab config={config} isLoading={isLoading} />; return (
<Suspense fallback={<TabLoading />}>
<GeneralTab config={config} isLoading={isLoading} />
</Suspense>
);
case "downloads": case "downloads":
return <DownloadsTab config={config} isLoading={isLoading} />; return (
<Suspense fallback={<TabLoading />}>
<DownloadsTab config={config} isLoading={isLoading} />
</Suspense>
);
case "formatting": case "formatting":
return <FormattingTab config={config} isLoading={isLoading} />; return (
<Suspense fallback={<TabLoading />}>
<FormattingTab config={config} isLoading={isLoading} />
</Suspense>
);
case "accounts": case "accounts":
return <AccountsTab />; return (
<Suspense fallback={<TabLoading />}>
<AccountsTab />
</Suspense>
);
case "watch": case "watch":
return <WatchTab />; return (
<Suspense fallback={<TabLoading />}>
<WatchTab />
</Suspense>
);
case "server": case "server":
return <ServerTab />; return (
<Suspense fallback={<TabLoading />}>
<ServerTab />
</Suspense>
);
default: default:
return null; return null;
} }

View File

@@ -99,6 +99,52 @@ export default defineConfig({
"@": resolve(__dirname, "./src"), "@": resolve(__dirname, "./src"),
}, },
}, },
build: {
chunkSizeWarningLimit: 1000, // Increase warning limit to 1MB
rollupOptions: {
output: {
manualChunks: {
// Core React and routing
'react-vendor': ['react', 'react-dom'],
'router-vendor': ['@tanstack/react-router'],
// Query and state management
'query-vendor': ['@tanstack/react-query'],
// UI and icon libraries
'ui-vendor': ['lucide-react', 'react-icons', 'sonner'],
// Table components (only used in specific routes)
'table-vendor': ['@tanstack/react-table'],
// Form handling
'form-vendor': ['react-hook-form', 'use-debounce'],
// HTTP client
'http-vendor': ['axios'],
// Config components (heavy route with many tabs)
'config-components': [
'./src/components/config/GeneralTab',
'./src/components/config/DownloadsTab',
'./src/components/config/FormattingTab',
'./src/components/config/AccountsTab',
'./src/components/config/WatchTab',
'./src/components/config/ServerTab',
'./src/components/config/UserManagementTab',
'./src/components/config/ProfileTab'
],
// Utilities and helpers
'utils-vendor': ['uuid'],
},
// Additional chunk optimization
chunkFileNames: () => {
return `assets/[name]-[hash].js`;
},
},
},
},
server: { server: {
host: true, host: true,
port: 5173, port: 5173,