Optimized frontend
This commit is contained in:
2
app.py
2
app.py
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user