optimization

This commit is contained in:
g
2026-01-11 07:35:52 +00:00
parent 027cc49430
commit 0fba981bca
8 changed files with 339 additions and 289 deletions

View File

@@ -5,6 +5,14 @@ export default function Document() {
<Html lang="en"> <Html lang="en">
<Head> <Head>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
{/* Font optimization */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
{/* Audio preload */}
<link rel="preload" href="/notification.mp3" as="audio" type="audio/mpeg" /> <link rel="preload" href="/notification.mp3" as="audio" type="audio/mpeg" />
</Head> </Head>
<body> <body>

View File

@@ -1,12 +1,13 @@
"use client"; "use client";
import { useState } from "react"; import { useState, useMemo } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Search, Filter, Eye, ChevronLeft, ChevronRight } from "lucide-react"; import { Search, Filter, Eye, ChevronLeft, ChevronRight } from "lucide-react";
import { FixedSizeList as List } from 'react-window';
import OrderDetailsModal from "./OrderDetailsModal"; import OrderDetailsModal from "./OrderDetailsModal";
interface Order { interface Order {
@@ -57,30 +58,85 @@ const getStatusStyle = (status: string) => {
* This component should only be used in admin contexts * This component should only be used in admin contexts
*/ */
export default function OrdersTable({ orders, enableModal = true }: OrdersTableProps) { export default function OrdersTable({ orders, enableModal = true }: OrdersTableProps) {
const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all");
const [selectedOrderId, setSelectedOrderId] = useState<number | string | null>(null); const [currentPage, setCurrentPage] = useState(1);
const [selectedOrderId, setSelectedOrderId] = useState<string | number | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const itemsPerPage = 10;
const itemsPerPage = 20;
// Filter orders based on search and status // Filter orders based on search and status
const filteredOrders = orders.filter(order => { const filteredOrders = useMemo(() => {
const matchesSearch = order.orderId.toString().toLowerCase().includes(searchTerm.toLowerCase()) || return orders.filter((order) => {
order.userId.toLowerCase().includes(searchTerm.toLowerCase()) || const matchesSearch = searchTerm === "" ||
(order.vendorUsername && order.vendorUsername.toLowerCase().includes(searchTerm.toLowerCase())); order.orderId.toString().toLowerCase().includes(searchTerm.toLowerCase()) ||
order.userId.toLowerCase().includes(searchTerm.toLowerCase()) ||
(order.vendorUsername && order.vendorUsername.toLowerCase().includes(searchTerm.toLowerCase()));
const matchesStatus = statusFilter === "all" || order.status === statusFilter; const matchesStatus = statusFilter === "all" || order.status === statusFilter;
return matchesSearch && matchesStatus; return matchesSearch && matchesStatus;
}); });
}, [orders, searchTerm, statusFilter]);
// Calculate pagination // Pagination calculations
const totalPages = Math.ceil(filteredOrders.length / itemsPerPage); const totalPages = Math.ceil(filteredOrders.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage; const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage; const endIndex = startIndex + itemsPerPage;
const currentOrders = filteredOrders.slice(startIndex, endIndex); const currentOrders = filteredOrders.slice(startIndex, endIndex);
// Virtual scrolling row renderer
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const order = currentOrders[index];
if (!order) return null;
return (
<div style={style}>
<TableRow>
<TableCell className="font-medium">{order.orderId}</TableCell>
<TableCell>{order.userId}</TableCell>
<TableCell>{order.vendorUsername || 'N/A'}</TableCell>
<TableCell className="max-w-[200px] truncate">
{order.items.length > 0 ? order.items[0].name : 'No items'}
</TableCell>
<TableCell>£{order.total.toFixed(2)}</TableCell>
<TableCell>
<div className={`px-3 py-1 rounded-full border ${getStatusStyle(order.status)}`}>
{order.status.toUpperCase()}
</div>
</TableCell>
<TableCell>N/A</TableCell>
<TableCell>{new Date(order.createdAt).toLocaleDateString()}</TableCell>
<TableCell className="text-right">
{enableModal ? (
<Button
variant="outline"
size="sm"
onClick={() => handleViewOrder(order.orderId)}
title="View order details (Admin only)"
>
<Eye className="h-4 w-4" />
</Button>
) : (
<Button
variant="outline"
size="sm"
disabled
title="Order details modal disabled"
>
<Eye className="h-4 w-4" />
</Button>
)}
</TableCell>
</TableRow>
</div>
);
};
// Determine if we should use virtual scrolling (for performance with large datasets)
const useVirtualScrolling = currentOrders.length > 50;
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
setCurrentPage(page); setCurrentPage(page);
}; };
@@ -152,47 +208,58 @@ export default function OrdersTable({ orders, enableModal = true }: OrdersTableP
<TableHead className="text-right">Actions</TableHead> <TableHead className="text-right">Actions</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> {useVirtualScrolling ? (
{currentOrders.map((order) => ( <List
<TableRow key={order.orderId}> height={400}
<TableCell className="font-medium">{order.orderId}</TableCell> itemCount={currentOrders.length}
<TableCell>{order.userId}</TableCell> itemSize={60}
<TableCell>{order.vendorUsername || 'N/A'}</TableCell> className="border"
<TableCell className="max-w-[200px] truncate"> >
{order.items.length > 0 ? order.items[0].name : 'No items'} {Row}
</TableCell> </List>
<TableCell>£{order.total.toFixed(2)}</TableCell> ) : (
<TableCell> <TableBody>
<div className={`px-3 py-1 rounded-full border ${getStatusStyle(order.status)}`}> {currentOrders.map((order) => (
{order.status.toUpperCase()} <TableRow key={order.orderId}>
</div> <TableCell className="font-medium">{order.orderId}</TableCell>
</TableCell> <TableCell>{order.userId}</TableCell>
<TableCell>N/A</TableCell> <TableCell>{order.vendorUsername || 'N/A'}</TableCell>
<TableCell>{new Date(order.createdAt).toLocaleDateString()}</TableCell> <TableCell className="max-w-[200px] truncate">
<TableCell className="text-right"> {order.items.length > 0 ? order.items[0].name : 'No items'}
{enableModal ? ( </TableCell>
<Button <TableCell>£{order.total.toFixed(2)}</TableCell>
variant="outline" <TableCell>
size="sm" <div className={`px-3 py-1 rounded-full border ${getStatusStyle(order.status)}`}>
onClick={() => handleViewOrder(order.orderId)} {order.status.toUpperCase()}
title="View order details (Admin only)" </div>
> </TableCell>
<Eye className="h-4 w-4" /> <TableCell>N/A</TableCell>
</Button> <TableCell>{new Date(order.createdAt).toLocaleDateString()}</TableCell>
) : ( <TableCell className="text-right">
<Button {enableModal ? (
variant="outline" <Button
size="sm" variant="outline"
disabled size="sm"
title="Order details modal disabled" onClick={() => handleViewOrder(order.orderId)}
> title="View order details (Admin only)"
<Eye className="h-4 w-4" /> >
</Button> <Eye className="h-4 w-4" />
)} </Button>
</TableCell> ) : (
</TableRow> <Button
))} variant="outline"
</TableBody> size="sm"
disabled
title="Order details modal disabled"
>
<Eye className="h-4 w-4" />
</Button>
)}
</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table> </Table>
{/* Pagination */} {/* Pagination */}

View File

@@ -47,7 +47,8 @@ import { DateRange } from "react-day-picker";
import { addDays, startOfDay, endOfDay } from "date-fns"; import { addDays, startOfDay, endOfDay } from "date-fns";
import type { DateRange as ProfitDateRange } from "@/lib/services/profit-analytics-service"; import type { DateRange as ProfitDateRange } from "@/lib/services/profit-analytics-service";
// Lazy load chart components // Lazy load chart components - already handled individually below
const RevenueChart = dynamic(() => import("./RevenueChart"), { const RevenueChart = dynamic(() => import("./RevenueChart"), {
loading: () => <ChartSkeleton />, loading: () => <ChartSkeleton />,
}); });

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect, memo, useMemo, useCallback } from "react";
import { import {
Card, Card,
CardContent, CardContent,

View File

@@ -5,8 +5,8 @@ const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'tr
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const baseConfig = { const baseConfig = {
output: 'standalone', output: 'standalone',
reactStrictMode: false, // Enable compression for better performance
compress: process.env.NODE_ENV === 'production', compress: true,
images: { images: {
remotePatterns: [ remotePatterns: [
{ {
@@ -58,10 +58,10 @@ const baseConfig = {
transform: 'date-fns/{{member}}', transform: 'date-fns/{{member}}',
}, },
}, },
// Webpack config (fallback if using --webpack flag) // Enhanced webpack config for better bundle splitting
webpack: (config, { isServer, dev }) => { webpack: (config, { isServer, dev }) => {
if (!isServer && !dev) { if (!isServer && !dev) {
// Only apply aggressive code splitting in production // Enhanced code splitting for better performance
config.optimization = { config.optimization = {
...config.optimization, ...config.optimization,
moduleIds: 'deterministic', moduleIds: 'deterministic',
@@ -70,41 +70,62 @@ const baseConfig = {
cacheGroups: { cacheGroups: {
default: false, default: false,
vendors: false, vendors: false,
recharts: { // Separate React and related libraries
test: /[\\/]node_modules[\\/]recharts[\\/]/, react: {
name: 'recharts', test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
name: 'react',
chunks: 'all', chunks: 'all',
priority: 30, priority: 50,
enforce: true,
}, },
// Separate UI library components
radix: { radix: {
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/, test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
name: 'radix-ui', name: 'radix-ui',
chunks: 'all', chunks: 'all',
priority: 40,
},
// Separate charting libraries
charts: {
test: /[\\/]node_modules[\\/](recharts|d3-.*)[\\/]/,
name: 'charts',
chunks: 'all',
priority: 35,
},
// Separate icon libraries
icons: {
test: /[\\/]node_modules[\\/]lucide-react[\\/]/,
name: 'icons',
chunks: 'all',
priority: 30,
},
// Separate date utilities
dateUtils: {
test: /[\\/]node_modules[\\/]date-fns[\\/]/,
name: 'date-utils',
chunks: 'all',
priority: 25, priority: 25,
}, },
reactMarkdown: { // Separate form libraries
test: /[\\/]node_modules[\\/]react-markdown[\\/]/, forms: {
name: 'react-markdown', test: /[\\/]node_modules[\\/](react-hook-form|@hookform)[\\/]/,
name: 'forms',
chunks: 'all', chunks: 'all',
priority: 20, priority: 20,
}, },
dateFns: { // Group remaining vendor libraries
test: /[\\/]node_modules[\\/]date-fns[\\/]/, vendor: {
name: 'date-fns', test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all', chunks: 'all',
priority: 15, priority: 10,
},
framework: {
name: 'framework',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
priority: 40,
enforce: true, enforce: true,
}, },
commons: { // Common application code
name: 'commons', common: {
minChunks: 2, name: 'common',
priority: 10, minChunks: 3,
priority: 5,
}, },
}, },
}, },
@@ -112,10 +133,6 @@ const baseConfig = {
} }
return config; return config;
}, },
onDemandEntries: {
maxInactiveAge: 15 * 1000,
pagesBufferLength: 2,
},
productionBrowserSourceMaps: false, productionBrowserSourceMaps: false,
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,

View File

@@ -43,6 +43,7 @@
"@radix-ui/react-toggle": "^1.1.1", "@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1", "@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6", "@radix-ui/react-tooltip": "^1.1.6",
"@tanstack/react-virtual": "^3.13.18",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"axios": "^1.8.1", "axios": "^1.8.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
@@ -64,6 +65,8 @@
"react-hook-form": "^7.54.1", "react-hook-form": "^7.54.1",
"react-markdown": "^10.0.0", "react-markdown": "^10.0.0",
"react-resizable-panels": "^2.1.7", "react-resizable-panels": "^2.1.7",
"react-window": "^2.2.4",
"react-window-infinite-loader": "^2.0.0",
"recharts": "^2.15.0", "recharts": "^2.15.0",
"sonner": "^1.7.4", "sonner": "^1.7.4",
"tailwind-merge": "^2.5.5", "tailwind-merge": "^2.5.5",
@@ -72,7 +75,6 @@
"zod": "^3.25.0" "zod": "^3.25.0"
}, },
"devDependencies": { "devDependencies": {
"@distube/ytdl-core": "^4.16.12",
"@next/bundle-analyzer": "^16.1.1", "@next/bundle-analyzer": "^16.1.1",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@types/lodash": "^4.17.16", "@types/lodash": "^4.17.16",

149
pnpm-lock.yaml generated
View File

@@ -86,6 +86,9 @@ importers:
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.1.6 specifier: ^1.1.6
version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/react-virtual':
specifier: ^3.13.18
version: 3.13.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
autoprefixer: autoprefixer:
specifier: ^10.4.20 specifier: ^10.4.20
version: 10.4.23(postcss@8.5.6) version: 10.4.23(postcss@8.5.6)
@@ -149,6 +152,12 @@ importers:
react-resizable-panels: react-resizable-panels:
specifier: ^2.1.7 specifier: ^2.1.7
version: 2.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 2.1.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-window:
specifier: ^2.2.4
version: 2.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-window-infinite-loader:
specifier: ^2.0.0
version: 2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
recharts: recharts:
specifier: ^2.15.0 specifier: ^2.15.0
version: 2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 2.15.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -168,9 +177,6 @@ importers:
specifier: ^3.25.0 specifier: ^3.25.0
version: 3.25.76 version: 3.25.76
devDependencies: devDependencies:
'@distube/ytdl-core':
specifier: ^4.16.12
version: 4.16.12
'@next/bundle-analyzer': '@next/bundle-analyzer':
specifier: ^16.1.1 specifier: ^16.1.1
version: 16.1.1 version: 16.1.1
@@ -289,10 +295,6 @@ packages:
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
'@distube/ytdl-core@4.16.12':
resolution: {integrity: sha512-/NR8Jur1Q4E2oD+DJta7uwWu7SkqdEkhwERt7f4iune70zg7ZlLLTOHs1+jgg3uD2jQjpdk7RGC16FqstG4RsA==}
engines: {node: '>=20.18.1'}
'@emnapi/core@1.8.1': '@emnapi/core@1.8.1':
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
@@ -1258,6 +1260,15 @@ packages:
peerDependencies: peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
'@tanstack/react-virtual@3.13.18':
resolution: {integrity: sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/virtual-core@3.13.18':
resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==}
'@tybys/wasm-util@0.10.1': '@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@@ -1506,10 +1517,6 @@ packages:
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
ajv@6.12.6: ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -2252,20 +2259,6 @@ packages:
html-url-attributes@3.0.1: html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
http-cookie-agent@7.0.3:
resolution: {integrity: sha512-EeZo7CGhfqPW6R006rJa4QtZZUpBygDa2HZH3DJqsTzTjyRE6foDBVQIv/pjVsxHC8z2GIdbB1Hvn9SRorP3WQ==}
engines: {node: '>=20.0.0'}
peerDependencies:
tough-cookie: ^4.0.0 || ^5.0.0 || ^6.0.0
undici: ^7.0.0
peerDependenciesMeta:
undici:
optional: true
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
@@ -2524,10 +2517,6 @@ packages:
peerDependencies: peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
m3u8stream@0.8.6:
resolution: {integrity: sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==}
engines: {node: '>=12'}
math-intrinsics@1.1.0: math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -2635,10 +2624,6 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
miniget@4.2.3:
resolution: {integrity: sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==}
engines: {node: '>=12'}
minimatch@3.1.2: minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -2973,6 +2958,18 @@ packages:
react: '>=16.6.0' react: '>=16.6.0'
react-dom: '>=16.6.0' react-dom: '>=16.6.0'
react-window-infinite-loader@2.0.0:
resolution: {integrity: sha512-dioOyvShGheEqqFHcPNKCixCOc2evwb2VEt9sitfJfTZ1hir8m6b8W0CNBvcUj+8Y8IeWu4yb88DI7k88aYTQQ==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
react-window@2.2.4:
resolution: {integrity: sha512-FiZsQHvt2qbnTz6cN+/FXvX62v2xukQ+AajUivkm/Ivdp9rnU3bp0B1eDcCNpQXNaDBdqkEVGNYHlvIUGU9yBw==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
react@19.2.3: react@19.2.3:
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -3046,10 +3043,6 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
sax@1.4.4:
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
engines: {node: '>=11.0.0'}
scheduler@0.27.0: scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@@ -3219,13 +3212,6 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tldts-core@6.1.86:
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
tldts@6.1.86:
resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
hasBin: true
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@@ -3234,10 +3220,6 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
tough-cookie@5.1.2:
resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
engines: {node: '>=16'}
trim-lines@3.0.1: trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@@ -3298,10 +3280,6 @@ packages:
undici-types@6.21.0: undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
undici@7.18.2:
resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==}
engines: {node: '>=20.18.1'}
unified@11.0.5: unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@@ -3544,18 +3522,6 @@ snapshots:
'@discoveryjs/json-ext@0.5.7': {} '@discoveryjs/json-ext@0.5.7': {}
'@distube/ytdl-core@4.16.12':
dependencies:
http-cookie-agent: 7.0.3(tough-cookie@5.1.2)(undici@7.18.2)
https-proxy-agent: 7.0.6
m3u8stream: 0.8.6
miniget: 4.2.3
sax: 1.4.4
tough-cookie: 5.1.2
undici: 7.18.2
transitivePeerDependencies:
- supports-color
'@emnapi/core@1.8.1': '@emnapi/core@1.8.1':
dependencies: dependencies:
'@emnapi/wasi-threads': 1.1.0 '@emnapi/wasi-threads': 1.1.0
@@ -4512,6 +4478,14 @@ snapshots:
postcss-selector-parser: 6.0.10 postcss-selector-parser: 6.0.10
tailwindcss: 3.4.19 tailwindcss: 3.4.19
'@tanstack/react-virtual@3.13.18(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@tanstack/virtual-core': 3.13.18
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@tanstack/virtual-core@3.13.18': {}
'@tybys/wasm-util@0.10.1': '@tybys/wasm-util@0.10.1':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@@ -4748,8 +4722,6 @@ snapshots:
acorn@8.15.0: {} acorn@8.15.0: {}
agent-base@7.1.4: {}
ajv@6.12.6: ajv@6.12.6:
dependencies: dependencies:
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@@ -5670,20 +5642,6 @@ snapshots:
html-url-attributes@3.0.1: {} html-url-attributes@3.0.1: {}
http-cookie-agent@7.0.3(tough-cookie@5.1.2)(undici@7.18.2):
dependencies:
agent-base: 7.1.4
tough-cookie: 5.1.2
optionalDependencies:
undici: 7.18.2
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
debug: 4.4.3
transitivePeerDependencies:
- supports-color
ignore@5.3.2: {} ignore@5.3.2: {}
ignore@7.0.5: {} ignore@7.0.5: {}
@@ -5926,11 +5884,6 @@ snapshots:
dependencies: dependencies:
react: 19.2.3 react: 19.2.3
m3u8stream@0.8.6:
dependencies:
miniget: 4.2.3
sax: 1.4.4
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
mdast-util-from-markdown@2.0.2: mdast-util-from-markdown@2.0.2:
@@ -6168,8 +6121,6 @@ snapshots:
dependencies: dependencies:
mime-db: 1.52.0 mime-db: 1.52.0
miniget@4.2.3: {}
minimatch@3.1.2: minimatch@3.1.2:
dependencies: dependencies:
brace-expansion: 1.1.12 brace-expansion: 1.1.12
@@ -6495,6 +6446,16 @@ snapshots:
react: 19.2.3 react: 19.2.3
react-dom: 19.2.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3)
react-window-infinite-loader@2.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-window@2.2.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react@19.2.3: {} react@19.2.3: {}
read-cache@1.0.0: read-cache@1.0.0:
@@ -6604,8 +6565,6 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
is-regex: 1.2.1 is-regex: 1.2.1
sax@1.4.4: {}
scheduler@0.27.0: {} scheduler@0.27.0: {}
semver@6.3.1: {} semver@6.3.1: {}
@@ -6861,22 +6820,12 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3 picomatch: 4.0.3
tldts-core@6.1.86: {}
tldts@6.1.86:
dependencies:
tldts-core: 6.1.86
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
totalist@3.0.1: {} totalist@3.0.1: {}
tough-cookie@5.1.2:
dependencies:
tldts: 6.1.86
trim-lines@3.0.1: {} trim-lines@3.0.1: {}
trough@2.2.0: {} trough@2.2.0: {}
@@ -6955,8 +6904,6 @@ snapshots:
undici-types@6.21.0: {} undici-types@6.21.0: {}
undici@7.18.2: {}
unified@11.0.5: unified@11.0.5:
dependencies: dependencies:
'@types/unist': 3.0.3 '@types/unist': 3.0.3

View File

@@ -1,114 +1,122 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
const config: Config = { const config: Config = {
darkMode: ["class"], darkMode: ["class"],
content: [ content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}", "./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}", "./app/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}",
"./lib/**/*.{js,ts,jsx,tsx,mdx}",
"./hooks/**/*.{js,ts,jsx,tsx,mdx}",
"./utils/**/*.{js,ts,jsx,tsx,mdx}",
], ],
theme: { theme: {
container: { container: {
center: true, center: true,
padding: "2rem", padding: "2rem",
screens: { screens: {
"2xl": "1400px", "2xl": "1400px",
}, },
}, },
extend: { extend: {
keyframes: { keyframes: {
"accordion-down": { "accordion-down": {
from: { height: "0" }, from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" }, to: { height: "var(--radix-accordion-content-height)" },
}, },
"accordion-up": { "accordion-up": {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" }, to: { height: "0" },
}, },
shimmer: { shimmer: {
'0%': { transform: 'translateX(-100%)' }, '0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' } '100%': { transform: 'translateX(100%)' }
}, },
twinkle: { twinkle: {
'0%, 100%': { opacity: '1', transform: 'scale(1)' }, '0%, 100%': { opacity: '1', transform: 'scale(1)' },
'50%': { opacity: '0.3', transform: 'scale(0.8)' } '50%': { opacity: '0.3', transform: 'scale(0.8)' }
}, },
snowflake: { snowflake: {
'0%': { transform: 'translateY(-100vh) rotate(0deg)', opacity: '1' }, '0%': { transform: 'translateY(-100vh) rotate(0deg)', opacity: '1' },
'100%': { transform: 'translateY(100vh) rotate(360deg)', opacity: '0' } '100%': { transform: 'translateY(100vh) rotate(360deg)', opacity: '0' }
}, },
sparkle: { sparkle: {
'0%, 100%': { opacity: '0', transform: 'scale(0) rotate(0deg)' }, '0%, 100%': { opacity: '0', transform: 'scale(0) rotate(0deg)' },
'50%': { opacity: '1', transform: 'scale(1) rotate(180deg)' } '50%': { opacity: '1', transform: 'scale(1) rotate(180deg)' }
}, },
glow: { glow: {
'0%, 100%': { '0%, 100%': {
boxShadow: '0 0 5px hsl(0 84% 50%), 0 0 10px hsl(0 84% 50%), 0 0 15px hsl(0 84% 50%)' boxShadow: '0 0 5px hsl(0 84% 50%), 0 0 10px hsl(0 84% 50%), 0 0 15px hsl(0 84% 50%)'
}, },
'50%': { '50%': {
boxShadow: '0 0 10px hsl(142 76% 36%), 0 0 20px hsl(142 76% 36%), 0 0 30px hsl(142 76% 36%)' boxShadow: '0 0 10px hsl(142 76% 36%), 0 0 20px hsl(142 76% 36%), 0 0 30px hsl(142 76% 36%)'
} }
} }
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
"shimmer": "shimmer 2s infinite", "shimmer": "shimmer 2s infinite",
"twinkle": "twinkle 2s ease-in-out infinite", "twinkle": "twinkle 2s ease-in-out infinite",
"snowflake": "snowflake 10s linear infinite", "snowflake": "snowflake 10s linear infinite",
"sparkle": "sparkle 1.5s ease-in-out infinite", "sparkle": "sparkle 1.5s ease-in-out infinite",
"glow": "glow 2s ease-in-out infinite" "glow": "glow 2s ease-in-out infinite"
}, },
colors: { colors: {
background: 'hsl(var(--background))', background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))', foreground: 'hsl(var(--foreground))',
card: { card: {
DEFAULT: 'hsl(var(--card))', DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))' foreground: 'hsl(var(--card-foreground))'
}, },
popover: { popover: {
DEFAULT: 'hsl(var(--popover))', DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))' foreground: 'hsl(var(--popover-foreground))'
}, },
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))' foreground: 'hsl(var(--primary-foreground))'
}, },
secondary: { secondary: {
DEFAULT: 'hsl(var(--secondary))', DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))' foreground: 'hsl(var(--secondary-foreground))'
}, },
muted: { muted: {
DEFAULT: 'hsl(var(--muted))', DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))' foreground: 'hsl(var(--muted-foreground))'
}, },
accent: { accent: {
DEFAULT: 'hsl(var(--accent))', DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))' foreground: 'hsl(var(--accent-foreground))'
}, },
destructive: { destructive: {
DEFAULT: 'hsl(var(--destructive))', DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))' foreground: 'hsl(var(--destructive-foreground))'
}, },
border: 'hsl(var(--border))', border: 'hsl(var(--border))',
input: 'hsl(var(--input))', input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))', ring: 'hsl(var(--ring))',
chart: { chart: {
'1': 'hsl(var(--chart-1))', '1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))', '2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))', '3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))', '4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))' '5': 'hsl(var(--chart-5))'
} }
}, },
borderRadius: { borderRadius: {
lg: 'var(--radius)', lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)', md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)' sm: 'calc(var(--radius) - 4px)'
} }
} }
}, },
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
// Optimize for production
future: {
hoverOnlyWhenSupported: true,
},
}; };
export default config; export default config;