diff --git a/app/_document.tsx b/app/_document.tsx
index 2adac95..b28a6fb 100644
--- a/app/_document.tsx
+++ b/app/_document.tsx
@@ -5,6 +5,14 @@ export default function Document() {
+ {/* Font optimization */}
+
+
+
+ {/* Audio preload */}
diff --git a/components/admin/OrdersTable.tsx b/components/admin/OrdersTable.tsx
index 2a38a7b..117c40f 100644
--- a/components/admin/OrdersTable.tsx
+++ b/components/admin/OrdersTable.tsx
@@ -1,12 +1,13 @@
"use client";
-import { useState } from "react";
+import { useState, useMemo } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Search, Filter, Eye, ChevronLeft, ChevronRight } from "lucide-react";
+import { FixedSizeList as List } from 'react-window';
import OrderDetailsModal from "./OrderDetailsModal";
interface Order {
@@ -57,30 +58,85 @@ const getStatusStyle = (status: string) => {
* This component should only be used in admin contexts
*/
export default function OrdersTable({ orders, enableModal = true }: OrdersTableProps) {
- const [currentPage, setCurrentPage] = useState(1);
const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState("all");
- const [selectedOrderId, setSelectedOrderId] = useState(null);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [selectedOrderId, setSelectedOrderId] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
- const itemsPerPage = 10;
+
+ const itemsPerPage = 20;
// Filter orders based on search and status
- const filteredOrders = orders.filter(order => {
- const matchesSearch = 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;
-
- return matchesSearch && matchesStatus;
- });
+ const filteredOrders = useMemo(() => {
+ return orders.filter((order) => {
+ const matchesSearch = searchTerm === "" ||
+ order.orderId.toString().toLowerCase().includes(searchTerm.toLowerCase()) ||
+ order.userId.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ (order.vendorUsername && order.vendorUsername.toLowerCase().includes(searchTerm.toLowerCase()));
- // Calculate pagination
+ const matchesStatus = statusFilter === "all" || order.status === statusFilter;
+
+ return matchesSearch && matchesStatus;
+ });
+ }, [orders, searchTerm, statusFilter]);
+
+ // Pagination calculations
const totalPages = Math.ceil(filteredOrders.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
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 (
+
+
+ {order.orderId}
+ {order.userId}
+ {order.vendorUsername || 'N/A'}
+
+ {order.items.length > 0 ? order.items[0].name : 'No items'}
+
+ £{order.total.toFixed(2)}
+
+
+ {order.status.toUpperCase()}
+
+
+ N/A
+ {new Date(order.createdAt).toLocaleDateString()}
+
+ {enableModal ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+ };
+
+ // Determine if we should use virtual scrolling (for performance with large datasets)
+ const useVirtualScrolling = currentOrders.length > 50;
+
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
@@ -152,47 +208,58 @@ export default function OrdersTable({ orders, enableModal = true }: OrdersTableP
Actions
-
- {currentOrders.map((order) => (
-
- {order.orderId}
- {order.userId}
- {order.vendorUsername || 'N/A'}
-
- {order.items.length > 0 ? order.items[0].name : 'No items'}
-
- £{order.total.toFixed(2)}
-
-
- {order.status.toUpperCase()}
-
-
- N/A
- {new Date(order.createdAt).toLocaleDateString()}
-
- {enableModal ? (
-
- ) : (
-
- )}
-
-
- ))}
-
+ {useVirtualScrolling ? (
+
+ {Row}
+
+ ) : (
+
+ {currentOrders.map((order) => (
+
+ {order.orderId}
+ {order.userId}
+ {order.vendorUsername || 'N/A'}
+
+ {order.items.length > 0 ? order.items[0].name : 'No items'}
+
+ £{order.total.toFixed(2)}
+
+
+ {order.status.toUpperCase()}
+
+
+ N/A
+ {new Date(order.createdAt).toLocaleDateString()}
+
+ {enableModal ? (
+
+ ) : (
+
+ )}
+
+
+ ))}
+
+ )}
{/* Pagination */}
diff --git a/components/analytics/AnalyticsDashboard.tsx b/components/analytics/AnalyticsDashboard.tsx
index 3e2abdd..d8154b8 100644
--- a/components/analytics/AnalyticsDashboard.tsx
+++ b/components/analytics/AnalyticsDashboard.tsx
@@ -47,7 +47,8 @@ import { DateRange } from "react-day-picker";
import { addDays, startOfDay, endOfDay } from "date-fns";
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"), {
loading: () => ,
});
diff --git a/components/analytics/PredictionsChart.tsx b/components/analytics/PredictionsChart.tsx
index bbf69d4..e8c7dc1 100644
--- a/components/analytics/PredictionsChart.tsx
+++ b/components/analytics/PredictionsChart.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState, useEffect } from "react";
+import { useState, useEffect, memo, useMemo, useCallback } from "react";
import {
Card,
CardContent,
diff --git a/next.config.mjs b/next.config.mjs
index 7126ff7..b1ffc55 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -5,8 +5,8 @@ const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'tr
/** @type {import('next').NextConfig} */
const baseConfig = {
output: 'standalone',
- reactStrictMode: false,
- compress: process.env.NODE_ENV === 'production',
+ // Enable compression for better performance
+ compress: true,
images: {
remotePatterns: [
{
@@ -58,10 +58,10 @@ const baseConfig = {
transform: 'date-fns/{{member}}',
},
},
- // Webpack config (fallback if using --webpack flag)
+ // Enhanced webpack config for better bundle splitting
webpack: (config, { isServer, dev }) => {
if (!isServer && !dev) {
- // Only apply aggressive code splitting in production
+ // Enhanced code splitting for better performance
config.optimization = {
...config.optimization,
moduleIds: 'deterministic',
@@ -70,41 +70,62 @@ const baseConfig = {
cacheGroups: {
default: false,
vendors: false,
- recharts: {
- test: /[\\/]node_modules[\\/]recharts[\\/]/,
- name: 'recharts',
+ // Separate React and related libraries
+ react: {
+ test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
+ name: 'react',
chunks: 'all',
- priority: 30,
+ priority: 50,
+ enforce: true,
},
+ // Separate UI library components
radix: {
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
name: 'radix-ui',
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,
},
- reactMarkdown: {
- test: /[\\/]node_modules[\\/]react-markdown[\\/]/,
- name: 'react-markdown',
+ // Separate form libraries
+ forms: {
+ test: /[\\/]node_modules[\\/](react-hook-form|@hookform)[\\/]/,
+ name: 'forms',
chunks: 'all',
priority: 20,
},
- dateFns: {
- test: /[\\/]node_modules[\\/]date-fns[\\/]/,
- name: 'date-fns',
+ // Group remaining vendor libraries
+ vendor: {
+ test: /[\\/]node_modules[\\/]/,
+ name: 'vendor',
chunks: 'all',
- priority: 15,
- },
- framework: {
- name: 'framework',
- chunks: 'all',
- test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
- priority: 40,
+ priority: 10,
enforce: true,
},
- commons: {
- name: 'commons',
- minChunks: 2,
- priority: 10,
+ // Common application code
+ common: {
+ name: 'common',
+ minChunks: 3,
+ priority: 5,
},
},
},
@@ -112,10 +133,6 @@ const baseConfig = {
}
return config;
},
- onDemandEntries: {
- maxInactiveAge: 15 * 1000,
- pagesBufferLength: 2,
- },
productionBrowserSourceMaps: false,
typescript: {
ignoreBuildErrors: true,
diff --git a/package.json b/package.json
index 8220e81..f11997a 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
+ "@tanstack/react-virtual": "^3.13.18",
"autoprefixer": "^10.4.20",
"axios": "^1.8.1",
"class-variance-authority": "^0.7.1",
@@ -64,6 +65,8 @@
"react-hook-form": "^7.54.1",
"react-markdown": "^10.0.0",
"react-resizable-panels": "^2.1.7",
+ "react-window": "^2.2.4",
+ "react-window-infinite-loader": "^2.0.0",
"recharts": "^2.15.0",
"sonner": "^1.7.4",
"tailwind-merge": "^2.5.5",
@@ -72,7 +75,6 @@
"zod": "^3.25.0"
},
"devDependencies": {
- "@distube/ytdl-core": "^4.16.12",
"@next/bundle-analyzer": "^16.1.1",
"@tailwindcss/typography": "^0.5.16",
"@types/lodash": "^4.17.16",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d6359c5..893387b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -86,6 +86,9 @@ importers:
'@radix-ui/react-tooltip':
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)
+ '@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:
specifier: ^10.4.20
version: 10.4.23(postcss@8.5.6)
@@ -149,6 +152,12 @@ importers:
react-resizable-panels:
specifier: ^2.1.7
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:
specifier: ^2.15.0
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
version: 3.25.76
devDependencies:
- '@distube/ytdl-core':
- specifier: ^4.16.12
- version: 4.16.12
'@next/bundle-analyzer':
specifier: ^16.1.1
version: 16.1.1
@@ -289,10 +295,6 @@ packages:
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
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':
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
@@ -1258,6 +1260,15 @@ packages:
peerDependencies:
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':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@@ -1506,10 +1517,6 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
- agent-base@7.1.4:
- resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
- engines: {node: '>= 14'}
-
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
@@ -2252,20 +2259,6 @@ packages:
html-url-attributes@3.0.1:
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:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -2524,10 +2517,6 @@ packages:
peerDependencies:
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:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -2635,10 +2624,6 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
- miniget@4.2.3:
- resolution: {integrity: sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==}
- engines: {node: '>=12'}
-
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -2973,6 +2958,18 @@ packages:
react: '>=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:
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
engines: {node: '>=0.10.0'}
@@ -3046,10 +3043,6 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
- sax@1.4.4:
- resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
- engines: {node: '>=11.0.0'}
-
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@@ -3219,13 +3212,6 @@ packages:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
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:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -3234,10 +3220,6 @@ packages:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
- tough-cookie@5.1.2:
- resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
- engines: {node: '>=16'}
-
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
@@ -3298,10 +3280,6 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
- undici@7.18.2:
- resolution: {integrity: sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==}
- engines: {node: '>=20.18.1'}
-
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@@ -3544,18 +3522,6 @@ snapshots:
'@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':
dependencies:
'@emnapi/wasi-threads': 1.1.0
@@ -4512,6 +4478,14 @@ snapshots:
postcss-selector-parser: 6.0.10
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':
dependencies:
tslib: 2.8.1
@@ -4748,8 +4722,6 @@ snapshots:
acorn@8.15.0: {}
- agent-base@7.1.4: {}
-
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -5670,20 +5642,6 @@ snapshots:
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@7.0.5: {}
@@ -5926,11 +5884,6 @@ snapshots:
dependencies:
react: 19.2.3
- m3u8stream@0.8.6:
- dependencies:
- miniget: 4.2.3
- sax: 1.4.4
-
math-intrinsics@1.1.0: {}
mdast-util-from-markdown@2.0.2:
@@ -6168,8 +6121,6 @@ snapshots:
dependencies:
mime-db: 1.52.0
- miniget@4.2.3: {}
-
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -6495,6 +6446,16 @@ snapshots:
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: {}
read-cache@1.0.0:
@@ -6604,8 +6565,6 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
- sax@1.4.4: {}
-
scheduler@0.27.0: {}
semver@6.3.1: {}
@@ -6861,22 +6820,12 @@ snapshots:
fdir: 6.5.0(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:
dependencies:
is-number: 7.0.0
totalist@3.0.1: {}
- tough-cookie@5.1.2:
- dependencies:
- tldts: 6.1.86
-
trim-lines@3.0.1: {}
trough@2.2.0: {}
@@ -6955,8 +6904,6 @@ snapshots:
undici-types@6.21.0: {}
- undici@7.18.2: {}
-
unified@11.0.5:
dependencies:
'@types/unist': 3.0.3
diff --git a/tailwind.config.ts b/tailwind.config.ts
index e11b707..c4204d0 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,114 +1,122 @@
import type { Config } from "tailwindcss";
const config: Config = {
- darkMode: ["class"],
- content: [
+ darkMode: ["class"],
+ content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{ts,tsx}",
+ "./lib/**/*.{js,ts,jsx,tsx,mdx}",
+ "./hooks/**/*.{js,ts,jsx,tsx,mdx}",
+ "./utils/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
- container: {
- center: true,
- padding: "2rem",
- screens: {
- "2xl": "1400px",
- },
- },
- extend: {
- keyframes: {
- "accordion-down": {
- from: { height: "0" },
- to: { height: "var(--radix-accordion-content-height)" },
- },
- "accordion-up": {
- from: { height: "var(--radix-accordion-content-height)" },
- to: { height: "0" },
- },
- shimmer: {
- '0%': { transform: 'translateX(-100%)' },
- '100%': { transform: 'translateX(100%)' }
- },
- twinkle: {
- '0%, 100%': { opacity: '1', transform: 'scale(1)' },
- '50%': { opacity: '0.3', transform: 'scale(0.8)' }
- },
- snowflake: {
- '0%': { transform: 'translateY(-100vh) rotate(0deg)', opacity: '1' },
- '100%': { transform: 'translateY(100vh) rotate(360deg)', opacity: '0' }
- },
- sparkle: {
- '0%, 100%': { opacity: '0', transform: 'scale(0) rotate(0deg)' },
- '50%': { opacity: '1', transform: 'scale(1) rotate(180deg)' }
- },
- glow: {
- '0%, 100%': {
- boxShadow: '0 0 5px hsl(0 84% 50%), 0 0 10px hsl(0 84% 50%), 0 0 15px hsl(0 84% 50%)'
- },
- '50%': {
- boxShadow: '0 0 10px hsl(142 76% 36%), 0 0 20px hsl(142 76% 36%), 0 0 30px hsl(142 76% 36%)'
- }
- }
- },
- animation: {
- "accordion-down": "accordion-down 0.2s ease-out",
- "accordion-up": "accordion-up 0.2s ease-out",
- "shimmer": "shimmer 2s infinite",
- "twinkle": "twinkle 2s ease-in-out infinite",
- "snowflake": "snowflake 10s linear infinite",
- "sparkle": "sparkle 1.5s ease-in-out infinite",
- "glow": "glow 2s ease-in-out infinite"
- },
- colors: {
- background: 'hsl(var(--background))',
- foreground: 'hsl(var(--foreground))',
- card: {
- DEFAULT: 'hsl(var(--card))',
- foreground: 'hsl(var(--card-foreground))'
- },
- popover: {
- DEFAULT: 'hsl(var(--popover))',
- foreground: 'hsl(var(--popover-foreground))'
- },
- primary: {
- DEFAULT: 'hsl(var(--primary))',
- foreground: 'hsl(var(--primary-foreground))'
- },
- secondary: {
- DEFAULT: 'hsl(var(--secondary))',
- foreground: 'hsl(var(--secondary-foreground))'
- },
- muted: {
- DEFAULT: 'hsl(var(--muted))',
- foreground: 'hsl(var(--muted-foreground))'
- },
- accent: {
- DEFAULT: 'hsl(var(--accent))',
- foreground: 'hsl(var(--accent-foreground))'
- },
- destructive: {
- DEFAULT: 'hsl(var(--destructive))',
- foreground: 'hsl(var(--destructive-foreground))'
- },
- border: 'hsl(var(--border))',
- input: 'hsl(var(--input))',
- ring: 'hsl(var(--ring))',
- chart: {
- '1': 'hsl(var(--chart-1))',
- '2': 'hsl(var(--chart-2))',
- '3': 'hsl(var(--chart-3))',
- '4': 'hsl(var(--chart-4))',
- '5': 'hsl(var(--chart-5))'
- }
- },
- borderRadius: {
- lg: 'var(--radius)',
- md: 'calc(var(--radius) - 2px)',
- sm: 'calc(var(--radius) - 4px)'
- }
- }
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
+ extend: {
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ shimmer: {
+ '0%': { transform: 'translateX(-100%)' },
+ '100%': { transform: 'translateX(100%)' }
+ },
+ twinkle: {
+ '0%, 100%': { opacity: '1', transform: 'scale(1)' },
+ '50%': { opacity: '0.3', transform: 'scale(0.8)' }
+ },
+ snowflake: {
+ '0%': { transform: 'translateY(-100vh) rotate(0deg)', opacity: '1' },
+ '100%': { transform: 'translateY(100vh) rotate(360deg)', opacity: '0' }
+ },
+ sparkle: {
+ '0%, 100%': { opacity: '0', transform: 'scale(0) rotate(0deg)' },
+ '50%': { opacity: '1', transform: 'scale(1) rotate(180deg)' }
+ },
+ glow: {
+ '0%, 100%': {
+ boxShadow: '0 0 5px hsl(0 84% 50%), 0 0 10px hsl(0 84% 50%), 0 0 15px hsl(0 84% 50%)'
+ },
+ '50%': {
+ boxShadow: '0 0 10px hsl(142 76% 36%), 0 0 20px hsl(142 76% 36%), 0 0 30px hsl(142 76% 36%)'
+ }
+ }
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ "shimmer": "shimmer 2s infinite",
+ "twinkle": "twinkle 2s ease-in-out infinite",
+ "snowflake": "snowflake 10s linear infinite",
+ "sparkle": "sparkle 1.5s ease-in-out infinite",
+ "glow": "glow 2s ease-in-out infinite"
+ },
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))'
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))'
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))'
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))'
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))'
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))'
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))'
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))'
+ }
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)'
+ }
+ }
},
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
+ // Optimize for production
+ future: {
+ hoverOnlyWhenSupported: true,
+ },
};
+
export default config;