From 0fba981bcae06ec5bd064a5aff20cc7edcb6c20d Mon Sep 17 00:00:00 2001 From: g Date: Sun, 11 Jan 2026 07:35:52 +0000 Subject: [PATCH] optimization --- app/_document.tsx | 8 + components/admin/OrdersTable.tsx | 177 ++++++++++++----- components/analytics/AnalyticsDashboard.tsx | 3 +- components/analytics/PredictionsChart.tsx | 2 +- next.config.mjs | 75 ++++--- package.json | 4 +- pnpm-lock.yaml | 149 +++++--------- tailwind.config.ts | 210 ++++++++++---------- 8 files changed, 339 insertions(+), 289 deletions(-) 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;