refactor: improve invoice editor UX and fix visual issues

- Remove clock icons and hour text from calendar month view, show only activity bars
- Fix calendar week view mobile layout (2-column grid instead of vertical stack)
- Update invoice form skeleton to match actual layout structure
- Add client-side validation for empty invoice item descriptions with auto-scroll to error
- Fix hourly rate defaulting logic with proper type guards
- Update invoice details skeleton to match page structure with PageHeader
- Fix hydration error in sidebar (div inside button -> span)
- Improve dashboard chart color consistency (draft status now matches monthly metrics)
- Fix mobile header layout to prevent text squishing (vertical stack on mobile)
- Add IDs to invoice line items for scroll-into-view functionality
This commit is contained in:
2025-12-11 19:57:54 -05:00
parent 39fdf16280
commit 1a3c2e08ce
27 changed files with 1685 additions and 2024 deletions

247
bun.lock
View File

@@ -26,64 +26,65 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@react-pdf/renderer": "^4.3.1",
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.90.10",
"@tanstack/react-query": "^5.90.12",
"@tanstack/react-table": "^8.21.3",
"@tiptap/extension-color": "^3.11.0",
"@tiptap/extension-list-item": "^3.11.0",
"@tiptap/extension-text-align": "^3.11.0",
"@tiptap/extension-text-style": "^3.11.0",
"@tiptap/react": "^3.11.0",
"@tiptap/starter-kit": "^3.11.0",
"@tiptap/extension-color": "^3.13.0",
"@tiptap/extension-list-item": "^3.13.0",
"@tiptap/extension-text-align": "^3.13.0",
"@tiptap/extension-text-style": "^3.13.0",
"@tiptap/react": "^3.13.0",
"@tiptap/starter-kit": "^3.13.0",
"@trpc/client": "^11.7.2",
"@trpc/react-query": "^11.7.2",
"@trpc/server": "^11.7.2",
"bcryptjs": "^3.0.3",
"better-auth": "^1.4.3",
"better-auth": "^1.4.6",
"chrono-node": "^2.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.44.7",
"file-saver": "^2.0.5",
"framer-motion": "^12.23.24",
"framer-motion": "^12.23.26",
"lucide-react": "^0.525.0",
"next": "^16.0.7",
"next": "^16.0.10",
"next-themes": "^0.4.6",
"pg": "^8.16.3",
"react": "^19.2.1",
"react-day-picker": "^9.11.2",
"react-dom": "^19.2.0",
"react": "^19.2.3",
"react-day-picker": "^9.12.0",
"react-dom": "^19.2.3",
"react-dropzone": "^14.3.8",
"recharts": "^3.5.0",
"recharts": "^3.5.1",
"resend": "^4.8.0",
"server-only": "^0.0.1",
"sonner": "^2.0.7",
"superjson": "^2.2.5",
"superjson": "^2.2.6",
"tailwind-merge": "^3.4.0",
"zod": "^3.25.76",
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.17",
"@tailwindcss/postcss": "^4.1.18",
"@types/bcryptjs": "^2.4.6",
"@types/file-saver": "^2.0.7",
"@types/node": "^20.19.25",
"@types/pg": "^8.15.6",
"@types/node": "^20.19.26",
"@types/pg": "^8.16.0",
"@types/raf": "^3.4.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"baseline-browser-mapping": "^2.8.32",
"baseline-browser-mapping": "^2.9.6",
"dotenv": "^17.2.3",
"drizzle-kit": "^0.30.6",
"eslint": "^9.39.1",
"eslint-config-next": "^16.0.5",
"eslint-config-next": "^16.0.10",
"eslint-plugin-drizzle": "^0.2.3",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"prettier": "^3.7.4",
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwindcss": "^4.1.17",
"tailwindcss": "^4.1.18",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.0",
"typescript-eslint": "^8.49.0",
},
},
},
@@ -130,9 +131,9 @@
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@better-auth/core": ["@better-auth/core@1.4.3", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.1.0", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-6PjF/GMvR+dV/PJDvInsU4BQaL+OvAB17i72Pz3zYwxF709hIaTHOshysTiFoLxjfFN2GGwgk5pGLKHVL/pB2w=="],
"@better-auth/core": ["@better-auth/core@1.4.6", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.1.5", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-cYjscr4wU5ZJPhk86JuUkecJT+LSYCFmUzYaitiLkizl+wCr1qdPFSEoAnRVZVTUEEoKpeS2XW69voBJ1NoB3g=="],
"@better-auth/telemetry": ["@better-auth/telemetry@1.4.3", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18" }, "peerDependencies": { "@better-auth/core": "1.4.3" } }, "sha512-rBkNdUCZJVuc6AQyg9W5A8fgYdOxDyhytfGy3aWrZw77JGJ2KiPwZfZ+OrFxubOzZvFdhoeTo6yfFURRqTFCwQ=="],
"@better-auth/telemetry": ["@better-auth/telemetry@1.4.6", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18" }, "peerDependencies": { "@better-auth/core": "1.4.6" } }, "sha512-idc9MGJXxWA7zl2U9zsbdG6+2ZCeqWdPq1KeFSfyqGMFtI1VPQOx9YWLqNPOt31YnOX77ojZSraU2sb7IRdBMA=="],
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
@@ -302,25 +303,25 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" } }, "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA=="],
"@next/env": ["@next/env@16.0.7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="],
"@next/env": ["@next/env@16.0.10", "", {}, "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.0.5", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-m1zPz6hsBvQt1CMRz7rTga8OXpRE9rVW4JHCSjW+tswTxiEU+6ev+GTlgm7ZzcCiMEVQAHTNhpEGFzDtVha9qg=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.0.10", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-b2NlWN70bbPLmfyoLvvidPKWENBYYIe017ZGUpElvQjDytCWgxPJx7L9juxHt0xHvNVA08ZHJdOyhGzon/KJuw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.10", "", { "os": "linux", "cpu": "x64" }, "sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.10", "", { "os": "win32", "cpu": "x64" }, "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q=="],
"@noble/ciphers": ["@noble/ciphers@2.0.1", "", {}, "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g=="],
@@ -470,105 +471,105 @@
"@t3-oss/env-nextjs": ["@t3-oss/env-nextjs@0.12.0", "", { "dependencies": { "@t3-oss/env-core": "0.12.0" }, "peerDependencies": { "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0" }, "optionalPeers": ["typescript", "valibot", "zod"] }, "sha512-rFnvYk1049RnNVUPvY8iQ55AuQh1Rr+qZzQBh3t++RttCGK4COpXGNxS4+45afuQq02lu+QAOy/5955aU8hRKw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
"@tanstack/query-core": ["@tanstack/query-core@5.90.10", "", {}, "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ=="],
"@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.10", "", { "dependencies": { "@tanstack/query-core": "5.90.10" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="],
"@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="],
"@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="],
"@tiptap/core": ["@tiptap/core@3.11.0", "", { "peerDependencies": { "@tiptap/pm": "^3.11.0" } }, "sha512-kmS7ZVpHm1EMnW1Wmft9H5ZLM7E0G0NGBx+aGEHGDcNxZBXD2ZUa76CuWjIhOGpwsPbELp684ZdpF2JWoNi4Dg=="],
"@tiptap/core": ["@tiptap/core@3.13.0", "", { "peerDependencies": { "@tiptap/pm": "^3.13.0" } }, "sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ=="],
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-0H8WVW6Vn4GJ7sQ6wfyDgUU+DqM8fp62g8N0fFPiEhoYtpIYUmCqGhpKnqYR0tet6ofFa648XmA6n2VX7sugzw=="],
"@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-K1z/PAIIwEmiWbzrP//4cC7iG1TZknDlF1yb42G7qkx2S2X4P0NiqX7sKOej3yqrPjKjGwPujLMSuDnCF87QkQ=="],
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-V/c3XYO09Le9GlBGq1MK4c97Fffi0GADQTbZ+LFoi65nUrAwutn5wYnXBcEyWQI6RmFWVDJTieamqtc4j9teyw=="],
"@tiptap/extension-bold": ["@tiptap/extension-bold@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-VYiDN9EEwR6ShaDLclG8mphkb/wlIzqfk7hxaKboq1G+NSDj8PcaSI9hldKKtTCLeaSNu6UR5nkdu/YHdzYWTw=="],
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.11.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-P3j9lQ+EZ5Zg/isJzLpCPX7bp7WUBmz8GPs/HPlyMyN2su8LqXntITBZr8IP1JNBlB/wR83k/W0XqdC57mG7cA=="],
"@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.13.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-qZ3j2DBsqP9DjG2UlExQ+tHMRhAnWlCKNreKddKocb/nAFrPdBCtvkqIEu+68zPlbLD4ukpoyjUklRJg+NipFg=="],
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.11.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.11.0" } }, "sha512-IKdb1C3bHA1sGPiUcntkL+wHebRg71K5+tgaaRnMw0qmtcpcOQb5zhQOSm5bXUsgCk/WgT04dkZPnpn6Gg1PvQ=="],
"@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.13.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.13.0" } }, "sha512-fFQmmEUoPzRGiQJ/KKutG35ZX21GE+1UCDo8Q6PoWH7Al9lex47nvyeU1BiDYOhcTKgIaJRtEH5lInsOsRJcSA=="],
"@tiptap/extension-code": ["@tiptap/extension-code@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-5OpR5O4bveHe1KG9CJsto86NgkuerYq3OLY78vzh9uFCLdv7xgXA2aZYJfRMhbZ7hKsR7hHg1etBJUCk+TKsMg=="],
"@tiptap/extension-code": ["@tiptap/extension-code@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-sF5raBni6iSVpXWvwJCAcOXw5/kZ+djDHx1YSGWhopm4+fsj0xW7GvVO+VTwiFjZGKSw+K5NeAxzcQTJZd3Vhw=="],
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-y01RJVbygDJWYXxZ0SiCYwvUF2X91RANCLSdb8X0qiwVPgNOzsDrrzS/iqoXkiYmM93pJw+ZWelEZxRvxEwsrg=="],
"@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-kIwfQ4iqootsWg9e74iYJK54/YMIj6ahUxEltjZRML5z/h4gTDcQt2eTpnEC8yjDjHeUVOR94zH9auCySyk9CQ=="],
"@tiptap/extension-color": ["@tiptap/extension-color@3.11.0", "", { "peerDependencies": { "@tiptap/extension-text-style": "^3.11.0" } }, "sha512-4H+3nyheow0tBt+q/Pkkm/pR/F6vUAdKZaq1HtWFi7LcFzCYxdB3Si625EparKO3kMvGvULjdd+/zt68NS7Acw=="],
"@tiptap/extension-color": ["@tiptap/extension-color@3.13.0", "", { "peerDependencies": { "@tiptap/extension-text-style": "^3.13.0" } }, "sha512-I+PTZ31p6GhCjUVpCh1qertTK6T07MVGuLm/LP+c14ByMoZ0L0nVgw0YbWhDqLYWbkI9davv0fuI3dQ0gKSPlA=="],
"@tiptap/extension-document": ["@tiptap/extension-document@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-N2G3cwL2Dtur/CgD/byJmFx9T5no6fTO/U462VP3rthQYrRA1AB3TCYqtlwJkmyoxRTNd4qIg4imaPl8ej6Heg=="],
"@tiptap/extension-document": ["@tiptap/extension-document@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-RjU7hTJwjKXIdY57o/Pc+Yr8swLkrwT7PBQ/m+LCX5oO/V2wYoWCjoBYnK5KSHrWlNy/aLzC33BvLeqZZ9nzlQ=="],
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.11.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.11.0" } }, "sha512-gW/QMGAyiXGSpO+X/lTeiBQn1Or8T8UVB3y9Cv2Lh6zx0SWU+FA28EH+y6s3fm872reN4dH/9rEvMuJjhU/BEw=="],
"@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.13.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.13.0" } }, "sha512-m7GPT3c/83ni+bbU8c+3dpNa8ug+aQ4phNB1Q52VQG3oTonDJnZS7WCtn3lB/Hi1LqoqMtEHwhepU2eD+JeXqQ=="],
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.11.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-nEHdWZHEJYX1II1oJQ4aeZ8O/Kss4BRbYFXQFGIvPelCfCYEATpUJh3aq3767ARSq40bOWyu+Dcd4SCW0We6Sw=="],
"@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.13.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA=="],
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.11.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.11.0" } }, "sha512-lXGEZiYX7k/pEFr8BgDE91vqjLTwuf+qhHLTgIpfhbt562nShLPIDj9Vzu3xrR4fwUAMiUNiLyaeInb8j3I4kg=="],
"@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.13.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.13.0" } }, "sha512-KVxjQKkd964nin+1IdM2Dvej/Jy4JTMcMgq5seusUhJ9T9P8F9s2D5Iefwgkps3OCzub/aF+eAsZe+1P5KSIgA=="],
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-NJEHTj++kFOayQXKSQSi9j9eAG33eSiJqai2pf4U+snW94fmb8cYLUurDmfYRe20O6EzBSX0X3GjVlkOz+5b7A=="],
"@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-nH1OBaO+/pakhu+P1jF208mPgB70IKlrR/9d46RMYoYbqJTNf4KVLx5lHAOHytIhjcNg+MjyTfJWfkK+dyCCyg=="],
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-4Eo67Yo7vsYLkizcMoGdZAR9aHbC7FFTrqfNEd4Em3ajRi0iNqyWMaI90UCYlitDdRdqFlq/njWrMqBOLUgaWQ=="],
"@tiptap/extension-heading": ["@tiptap/extension-heading@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-8VKWX8waYPtUWN97J89em9fOtxNteh6pvUEd0htcOAtoxjt2uZjbW5N4lKyWhNKifZBrVhH2Cc2NUPuftCVgxw=="],
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-FugFHZG+oiMBV6k42hn9NOA4wRNc2b9UeEIMR+XwEMpWJInV4VwSwDvu8JClgkDo8z7FEnker9e51DZ00CLWqg=="],
"@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-ZUFyORtjj22ib8ykbxRhWFQOTZjNKqOsMQjaAGof30cuD2DN5J5pMz7Haj2fFRtLpugWYH+f0Mi+WumQXC3hCw=="],
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-WP6wL2b//8bLVdeUCWOpYA7nUStvrAMMD0nRn0F9CEW+l7vH6El2PZFhHmJ9uqXo5MnyugBpARiwgxfoAlef5w=="],
"@tiptap/extension-italic": ["@tiptap/extension-italic@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-XbVTgmzk1kgUMTirA6AGdLTcKHUvEJoh3R4qMdPtwwygEOe7sBuvKuLtF6AwUtpnOM+Y3tfWUTNEDWv9AcEdww=="],
"@tiptap/extension-link": ["@tiptap/extension-link@3.11.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-RoUkGqowVMKLE76KktNOGhzNMyKtwrSDRqeYCe1ODPuOMZvDGexOE8cIuA4A1ODkgN6ji9qE/9Sf8uhpZdH39Q=="],
"@tiptap/extension-link": ["@tiptap/extension-link@3.13.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-LuFPJ5GoL12GHW4A+USsj60O90pLcwUPdvEUSWewl9USyG6gnLnY/j5ZOXPYH7LiwYW8+lhq7ABwrDF2PKyBbA=="],
"@tiptap/extension-list": ["@tiptap/extension-list@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-4Ane7VCVZ+GFOQNuy2nMP+SoWH7EemC3geTTqvgHm1H0tbSosxLJAVaZ9dF06F35RJmYCm+jLJUhRVd156eCRQ=="],
"@tiptap/extension-list": ["@tiptap/extension-list@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-MMFH0jQ4LeCPkJJFyZ77kt6eM/vcKujvTbMzW1xSHCIEA6s4lEcx9QdZMPpfmnOvTzeoVKR4nsu2t2qT9ZXzAw=="],
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.11.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.11.0" } }, "sha512-KXTTSBH/T/WW8O1YhK/lVmwlSGh2w2VVucUkMLhgk1VPchahAkn2LfgbgKrCRG/F8M8Jlfvz67iJDo6+bbNqew=="],
"@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.13.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.13.0" } }, "sha512-63NbcS/XeQP2jcdDEnEAE3rjJICDj8y1SN1h/MsJmSt1LusnEo8WQ2ub86QELO6XnD3M04V03cY6Knf6I5mTkw=="],
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.11.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.11.0" } }, "sha512-vm1zGdEqcbQnrGlVXchk1ibmTsyxyfGcGPVWsc4MG+UAFcNfcpAnvCar71BF4RGGPtpzOWdqGkvJENyh0L5/Hw=="],
"@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.13.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.13.0" } }, "sha512-P+HtIa1iwosb1feFc8B/9MN5EAwzS+/dZ0UH0CTF2E4wnp5Z9OMxKl1IYjfiCwHzZrU5Let+S/maOvJR/EmV0g=="],
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.11.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.11.0" } }, "sha512-kO8GH4w4Xil+qPiHJLAyILdGHF9hCjkhoVtPD8YEfqK6Qx3bZql5FPySCQNs+MU6rLSCCdam8SUPGY/+SCufqA=="],
"@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.13.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.13.0" } }, "sha512-QuDyLzuK/3vCvx9GeKhgvHWrGECBzmJyAx6gli2HY+Iil7XicbfltV4nvhIxgxzpx3LDHLKzJN9pBi+2MzX60g=="],
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-hxgjZOXOqstRTWv+QjWJjK23rD5qzIV9ePlhX3imLeq/MgX0aU9VBDaG5SGKbSjaBNQnpLw6+sABJi3CDP6Z5A=="],
"@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-9csQde1i0yeZI5oQQ9e1GYNtGL2JcC2d8Fwtw9FsGC8yz2W0h+Fmk+3bc2kobbtO5LGqupSc1fKM8fAg5rSRDg=="],
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-XVP/WMYLrqLBfUsGPu2H9MrOUZLhGUaxtZ3hSRffDi/lsw53x/coZ9eO0FxOB9R7z2ksHWmticIs+0YnKt9LNQ=="],
"@tiptap/extension-strike": ["@tiptap/extension-strike@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-VHhWNqTAMOfrC48m2FcPIZB0nhl6XHQviAV16SBc+EFznKNv9tQUsqQrnuQ2y6ZVfqq5UxvZ3hKF/JlN/Ff7xw=="],
"@tiptap/extension-text": ["@tiptap/extension-text@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-ELAYm2BuChzZOqDG9B0k3W6zqM4pwNvXkam28KgHGiT2y7Ni68Rb+NXp16uVR+5zR6hkqnQ/BmJSKzAW59MXpA=="],
"@tiptap/extension-text": ["@tiptap/extension-text@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-VcZIna93rixw7hRkHGCxDbL3kvJWi80vIT25a2pXg0WP1e7Pi3nBYvZIL4SQtkbBCji9EHrbZx3p8nNPzfazYw=="],
"@tiptap/extension-text-align": ["@tiptap/extension-text-align@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-Hmcnc10vP2TecVYEuIKpx9HPWXQ263Vaqq8BoplXIt7XQ+pCZFS/TF6F8zcClb8gMIhICI89GzF4TEvxnHlxFw=="],
"@tiptap/extension-text-align": ["@tiptap/extension-text-align@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-hebIus9tdXWb+AmhO+LTeUxZLdb0tqwdeaL/0wYxJQR5DeCTlJe6huXacMD/BkmnlEpRhxzQH0FrmXAd0d4Wgg=="],
"@tiptap/extension-text-style": ["@tiptap/extension-text-style@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-q8RM4gzmdnUHosL65SIJzTTmL29bm+3hNPdloOuJyLd4sTYs2q+cues5mH5/n85HqX3+TvKrfrTVb1Yj62E1NA=="],
"@tiptap/extension-text-style": ["@tiptap/extension-text-style@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-M7ob3pfYNYgFPihncEp33r9477hXQgC8j3iU8BsewvPlSx2bMSy5jp2XHDXyEX8dV6flr7acH4GkXXw+DHpaPA=="],
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0" } }, "sha512-D3PsS/84RlQKFjd5eerMIUioC0mNh4yy1RRV/WbXx6ugu+6T+0hT42gNk9Ap8pDsVQZCk0SHfDyBEUFC2KOwKw=="],
"@tiptap/extension-underline": ["@tiptap/extension-underline@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0" } }, "sha512-VDQi+UYw0tFnfghpthJTFmtJ3yx90kXeDwFvhmT8G+O+si5VmP05xYDBYBmYCix5jqKigJxEASiBL0gYOgMDEg=="],
"@tiptap/extensions": ["@tiptap/extensions@3.11.0", "", { "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-g43beA73ZMLezez1st9LEwYrRHZ0FLzlsSlOZKk7sdmtHLmuqWHf4oyb0XAHol1HZIdGv104rYaGNgmQXr1ecQ=="],
"@tiptap/extensions": ["@tiptap/extensions@3.13.0", "", { "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA=="],
"@tiptap/pm": ["@tiptap/pm@3.11.0", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-plCQDLCZIOc92cizB8NNhBRN0szvYR3cx9i5IXo6v9Xsgcun8KHNcJkesc2AyeqdIs0BtOJZaqQ9adHThz8UDw=="],
"@tiptap/pm": ["@tiptap/pm@3.13.0", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ=="],
"@tiptap/react": ["@tiptap/react@3.11.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "fast-deep-equal": "^3.1.3", "use-sync-external-store": "^1.4.0" }, "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.11.0", "@tiptap/extension-floating-menu": "^3.11.0" }, "peerDependencies": { "@tiptap/core": "^3.11.0", "@tiptap/pm": "^3.11.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SDGei/2DjwmhzsxIQNr6dkB6NxLgXZjQ6hF36NfDm4937r5NLrWrNk5tCsoDQiKZ0DHEzuJ6yZM5C7I7LZLB6w=="],
"@tiptap/react": ["@tiptap/react@3.13.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "fast-equals": "^5.3.3", "use-sync-external-store": "^1.4.0" }, "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.13.0", "@tiptap/extension-floating-menu": "^3.13.0" }, "peerDependencies": { "@tiptap/core": "^3.13.0", "@tiptap/pm": "^3.13.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VqpqNZ9qtPr3pWK4NsZYxXgLSEiAnzl6oS7tEGmkkvJbcGSC+F7R13Xc9twv/zT5QCLxaHdEbmxHbuAIkrMgJQ=="],
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.11.0", "", { "dependencies": { "@tiptap/core": "^3.11.0", "@tiptap/extension-blockquote": "^3.11.0", "@tiptap/extension-bold": "^3.11.0", "@tiptap/extension-bullet-list": "^3.11.0", "@tiptap/extension-code": "^3.11.0", "@tiptap/extension-code-block": "^3.11.0", "@tiptap/extension-document": "^3.11.0", "@tiptap/extension-dropcursor": "^3.11.0", "@tiptap/extension-gapcursor": "^3.11.0", "@tiptap/extension-hard-break": "^3.11.0", "@tiptap/extension-heading": "^3.11.0", "@tiptap/extension-horizontal-rule": "^3.11.0", "@tiptap/extension-italic": "^3.11.0", "@tiptap/extension-link": "^3.11.0", "@tiptap/extension-list": "^3.11.0", "@tiptap/extension-list-item": "^3.11.0", "@tiptap/extension-list-keymap": "^3.11.0", "@tiptap/extension-ordered-list": "^3.11.0", "@tiptap/extension-paragraph": "^3.11.0", "@tiptap/extension-strike": "^3.11.0", "@tiptap/extension-text": "^3.11.0", "@tiptap/extension-underline": "^3.11.0", "@tiptap/extensions": "^3.11.0", "@tiptap/pm": "^3.11.0" } }, "sha512-8kMMYqVSZ2Oqji+mY1o9meTjCRWp4DplFegu7APqDEQRhlb6mBI0wNuazYb7FKJIHJTtf0F6cYglJrxpu9c/fA=="],
"@tiptap/starter-kit": ["@tiptap/starter-kit@3.13.0", "", { "dependencies": { "@tiptap/core": "^3.13.0", "@tiptap/extension-blockquote": "^3.13.0", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-bullet-list": "^3.13.0", "@tiptap/extension-code": "^3.13.0", "@tiptap/extension-code-block": "^3.13.0", "@tiptap/extension-document": "^3.13.0", "@tiptap/extension-dropcursor": "^3.13.0", "@tiptap/extension-gapcursor": "^3.13.0", "@tiptap/extension-hard-break": "^3.13.0", "@tiptap/extension-heading": "^3.13.0", "@tiptap/extension-horizontal-rule": "^3.13.0", "@tiptap/extension-italic": "^3.13.0", "@tiptap/extension-link": "^3.13.0", "@tiptap/extension-list": "^3.13.0", "@tiptap/extension-list-item": "^3.13.0", "@tiptap/extension-list-keymap": "^3.13.0", "@tiptap/extension-ordered-list": "^3.13.0", "@tiptap/extension-paragraph": "^3.13.0", "@tiptap/extension-strike": "^3.13.0", "@tiptap/extension-text": "^3.13.0", "@tiptap/extension-underline": "^3.13.0", "@tiptap/extensions": "^3.13.0", "@tiptap/pm": "^3.13.0" } }, "sha512-Ojn6sRub04CRuyQ+9wqN62JUOMv+rG1vXhc2s6DCBCpu28lkCMMW+vTe7kXJcEdbot82+5swPbERw9vohswFzg=="],
"@trpc/client": ["@trpc/client@11.7.2", "", { "peerDependencies": { "@trpc/server": "11.7.2", "typescript": ">=5.7.2" } }, "sha512-OQxqUMfpDvjcszo9dbnqWQXnW2L5IbrKSz2H7l8s+mVM3EvYw7ztQ/gjFIN3iy0NcamiQfd4eE6qjcb9Lm+63A=="],
@@ -612,9 +613,9 @@
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
"@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="],
"@types/node": ["@types/node@20.19.26", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg=="],
"@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
"@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="],
"@types/raf": ["@types/raf@3.4.3", "", {}, "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="],
@@ -624,25 +625,25 @@
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/type-utils": "8.48.0", "@typescript-eslint/utils": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.49.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/type-utils": "8.49.0", "@typescript-eslint/utils": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.49.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JXij0vzIaTtCwu6SxTh8qBc66kmf1xs7pI4UOiMDFVct6q86G0Zs7KRcEoJgY3Cav3x5Tq0MF5jwgpgLqgKG3A=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.49.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.0", "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.49.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.49.0", "@typescript-eslint/types": "^8.49.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0" } }, "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0" } }, "sha512-npgS3zi+/30KSOkXNs0LQXtsg9ekZ8OISAOLGWA/ZOEn0ZH74Ginfl7foziV8DT+D98WfQ5Kopwqb/PZOaIJGg=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.49.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KTExJfQ+svY8I10P4HdxKzWsvtVnsuCifU5MvXrRwoP2KOlNZ9ADNEWWsQTJgMxLzS5VLQKDjkCT/YzgsnqmZg=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.48.0", "", {}, "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.49.0", "", {}, "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.0", "@typescript-eslint/tsconfig-utils": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.49.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.49.0", "@typescript-eslint/tsconfig-utils": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/visitor-keys": "8.49.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.49.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-N3W7rJw7Rw+z1tRsHZbK395TWSYvufBXumYtEGzypgMUthlg0/hmCImeA8hgO2d2G4pd7ftpxxul2J8OdtdaFA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.49.0", "", { "dependencies": { "@typescript-eslint/types": "8.49.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA=="],
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
@@ -730,13 +731,13 @@
"base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.6", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg=="],
"bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="],
"better-auth": ["better-auth@1.4.3", "", { "dependencies": { "@better-auth/core": "1.4.3", "@better-auth/telemetry": "1.4.3", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@standard-schema/spec": "^1.0.0", "better-call": "1.1.0", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" } }, "sha512-cMY6PxXZ9Ep+KmLUcVEQ5RwtZtdawxTbDqUIgIIUYWJgq0KwNkQfFNimSYjHI0cNZwwAJyvbV42+uLogsDOUqQ=="],
"better-auth": ["better-auth@1.4.6", "", { "dependencies": { "@better-auth/core": "1.4.6", "@better-auth/telemetry": "1.4.6", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.5", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "ms": "4.0.0-nightly.202508271359", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@sveltejs/kit", "@tanstack/react-start", "next", "react", "react-dom", "solid-js", "svelte", "vue"] }, "sha512-5wEBzjolrQA26b4uT6FVVYICsE3SmE/MzrZtl8cb2a3TJtswpP8v3OVV5yTso+ef9z85swgZk0/qBzcULFWVtA=="],
"better-call": ["better-call@1.1.0", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1" } }, "sha512-7CecYG+yN8J1uBJni/Mpjryp8bW/YySYsrGEWgFe048ORASjq17keGjbKI2kHEOSc6u8pi11UxzkJ7jIovQw6w=="],
"better-call": ["better-call@1.1.5", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-nQJ3S87v6wApbDwbZ++FrQiSiVxWvZdjaO+2v6lZJAG2WWggkB2CziUDjPciz3eAt9TqfRursIQMZIcpkBnvlw=="],
"bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
@@ -906,7 +907,7 @@
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
"eslint-config-next": ["eslint-config-next@16.0.5", "", { "dependencies": { "@next/eslint-plugin-next": "16.0.5", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-9rBjZ/biSpolkIUiqvx/iwJJaz8sxJ6pKWSPptJenpj01HlWbCDeaA1v0yG3a71IIPMplxVCSXhmtP27SXqMdg=="],
"eslint-config-next": ["eslint-config-next@16.0.10", "", { "dependencies": { "@next/eslint-plugin-next": "16.0.10", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-BxouZUm0I45K4yjOOIzj24nTi0H2cGo0y7xUmk+Po/PYtJXFBYVDS1BguE7t28efXjKdcN0tmiLivxQy//SsZg=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
@@ -924,8 +925,6 @@
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
"eslint-plugin-react-perf": ["eslint-plugin-react-perf@3.3.3", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-EzPdxsRJg5IllCAH9ny/3nK7sv9251tvKmi/d3Ouv5KzI8TB3zNhzScxL9wnh9Hvv8GYC5LEtzTauynfOEYiAw=="],
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
@@ -946,6 +945,8 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="],
"fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
@@ -974,7 +975,7 @@
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="],
"framer-motion": ["framer-motion@12.23.26", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -1006,8 +1007,6 @@
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
@@ -1210,7 +1209,7 @@
"motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"ms": ["ms@4.0.0-nightly.202508271359", "", {}, "sha512-WC/Eo7NzFrOV/RRrTaI0fxKVbNCzEy76j2VqNV8SxDf9D69gSE2Lh0QwYvDlhiYmheBYExAvEAxVf5NoN0cj2A=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
@@ -1220,7 +1219,9 @@
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"next": ["next@16.0.7", "", { "dependencies": { "@next/env": "16.0.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.7", "@next/swc-darwin-x64": "16.0.7", "@next/swc-linux-arm64-gnu": "16.0.7", "@next/swc-linux-arm64-musl": "16.0.7", "@next/swc-linux-x64-gnu": "16.0.7", "@next/swc-linux-x64-musl": "16.0.7", "@next/swc-win32-arm64-msvc": "16.0.7", "@next/swc-win32-x64-msvc": "16.0.7", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A=="],
"next": ["next@16.0.10", "", { "dependencies": { "@next/env": "16.0.10", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.10", "@next/swc-darwin-x64": "16.0.10", "@next/swc-linux-arm64-gnu": "16.0.10", "@next/swc-linux-arm64-musl": "16.0.10", "@next/swc-linux-x64-gnu": "16.0.10", "@next/swc-linux-x64-musl": "16.0.10", "@next/swc-win32-arm64-msvc": "16.0.10", "@next/swc-win32-x64-msvc": "16.0.10", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA=="],
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
@@ -1304,7 +1305,7 @@
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.14", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg=="],
@@ -1354,11 +1355,11 @@
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
"react-day-picker": ["react-day-picker@9.11.2", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-TD/xMUGg2oiKX8jUR21MST5pj+7Y36097YtnDHQFlIcZOu3mbLLw2B2JqEByEGrR3HHveWYnKlyls6WqJgohAg=="],
"react-day-picker": ["react-day-picker@9.12.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-t8OvG/Zrciso5CQJu5b1A7yzEmebvST+S3pOVQJWxwjjVngyG/CA2htN/D15dLI4uTEuLLkbZyS4YYt480FAtA=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"react-dropzone": ["react-dropzone@14.3.8", "", { "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">= 16.8 || 18.0.0" } }, "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug=="],
@@ -1374,7 +1375,7 @@
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
"recharts": ["recharts@3.5.0", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eslint-plugin-react-perf": "^3.3.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-jWqBtu8L3VICXWa3g/y+bKjL8DDHSRme7DHD/70LQ/Tk0di1h11Y0kKC0nPh6YJ2oaa0k6anIFNhg6SfzHWdEA=="],
"recharts": ["recharts@3.5.1", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+v+HJojK7gnEgG6h+b2u7k8HH7FhyFUzAc4+cPrsjL4Otdgqr/ecXzAnHciqlzV1ko064eNcsdzrYOM78kankA=="],
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
@@ -1402,7 +1403,7 @@
"rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="],
"rou3": ["rou3@0.5.1", "", {}, "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ=="],
"rou3": ["rou3@0.7.10", "", {}, "sha512-aoFj6f7MJZ5muJ+Of79nrhs9N3oLGqi2VEMe94Zbkjb6Wupha46EuoYgpWSOZlXww3bbd8ojgXTAA2mzimX5Ww=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@@ -1482,7 +1483,7 @@
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"superjson": ["superjson@2.2.5", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w=="],
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -1492,7 +1493,7 @@
"tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
"tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
@@ -1526,7 +1527,7 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.48.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.0", "@typescript-eslint/parser": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw=="],
"typescript-eslint": ["typescript-eslint@8.49.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.49.0", "@typescript-eslint/parser": "8.49.0", "@typescript-eslint/typescript-estree": "8.49.0", "@typescript-eslint/utils": "8.49.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zRSVH1WXD0uXczCXw+nsdjGPUdx4dfrs5VQoHnUWmv1U3oNlAKv4FUNdLDhVUg+gYn+a5hUESqch//Rv5wVhrg=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
@@ -1632,6 +1633,8 @@
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
"@react-email/render/prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"@react-pdf/reconciler/scheduler": ["scheduler@0.25.0-rc-603e6108-20241029", "", {}, "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
@@ -1640,7 +1643,7 @@
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
@@ -1650,14 +1653,20 @@
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"better-auth/zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
"brotli/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="],
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -1737,5 +1746,11 @@
"@typescript-eslint/typescript-estree/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"eslint-import-resolver-node/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"eslint-module-utils/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"eslint-plugin-import/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
}
}

View File

@@ -45,64 +45,65 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@react-pdf/renderer": "^4.3.1",
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.90.10",
"@tanstack/react-query": "^5.90.12",
"@tanstack/react-table": "^8.21.3",
"@tiptap/extension-color": "^3.11.0",
"@tiptap/extension-list-item": "^3.11.0",
"@tiptap/extension-text-align": "^3.11.0",
"@tiptap/extension-text-style": "^3.11.0",
"@tiptap/react": "^3.11.0",
"@tiptap/starter-kit": "^3.11.0",
"@tiptap/extension-color": "^3.13.0",
"@tiptap/extension-list-item": "^3.13.0",
"@tiptap/extension-text-align": "^3.13.0",
"@tiptap/extension-text-style": "^3.13.0",
"@tiptap/react": "^3.13.0",
"@tiptap/starter-kit": "^3.13.0",
"@trpc/client": "^11.7.2",
"@trpc/react-query": "^11.7.2",
"@trpc/server": "^11.7.2",
"bcryptjs": "^3.0.3",
"better-auth": "^1.4.3",
"better-auth": "^1.4.6",
"chrono-node": "^2.9.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.44.7",
"file-saver": "^2.0.5",
"framer-motion": "^12.23.24",
"framer-motion": "^12.23.26",
"lucide-react": "^0.525.0",
"next": "^16.0.7",
"next": "^16.0.10",
"next-themes": "^0.4.6",
"pg": "^8.16.3",
"react": "^19.2.1",
"react-day-picker": "^9.11.2",
"react-dom": "^19.2.0",
"react": "^19.2.3",
"react-day-picker": "^9.12.0",
"react-dom": "^19.2.3",
"react-dropzone": "^14.3.8",
"recharts": "^3.5.0",
"recharts": "^3.5.1",
"resend": "^4.8.0",
"server-only": "^0.0.1",
"sonner": "^2.0.7",
"superjson": "^2.2.5",
"superjson": "^2.2.6",
"tailwind-merge": "^3.4.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.17",
"@tailwindcss/postcss": "^4.1.18",
"@types/bcryptjs": "^2.4.6",
"@types/file-saver": "^2.0.7",
"@types/node": "^20.19.25",
"@types/pg": "^8.15.6",
"@types/node": "^20.19.26",
"@types/pg": "^8.16.0",
"@types/raf": "^3.4.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"baseline-browser-mapping": "^2.8.32",
"baseline-browser-mapping": "^2.9.6",
"dotenv": "^17.2.3",
"drizzle-kit": "^0.30.6",
"eslint": "^9.39.1",
"eslint-config-next": "^16.0.5",
"eslint-config-next": "^16.0.10",
"eslint-plugin-drizzle": "^0.2.3",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"prettier": "^3.7.4",
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwindcss": "^4.1.17",
"tailwindcss": "^4.1.18",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.0"
"typescript-eslint": "^8.49.0"
},
"ct3aMetadata": {
"initVersion": "7.39.3"

View File

@@ -48,18 +48,24 @@ function SignInForm() {
}
return (
<div className="bg-background flex min-h-screen items-center justify-center">
<Card className="mx-auto h-screen w-full overflow-hidden border-0 shadow-none md:h-auto md:max-w-6xl md:border md:shadow-lg">
<div className="flex min-h-screen items-center justify-center relative overflow-hidden">
{/* Blob Background */}
<div className="fixed inset-0 -z-10 overflow-hidden pointer-events-none flex items-center justify-center">
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
<div className="w-[800px] h-[800px] bg-neutral-400/30 dark:bg-neutral-500/20 rounded-full blur-3xl animate-blob"></div>
</div>
<Card className="mx-auto h-screen w-full overflow-hidden border-0 shadow-none md:h-auto md:max-w-6xl md:border md:shadow-2xl md:bg-background/80 md:backdrop-blur-xl md:border-border/50 md:rounded-3xl">
<CardContent className="grid h-full p-0 md:grid-cols-2">
{/* Hero Section - Hidden on mobile */}
<div className="bg-muted relative hidden md:flex md:flex-col md:justify-center md:p-12">
<div className="bg-primary/5 relative hidden md:flex md:flex-col md:justify-center md:p-12 border-r border-border/50">
<div className="space-y-8">
<div className="space-y-4">
<Logo size="xl" />
<div className="space-y-3">
<h1 className="text-3xl font-bold lg:text-4xl">
<h1 className="text-3xl font-bold lg:text-4xl font-heading">
Welcome back to your
<span className="text-primary"> invoicing workspace</span>
<span className="text-primary italic"> invoicing workspace</span>
</h1>
<p className="text-muted-foreground text-lg">
Continue managing your clients and creating professional
@@ -68,13 +74,13 @@ function SignInForm() {
</div>
</div>
<div className="grid gap-4">
<div className="grid gap-6">
<div className="flex items-start space-x-4">
<div className="bg-primary/10 rounded-lg p-2">
<div className="bg-primary/10 rounded-xl p-3">
<Users className="text-primary h-5 w-5" />
</div>
<div className="space-y-1">
<h3 className="font-semibold">Client Management</h3>
<h3 className="font-semibold text-foreground">Client Management</h3>
<p className="text-muted-foreground text-sm">
Organize and track all your clients in one place
</p>
@@ -82,11 +88,11 @@ function SignInForm() {
</div>
<div className="flex items-start space-x-4">
<div className="bg-primary/10 rounded-lg p-2">
<div className="bg-primary/10 rounded-xl p-3">
<FileText className="text-primary h-5 w-5" />
</div>
<div className="space-y-1">
<h3 className="font-semibold">Professional Invoices</h3>
<h3 className="font-semibold text-foreground">Professional Invoices</h3>
<p className="text-muted-foreground text-sm">
Beautiful templates that get you paid faster
</p>
@@ -94,11 +100,11 @@ function SignInForm() {
</div>
<div className="flex items-start space-x-4">
<div className="bg-primary/10 rounded-lg p-2">
<div className="bg-primary/10 rounded-xl p-3">
<TrendingUp className="text-primary h-5 w-5" />
</div>
<div className="space-y-1">
<h3 className="font-semibold">Payment Tracking</h3>
<h3 className="font-semibold text-foreground">Payment Tracking</h3>
<p className="text-muted-foreground text-sm">
Monitor your income with real-time insights
</p>
@@ -117,7 +123,7 @@ function SignInForm() {
</div>
<div className="space-y-2 text-center md:text-left">
<h1 className="text-2xl font-bold">Sign In</h1>
<h1 className="text-3xl font-bold font-heading">Sign In</h1>
<p className="text-muted-foreground">
Enter your credentials to access your account
</p>
@@ -135,7 +141,7 @@ function SignInForm() {
onChange={(e) => setEmail(e.target.value)}
required
autoFocus
className="h-11 pl-10"
className="h-11 pl-10 bg-background/50 border-border/60 focus:bg-background transition-all"
placeholder="m@example.com"
/>
</div>
@@ -159,7 +165,7 @@ function SignInForm() {
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="h-11 pl-10"
className="h-11 pl-10 bg-background/50 border-border/60 focus:bg-background transition-all"
placeholder="Enter your password"
/>
</div>
@@ -167,7 +173,7 @@ function SignInForm() {
<Button
type="submit"
className="h-11 w-full"
className="h-11 w-full rounded-xl text-base shadow-lg shadow-primary/20 hover:shadow-primary/30"
disabled={loading}
>
{loading ? (

View File

@@ -46,11 +46,11 @@ export function InvoiceStatusChart({ invoices }: InvoiceStatusChartProps) {
// Use theme-aware colors
const COLORS = {
draft: "hsl(0, 0%, 60%)", // grey
sent: "hsl(214, 100%, 50%)", // blue
pending: "hsl(45, 100%, 50%)", // yellow
paid: "hsl(142, 76%, 36%)", // green
overdue: "hsl(0, 84%, 60%)", // red
draft: "hsl(0, 0%, 60%)", // neutral grey - matches monthly metrics chart
sent: "hsl(217, 91%, 60%)", // vibrant blue
pending: "hsl(217, 91%, 60%)", // blue
paid: "hsl(142, 71%, 45%)", // vibrant green
overdue: "hsl(var(--destructive))", // red
};
// Animation / motion preferences
const { prefersReducedMotion, animationSpeedMultiplier } =

View File

@@ -118,17 +118,17 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
<div className="bg-card border-border rounded-lg border p-3 shadow-lg">
<p className="font-medium">{label}</p>
<div className="space-y-1 text-sm">
<p style={{ color: "var(--chart-2)" }}>Paid: {data.paidInvoices}</p>
<p style={{ color: "var(--chart-1)" }}>
<p className="text-primary font-medium">Paid: {data.paidInvoices}</p>
<p className="text-primary/80">
Pending: {data.pendingInvoices}
</p>
<p style={{ color: "var(--chart-3)" }}>
<p className="text-destructive">
Overdue: {data.overdueInvoices}
</p>
<p style={{ color: "hsl(0, 0%, 60%)" }}>
<p className="text-muted-foreground">
Draft: {data.draftInvoices}
</p>
<p className="text-muted-foreground border-t pt-1">
<p className="text-foreground font-medium border-t pt-1">
Total: {data.totalInvoices}
</p>
</div>
@@ -182,7 +182,7 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
<Bar
dataKey="paidInvoices"
stackId="a"
fill="var(--chart-2)"
fill="hsl(142, 71%, 45%)"
radius={[0, 0, 0, 0]}
isAnimationActive={!prefersReducedMotion}
animationDuration={barAnimationDuration}
@@ -191,7 +191,8 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
<Bar
dataKey="pendingInvoices"
stackId="a"
fill="var(--chart-1)"
fill="hsl(217, 91%, 60%)"
fillOpacity={0.6}
radius={[0, 0, 0, 0]}
isAnimationActive={!prefersReducedMotion}
animationDuration={barAnimationDuration}
@@ -200,7 +201,7 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
<Bar
dataKey="overdueInvoices"
stackId="a"
fill="var(--chart-3)"
fill="hsl(var(--destructive))"
radius={[2, 2, 0, 0]}
isAnimationActive={!prefersReducedMotion}
animationDuration={barAnimationDuration}
@@ -222,21 +223,20 @@ export function MonthlyMetricsChart({ invoices }: MonthlyMetricsChartProps) {
<div className="flex items-center space-x-2">
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: "var(--chart-2)" }}
style={{ backgroundColor: "hsl(142, 71%, 45%)" }}
/>
<span className="text-xs">Paid</span>
</div>
<div className="flex items-center space-x-2">
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: "var(--chart-1)" }}
style={{ backgroundColor: "hsl(217, 91%, 60%)", opacity: 0.6 }}
/>
<span className="text-xs">Pending</span>
</div>
<div className="flex items-center space-x-2">
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: "var(--chart-3)" }}
className="h-3 w-3 rounded-full bg-destructive"
/>
<span className="text-xs">Overdue</span>
</div>

View File

@@ -88,10 +88,10 @@ export function RevenueChart({ data }: RevenueChartProps) {
<AreaChart data={chartData}>
<defs>
<linearGradient id="revenueGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="hsl(0, 0%, 60%)" stopOpacity={0.4} />
<stop offset="5%" stopColor="hsl(217, 91%, 60%)" stopOpacity={0.4} />
<stop
offset="95%"
stopColor="hsl(0, 0%, 60%)"
stopColor="hsl(217, 91%, 60%)"
stopOpacity={0.05}
/>
</linearGradient>
@@ -100,19 +100,19 @@ export function RevenueChart({ data }: RevenueChartProps) {
dataKey="monthLabel"
axisLine={false}
tickLine={false}
tick={{ fontSize: 12, fill: "var(--muted-foreground)" }}
tick={{ fontSize: 12, fill: "hsl(var(--muted-foreground))" }}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fontSize: 12, fill: "var(--muted-foreground)" }}
tick={{ fontSize: 12, fill: "hsl(var(--muted-foreground))" }}
tickFormatter={formatCurrency}
/>
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="revenue"
stroke="hsl(0, 0%, 60%)"
stroke="hsl(217, 91%, 60%)"
strokeWidth={2}
fill="url(#revenueGradient)"
isAnimationActive={!prefersReducedMotion}

View File

@@ -1,44 +1,45 @@
import { Card, CardContent, CardHeader } from "~/components/ui/card";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Separator } from "~/components/ui/separator";
import { Skeleton } from "~/components/ui/skeleton";
import { PageHeader } from "~/components/layout/page-header";
export function InvoiceDetailsSkeleton() {
return (
<div className="space-y-6 pb-24">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<Skeleton className="bg-muted/30 h-8 w-48 sm:h-9 sm:w-64" />
<Skeleton className="bg-muted/30 mt-1 h-4 w-40 sm:w-48" />
</div>
<div className="flex items-center gap-2">
<Skeleton className="bg-muted/30 h-8 w-20 sm:h-9 sm:w-24" />
<Skeleton className="bg-muted/30 h-8 w-16 sm:h-9 sm:w-20" />
</div>
</div>
<PageHeader
title="Loading..."
description="View and manage invoice information"
variant="gradient"
>
<Skeleton className="h-10 w-10 sm:w-32" />
<Skeleton className="h-10 w-24" />
</PageHeader>
{/* Content */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
{/* Left Column */}
<div className="space-y-6 lg:col-span-2">
{/* Invoice Header Skeleton */}
<Card className="bg-card border-border border">
<Card>
<CardContent className="p-4 sm:p-6">
<div className="space-y-4">
<div className="flex items-start justify-between gap-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
<div className="min-w-0 flex-1 space-y-2">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
<Skeleton className="bg-muted/30 h-6 w-40 sm:h-8 sm:w-48" />
<Skeleton className="bg-muted/30 h-5 w-16 sm:h-6" />
<Skeleton className="h-8 w-48" />
<Skeleton className="h-6 w-24 rounded-full" />
</div>
<div className="space-y-1 sm:space-y-0">
<Skeleton className="bg-muted/30 h-3 w-32 sm:h-4 sm:w-40" />
<Skeleton className="bg-muted/30 h-3 w-28 sm:hidden sm:h-4 sm:w-36" />
<div className="space-y-1 sm:space-y-0 text-sm">
<div className="flex gap-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-32 hidden sm:block" />
</div>
</div>
</div>
<div className="flex-shrink-0 text-right">
<Skeleton className="bg-muted/30 h-3 w-20 sm:h-4" />
<Skeleton className="bg-muted/30 mt-1 h-6 w-24 sm:h-8 sm:w-28" />
<div className="flex-shrink-0 text-left sm:text-right">
<Skeleton className="h-4 w-24 mb-1 sm:ml-auto" />
<Skeleton className="h-9 w-32 sm:ml-auto" />
</div>
</div>
</div>
@@ -47,105 +48,126 @@ export function InvoiceDetailsSkeleton() {
{/* Client & Business Info */}
<div className="grid gap-4 sm:grid-cols-2">
{Array.from({ length: 2 }).map((_, i) => (
<Card key={i} className="bg-card border-border border">
<CardHeader className="pb-3">
<div className="flex items-center gap-2">
<Skeleton className="bg-muted/30 h-4 w-4 sm:h-5 sm:w-5" />
<Skeleton className="bg-muted/30 h-5 w-16 sm:h-6" />
{/* Client Skeleton */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2">
<Skeleton className="h-5 w-5 rounded-full" />
<Skeleton className="h-5 w-16" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Skeleton className="h-7 w-48" />
<div className="space-y-3">
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-md" />
<Skeleton className="h-4 w-40" />
</div>
</CardHeader>
<CardContent className="space-y-4">
<Skeleton className="bg-muted/30 h-5 w-32 sm:h-6" />
<div className="space-y-3">
{Array.from({ length: 3 }).map((_, j) => (
<div key={j} className="flex items-center gap-3">
<Skeleton className="bg-muted/30 h-8 w-8 " />
<Skeleton className="bg-muted/30 h-4 w-28" />
</div>
))}
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-md" />
<Skeleton className="h-4 w-32" />
</div>
</CardContent>
</Card>
))}
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-md" />
<div className="space-y-1">
<Skeleton className="h-4 w-48" />
<Skeleton className="h-4 w-32" />
</div>
</div>
</div>
</CardContent>
</Card>
{/* Business Skeleton */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2">
<Skeleton className="h-5 w-5 rounded-full" />
<Skeleton className="h-5 w-16" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<Skeleton className="h-7 w-48" />
<div className="space-y-3">
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-md" />
<Skeleton className="h-4 w-40" />
</div>
<div className="flex items-center gap-3">
<Skeleton className="h-8 w-8 rounded-md" />
<Skeleton className="h-4 w-32" />
</div>
</div>
</CardContent>
</Card>
</div>
{/* Invoice Items Skeleton */}
<Card className="bg-card border-border border">
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Skeleton className="bg-muted/30 h-4 w-4 sm:h-5 sm:w-5" />
<Skeleton className="bg-muted/30 h-5 w-28 sm:h-6" />
</div>
<CardTitle className="flex items-center gap-2">
<Skeleton className="h-5 w-5 rounded-full" />
<Skeleton className="h-5 w-32" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Item Rows */}
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="space-y-3 border p-4">
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<Skeleton className="bg-muted/30 mb-2 h-4 w-full sm:h-5 sm:w-3/4" />
<div className="space-y-1 sm:space-y-0">
<Skeleton className="bg-muted/30 h-3 w-20 sm:h-4 sm:w-24" />
<Skeleton className="bg-muted/30 h-3 w-16 sm:hidden sm:h-4 sm:w-20" />
<Skeleton className="bg-muted/30 h-3 w-24 sm:hidden sm:h-4 sm:w-28" />
<Card key={i} className="bg-secondary/50 border-0">
<CardContent className="p-3">
<div className="space-y-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 flex-1">
<Skeleton className="h-5 w-3/4 mb-2" />
<div className="flex gap-4">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-16" />
<Skeleton className="h-4 w-20" />
</div>
</div>
<Skeleton className="h-6 w-24" />
</div>
</div>
<div className="flex-shrink-0 text-right">
<Skeleton className="bg-muted/30 h-4 w-16 sm:h-5 sm:w-20" />
</div>
</div>
</div>
</CardContent>
</Card>
))}
{/* Totals */}
<div className="bg-muted/30 p-4">
<div className="bg-secondary rounded-lg p-4">
<div className="space-y-3">
<div className="flex justify-between">
<Skeleton className="bg-muted/30 h-4 w-16" />
<Skeleton className="bg-muted/30 h-4 w-20" />
<Skeleton className="h-4 w-20" />
<Skeleton className="h-4 w-24" />
</div>
<div className="flex justify-between">
<Skeleton className="bg-muted/30 h-4 w-20" />
<Skeleton className="bg-muted/30 h-4 w-16" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-24" />
</div>
<Separator />
<div className="flex justify-between">
<Skeleton className="bg-muted/30 h-5 w-12" />
<Skeleton className="bg-muted/30 h-5 w-24" />
<Skeleton className="h-6 w-16" />
<Skeleton className="h-6 w-32" />
</div>
</div>
</div>
</CardContent>
</Card>
{/* Notes */}
<Card className="bg-card border-border border">
<CardHeader>
<Skeleton className="bg-muted/30 h-6 w-16" />
</CardHeader>
<CardContent>
<div className="space-y-2">
<Skeleton className="bg-muted/30 h-4 w-full" />
<Skeleton className="bg-muted/30 h-4 w-3/4" />
<Skeleton className="bg-muted/30 h-4 w-1/2" />
</div>
</CardContent>
</Card>
</div>
{/* Right Column - Actions */}
<div className="space-y-6">
<Card className="bg-card border-border border sticky top-6">
<Card className="sticky top-20">
<CardHeader>
<div className="flex items-center gap-2">
<Skeleton className="bg-muted/30 h-5 w-5" />
<Skeleton className="bg-muted/30 h-6 w-16" />
</div>
<CardTitle className="flex items-center gap-2">
<Skeleton className="h-5 w-5 rounded-full" />
<Skeleton className="h-5 w-24" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="bg-muted/30 h-10 w-full" />
))}
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
<Skeleton className="h-10 w-full" />
</CardContent>
</Card>
</div>

View File

@@ -1,13 +1,11 @@
import "~/styles/globals.css";
import { type Metadata } from "next";
import { Geist, Geist_Mono, Instrument_Serif } from "next/font/google";
import { Inter, Playfair_Display, Geist_Mono } from "next/font/google";
import { TRPCReactProvider } from "~/trpc/react";
import { Toaster } from "~/components/ui/sonner";
import { AnimationPreferencesProvider } from "~/components/providers/animation-preferences-provider";
import { MotionBackground } from "~/components/layout/motion-background";
import { ThemeProvider } from "~/components/providers/theme-provider";
import { ColorThemeProvider } from "~/components/providers/color-theme-provider";
import { UmamiScript } from "~/components/analytics/umami-script";
@@ -19,9 +17,15 @@ export const metadata: Metadata = {
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
const geistSans = Geist({
const inter = Inter({
subsets: ["latin"],
variable: "--font-geist-sans",
variable: "--font-sans",
display: "swap",
});
const playfair = Playfair_Display({
subsets: ["latin"],
variable: "--font-heading",
display: "swap",
});
@@ -31,13 +35,6 @@ const geistMono = Geist_Mono({
display: "swap",
});
const instrumentSerif = Instrument_Serif({
subsets: ["latin"],
variable: "--font-serif",
display: "swap",
weight: "400",
});
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
@@ -45,105 +42,21 @@ export default function RootLayout({
<html
suppressHydrationWarning
lang="en"
className={`${geistSans.variable} ${geistMono.variable} ${instrumentSerif.variable}`}
className={`${inter.variable} ${playfair.variable} ${geistMono.variable}`}
>
<head>
{/* Inline early theme and animation preference script to avoid FOUC */}
<script
dangerouslySetInnerHTML={{
__html: `(function(){
try {
var root = document.documentElement;
// Mode theme persistence (light/dark/system)
var modeTheme = localStorage.getItem('theme');
var systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
root.classList.remove('light', 'dark');
if (modeTheme === 'dark' || modeTheme === 'light') {
root.classList.add(modeTheme);
} else {
// Default to system if no preference or 'system'
root.classList.add(systemTheme);
}
// Color theme persistence (custom accent colors)
var customColor = localStorage.getItem('customThemeColor');
var isCustom = localStorage.getItem('isCustomTheme') === 'true';
if (isCustom && customColor) {
try {
var themeData = JSON.parse(customColor);
if (themeData && themeData.colors && themeData.colors.light) {
// Apply saved colors directly
for (var key in themeData.colors.light) {
if (themeData.colors.light.hasOwnProperty(key)) {
root.style.setProperty(key, themeData.colors.light[key]);
}
}
}
} catch (e) {
// Fallback logic omitted for brevity, relying on provider for full recovery
}
} else {
// Apply preset color theme
var colorTheme = localStorage.getItem('color-theme');
if (colorTheme) {
root.classList.add(colorTheme);
} else {
root.classList.add('slate'); // Default
}
}
// Animation preferences script (existing)
var STORAGE_KEY='bv.animation.prefs';
var raw=localStorage.getItem(STORAGE_KEY);
var prefersReduced=false;
var speed=1;
if(raw){
try{
var parsed=JSON.parse(raw);
if(typeof parsed.prefersReducedMotion==='boolean'){
prefersReduced=parsed.prefersReducedMotion;
}else{
prefersReduced=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
if(typeof parsed.animationSpeedMultiplier==='number'){
speed=parsed.animationSpeedMultiplier;
if(isNaN(speed)||speed<0.25||speed>4)speed=1;
}
}catch(e){}
}else{
prefersReduced=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
if(prefersReduced)root.classList.add('user-reduce-motion');
function apply(fast,normal,slow){
root.style.setProperty('--animation-speed-fast',fast+'s');
root.style.setProperty('--animation-speed-normal',normal+'s');
root.style.setProperty('--animation-speed-slow',slow+'s');
}
if(prefersReduced){
apply(0.01,0.01,0.01);
}else{
var fast=(0.15/speed).toFixed(4);
var normal=(0.30/speed).toFixed(4);
var slow=(0.50/speed).toFixed(4);
apply(fast,normal,slow);
}
} catch(_e) {}
})();`,
}}
/>
</head>
<body className="bg-background text-foreground relative min-h-screen overflow-x-hidden font-sans antialiased">
<div className="fixed inset-0 -z-10 overflow-hidden pointer-events-none flex items-center justify-center">
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
<div className="w-[800px] h-[800px] bg-neutral-400/40 dark:bg-neutral-500/30 rounded-full blur-3xl animate-blob"></div>
</div>
<TRPCReactProvider>
<ThemeProvider>
<ColorThemeProvider>
<AnimationPreferencesProvider>
<MotionBackground />
{children}
<div className="relative z-10">
{children}
</div>
</AnimationPreferencesProvider>
<Toaster />
<UmamiScript />

View File

@@ -16,15 +16,21 @@ import {
export default function HomePage() {
return (
<div className="bg-background min-h-screen">
<div className="min-h-screen relative overflow-x-hidden">
<AuthRedirect />
{/* Blob Background for Homepage */}
<div className="fixed inset-0 -z-10 overflow-hidden pointer-events-none flex items-center justify-center">
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
<div className="w-[800px] h-[800px] bg-neutral-400/30 dark:bg-neutral-500/20 rounded-full blur-3xl animate-blob"></div>
</div>
{/* Navigation */}
<nav className="bg-background border-border sticky top-0 z-50 border-b">
<div className="container mx-auto px-4">
<div className="flex h-14 items-center justify-between sm:h-16">
<nav className="fixed top-4 left-4 right-4 z-50 m-4 rounded-2xl border border-border/60 bg-background/80 backdrop-blur-md">
<div className="mx-auto px-6">
<div className="flex h-16 items-center justify-between">
<Logo />
<div className="hidden items-center space-x-6 md:flex">
<div className="hidden items-center space-x-8 md:flex">
<a
href="#features"
className="text-muted-foreground hover:text-foreground text-sm font-medium transition-colors"
@@ -38,21 +44,19 @@ export default function HomePage() {
Pricing
</a>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-4">
<Link href="/auth/signin">
<Button
variant="ghost"
size="sm"
className="text-muted-foreground hover:text-foreground"
>
<span className="hidden sm:inline">Sign In</span>
<span className="sm:hidden">Sign In</span>
Sign In
</Button>
</Link>
<Link href="/auth/register">
<Button size="sm" variant="default">
<span className="hidden sm:inline">Get Started</span>
<span className="sm:hidden">Start</span>
<Button size="sm" variant="default" className="rounded-xl px-6">
Get Started
</Button>
</Link>
</div>
@@ -61,248 +65,156 @@ export default function HomePage() {
</nav>
{/* Hero Section */}
<section className="bg-background relative overflow-hidden px-4 pt-12 pb-16 sm:pt-20">
<div className="relative container mx-auto text-center">
<section className="relative pt-48 pb-32">
<div className="container mx-auto px-4 text-center">
<div className="mx-auto max-w-4xl">
<Badge className="bg-primary/10 text-primary border-primary/20 mb-4 border sm:mb-6">
<Zap className="mr-1 h-3 w-3" />
Free Forever
<Badge className="bg-primary/10 text-primary border-primary/20 mb-8 border px-4 py-1 text-sm rounded-full">
<Zap className="mr-2 h-3.5 w-3.5" />
Completely Free for Everyone
</Badge>
<h1 className="text-foreground mb-4 text-4xl font-bold tracking-tight sm:mb-6 sm:text-6xl lg:text-7xl">
Simple Invoicing for
<span className="text-primary block">Freelancers</span>
<h1 className="text-foreground mb-8 text-6xl font-heading font-bold tracking-tight sm:text-7xl lg:text-8xl leading-tight">
Invoicing Made <br />
<span className="text-primary italic">Beautifully Simple.</span>
</h1>
<p className="text-muted-foreground mx-auto mb-6 max-w-2xl text-lg leading-relaxed sm:mb-8 sm:text-xl">
Create professional invoices, manage clients, and track payments.
Built for freelancers and small businesses
<span className="text-foreground font-semibold">
completely free
</span>
.
<p className="text-muted-foreground mx-auto mb-12 max-w-2xl text-xl leading-relaxed font-sans">
Create professional invoices, manage clients, and track payments with a tool that feels as good as it looks.
</p>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<div className="flex flex-col items-center gap-6 sm:flex-row sm:justify-center">
<Link href="/auth/register">
<Button
size="lg"
variant="default"
className="group w-full px-6 py-3 text-base font-semibold sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
className="h-14 px-10 text-lg rounded-2xl shadow-xl shadow-primary/20 hover:shadow-2xl hover:shadow-primary/30 transition-all duration-300"
>
Get Started
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
Start For Free
<ArrowRight className="ml-2 h-5 w-5" />
</Button>
</Link>
<Link href="#features">
<a href="#features">
<Button
variant="outline"
size="lg"
className="group w-full px-6 py-3 text-base sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
className="h-14 px-10 text-lg rounded-2xl border-border/50 bg-background/50 hover:bg-background/80 backdrop-blur-sm"
>
Learn More
<ChevronRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
</Button>
</Link>
</a>
</div>
<div className="text-muted-foreground mt-8 flex flex-col items-center justify-center gap-2 text-sm sm:mt-12 sm:flex-row sm:gap-6">
{[
"No credit card required",
"Setup in 2 minutes",
"Free forever",
].map((text, i) => (
<div key={i} className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-center">{text}</span>
</div>
))}
<div className="mt-16 text-muted-foreground/80 flex flex-col items-center justify-center gap-2 text-sm sm:flex-row sm:gap-8">
<div className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span>No credit card required</span>
</div>
<div className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span>Setup in 2 minutes</span>
</div>
<div className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span>Free forever</span>
</div>
</div>
</div>
</div>
</section>
{/* Features Section */}
<section
id="features"
className="bg-muted/20 relative overflow-hidden py-16 sm:py-24"
>
<div className="relative container mx-auto px-4">
<div className="mb-12 text-center sm:mb-16">
<Badge className="bg-primary/10 text-primary border-primary/20 mb-4 border">
<Zap className="mr-1 h-3 w-3" />
Features
</Badge>
<h2 className="text-foreground mb-4 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
Everything you need to
<span className="text-primary block">get paid</span>
<section id="features" className="py-24 relative">
<div className="container mx-auto px-4 relative z-10">
<div className="mb-20 text-center">
<h2 className="text-foreground mb-6 text-4xl font-heading font-bold sm:text-5xl">
Everything you need to <span className="italic text-primary">thrive</span>
</h2>
<p className="text-muted-foreground mx-auto max-w-2xl text-lg sm:text-xl">
Simple, powerful features for freelancers and small businesses.
<p className="text-muted-foreground mx-auto max-w-2xl text-lg">
Powerful features wrapped in a calm, focused interface.
</p>
</div>
<div className="grid gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3">
{/* Feature 1 */}
<Card className="bg-card border-border hover:border-primary/20 border transition-all">
<CardContent className="p-6 sm:p-8">
<div className="bg-primary/10 text-primary mb-4 inline-flex p-3">
<Rocket className="h-6 w-6" />
</div>
<h3 className="text-foreground mb-3 text-xl font-bold">
Quick Setup
</h3>
<p className="text-muted-foreground mb-4">
Start creating invoices immediately. No complicated setup
required.
</p>
<ul className="space-y-2">
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Simple client management
</span>
</li>
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Professional templates
</span>
</li>
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Easy invoice sending
</span>
</li>
</ul>
</CardContent>
</Card>
{/* Feature 2 */}
<Card className="bg-card border-border hover:border-primary/20 border transition-all">
<CardContent className="p-6 sm:p-8">
<div className="bg-primary/10 text-primary mb-4 inline-flex p-3">
<BarChart3 className="h-6 w-6" />
</div>
<h3 className="text-foreground mb-3 text-xl font-bold">
Payment Tracking
</h3>
<p className="text-muted-foreground mb-4">
Keep track of invoice status and monitor payments.
</p>
<ul className="space-y-2">
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Invoice status tracking
</span>
</li>
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Payment history
</span>
</li>
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Overdue notifications
</span>
</li>
</ul>
</CardContent>
</Card>
{/* Feature 3 */}
<Card className="bg-card border-border hover:border-primary/20 border transition-all">
<CardContent className="p-6 sm:p-8">
<div className="bg-primary/10 text-primary mb-4 inline-flex p-3">
<Shield className="h-6 w-6" />
</div>
<h3 className="text-foreground mb-3 text-xl font-bold">
Professional Features
</h3>
<p className="text-muted-foreground mb-4">
Professional features to help you get paid on time.
</p>
<ul className="space-y-2">
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
PDF generation
</span>
</li>
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Custom tax rates
</span>
</li>
<li className="flex items-center gap-2">
<Check className="text-primary h-4 w-4" />
<span className="text-muted-foreground text-sm">
Professional numbering
</span>
</li>
</ul>
</CardContent>
</Card>
<div className="grid gap-8 md:grid-cols-3">
{[
{
icon: Rocket,
title: "Quick Setup",
description: "Start creating invoices immediately. No complicated setup required.",
items: ["Simple client management", "Professional templates", "Easy invoice sending"]
},
{
icon: BarChart3,
title: "Payment Tracking",
description: "Keep track of invoice status and monitor your payments effortlessly.",
items: ["Invoice status tracking", "Payment history", "Overdue notifications"]
},
{
icon: Shield,
title: "Professional Features",
description: "Tools that make you look professional and get you paid faster.",
items: ["PDF generation", "Custom tax rates", "Professional numbering"]
}
].map((feature, i) => (
<Card key={i} className="group hover:-translate-y-2 transition-transform duration-500 border-border/40 bg-background/60 backdrop-blur-xl">
<CardContent className="p-8">
<div className="bg-primary/10 text-primary mb-6 inline-flex rounded-2xl p-4">
<feature.icon className="h-8 w-8" />
</div>
<h3 className="text-foreground mb-4 text-2xl font-bold font-heading">
{feature.title}
</h3>
<p className="text-muted-foreground mb-6 leading-relaxed">
{feature.description}
</p>
<ul className="space-y-3">
{feature.items.map((item, j) => (
<li key={j} className="flex items-center gap-3 text-sm text-foreground/80">
<div className="h-1.5 w-1.5 rounded-full bg-primary" />
{item}
</li>
))}
</ul>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Pricing Section */}
<section
id="pricing"
className="bg-background relative overflow-hidden py-16 sm:py-24"
>
<div className="relative container mx-auto px-4">
<div className="mb-12 text-center sm:mb-16">
<h2 className="text-foreground mb-4 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
Simple pricing
</h2>
<p className="text-muted-foreground mx-auto max-w-2xl text-lg sm:text-xl">
Start free, stay free. No hidden fees or limits.
</p>
<section id="pricing" className="py-24 relative overflow-hidden">
<div className="container mx-auto px-4 relative z-10">
<div className="max-w-4xl mx-auto text-center mb-16">
<h2 className="text-5xl font-heading font-bold mb-6">Simple Pricing</h2>
<p className="text-xl text-muted-foreground">Focus on your work, not on fees.</p>
</div>
<div className="mx-auto max-w-md">
<Card className="bg-card border-primary border-2">
<div className="bg-primary/10 text-primary border-primary/20 mx-auto -mt-3 w-fit border px-6 py-1 text-sm font-medium">
<div className="max-w-md mx-auto">
<Card className="relative overflow-visible border-primary/50 shadow-2xl shadow-primary/5 bg-background/80 backdrop-blur-xl">
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-primary text-primary-foreground px-6 py-1.5 rounded-full text-sm font-medium shadow-lg">
Forever Free
</div>
<CardContent className="p-6 sm:p-8">
<div className="mb-6 text-center">
<div className="text-foreground mb-2 text-4xl font-bold sm:text-5xl">
$0
</div>
<p className="text-muted-foreground">
Forever. No credit card required.
</p>
<CardContent className="p-10 text-center">
<div className="mb-2 text-6xl font-bold font-heading">$0</div>
<div className="text-muted-foreground mb-8">No credit card required.</div>
<div className="space-y-4 mb-10 text-left pl-8">
{[
"Unlimited Invoices",
"Unlimited Clients",
"PDF Downloads",
"Payment Tracking",
"Email Support"
].map((item, i) => (
<div key={i} className="flex items-center gap-3">
<Check className="h-5 w-5 text-primary shrink-0" />
<span className="text-foreground/90">{item}</span>
</div>
))}
</div>
<ul className="mb-8 space-y-3">
{[
"Unlimited invoices",
"Client management",
"PDF generation",
"Payment tracking",
"Professional templates",
"Custom tax rates",
"Email support",
].map((feature, i) => (
<li key={i} className="flex items-center gap-3">
<Check className="text-primary h-5 w-5" />
<span className="text-foreground">{feature}</span>
</li>
))}
</ul>
<Link href="/auth/register" className="block">
<Button className="w-full" size="lg">
Get Started Free
<ArrowRight className="ml-2 h-4 w-4" />
<Button size="lg" className="w-full text-lg h-12 rounded-xl">
Get Started
</Button>
</Link>
</CardContent>
@@ -311,58 +223,21 @@ export default function HomePage() {
</div>
</section>
{/* CTA Section */}
<section className="bg-primary relative overflow-hidden py-16 sm:py-24">
<div className="relative container mx-auto px-4 text-center">
<h2 className="text-primary-foreground mb-4 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
Ready to get started?
</h2>
<p className="text-primary-foreground/80 mx-auto mb-8 max-w-2xl text-lg sm:text-xl">
Join thousands of freelancers who trust beenvoice for their
invoicing needs.
</p>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
<Link href="/auth/register">
<Button
variant="secondary"
size="lg"
className="group w-full px-6 py-3 text-base font-semibold sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
>
Start Free Today
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
</Button>
</Link>
</div>
</div>
</section>
{/* Footer */}
<footer className="bg-muted border-border border-t py-8">
<div className="container mx-auto px-4">
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
<div className="flex items-center gap-2">
<Logo size="sm" />
<span className="text-muted-foreground text-sm">
© 2024 beenvoice. Built for freelancers.
</span>
</div>
<div className="flex items-center gap-6">
<Link
href="/auth/signin"
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
>
Sign In
</Link>
<Link
href="/auth/register"
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
>
Get Started
</Link>
</div>
<footer className="border-t border-border/40 bg-background/50 backdrop-blur-sm py-12 mt-12">
<div className="container mx-auto px-6 flex flex-col md:flex-row items-center justify-between gap-6">
<div className="flex items-center gap-3">
<Logo size="sm" />
<span className="text-sm text-muted-foreground">© 2024 beenvoice</span>
</div>
<div className="flex gap-8 text-sm text-muted-foreground">
<a href="#" className="hover:text-foreground transition-colors">Privacy</a>
<a href="#" className="hover:text-foreground transition-colors">Terms</a>
<a href="#" className="hover:text-foreground transition-colors">Contact</a>
</div>
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,375 @@
"use client";
import * as React from "react";
import { format, startOfWeek, endOfWeek, eachDayOfInterval, isSameDay, subWeeks, addWeeks, subMonths, addMonths } from "date-fns";
import { Calendar } from "~/components/ui/calendar";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "~/components/ui/dialog";
import { Button } from "~/components/ui/button";
import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { NumberInput } from "~/components/ui/number-input";
import { Plus, Trash2, Clock, DollarSign, Calendar as CalendarIcon, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "~/lib/utils";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs";
interface InvoiceItem {
id: string;
date: Date;
description: string;
hours: number;
rate: number;
amount: number;
}
interface InvoiceCalendarViewProps {
items: InvoiceItem[];
onUpdateItem: (
index: number,
field: string,
value: string | number | Date
) => void;
onAddItem: (date?: Date) => void;
onRemoveItem: (index: number) => void;
className?: string;
defaultHourlyRate: number | null;
}
export function InvoiceCalendarView({
items,
onUpdateItem,
onAddItem,
onRemoveItem,
className,
defaultHourlyRate,
}: InvoiceCalendarViewProps) {
const [date, setDate] = React.useState<Date | undefined>(undefined); // Start unselected
const [viewDate, setViewDate] = React.useState<Date>(new Date()); // Controls the view (month/week)
const [view, setView] = React.useState<"month" | "week">("month");
const [dialogOpen, setDialogOpen] = React.useState(false);
const [selectedDateItems, setSelectedDateItems] = React.useState<{ item: InvoiceItem; index: number }[]>([]);
// Function to get items for the selected date
const getItemsForDate = React.useCallback((targetDate: Date) => {
return items
.map((item, index) => ({ item, index }))
.filter((wrapper) => {
const itemDate = new Date(wrapper.item.date);
return isSameDay(itemDate, targetDate);
});
}, [items]);
const handleSelectDate = (newDate: Date | undefined) => {
if (!newDate) return;
setDate(newDate);
// Optionally update viewDate to match selection if desired, but user wants them decoupled during nav
// setViewDate(newDate);
const dateItems = getItemsForDate(newDate);
setSelectedDateItems(dateItems);
setDialogOpen(true);
};
// refresh selected items when main items change
React.useEffect(() => {
if (date && dialogOpen) {
setSelectedDateItems(getItemsForDate(date));
}
}, [items, date, dialogOpen, getItemsForDate]);
const handleAddNewItem = () => {
if (date) {
onAddItem(date);
}
};
// Week View Logic - Uses viewDate
const currentWeekStart = startOfWeek(viewDate);
const currentWeekEnd = endOfWeek(viewDate);
const weekDays = eachDayOfInterval({ start: currentWeekStart, end: currentWeekEnd });
const handleCloseDialog = (isOpen: boolean) => {
setDialogOpen(isOpen);
if (!isOpen) {
setDate(undefined);
}
};
return (
<div className={cn("flex flex-col gap-4 h-full w-full", className)}>
<div className="flex items-center justify-between px-4 pt-4 w-full gap-4">
{/* Navigation Controls */}
<div className="flex items-center gap-2">
{view === "week" ? (
<>
<Button variant="outline" size="icon" onClick={() => setViewDate(d => subWeeks(d, 1))} className="h-8 w-8 rounded-lg">
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm font-medium w-36 text-center">
{`${format(currentWeekStart, "MMM d")} - ${format(currentWeekEnd, "MMM d")}`}
</span>
<Button variant="outline" size="icon" onClick={() => setViewDate(d => addWeeks(d, 1))} className="h-8 w-8 rounded-lg">
<ChevronRight className="h-4 w-4" />
</Button>
</>
) : (
<>
<Button variant="outline" size="icon" onClick={() => setViewDate(d => subMonths(d, 1))} className="h-8 w-8 rounded-lg">
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm font-medium w-36 text-center">
{format(viewDate, "MMMM yyyy")}
</span>
<Button variant="outline" size="icon" onClick={() => setViewDate(d => addMonths(d, 1))} className="h-8 w-8 rounded-lg">
<ChevronRight className="h-4 w-4" />
</Button>
</>
)}
</div>
<div className="flex items-center space-x-2 ml-auto">
{/* View Switcher */}
<div className="bg-muted p-1 rounded-lg flex text-sm">
<button
type="button"
onClick={() => setView("month")}
className={cn("px-3 py-1.5 rounded-md transition-all text-center font-medium", view === "month" ? "bg-background shadow text-foreground" : "text-muted-foreground hover:text-foreground")}
>
Month
</button>
<button
type="button"
onClick={() => setView("week")}
className={cn("px-3 py-1.5 rounded-md transition-all text-center font-medium", view === "week" ? "bg-background shadow text-foreground" : "text-muted-foreground hover:text-foreground")}
>
Week
</button>
</div>
</div>
</div>
<div className="flex-1 w-full overflow-hidden">
{view === "month" ? (
<Calendar
mode="single"
selected={date}
onSelect={handleSelectDate}
month={viewDate}
onMonthChange={setViewDate}
className="rounded-md border-0 w-full p-0"
classNames={{
root: "w-full p-0",
months: "flex flex-col w-full",
month: "flex flex-col w-full space-y-4",
// Grid - Revert to Flex but Enforce 1/7th Width
// table: "w-full border-collapse", // No table-fixed
head_row: "flex w-full",
row: "flex w-full mt-2",
// Cells & Headers: Explicit width 14.28%
// Use calc(100%/7) via tailwind arbitrary or just flex bases.
// Better: w-[14.28%] flex-none (approx 1/7)
weekdays: "flex w-full border-b",
weekday: "w-[14.285%] flex-none text-muted-foreground font-normal text-[0.8rem] text-center pb-4",
week: "flex w-full mt-2",
cell: "w-[14.285%] flex-none h-32 border-b p-0 relative focus-within:relative focus-within:z-20 text-center text-sm",
// Hide internal navigation & caption entirely
nav: "hidden",
caption: "hidden",
day: cn(
"w-full h-full p-2 font-normal aria-selected:opacity-100 flex flex-col items-start justify-start gap-1 hover:bg-accent/50 hover:text-accent-foreground align-top transition-colors rounded-xl"
),
day_selected: "bg-primary/5 text-primary",
day_today: "bg-accent/20",
day_outside: "text-muted-foreground opacity-30",
}}
formatters={{
formatMonthCaption: () => "", // Clear default caption text to prevent duplication
}}
components={{
DayButton: (props) => {
const { day, modifiers, className, ...buttonProps } = props;
const DayDate = day.date;
const dayItems = getItemsForDate(DayDate);
// const totalHours = dayItems.reduce((acc, curr) => acc + curr.item.hours, 0); // Unused now
return (
<button
{...buttonProps}
type="button"
className={cn(
"relative flex h-full w-full flex-col items-start justify-between p-2 transition-all rounded-xl border border-transparent hover:border-border/50 hover:bg-secondary/30 text-left overflow-hidden",
// Selected State: Filled Box, No Outline
modifiers.selected && "bg-primary text-primary-foreground hover:bg-primary/90 shadow-md transform scale-[0.98]",
modifiers.today && !modifiers.selected && "bg-accent/40 rounded-xl",
className
)}
>
<span className="text-sm font-medium z-10">{DayDate.getDate()}</span>
{dayItems.length > 0 && (
<div className="flex flex-col gap-1 w-full mt-1 overflow-hidden h-full justify-end pb-1">
<div className="flex flex-col gap-1 w-full mt-1">
{dayItems.slice(0, 4).map((item, idx) => (
<div key={idx} className={cn("h-1 w-full rounded-full", modifiers.selected ? "bg-primary-foreground/50" : "bg-primary/50")} />
))}
{dayItems.length > 4 && <div className={cn("h-1 w-1/3 rounded-full", modifiers.selected ? "bg-primary-foreground/30" : "bg-muted-foreground/30")} />}
</div>
</div>
)}
</button>
);
}
}}
/>
) : (
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-4 p-4 h-full w-full">
{weekDays.map((day) => {
const isSelected = date && isSameDay(day, date);
const isToday = isSameDay(day, new Date());
const dayItems = getItemsForDate(day);
const totalHours = dayItems.reduce((acc, curr) => acc + curr.item.hours, 0);
return (
<button
key={day.toString()}
type="button"
onClick={() => handleSelectDate(day)}
className={cn(
"flex flex-col h-full min-h-[400px] border rounded-3xl p-4 text-left transition-all hover:bg-accent/30 w-full",
isSelected ? "ring-2 ring-primary ring-offset-2 bg-primary/5" : "bg-background/40",
isToday && !isSelected ? "bg-accent/40" : ""
)}
>
<div className="flex flex-col items-center mb-4 pb-4 border-b w-full">
<span className="text-xs font-bold text-muted-foreground uppercase">{format(day, "EEE")}</span>
<span className="text-2xl font-light">{format(day, "d")}</span>
</div>
<div className="flex-1 space-y-2 w-full overflow-hidden">
{dayItems.length > 0 ? (
dayItems.map(({ item }, i) => (
<div key={i} className="bg-background rounded-xl p-2 text-xs shadow-sm border">
<div className="font-medium truncate">{item.description || "No description"}</div>
<div className="text-muted-foreground">{item.hours}h</div>
</div>
))
) : (
<div className="h-full flex items-center justify-center text-muted-foreground/20">
<Plus className="w-8 h-8" />
</div>
)}
</div>
{dayItems.length > 0 && (
<div className="pt-2 mt-auto text-center w-full">
<span className="text-sm font-semibold">{totalHours}h Total</span>
</div>
)}
</button>
);
})}
</div>
)}
</div>
{/* Dialog for Day Details - Now consistently used and rounded */}
<Dialog
open={dialogOpen}
onOpenChange={handleCloseDialog}
>
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-[600px] rounded-3xl">
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-xl">
<div className="bg-primary/10 p-2 rounded-full">
<CalendarIcon className="w-5 h-5 text-primary" />
</div>
{date ? format(date, "EEEE, MMMM do") : "Details"}
</DialogTitle>
</DialogHeader>
<div className="space-y-6 py-4">
{date && selectedDateItems.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center space-y-3 bg-secondary/20 rounded-3xl border border-dashed border-border">
<Clock className="w-12 h-12 text-muted-foreground/30" />
<div className="space-y-1">
<p className="font-medium text-foreground">No hours logged</p>
<p className="text-sm text-muted-foreground">Add time entries for this day.</p>
</div>
<Button onClick={handleAddNewItem} variant="secondary" className="mt-2 text-primary">
Start Logging
</Button>
</div>
) : (
<div className="space-y-4">
{selectedDateItems.map(({ item, index }) => (
<div key={item.id} className="group relative bg-card hover:bg-accent/10 transition-colors p-4 rounded-2xl border shadow-sm space-y-3">
<div className="flex gap-4">
<div className="flex-1 space-y-1.5">
<Label className="text-xs font-semibold text-muted-foreground">Description</Label>
<Input
value={item.description}
onChange={(e) => onUpdateItem(index, "description", e.target.value)}
placeholder="What did you work on?"
className="bg-background/50 border-transparent focus:border-input focus:bg-background transition-all"
/>
</div>
</div>
<div className="flex items-end gap-3">
<div className="w-24 space-y-1.5">
<Label className="text-xs font-semibold text-muted-foreground">Hours</Label>
<NumberInput
value={item.hours}
onChange={v => onUpdateItem(index, "hours", v)}
step={0.25}
min={0}
className="bg-background/50"
/>
</div>
<div className="w-28 space-y-1.5">
<Label className="text-xs font-semibold text-muted-foreground">Rate ($/hr)</Label>
<NumberInput
value={item.rate}
onChange={v => onUpdateItem(index, "rate", v)}
prefix="$"
min={0}
className="bg-background/50"
/>
</div>
<div className="flex-1 flex justify-end items-center pb-2 text-sm font-medium text-muted-foreground">
<span>${(item.hours * item.rate).toFixed(2)}</span>
</div>
<Button
variant="ghost"
size="icon"
className="h-10 w-10 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-xl"
onClick={() => onRemoveItem(index)}
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
))}
<Button variant="outline" onClick={handleAddNewItem} className="w-full border-dashed py-6 rounded-xl hover:bg-accent/40 hover:border-primary/50 text-muted-foreground hover:text-primary transition-all">
<Plus className="w-4 h-4 mr-2" />
Add Another Entry
</Button>
</div>
)}
</div>
<DialogFooter>
<Button className="w-full sm:w-auto rounded-xl" onClick={() => handleCloseDialog(false)}>Done</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -119,12 +119,14 @@ function SortableLineItem({
ref={setNodeRef}
style={style}
layout
// Add ID here for scrolling
id={`invoice-item-${index}`}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2, ease: "easeOut" }}
className={cn(
"bg-secondary hidden rounded-lg p-4 md:block",
"bg-secondary hidden rounded-lg p-4 md:block transition-all",
isDragging && "opacity-50",
)}
>
@@ -249,6 +251,11 @@ function MobileLineItem({
return (
<motion.div
layout
// Add ID here for scrolling (mobile uses same ID since only one is shown usually via CSS)
// But safer to differentiate or handle duplicates?
// Actually, IDs must be unique. Let's rely on the structure that only one is visible.
// Or just duplicate ID knowing it's slightly invalid but functional if one is `display:none`.
id={`invoice-item-${index}-mobile`}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}

View File

@@ -0,0 +1,202 @@
"use client";
import { cn } from "~/lib/utils";
import { Label } from "~/components/ui/label";
import { Input } from "~/components/ui/input";
import { Textarea } from "~/components/ui/textarea";
import { DatePicker } from "~/components/ui/date-picker";
import { NumberInput } from "~/components/ui/number-input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import {
STATUS_OPTIONS,
} from "./types";
import type {
InvoiceFormData,
ClientType,
BusinessType,
} from "./types";
interface InvoiceMetaSidebarProps {
formData: InvoiceFormData;
updateField: <K extends keyof InvoiceFormData>(
field: K,
value: InvoiceFormData[K]
) => void;
clients: ClientType[] | undefined;
businesses: BusinessType[] | undefined;
className?: string;
}
export function InvoiceMetaSidebar({
formData,
updateField,
clients,
businesses,
className,
}: InvoiceMetaSidebarProps) {
return (
<div className={cn("flex flex-col gap-6 p-4 h-full", className)}>
<div className="space-y-4">
<h3 className="font-semibold text-sm text-muted-foreground uppercase tracking-wider">
Invoice Details
</h3>
{/* Status */}
<div className="space-y-1.5">
<Label htmlFor="status" className="text-xs">Status</Label>
<Select
value={formData.status}
onValueChange={(value: "draft" | "sent" | "paid") =>
updateField("status", value)
}
>
<SelectTrigger className="bg-background/50">
<SelectValue />
</SelectTrigger>
<SelectContent>
{STATUS_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Invoice Number */}
<div className="space-y-1.5">
<Label htmlFor="invoiceNumber" className="text-xs">Invoice Number</Label>
<Input
id="invoiceNumber"
value={formData.invoiceNumber}
placeholder="INV-..."
disabled
className="bg-muted/50 font-mono text-sm"
/>
</div>
</div>
<div className="space-y-4">
<h3 className="font-semibold text-sm text-muted-foreground uppercase tracking-wider">
Involved Parties
</h3>
{/* From (Business) */}
<div className="space-y-1.5">
<Label htmlFor="business" className="text-xs">From (Business)</Label>
<Select
value={formData.businessId}
onValueChange={(value) => updateField("businessId", value)}
>
<SelectTrigger aria-label="From Business" className="bg-background/50 text-sm">
<span className="truncate">
<SelectValue placeholder="Select business" />
</span>
</SelectTrigger>
<SelectContent>
{businesses?.map((business) => (
<SelectItem key={business.id} value={business.id}>
{business.name}{business.nickname ? ` (${business.nickname})` : ""}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* Bill To (Client) */}
<div className="space-y-1.5">
<Label htmlFor="client" className="text-xs">Bill To (Client)</Label>
<Select
value={formData.clientId}
onValueChange={(value) => updateField("clientId", value)}
>
<SelectTrigger aria-label="Bill To Client" className="bg-background/50 text-sm">
<span className="truncate">
<SelectValue placeholder="Select client" />
</span>
</SelectTrigger>
<SelectContent>
{clients?.map((client) => (
<SelectItem key={client.id} value={client.id}>
{client.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-4">
<h3 className="font-semibold text-sm text-muted-foreground uppercase tracking-wider">
Dates
</h3>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-xs">Issued</Label>
<DatePicker
date={formData.issueDate}
onDateChange={(date) => updateField("issueDate", date ?? new Date())}
className="w-full bg-background/50"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Due</Label>
<DatePicker
date={formData.dueDate}
onDateChange={(date) => updateField("dueDate", date ?? new Date())}
className="w-full bg-background/50"
/>
</div>
</div>
</div>
<div className="space-y-4">
<h3 className="font-semibold text-sm text-muted-foreground uppercase tracking-wider">
Config
</h3>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-1.5">
<Label className="text-xs">Tax Rate</Label>
<NumberInput
value={formData.taxRate}
onChange={(v) => updateField("taxRate", v)}
min={0}
max={100}
step={1}
suffix="%"
className="bg-background/50"
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Hourly Rate</Label>
<NumberInput
value={formData.defaultHourlyRate ?? 0}
onChange={(v) => updateField("defaultHourlyRate", v)}
min={0}
prefix="$"
placeholder={!formData.clientId ? "Select client" : "Rate"}
disabled={!formData.clientId}
className={cn("bg-background/50", !formData.clientId && "opacity-50")}
/>
</div>
</div>
</div>
<div className="space-y-1.5 flex-1">
<Label className="text-xs">Notes</Label>
<Textarea
value={formData.notes}
onChange={(e) => updateField("notes", e.target.value)}
placeholder="Notes for client..."
className="bg-background/50 resize-none h-24"
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,108 @@
"use client";
import * as React from "react";
import { cn } from "~/lib/utils";
import { Button } from "~/components/ui/button";
import { Card, CardContent } from "~/components/ui/card";
import { ScrollArea } from "~/components/ui/scroll-area";
import { List, Calendar as CalendarIcon, Plus } from "lucide-react";
import { InvoiceLineItems } from "../invoice-line-items";
import { InvoiceCalendarView } from "../invoice-calendar-view";
import type { InvoiceFormData } from "./types";
interface InvoiceWorkspaceProps {
formData: InvoiceFormData;
viewMode: "list" | "calendar";
setViewMode: (mode: "list" | "calendar") => void;
addItem: (date?: Date) => void;
removeItem: (index: number) => void;
updateItem: (index: number, field: string, value: string | number | Date) => void;
moveItemUp: (index: number) => void;
moveItemDown: (index: number) => void;
reorderItems: (items: any[]) => void;
className?: string;
}
export function InvoiceWorkspace({
formData,
viewMode,
setViewMode,
addItem,
removeItem,
updateItem,
moveItemUp,
moveItemDown,
reorderItems,
className,
}: InvoiceWorkspaceProps) {
return (
<div className={cn("flex flex-col h-full", className)}>
{/* Workspace Header / View Toggle */}
<div className="flex items-center justify-between p-4 border-b bg-background/50 backdrop-blur-sm sticky top-0 z-10">
<div className="flex items-center gap-2">
<h2 className="text-lg font-semibold tracking-tight">
{viewMode === 'list' ? 'Line Items' : 'Timesheet'}
</h2>
<div className="text-sm text-muted-foreground ml-2">
{formData.items.length} {formData.items.length === 1 ? 'entry' : 'entries'}
</div>
</div>
<div className="flex items-center bg-secondary/50 p-1 rounded-lg">
<Button
variant={viewMode === 'list' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setViewMode('list')}
className="h-8 gap-2 text-xs"
>
<List className="w-3.5 h-3.5" />
List
</Button>
<Button
variant={viewMode === 'calendar' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setViewMode('calendar')}
className="h-8 gap-2 text-xs"
>
<CalendarIcon className="w-3.5 h-3.5" />
Calendar
</Button>
</div>
</div>
{/* Workspace Content */}
<div className="flex-1 overflow-hidden relative">
<div className="absolute inset-0 overflow-y-auto p-6 md:p-8">
{viewMode === 'list' ? (
<div className="max-w-4xl mx-auto space-y-6">
<div className="bg-background/40 backdrop-blur-md rounded-xl border border-white/10 p-1">
<InvoiceLineItems
items={formData.items}
onAddItem={() => addItem()}
onRemoveItem={removeItem}
onUpdateItem={updateItem}
onMoveUp={moveItemUp}
onMoveDown={moveItemDown}
onReorderItems={reorderItems}
className="p-4"
/>
</div>
</div>
) : (
<div className="h-full">
<InvoiceCalendarView
items={formData.items}
onAddItem={addItem}
onRemoveItem={removeItem}
onUpdateItem={updateItem}
defaultHourlyRate={formData.defaultHourlyRate}
className="h-full"
/>
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,32 @@
import { type RouterOutputs } from "~/trpc/react";
export type ClientType = RouterOutputs["clients"]["getAll"][number];
export type BusinessType = RouterOutputs["businesses"]["getAll"][number];
export interface InvoiceItem {
id: string;
date: Date;
description: string;
hours: number;
rate: number;
amount: number;
}
export interface InvoiceFormData {
invoiceNumber: string;
businessId: string;
clientId: string;
issueDate: Date;
dueDate: Date;
status: "draft" | "sent" | "paid";
notes: string;
taxRate: number;
defaultHourlyRate: number | null;
items: InvoiceItem[];
}
export const STATUS_OPTIONS = [
{ value: "draft", label: "Draft" },
{ value: "sent", label: "Sent" },
{ value: "paid", label: "Paid" },
] as const;

View File

@@ -5,6 +5,7 @@ import { Sidebar } from "~/components/layout/sidebar";
import { SidebarProvider, useSidebar } from "~/components/layout/sidebar-provider";
import { cn } from "~/lib/utils";
import { Menu } from "lucide-react";
import { Logo } from "~/components/branding/logo";
import { Button } from "~/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "~/components/ui/sheet";
import { DashboardBreadcrumbs } from "~/components/navigation/dashboard-breadcrumbs";
@@ -21,15 +22,22 @@ function DashboardContent({ children }: { children: React.ReactNode }) {
</div>
{/* Mobile Sidebar (Sheet) */}
<div className="md:hidden fixed top-4 left-4 z-50">
<div className="md:hidden fixed top-0 left-0 right-0 h-16 bg-background/80 backdrop-blur-md border-b z-50 px-4 flex items-center">
<Sheet open={isMobileOpen} onOpenChange={setIsMobileOpen}>
<SheetTrigger asChild>
<Button variant="outline" size="icon" className="h-10 w-10 bg-background shadow-sm">
<Button variant="outline" size="icon" className="h-10 w-10 bg-background shadow-sm" suppressHydrationWarning>
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle menu</span>
</Button>
</SheetTrigger>
{/* Mobile Link / Logo */}
<div className="ml-4 flex items-center gap-2">
<Logo size="sm" />
</div>
<SheetContent side="left" className="p-0 w-72">
<div className="sr-only">
<h2 id="mobile-nav-title">Navigation Menu</h2>
</div>
<Sidebar mobile onClose={() => setIsMobileOpen(false)} />
</SheetContent>
</Sheet>
@@ -39,7 +47,7 @@ function DashboardContent({ children }: { children: React.ReactNode }) {
<main
suppressHydrationWarning
className={cn(
"flex-1 min-h-screen transition-all duration-300 ease-in-out",
"flex-1 min-h-screen min-w-0 transition-all duration-300 ease-in-out",
// Desktop margins based on collapsed state
"md:ml-0",
// Sidebar is fixed at left: 1rem (16px), width: 16rem (256px) or 4rem (64px)

View File

@@ -13,12 +13,15 @@ interface FloatingActionBarProps {
className?: string;
}
import { useSidebar } from "~/components/layout/sidebar-provider";
export function FloatingActionBar({
leftContent,
children,
className,
}: FloatingActionBarProps) {
const [isDocked, setIsDocked] = useState(false);
const { isCollapsed } = useSidebar();
useEffect(() => {
const handleScroll = () => {
@@ -48,9 +51,10 @@ export function FloatingActionBar({
<div
className={cn(
// Base positioning - always at bottom
"fixed right-0 left-0 z-50",
"fixed right-0 z-50 transition-all duration-300 ease-in-out",
// Safe area and sidebar adjustments
"pb-safe-area-inset-bottom md:left-64",
"pb-safe-area-inset-bottom left-0",
isCollapsed ? "md:left-24" : "md:left-[18rem]",
// Conditional centering based on dock state
isDocked ? "flex justify-center" : "",
// Dynamic bottom positioning

View File

@@ -46,7 +46,8 @@ export function PageHeader({
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-transparent pointer-events-none" />
<div className="p-6 relative">
<DashboardBreadcrumbs className="mb-4" />
<div className="flex items-start justify-between gap-4">
{/* UPDATED: flex-col on mobile to prevent squishing, row on sm+ */}
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
<div className="space-y-1">
<h1 className={titleClassName ?? getTitleClasses()}>{title}</h1>
{description && (
@@ -56,7 +57,7 @@ export function PageHeader({
)}
</div>
{children && (
<div className="flex flex-shrink-0 gap-2 sm:gap-3">
<div className="flex flex-shrink-0 gap-2 sm:gap-3 w-full sm:w-auto">
{children}
</div>
)}
@@ -66,7 +67,8 @@ export function PageHeader({
) : (
<>
<DashboardBreadcrumbs className="mb-2 sm:mb-4" />
<div className="flex items-start justify-between gap-4">
{/* UPDATED: flex-col on mobile to prevent squishing, row on sm+ */}
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
<div className="animate-fade-in-up space-y-1">
<h1 className={titleClassName ?? getTitleClasses()}>{title}</h1>
{description && (
@@ -78,7 +80,7 @@ export function PageHeader({
)}
</div>
{children && (
<div className="animate-slide-in-right animate-delay-200 flex flex-shrink-0 gap-2 sm:gap-3">
<div className="animate-slide-in-right animate-delay-200 flex flex-shrink-0 gap-2 sm:gap-3 w-full sm:w-auto">
{children}
</div>
)}

View File

@@ -160,21 +160,27 @@ export function Sidebar({ mobile, onClose }: SidebarProps) {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className={cn("w-full justify-start p-0 hover:bg-transparent", collapsed && "justify-center")}>
<div className={cn("flex items-center gap-3", collapsed ? "justify-center" : "w-full")}>
{/* FIXED: Changed div to span to prevent hydration error */}
<span className={cn("flex items-center gap-3", collapsed ? "justify-center" : "w-full")}>
<Avatar className="h-9 w-9 border border-border">
<AvatarImage src={getGravatarUrl(session.user.email)} alt={session.user.name ?? "User"} />
<AvatarFallback>{session.user.name?.[0] ?? "U"}</AvatarFallback>
</Avatar>
{!collapsed && (
<div className="flex-1 min-w-0 text-left">
<p className="text-sm font-medium truncate">{session.user.name}</p>
<p className="text-xs text-muted-foreground truncate">{session.user.email}</p>
</div>
<span className="flex-1 min-w-0 text-left">
<span className="block text-sm font-medium truncate">{session.user.name}</span>
<span className="block text-xs text-muted-foreground truncate">{session.user.email}</span>
</span>
)}
</div>
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="right" align="end" className="w-56" sideOffset={10}>
<DropdownMenuContent
side="right"
align="end"
className="w-56 bg-background/80 backdrop-blur-xl border-border/50"
sideOffset={10}
>
<DropdownMenuLabel>
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">{session.user.name}</p>
@@ -212,7 +218,7 @@ export function Sidebar({ mobile, onClose }: SidebarProps) {
<aside
className={cn(
"fixed top-4 bottom-4 left-4 z-30 hidden md:flex flex-col",
"bg-card border border-border shadow-xl rounded-xl transition-all duration-300 ease-in-out",
"bg-background/80 backdrop-blur-xl border-border/50 border shadow-xl rounded-3xl transition-all duration-300 ease-in-out",
isCollapsed ? "w-16" : "w-64"
)}
>

View File

@@ -4,17 +4,17 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "~/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
"inline-flex w-fit items-center rounded-md px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
"bg-secondary text-secondary-foreground shadow-sm border border-secondary/50 hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
"bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground border border-border", // Outline needs border
},
},
defaultVariants: {

View File

@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "~/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
"inline-flex items-center justify-center rounded-xl text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 button-hover",
{
variants: {
variant: {
@@ -22,9 +22,9 @@ const buttonVariants = cva(
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
sm: "h-8 rounded-lg px-3 text-xs",
lg: "h-10 rounded-xl px-8",
icon: "h-9 w-9 rounded-full",
},
},
defaultVariants: {
@@ -36,7 +36,7 @@ const buttonVariants = cva(
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}

View File

@@ -1,26 +1,15 @@
"use client";
"use client"
import * as React from "react";
import * as React from "react"
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react";
import {
type DayButton,
DayPicker,
getDefaultClassNames,
} from "react-day-picker";
} from "lucide-react"
import { type DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "~/lib/utils";
import { Button, buttonVariants } from "~/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { cn } from "~/lib/utils"
import { Button, buttonVariants } from "~/components/ui/button"
function Calendar({
className,
@@ -28,35 +17,13 @@ function Calendar({
showOutsideDays = true,
captionLayout = "label",
buttonVariant = "ghost",
formatters: _formatters,
formatters,
components,
month,
onMonthChange,
...props
}: React.ComponentProps<typeof DayPicker> & {
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
}) {
const defaultClassNames = getDefaultClassNames();
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const currentYear = month?.getFullYear() ?? new Date().getFullYear();
const currentMonth = month?.getMonth() ?? new Date().getMonth();
const years = Array.from({ length: 11 }, (_, i) => currentYear - 5 + i);
const defaultClassNames = getDefaultClassNames()
return (
<DayPicker
@@ -65,82 +32,97 @@ function Calendar({
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
className,
className
)}
captionLayout={captionLayout}
month={month}
onMonthChange={onMonthChange}
formatters={{
formatMonthDropdown: (date) =>
date.toLocaleString("default", { month: "short" }),
...formatters,
}}
classNames={{
root: cn("w-fit", defaultClassNames.root),
months: cn(
"flex gap-4 flex-col md:flex-row relative",
defaultClassNames.months,
defaultClassNames.months
),
month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
nav: cn(
"flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
defaultClassNames.nav,
defaultClassNames.nav
),
button_previous: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_previous,
defaultClassNames.button_previous
),
button_next: cn(
buttonVariants({ variant: buttonVariant }),
"size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
defaultClassNames.button_next,
defaultClassNames.button_next
),
month_caption: cn(
"flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
defaultClassNames.month_caption,
defaultClassNames.month_caption
),
dropdowns: cn(
"w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
defaultClassNames.dropdowns,
defaultClassNames.dropdowns
),
dropdown_root: cn(
"relative has-focus:border-ring border border-input shadow-sm has-focus:ring-ring/50 has-focus:ring-2 h-8",
defaultClassNames.dropdown_root,
"relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
defaultClassNames.dropdown_root
),
dropdown: cn(
"absolute bg-transparent inset-0 w-full h-full opacity-0 cursor-pointer",
defaultClassNames.dropdown,
"absolute bg-popover inset-0 opacity-0",
defaultClassNames.dropdown
),
caption_label: cn(
"select-none font-medium text-sm hidden",
defaultClassNames.caption_label,
"select-none font-medium",
captionLayout === "label"
? "text-sm"
: "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
defaultClassNames.caption_label
),
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground flex-1 font-normal text-[0.8rem] select-none",
defaultClassNames.weekday,
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
defaultClassNames.weekday
),
week: cn("flex w-full mt-2", defaultClassNames.week),
week_number_header: cn(
"select-none w-(--cell-size)",
defaultClassNames.week_number_header,
defaultClassNames.week_number_header
),
week_number: cn(
"text-[0.8rem] select-none text-muted-foreground",
defaultClassNames.week_number,
defaultClassNames.week_number
),
day: cn(
"relative w-full h-full p-0 text-center group/day aspect-square select-none",
defaultClassNames.day,
"relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
props.showWeekNumber
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
: "[&:first-child[data-selected=true]_button]:rounded-l-md",
defaultClassNames.day
),
range_start: cn(
"rounded-l-md bg-accent",
defaultClassNames.range_start
),
range_middle: cn("rounded-none", defaultClassNames.range_middle),
range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
today: cn(
"bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
defaultClassNames.today
),
range_start: cn("", defaultClassNames.range_start),
range_middle: cn("", defaultClassNames.range_middle),
range_end: cn("", defaultClassNames.range_end),
today: cn("font-semibold", defaultClassNames.today),
outside: cn(
"text-muted-foreground aria-selected:text-muted-foreground",
defaultClassNames.outside,
defaultClassNames.outside
),
disabled: cn(
"text-muted-foreground opacity-50",
defaultClassNames.disabled,
defaultClassNames.disabled
),
hidden: cn("invisible", defaultClassNames.hidden),
...classNames,
@@ -154,13 +136,13 @@ function Calendar({
className={cn(className)}
{...props}
/>
);
)
},
Chevron: ({ className, orientation, ...props }) => {
if (orientation === "left") {
return (
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
);
)
}
if (orientation === "right") {
@@ -169,67 +151,14 @@ function Calendar({
className={cn("size-4", className)}
{...props}
/>
);
)
}
return (
<ChevronDownIcon className={cn("size-4", className)} {...props} />
);
)
},
DayButton: CalendarDayButton,
MonthCaption: ({ calendarMonth: _calendarMonth }) => {
if (captionLayout !== "dropdown") {
return <></>;
}
return (
<div className="calendar-custom-header flex items-center justify-center gap-2 py-2">
<Select
value={currentMonth.toString()}
onValueChange={(value) => {
const newDate = new Date(currentYear, parseInt(value), 1);
onMonthChange?.(newDate);
}}
>
<SelectTrigger
size="sm"
className="w-auto px-2 text-sm font-semibold"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{months.map((monthName, index) => (
<SelectItem key={index} value={index.toString()}>
{monthName}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={currentYear.toString()}
onValueChange={(value) => {
const newDate = new Date(parseInt(value), currentMonth, 1);
onMonthChange?.(newDate);
}}
>
<SelectTrigger
size="sm"
className="w-auto px-2 text-sm font-semibold"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{years.map((year) => (
<SelectItem key={year} value={year.toString()}>
{year}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
},
WeekNumber: ({ children, ...props }) => {
return (
<td {...props}>
@@ -237,13 +166,13 @@ function Calendar({
{children}
</div>
</td>
);
)
},
...components,
}}
{...props}
/>
);
)
}
function CalendarDayButton({
@@ -252,12 +181,12 @@ function CalendarDayButton({
modifiers,
...props
}: React.ComponentProps<typeof DayButton>) {
// const _defaultClassNames = getDefaultClassNames();
const defaultClassNames = getDefaultClassNames()
const ref = React.useRef<HTMLButtonElement>(null);
const ref = React.useRef<HTMLButtonElement>(null)
React.useEffect(() => {
if (modifiers.focused) ref.current?.focus();
}, [modifiers.focused]);
if (modifiers.focused) ref.current?.focus()
}, [modifiers.focused])
return (
<Button
@@ -275,14 +204,13 @@ function CalendarDayButton({
data-range-end={modifiers.range_end}
data-range-middle={modifiers.range_middle}
className={cn(
"hover:bg-accent hover:text-foreground-foreground flex aspect-square size-auto h-8 w-full min-w-8 items-center justify-center border-0 text-sm leading-none font-normal shadow-none",
modifiers.selected && "bg-primary text-primary-foreground",
modifiers.today && !modifiers.selected && "bg-accent font-semibold",
className,
"data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
defaultClassNames.day,
className
)}
{...props}
/>
);
)
}
export { Calendar, CalendarDayButton };
export { Calendar, CalendarDayButton }

View File

@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground border-border/40 flex flex-col rounded-lg border shadow-lg",
"bg-background/80 backdrop-blur-xl border-border/50 text-card-foreground flex flex-col rounded-3xl border shadow-sm overflow-hidden",
className,
)}
{...props}

View File

@@ -0,0 +1,37 @@
import { useState } from "react";
import Image, { type ImageProps } from "next/image";
import { cn } from "~/lib/utils";
import { Skeleton } from "~/components/ui/skeleton";
interface ImageWithSkeletonProps extends ImageProps {
containerClassName?: string;
}
export function ImageWithSkeleton({
className,
containerClassName,
alt,
...props
}: ImageWithSkeletonProps) {
const [isLoading, setIsLoading] = useState(true);
return (
<div className={cn("relative overflow-hidden", containerClassName)}>
{isLoading && (
<Skeleton className="absolute inset-0 h-full w-full animate-pulse" />
)}
<Image
className={cn(
"duration-700 ease-in-out",
isLoading
? "scale-110 blur-2xl grayscale"
: "scale-100 blur-0 grayscale-0",
className
)}
onLoad={() => setIsLoading(false)}
alt={alt}
{...props}
/>
</div>
);
}

View File

@@ -1,10 +1,14 @@
"use client";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import { useTheme } from "~/components/providers/theme-provider";
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
position="bottom-right"
closeButton

View File

@@ -1,462 +1,154 @@
@import "tailwindcss";
@import "tw-animate-css";
@font-face {
font-family: "Frutiger";
src: url("/fonts/frutiger/Frutiger.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@layer base {
:root {
--background: 0 0% 100%;
/* #FFFFFF */
--foreground: 240 10% 3.9%;
/* #09090B */
--card: 0 0% 100%;
/* #FFFFFF */
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
/* #18181B */
--primary-foreground: 0 0% 98%;
/* #FAFAFA */
--secondary: 240 4.8% 90%;
/* #E4E4E7 (Darkened for contrast) */
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
/* #F4F4F5 */
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
/* #E4E4E7 */
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--radius: 1rem;
/* 16px Global Radius */
}
@font-face {
font-family: "Frutiger";
src: url("/fonts/frutiger/Frutiger_bold.ttf") format("truetype");
font-weight: bold;
font-style: normal;
}
:root {
--radius: 0.8rem;
}
.slate {
--background: oklch(0.98 0.01 230);
--foreground: oklch(0.2 0.03 230);
--card: oklch(1 0 0);
--card-foreground: oklch(0.2 0.03 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.03 230);
--primary: oklch(0.6 0.01 240);
--primary-foreground: oklch(0.98 0.01 240);
--secondary: oklch(0.92 0.01 240);
--secondary-foreground: oklch(0.2 0.02 240);
--muted: oklch(0.92 0.01 240);
--muted-foreground: oklch(0.5 0.02 240);
--accent: oklch(0.94 0.01 240);
--accent-foreground: oklch(0.2 0.02 240);
--destructive: oklch(0.58 0.24 28);
--destructive-foreground: oklch(0.98 0.01 230);
--success: oklch(0.55 0.15 142);
--success-foreground: oklch(0.98 0.01 230);
--warning: oklch(0.65 0.15 38);
--warning-foreground: oklch(0.2 0.03 230);
--border: oklch(0.9 0.01 240);
--input: oklch(0.9 0.01 240);
--ring: oklch(0.6 0.01 240);
--sidebar: oklch(0.96 0.01 240);
--sidebar-foreground: oklch(0.2 0.02 240);
--sidebar-primary: oklch(0.2 0.02 240);
--sidebar-primary-foreground: oklch(0.98 0.01 240);
--sidebar-accent: oklch(0.92 0.01 240);
--sidebar-accent-foreground: oklch(0.2 0.02 240);
--sidebar-border: oklch(0.88 0.01 240);
--sidebar-ring: oklch(0.6 0.01 240);
--navbar: oklch(0.96 0.01 240);
--navbar-foreground: oklch(0.2 0.02 240);
--navbar-border: oklch(0.88 0.01 240);
}
.dark.slate {
--background: oklch(0.15 0.02 240);
--foreground: oklch(0.9 0.02 240);
--card: oklch(0.2 0.02 240);
--card-foreground: oklch(0.9 0.02 240);
--popover: oklch(0.22 0.02 240);
--popover-foreground: oklch(0.9 0.02 240);
--primary: oklch(0.6 0.01 240);
--primary-foreground: oklch(0.1 0.02 240);
--secondary: oklch(0.25 0.02 240);
--secondary-foreground: oklch(0.9 0.02 240);
--muted: oklch(0.25 0.02 240);
--muted-foreground: oklch(0.7 0.02 240);
--accent: oklch(0.3 0.02 240);
--accent-foreground: oklch(0.9 0.02 240);
--destructive: oklch(0.7 0.19 22);
--destructive-foreground: oklch(0.2 0.02 240);
--success: oklch(0.6 0.15 142);
--success-foreground: oklch(0.98 0.01 240);
--warning: oklch(0.7 0.15 38);
--warning-foreground: oklch(0.2 0.02 240);
--border: oklch(0.28 0.02 240);
--input: oklch(0.35 0.02 240);
--ring: oklch(0.6 0.01 240);
--sidebar: oklch(0.1 0.02 240);
--sidebar-foreground: oklch(0.9 0.02 240);
--sidebar-primary: oklch(0.9 0.02 240);
--sidebar-primary-foreground: oklch(0.1 0.02 240);
--sidebar-accent: oklch(0.2 0.02 240);
--sidebar-accent-foreground: oklch(0.9 0.02 240);
--sidebar-border: oklch(0.25 0.02 240);
--sidebar-ring: oklch(0.35 0.02 240);
--navbar: oklch(0.1 0.02 240);
--navbar-foreground: oklch(0.9 0.02 240);
--navbar-border: oklch(0.25 0.02 240);
}
.blue {
--background: oklch(0.98 0.01 230);
--foreground: oklch(0.2 0.03 230);
--card: oklch(1 0 0);
--card-foreground: oklch(0.2 0.03 230);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.03 230);
--primary: oklch(0.6 0.15 220);
--primary-foreground: oklch(0.98 0.01 230);
--secondary: oklch(0.92 0.02 230);
--secondary-foreground: oklch(0.2 0.03 230);
--muted: oklch(0.92 0.02 230);
--muted-foreground: oklch(0.5 0.03 230);
--accent: oklch(0.94 0.02 230);
--accent-foreground: oklch(0.2 0.03 230);
--destructive: oklch(0.58 0.24 28);
--destructive-foreground: oklch(0.98 0.01 230);
--success: oklch(0.55 0.15 142);
--success-foreground: oklch(0.98 0.01 230);
--warning: oklch(0.65 0.15 38);
--warning-foreground: oklch(0.2 0.03 230);
--border: oklch(0.9 0.02 230);
--input: oklch(0.9 0.02 230);
--ring: oklch(0.6 0.15 220);
--sidebar: oklch(0.96 0.01 230);
--sidebar-foreground: oklch(0.2 0.03 230);
--sidebar-primary: oklch(0.2 0.03 230);
--sidebar-primary-foreground: oklch(0.98 0.01 230);
--sidebar-accent: oklch(0.92 0.02 230);
--sidebar-accent-foreground: oklch(0.2 0.03 230);
--sidebar-border: oklch(0.88 0.02 230);
--sidebar-ring: oklch(0.6 0.15 220);
--navbar: oklch(0.96 0.01 230);
--navbar-foreground: oklch(0.2 0.03 230);
--navbar-border: oklch(0.88 0.02 230);
}
.dark.blue {
--background: oklch(0.15 0.03 230);
--foreground: oklch(0.9 0.01 230);
--card: oklch(0.2 0.03 230);
--card-foreground: oklch(0.9 0.01 230);
--popover: oklch(0.22 0.03 230);
--popover-foreground: oklch(0.9 0.01 230);
--primary: oklch(0.6 0.15 220);
--primary-foreground: oklch(0.98 0.01 230);
--secondary: oklch(0.25 0.03 230);
--secondary-foreground: oklch(0.9 0.01 230);
--muted: oklch(0.25 0.03 230);
--muted-foreground: oklch(0.7 0.01 230);
--accent: oklch(0.3 0.03 230);
--accent-foreground: oklch(0.9 0.01 230);
--destructive: oklch(0.7 0.19 22);
--destructive-foreground: oklch(0.2 0.03 230);
--success: oklch(0.6 0.15 142);
--success-foreground: oklch(0.98 0.01 230);
--warning: oklch(0.7 0.15 38);
--warning-foreground: oklch(0.2 0.03 230);
--border: oklch(0.28 0.03 230);
--input: oklch(0.35 0.03 230);
--ring: oklch(0.6 0.15 220);
--sidebar: oklch(0.1 0.03 230);
--sidebar-foreground: oklch(0.9 0.01 230);
--sidebar-primary: oklch(0.9 0.01 230);
--sidebar-primary-foreground: oklch(0.1 0.03 230);
--sidebar-accent: oklch(0.2 0.03 230);
--sidebar-accent-foreground: oklch(0.9 0.01 230);
--sidebar-border: oklch(0.25 0.03 230);
--sidebar-ring: oklch(0.35 0.03 230);
--navbar: oklch(0.1 0.03 230);
--navbar-foreground: oklch(0.9 0.01 230);
--navbar-border: oklch(0.25 0.03 230);
}
.green {
--background: oklch(0.98 0.01 140);
--foreground: oklch(0.2 0.05 140);
--card: oklch(1 0 0);
--card-foreground: oklch(0.2 0.05 140);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.05 140);
--primary: oklch(0.5 0.1 150);
--primary-foreground: oklch(0.98 0.01 140);
--secondary: oklch(0.94 0.02 140);
--secondary-foreground: oklch(0.2 0.05 140);
--muted: oklch(0.94 0.02 140);
--muted-foreground: oklch(0.5 0.05 140);
--accent: oklch(0.94 0.02 140);
--accent-foreground: oklch(0.2 0.05 140);
--destructive: oklch(0.58 0.24 28);
--destructive-foreground: oklch(0.98 0.01 140);
--success: oklch(0.55 0.15 142);
--success-foreground: oklch(0.98 0.01 140);
--warning: oklch(0.65 0.15 38);
--warning-foreground: oklch(0.2 0.05 140);
--border: oklch(0.9 0.02 140);
--input: oklch(0.9 0.02 140);
--ring: oklch(0.5 0.1 150);
--sidebar: oklch(0.96 0.01 140);
--sidebar-foreground: oklch(0.2 0.05 140);
--sidebar-primary: oklch(0.2 0.05 140);
--sidebar-primary-foreground: oklch(0.98 0.01 140);
--sidebar-accent: oklch(0.92 0.02 140);
--sidebar-accent-foreground: oklch(0.2 0.05 140);
--sidebar-border: oklch(0.88 0.02 140);
--sidebar-ring: oklch(0.5 0.1 150);
--navbar: oklch(0.96 0.01 140);
--navbar-foreground: oklch(0.2 0.05 140);
--navbar-border: oklch(0.88 0.02 140);
}
.dark.green {
--background: oklch(0.15 0.05 140);
--foreground: oklch(0.9 0.05 140);
--card: oklch(0.2 0.05 140);
--card-foreground: oklch(0.9 0.05 140);
--popover: oklch(0.22 0.05 140);
--popover-foreground: oklch(0.9 0.05 140);
--primary: oklch(0.5 0.1 150);
--primary-foreground: oklch(0.1 0.05 140);
--secondary: oklch(0.25 0.05 140);
--secondary-foreground: oklch(0.9 0.05 140);
--muted: oklch(0.25 0.05 140);
--muted-foreground: oklch(0.7 0.05 140);
--accent: oklch(0.3 0.05 140);
--accent-foreground: oklch(0.9 0.05 140);
--destructive: oklch(0.7 0.19 22);
--destructive-foreground: oklch(0.2 0.05 140);
--success: oklch(0.6 0.15 142);
--success-foreground: oklch(0.98 0.01 140);
--warning: oklch(0.7 0.15 38);
--warning-foreground: oklch(0.2 0.05 140);
--border: oklch(0.28 0.05 140);
--input: oklch(0.35 0.05 140);
--ring: oklch(0.5 0.1 150);
--sidebar: oklch(0.1 0.05 140);
--sidebar-foreground: oklch(0.9 0.05 140);
--sidebar-primary: oklch(0.9 0.05 140);
--sidebar-primary-foreground: oklch(0.1 0.05 140);
--sidebar-accent: oklch(0.2 0.05 140);
--sidebar-accent-foreground: oklch(0.9 0.05 140);
--sidebar-border: oklch(0.25 0.05 140);
--sidebar-ring: oklch(0.35 0.05 140);
--navbar: oklch(0.1 0.05 140);
--navbar-foreground: oklch(0.9 0.05 140);
--navbar-border: oklch(0.25 0.05 140);
}
.rose {
--background: oklch(0.98 0.01 20);
--foreground: oklch(0.2 0.05 20);
--card: oklch(1 0 0);
--card-foreground: oklch(0.2 0.05 20);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.05 20);
--primary: oklch(0.7 0.2 10);
--primary-foreground: oklch(0.98 0.01 20);
--secondary: oklch(0.94 0.02 20);
--secondary-foreground: oklch(0.2 0.05 20);
--muted: oklch(0.94 0.02 20);
--muted-foreground: oklch(0.5 0.05 20);
--accent: oklch(0.94 0.02 20);
--accent-foreground: oklch(0.2 0.05 20);
--destructive: oklch(0.58 0.24 28);
--destructive-foreground: oklch(0.98 0.01 20);
--success: oklch(0.55 0.15 142);
--success-foreground: oklch(0.98 0.01 20);
--warning: oklch(0.65 0.15 38);
--warning-foreground: oklch(0.2 0.05 20);
--border: oklch(0.9 0.02 20);
--input: oklch(0.9 0.02 20);
--ring: oklch(0.7 0.2 10);
--sidebar: oklch(0.96 0.01 20);
--sidebar-foreground: oklch(0.2 0.05 20);
--sidebar-primary: oklch(0.2 0.05 20);
--sidebar-primary-foreground: oklch(0.98 0.01 20);
--sidebar-accent: oklch(0.92 0.02 20);
--sidebar-accent-foreground: oklch(0.2 0.05 20);
--sidebar-border: oklch(0.88 0.02 20);
--sidebar-ring: oklch(0.7 0.2 10);
--navbar: oklch(0.96 0.01 20);
--navbar-foreground: oklch(0.2 0.05 20);
--navbar-border: oklch(0.88 0.02 20);
}
.dark.rose {
--background: oklch(0.15 0.05 20);
--foreground: oklch(0.9 0.05 20);
--card: oklch(0.2 0.05 20);
--card-foreground: oklch(0.9 0.05 20);
--popover: oklch(0.22 0.05 20);
--popover-foreground: oklch(0.9 0.05 20);
--primary: oklch(0.7 0.2 10);
--primary-foreground: oklch(0.1 0.05 20);
--secondary: oklch(0.25 0.05 20);
--secondary-foreground: oklch(0.9 0.05 20);
--muted: oklch(0.25 0.05 20);
--muted-foreground: oklch(0.7 0.05 20);
--accent: oklch(0.3 0.05 20);
--accent-foreground: oklch(0.9 0.05 20);
--destructive: oklch(0.7 0.19 22);
--destructive-foreground: oklch(0.2 0.05 20);
--success: oklch(0.6 0.15 142);
--success-foreground: oklch(0.98 0.01 20);
--warning: oklch(0.7 0.15 38);
--warning-foreground: oklch(0.2 0.05 20);
--border: oklch(0.28 0.05 20);
--input: oklch(0.35 0.05 20);
--ring: oklch(0.7 0.2 10);
--sidebar: oklch(0.1 0.05 20);
--sidebar-foreground: oklch(0.9 0.05 20);
--sidebar-primary: oklch(0.9 0.05 20);
--sidebar-primary-foreground: oklch(0.1 0.05 20);
--sidebar-accent: oklch(0.2 0.05 20);
--sidebar-accent-foreground: oklch(0.9 0.05 20);
--sidebar-border: oklch(0.25 0.05 20);
--sidebar-ring: oklch(0.35 0.05 20);
--navbar: oklch(0.1 0.05 20);
--navbar-foreground: oklch(0.9 0.05 20);
--navbar-border: oklch(0.25 0.05 20);
}
.orange {
--background: oklch(0.98 0.01 40);
--foreground: oklch(0.2 0.05 40);
--card: oklch(1 0 0);
--card-foreground: oklch(0.2 0.05 40);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.2 0.05 40);
--primary: oklch(0.7 0.2 50);
--primary-foreground: oklch(0.98 0.01 40);
--secondary: oklch(0.94 0.02 40);
--secondary-foreground: oklch(0.2 0.05 40);
--muted: oklch(0.94 0.02 40);
--muted-foreground: oklch(0.5 0.05 40);
--accent: oklch(0.94 0.02 40);
--accent-foreground: oklch(0.2 0.05 40);
--destructive: oklch(0.58 0.24 28);
--destructive-foreground: oklch(0.98 0.01 40);
--success: oklch(0.55 0.15 142);
--success-foreground: oklch(0.98 0.01 40);
--warning: oklch(0.65 0.15 38);
--warning-foreground: oklch(0.2 0.05 40);
--border: oklch(0.9 0.02 40);
--input: oklch(0.9 0.02 40);
--ring: oklch(0.7 0.2 50);
--sidebar: oklch(0.96 0.01 40);
--sidebar-foreground: oklch(0.2 0.05 40);
--sidebar-primary: oklch(0.2 0.05 40);
--sidebar-primary-foreground: oklch(0.98 0.01 40);
--sidebar-accent: oklch(0.92 0.02 40);
--sidebar-accent-foreground: oklch(0.2 0.05 40);
--sidebar-border: oklch(0.88 0.02 40);
--sidebar-ring: oklch(0.7 0.2 50);
--navbar: oklch(0.96 0.01 40);
--navbar-foreground: oklch(0.2 0.05 40);
--navbar-border: oklch(0.88 0.02 40);
}
.dark.orange {
--background: oklch(0.15 0.05 40);
--foreground: oklch(0.9 0.05 40);
--card: oklch(0.2 0.05 40);
--card-foreground: oklch(0.9 0.05 40);
--popover: oklch(0.22 0.05 40);
--popover-foreground: oklch(0.9 0.05 40);
--primary: oklch(0.7 0.2 50);
--primary-foreground: oklch(0.1 0.05 40);
--secondary: oklch(0.25 0.05 40);
--secondary-foreground: oklch(0.9 0.05 40);
--muted: oklch(0.25 0.05 40);
--muted-foreground: oklch(0.7 0.05 40);
--accent: oklch(0.3 0.05 40);
--accent-foreground: oklch(0.9 0.05 40);
--destructive: oklch(0.7 0.19 22);
--destructive-foreground: oklch(0.2 0.05 40);
--success: oklch(0.6 0.15 142);
--success-foreground: oklch(0.98 0.01 40);
--warning: oklch(0.7 0.15 38);
--warning-foreground: oklch(0.2 0.05 40);
--border: oklch(0.28 0.05 40);
--input: oklch(0.35 0.05 40);
--ring: oklch(0.7 0.2 50);
--sidebar: oklch(0.1 0.05 40);
--sidebar-foreground: oklch(0.9 0.05 40);
--sidebar-primary: oklch(0.9 0.05 40);
--sidebar-primary-foreground: oklch(0.1 0.05 40);
--sidebar-accent: oklch(0.2 0.05 40);
--sidebar-accent-foreground: oklch(0.9 0.05 40);
--sidebar-border: oklch(0.25 0.05 40);
--sidebar-ring: oklch(0.35 0.05 40);
--navbar: oklch(0.1 0.05 40);
--navbar-foreground: oklch(0.9 0.05 40);
--navbar-border: oklch(0.25 0.05 40);
.dark {
--background: 240 10% 3.9%;
/* #09090B */
--foreground: 0 0% 98%;
/* #FAFAFA */
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 20%;
/* #27272A */
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
/* #27272A */
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-navbar: var(--navbar);
--color-navbar-foreground: var(--navbar-foreground);
--color-navbar-border: var(--navbar-border);
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--font-sans: var(--font-sans);
--font-mono: var(--font-mono);
--font-serif: var(--font-serif);
--font-sans: var(--font-sans), sans-serif;
--font-heading: var(--font-heading), serif;
--font-mono: var(--font-geist-mono), monospace;
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--shadow-2xs: var(--shadow-2xs);
--shadow-xs: var(--shadow-xs);
--shadow-sm: var(--shadow-sm);
--shadow: var(--shadow);
--shadow-md: var(--shadow-md);
--shadow-lg: var(--shadow-lg);
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);
}
/* Base styles for proper defaults */
* {
border-color: var(--border);
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-heading;
}
}
body {
color: var(--foreground);
background-image: radial-gradient(var(--border) 1px, transparent 1px);
background-size: 24px 24px;
background-attachment: fixed;
@layer utilities {
.animate-blob {
animation: blob 7s infinite;
}
.card-hover {
transition: all 0.3s ease-out;
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px -10px hsl(var(--foreground) / 0.1);
}
.button-hover {
transition: all 0.2s ease-out;
}
.button-hover:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px -4px hsl(var(--foreground) / 0.1);
}
}
@keyframes blob {
0% {
transform: translate(0px, 0px) scale(1);
}
33% {
transform: translate(30px, -50px) scale(1.1);
}
66% {
transform: translate(-20px, 20px) scale(0.9);
}
100% {
transform: translate(0px, 0px) scale(1);
}
}

View File

@@ -1,5 +1,6 @@
import type { Config } from "tailwindcss";
import animate from "tailwindcss-animate";
import defaultTheme from "tailwindcss/defaultTheme";
export default {
darkMode: "class",
@@ -14,71 +15,57 @@ export default {
xs: "475px",
},
fontFamily: {
sans: ["var(--font-geist-sans)", "Frutiger", "sans-serif"],
mono: ["var(--font-geist-mono)", "monospace"],
serif: ["var(--font-serif)", "serif"],
sans: ["var(--font-sans)", ...defaultTheme.fontFamily.sans],
heading: ["var(--font-heading)", ...defaultTheme.fontFamily.sans],
mono: ["var(--font-geist-mono)", ...defaultTheme.fontFamily.mono],
},
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "var(--card)",
foreground: "var(--card-foreground)",
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "var(--popover)",
foreground: "var(--popover-foreground)",
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "var(--primary)",
foreground: "var(--primary-foreground)",
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "var(--secondary)",
foreground: "var(--secondary-foreground)",
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "var(--muted)",
foreground: "var(--muted-foreground)",
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "var(--accent)",
foreground: "var(--accent-foreground)",
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "var(--destructive)",
foreground: "var(--destructive-foreground)",
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
success: {
DEFAULT: "var(--success)",
foreground: "var(--success-foreground)",
DEFAULT: "hsl(var(--success))",
foreground: "hsl(var(--success-foreground))",
},
warning: {
DEFAULT: "var(--warning)",
foreground: "var(--warning-foreground)",
DEFAULT: "hsl(var(--warning))",
foreground: "hsl(var(--warning-foreground))",
},
border: "var(--border)",
input: "var(--input)",
ring: "var(--ring)",
sidebar: {
DEFAULT: "var(--sidebar)",
foreground: "var(--sidebar-foreground)",
primary: "var(--sidebar-primary)",
"primary-foreground": "var(--sidebar-primary-foreground)",
accent: "var(--sidebar-accent)",
"accent-foreground": "var(--sidebar-accent-foreground)",
border: "var(--sidebar-border)",
ring: "var(--sidebar-ring)",
},
navbar: "var(--navbar)",
"navbar-foreground": "var(--navbar-foreground)",
"navbar-border": "var(--navbar-border)",
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"subtle-spin": "spin-slow 20s linear infinite",
"subtle-wave": "wave 15s ease-in-out infinite alternate",
blob: "blob 7s infinite",
},
keyframes: {
"accordion-down": {
@@ -89,15 +76,11 @@ export default {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"spin-slow": {
from: { transform: "rotate(0deg)" },
to: { transform: "rotate(360deg)" },
},
wave: {
"0%": { transform: "translate(0, 0) scale(1)" },
blob: {
"0%": { transform: "translate(0px, 0px) scale(1)" },
"33%": { transform: "translate(30px, -50px) scale(1.1)" },
"66%": { transform: "translate(-20px, 20px) scale(0.9)" },
"100%": { transform: "translate(20px, -30px) scale(1.05)" },
"100%": { transform: "translate(0px, 0px) scale(1)" },
},
},
borderRadius: {