mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2025-12-13 09:34:44 -05:00
Theme overhaul
This commit is contained in:
289
bun.lock
289
bun.lock
@@ -4,7 +4,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "beenvoice",
|
"name": "beenvoice",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/drizzle-adapter": "^1.7.2",
|
"@auth/drizzle-adapter": "^1.10.0",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@react-pdf/renderer": "^4.3.0",
|
"@react-pdf/renderer": "^4.3.0",
|
||||||
"@t3-oss/env-nextjs": "^0.12.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
"@tanstack/react-query": "^5.69.0",
|
"@tanstack/react-query": "^5.84.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tiptap/extension-color": "^3.0.7",
|
"@tiptap/extension-color": "^3.0.7",
|
||||||
"@tiptap/extension-list-item": "^3.0.7",
|
"@tiptap/extension-list-item": "^3.0.7",
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
"@tiptap/extension-text-style": "^3.0.7",
|
"@tiptap/extension-text-style": "^3.0.7",
|
||||||
"@tiptap/react": "^3.0.7",
|
"@tiptap/react": "^3.0.7",
|
||||||
"@tiptap/starter-kit": "^3.0.7",
|
"@tiptap/starter-kit": "^3.0.7",
|
||||||
"@trpc/client": "^11.0.0",
|
"@trpc/client": "^11.4.3",
|
||||||
"@trpc/react-query": "^11.0.0",
|
"@trpc/react-query": "^11.4.3",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.4.3",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/pg": "^8.15.5",
|
"@types/pg": "^8.15.5",
|
||||||
@@ -46,45 +46,48 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-orm": "^0.44.4",
|
"drizzle-orm": "^0.44.4",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
"lucide": "^0.525.0",
|
"lucide": "^0.525.0",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next": "^15.4.4",
|
"next": "^15.4.5",
|
||||||
"next-auth": "5.0.0-beta.25",
|
"next-auth": "5.0.0-beta.25",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.1.1",
|
||||||
"react-day-picker": "^9.8.0",
|
"react-day-picker": "^9.8.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.1.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
|
"recharts": "^3.1.0",
|
||||||
"resend": "^4.7.0",
|
"resend": "^4.7.0",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"sonner": "^2.0.6",
|
"sonner": "^2.0.6",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.2",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"turso": "^0.1.0",
|
"turso": "^0.1.0",
|
||||||
"vercel": "^44.6.4",
|
"vercel": "^44.6.5",
|
||||||
"zod": "^3.24.2",
|
"zod": "^3.25.76",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@tailwindcss/postcss": "^4.0.15",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.19.9",
|
||||||
"@types/raf": "^3.4.3",
|
"@types/raf": "^3.4.3",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.1.7",
|
||||||
"better-sqlite3": "^12.2.0",
|
"better-sqlite3": "^12.2.0",
|
||||||
"drizzle-kit": "^0.30.5",
|
"drizzle-kit": "^0.30.6",
|
||||||
"eslint": "^9.23.0",
|
"eslint": "^9.32.0",
|
||||||
"eslint-config-next": "^15.2.3",
|
"eslint-config-next": "^15.4.5",
|
||||||
"eslint-plugin-drizzle": "^0.2.3",
|
"eslint-plugin-drizzle": "^0.2.3",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"tailwindcss": "^4.0.15",
|
"tailwindcss": "^4.1.11",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.6",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.9.2",
|
||||||
"typescript-eslint": "^8.27.0",
|
"typescript-eslint": "^8.38.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -194,15 +197,15 @@
|
|||||||
|
|
||||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="],
|
"@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="],
|
||||||
|
|
||||||
"@eslint/core": ["@eslint/core@0.14.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg=="],
|
"@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="],
|
||||||
|
|
||||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
|
||||||
|
|
||||||
"@eslint/js": ["@eslint/js@9.30.1", "", {}, "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg=="],
|
"@eslint/js": ["@eslint/js@9.32.0", "", {}, "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg=="],
|
||||||
|
|
||||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
|
||||||
|
|
||||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.3", "", { "dependencies": { "@eslint/core": "^0.15.1", "levn": "^0.4.1" } }, "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag=="],
|
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.4", "", { "dependencies": { "@eslint/core": "^0.15.1", "levn": "^0.4.1" } }, "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw=="],
|
||||||
|
|
||||||
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
|
||||||
|
|
||||||
@@ -282,25 +285,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=="],
|
"@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@15.4.4", "", {}, "sha512-SJKOOkULKENyHSYXE5+KiFU6itcIb6wSBjgM92meK0HVKpo94dNOLZVdLLuS7/BxImROkGoPsjR4EnuDucqiiA=="],
|
"@next/env": ["@next/env@15.4.5", "", {}, "sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ=="],
|
||||||
|
|
||||||
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.3.5", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-BZwWPGfp9po/rAnJcwUBaM+yT/+yTWIkWdyDwc74G9jcfTrNrmsHe+hXHljV066YNdVs8cxROxX5IgMQGX190w=="],
|
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.4.5", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw=="],
|
||||||
|
|
||||||
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eVG55dnGwfUuG+TtnUCt+mEJ+8TGgul6nHEvdb8HEH7dmJIFYOCApAaFrIrxwtEq2Cdf+0m5sG1Np8cNpw9EAw=="],
|
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA=="],
|
||||||
|
|
||||||
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zqG+/8apsu49CltEj4NAmCGZvHcZbOOOsNoTVeIXphYWIbE4l6A/vuQHyqll0flU2o3dmYCXsBW5FmbrGDgljQ=="],
|
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-LRD4l2lq4R+2QCHBQVC0wjxxkLlALGJCwigaJ5FSRSqnje+MRKHljQNZgDCaKUZQzO/TXxlmUdkZP/X3KNGZaw=="],
|
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ=="],
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-LsGUCTvuZ0690fFWerA4lnQvjkYg9gHo12A3wiPUR4kCxbx/d+SlwmonuTH2SWZI+RVGA9VL3N0S03WTYv6bYg=="],
|
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-aOy5yNRpLL3wNiJVkFYl6w22hdREERNjvegE6vvtix8LHRdsTHhWTpgvcYdCK7AIDCQW5ATmzr9XkPHvSoAnvg=="],
|
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA=="],
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-FL7OAn4UkR8hKQRGBmlHiHinzOb07tsfARdGh7v0Z0jEJ3sz8/7L5bR23ble9E6DZMabSStqlATHlSxv1fuzAg=="],
|
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ=="],
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-eEdNW/TXwjYhOulQh0pffTMMItWVwKCQpbziSBmgBNFZIIRn2GTXrhrewevs8wP8KXWYMx8Z+mNU0X+AfvtrRg=="],
|
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ=="],
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-SE5pYNbn/xZKMy1RE3pAs+4xD32OI4rY6mzJa4XUkp/ItZY+OMjIgilskmErt8ls/fVJ+Ihopi2QIeW6O3TrMw=="],
|
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
@@ -426,6 +429,8 @@
|
|||||||
|
|
||||||
"@react-pdf/types": ["@react-pdf/types@2.9.0", "", { "dependencies": { "@react-pdf/font": "^4.0.2", "@react-pdf/primitives": "^4.1.1", "@react-pdf/stylesheet": "^6.1.0" } }, "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ=="],
|
"@react-pdf/types": ["@react-pdf/types@2.9.0", "", { "dependencies": { "@react-pdf/font": "^4.0.2", "@react-pdf/primitives": "^4.1.1", "@react-pdf/stylesheet": "^6.1.0" } }, "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ=="],
|
||||||
|
|
||||||
|
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.8.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A=="],
|
||||||
|
|
||||||
"@remirror/core-constants": ["@remirror/core-constants@3.0.0", "", {}, "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg=="],
|
"@remirror/core-constants": ["@remirror/core-constants@3.0.0", "", {}, "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg=="],
|
||||||
|
|
||||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
||||||
@@ -438,6 +443,10 @@
|
|||||||
|
|
||||||
"@sinclair/typebox": ["@sinclair/typebox@0.25.24", "", {}, "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ=="],
|
"@sinclair/typebox": ["@sinclair/typebox@0.25.24", "", {}, "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ=="],
|
||||||
|
|
||||||
|
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||||
|
|
||||||
|
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
|
||||||
|
|
||||||
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
||||||
|
|
||||||
"@t3-oss/env-core": ["@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-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw=="],
|
"@t3-oss/env-core": ["@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-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw=="],
|
||||||
@@ -474,9 +483,9 @@
|
|||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="],
|
||||||
|
|
||||||
"@tanstack/query-core": ["@tanstack/query-core@5.82.0", "", {}, "sha512-JrjoVuaajBQtnoWSg8iaPHaT4mW73lK2t+exxHNOSMqy0+13eKLqJgTKXKImLejQIfdAHQ6Un0njEhOvUtOd5w=="],
|
"@tanstack/query-core": ["@tanstack/query-core@5.83.1", "", {}, "sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q=="],
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.82.0", "", { "dependencies": { "@tanstack/query-core": "5.82.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-mnk8/ofKEthFeMdhV1dV8YXRf+9HqvXAcciXkoo755d/ocfWq7N/Y9jGOzS3h7ZW9dDGwSIhs3/HANWUBsyqYg=="],
|
"@tanstack/react-query": ["@tanstack/react-query@5.84.0", "", { "dependencies": { "@tanstack/query-core": "5.83.1" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-iPycFGLq5lltDE16Jf13Nx7SOvtfoopfOH/+Ahbdd+z4QqOfYu/SOkY86AVYVcKjneuqPxTm8e85lSGhwe0cog=="],
|
||||||
|
|
||||||
"@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/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=="],
|
||||||
|
|
||||||
@@ -570,6 +579,24 @@
|
|||||||
|
|
||||||
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
||||||
|
|
||||||
|
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
||||||
|
|
||||||
|
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
||||||
|
|
||||||
|
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
|
||||||
|
|
||||||
|
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
|
||||||
|
|
||||||
|
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
|
||||||
|
|
||||||
|
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
|
||||||
|
|
||||||
|
"@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="],
|
||||||
|
|
||||||
|
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
|
||||||
|
|
||||||
|
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
||||||
"@types/file-saver": ["@types/file-saver@2.0.7", "", {}, "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A=="],
|
"@types/file-saver": ["@types/file-saver@2.0.7", "", {}, "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A=="],
|
||||||
@@ -584,15 +611,15 @@
|
|||||||
|
|
||||||
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
|
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@20.19.6", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-uYssdp9z5zH5GQ0L4zEJ2ZuavYsJwkozjiUzCRfGtaaQcyjAMJ34aP8idv61QlqTozu6kudyr6JMq9Chf09dfA=="],
|
"@types/node": ["@types/node@20.19.9", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw=="],
|
||||||
|
|
||||||
"@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="],
|
"@types/pg": ["@types/pg@8.15.5", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ=="],
|
||||||
|
|
||||||
"@types/raf": ["@types/raf@3.4.3", "", {}, "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="],
|
"@types/raf": ["@types/raf@3.4.3", "", {}, "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
"@types/react": ["@types/react@19.1.9", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.1.6", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw=="],
|
"@types/react-dom": ["@types/react-dom@19.1.7", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw=="],
|
||||||
|
|
||||||
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
|
"@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="],
|
||||||
|
|
||||||
@@ -600,19 +627,19 @@
|
|||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.36.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.36.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-FuYgkHwZLuPbZjQHzJXrtXreJdFMKl16BFYyRrLxDhWr6Qr7Kbcu2s1Yhu8tsiMXw1S0W1pjfFfYEt+R604s+Q=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.38.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.38.0", "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg=="],
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0" } }, "sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA=="],
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.38.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.36.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/utils": "8.36.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.36.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.36.0", "@typescript-eslint/utils": "8.36.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.36.0", "", {}, "sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.38.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.38.0", "@typescript-eslint/tsconfig-utils": "8.38.0", "@typescript-eslint/types": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.38.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.36.0", "", { "dependencies": { "@typescript-eslint/types": "8.36.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA=="],
|
||||||
|
|
||||||
@@ -670,11 +697,11 @@
|
|||||||
|
|
||||||
"@vercel/go": ["@vercel/go@3.2.2", "", {}, "sha512-yYIU3OXkuoxzr47vf+HZnVGujzkVW/e5hrUK0jzCzzmjgo4+5ECrXDSA1bTB6qmWEy6HVsSovnKoHkwDqNIN6Q=="],
|
"@vercel/go": ["@vercel/go@3.2.2", "", {}, "sha512-yYIU3OXkuoxzr47vf+HZnVGujzkVW/e5hrUK0jzCzzmjgo4+5ECrXDSA1bTB6qmWEy6HVsSovnKoHkwDqNIN6Q=="],
|
||||||
|
|
||||||
"@vercel/hono": ["@vercel/hono@0.0.4", "", { "dependencies": { "@vercel/node": "5.3.7", "@vercel/static-config": "3.1.1", "ts-morph": "12.0.0" } }, "sha512-seH+eaHEooC8EWVKGRtPtFMrl7n2dvO1OJ3YCt1Fou6tRBfi4spErGs2sdC4raogAWO645BK2bepH5Fo/wJFZw=="],
|
"@vercel/hono": ["@vercel/hono@0.0.5", "", { "dependencies": { "@vercel/node": "5.3.7", "@vercel/static-config": "3.1.1", "ts-morph": "12.0.0" } }, "sha512-y+bmKJq9OcSMwtGbvpzXCIrwPrM1t3XkDQMo9KamVtDF9qK3V8NteX09wBeEZzObovFkfUwhneOth1HUVhqC0w=="],
|
||||||
|
|
||||||
"@vercel/hydrogen": ["@vercel/hydrogen@1.2.3", "", { "dependencies": { "@vercel/static-config": "3.1.1", "ts-morph": "12.0.0" } }, "sha512-sU0HWHabNxlTdKQWPMDqwr+3MqnhXc4Tvnl4i9cbdEYn5061SEKhdfpl2RhuvPByQJ8sZL8soVXFHIjFBJAR4w=="],
|
"@vercel/hydrogen": ["@vercel/hydrogen@1.2.3", "", { "dependencies": { "@vercel/static-config": "3.1.1", "ts-morph": "12.0.0" } }, "sha512-sU0HWHabNxlTdKQWPMDqwr+3MqnhXc4Tvnl4i9cbdEYn5061SEKhdfpl2RhuvPByQJ8sZL8soVXFHIjFBJAR4w=="],
|
||||||
|
|
||||||
"@vercel/next": ["@vercel/next@4.10.8", "", { "dependencies": { "@vercel/nft": "0.29.2" } }, "sha512-2con6J6uj4IAs+3SU55ZjH6flskDpi6zCRH4XmfnR0SUrsGiy6hmDbZAvfJnSHKxLFQpmP0+cn5XC+hoUAUQLg=="],
|
"@vercel/next": ["@vercel/next@4.10.9", "", { "dependencies": { "@vercel/nft": "0.29.2" } }, "sha512-QtXJaOl2GyDYac3Bzgt6YqYgBb90KG5fLC4D2dC9KAxq2NNBt7y8yEakaWYrIyXCw0bhyX/TjcYf7VneIrHVpg=="],
|
||||||
|
|
||||||
"@vercel/nft": ["@vercel/nft@0.29.2", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^10.4.5", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA=="],
|
"@vercel/nft": ["@vercel/nft@0.29.2", "", { "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", "@rollup/pluginutils": "^5.1.3", "acorn": "^8.6.0", "acorn-import-attributes": "^1.9.5", "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", "glob": "^10.4.5", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", "resolve-from": "^5.0.0" }, "bin": { "nft": "out/cli.js" } }, "sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA=="],
|
||||||
|
|
||||||
@@ -846,6 +873,28 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
||||||
|
|
||||||
|
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
||||||
|
|
||||||
|
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
|
||||||
|
|
||||||
|
"d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="],
|
||||||
|
|
||||||
|
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
||||||
|
|
||||||
|
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
||||||
|
|
||||||
|
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
|
||||||
|
|
||||||
|
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
|
||||||
|
|
||||||
|
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
|
||||||
|
|
||||||
|
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
|
||||||
|
|
||||||
|
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
||||||
|
|
||||||
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
|
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
|
||||||
|
|
||||||
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
|
||||||
@@ -862,6 +911,8 @@
|
|||||||
|
|
||||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||||
|
|
||||||
|
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
|
||||||
|
|
||||||
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||||
|
|
||||||
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
|
||||||
@@ -932,6 +983,8 @@
|
|||||||
|
|
||||||
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
|
||||||
|
|
||||||
|
"es-toolkit": ["es-toolkit@1.39.8", "", {}, "sha512-A8QO9TfF+rltS8BXpdu8OS+rpGgEdnRhqIVxO/ZmNvnXBYgOdSsxukT55ELyP94gZIntWJ+Li9QRrT2u1Kitpg=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
|
"esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
|
||||||
|
|
||||||
"esbuild-android-64": ["esbuild-android-64@0.14.47", "", { "os": "android", "cpu": "x64" }, "sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g=="],
|
"esbuild-android-64": ["esbuild-android-64@0.14.47", "", { "os": "android", "cpu": "x64" }, "sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g=="],
|
||||||
@@ -978,9 +1031,9 @@
|
|||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@9.30.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.30.1", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "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-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ=="],
|
"eslint": ["eslint@9.32.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.32.0", "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "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-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg=="],
|
||||||
|
|
||||||
"eslint-config-next": ["eslint-config-next@15.3.5", "", { "dependencies": { "@next/eslint-plugin-next": "15.3.5", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg=="],
|
"eslint-config-next": ["eslint-config-next@15.4.5", "", { "dependencies": { "@next/eslint-plugin-next": "15.4.5", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-IMijiXaZ43qFB+Gcpnb374ipTKD8JIyVNR+6VsifFQ/LHyx+A9wgcgSIhCX5PYSjwOoSYD5LtNHKlM5uc23eww=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -1016,6 +1069,8 @@
|
|||||||
|
|
||||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||||
|
|
||||||
|
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||||
|
|
||||||
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||||
|
|
||||||
"events-intercept": ["events-intercept@2.0.0", "", {}, "sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q=="],
|
"events-intercept": ["events-intercept@2.0.0", "", {}, "sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q=="],
|
||||||
@@ -1058,6 +1113,8 @@
|
|||||||
|
|
||||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||||
|
|
||||||
|
"framer-motion": ["framer-motion@12.23.12", "", { "dependencies": { "motion-dom": "^12.23.12", "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-6e78rdVtnBvlEVgu6eFEAgG9v3wLnYEboM8I5O5EXvfKC8gxGQB8wXJdhkMy10iVcn05jl6CNw7/HTsTCfwcWg=="],
|
||||||
|
|
||||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||||
|
|
||||||
"fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="],
|
"fs-extra": ["fs-extra@11.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw=="],
|
||||||
@@ -1134,6 +1191,8 @@
|
|||||||
|
|
||||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||||
|
|
||||||
|
"immer": ["immer@10.1.1", "", {}, "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw=="],
|
||||||
|
|
||||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||||
|
|
||||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||||
@@ -1144,6 +1203,8 @@
|
|||||||
|
|
||||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||||
|
|
||||||
|
"internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="],
|
||||||
|
|
||||||
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
|
||||||
|
|
||||||
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
|
||||||
@@ -1322,6 +1383,10 @@
|
|||||||
|
|
||||||
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
|
||||||
|
|
||||||
|
"motion-dom": ["motion-dom@12.23.12", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw=="],
|
||||||
|
|
||||||
|
"motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
|
||||||
|
|
||||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
@@ -1334,10 +1399,12 @@
|
|||||||
|
|
||||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||||
|
|
||||||
"next": ["next@15.4.4", "", { "dependencies": { "@next/env": "15.4.4", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.4", "@next/swc-darwin-x64": "15.4.4", "@next/swc-linux-arm64-gnu": "15.4.4", "@next/swc-linux-arm64-musl": "15.4.4", "@next/swc-linux-x64-gnu": "15.4.4", "@next/swc-linux-x64-musl": "15.4.4", "@next/swc-win32-arm64-msvc": "15.4.4", "@next/swc-win32-x64-msvc": "15.4.4", "sharp": "^0.34.3" }, "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-kNcubvJjOL9yUOfwtZF3HfDhuhp+kVD+FM2A6Tyua1eI/xfmY4r/8ZS913MMz+oWKDlbps/dQOWdDricuIkXLw=="],
|
"next": ["next@15.4.5", "", { "dependencies": { "@next/env": "15.4.5", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.4.5", "@next/swc-darwin-x64": "15.4.5", "@next/swc-linux-arm64-gnu": "15.4.5", "@next/swc-linux-arm64-musl": "15.4.5", "@next/swc-linux-x64-gnu": "15.4.5", "@next/swc-linux-x64-musl": "15.4.5", "@next/swc-win32-arm64-msvc": "15.4.5", "@next/swc-win32-x64-msvc": "15.4.5", "sharp": "^0.34.3" }, "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-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g=="],
|
||||||
|
|
||||||
"next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0-0", "nodemailer": "^6.6.5", "react": "^18.2.0 || ^19.0.0-0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="],
|
"next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0-0", "nodemailer": "^6.6.5", "react": "^18.2.0 || ^19.0.0-0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="],
|
||||||
|
|
||||||
|
"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-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
|
"node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
|
||||||
|
|
||||||
"node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
|
"node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
|
||||||
@@ -1518,11 +1585,11 @@
|
|||||||
|
|
||||||
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
||||||
|
|
||||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
|
||||||
|
|
||||||
"react-day-picker": ["react-day-picker@9.8.0", "", { "dependencies": { "@date-fns/tz": "1.2.0", "date-fns": "4.1.0", "date-fns-jalali": "4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-E0yhhg7R+pdgbl/2toTb0xBhsEAtmAx1l7qjIWYfcxOy8w4rTSVfbtBoSzVVhPwKP/5E9iL38LivzoE3AQDhCQ=="],
|
"react-day-picker": ["react-day-picker@9.8.1", "", { "dependencies": { "@date-fns/tz": "^1.2.0", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-kMcLrp3PfN/asVJayVv82IjF3iLOOxuH5TNFWezX6lS/T8iVRFPTETpHl3TUSTH99IDMZLubdNPJr++rQctkEw=="],
|
||||||
|
|
||||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
|
||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -1530,6 +1597,8 @@
|
|||||||
|
|
||||||
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
|
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
|
||||||
|
|
||||||
|
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
||||||
|
|
||||||
"react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="],
|
"react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="],
|
||||||
|
|
||||||
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
||||||
@@ -1540,12 +1609,20 @@
|
|||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
|
||||||
|
"recharts": ["recharts@3.1.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", "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-NqAqQcGBmLrfDs2mHX/bz8jJCQtG2FeXfE0GqpZmIuXIjkpIwj8sd9ad0WyvKiBKPd8ZgNG0hL85c8sFDwascw=="],
|
||||||
|
|
||||||
|
"redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="],
|
||||||
|
|
||||||
|
"redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
|
||||||
|
|
||||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||||
|
|
||||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||||
|
|
||||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||||
|
|
||||||
|
"reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||||
|
|
||||||
"resend": ["resend@4.7.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-30IbXGBUbmDweQH2IlO53XOXX7ndjYV9xFZ8IEBiWqefqQ/qmTsgrX0Ab6MUnmobJXbpdReVv+iXGRQPubQL5Q=="],
|
"resend": ["resend@4.7.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-30IbXGBUbmDweQH2IlO53XOXX7ndjYV9xFZ8IEBiWqefqQ/qmTsgrX0Ab6MUnmobJXbpdReVv+iXGRQPubQL5Q=="],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||||
@@ -1692,6 +1769,8 @@
|
|||||||
|
|
||||||
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||||
|
|
||||||
|
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||||
|
|
||||||
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
|
||||||
@@ -1720,7 +1799,7 @@
|
|||||||
|
|
||||||
"turso": ["turso@0.1.0", "", {}, "sha512-gM6zaKIQA35QoujeknL1oNFjgduei9sgVRrKc9XRSG1P+VAUAQzfKJGlLQqcnsHFh+a1kqRV16gI/mCvyEJS1Q=="],
|
"turso": ["turso@0.1.0", "", {}, "sha512-gM6zaKIQA35QoujeknL1oNFjgduei9sgVRrKc9XRSG1P+VAUAQzfKJGlLQqcnsHFh+a1kqRV16gI/mCvyEJS1Q=="],
|
||||||
|
|
||||||
"tw-animate-css": ["tw-animate-css@1.3.5", "", {}, "sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA=="],
|
"tw-animate-css": ["tw-animate-css@1.3.6", "", {}, "sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA=="],
|
||||||
|
|
||||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||||
|
|
||||||
@@ -1732,9 +1811,9 @@
|
|||||||
|
|
||||||
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.36.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.36.0", "@typescript-eslint/parser": "8.36.0", "@typescript-eslint/utils": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-fTCqxthY+h9QbEgSIBfL9iV6CvKDFuoxg6bHPNpJ9HIUzS+jy2lCEyCmGyZRWEBSaykqcDPf1SJ+BfCI8DRopA=="],
|
"typescript-eslint": ["typescript-eslint@8.38.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.38.0", "@typescript-eslint/parser": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0", "@typescript-eslint/utils": "8.38.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg=="],
|
||||||
|
|
||||||
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
|
||||||
|
|
||||||
@@ -1768,7 +1847,9 @@
|
|||||||
|
|
||||||
"v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
|
"v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
|
||||||
|
|
||||||
"vercel": ["vercel@44.6.4", "", { "dependencies": { "@vercel/blob": "1.0.2", "@vercel/build-utils": "11.0.0", "@vercel/fun": "1.1.6", "@vercel/go": "3.2.2", "@vercel/hono": "0.0.4", "@vercel/hydrogen": "1.2.3", "@vercel/next": "4.10.8", "@vercel/node": "5.3.7", "@vercel/python": "5.0.0", "@vercel/redwood": "2.3.4", "@vercel/remix-builder": "5.4.10", "@vercel/ruby": "2.2.1", "@vercel/static-build": "2.7.17", "chokidar": "4.0.0", "jose": "5.9.6" }, "bin": { "vc": "dist/vc.js", "vercel": "dist/vc.js" } }, "sha512-BfZelATWvXMhjta0LgaUU+HTB3YOk1a3iCuRkJwPDRB8SIKjCgIJSRNA3lzW/sDswpVGb3ZxWo4HLp9TYERM1w=="],
|
"vercel": ["vercel@44.6.5", "", { "dependencies": { "@vercel/blob": "1.0.2", "@vercel/build-utils": "11.0.0", "@vercel/fun": "1.1.6", "@vercel/go": "3.2.2", "@vercel/hono": "0.0.5", "@vercel/hydrogen": "1.2.3", "@vercel/next": "4.10.9", "@vercel/node": "5.3.7", "@vercel/python": "5.0.0", "@vercel/redwood": "2.3.4", "@vercel/remix-builder": "5.4.10", "@vercel/ruby": "2.2.1", "@vercel/static-build": "2.7.17", "chokidar": "4.0.0", "jose": "5.9.6" }, "bin": { "vc": "dist/vc.js", "vercel": "dist/vc.js" } }, "sha512-a5fJkYbWEVUgzxPU9depZlWZir/8PAPo4pHCzykQ6NAubNSWmYZE0rZBYrw+jSAhzGJKdbS0ATVbti1MF/e/xA=="],
|
||||||
|
|
||||||
|
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
|
||||||
|
|
||||||
"vite-compatible-readable-stream": ["vite-compatible-readable-stream@3.6.1", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ=="],
|
"vite-compatible-readable-stream": ["vite-compatible-readable-stream@3.6.1", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ=="],
|
||||||
|
|
||||||
@@ -1828,8 +1909,6 @@
|
|||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
"@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="],
|
|
||||||
|
|
||||||
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
|
||||||
|
|
||||||
"@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.6.9", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg=="],
|
"@mapbox/node-pre-gyp/node-fetch": ["node-fetch@2.6.9", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg=="],
|
||||||
@@ -1854,12 +1933,34 @@
|
|||||||
|
|
||||||
"@ts-morph/common/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
"@ts-morph/common/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
||||||
|
|
||||||
|
"@types/better-sqlite3/@types/node": ["@types/node@20.19.6", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-uYssdp9z5zH5GQ0L4zEJ2ZuavYsJwkozjiUzCRfGtaaQcyjAMJ34aP8idv61QlqTozu6kudyr6JMq9Chf09dfA=="],
|
||||||
|
|
||||||
|
"@types/pg/@types/node": ["@types/node@20.19.6", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-uYssdp9z5zH5GQ0L4zEJ2ZuavYsJwkozjiUzCRfGtaaQcyjAMJ34aP8idv61QlqTozu6kudyr6JMq9Chf09dfA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.36.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/typescript-estree": "8.36.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "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.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
"@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "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.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0" } }, "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
"@vercel/fun/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="],
|
"@vercel/fun/debug": ["debug@4.3.4", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="],
|
||||||
|
|
||||||
"@vercel/fun/ms": ["ms@2.1.1", "", {}, "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="],
|
"@vercel/fun/ms": ["ms@2.1.1", "", {}, "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="],
|
||||||
@@ -1948,6 +2049,10 @@
|
|||||||
|
|
||||||
"ts-node/arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
|
"ts-node/arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.38.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/type-utils": "8.38.0", "@typescript-eslint/utils": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.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.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.38.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.38.0", "@typescript-eslint/types": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ=="],
|
||||||
|
|
||||||
"unicode-properties/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
"unicode-properties/base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
"unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
"unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||||
@@ -2004,10 +2109,30 @@
|
|||||||
|
|
||||||
"@ts-morph/common/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"@ts-morph/common/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.36.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.36.0", "@typescript-eslint/tsconfig-utils": "8.36.0", "@typescript-eslint/types": "8.36.0", "@typescript-eslint/visitor-keys": "8.36.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "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.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "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.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
"@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g=="],
|
||||||
|
|
||||||
"@vercel/fun/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
"@vercel/fun/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||||
|
|
||||||
"@vercel/fun/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
|
"@vercel/fun/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
|
||||||
@@ -2040,10 +2165,50 @@
|
|||||||
|
|
||||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0" } }, "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "@typescript-eslint/typescript-estree": "8.38.0", "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "@typescript-eslint/visitor-keys": "8.38.0" } }, "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.38.0", "", { "dependencies": { "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g=="],
|
||||||
|
|
||||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.36.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.36.0", "@typescript-eslint/types": "^8.36.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.36.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "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.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/parser/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
|
|
||||||
"@vercel/fun/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
"@vercel/fun/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
|
"typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.38.0", "", {}, "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||||
|
|
||||||
|
"@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
package.json
55
package.json
@@ -28,7 +28,7 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/drizzle-adapter": "^1.7.2",
|
"@auth/drizzle-adapter": "^1.10.0",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"@react-pdf/renderer": "^4.3.0",
|
"@react-pdf/renderer": "^4.3.0",
|
||||||
"@t3-oss/env-nextjs": "^0.12.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
"@tanstack/react-query": "^5.69.0",
|
"@tanstack/react-query": "^5.84.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tiptap/extension-color": "^3.0.7",
|
"@tiptap/extension-color": "^3.0.7",
|
||||||
"@tiptap/extension-list-item": "^3.0.7",
|
"@tiptap/extension-list-item": "^3.0.7",
|
||||||
@@ -56,9 +56,9 @@
|
|||||||
"@tiptap/extension-text-style": "^3.0.7",
|
"@tiptap/extension-text-style": "^3.0.7",
|
||||||
"@tiptap/react": "^3.0.7",
|
"@tiptap/react": "^3.0.7",
|
||||||
"@tiptap/starter-kit": "^3.0.7",
|
"@tiptap/starter-kit": "^3.0.7",
|
||||||
"@trpc/client": "^11.0.0",
|
"@trpc/client": "^11.4.3",
|
||||||
"@trpc/react-query": "^11.0.0",
|
"@trpc/react-query": "^11.4.3",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.4.3",
|
||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/pg": "^8.15.5",
|
"@types/pg": "^8.15.5",
|
||||||
@@ -70,45 +70,48 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-orm": "^0.44.4",
|
"drizzle-orm": "^0.44.4",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"framer-motion": "^12.23.12",
|
||||||
"lucide": "^0.525.0",
|
"lucide": "^0.525.0",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next": "^15.4.4",
|
"next": "^15.4.5",
|
||||||
"next-auth": "5.0.0-beta.25",
|
"next-auth": "5.0.0-beta.25",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.1.1",
|
||||||
"react-day-picker": "^9.8.0",
|
"react-day-picker": "^9.8.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.1.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
|
"recharts": "^3.1.0",
|
||||||
"resend": "^4.7.0",
|
"resend": "^4.7.0",
|
||||||
"server-only": "^0.0.1",
|
"server-only": "^0.0.1",
|
||||||
"sonner": "^2.0.6",
|
"sonner": "^2.0.6",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.2",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"turso": "^0.1.0",
|
"turso": "^0.1.0",
|
||||||
"vercel": "^44.6.4",
|
"vercel": "^44.6.5",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.25.76"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@tailwindcss/postcss": "^4.0.15",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^20.14.10",
|
"@types/node": "^20.19.9",
|
||||||
"@types/raf": "^3.4.3",
|
"@types/raf": "^3.4.3",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.1.7",
|
||||||
"better-sqlite3": "^12.2.0",
|
"better-sqlite3": "^12.2.0",
|
||||||
"drizzle-kit": "^0.30.5",
|
"drizzle-kit": "^0.30.6",
|
||||||
"eslint": "^9.23.0",
|
"eslint": "^9.32.0",
|
||||||
"eslint-config-next": "^15.2.3",
|
"eslint-config-next": "^15.4.5",
|
||||||
"eslint-plugin-drizzle": "^0.2.3",
|
"eslint-plugin-drizzle": "^0.2.3",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"tailwindcss": "^4.0.15",
|
"tailwindcss": "^4.1.11",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.6",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.9.2",
|
||||||
"typescript-eslint": "^8.27.0"
|
"typescript-eslint": "^8.38.0"
|
||||||
},
|
},
|
||||||
"ct3aMetadata": {
|
"ct3aMetadata": {
|
||||||
"initVersion": "7.39.3"
|
"initVersion": "7.39.3"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 56 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,20 +1,19 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState, Suspense } from "react";
|
import { useState, Suspense } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent } from "~/components/ui/card";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Logo } from "~/components/branding/logo";
|
import { Logo } from "~/components/branding/logo";
|
||||||
import { User, Mail, Lock, ArrowRight } from "lucide-react";
|
import { LegalModal } from "~/components/ui/legal-modal";
|
||||||
|
import { Mail, Lock, ArrowRight, User, Clock, Rocket, Zap } from "lucide-react";
|
||||||
|
|
||||||
function RegisterForm() {
|
function RegisterForm() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const callbackUrl = searchParams.get("callbackUrl") ?? "/dashboard";
|
|
||||||
const [firstName, setFirstName] = useState("");
|
const [firstName, setFirstName] = useState("");
|
||||||
const [lastName, setLastName] = useState("");
|
const [lastName, setLastName] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@@ -24,177 +23,238 @@ function RegisterForm() {
|
|||||||
async function handleRegister(e: React.FormEvent) {
|
async function handleRegister(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const res = await fetch("/api/auth/register", {
|
const res = await fetch("/api/auth/register", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
firstName,
|
name: `${firstName} ${lastName}`,
|
||||||
lastName,
|
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
toast.success("Account created successfully! Please sign in.");
|
toast.success("Account created successfully! Please sign in.");
|
||||||
const signInUrl =
|
router.push("/auth/signin");
|
||||||
callbackUrl !== "/dashboard"
|
|
||||||
? `/auth/signin?callbackUrl=${encodeURIComponent(callbackUrl)}`
|
|
||||||
: "/auth/signin";
|
|
||||||
router.push(signInUrl);
|
|
||||||
} else {
|
} else {
|
||||||
const error = await res.text();
|
const data = (await res.json()) as { error?: string };
|
||||||
toast.error(error || "Failed to create account");
|
toast.error(data.error ?? "Registration failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="floating-orbs flex min-h-screen items-center justify-center p-4">
|
<div className="bg-background flex min-h-screen items-center justify-center">
|
||||||
<div className="w-full max-w-md space-y-8">
|
<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">
|
||||||
{/* Logo and Welcome */}
|
<CardContent className="grid h-full p-0 md:grid-cols-2">
|
||||||
<div className="space-y-4 text-center">
|
{/* Hero Section - Hidden on mobile */}
|
||||||
<Logo size="lg" className="mx-auto" />
|
<div className="bg-muted relative hidden md:flex md:flex-col md:justify-center md:p-12">
|
||||||
<div>
|
<div className="space-y-8">
|
||||||
<h1 className="text-foreground text-2xl font-bold">
|
<div className="space-y-4">
|
||||||
Join beenvoice
|
<div className="flex items-center space-x-2">
|
||||||
</h1>
|
<Logo size="xl" />
|
||||||
<p className="text-muted-foreground mt-2">
|
</div>
|
||||||
Create your account to get started
|
<div className="space-y-3">
|
||||||
</p>
|
<h1 className="text-3xl font-bold lg:text-4xl">
|
||||||
</div>
|
Start your
|
||||||
</div>
|
<span className="text-primary"> invoicing journey</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-lg">
|
||||||
|
Join thousands of freelancers and small businesses who trust
|
||||||
|
beenvoice to manage their invoicing and get paid faster.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Registration Form */}
|
<div className="grid gap-4">
|
||||||
<Card className="card-primary">
|
<div className="flex items-start space-x-4">
|
||||||
<CardHeader className="space-y-1">
|
<div className="bg-primary/10 rounded-lg p-2">
|
||||||
<CardTitle className="text-center text-xl">
|
<Rocket className="text-primary h-5 w-5" />
|
||||||
Create Account
|
</div>
|
||||||
</CardTitle>
|
<div className="space-y-1">
|
||||||
</CardHeader>
|
<h3 className="font-semibold">Quick Setup</h3>
|
||||||
<CardContent>
|
<p className="text-muted-foreground text-sm">
|
||||||
<form onSubmit={handleRegister} className="space-y-4">
|
Get started in minutes with our intuitive setup wizard
|
||||||
<div className="grid grid-cols-2 gap-4">
|
</p>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="firstName">First Name</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<User className="form-icon-left" />
|
|
||||||
<Input
|
|
||||||
id="firstName"
|
|
||||||
type="text"
|
|
||||||
value={firstName}
|
|
||||||
onChange={(e) => setFirstName(e.target.value)}
|
|
||||||
required
|
|
||||||
autoFocus
|
|
||||||
className="form-input-with-icon"
|
|
||||||
placeholder="First name"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="lastName">Last Name</Label>
|
<div className="flex items-start space-x-4">
|
||||||
<div className="relative">
|
<div className="bg-primary/10 rounded-lg p-2">
|
||||||
<User className="form-icon-left" />
|
<Zap className="text-primary h-5 w-5" />
|
||||||
<Input
|
</div>
|
||||||
id="lastName"
|
<div className="space-y-1">
|
||||||
type="text"
|
<h3 className="font-semibold">Fast Payments</h3>
|
||||||
value={lastName}
|
<p className="text-muted-foreground text-sm">
|
||||||
onChange={(e) => setLastName(e.target.value)}
|
Professional invoices that get you paid 3x faster
|
||||||
required
|
</p>
|
||||||
className="form-input-with-icon"
|
</div>
|
||||||
placeholder="Last name"
|
</div>
|
||||||
/>
|
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<div className="bg-primary/10 rounded-lg p-2">
|
||||||
|
<Clock className="text-primary h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="font-semibold">Time Tracking</h3>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Track time and convert it to accurate invoices instantly
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
</div>
|
||||||
<Label htmlFor="email">Email</Label>
|
</div>
|
||||||
<div className="relative">
|
|
||||||
<Mail className="form-icon-left" />
|
{/* Sign Up Form */}
|
||||||
<Input
|
<div className="flex flex-col justify-center p-6 md:p-12">
|
||||||
id="email"
|
<div className="mx-auto w-full max-w-sm space-y-6">
|
||||||
type="email"
|
{/* Mobile Logo */}
|
||||||
value={email}
|
<div className="flex justify-center md:hidden">
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
<Logo size="lg" />
|
||||||
required
|
|
||||||
className="form-input-with-icon"
|
|
||||||
placeholder="Enter your email"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
<div className="space-y-2 text-center md:text-left">
|
||||||
<div className="relative">
|
<h1 className="text-2xl font-bold">Create your account</h1>
|
||||||
<Lock className="form-icon-left" />
|
<p className="text-muted-foreground">
|
||||||
<Input
|
Supercharge your invoicing today
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
required
|
|
||||||
minLength={6}
|
|
||||||
className="form-input-with-icon"
|
|
||||||
placeholder="Create a password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className="text-muted-foreground text-xs">
|
|
||||||
Must be at least 6 characters
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" className="w-full" disabled={loading}>
|
|
||||||
{loading ? (
|
|
||||||
"Creating account..."
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
Create Account
|
|
||||||
<ArrowRight className="ml-2 h-4 w-4" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
<div className="mt-6 text-center text-sm">
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
Already have an account?{" "}
|
|
||||||
</span>
|
|
||||||
<Link href="/auth/signin" className="nav-link-brand">
|
|
||||||
Sign in here
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Features */}
|
<form onSubmit={handleRegister} className="space-y-4">
|
||||||
<div className="space-y-4 text-center">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<p className="welcome-description">Start invoicing like a pro</p>
|
<div className="space-y-2">
|
||||||
<div className="welcome-feature-list">
|
<Label htmlFor="firstName">First Name</Label>
|
||||||
<span>✓ Free to start</span>
|
<div className="relative">
|
||||||
<span>✓ No credit card</span>
|
<User className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 z-10 h-4 w-4 -translate-y-1/2" />
|
||||||
<span>✓ Cancel anytime</span>
|
<Input
|
||||||
|
id="firstName"
|
||||||
|
type="text"
|
||||||
|
value={firstName}
|
||||||
|
onChange={(e) => setFirstName(e.target.value)}
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
className="h-11 pl-10"
|
||||||
|
placeholder="John"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="lastName">Last Name</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<User className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 z-10 h-4 w-4 -translate-y-1/2" />
|
||||||
|
<Input
|
||||||
|
id="lastName"
|
||||||
|
type="text"
|
||||||
|
value={lastName}
|
||||||
|
onChange={(e) => setLastName(e.target.value)}
|
||||||
|
required
|
||||||
|
className="h-11 pl-10"
|
||||||
|
placeholder="Doe"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email Address</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 z-10 h-4 w-4 -translate-y-1/2" />
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
className="h-11 pl-10"
|
||||||
|
placeholder="john@example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 z-10 h-4 w-4 -translate-y-1/2" />
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
className="h-11 pl-10"
|
||||||
|
placeholder="Create a strong password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground text-xs">
|
||||||
|
Must be at least 8 characters long
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="h-11 w-full"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="border-primary-foreground/30 border-t-primary-foreground h-4 w-4 animate-spin rounded-full border-2"></div>
|
||||||
|
<span>Creating account...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span>Create Account</span>
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="text-center text-sm">
|
||||||
|
Already have an account?{" "}
|
||||||
|
<a
|
||||||
|
href="/auth/signin"
|
||||||
|
className="text-primary font-medium hover:underline"
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-muted-foreground text-center text-xs leading-relaxed">
|
||||||
|
By creating an account, you agree to our{" "}
|
||||||
|
<LegalModal
|
||||||
|
type="terms"
|
||||||
|
trigger={
|
||||||
|
<span className="text-primary inline cursor-pointer hover:underline">
|
||||||
|
Terms of Service
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>{" "}
|
||||||
|
and{" "}
|
||||||
|
<LegalModal
|
||||||
|
type="privacy"
|
||||||
|
trigger={
|
||||||
|
<span className="text-primary inline cursor-pointer hover:underline">
|
||||||
|
Privacy Policy
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
fallback={
|
|
||||||
<div className="floating-orbs flex min-h-screen items-center justify-center p-4">
|
|
||||||
<div className="w-full max-w-md space-y-8">
|
|
||||||
<div className="space-y-4 text-center">
|
|
||||||
<Logo size="lg" className="mx-auto" />
|
|
||||||
<div>
|
|
||||||
<h1 className="text-foreground text-2xl font-bold">
|
|
||||||
Join beenvoice
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground mt-2">Loading...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<RegisterForm />
|
<RegisterForm />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useState, Suspense } from "react";
|
import { useState, Suspense } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
import { Card, CardContent } from "~/components/ui/card";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Label } from "~/components/ui/label";
|
import { Label } from "~/components/ui/label";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Logo } from "~/components/branding/logo";
|
import { Logo } from "~/components/branding/logo";
|
||||||
import { Mail, Lock, ArrowRight } from "lucide-react";
|
import { LegalModal } from "~/components/ui/legal-modal";
|
||||||
|
import {
|
||||||
|
Mail,
|
||||||
|
Lock,
|
||||||
|
ArrowRight,
|
||||||
|
Users,
|
||||||
|
FileText,
|
||||||
|
TrendingUp,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
function SignInForm() {
|
function SignInForm() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -42,114 +49,184 @@ function SignInForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="floating-orbs flex min-h-screen items-center justify-center p-4">
|
<div className="bg-background flex min-h-screen items-center justify-center">
|
||||||
<div className="w-full max-w-md space-y-8">
|
<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">
|
||||||
{/* Logo and Welcome */}
|
<CardContent className="grid h-full p-0 md:grid-cols-2">
|
||||||
<div className="space-y-4 text-center">
|
{/* Hero Section - Hidden on mobile */}
|
||||||
<Logo size="lg" className="mx-auto" />
|
<div className="bg-muted relative hidden md:flex md:flex-col md:justify-center md:p-12">
|
||||||
<div>
|
<div className="space-y-8">
|
||||||
<h1 className="text-foreground text-2xl font-bold">Welcome back</h1>
|
<div className="space-y-4">
|
||||||
<p className="text-muted-foreground mt-2">
|
<Logo size="xl" />
|
||||||
Sign in to your beenvoice account
|
<div className="space-y-3">
|
||||||
</p>
|
<h1 className="text-3xl font-bold lg:text-4xl">
|
||||||
</div>
|
Welcome back to your
|
||||||
</div>
|
<span className="text-primary"> invoicing workspace</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground text-lg">
|
||||||
|
Continue managing your clients and creating professional
|
||||||
|
invoices that get you paid faster.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Sign In Form */}
|
<div className="grid gap-4">
|
||||||
<Card className="card-primary">
|
<div className="flex items-start space-x-4">
|
||||||
<CardHeader className="space-y-1">
|
<div className="bg-primary/10 rounded-lg p-2">
|
||||||
<CardTitle className="text-center text-xl">Sign In</CardTitle>
|
<Users className="text-primary h-5 w-5" />
|
||||||
</CardHeader>
|
</div>
|
||||||
<CardContent>
|
<div className="space-y-1">
|
||||||
<form onSubmit={handleSignIn} className="space-y-4">
|
<h3 className="font-semibold">Client Management</h3>
|
||||||
<div className="space-y-2">
|
<p className="text-muted-foreground text-sm">
|
||||||
<Label htmlFor="email">Email</Label>
|
Organize and track all your clients in one place
|
||||||
<div className="relative">
|
</p>
|
||||||
<Mail className="form-icon-left" />
|
</div>
|
||||||
<Input
|
</div>
|
||||||
id="email"
|
|
||||||
type="email"
|
<div className="flex items-start space-x-4">
|
||||||
value={email}
|
<div className="bg-primary/10 rounded-lg p-2">
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
<FileText className="text-primary h-5 w-5" />
|
||||||
required
|
</div>
|
||||||
autoFocus
|
<div className="space-y-1">
|
||||||
className="form-input-with-icon"
|
<h3 className="font-semibold">Professional Invoices</h3>
|
||||||
placeholder="Enter your email"
|
<p className="text-muted-foreground text-sm">
|
||||||
/>
|
Beautiful templates that get you paid faster
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<div className="bg-primary/10 rounded-lg p-2">
|
||||||
|
<TrendingUp className="text-primary h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h3 className="font-semibold">Payment Tracking</h3>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Monitor your income with real-time insights
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Lock className="form-icon-left" />
|
|
||||||
<Input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
required
|
|
||||||
className="form-input-with-icon"
|
|
||||||
placeholder="Enter your password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button type="submit" className="w-full" disabled={loading}>
|
|
||||||
{loading ? (
|
|
||||||
"Signing in..."
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
Sign In
|
|
||||||
<ArrowRight className="ml-2 h-4 w-4" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
<div className="mt-6 text-center text-sm">
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
Don't have an account?{" "}
|
|
||||||
</span>
|
|
||||||
<Link href="/auth/register" className="nav-link-brand">
|
|
||||||
Create one now
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Features */}
|
|
||||||
<div className="space-y-4 text-center">
|
|
||||||
<p className="welcome-description">
|
|
||||||
Simple invoicing for freelancers and small businesses
|
|
||||||
</p>
|
|
||||||
<div className="welcome-feature-list">
|
|
||||||
<span>✓ Easy client management</span>
|
|
||||||
<span>✓ Professional invoices</span>
|
|
||||||
<span>✓ Payment tracking</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
{/* Sign In Form */}
|
||||||
|
<div className="flex flex-col justify-center p-6 md:p-12">
|
||||||
|
<div className="mx-auto w-full max-w-sm space-y-6">
|
||||||
|
{/* Mobile Logo */}
|
||||||
|
<div className="flex justify-center md:hidden">
|
||||||
|
<Logo size="lg" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2 text-center md:text-left">
|
||||||
|
<h1 className="text-2xl font-bold">Sign In</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Enter your credentials to access your account
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSignIn} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email Address</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 z-10 h-4 w-4 -translate-y-1/2" />
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
className="h-11 pl-10"
|
||||||
|
placeholder="m@example.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<a
|
||||||
|
href="/auth/forgot-password"
|
||||||
|
className="text-primary text-sm hover:underline"
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="text-muted-foreground pointer-events-none absolute top-1/2 left-3 z-10 h-4 w-4 -translate-y-1/2" />
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
className="h-11 pl-10"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="h-11 w-full"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="border-primary-foreground/30 border-t-primary-foreground h-4 w-4 animate-spin rounded-full border-2"></div>
|
||||||
|
<span>Signing in...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span>Sign In</span>
|
||||||
|
<ArrowRight className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="text-center text-sm">
|
||||||
|
Don't have an account?{" "}
|
||||||
|
<a
|
||||||
|
href="/auth/register"
|
||||||
|
className="text-primary font-medium hover:underline"
|
||||||
|
>
|
||||||
|
Sign up
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-muted-foreground text-center text-xs leading-relaxed">
|
||||||
|
By signing in, you agree to our{" "}
|
||||||
|
<LegalModal
|
||||||
|
type="terms"
|
||||||
|
trigger={
|
||||||
|
<span className="text-primary inline cursor-pointer hover:underline">
|
||||||
|
Terms of Service
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>{" "}
|
||||||
|
and{" "}
|
||||||
|
<LegalModal
|
||||||
|
type="privacy"
|
||||||
|
trigger={
|
||||||
|
<span className="text-primary inline cursor-pointer hover:underline">
|
||||||
|
Privacy Policy
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SignInPage() {
|
export default function SignInPage() {
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
fallback={
|
|
||||||
<div className="floating-orbs flex min-h-screen items-center justify-center p-4">
|
|
||||||
<div className="w-full max-w-md space-y-8">
|
|
||||||
<div className="space-y-4 text-center">
|
|
||||||
<Logo size="lg" className="mx-auto" />
|
|
||||||
<div>
|
|
||||||
<h1 className="text-foreground text-2xl font-bold">
|
|
||||||
Welcome back
|
|
||||||
</h1>
|
|
||||||
<p className="text-muted-foreground mt-2">Loading...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<SignInForm />
|
<SignInForm />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export function StatusManager({
|
|||||||
|
|
||||||
{/* Overdue Warning */}
|
{/* Overdue Warning */}
|
||||||
{isOverdue && (
|
{isOverdue && (
|
||||||
<div className="flex items-center gap-2 rounded-lg bg-red-50 p-3 text-red-800 dark:bg-red-900/20 dark:text-red-300">
|
<div className="bg-destructive/10 text-destructive flex items-center gap-2 p-3">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{daysPastDue} day{daysPastDue !== 1 ? "s" : ""} overdue
|
{daysPastDue} day{daysPastDue !== 1 ? "s" : ""} overdue
|
||||||
@@ -325,7 +325,7 @@ export function StatusManager({
|
|||||||
|
|
||||||
{/* No Email Warning */}
|
{/* No Email Warning */}
|
||||||
{!clientEmail && effectiveStatus !== "paid" && (
|
{!clientEmail && effectiveStatus !== "paid" && (
|
||||||
<div className="rounded-lg bg-amber-50 p-3 text-amber-800 dark:bg-amber-900/20 dark:text-amber-300">
|
<div className="bg-muted text-muted-foreground p-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default async function BusinessDetailPage({
|
|||||||
<span>Back to Businesses</span>
|
<span>Back to Businesses</span>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild className="btn-brand-primary shadow-md">
|
<Button asChild variant="default" className="shadow-md">
|
||||||
<Link href={`/dashboard/businesses/${business.id}/edit`}>
|
<Link href={`/dashboard/businesses/${business.id}/edit`}>
|
||||||
<Edit className="mr-2 h-4 w-4" />
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
<span>Edit Business</span>
|
<span>Edit Business</span>
|
||||||
@@ -66,11 +66,11 @@ export default async function BusinessDetailPage({
|
|||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
{/* Business Information Card */}
|
{/* Business Information Card */}
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-blue-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-icon-blue h-5 w-5" />
|
<Building className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Business Information</span>
|
<span>Business Information</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -84,8 +84,8 @@ export default async function BusinessDetailPage({
|
|||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{business.email && (
|
{business.email && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Mail className="text-icon-green h-4 w-4" />
|
<Mail className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -100,8 +100,8 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{business.phone && (
|
{business.phone && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Phone className="text-icon-green h-4 w-4" />
|
<Phone className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -116,8 +116,8 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{business.website && (
|
{business.website && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Globe className="text-icon-green h-4 w-4" />
|
<Globe className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -137,8 +137,8 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{business.taxId && (
|
{business.taxId && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Hash className="text-icon-green h-4 w-4" />
|
<Hash className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -162,8 +162,8 @@ export default async function BusinessDetailPage({
|
|||||||
Business Address
|
Business Address
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<MapPin className="text-icon-green h-4 w-4" />
|
<MapPin className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 text-sm">
|
<div className="space-y-1 text-sm">
|
||||||
{business.addressLine1 && (
|
{business.addressLine1 && (
|
||||||
@@ -205,8 +205,8 @@ export default async function BusinessDetailPage({
|
|||||||
<h3 className="mb-4 text-lg font-semibold">Business Details</h3>
|
<h3 className="mb-4 text-lg font-semibold">Business Details</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Calendar className="text-icon-green h-4 w-4" />
|
<Calendar className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -221,8 +221,8 @@ export default async function BusinessDetailPage({
|
|||||||
{/* Default Business Badge */}
|
{/* Default Business Badge */}
|
||||||
{business.isDefault && (
|
{business.isDefault && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-icon-green h-4 w-4" />
|
<Building className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -230,7 +230,7 @@ export default async function BusinessDetailPage({
|
|||||||
</p>
|
</p>
|
||||||
<Badge
|
<Badge
|
||||||
variant="default"
|
variant="default"
|
||||||
className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
|
className="bg-primary/10 text-primary"
|
||||||
>
|
>
|
||||||
Default Business
|
Default Business
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -245,11 +245,11 @@ export default async function BusinessDetailPage({
|
|||||||
|
|
||||||
{/* Settings & Actions Card */}
|
{/* Settings & Actions Card */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-blue-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-icon-blue h-5 w-5" />
|
<Building className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Quick Actions</span>
|
<span>Quick Actions</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -281,7 +281,7 @@ export default async function BusinessDetailPage({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Information Card */}
|
{/* Information Card */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg">About This Business</CardTitle>
|
<CardTitle className="text-lg">About This Business</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -292,7 +292,7 @@ export default async function BusinessDetailPage({
|
|||||||
represents your company information to clients.
|
represents your company information to clients.
|
||||||
</p>
|
</p>
|
||||||
{business.isDefault && (
|
{business.isDefault && (
|
||||||
<p className="text-green-600 dark:text-green-400">
|
<p className="text-primary">
|
||||||
This is your default business and will be automatically
|
This is your default business and will be automatically
|
||||||
selected when creating new invoices.
|
selected when creating new invoices.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ export function BusinessesDataTable({ businesses }: BusinessesDataTableProps) {
|
|||||||
const business = row.original;
|
const business = row.original;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-blue-subtle hidden rounded-lg p-2 sm:flex">
|
<div className="bg-primary/10 hidden p-2 sm:flex">
|
||||||
<Building className="text-icon-blue h-4 w-4" />
|
<Building className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="truncate font-medium">{business.name}</p>
|
<p className="truncate font-medium">{business.name}</p>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default async function BusinessesPage() {
|
|||||||
description="Manage your businesses and their information"
|
description="Manage your businesses and their information"
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
>
|
>
|
||||||
<Button asChild className="btn-brand-primary shadow-md">
|
<Button asChild variant="default" className="shadow-md">
|
||||||
<Link href="/dashboard/businesses/new">
|
<Link href="/dashboard/businesses/new">
|
||||||
<Plus className="mr-2 h-5 w-5" />
|
<Plus className="mr-2 h-5 w-5" />
|
||||||
<span>Add Business</span>
|
<span>Add Business</span>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default async function ClientDetailPage({
|
|||||||
<span>Back to Clients</span>
|
<span>Back to Clients</span>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild className="btn-brand-primary shadow-md">
|
<Button asChild variant="default" className="shadow-md">
|
||||||
<Link href={`/dashboard/clients/${client.id}/edit`}>
|
<Link href={`/dashboard/clients/${client.id}/edit`}>
|
||||||
<Edit className="mr-2 h-4 w-4" />
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
<span>Edit Client</span>
|
<span>Edit Client</span>
|
||||||
@@ -80,11 +80,11 @@ export default async function ClientDetailPage({
|
|||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
{/* Client Information Card */}
|
{/* Client Information Card */}
|
||||||
<div className="lg:col-span-2">
|
<div className="lg:col-span-2">
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-blue-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Building className="text-icon-blue h-5 w-5" />
|
<Building className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Contact Information</span>
|
<span>Contact Information</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -94,8 +94,8 @@ export default async function ClientDetailPage({
|
|||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
{client.email && (
|
{client.email && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Mail className="text-icon-green h-4 w-4" />
|
<Mail className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -108,8 +108,8 @@ export default async function ClientDetailPage({
|
|||||||
|
|
||||||
{client.phone && (
|
{client.phone && (
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Phone className="text-icon-green h-4 w-4" />
|
<Phone className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -126,8 +126,8 @@ export default async function ClientDetailPage({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="mb-4 text-lg font-semibold">Client Address</h3>
|
<h3 className="mb-4 text-lg font-semibold">Client Address</h3>
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<MapPin className="text-icon-green h-4 w-4" />
|
<MapPin className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 text-sm">
|
<div className="space-y-1 text-sm">
|
||||||
{client.addressLine1 && (
|
{client.addressLine1 && (
|
||||||
@@ -155,8 +155,8 @@ export default async function ClientDetailPage({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="mb-4 text-lg font-semibold">Client Details</h3>
|
<h3 className="mb-4 text-lg font-semibold">Client Details</h3>
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="bg-green-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<Calendar className="text-icon-green h-4 w-4" />
|
<Calendar className="text-primary h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-muted-foreground text-sm font-medium">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
@@ -174,11 +174,11 @@ export default async function ClientDetailPage({
|
|||||||
|
|
||||||
{/* Stats Card */}
|
{/* Stats Card */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-blue-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<DollarSign className="text-icon-blue h-5 w-5" />
|
<DollarSign className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Invoice Summary</span>
|
<span>Invoice Summary</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -213,8 +213,8 @@ export default async function ClientDetailPage({
|
|||||||
<Card className="">
|
<Card className="">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<div className="bg-blue-subtle rounded-lg p-2">
|
<div className="bg-primary/10 p-2">
|
||||||
<DollarSign className="text-icon-blue h-5 w-5" />
|
<DollarSign className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<span>Recent Invoices</span>
|
<span>Recent Invoices</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -224,7 +224,7 @@ export default async function ClientDetailPage({
|
|||||||
{client.invoices.slice(0, 3).map((invoice) => (
|
{client.invoices.slice(0, 3).map((invoice) => (
|
||||||
<div
|
<div
|
||||||
key={invoice.id}
|
key={invoice.id}
|
||||||
className="card-secondary flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-gray-200/70 dark:hover:bg-gray-700/60"
|
className="card-secondary hover:bg-muted/50 flex items-center justify-between border p-3 transition-colors"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-foreground font-medium">
|
<p className="text-foreground font-medium">
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function ClientsDataTable({
|
|||||||
const client = row.original;
|
const client = row.original;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-status-info-muted hidden rounded-lg p-2 sm:flex">
|
<div className="bg-status-info-muted hidden p-2 sm:flex">
|
||||||
<UserPlus className="text-status-info h-4 w-4" />
|
<UserPlus className="text-status-info h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default async function ClientsPage() {
|
|||||||
description="Manage your clients and their information."
|
description="Manage your clients and their information."
|
||||||
variant="gradient"
|
variant="gradient"
|
||||||
>
|
>
|
||||||
<Button asChild className="btn-brand-primary shadow-md">
|
<Button asChild variant="default" className="shadow-md">
|
||||||
<Link href="/dashboard/clients/new">
|
<Link href="/dashboard/clients/new">
|
||||||
<Plus className="mr-2 h-5 w-5" />
|
<Plus className="mr-2 h-5 w-5" />
|
||||||
<span>Add Client</span>
|
<span>Add Client</span>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
{/* Left Column */}
|
{/* Left Column */}
|
||||||
<div className="space-y-6 lg:col-span-2">
|
<div className="space-y-6 lg:col-span-2">
|
||||||
{/* Invoice Header Skeleton */}
|
{/* Invoice Header Skeleton */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardContent className="p-4 sm:p-6">
|
<CardContent className="p-4 sm:p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-start justify-between gap-6">
|
<div className="flex items-start justify-between gap-6">
|
||||||
@@ -48,7 +48,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
{/* Client & Business Info */}
|
{/* Client & Business Info */}
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
{Array.from({ length: 2 }).map((_, i) => (
|
{Array.from({ length: 2 }).map((_, i) => (
|
||||||
<Card key={i} className="card-primary">
|
<Card key={i} className="bg-card border-border border">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center gap-2">
|
<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-4 w-4 sm:h-5 sm:w-5" />
|
||||||
@@ -60,7 +60,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{Array.from({ length: 3 }).map((_, j) => (
|
{Array.from({ length: 3 }).map((_, j) => (
|
||||||
<div key={j} className="flex items-center gap-3">
|
<div key={j} className="flex items-center gap-3">
|
||||||
<Skeleton className="bg-muted/30 h-8 w-8 rounded-lg" />
|
<Skeleton className="bg-muted/30 h-8 w-8 " />
|
||||||
<Skeleton className="bg-muted/30 h-4 w-28" />
|
<Skeleton className="bg-muted/30 h-4 w-28" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -71,7 +71,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Invoice Items Skeleton */}
|
{/* Invoice Items Skeleton */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<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-4 w-4 sm:h-5 sm:w-5" />
|
||||||
@@ -80,7 +80,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<div key={i} className="space-y-3 rounded-lg border p-4">
|
<div key={i} className="space-y-3 border p-4">
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="min-w-0 flex-1">
|
<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" />
|
<Skeleton className="bg-muted/30 mb-2 h-4 w-full sm:h-5 sm:w-3/4" />
|
||||||
@@ -98,7 +98,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Totals */}
|
{/* Totals */}
|
||||||
<div className="bg-muted/30 rounded-lg p-4">
|
<div className="bg-muted/30 p-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Skeleton className="bg-muted/30 h-4 w-16" />
|
<Skeleton className="bg-muted/30 h-4 w-16" />
|
||||||
@@ -119,7 +119,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Notes */}
|
{/* Notes */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<Skeleton className="bg-muted/30 h-6 w-16" />
|
<Skeleton className="bg-muted/30 h-6 w-16" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -135,7 +135,7 @@ export function InvoiceDetailsSkeleton() {
|
|||||||
|
|
||||||
{/* Right Column - Actions */}
|
{/* Right Column - Actions */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="card-primary sticky top-6">
|
<Card className="bg-card border-border border sticky top-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Skeleton className="bg-muted/30 h-5 w-5" />
|
<Skeleton className="bg-muted/30 h-5 w-5" />
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const columns: ColumnDef<InvoiceItem>[] = [
|
|||||||
accessorKey: "amount",
|
accessorKey: "amount",
|
||||||
header: "Amount",
|
header: "Amount",
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-icon-emerald text-right font-medium">
|
<div className="text-primary text-right font-medium">
|
||||||
{formatCurrency(row.getValue("amount"))}
|
{formatCurrency(row.getValue("amount"))}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,12 +1,511 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useParams } from "next/navigation";
|
import { DollarSign, Edit, Loader2, Trash2 } from "lucide-react";
|
||||||
import InvoiceForm from "~/components/forms/invoice-form";
|
import Link from "next/link";
|
||||||
|
import { notFound, useParams, useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { StatusBadge, type StatusType } from "~/components/data/status-badge";
|
||||||
|
import { PageHeader } from "~/components/layout/page-header";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "~/components/ui/dialog";
|
||||||
|
import { Separator } from "~/components/ui/separator";
|
||||||
|
import {
|
||||||
|
getEffectiveInvoiceStatus,
|
||||||
|
isInvoiceOverdue,
|
||||||
|
} from "~/lib/invoice-status";
|
||||||
|
import { api } from "~/trpc/react";
|
||||||
|
import type { StoredInvoiceStatus } from "~/types/invoice";
|
||||||
|
import { InvoiceDetailsSkeleton } from "./_components/invoice-details-skeleton";
|
||||||
|
import { PDFDownloadButton } from "./_components/pdf-download-button";
|
||||||
|
import { EnhancedSendInvoiceButton } from "~/components/forms/enhanced-send-invoice-button";
|
||||||
|
|
||||||
export default function InvoiceFormPage() {
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
Building,
|
||||||
|
Check,
|
||||||
|
FileText,
|
||||||
|
Mail,
|
||||||
|
MapPin,
|
||||||
|
Phone,
|
||||||
|
User,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
||||||
|
const router = useRouter();
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const { data: invoice, isLoading } = api.invoices.getById.useQuery({
|
||||||
|
id: invoiceId,
|
||||||
|
});
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const deleteInvoice = api.invoices.delete.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Invoice deleted successfully");
|
||||||
|
router.push("/dashboard/invoices");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message ?? "Failed to delete invoice");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateStatus = api.invoices.updateStatus.useMutation({
|
||||||
|
onSuccess: (data) => {
|
||||||
|
toast.success(data.message);
|
||||||
|
void utils.invoices.getById.invalidate({ id: invoiceId });
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message ?? "Failed to update invoice status");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
setDeleteDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMarkAsPaid = () => {
|
||||||
|
updateStatus.mutate({
|
||||||
|
id: invoiceId,
|
||||||
|
status: "paid" as StoredInvoiceStatus,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = () => {
|
||||||
|
deleteInvoice.mutate({ id: invoiceId });
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <InvoiceDetailsSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!invoice) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
}).format(new Date(date));
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCurrency = (amount: number) => {
|
||||||
|
return new Intl.NumberFormat("en-US", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "USD",
|
||||||
|
}).format(amount);
|
||||||
|
};
|
||||||
|
|
||||||
|
const subtotal = invoice.items.reduce((sum, item) => sum + item.amount, 0);
|
||||||
|
const taxAmount = (subtotal * invoice.taxRate) / 100;
|
||||||
|
const total = subtotal + taxAmount;
|
||||||
|
const effectiveStatus = getEffectiveInvoiceStatus(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
);
|
||||||
|
const isOverdue = isInvoiceOverdue(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getStatusType = (): StatusType => {
|
||||||
|
return effectiveStatus as StatusType;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 pb-24">
|
||||||
|
<PageHeader
|
||||||
|
title="Invoice Details"
|
||||||
|
description="View and manage invoice information"
|
||||||
|
variant="gradient"
|
||||||
|
>
|
||||||
|
<PDFDownloadButton invoiceId={invoice.id} variant="outline" />
|
||||||
|
<Button asChild variant="default">
|
||||||
|
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
||||||
|
<Edit className="h-5 w-5" />
|
||||||
|
<span>Edit</span>
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</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 */}
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4 sm:p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-start justify-between 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">
|
||||||
|
<h2 className="text-foreground truncate text-2xl font-bold">
|
||||||
|
{invoice.invoiceNumber}
|
||||||
|
</h2>
|
||||||
|
<StatusBadge status={getStatusType()} />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground space-y-1 text-sm sm:space-y-0">
|
||||||
|
<div className="sm:inline">
|
||||||
|
Issued {formatDate(invoice.issueDate)}
|
||||||
|
</div>
|
||||||
|
<div className="sm:inline sm:before:content-['_•_']">
|
||||||
|
Due {formatDate(invoice.dueDate)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 text-right">
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Total Amount
|
||||||
|
</p>
|
||||||
|
<p className="text-primary text-3xl font-bold">
|
||||||
|
{formatCurrency(total)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Overdue Alert */}
|
||||||
|
{isOverdue && (
|
||||||
|
<Card className="border-destructive/20 bg-destructive/5">
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="text-destructive flex items-center gap-3">
|
||||||
|
<AlertTriangle className="h-5 w-5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">Invoice Overdue</p>
|
||||||
|
<p className="text-sm">
|
||||||
|
{Math.ceil(
|
||||||
|
(new Date().getTime() -
|
||||||
|
new Date(invoice.dueDate).getTime()) /
|
||||||
|
(1000 * 60 * 60 * 24),
|
||||||
|
)}{" "}
|
||||||
|
days past due date
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Client & Business Info */}
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
{/* Client Information */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<User className="h-5 w-5" />
|
||||||
|
Bill To
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-foreground text-xl font-semibold">
|
||||||
|
{invoice.client.name}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{invoice.client.email && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-primary/10 p-2">
|
||||||
|
<Mail className="text-primary h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm break-all">
|
||||||
|
{invoice.client.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{invoice.client.phone && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-primary/10 p-2">
|
||||||
|
<Phone className="text-primary h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">{invoice.client.phone}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(invoice.client.addressLine1 ?? invoice.client.city) && (
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<div className="bg-primary/10 p-2">
|
||||||
|
<MapPin className="text-primary h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1 text-sm">
|
||||||
|
{invoice.client.addressLine1 && (
|
||||||
|
<div>{invoice.client.addressLine1}</div>
|
||||||
|
)}
|
||||||
|
{invoice.client.addressLine2 && (
|
||||||
|
<div>{invoice.client.addressLine2}</div>
|
||||||
|
)}
|
||||||
|
{(invoice.client.city ??
|
||||||
|
invoice.client.state ??
|
||||||
|
invoice.client.postalCode) && (
|
||||||
|
<div>
|
||||||
|
{[
|
||||||
|
invoice.client.city,
|
||||||
|
invoice.client.state,
|
||||||
|
invoice.client.postalCode,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(", ")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{invoice.client.country && (
|
||||||
|
<div>{invoice.client.country}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Business Information */}
|
||||||
|
{invoice.business && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Building className="h-5 w-5" />
|
||||||
|
From
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-foreground text-xl font-semibold">
|
||||||
|
{invoice.business.name}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{invoice.business.email && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-primary/10 p-2">
|
||||||
|
<Mail className="text-primary h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm break-all">
|
||||||
|
{invoice.business.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{invoice.business.phone && (
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-primary/10 p-2">
|
||||||
|
<Phone className="text-primary h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm">
|
||||||
|
{invoice.business.phone}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Invoice Items */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<FileText className="h-5 w-5" />
|
||||||
|
Invoice Items
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{invoice.items.map((item) => (
|
||||||
|
<Card key={item.id} className="card-secondary">
|
||||||
|
<CardContent className="py-2">
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-foreground mb-2 text-base font-medium">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
<div className="text-muted-foreground text-sm">
|
||||||
|
<span className="inline whitespace-nowrap">
|
||||||
|
{formatDate(item.date).replace(/ /g, "\u00A0")}
|
||||||
|
</span>
|
||||||
|
<span className="inline whitespace-nowrap before:mx-2 before:content-['_|_']">
|
||||||
|
{item.hours.toString().replace(/ /g, "\u00A0")}
|
||||||
|
hours
|
||||||
|
</span>
|
||||||
|
<span className="inline whitespace-nowrap before:mx-2 before:content-['_|_']">
|
||||||
|
@ ${item.rate}/hr
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 text-right">
|
||||||
|
<p className="text-primary text-lg font-semibold">
|
||||||
|
{formatCurrency(item.amount)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Totals */}
|
||||||
|
<div className="bg-muted/30 p-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-muted-foreground">Subtotal:</span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{formatCurrency(subtotal)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{invoice.taxRate > 0 && (
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
Tax ({invoice.taxRate}%):
|
||||||
|
</span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{formatCurrency(taxAmount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Separator />
|
||||||
|
<div className="flex justify-between text-lg font-bold">
|
||||||
|
<span>Total:</span>
|
||||||
|
<span className="text-primary">
|
||||||
|
{formatCurrency(total)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Notes */}
|
||||||
|
{invoice.notes && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notes</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-foreground whitespace-pre-wrap">
|
||||||
|
{invoice.notes}
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Actions */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<Card className="sticky top-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Check className="h-5 w-5" />
|
||||||
|
Actions
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
<Button asChild variant="outline" className="w-full">
|
||||||
|
<Link href={`/dashboard/invoices/${invoice.id}/edit`}>
|
||||||
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
|
Edit Invoice
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{invoice.items && invoice.client && (
|
||||||
|
<PDFDownloadButton invoiceId={invoice.id} className="w-full" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Send Invoice Button - Show for draft, sent, and overdue */}
|
||||||
|
{effectiveStatus === "draft" && (
|
||||||
|
<EnhancedSendInvoiceButton
|
||||||
|
invoiceId={invoice.id}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(effectiveStatus === "sent" ||
|
||||||
|
effectiveStatus === "overdue") && (
|
||||||
|
<EnhancedSendInvoiceButton
|
||||||
|
invoiceId={invoice.id}
|
||||||
|
className="w-full"
|
||||||
|
showResend={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Manual Status Updates */}
|
||||||
|
{(effectiveStatus === "sent" ||
|
||||||
|
effectiveStatus === "overdue") && (
|
||||||
|
<Button
|
||||||
|
onClick={handleMarkAsPaid}
|
||||||
|
disabled={updateStatus.isPending}
|
||||||
|
className="bg-primary text-primary-foreground hover:bg-primary/90 w-full"
|
||||||
|
>
|
||||||
|
{updateStatus.isPending ? (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<DollarSign className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
Mark as Paid
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={deleteInvoice.isPending}
|
||||||
|
className="text-destructive hover:bg-destructive/10 w-full"
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Delete Invoice
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Invoice</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete invoice{" "}
|
||||||
|
<strong>{invoice.invoiceNumber}</strong>? This action cannot be
|
||||||
|
undone and will permanently remove the invoice and all its data.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setDeleteDialogOpen(false)}
|
||||||
|
disabled={deleteInvoice.isPending}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={confirmDelete}
|
||||||
|
disabled={deleteInvoice.isPending}
|
||||||
|
>
|
||||||
|
{deleteInvoice.isPending ? "Deleting..." : "Delete Invoice"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InvoiceViewPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = params.id as string;
|
const id = params.id as string;
|
||||||
|
|
||||||
// Pass the actual id, let the form component handle the logic
|
return <InvoiceViewContent invoiceId={id} />;
|
||||||
return <InvoiceForm invoiceId={id} />;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ function SendEmailPageSkeleton() {
|
|||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
<div className="space-y-6 lg:col-span-2">
|
<div className="space-y-6 lg:col-span-2">
|
||||||
<div className="bg-muted h-96 animate-pulse rounded-lg" />
|
<div className="bg-muted h-96 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-muted h-64 animate-pulse rounded-lg" />
|
<div className="bg-muted h-64 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +91,7 @@ export default function SendEmailPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Navigate back to invoice view
|
// Navigate back to invoice view
|
||||||
router.push(`/dashboard/invoices/${invoiceId}/view`);
|
router.push(`/dashboard/invoices/${invoiceId}`);
|
||||||
|
|
||||||
// Refresh invoice data
|
// Refresh invoice data
|
||||||
void utils.invoices.getById.invalidate({ id: invoiceId });
|
void utils.invoices.getById.invalidate({ id: invoiceId });
|
||||||
@@ -275,7 +275,7 @@ export default function SendEmailPage() {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => router.push(`/dashboard/invoices/${invoiceId}/view`)}
|
onClick={() => router.push(`/dashboard/invoices/${invoiceId}`)}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Back to Invoice
|
Back to Invoice
|
||||||
@@ -334,9 +334,9 @@ export default function SendEmailPage() {
|
|||||||
onBccEmailChange={setBccEmail}
|
onBccEmailChange={setBccEmail}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-muted flex h-[400px] items-center justify-center rounded-md border">
|
<div className="bg-muted flex h-[400px] items-center justify-center border">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="border-primary mx-auto mb-2 h-4 w-4 animate-spin rounded-full border-2 border-t-transparent"></div>
|
<div className="border-primary mx-auto mb-2 h-4 w-4 animate-spin border-2 border-t-transparent"></div>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
Initializing email content...
|
Initializing email content...
|
||||||
</p>
|
</p>
|
||||||
@@ -382,7 +382,7 @@ export default function SendEmailPage() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-lg">
|
<CardTitle className="flex items-center gap-2 text-lg">
|
||||||
<FileText className="h-5 w-5 text-green-600" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
Invoice #{invoice.invoiceNumber}
|
Invoice #{invoice.invoiceNumber}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -506,14 +506,12 @@ export default function SendEmailPage() {
|
|||||||
<FloatingActionBar
|
<FloatingActionBar
|
||||||
leftContent={
|
leftContent={
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="rounded-lg bg-green-100 p-2 dark:bg-green-900/30">
|
<div className="bg-primary/10 p-2">
|
||||||
<Send className="h-5 w-5 text-green-600 dark:text-green-400" />
|
<Send className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
<p className="text-foreground font-medium">Send Invoice</p>
|
||||||
Send Invoice
|
<p className="text-muted-foreground text-sm">
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
|
||||||
Email invoice to {invoice.client?.name ?? "client"}
|
Email invoice to {invoice.client?.name ?? "client"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -523,7 +521,7 @@ export default function SendEmailPage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => router.push(`/dashboard/invoices/${invoiceId}/view`)}
|
onClick={() => router.push(`/dashboard/invoices/${invoiceId}`)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
@@ -531,7 +529,7 @@ export default function SendEmailPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSendEmail}
|
onClick={handleSendEmail}
|
||||||
disabled={!canSend || isSending}
|
disabled={!canSend || isSending}
|
||||||
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-md transition-colors duration-200 hover:from-emerald-700 hover:to-teal-700"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{isSending ? (
|
{isSending ? (
|
||||||
@@ -570,7 +568,7 @@ export default function SendEmailPage() {
|
|||||||
)}
|
)}
|
||||||
.
|
.
|
||||||
{retryCount > 0 && (
|
{retryCount > 0 && (
|
||||||
<div className="mt-2 text-sm text-yellow-600">
|
<div className="text-muted-foreground mt-2 text-sm">
|
||||||
Retry attempt {retryCount} of 2
|
Retry attempt {retryCount} of 2
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -583,10 +581,7 @@ export default function SendEmailPage() {
|
|||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={confirmSendEmail} variant="default">
|
||||||
onClick={confirmSendEmail}
|
|
||||||
className="bg-gradient-to-r from-emerald-600 to-teal-600 hover:from-emerald-700 hover:to-teal-700"
|
|
||||||
>
|
|
||||||
<Send className="mr-2 h-4 w-4" />
|
<Send className="mr-2 h-4 w-4" />
|
||||||
Send Email
|
Send Email
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,511 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { DollarSign, Edit, Loader2, Trash2 } from "lucide-react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { notFound, useParams, useRouter } from "next/navigation";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { StatusBadge, type StatusType } from "~/components/data/status-badge";
|
|
||||||
import { PageHeader } from "~/components/layout/page-header";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "~/components/ui/dialog";
|
|
||||||
import { Separator } from "~/components/ui/separator";
|
|
||||||
import {
|
|
||||||
getEffectiveInvoiceStatus,
|
|
||||||
isInvoiceOverdue,
|
|
||||||
} from "~/lib/invoice-status";
|
|
||||||
import { api } from "~/trpc/react";
|
|
||||||
import type { StoredInvoiceStatus } from "~/types/invoice";
|
|
||||||
import { InvoiceDetailsSkeleton } from "../_components/invoice-details-skeleton";
|
|
||||||
import { PDFDownloadButton } from "../_components/pdf-download-button";
|
|
||||||
import { EnhancedSendInvoiceButton } from "~/components/forms/enhanced-send-invoice-button";
|
|
||||||
|
|
||||||
import {
|
|
||||||
AlertTriangle,
|
|
||||||
Building,
|
|
||||||
Check,
|
|
||||||
FileText,
|
|
||||||
Mail,
|
|
||||||
MapPin,
|
|
||||||
Phone,
|
|
||||||
User,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
|
|
||||||
const router = useRouter();
|
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
||||||
|
|
||||||
const { data: invoice, isLoading } = api.invoices.getById.useQuery({
|
|
||||||
id: invoiceId,
|
|
||||||
});
|
|
||||||
const utils = api.useUtils();
|
|
||||||
|
|
||||||
const deleteInvoice = api.invoices.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
toast.success("Invoice deleted successfully");
|
|
||||||
router.push("/dashboard/invoices");
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message ?? "Failed to delete invoice");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateStatus = api.invoices.updateStatus.useMutation({
|
|
||||||
onSuccess: (data) => {
|
|
||||||
toast.success(data.message);
|
|
||||||
void utils.invoices.getById.invalidate({ id: invoiceId });
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
toast.error(error.message ?? "Failed to update invoice status");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDelete = () => {
|
|
||||||
setDeleteDialogOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMarkAsPaid = () => {
|
|
||||||
updateStatus.mutate({
|
|
||||||
id: invoiceId,
|
|
||||||
status: "paid" as StoredInvoiceStatus,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmDelete = () => {
|
|
||||||
deleteInvoice.mutate({ id: invoiceId });
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <InvoiceDetailsSkeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!invoice) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDate = (date: Date) => {
|
|
||||||
return new Intl.DateTimeFormat("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
}).format(new Date(date));
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatCurrency = (amount: number) => {
|
|
||||||
return new Intl.NumberFormat("en-US", {
|
|
||||||
style: "currency",
|
|
||||||
currency: "USD",
|
|
||||||
}).format(amount);
|
|
||||||
};
|
|
||||||
|
|
||||||
const subtotal = invoice.items.reduce((sum, item) => sum + item.amount, 0);
|
|
||||||
const taxAmount = (subtotal * invoice.taxRate) / 100;
|
|
||||||
const total = subtotal + taxAmount;
|
|
||||||
const effectiveStatus = getEffectiveInvoiceStatus(
|
|
||||||
invoice.status as StoredInvoiceStatus,
|
|
||||||
invoice.dueDate,
|
|
||||||
);
|
|
||||||
const isOverdue = isInvoiceOverdue(
|
|
||||||
invoice.status as StoredInvoiceStatus,
|
|
||||||
invoice.dueDate,
|
|
||||||
);
|
|
||||||
|
|
||||||
const getStatusType = (): StatusType => {
|
|
||||||
return effectiveStatus as StatusType;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6 pb-24">
|
|
||||||
<PageHeader
|
|
||||||
title="Invoice Details"
|
|
||||||
description="View and manage invoice information"
|
|
||||||
variant="gradient"
|
|
||||||
>
|
|
||||||
<PDFDownloadButton invoiceId={invoice.id} variant="outline" />
|
|
||||||
<Button asChild variant="default">
|
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
|
||||||
<Edit className="h-5 w-5" />
|
|
||||||
<span>Edit</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</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 */}
|
|
||||||
<Card className="card-primary">
|
|
||||||
<CardContent className="p-4 sm:p-6">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-start justify-between 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">
|
|
||||||
<h2 className="text-foreground truncate text-2xl font-bold">
|
|
||||||
{invoice.invoiceNumber}
|
|
||||||
</h2>
|
|
||||||
<StatusBadge status={getStatusType()} />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground space-y-1 text-sm sm:space-y-0">
|
|
||||||
<div className="sm:inline">
|
|
||||||
Issued {formatDate(invoice.issueDate)}
|
|
||||||
</div>
|
|
||||||
<div className="sm:inline sm:before:content-['_•_']">
|
|
||||||
Due {formatDate(invoice.dueDate)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-shrink-0 text-right">
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
Total Amount
|
|
||||||
</p>
|
|
||||||
<p className="text-primary text-3xl font-bold">
|
|
||||||
{formatCurrency(total)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Overdue Alert */}
|
|
||||||
{isOverdue && (
|
|
||||||
<Card className="border-destructive/20 bg-destructive/5 card-secondary">
|
|
||||||
<CardContent className="p-4">
|
|
||||||
<div className="text-destructive flex items-center gap-3">
|
|
||||||
<AlertTriangle className="h-5 w-5 flex-shrink-0" />
|
|
||||||
<div>
|
|
||||||
<p className="font-medium">Invoice Overdue</p>
|
|
||||||
<p className="text-sm">
|
|
||||||
{Math.ceil(
|
|
||||||
(new Date().getTime() -
|
|
||||||
new Date(invoice.dueDate).getTime()) /
|
|
||||||
(1000 * 60 * 60 * 24),
|
|
||||||
)}{" "}
|
|
||||||
days past due date
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Client & Business Info */}
|
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
|
||||||
{/* Client Information */}
|
|
||||||
<Card className="card-primary">
|
|
||||||
<CardHeader className="pb-3">
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<User className="h-5 w-5" />
|
|
||||||
Bill To
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-foreground text-xl font-semibold">
|
|
||||||
{invoice.client.name}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{invoice.client.email && (
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="bg-primary/10 rounded-lg p-2">
|
|
||||||
<Mail className="text-primary h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<span className="text-sm break-all">
|
|
||||||
{invoice.client.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{invoice.client.phone && (
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="bg-primary/10 rounded-lg p-2">
|
|
||||||
<Phone className="text-primary h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<span className="text-sm">{invoice.client.phone}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(invoice.client.addressLine1 ?? invoice.client.city) && (
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="bg-primary/10 rounded-lg p-2">
|
|
||||||
<MapPin className="text-primary h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1 text-sm">
|
|
||||||
{invoice.client.addressLine1 && (
|
|
||||||
<div>{invoice.client.addressLine1}</div>
|
|
||||||
)}
|
|
||||||
{invoice.client.addressLine2 && (
|
|
||||||
<div>{invoice.client.addressLine2}</div>
|
|
||||||
)}
|
|
||||||
{(invoice.client.city ??
|
|
||||||
invoice.client.state ??
|
|
||||||
invoice.client.postalCode) && (
|
|
||||||
<div>
|
|
||||||
{[
|
|
||||||
invoice.client.city,
|
|
||||||
invoice.client.state,
|
|
||||||
invoice.client.postalCode,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(", ")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{invoice.client.country && (
|
|
||||||
<div>{invoice.client.country}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Business Information */}
|
|
||||||
{invoice.business && (
|
|
||||||
<Card className="card-primary">
|
|
||||||
<CardHeader className="pb-3">
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Building className="h-5 w-5" />
|
|
||||||
From
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-foreground text-xl font-semibold">
|
|
||||||
{invoice.business.name}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{invoice.business.email && (
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="bg-primary/10 rounded-lg p-2">
|
|
||||||
<Mail className="text-primary h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<span className="text-sm break-all">
|
|
||||||
{invoice.business.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{invoice.business.phone && (
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="bg-primary/10 rounded-lg p-2">
|
|
||||||
<Phone className="text-primary h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<span className="text-sm">
|
|
||||||
{invoice.business.phone}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Invoice Items */}
|
|
||||||
<Card className="card-primary">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<FileText className="h-5 w-5" />
|
|
||||||
Invoice Items
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-4">
|
|
||||||
{invoice.items.map((item) => (
|
|
||||||
<Card key={item.id} className="card-secondary">
|
|
||||||
<CardContent className="py-2">
|
|
||||||
<div className="flex items-start justify-between gap-4">
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<p className="text-foreground mb-2 text-base font-medium">
|
|
||||||
{item.description}
|
|
||||||
</p>
|
|
||||||
<div className="text-muted-foreground text-sm">
|
|
||||||
<span className="inline whitespace-nowrap">
|
|
||||||
{formatDate(item.date).replace(/ /g, "\u00A0")}
|
|
||||||
</span>
|
|
||||||
<span className="inline whitespace-nowrap before:mx-2 before:content-['_|_']">
|
|
||||||
{item.hours.toString().replace(/ /g, "\u00A0")}
|
|
||||||
hours
|
|
||||||
</span>
|
|
||||||
<span className="inline whitespace-nowrap before:mx-2 before:content-['_|_']">
|
|
||||||
@ ${item.rate}/hr
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-shrink-0 text-right">
|
|
||||||
<p className="text-primary text-lg font-semibold">
|
|
||||||
{formatCurrency(item.amount)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Totals */}
|
|
||||||
<div className="bg-muted/30 rounded-lg p-4">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">Subtotal:</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{formatCurrency(subtotal)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{invoice.taxRate > 0 && (
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-muted-foreground">
|
|
||||||
Tax ({invoice.taxRate}%):
|
|
||||||
</span>
|
|
||||||
<span className="font-medium">
|
|
||||||
{formatCurrency(taxAmount)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Separator />
|
|
||||||
<div className="flex justify-between text-lg font-bold">
|
|
||||||
<span>Total:</span>
|
|
||||||
<span className="text-primary">
|
|
||||||
{formatCurrency(total)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Notes */}
|
|
||||||
{invoice.notes && (
|
|
||||||
<Card className="card-primary">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Notes</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<p className="text-foreground whitespace-pre-wrap">
|
|
||||||
{invoice.notes}
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column - Actions */}
|
|
||||||
<div className="space-y-6">
|
|
||||||
<Card className="card-primary sticky top-6">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Check className="h-5 w-5" />
|
|
||||||
Actions
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3">
|
|
||||||
<Button asChild variant="outline" className="w-full">
|
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
|
||||||
<Edit className="mr-2 h-4 w-4" />
|
|
||||||
Edit Invoice
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{invoice.items && invoice.client && (
|
|
||||||
<PDFDownloadButton invoiceId={invoice.id} className="w-full" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Send Invoice Button - Show for draft, sent, and overdue */}
|
|
||||||
{effectiveStatus === "draft" && (
|
|
||||||
<EnhancedSendInvoiceButton
|
|
||||||
invoiceId={invoice.id}
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(effectiveStatus === "sent" ||
|
|
||||||
effectiveStatus === "overdue") && (
|
|
||||||
<EnhancedSendInvoiceButton
|
|
||||||
invoiceId={invoice.id}
|
|
||||||
className="w-full"
|
|
||||||
showResend={true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Manual Status Updates */}
|
|
||||||
{(effectiveStatus === "sent" ||
|
|
||||||
effectiveStatus === "overdue") && (
|
|
||||||
<Button
|
|
||||||
onClick={handleMarkAsPaid}
|
|
||||||
disabled={updateStatus.isPending}
|
|
||||||
className="w-full bg-green-600 text-white hover:bg-green-700"
|
|
||||||
>
|
|
||||||
{updateStatus.isPending ? (
|
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<DollarSign className="mr-2 h-4 w-4" />
|
|
||||||
)}
|
|
||||||
Mark as Paid
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={handleDelete}
|
|
||||||
disabled={deleteInvoice.isPending}
|
|
||||||
className="w-full text-red-700 hover:bg-red-50"
|
|
||||||
>
|
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
|
||||||
Delete Invoice
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Delete Confirmation Dialog */}
|
|
||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Delete Invoice</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are you sure you want to delete invoice{" "}
|
|
||||||
<strong>{invoice.invoiceNumber}</strong>? This action cannot be
|
|
||||||
undone and will permanently remove the invoice and all its data.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
|
||||||
disabled={deleteInvoice.isPending}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
onClick={confirmDelete}
|
|
||||||
disabled={deleteInvoice.isPending}
|
|
||||||
>
|
|
||||||
{deleteInvoice.isPending ? "Deleting..." : "Delete Invoice"}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function InvoiceViewPage() {
|
|
||||||
const params = useParams();
|
|
||||||
const id = params.id as string;
|
|
||||||
|
|
||||||
return <InvoiceViewContent invoiceId={id} />;
|
|
||||||
}
|
|
||||||
@@ -107,7 +107,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleRowClick = (invoice: Invoice) => {
|
const handleRowClick = (invoice: Invoice) => {
|
||||||
router.push(`/dashboard/invoices/${invoice.id}/view`);
|
router.push(`/dashboard/invoices/${invoice.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (invoice: Invoice) => {
|
const handleDelete = (invoice: Invoice) => {
|
||||||
@@ -206,7 +206,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
|||||||
const invoice = row.original;
|
const invoice = row.original;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-end gap-1">
|
<div className="flex items-center justify-end gap-1">
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}/view`}>
|
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -216,7 +216,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
|||||||
<Eye className="h-3.5 w-3.5" />
|
<Eye className="h-3.5 w-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
<Link href={`/dashboard/invoices/${invoice.id}/edit`}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -229,7 +229,7 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-8 w-8 p-0 text-red-600 hover:text-red-700"
|
className="text-destructive hover:text-destructive/80 h-8 w-8 p-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDelete(invoice);
|
handleDelete(invoice);
|
||||||
|
|||||||
@@ -21,16 +21,16 @@ function FormatInstructions() {
|
|||||||
return (
|
return (
|
||||||
<div className="grid gap-6 lg:grid-cols-2">
|
<div className="grid gap-6 lg:grid-cols-2">
|
||||||
{/* Required Format */}
|
{/* Required Format */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-info">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<FileText className="text-icon-blue h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
Required CSV Format
|
Required CSV Format
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="bg-muted-subtle rounded-lg p-4">
|
<div className="bg-muted/50 p-4">
|
||||||
<p className="text-secondary font-mono text-sm">
|
<p className="text-muted-foreground font-mono text-sm">
|
||||||
DATE,DESCRIPTION,HOURS,RATE,AMOUNT
|
DATE,DESCRIPTION,HOURS,RATE,AMOUNT
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,7 +49,7 @@ function FormatInstructions() {
|
|||||||
},
|
},
|
||||||
].map((col) => (
|
].map((col) => (
|
||||||
<div key={col.field} className="flex items-start gap-3">
|
<div key={col.field} className="flex items-start gap-3">
|
||||||
<Badge className="badge-outline text-xs">{col.field}</Badge>
|
<Badge className="border text-xs">{col.field}</Badge>
|
||||||
<span className="text-muted-foreground text-sm">
|
<span className="text-muted-foreground text-sm">
|
||||||
{col.desc}
|
{col.desc}
|
||||||
</span>
|
</span>
|
||||||
@@ -72,10 +72,10 @@ function FormatInstructions() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Sample Data & Download */}
|
{/* Sample Data & Download */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<Download className="text-icon-green h-5 w-5" />
|
<Download className="text-primary h-5 w-5" />
|
||||||
Sample Template
|
Sample Template
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -85,9 +85,9 @@ function FormatInstructions() {
|
|||||||
for importing time entries.
|
for importing time entries.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="bg-green-subtle rounded-lg p-4">
|
<div className="bg-primary/10 p-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Info className="text-icon-green mt-0.5 h-5 w-5" />
|
<Info className="text-primary mt-0.5 h-5 w-5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-success text-sm font-medium">Pro Tip</p>
|
<p className="text-success text-sm font-medium">Pro Tip</p>
|
||||||
<p className="text-success text-sm">
|
<p className="text-success text-sm">
|
||||||
@@ -100,7 +100,7 @@ function FormatInstructions() {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-sm font-semibold">Sample Row:</h4>
|
<h4 className="text-sm font-semibold">Sample Row:</h4>
|
||||||
<div className="bg-muted-subtle rounded-lg p-3">
|
<div className="bg-muted/50 p-3">
|
||||||
<p className="text-muted font-mono text-xs break-all">
|
<p className="text-muted font-mono text-xs break-all">
|
||||||
1/15/24,"Web development work",8,75.00,600.00
|
1/15/24,"Web development work",8,75.00,600.00
|
||||||
</p>
|
</p>
|
||||||
@@ -109,7 +109,7 @@ function FormatInstructions() {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="text-sm font-semibold">Sample Filename:</h4>
|
<h4 className="text-sm font-semibold">Sample Filename:</h4>
|
||||||
<div className="bg-muted-subtle rounded-lg p-3">
|
<div className="bg-muted/50 p-3">
|
||||||
<p className="text-muted font-mono text-xs">2024-01-15.csv</p>
|
<p className="text-muted font-mono text-xs">2024-01-15.csv</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,10 +122,10 @@ function FormatInstructions() {
|
|||||||
// Important Notes Section
|
// Important Notes Section
|
||||||
function ImportantNotes() {
|
function ImportantNotes() {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary border-l-4 border-l-amber-500">
|
<Card className="bg-card border-border border border-l-4 border-l-amber-500">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-warning">
|
<CardTitle className="text-destructive flex items-center gap-2">
|
||||||
<AlertCircle className="text-icon-amber h-5 w-5" />
|
<AlertCircle className="text-primary h-5 w-5" />
|
||||||
Important Notes
|
Important Notes
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -158,18 +158,18 @@ function ImportantNotes() {
|
|||||||
// File Format Help Section
|
// File Format Help Section
|
||||||
function FileFormatHelp() {
|
function FileFormatHelp() {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-info">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<FileSpreadsheet className="text-icon-blue h-5 w-5" />
|
<FileSpreadsheet className="text-primary h-5 w-5" />
|
||||||
Supported File Formats
|
Supported File Formats
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="grid gap-4 md:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-3">
|
||||||
<div className="space-y-2 text-center">
|
<div className="space-y-2 text-center">
|
||||||
<div className="mx-auto w-fit rounded-full bg-blue-50 p-3 dark:bg-blue-900/20">
|
<div className="bg-accent mx-auto w-fit p-3">
|
||||||
<FileSpreadsheet className="h-6 w-6 text-blue-600" />
|
<FileSpreadsheet className="text-foreground-foreground h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold">CSV Files</h4>
|
<h4 className="font-semibold">CSV Files</h4>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
@@ -178,8 +178,8 @@ function FileFormatHelp() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 text-center">
|
<div className="space-y-2 text-center">
|
||||||
<div className="mx-auto w-fit rounded-full bg-green-50 p-3 dark:bg-green-900/20">
|
<div className="bg-primary/10 mx-auto w-fit p-3">
|
||||||
<Upload className="h-6 w-6 text-green-600" />
|
<Upload className="text-primary h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold">Max Size</h4>
|
<h4 className="font-semibold">Max Size</h4>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
@@ -187,8 +187,8 @@ function FileFormatHelp() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2 text-center">
|
<div className="space-y-2 text-center">
|
||||||
<div className="mx-auto w-fit rounded-full bg-purple-50 p-3 dark:bg-purple-900/20">
|
<div className="bg-secondary mx-auto w-fit p-3">
|
||||||
<CheckCircle className="h-6 w-6 text-purple-600" />
|
<CheckCircle className="text-muted-foreground-foreground h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-semibold">Validation</h4>
|
<h4 className="font-semibold">Validation</h4>
|
||||||
<p className="text-muted-foreground text-sm">
|
<p className="text-muted-foreground text-sm">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default async function InvoicesPage() {
|
|||||||
<span>Import CSV</span>
|
<span>Import CSV</span>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild className="btn-brand-primary shadow-md">
|
<Button asChild variant="default" className="shadow-md">
|
||||||
<Link href="/dashboard/invoices/new">
|
<Link href="/dashboard/invoices/new">
|
||||||
<Plus className="mr-2 h-5 w-5" />
|
<Plus className="mr-2 h-5 w-5" />
|
||||||
<span>Create Invoice</span>
|
<span>Create Invoice</span>
|
||||||
|
|||||||
@@ -8,19 +8,19 @@ export default function DashboardLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="floating-orbs relative min-h-screen">
|
<div className="bg-dashboard relative min-h-screen">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
{/* Mobile layout - no left margin */}
|
{/* Mobile layout - no left margin */}
|
||||||
<main className="relative z-10 min-h-screen pt-20 md:hidden">
|
<main className="relative z-10 min-h-screen pt-16 md:hidden">
|
||||||
<div className="px-4 pt-4 pb-6 sm:px-6">
|
<div className="bg-background px-4 pt-4 pb-6 sm:px-6">
|
||||||
<DashboardBreadcrumbs />
|
<DashboardBreadcrumbs />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{/* Desktop layout - with sidebar margin */}
|
{/* Desktop layout - with sidebar margin */}
|
||||||
<main className="relative z-10 hidden min-h-screen pt-20 md:ml-[276px] md:block">
|
<main className="relative z-10 hidden min-h-screen pt-16 md:ml-64 md:block">
|
||||||
<div className="px-6 pt-6 pb-6">
|
<div className="bg-background px-6 pt-6 pb-6">
|
||||||
<DashboardBreadcrumbs />
|
<DashboardBreadcrumbs />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
Eye,
|
Eye,
|
||||||
FileText,
|
FileText,
|
||||||
Plus,
|
Plus,
|
||||||
|
TrendingDown,
|
||||||
|
TrendingUp,
|
||||||
Users,
|
Users,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -21,23 +23,23 @@ import { getEffectiveInvoiceStatus } from "~/lib/invoice-status";
|
|||||||
import { auth } from "~/server/auth";
|
import { auth } from "~/server/auth";
|
||||||
import { HydrateClient, api } from "~/trpc/server";
|
import { HydrateClient, api } from "~/trpc/server";
|
||||||
import type { StoredInvoiceStatus } from "~/types/invoice";
|
import type { StoredInvoiceStatus } from "~/types/invoice";
|
||||||
|
import { RevenueChart } from "~/app/dashboard/_components/revenue-chart";
|
||||||
|
import { InvoiceStatusChart } from "~/app/dashboard/_components/invoice-status-chart";
|
||||||
|
import { MonthlyMetricsChart } from "~/app/dashboard/_components/monthly-metrics-chart";
|
||||||
|
|
||||||
// Modern gradient background component
|
// Hero section with clean mono design
|
||||||
function DashboardHero({ firstName }: { firstName: string }) {
|
function DashboardHero({ firstName }: { firstName: string }) {
|
||||||
return (
|
return (
|
||||||
<Card className="relative mb-8 overflow-hidden border-0 p-8 shadow-sm transition-shadow hover:shadow-md">
|
<div className="mb-8">
|
||||||
<div className="absolute inset-0" />
|
<h1 className="mb-2 text-3xl font-bold">Welcome back, {firstName}!</h1>
|
||||||
<div className="relative z-10">
|
<p className="text-muted-foreground text-lg">
|
||||||
<h1 className="mb-2 text-3xl font-bold">Welcome back, {firstName}!</h1>
|
Here's what's happening with your business today
|
||||||
<p className="text-lg">Ready to manage your invoicing business</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute -top-8 -right-8 h-32 w-32 rounded-full bg-white/10" />
|
|
||||||
<div className="absolute -right-4 -bottom-4 h-24 w-24 rounded-full bg-white/5" />
|
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced stats cards with better visual hierarchy
|
// Enhanced stats cards with better visuals
|
||||||
async function DashboardStats() {
|
async function DashboardStats() {
|
||||||
const [clients, invoices] = await Promise.all([
|
const [clients, invoices] = await Promise.all([
|
||||||
api.clients.getAll(),
|
api.clients.getAll(),
|
||||||
@@ -45,8 +47,48 @@ async function DashboardStats() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const totalClients = clients.length;
|
const totalClients = clients.length;
|
||||||
const totalInvoices = invoices.length;
|
const paidInvoices = invoices.filter(
|
||||||
const totalRevenue = invoices
|
(invoice) =>
|
||||||
|
getEffectiveInvoiceStatus(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
) === "paid",
|
||||||
|
);
|
||||||
|
const totalRevenue = paidInvoices.reduce(
|
||||||
|
(sum, invoice) => sum + invoice.totalAmount,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const pendingInvoices = invoices.filter((invoice) => {
|
||||||
|
const effectiveStatus = getEffectiveInvoiceStatus(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
);
|
||||||
|
return effectiveStatus === "sent" || effectiveStatus === "overdue";
|
||||||
|
});
|
||||||
|
const pendingAmount = pendingInvoices.reduce(
|
||||||
|
(sum, invoice) => sum + invoice.totalAmount,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const overdueInvoices = invoices.filter(
|
||||||
|
(invoice) =>
|
||||||
|
getEffectiveInvoiceStatus(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
) === "overdue",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate month-over-month trends
|
||||||
|
const now = new Date();
|
||||||
|
const currentMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||||
|
|
||||||
|
// Current month data
|
||||||
|
const currentMonthInvoices = invoices.filter(
|
||||||
|
(invoice) => new Date(invoice.issueDate) >= currentMonth,
|
||||||
|
);
|
||||||
|
const currentMonthRevenue = currentMonthInvoices
|
||||||
.filter(
|
.filter(
|
||||||
(invoice) =>
|
(invoice) =>
|
||||||
getEffectiveInvoiceStatus(
|
getEffectiveInvoiceStatus(
|
||||||
@@ -55,79 +97,134 @@ async function DashboardStats() {
|
|||||||
) === "paid",
|
) === "paid",
|
||||||
)
|
)
|
||||||
.reduce((sum, invoice) => sum + invoice.totalAmount, 0);
|
.reduce((sum, invoice) => sum + invoice.totalAmount, 0);
|
||||||
const pendingAmount = invoices
|
|
||||||
.filter((invoice) => {
|
// Last month data
|
||||||
const effectiveStatus = getEffectiveInvoiceStatus(
|
const lastMonthInvoices = invoices.filter((invoice) => {
|
||||||
|
const date = new Date(invoice.issueDate);
|
||||||
|
return date >= lastMonth && date < currentMonth;
|
||||||
|
});
|
||||||
|
const lastMonthRevenue = lastMonthInvoices
|
||||||
|
.filter(
|
||||||
|
(invoice) =>
|
||||||
|
getEffectiveInvoiceStatus(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
) === "paid",
|
||||||
|
)
|
||||||
|
.reduce((sum, invoice) => sum + invoice.totalAmount, 0);
|
||||||
|
|
||||||
|
// Previous month data for clients
|
||||||
|
const prevMonthClients = clients.filter(
|
||||||
|
(client) => new Date(client.createdAt) < currentMonth,
|
||||||
|
).length;
|
||||||
|
|
||||||
|
// Calculate trends
|
||||||
|
const revenueChange =
|
||||||
|
lastMonthRevenue > 0
|
||||||
|
? ((currentMonthRevenue - lastMonthRevenue) / lastMonthRevenue) * 100
|
||||||
|
: currentMonthRevenue > 0
|
||||||
|
? 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const pendingChange =
|
||||||
|
lastMonthInvoices.length > 0
|
||||||
|
? ((pendingInvoices.length -
|
||||||
|
lastMonthInvoices.filter((invoice) => {
|
||||||
|
const status = getEffectiveInvoiceStatus(
|
||||||
|
invoice.status as StoredInvoiceStatus,
|
||||||
|
invoice.dueDate,
|
||||||
|
);
|
||||||
|
return status === "sent" || status === "overdue";
|
||||||
|
}).length) /
|
||||||
|
lastMonthInvoices.length) *
|
||||||
|
100
|
||||||
|
: pendingInvoices.length > 0
|
||||||
|
? 100
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const clientChange = totalClients - prevMonthClients;
|
||||||
|
|
||||||
|
const lastMonthOverdue = lastMonthInvoices.filter(
|
||||||
|
(invoice) =>
|
||||||
|
getEffectiveInvoiceStatus(
|
||||||
invoice.status as StoredInvoiceStatus,
|
invoice.status as StoredInvoiceStatus,
|
||||||
invoice.dueDate,
|
invoice.dueDate,
|
||||||
);
|
) === "overdue",
|
||||||
return effectiveStatus === "sent" || effectiveStatus === "overdue";
|
).length;
|
||||||
})
|
const overdueChange = overdueInvoices.length - lastMonthOverdue;
|
||||||
.reduce((sum, invoice) => sum + invoice.totalAmount, 0);
|
|
||||||
|
const formatTrend = (value: number, isCount = false) => {
|
||||||
|
if (isCount) {
|
||||||
|
return value > 0 ? `+${value}` : value.toString();
|
||||||
|
}
|
||||||
|
return value > 0 ? `+${value.toFixed(1)}%` : `${value.toFixed(1)}%`;
|
||||||
|
};
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{
|
{
|
||||||
title: "Total Revenue",
|
title: "Total Revenue",
|
||||||
value: `$${totalRevenue.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
|
value: `$${totalRevenue.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
|
||||||
change: "+12.5%",
|
change: formatTrend(revenueChange),
|
||||||
|
trend: revenueChange >= 0 ? ("up" as const) : ("down" as const),
|
||||||
icon: DollarSign,
|
icon: DollarSign,
|
||||||
color: "",
|
description: `From ${paidInvoices.length} paid invoices`,
|
||||||
bgColor: "bg-green-50",
|
|
||||||
changeColor: "",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Pending Amount",
|
title: "Pending Amount",
|
||||||
value: `$${pendingAmount.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
|
value: `$${pendingAmount.toLocaleString("en-US", { minimumFractionDigits: 2 })}`,
|
||||||
change: "+8.2%",
|
change: formatTrend(pendingChange),
|
||||||
|
trend: pendingChange >= 0 ? ("up" as const) : ("down" as const),
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
color: "",
|
description: `${pendingInvoices.length} invoices awaiting payment`,
|
||||||
bgColor: "bg-amber-50",
|
|
||||||
changeColor: "",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Active Clients",
|
title: "Active Clients",
|
||||||
value: totalClients.toString(),
|
value: totalClients.toString(),
|
||||||
change: "+3",
|
change: formatTrend(clientChange, true),
|
||||||
|
trend: clientChange >= 0 ? ("up" as const) : ("down" as const),
|
||||||
icon: Users,
|
icon: Users,
|
||||||
color: "",
|
description: "Total registered clients",
|
||||||
bgColor: "bg-blue-50",
|
|
||||||
changeColor: "",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Total Invoices",
|
title: "Overdue Invoices",
|
||||||
value: totalInvoices.toString(),
|
value: overdueInvoices.length.toString(),
|
||||||
change: "+15",
|
change: formatTrend(overdueChange, true),
|
||||||
icon: FileText,
|
trend: overdueChange <= 0 ? ("up" as const) : ("down" as const),
|
||||||
color: "",
|
icon: TrendingDown,
|
||||||
bgColor: "bg-purple-50",
|
description: "Invoices past due date",
|
||||||
changeColor: "",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-8 grid grid-cols-2 gap-3 sm:gap-6 lg:grid-cols-4">
|
<div className="mb-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
{stats.map((stat) => {
|
{stats.map((stat) => {
|
||||||
const Icon = stat.icon;
|
const Icon = stat.icon;
|
||||||
|
const TrendIcon = stat.trend === "up" ? TrendingUp : TrendingDown;
|
||||||
|
const isPositive = stat.trend === "up";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card key={stat.title}>
|
||||||
key={stat.title}
|
<CardContent className="p-6">
|
||||||
className="border-0 shadow-sm transition-shadow hover:shadow-md"
|
<div className="flex items-center justify-between space-y-0 pb-2">
|
||||||
>
|
<div className="flex items-center space-x-2">
|
||||||
<CardContent className="p-3 sm:p-4 lg:p-6">
|
<Icon className="text-muted-foreground h-5 w-5" />
|
||||||
<div className="mb-2 flex items-center justify-between sm:mb-3 lg:mb-4">
|
<p className="text-muted-foreground text-sm font-medium">
|
||||||
<div className={`rounded-lg p-1.5 sm:p-2 ${stat.bgColor}`}>
|
{stat.title}
|
||||||
<Icon className="h-3 w-3 text-gray-700 sm:h-4 sm:w-4 lg:h-5 lg:w-5 dark:text-gray-800" />
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`flex items-center space-x-1 text-xs ${
|
||||||
|
isPositive ? "text-green-600" : "text-red-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<TrendIcon className="h-3 w-3" />
|
||||||
|
<span>{stat.change}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-medium text-teal-600 dark:text-teal-400">
|
|
||||||
{stat.change}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="space-y-1">
|
||||||
<p className="mb-1 text-base font-bold text-gray-900 sm:text-xl lg:text-2xl dark:text-gray-100">
|
<p className="text-2xl font-bold">{stat.value}</p>
|
||||||
{stat.value}
|
<p className="text-muted-foreground text-xs">
|
||||||
</p>
|
{stat.description}
|
||||||
<p className="text-xs text-gray-600 lg:text-sm dark:text-gray-300">
|
|
||||||
{stat.title}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -138,64 +235,111 @@ async function DashboardStats() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick Actions with better visual design
|
// Charts section
|
||||||
|
async function ChartsSection() {
|
||||||
|
const invoices = await api.invoices.getAll();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
|
{/* Revenue Trend Chart */}
|
||||||
|
<Card className="lg:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<BarChart3 className="h-5 w-5" />
|
||||||
|
Revenue Over Time
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<RevenueChart invoices={invoices} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Invoice Status Breakdown */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Activity className="h-5 w-5" />
|
||||||
|
Invoice Status
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<InvoiceStatusChart invoices={invoices} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Monthly Metrics */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Calendar className="h-5 w-5" />
|
||||||
|
Monthly Metrics
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<MonthlyMetricsChart invoices={invoices} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced Quick Actions
|
||||||
function QuickActions() {
|
function QuickActions() {
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
title: "Create Invoice",
|
title: "Create Invoice",
|
||||||
description: "Start a new invoice",
|
description: "Start a new invoice for a client",
|
||||||
href: "/dashboard/invoices/new",
|
href: "/dashboard/invoices/new",
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
primary: true,
|
featured: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Add Client",
|
title: "Add Client",
|
||||||
description: "Add a new client",
|
description: "Register a new client",
|
||||||
href: "/dashboard/clients/new",
|
href: "/dashboard/clients/new",
|
||||||
icon: Users,
|
icon: Users,
|
||||||
primary: false,
|
featured: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "View Reports",
|
title: "View All Invoices",
|
||||||
description: "Business analytics",
|
description: "Manage your invoice pipeline",
|
||||||
href: "/dashboard/reports",
|
href: "/dashboard/invoices",
|
||||||
icon: BarChart3,
|
icon: BarChart3,
|
||||||
primary: false,
|
featured: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-0 shadow-sm">
|
<Card>
|
||||||
<CardHeader className="pb-3">
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-lg">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Plus className="h-5 w-5 text-teal-600 dark:text-teal-400" />
|
<Plus className="h-5 w-5" />
|
||||||
Quick Actions
|
Quick Actions
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2">
|
<CardContent className="space-y-3">
|
||||||
{actions.map((action) => {
|
{actions.map((action) => {
|
||||||
const Icon = action.icon;
|
const Icon = action.icon;
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={action.title}
|
key={action.title}
|
||||||
asChild
|
asChild
|
||||||
variant={action.primary ? "default" : "outline"}
|
variant="outline"
|
||||||
className={`h-12 w-full justify-start px-3 ${
|
className={`h-auto w-full justify-start p-4 ${
|
||||||
action.primary
|
action.featured
|
||||||
? "bg-teal-600 text-white hover:bg-teal-700"
|
? "border-foreground/20 bg-muted/50 hover:bg-muted"
|
||||||
: "border-gray-200 hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-800"
|
: "hover:bg-muted/50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Link href={action.href}>
|
<Link href={action.href}>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center space-x-3">
|
||||||
<Icon
|
<Icon className="h-5 w-5 flex-shrink-0" />
|
||||||
className={`h-4 w-4 ${action.primary ? "text-white" : "text-gray-600 dark:text-gray-300"}`}
|
<div className="text-left">
|
||||||
/>
|
<p className="font-semibold">{action.title}</p>
|
||||||
<span
|
<p className="text-muted-foreground text-sm">
|
||||||
className={`font-medium ${action.primary ? "text-white" : "text-gray-900 dark:text-gray-100"}`}
|
{action.description}
|
||||||
>
|
</p>
|
||||||
{action.title}
|
</div>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -206,7 +350,7 @@ function QuickActions() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current work in progress
|
// Current work section with enhanced design
|
||||||
async function CurrentWork() {
|
async function CurrentWork() {
|
||||||
const invoices = await api.invoices.getAll();
|
const invoices = await api.invoices.getAll();
|
||||||
const draftInvoices = invoices.filter(
|
const draftInvoices = invoices.filter(
|
||||||
@@ -220,20 +364,21 @@ async function CurrentWork() {
|
|||||||
|
|
||||||
if (!currentInvoice) {
|
if (!currentInvoice) {
|
||||||
return (
|
return (
|
||||||
<Card className="border-0 shadow-sm">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-lg">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Activity className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<Activity className="h-5 w-5" />
|
||||||
Current Work
|
Current Work
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="py-8 text-center">
|
<div className="py-8 text-center">
|
||||||
<FileText className="mx-auto mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
|
<FileText className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
|
||||||
<p className="mb-4 text-gray-600 dark:text-gray-300">
|
<h3 className="mb-2 text-lg font-semibold">No active drafts</h3>
|
||||||
No draft invoices found
|
<p className="text-muted-foreground mb-4">
|
||||||
|
Create a new invoice to get started
|
||||||
</p>
|
</p>
|
||||||
<Button asChild className="bg-teal-600 hover:bg-teal-700">
|
<Button asChild variant="outline" className="border-foreground/20">
|
||||||
<Link href="/dashboard/invoices/new">
|
<Link href="/dashboard/invoices/new">
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
Create Invoice
|
Create Invoice
|
||||||
@@ -249,49 +394,41 @@ async function CurrentWork() {
|
|||||||
currentInvoice.items?.reduce((sum, item) => sum + item.hours, 0) ?? 0;
|
currentInvoice.items?.reduce((sum, item) => sum + item.hours, 0) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-0 shadow-sm">
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-lg">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Activity className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<Activity className="h-5 w-5" />
|
||||||
Current Work
|
Current Work
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Badge variant="secondary">In Progress</Badge>
|
<Badge variant="secondary">In Progress</Badge>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="space-y-2">
|
||||||
<div>
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
#{currentInvoice.invoiceNumber}
|
#{currentInvoice.invoiceNumber}
|
||||||
</p>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<span className="text-primary text-2xl font-bold">
|
||||||
{currentInvoice.client?.name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
|
||||||
<p className="text-2xl font-bold text-teal-600 dark:text-teal-400">
|
|
||||||
${currentInvoice.totalAmount.toFixed(2)}
|
${currentInvoice.totalAmount.toFixed(2)}
|
||||||
</p>
|
</span>
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
</div>
|
||||||
{totalHours.toFixed(1)} hours
|
<div className="text-muted-foreground flex items-center justify-between text-sm">
|
||||||
</p>
|
<span>{currentInvoice.client?.name}</span>
|
||||||
|
<span>{totalHours.toFixed(1)} hours logged</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button asChild variant="outline" size="sm" className="flex-1">
|
<Button asChild variant="outline" size="sm" className="flex-1">
|
||||||
<Link href={`/dashboard/invoices/${currentInvoice.id}`}>
|
<Link href={`/dashboard/invoices/${currentInvoice.id}`}>
|
||||||
<Eye className="mr-2 h-3 w-3" />
|
<Eye className="mr-2 h-4 w-4" />
|
||||||
View
|
View
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button asChild size="sm" className="flex-1">
|
||||||
asChild
|
<Link href={`/dashboard/invoices/${currentInvoice.id}/edit`}>
|
||||||
size="sm"
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
className="flex-1 bg-teal-600 hover:bg-teal-700"
|
|
||||||
>
|
|
||||||
<Link href={`/dashboard/invoices/${currentInvoice.id}`}>
|
|
||||||
<Edit className="mr-2 h-3 w-3" />
|
|
||||||
Continue
|
Continue
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -302,7 +439,7 @@ async function CurrentWork() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recent activity with enhanced design
|
// Enhanced recent activity
|
||||||
async function RecentActivity() {
|
async function RecentActivity() {
|
||||||
const invoices = await api.invoices.getAll();
|
const invoices = await api.invoices.getAll();
|
||||||
const recentInvoices = invoices
|
const recentInvoices = invoices
|
||||||
@@ -315,21 +452,21 @@ async function RecentActivity() {
|
|||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "paid":
|
case "paid":
|
||||||
return "bg-green-50 border-green-200";
|
return "bg-green-50 border-green-200 text-green-700";
|
||||||
case "sent":
|
case "sent":
|
||||||
return "bg-blue-50 border-blue-200";
|
return "bg-blue-50 border-blue-200 text-blue-700";
|
||||||
case "overdue":
|
case "overdue":
|
||||||
return "bg-red-50 border-red-200";
|
return "bg-red-50 border-red-200 text-red-700";
|
||||||
default:
|
default:
|
||||||
return "bg-gray-50 border-gray-200";
|
return "bg-gray-50 border-gray-200 text-gray-700";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border-0 shadow-sm">
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-lg">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Calendar className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
<Calendar className="h-5 w-5" />
|
||||||
Recent Activity
|
Recent Activity
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<Button variant="ghost" size="sm" asChild>
|
<Button variant="ghost" size="sm" asChild>
|
||||||
@@ -342,11 +479,12 @@ async function RecentActivity() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
{recentInvoices.length === 0 ? (
|
{recentInvoices.length === 0 ? (
|
||||||
<div className="py-8 text-center">
|
<div className="py-8 text-center">
|
||||||
<FileText className="mx-auto mb-4 h-12 w-12 text-gray-300 dark:text-gray-600" />
|
<FileText className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
|
||||||
<p className="mb-4 text-gray-600 dark:text-gray-300">
|
<h3 className="mb-2 text-lg font-semibold">No invoices yet</h3>
|
||||||
No invoices yet
|
<p className="text-muted-foreground mb-4">
|
||||||
|
Create your first invoice to get started
|
||||||
</p>
|
</p>
|
||||||
<Button asChild className="bg-teal-600 hover:bg-teal-700">
|
<Button asChild variant="outline" className="border-foreground/20">
|
||||||
<Link href="/dashboard/invoices/new">
|
<Link href="/dashboard/invoices/new">
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
Create Your First Invoice
|
Create Your First Invoice
|
||||||
@@ -361,39 +499,28 @@ async function RecentActivity() {
|
|||||||
href={`/dashboard/invoices/${invoice.id}`}
|
href={`/dashboard/invoices/${invoice.id}`}
|
||||||
className="block"
|
className="block"
|
||||||
>
|
>
|
||||||
<Card className="card-secondary transition-colors hover:bg-gray-200/70 dark:hover:bg-gray-700/60">
|
<div className="hover:bg-muted/50 flex items-center justify-between rounded-lg border p-3 transition-colors">
|
||||||
<CardContent className="p-4">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="space-y-3">
|
<div className="bg-muted rounded-lg p-2">
|
||||||
<div className="flex items-center gap-3">
|
<FileText className="text-muted-foreground h-4 w-4" />
|
||||||
<div className="rounded-lg bg-gray-100 p-2 dark:bg-gray-700">
|
|
||||||
<FileText className="h-4 w-4 text-gray-600 dark:text-gray-300" />
|
|
||||||
</div>
|
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
|
||||||
#{invoice.invoiceNumber}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
|
||||||
{invoice.client?.name} •{" "}
|
|
||||||
{new Date(invoice.issueDate).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg p-1 transition-colors hover:bg-gray-300/50 dark:hover:bg-gray-600/50">
|
|
||||||
<Eye className="h-4 w-4 text-gray-600 dark:text-gray-300" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Badge
|
|
||||||
className={`border ${getStatusColor(invoice.status)}`}
|
|
||||||
>
|
|
||||||
{invoice.status}
|
|
||||||
</Badge>
|
|
||||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
|
||||||
${invoice.totalAmount.toFixed(2)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
<div className="min-w-0 flex-1">
|
||||||
</Card>
|
<p className="font-medium">#{invoice.invoiceNumber}</p>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
{invoice.client?.name} •{" "}
|
||||||
|
{new Date(invoice.issueDate).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<Badge className={getStatusColor(invoice.status)}>
|
||||||
|
{invoice.status}
|
||||||
|
</Badge>
|
||||||
|
<span className="font-semibold">
|
||||||
|
${invoice.totalAmount.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -406,16 +533,16 @@ async function RecentActivity() {
|
|||||||
// Loading skeletons
|
// Loading skeletons
|
||||||
function StatsSkeleton() {
|
function StatsSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="mb-8 grid grid-cols-2 gap-3 sm:gap-6 lg:grid-cols-4">
|
<div className="mb-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
{Array.from({ length: 4 }).map((_, i) => (
|
{Array.from({ length: 4 }).map((_, i) => (
|
||||||
<Card key={i} className="border-0 shadow-sm">
|
<Card key={i}>
|
||||||
<CardContent className="p-3 sm:p-4 lg:p-6">
|
<CardContent className="p-6">
|
||||||
<div className="mb-2 flex items-center justify-between sm:mb-3 lg:mb-4">
|
<div className="flex items-center justify-between space-y-0 pb-2">
|
||||||
<Skeleton className="h-6 w-6 rounded-lg sm:h-8 sm:w-8 lg:h-9 lg:w-9" />
|
<Skeleton className="h-4 w-24" />
|
||||||
<Skeleton className="h-3 w-8 sm:h-4 sm:w-12" />
|
<Skeleton className="h-4 w-12" />
|
||||||
</div>
|
</div>
|
||||||
<Skeleton className="mb-1 h-5 w-16 sm:mb-2 sm:h-6 sm:w-20 lg:h-8" />
|
<Skeleton className="mb-2 h-8 w-20" />
|
||||||
<Skeleton className="h-3 w-20 sm:h-4 sm:w-24" />
|
<Skeleton className="h-3 w-32" />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -423,9 +550,40 @@ function StatsSkeleton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ChartsSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="mb-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
|
<Card className="lg:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<Skeleton className="h-6 w-40" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton className="h-64 w-full" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<Skeleton className="h-6 w-32" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton className="h-64 w-full" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<Skeleton className="h-6 w-36" />
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Skeleton className="h-64 w-full" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function CardSkeleton() {
|
function CardSkeleton() {
|
||||||
return (
|
return (
|
||||||
<Card className="border-0 shadow-sm">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<Skeleton className="h-6 w-32" />
|
<Skeleton className="h-6 w-32" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -454,21 +612,28 @@ export default async function DashboardPage() {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</HydrateClient>
|
</HydrateClient>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
|
||||||
<HydrateClient>
|
|
||||||
<Suspense fallback={<CardSkeleton />}>
|
|
||||||
<CurrentWork />
|
|
||||||
</Suspense>
|
|
||||||
</HydrateClient>
|
|
||||||
|
|
||||||
<QuickActions />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<HydrateClient>
|
<HydrateClient>
|
||||||
<Suspense fallback={<CardSkeleton />}>
|
<Suspense fallback={<ChartsSkeleton />}>
|
||||||
<RecentActivity />
|
<ChartsSection />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</HydrateClient>
|
</HydrateClient>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||||
|
<div className="space-y-8">
|
||||||
|
<HydrateClient>
|
||||||
|
<Suspense fallback={<CardSkeleton />}>
|
||||||
|
<CurrentWork />
|
||||||
|
</Suspense>
|
||||||
|
</HydrateClient>
|
||||||
|
<QuickActions />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HydrateClient>
|
||||||
|
<Suspense fallback={<CardSkeleton />}>
|
||||||
|
<RecentActivity />
|
||||||
|
</Suspense>
|
||||||
|
</HydrateClient>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,22 +267,22 @@ export function SettingsContent() {
|
|||||||
label: "Clients",
|
label: "Clients",
|
||||||
value: dataStats?.clients ?? 0,
|
value: dataStats?.clients ?? 0,
|
||||||
icon: Users,
|
icon: Users,
|
||||||
color: "text-blue-600",
|
color: "text-primary",
|
||||||
bgColor: "bg-blue-50 dark:bg-blue-900/20",
|
bgColor: "bg-primary/10",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Businesses",
|
label: "Businesses",
|
||||||
value: dataStats?.businesses ?? 0,
|
value: dataStats?.businesses ?? 0,
|
||||||
icon: Building,
|
icon: Building,
|
||||||
color: "text-purple-600",
|
color: "text-muted-foreground",
|
||||||
bgColor: "bg-purple-50 dark:bg-purple-900/20",
|
bgColor: "bg-muted",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Invoices",
|
label: "Invoices",
|
||||||
value: dataStats?.invoices ?? 0,
|
value: dataStats?.invoices ?? 0,
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
color: "text-emerald-600",
|
color: "text-primary",
|
||||||
bgColor: "bg-emerald-50 dark:bg-emerald-900/20",
|
bgColor: "bg-accent",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -291,10 +291,10 @@ export function SettingsContent() {
|
|||||||
{/* Profile & Account Overview */}
|
{/* Profile & Account Overview */}
|
||||||
<div className="grid gap-6 lg:grid-cols-2">
|
<div className="grid gap-6 lg:grid-cols-2">
|
||||||
{/* Profile Section */}
|
{/* Profile Section */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<User className="text-icon-blue h-5 w-5" />
|
<User className="text-primary h-5 w-5" />
|
||||||
Profile Information
|
Profile Information
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -327,7 +327,7 @@ export function SettingsContent() {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={updateProfileMutation.isPending}
|
disabled={updateProfileMutation.isPending}
|
||||||
className="btn-brand-primary"
|
variant="default"
|
||||||
>
|
>
|
||||||
{updateProfileMutation.isPending
|
{updateProfileMutation.isPending
|
||||||
? "Updating..."
|
? "Updating..."
|
||||||
@@ -338,10 +338,10 @@ export function SettingsContent() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Data Overview */}
|
{/* Data Overview */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-info">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<Database className="text-icon-blue h-5 w-5" />
|
<Database className="text-primary h-5 w-5" />
|
||||||
Account Data
|
Account Data
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -355,10 +355,10 @@ export function SettingsContent() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.label}
|
key={item.label}
|
||||||
className="bg-card flex items-center justify-between rounded-lg border p-4 transition-shadow hover:shadow-sm"
|
className="bg-card flex items-center justify-between border p-4 transition-shadow hover:shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`rounded-lg p-2 ${item.bgColor}`}>
|
<div className={` p-2 ${item.bgColor}`}>
|
||||||
<Icon className={`h-4 w-4 ${item.color}`} />
|
<Icon className={`h-4 w-4 ${item.color}`} />
|
||||||
</div>
|
</div>
|
||||||
<span className="font-medium">{item.label}</span>
|
<span className="font-medium">{item.label}</span>
|
||||||
@@ -378,10 +378,10 @@ export function SettingsContent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Security Settings */}
|
{/* Security Settings */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<Key className="text-icon-amber h-5 w-5" />
|
<Key className="text-primary h-5 w-5" />
|
||||||
Security Settings
|
Security Settings
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -474,7 +474,7 @@ export function SettingsContent() {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={changePasswordMutation.isPending}
|
disabled={changePasswordMutation.isPending}
|
||||||
className="btn-brand-primary"
|
variant="default"
|
||||||
>
|
>
|
||||||
{changePasswordMutation.isPending
|
{changePasswordMutation.isPending
|
||||||
? "Changing Password..."
|
? "Changing Password..."
|
||||||
@@ -485,9 +485,9 @@ export function SettingsContent() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Data Management */}
|
{/* Data Management */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<Shield className="text-icon-indigo h-5 w-5" />
|
<Shield className="text-icon-indigo h-5 w-5" />
|
||||||
Data Management
|
Data Management
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -604,7 +604,7 @@ export function SettingsContent() {
|
|||||||
disabled={
|
disabled={
|
||||||
!importData.trim() || importDataMutation.isPending
|
!importData.trim() || importDataMutation.isPending
|
||||||
}
|
}
|
||||||
className="btn-brand-primary"
|
variant="default"
|
||||||
>
|
>
|
||||||
{importDataMutation.isPending
|
{importDataMutation.isPending
|
||||||
? "Importing..."
|
? "Importing..."
|
||||||
@@ -617,7 +617,7 @@ export function SettingsContent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Backup Information */}
|
{/* Backup Information */}
|
||||||
<div className="border-border bg-muted/20 rounded-lg border p-4">
|
<div className="border-border bg-muted/20 border p-4">
|
||||||
<h4 className="font-medium">Backup Information</h4>
|
<h4 className="font-medium">Backup Information</h4>
|
||||||
<ul className="text-muted-foreground mt-2 space-y-1 text-sm">
|
<ul className="text-muted-foreground mt-2 space-y-1 text-sm">
|
||||||
<li>• Regular backups protect your important business data</li>
|
<li>• Regular backups protect your important business data</li>
|
||||||
@@ -634,9 +634,9 @@ export function SettingsContent() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Danger Zone */}
|
{/* Danger Zone */}
|
||||||
<Card className="card-primary border-l-4 border-l-red-500">
|
<Card className="bg-card border-border border border-l-4 border-l-red-500">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-warning">
|
<CardTitle className="text-destructive flex items-center gap-2">
|
||||||
<AlertTriangle className="text-icon-red h-5 w-5" />
|
<AlertTriangle className="text-icon-red h-5 w-5" />
|
||||||
Danger Zone
|
Danger Zone
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -646,8 +646,8 @@ export function SettingsContent() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-lg border border-red-200 bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20">
|
<div className="bg-destructive/10 border-destructive/20 border p-4">
|
||||||
<h4 className="font-medium text-red-600 dark:text-red-400">
|
<h4 className="text-destructive font-medium">
|
||||||
Delete All Account Data
|
Delete All Account Data
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-muted-foreground mt-2 text-sm">
|
<p className="text-muted-foreground mt-2 text-sm">
|
||||||
@@ -672,7 +672,7 @@ export function SettingsContent() {
|
|||||||
This action cannot be undone. This will permanently delete
|
This action cannot be undone. This will permanently delete
|
||||||
all your:
|
all your:
|
||||||
</div>
|
</div>
|
||||||
<ul className="border-border bg-muted/50 list-inside list-disc space-y-1 rounded-lg border p-3 text-sm">
|
<ul className="border-border bg-muted/50 list-inside list-disc space-y-1 border p-3 text-sm">
|
||||||
<li>Client information and contact details</li>
|
<li>Client information and contact details</li>
|
||||||
<li>Business profiles and settings</li>
|
<li>Business profiles and settings</li>
|
||||||
<li>Invoices and invoice line items</li>
|
<li>Invoices and invoice line items</li>
|
||||||
@@ -703,7 +703,7 @@ export function SettingsContent() {
|
|||||||
deleteConfirmText !== "delete all my data" ||
|
deleteConfirmText !== "delete all my data" ||
|
||||||
deleteDataMutation.isPending
|
deleteDataMutation.isPending
|
||||||
}
|
}
|
||||||
className="bg-red-600 hover:bg-red-700"
|
className="bg-destructive hover:bg-destructive/90"
|
||||||
>
|
>
|
||||||
{deleteDataMutation.isPending
|
{deleteDataMutation.isPending
|
||||||
? "Deleting..."
|
? "Deleting..."
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import "~/styles/globals.css";
|
|||||||
|
|
||||||
import { Analytics } from "@vercel/analytics/next";
|
import { Analytics } from "@vercel/analytics/next";
|
||||||
import { type Metadata } from "next";
|
import { type Metadata } from "next";
|
||||||
import { Geist, Azeret_Mono } from "next/font/google";
|
import { Geist_Mono } from "next/font/google";
|
||||||
|
|
||||||
import { TRPCReactProvider } from "~/trpc/react";
|
import { TRPCReactProvider } from "~/trpc/react";
|
||||||
import { Toaster } from "~/components/ui/toaster";
|
import { Toaster } from "~/components/ui/sonner";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "beenvoice - Invoicing Made Simple",
|
title: "beenvoice - Invoicing Made Simple",
|
||||||
@@ -14,14 +14,9 @@ export const metadata: Metadata = {
|
|||||||
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const geist = Geist({
|
const geistMono = Geist_Mono({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-mono",
|
||||||
});
|
|
||||||
|
|
||||||
const azeretMono = Azeret_Mono({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-azeret-mono",
|
|
||||||
display: "swap",
|
display: "swap",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -29,9 +24,9 @@ export default function RootLayout({
|
|||||||
children,
|
children,
|
||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={`${geist.variable} ${azeretMono.variable}`}>
|
<html lang="en" className={geistMono.variable}>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
<body className="relative min-h-screen overflow-x-hidden font-sans antialiased">
|
<body className="bg-background text-foreground relative min-h-screen overflow-x-hidden font-sans antialiased">
|
||||||
<TRPCReactProvider>{children}</TRPCReactProvider>
|
<TRPCReactProvider>{children}</TRPCReactProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
441
src/app/page.tsx
441
src/app/page.tsx
@@ -9,30 +9,32 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
Zap,
|
Zap,
|
||||||
Shield,
|
Shield,
|
||||||
Sparkles,
|
|
||||||
BarChart3,
|
BarChart3,
|
||||||
Clock,
|
|
||||||
Rocket,
|
Rocket,
|
||||||
Heart,
|
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Stars,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-page-gradient min-h-screen">
|
<div className="bg-background min-h-screen">
|
||||||
<AuthRedirect />
|
<AuthRedirect />
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav className="nav-sticky">
|
<nav className="bg-background border-border sticky top-0 z-50 border-b">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="flex h-14 items-center justify-between sm:h-16">
|
<div className="flex h-14 items-center justify-between sm:h-16">
|
||||||
<Logo />
|
<Logo />
|
||||||
<div className="hidden items-center space-x-6 md:flex">
|
<div className="hidden items-center space-x-6 md:flex">
|
||||||
<a href="#features" className="nav-link">
|
<a
|
||||||
|
href="#features"
|
||||||
|
className="text-muted-foreground hover:text-foreground text-sm font-medium transition-colors"
|
||||||
|
>
|
||||||
Features
|
Features
|
||||||
</a>
|
</a>
|
||||||
<a href="#pricing" className="nav-link">
|
<a
|
||||||
|
href="#pricing"
|
||||||
|
className="text-muted-foreground hover:text-foreground text-sm font-medium transition-colors"
|
||||||
|
>
|
||||||
Pricing
|
Pricing
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,14 +43,14 @@ export default function HomePage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="text-slate-700 hover:text-slate-900 dark:text-slate-200 dark:hover:text-white"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<span className="hidden sm:inline">Sign In</span>
|
<span className="hidden sm:inline">Sign In</span>
|
||||||
<span className="sm:hidden">Sign In</span>
|
<span className="sm:hidden">Sign In</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/auth/register">
|
<Link href="/auth/register">
|
||||||
<Button size="sm" className="btn-brand-primary">
|
<Button size="sm" variant="default">
|
||||||
<span className="hidden sm:inline">Get Started</span>
|
<span className="hidden sm:inline">Get Started</span>
|
||||||
<span className="sm:hidden">Start</span>
|
<span className="sm:hidden">Start</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -59,96 +61,59 @@ export default function HomePage() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="bg-hero-gradient relative overflow-hidden px-4 pt-12 pb-16 sm:pt-20">
|
<section className="bg-background relative overflow-hidden px-4 pt-12 pb-16 sm:pt-20">
|
||||||
{/* Background decoration */}
|
|
||||||
<div className="hero-overlay"></div>
|
|
||||||
<div className="hero-orb-1 animate-glow-pulse animate-pulse"></div>
|
|
||||||
<div className="hero-orb-2 animate-rainbow animate-bounce"></div>
|
|
||||||
<div className="hero-orb-3 animate-glow-pulse animate-pulse"></div>
|
|
||||||
|
|
||||||
{/* Particle Effects */}
|
|
||||||
<div className="particles-container">
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
<div className="particle"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Floating icons */}
|
|
||||||
<div className="animate-float-slow hover:animate-wiggle absolute top-20 left-10">
|
|
||||||
<Stars className="h-6 w-6 cursor-pointer text-white/20 transition-colors hover:text-white/40" />
|
|
||||||
</div>
|
|
||||||
<div className="animate-float-delayed hover:animate-wiggle absolute top-32 right-16">
|
|
||||||
<Zap className="h-8 w-8 cursor-pointer text-emerald-300/30 transition-colors hover:text-emerald-300/60" />
|
|
||||||
</div>
|
|
||||||
<div className="animate-float hover:animate-wiggle absolute bottom-32 left-20">
|
|
||||||
<Heart className="h-5 w-5 cursor-pointer text-pink-300/25 transition-colors hover:text-pink-300/50" />
|
|
||||||
</div>
|
|
||||||
<div className="relative container mx-auto text-center">
|
<div className="relative container mx-auto text-center">
|
||||||
<div className="mx-auto max-w-4xl">
|
<div className="mx-auto max-w-4xl">
|
||||||
<Badge className="badge-brand hover:animate-wiggle mb-4 animate-pulse cursor-pointer sm:mb-6">
|
<Badge className="bg-primary/10 text-primary border-primary/20 mb-4 border sm:mb-6">
|
||||||
<Sparkles className="hover:animate-rainbow mr-1 h-3 w-3 animate-spin" />
|
<Zap className="mr-1 h-3 w-3" />
|
||||||
Free Forever
|
Free Forever
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<h1 className="animate-fade-in-up animate-glitch mb-4 text-4xl font-bold tracking-tight text-white sm:mb-6 sm:text-6xl lg:text-7xl">
|
<h1 className="text-foreground mb-4 text-4xl font-bold tracking-tight sm:mb-6 sm:text-6xl lg:text-7xl">
|
||||||
Simple Invoicing for
|
Simple Invoicing for
|
||||||
<span className="animate-text-shimmer neon-glow block bg-gradient-to-r from-emerald-50 via-white to-emerald-50 bg-[length:200%_100%] bg-clip-text text-transparent">
|
<span className="text-primary block">Freelancers</span>
|
||||||
Freelancers
|
|
||||||
</span>
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="animate-fade-in-up animation-delay-300 mx-auto mb-6 max-w-2xl text-lg leading-relaxed text-emerald-50/90 sm:mb-8 sm:text-xl">
|
<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.
|
Create professional invoices, manage clients, and track payments.
|
||||||
Built for freelancers and small businesses—
|
Built for freelancers and small businesses—
|
||||||
<span className="animate-pulse font-semibold text-white">
|
<span className="text-foreground font-semibold">
|
||||||
completely free
|
completely free
|
||||||
</span>
|
</span>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="btn-group animate-fade-in-up animation-delay-500">
|
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
||||||
<Link href="/auth/register">
|
<Link href="/auth/register">
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
className="btn-brand-secondary btn-flashy group w-full transform px-6 py-3 text-base font-semibold shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl sm:w-auto sm:px-8 sm:py-4 sm:text-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"
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
<ArrowRight className="ml-2 h-4 w-4 transition-all group-hover:translate-x-1 group-hover:scale-110 group-hover:rotate-12 sm:h-5 sm:w-5" />
|
<ArrowRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="#features">
|
<Link href="#features">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="btn-flashy group w-full transform border-white/30 px-6 py-3 text-base text-white transition-all duration-300 hover:scale-105 hover:bg-white/10 hover:shadow-xl sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
|
className="group w-full px-6 py-3 text-base sm:w-auto sm:px-8 sm:py-4 sm:text-lg"
|
||||||
>
|
>
|
||||||
Learn More
|
Learn More
|
||||||
<ChevronRight className="ml-2 h-4 w-4 transition-all group-hover:translate-x-1 group-hover:scale-110 group-hover:rotate-12 sm:h-5 sm:w-5" />
|
<ChevronRight className="ml-2 h-4 w-4 transition-transform group-hover:translate-x-1 sm:h-5 sm:w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="animate-fade-in-up animation-delay-700 mt-8 flex flex-col items-center justify-center gap-2 text-sm text-emerald-50/80 sm:mt-12 sm:flex-row sm:gap-6">
|
<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",
|
"No credit card required",
|
||||||
"Setup in 2 minutes",
|
"Setup in 2 minutes",
|
||||||
"Free forever",
|
"Free forever",
|
||||||
].map((text, i) => (
|
].map((text, i) => (
|
||||||
<div
|
<div key={i} className="flex items-center gap-2">
|
||||||
key={i}
|
<Check className="text-primary h-4 w-4" />
|
||||||
className="animate-fade-in-up flex items-center gap-2"
|
|
||||||
style={{ animationDelay: `${800 + i * 100}ms` }}
|
|
||||||
>
|
|
||||||
<Check
|
|
||||||
className="h-4 w-4 animate-bounce text-emerald-100"
|
|
||||||
style={{ animationDelay: `${1000 + i * 150}ms` }}
|
|
||||||
/>
|
|
||||||
<span className="text-center">{text}</span>
|
<span className="text-center">{text}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -160,110 +125,125 @@ export default function HomePage() {
|
|||||||
{/* Features Section */}
|
{/* Features Section */}
|
||||||
<section
|
<section
|
||||||
id="features"
|
id="features"
|
||||||
className="bg-features-gradient relative overflow-hidden py-16 sm:py-24"
|
className="bg-muted/20 relative overflow-hidden py-16 sm:py-24"
|
||||||
>
|
>
|
||||||
{/* Floating background elements */}
|
|
||||||
<div className="floating-decoration-1"></div>
|
|
||||||
<div className="floating-decoration-2"></div>
|
|
||||||
<div className="relative container mx-auto px-4">
|
<div className="relative container mx-auto px-4">
|
||||||
<div className="mb-12 text-center sm:mb-16">
|
<div className="mb-12 text-center sm:mb-16">
|
||||||
<Badge className="badge-features mb-4">
|
<Badge className="bg-primary/10 text-primary border-primary/20 mb-4 border">
|
||||||
<Zap className="mr-1 h-3 w-3" />
|
<Zap className="mr-1 h-3 w-3" />
|
||||||
Features
|
Features
|
||||||
</Badge>
|
</Badge>
|
||||||
<h2 className="mb-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl lg:text-5xl dark:text-slate-100">
|
<h2 className="text-foreground mb-4 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
|
||||||
Everything you need to
|
Everything you need to
|
||||||
<span className="text-brand-gradient block">get paid</span>
|
<span className="text-primary block">get paid</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-auto max-w-2xl text-lg text-slate-600 sm:text-xl dark:text-slate-300">
|
<p className="text-muted-foreground mx-auto max-w-2xl text-lg sm:text-xl">
|
||||||
Simple, powerful features for freelancers and small businesses.
|
Simple, powerful features for freelancers and small businesses.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-6 sm:gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{/* Feature 1 */}
|
{/* Feature 1 */}
|
||||||
<Card className="card-floating interactive-card group animate-fade-in-up animate-glow-pulse transform transition-all duration-500 hover:scale-105 hover:shadow-2xl">
|
<Card className="bg-card border-border hover:border-primary/20 border transition-all">
|
||||||
<CardContent className="p-6 sm:p-8">
|
<CardContent className="p-6 sm:p-8">
|
||||||
<div className="icon-bg-brand hover:animate-wiggle mb-4 animate-bounce">
|
<div className="bg-primary/10 text-primary mb-4 inline-flex p-3">
|
||||||
<Rocket className="h-6 w-6 transition-all group-hover:scale-125 group-hover:rotate-12" />
|
<Rocket className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
|
<h3 className="text-foreground mb-3 text-xl font-bold">
|
||||||
Quick Setup
|
Quick Setup
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-slate-600 dark:text-slate-300">
|
<p className="text-muted-foreground mb-4">
|
||||||
Start creating invoices immediately. No complicated setup
|
Start creating invoices immediately. No complicated setup
|
||||||
required.
|
required.
|
||||||
</p>
|
</p>
|
||||||
<ul className="feature-list">
|
<ul className="space-y-2">
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Simple client management
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Simple client management
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Professional templates
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Professional templates
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Easy invoice sending
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Easy invoice sending
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Feature 2 */}
|
{/* Feature 2 */}
|
||||||
<Card className="card-floating interactive-card group animate-fade-in-up animation-delay-300 transform transition-all duration-500 hover:scale-105 hover:shadow-2xl">
|
<Card className="bg-card border-border hover:border-primary/20 border transition-all">
|
||||||
<CardContent className="p-6 sm:p-8">
|
<CardContent className="p-6 sm:p-8">
|
||||||
<div className="icon-bg-blue mb-4 animate-pulse">
|
<div className="bg-primary/10 text-primary mb-4 inline-flex p-3">
|
||||||
<BarChart3 className="h-6 w-6 transition-transform group-hover:scale-110" />
|
<BarChart3 className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
|
<h3 className="text-foreground mb-3 text-xl font-bold">
|
||||||
Payment Tracking
|
Payment Tracking
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-slate-600 dark:text-slate-300">
|
<p className="text-muted-foreground mb-4">
|
||||||
Keep track of invoice status and monitor payments.
|
Keep track of invoice status and monitor payments.
|
||||||
</p>
|
</p>
|
||||||
<ul className="feature-list">
|
<ul className="space-y-2">
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Invoice status tracking
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Invoice status tracking
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Payment history
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Payment history
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Overdue notifications
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Overdue notifications
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Feature 3 */}
|
{/* Feature 3 */}
|
||||||
<Card className="card-floating interactive-card group animate-fade-in-up animation-delay-500 transform transition-all duration-500 hover:scale-105 hover:shadow-2xl">
|
<Card className="bg-card border-border hover:border-primary/20 border transition-all">
|
||||||
<CardContent className="p-6 sm:p-8">
|
<CardContent className="p-6 sm:p-8">
|
||||||
<div className="icon-bg-purple animate-float mb-4">
|
<div className="bg-primary/10 text-primary mb-4 inline-flex p-3">
|
||||||
<Shield className="h-6 w-6 transition-all group-hover:scale-110 group-hover:rotate-12" />
|
<Shield className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
|
<h3 className="text-foreground mb-3 text-xl font-bold">
|
||||||
Professional Features
|
Professional Features
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-slate-600 dark:text-slate-300">
|
<p className="text-muted-foreground mb-4">
|
||||||
Professional features to help you get paid on time.
|
Professional features to help you get paid on time.
|
||||||
</p>
|
</p>
|
||||||
<ul className="feature-list">
|
<ul className="space-y-2">
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
PDF generation
|
<span className="text-muted-foreground text-sm">
|
||||||
|
PDF generation
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Custom tax rates
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Custom tax rates
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="feature-item">
|
<li className="flex items-center gap-2">
|
||||||
<Check className="feature-check" />
|
<Check className="text-primary h-4 w-4" />
|
||||||
Professional numbering
|
<span className="text-muted-foreground text-sm">
|
||||||
|
Professional numbering
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -275,223 +255,110 @@ export default function HomePage() {
|
|||||||
{/* Pricing Section */}
|
{/* Pricing Section */}
|
||||||
<section
|
<section
|
||||||
id="pricing"
|
id="pricing"
|
||||||
className="bg-features-gradient relative overflow-hidden py-16 sm:py-24"
|
className="bg-background relative overflow-hidden py-16 sm:py-24"
|
||||||
>
|
>
|
||||||
{/* Floating background elements */}
|
|
||||||
<div className="floating-decoration-1"></div>
|
|
||||||
<div className="floating-decoration-2"></div>
|
|
||||||
<div className="relative container mx-auto px-4">
|
<div className="relative container mx-auto px-4">
|
||||||
<div className="mb-12 text-center sm:mb-16">
|
<div className="mb-12 text-center sm:mb-16">
|
||||||
<h2 className="mb-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl lg:text-5xl dark:text-slate-100">
|
<h2 className="text-foreground mb-4 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
|
||||||
Simple pricing
|
Simple pricing
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-auto max-w-2xl text-lg text-slate-600 sm:text-xl dark:text-slate-300">
|
<p className="text-muted-foreground mx-auto max-w-2xl text-lg sm:text-xl">
|
||||||
Start free, stay free. No hidden fees or limits.
|
Start free, stay free. No hidden fees or limits.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto max-w-md">
|
<div className="mx-auto max-w-md">
|
||||||
<Card className="animate-fade-in-up animate-glow-pulse interactive-card hover:shadow-3xl relative transform border-2 border-emerald-500 bg-white/90 shadow-2xl backdrop-blur-sm transition-all duration-500 hover:scale-105 dark:border-emerald-400 dark:bg-slate-800/90">
|
<Card className="bg-card border-primary border-2">
|
||||||
<div className="absolute -top-4 left-1/2 -translate-x-1/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">
|
||||||
<Badge className="badge-success animate-pulse px-6 py-1">
|
Forever Free
|
||||||
Forever Free
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
<CardContent className="p-6 text-center sm:p-8">
|
<CardContent className="p-6 sm:p-8">
|
||||||
<div className="mb-6">
|
<div className="mb-6 text-center">
|
||||||
<div className="animate-text-shimmer mb-2 bg-gradient-to-r from-emerald-500 via-teal-500 to-emerald-500 bg-[length:200%_100%] bg-clip-text text-5xl font-bold text-transparent sm:text-6xl">
|
<div className="text-foreground mb-2 text-4xl font-bold sm:text-5xl">
|
||||||
$0
|
$0
|
||||||
</div>
|
</div>
|
||||||
<div className="text-slate-600 dark:text-slate-400">
|
<p className="text-muted-foreground">
|
||||||
per month, forever
|
Forever. No credit card required.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-6 space-y-3 text-left sm:mb-8 sm:space-y-4">
|
<ul className="mb-8 space-y-3">
|
||||||
{[
|
{[
|
||||||
"Unlimited invoices",
|
"Unlimited invoices",
|
||||||
"Unlimited clients",
|
"Client management",
|
||||||
"Professional templates",
|
"PDF generation",
|
||||||
"PDF export",
|
|
||||||
"Payment tracking",
|
"Payment tracking",
|
||||||
"Multi-business support",
|
"Professional templates",
|
||||||
"Line item details",
|
"Custom tax rates",
|
||||||
"Free forever",
|
"Email support",
|
||||||
].map((feature, i) => (
|
].map((feature, i) => (
|
||||||
<div
|
<li key={i} className="flex items-center gap-3">
|
||||||
key={i}
|
<Check className="text-primary h-5 w-5" />
|
||||||
className="animate-fade-in-up flex items-center gap-3"
|
<span className="text-foreground">{feature}</span>
|
||||||
style={{ animationDelay: `${i * 100}ms` }}
|
</li>
|
||||||
>
|
|
||||||
<Check
|
|
||||||
className="h-5 w-5 flex-shrink-0 animate-bounce text-emerald-500"
|
|
||||||
style={{ animationDelay: `${200 + i * 100}ms` }}
|
|
||||||
/>
|
|
||||||
<span className="text-slate-700 dark:text-slate-300">
|
|
||||||
{feature}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</ul>
|
||||||
|
|
||||||
<Link href="/auth/register">
|
<Link href="/auth/register" className="block">
|
||||||
<Button
|
<Button className="w-full" size="lg">
|
||||||
variant="brand"
|
Get Started Free
|
||||||
className="btn-flashy animate-magnetic w-full transform py-3 text-base font-semibold transition-all duration-300 hover:scale-105 hover:shadow-lg sm:text-lg"
|
<ArrowRight className="ml-2 h-4 w-4" />
|
||||||
>
|
|
||||||
Get Started ✨
|
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<p className="mt-4 text-sm text-slate-600 dark:text-slate-400">
|
|
||||||
No credit card required
|
|
||||||
</p>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Why Choose */}
|
|
||||||
<section className="bg-features-gradient relative overflow-hidden py-16 sm:py-24">
|
|
||||||
{/* Floating background elements */}
|
|
||||||
<div className="floating-decoration-1"></div>
|
|
||||||
<div className="floating-decoration-2"></div>
|
|
||||||
<div className="relative container mx-auto px-4">
|
|
||||||
<div className="mb-12 text-center sm:mb-16">
|
|
||||||
<h2 className="mb-4 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl lg:text-5xl dark:text-slate-100">
|
|
||||||
Why choose
|
|
||||||
<span className="animate-text-shimmer neon-glow animate-glitch block bg-gradient-to-r from-teal-500 via-emerald-600 to-teal-500 bg-[length:200%_100%] bg-clip-text text-transparent">
|
|
||||||
BeenVoice
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-6 sm:gap-8 md:grid-cols-3">
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="icon-bg-emerald mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl text-white shadow-lg">
|
|
||||||
<Zap className="h-6 w-6" />
|
|
||||||
</div>
|
|
||||||
<h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
|
|
||||||
Quick & Simple
|
|
||||||
</h3>
|
|
||||||
<p className="text-slate-600 dark:text-slate-300">
|
|
||||||
No learning curve. Start creating invoices in minutes.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="icon-bg-blue mb-4 inline-flex h-12 w-12 items-center justify-center rounded-xl text-white shadow-lg">
|
|
||||||
<Shield className="h-6 w-6" />
|
|
||||||
</div>
|
|
||||||
<h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
|
|
||||||
Always Free
|
|
||||||
</h3>
|
|
||||||
<p className="text-slate-600 dark:text-slate-300">
|
|
||||||
No hidden fees, no premium tiers. All features are free.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="icon-bg-purple mb-4">
|
|
||||||
<Clock className="h-6 w-6" />
|
|
||||||
</div>
|
|
||||||
<h3 className="mb-3 text-xl font-bold text-slate-900 dark:text-slate-100">
|
|
||||||
Save Time
|
|
||||||
</h3>
|
|
||||||
<p className="text-slate-600 dark:text-slate-300">
|
|
||||||
Focus on your work, not paperwork. Automated calculations and
|
|
||||||
formatting.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section className="bg-hero-gradient relative overflow-hidden py-16 sm:py-24">
|
<section className="bg-primary relative overflow-hidden py-16 sm:py-24">
|
||||||
<div className="hero-overlay"></div>
|
|
||||||
<div className="hero-orb-1"></div>
|
|
||||||
<div className="hero-orb-2"></div>
|
|
||||||
<div className="hero-orb-3"></div>
|
|
||||||
|
|
||||||
<div className="relative container mx-auto px-4 text-center">
|
<div className="relative container mx-auto px-4 text-center">
|
||||||
<div className="mx-auto max-w-3xl">
|
<h2 className="text-primary-foreground mb-4 text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl">
|
||||||
<h2 className="mb-4 text-3xl font-bold text-white sm:mb-6 sm:text-4xl lg:text-5xl">
|
Ready to get started?
|
||||||
Ready to get started?
|
</h2>
|
||||||
</h2>
|
<p className="text-primary-foreground/80 mx-auto mb-8 max-w-2xl text-lg sm:text-xl">
|
||||||
<p className="mb-6 text-lg text-emerald-50/90 sm:mb-8 sm:text-xl">
|
Join thousands of freelancers who trust beenvoice for their
|
||||||
Join thousands of freelancers already using BeenVoice. Start
|
invoicing needs.
|
||||||
today—completely free.
|
</p>
|
||||||
</p>
|
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
||||||
|
<Link href="/auth/register">
|
||||||
<div className="flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
|
<Button
|
||||||
<Link href="/auth/register">
|
variant="secondary"
|
||||||
<Button
|
size="lg"
|
||||||
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"
|
||||||
variant="secondary"
|
>
|
||||||
className="btn-brand-secondary btn-flashy group animate-glow-pulse w-full transform px-6 py-3 text-base font-semibold shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl 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" />
|
||||||
Start Free Today
|
</Button>
|
||||||
<Rocket className="ml-2 h-4 w-4 animate-bounce transition-all group-hover:translate-x-1 group-hover:scale-125 group-hover:rotate-45 sm:h-5 sm:w-5" />
|
</Link>
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex flex-col items-center justify-center gap-3 text-emerald-50/80 sm:mt-8 sm:flex-row sm:gap-6">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Heart className="h-4 w-4" />
|
|
||||||
Free forever
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Shield className="h-4 w-4" />
|
|
||||||
Secure & private
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Clock className="h-4 w-4" />
|
|
||||||
2-minute setup
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="bg-features-gradient border-t py-8 sm:py-12 dark:border-slate-700">
|
<footer className="bg-muted border-border border-t py-8">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="text-center">
|
<div className="flex flex-col items-center justify-between gap-4 sm:flex-row">
|
||||||
<Logo className="mx-auto mb-4" />
|
<div className="flex items-center gap-2">
|
||||||
<p className="mb-4 text-sm text-slate-600 sm:mb-6 sm:text-base dark:text-slate-400">
|
<Logo size="sm" />
|
||||||
Simple invoicing for freelancers. Free, forever.
|
<span className="text-muted-foreground text-sm">
|
||||||
</p>
|
© 2024 beenvoice. Built for freelancers.
|
||||||
<div className="flex flex-wrap items-center justify-center gap-4 text-sm text-slate-600 sm:gap-6 dark:text-slate-400">
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
<Link
|
<Link
|
||||||
href="/auth/signin"
|
href="/auth/signin"
|
||||||
className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
|
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
||||||
>
|
>
|
||||||
Sign In
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/auth/register"
|
href="/auth/register"
|
||||||
className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
|
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
||||||
>
|
>
|
||||||
Register
|
Get Started
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
|
||||||
href="#features"
|
|
||||||
className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
|
|
||||||
>
|
|
||||||
Features
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="#pricing"
|
|
||||||
className="transition-colors hover:text-emerald-600 dark:hover:text-emerald-400"
|
|
||||||
>
|
|
||||||
Pricing
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 border-t border-slate-200 pt-6 sm:mt-8 sm:pt-8 dark:border-slate-700">
|
|
||||||
<p className="text-sm text-slate-600 sm:text-base dark:text-slate-400">
|
|
||||||
© 2025 Sean O'Connor.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function AddressAutocomplete({
|
|||||||
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
|
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
|
||||||
/>
|
/>
|
||||||
{showSuggestions && suggestions.length > 0 && (
|
{showSuggestions && suggestions.length > 0 && (
|
||||||
<Card className="card-primary absolute z-10 mt-1 max-h-60 w-full overflow-auto">
|
<Card className="bg-card border-border border absolute z-10 mt-1 max-h-60 w-full overflow-auto">
|
||||||
<ul>
|
<ul>
|
||||||
{suggestions.map((s) => (
|
{suggestions.map((s) => (
|
||||||
<li
|
<li
|
||||||
|
|||||||
@@ -1,26 +1,72 @@
|
|||||||
import Image from "next/image";
|
"use client";
|
||||||
|
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
interface LogoProps {
|
interface LogoProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: "sm" | "md" | "lg";
|
size?: "sm" | "md" | "lg" | "xl";
|
||||||
|
animated?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Logo({ className, size = "md" }: LogoProps) {
|
export function Logo({ className, size = "md", animated = true }: LogoProps) {
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
sm: { width: 120, height: 32 },
|
sm: "text-sm",
|
||||||
md: { width: 160, height: 42 },
|
md: "text-lg",
|
||||||
lg: { width: 240, height: 64 },
|
lg: "text-2xl",
|
||||||
|
xl: "text-4xl",
|
||||||
};
|
};
|
||||||
const { width, height } = sizeClasses[size];
|
|
||||||
|
const LogoContent = () => (
|
||||||
|
<div className={cn("flex items-center", sizeClasses[size], className)}>
|
||||||
|
<span className="text-primary font-bold tracking-tight">$</span>
|
||||||
|
<span className="inline-block w-2"></span>
|
||||||
|
<span className="text-foreground font-bold tracking-tight">been</span>
|
||||||
|
<span className="text-foreground/70 font-bold tracking-tight">voice</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!animated) {
|
||||||
|
return <LogoContent />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Image
|
<motion.div
|
||||||
src="/beenvoice-logo.svg"
|
initial={{ opacity: 0 }}
|
||||||
alt="beenvoice logo"
|
animate={{ opacity: 1 }}
|
||||||
width={width}
|
transition={{ duration: 0.1, ease: "easeOut" }}
|
||||||
height={height}
|
className={cn("flex items-center", sizeClasses[size], className)}
|
||||||
className={className}
|
>
|
||||||
priority
|
<motion.span
|
||||||
/>
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.02, duration: 0.05, ease: "easeOut" }}
|
||||||
|
className="text-primary font-bold tracking-tight"
|
||||||
|
>
|
||||||
|
$
|
||||||
|
</motion.span>
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.03, duration: 0.05, ease: "easeOut" }}
|
||||||
|
className="inline-block w-2"
|
||||||
|
></motion.span>
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.04, duration: 0.05, ease: "easeOut" }}
|
||||||
|
className="text-foreground font-bold tracking-tight"
|
||||||
|
>
|
||||||
|
been
|
||||||
|
</motion.span>
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.06, duration: 0.05, ease: "easeOut" }}
|
||||||
|
className="text-foreground/70 font-bold tracking-tight"
|
||||||
|
>
|
||||||
|
voice
|
||||||
|
</motion.span>
|
||||||
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -438,10 +438,10 @@ export function CSVImportPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Global Client Selection */}
|
{/* Global Client Selection */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<Users className="text-icon-blue h-5 w-5" />
|
<Users className="text-primary h-5 w-5" />
|
||||||
Default Client
|
Default Client
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -460,7 +460,7 @@ export function CSVImportPage() {
|
|||||||
applyGlobalClient(newClientId);
|
applyGlobalClient(newClientId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-12 w-full rounded-md border px-3 py-2 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus:ring-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-12 w-full border px-3 py-2 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus:ring-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={loadingClients}
|
disabled={loadingClients}
|
||||||
>
|
>
|
||||||
<option value="">No default client (select individually)</option>
|
<option value="">No default client (select individually)</option>
|
||||||
@@ -479,10 +479,10 @@ export function CSVImportPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* File Upload Area */}
|
{/* File Upload Area */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<Upload className="text-icon-emerald h-5 w-5" />
|
<Upload className="text-primary h-5 w-5" />
|
||||||
Upload CSV Files
|
Upload CSV Files
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -498,49 +498,45 @@ export function CSVImportPage() {
|
|||||||
|
|
||||||
{/* Summary Card */}
|
{/* Summary Card */}
|
||||||
{totalFiles > 0 && (
|
{totalFiles > 0 && (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<FileText className="text-icon-emerald h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
Import Summary
|
Import Summary
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-2 gap-4 rounded-lg bg-green-50/50 p-4 md:grid-cols-4 dark:bg-green-900/10">
|
<div className="bg-primary/10 grid grid-cols-2 gap-4 p-4 md:grid-cols-4">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-primary text-2xl font-bold">
|
||||||
{totalFiles}
|
{totalFiles}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
<div className="text-muted-foreground text-sm">Files</div>
|
||||||
Files
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-primary text-2xl font-bold">
|
||||||
{totalItems}
|
{totalItems}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
<div className="text-muted-foreground text-sm">
|
||||||
Total Items
|
Total Items
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-primary text-2xl font-bold">
|
||||||
{totalAmount.toLocaleString("en-US", {
|
{totalAmount.toLocaleString("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: "USD",
|
currency: "USD",
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
<div className="text-muted-foreground text-sm">
|
||||||
Total Amount
|
Total Amount
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-2xl font-bold text-green-600">
|
<div className="text-primary text-2xl font-bold">
|
||||||
{readyFiles}/{totalFiles}
|
{readyFiles}/{totalFiles}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
<div className="text-muted-foreground text-sm">Ready</div>
|
||||||
Ready
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -551,9 +547,9 @@ export function CSVImportPage() {
|
|||||||
|
|
||||||
{/* File List */}
|
{/* File List */}
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
Uploaded Files
|
Uploaded Files
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -562,11 +558,11 @@ export function CSVImportPage() {
|
|||||||
{files.map((fileData, index) => (
|
{files.map((fileData, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="border-border bg-card rounded-lg border p-4"
|
className="border-border bg-card border p-4"
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
<div className="mb-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<FileText className="text-icon-emerald h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-foreground truncate font-medium">
|
<h3 className="text-foreground truncate font-medium">
|
||||||
{fileData.file.name}
|
{fileData.file.name}
|
||||||
@@ -593,7 +589,7 @@ export function CSVImportPage() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => removeFile(index)}
|
onClick={() => removeFile(index)}
|
||||||
className="text-red-600 hover:text-red-700"
|
className="text-destructive hover:text-destructive/80"
|
||||||
>
|
>
|
||||||
<Trash2 className="mr-1 h-4 w-4" />
|
<Trash2 className="mr-1 h-4 w-4" />
|
||||||
Remove
|
Remove
|
||||||
@@ -623,7 +619,7 @@ export function CSVImportPage() {
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
updateFileData(index, { clientId: e.target.value })
|
updateFileData(index, { clientId: e.target.value })
|
||||||
}
|
}
|
||||||
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus:ring-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
className="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-9 w-full border px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus:ring-1 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
disabled={loadingClients}
|
disabled={loadingClients}
|
||||||
>
|
>
|
||||||
<option value="">Select Client</option>
|
<option value="">Select Client</option>
|
||||||
@@ -666,20 +662,20 @@ export function CSVImportPage() {
|
|||||||
|
|
||||||
{/* Error Display */}
|
{/* Error Display */}
|
||||||
{fileData.errors.length > 0 && (
|
{fileData.errors.length > 0 && (
|
||||||
<div className="mt-4 rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/20">
|
<div className="border-destructive/20 bg-destructive/10 mt-4 border p-3">
|
||||||
<div className="mb-2 flex items-center gap-2">
|
<div className="mb-2 flex items-center gap-2">
|
||||||
<AlertCircle className="h-4 w-4 text-red-600" />
|
<AlertCircle className="text-destructive h-4 w-4" />
|
||||||
<span className="text-sm font-medium text-red-800 dark:text-red-200">
|
<span className="text-destructive text-sm font-medium">
|
||||||
Issues Found
|
Issues Found
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-1 text-sm text-red-700 dark:text-red-300">
|
<ul className="text-destructive space-y-1 text-sm">
|
||||||
{fileData.errors.map((error, errorIndex) => (
|
{fileData.errors.map((error, errorIndex) => (
|
||||||
<li
|
<li
|
||||||
key={errorIndex}
|
key={errorIndex}
|
||||||
className="flex items-start gap-2"
|
className="flex items-start gap-2"
|
||||||
>
|
>
|
||||||
<span className="text-red-600">•</span>
|
<span className="text-destructive">•</span>
|
||||||
<span>{error}</span>
|
<span>{error}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -735,10 +731,10 @@ export function CSVImportPage() {
|
|||||||
|
|
||||||
{/* Batch Actions */}
|
{/* Batch Actions */}
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<DollarSign className="text-icon-green h-5 w-5" />
|
<DollarSign className="text-primary h-5 w-5" />
|
||||||
Create Invoices
|
Create Invoices
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -762,7 +758,7 @@ export function CSVImportPage() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={processBatch}
|
onClick={processBatch}
|
||||||
disabled={readyFiles === 0 || isProcessing}
|
disabled={readyFiles === 0 || isProcessing}
|
||||||
className="btn-brand-primary"
|
variant="default"
|
||||||
>
|
>
|
||||||
{isProcessing
|
{isProcessing
|
||||||
? "Processing..."
|
? "Processing..."
|
||||||
@@ -776,10 +772,10 @@ export function CSVImportPage() {
|
|||||||
|
|
||||||
{/* Preview Modal */}
|
{/* Preview Modal */}
|
||||||
<Dialog open={previewModalOpen} onOpenChange={setPreviewModalOpen}>
|
<Dialog open={previewModalOpen} onOpenChange={setPreviewModalOpen}>
|
||||||
<DialogContent className="card-primary flex max-h-[90vh] max-w-4xl flex-col">
|
<DialogContent className="bg-card border-border border flex max-h-[90vh] max-w-4xl flex-col">
|
||||||
<DialogHeader className="flex-shrink-0">
|
<DialogHeader className="flex-shrink-0">
|
||||||
<DialogTitle className="flex items-center gap-2 text-xl font-bold text-gray-800">
|
<DialogTitle className="text-foreground flex items-center gap-2 text-xl font-bold">
|
||||||
<FileText className="h-5 w-5 text-emerald-600" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
{selectedFileIndex !== null &&
|
{selectedFileIndex !== null &&
|
||||||
files[selectedFileIndex]?.file.name}
|
files[selectedFileIndex]?.file.name}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
@@ -792,14 +788,14 @@ export function CSVImportPage() {
|
|||||||
<div className="flex min-h-0 flex-1 flex-col space-y-4">
|
<div className="flex min-h-0 flex-1 flex-col space-y-4">
|
||||||
<div className="grid flex-shrink-0 grid-cols-1 gap-4 md:grid-cols-3">
|
<div className="grid flex-shrink-0 grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileText className="h-4 w-4 text-emerald-600" />
|
<FileText className="text-primary h-4 w-4" />
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-muted-foreground text-sm">
|
||||||
{files[selectedFileIndex].parsedItems.length} items
|
{files[selectedFileIndex].parsedItems.length} items
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Clock className="h-4 w-4 text-emerald-600" />
|
<Clock className="text-primary h-4 w-4" />
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-muted-foreground text-sm">
|
||||||
{files[selectedFileIndex].parsedItems
|
{files[selectedFileIndex].parsedItems
|
||||||
.reduce((sum, item) => sum + item.hours, 0)
|
.reduce((sum, item) => sum + item.hours, 0)
|
||||||
.toFixed(1)}{" "}
|
.toFixed(1)}{" "}
|
||||||
@@ -807,8 +803,8 @@ export function CSVImportPage() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<DollarSign className="h-4 w-4 text-emerald-600" />
|
<DollarSign className="text-primary h-4 w-4" />
|
||||||
<span className="text-sm font-medium text-gray-600">
|
<span className="text-muted-foreground text-sm font-medium">
|
||||||
{files[selectedFileIndex].parsedItems
|
{files[selectedFileIndex].parsedItems
|
||||||
.reduce((sum, item) => sum + item.amount, 0)
|
.reduce((sum, item) => sum + item.amount, 0)
|
||||||
.toLocaleString("en-US", {
|
.toLocaleString("en-US", {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function ClientList() {
|
|||||||
return (
|
return (
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{Array.from({ length: 3 }, (_, i: number) => (
|
{Array.from({ length: 3 }, (_, i: number) => (
|
||||||
<Card key={i} className="card-primary">
|
<Card key={i} className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="h-4 animate-pulse rounded bg-gray-200" />
|
<div className="h-4 animate-pulse rounded bg-gray-200" />
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -91,9 +91,9 @@ export function ClientList() {
|
|||||||
|
|
||||||
if (!clients || clients.length === 0) {
|
if (!clients || clients.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader className="text-center">
|
<CardHeader className="text-center">
|
||||||
<CardTitle className="text-brand-gradient text-2xl font-bold">
|
<CardTitle className="text-primary text-2xl font-bold">
|
||||||
No Clients Yet
|
No Clients Yet
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-lg">
|
<CardDescription className="text-lg">
|
||||||
@@ -142,20 +142,20 @@ export function ClientList() {
|
|||||||
{filteredClients.map((client) => (
|
{filteredClients.map((client) => (
|
||||||
<Card
|
<Card
|
||||||
key={client.id}
|
key={client.id}
|
||||||
className="group card-primary transition-all duration-300 hover:shadow-lg"
|
className="group bg-card border-border border transition-all duration-300 hover:shadow-lg"
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center justify-between text-lg">
|
<CardTitle className="flex items-center justify-between text-lg">
|
||||||
<span className="text-accent group-hover:text-icon-emerald font-semibold transition-colors">
|
<span className="text-foreground group-hover:text-primary font-semibold transition-colors">
|
||||||
{client.name}
|
{client.name}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex space-x-1 opacity-0 transition-opacity group-hover:opacity-100">
|
<div className="flex space-x-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
<Link href={`/clients/${client.id}`}>
|
<Link href={`/dashboard/clients/${client.id}`}>
|
||||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`/clients/${client.id}/edit`}>
|
<Link href={`/dashboard/clients/${client.id}/edit`}>
|
||||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
<Button variant="ghost" size="sm" className="h-8 w-8 p-0">
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -173,25 +173,25 @@ export function ClientList() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{client.email && (
|
{client.email && (
|
||||||
<div className="text-secondary flex items-center text-sm">
|
<div className="text-muted-foreground flex items-center text-sm">
|
||||||
<div className="bg-brand-muted mr-3 rounded p-1.5">
|
<div className="bg-muted mr-3 rounded p-1.5">
|
||||||
<Mail className="text-icon-emerald h-3 w-3" />
|
<Mail className="text-muted-foreground h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
{client.email}
|
{client.email}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{client.phone && (
|
{client.phone && (
|
||||||
<div className="text-secondary flex items-center text-sm">
|
<div className="text-muted-foreground flex items-center text-sm">
|
||||||
<div className="bg-brand-muted-blue mr-3 rounded p-1.5">
|
<div className="bg-muted mr-3 rounded p-1.5">
|
||||||
<Phone className="text-icon-blue h-3 w-3" />
|
<Phone className="text-muted-foreground h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
{client.phone}
|
{client.phone}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(client.addressLine1 ?? client.city ?? client.state) && (
|
{(client.addressLine1 ?? client.city ?? client.state) && (
|
||||||
<div className="text-secondary flex items-start text-sm">
|
<div className="text-muted-foreground flex items-start text-sm">
|
||||||
<div className="bg-brand-muted-teal mt-0.5 mr-3 flex-shrink-0 rounded p-1.5">
|
<div className="bg-muted mt-0.5 mr-3 flex-shrink-0 rounded p-1.5">
|
||||||
<MapPin className="text-icon-teal h-3 w-3" />
|
<MapPin className="text-muted-foreground h-3 w-3" />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
{client.addressLine1 && <div>{client.addressLine1}</div>}
|
{client.addressLine1 && <div>{client.addressLine1}</div>}
|
||||||
@@ -213,12 +213,12 @@ export function ClientList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
<DialogContent className="card-primary">
|
<DialogContent className="bg-card border-border border">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-accent text-xl font-bold">
|
<DialogTitle className="text-foreground text-xl font-bold">
|
||||||
Delete Client
|
Delete Client
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription className="text-secondary">
|
<DialogDescription className="text-muted-foreground">
|
||||||
Are you sure you want to delete this client? This action cannot be
|
Are you sure you want to delete this client? This action cannot be
|
||||||
undone.
|
undone.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@@ -227,14 +227,14 @@ export function ClientList() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
onClick={() => setDeleteDialogOpen(false)}
|
||||||
className="text-secondary"
|
className="text-muted-foreground"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={confirmDelete}
|
onClick={confirmDelete}
|
||||||
className="bg-red-600 hover:bg-red-700"
|
className="bg-destructive hover:bg-destructive/90"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ export function CurrentOpenInvoiceCard() {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<FileText className="text-icon-emerald h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
Current Open Invoice
|
Current Open Invoice
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -49,10 +49,10 @@ export function CurrentOpenInvoiceCard() {
|
|||||||
|
|
||||||
if (!currentInvoice) {
|
if (!currentInvoice) {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<FileText className="text-icon-emerald h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
Current Open Invoice
|
Current Open Invoice
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -80,10 +80,10 @@ export function CurrentOpenInvoiceCard() {
|
|||||||
const totalAmount = currentInvoice.totalAmount;
|
const totalAmount = currentInvoice.totalAmount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="card-title-secondary">
|
<CardTitle className="text-foreground flex items-center gap-2">
|
||||||
<FileText className="text-icon-emerald h-5 w-5" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
Current Open Invoice
|
Current Open Invoice
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -91,13 +91,13 @@ export function CurrentOpenInvoiceCard() {
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Badge className="badge-secondary text-xs">
|
<Badge className="bg-secondary text-secondary-foreground text-xs">
|
||||||
{currentInvoice.invoiceNumber}
|
{currentInvoice.invoiceNumber}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge className="badge-outline text-xs">Draft</Badge>
|
<Badge className="border text-xs">Draft</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="text-icon-emerald text-sm font-medium">
|
<p className="text-primary text-sm font-medium">
|
||||||
{formatCurrency(totalAmount)}
|
{formatCurrency(totalAmount)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -215,12 +215,12 @@ export function DataTable<TData, TValue>({
|
|||||||
|
|
||||||
{/* Filter Bar Card */}
|
{/* Filter Bar Card */}
|
||||||
{(showSearch || filterableColumns.length > 0 || showColumnVisibility) && (
|
{(showSearch || filterableColumns.length > 0 || showColumnVisibility) && (
|
||||||
<Card className="card-primary py-2">
|
<Card className="bg-card border-border border py-2">
|
||||||
<CardContent className="px-3 py-0">
|
<CardContent className="px-3 py-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{showSearch && (
|
{showSearch && (
|
||||||
<div className="relative min-w-0 flex-1">
|
<div className="relative min-w-0 flex-1">
|
||||||
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
<Search className="text-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
||||||
<Input
|
<Input
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
value={globalFilter ?? ""}
|
value={globalFilter ?? ""}
|
||||||
@@ -244,7 +244,7 @@ export function DataTable<TData, TValue>({
|
|||||||
>
|
>
|
||||||
<SelectTrigger className="h-9 w-9 p-0 sm:w-[180px] sm:px-3 [&>svg]:hidden sm:[&>svg]:inline-flex">
|
<SelectTrigger className="h-9 w-9 p-0 sm:w-[180px] sm:px-3 [&>svg]:hidden sm:[&>svg]:inline-flex">
|
||||||
<div className="flex w-full items-center justify-center">
|
<div className="flex w-full items-center justify-center">
|
||||||
<Filter className="h-4 w-4 sm:hidden" />
|
<Filter className="text-foreground h-4 w-4 sm:hidden" />
|
||||||
<span className="hidden sm:inline">
|
<span className="hidden sm:inline">
|
||||||
<SelectValue placeholder={column.title} />
|
<SelectValue placeholder={column.title} />
|
||||||
</span>
|
</span>
|
||||||
@@ -272,7 +272,7 @@ export function DataTable<TData, TValue>({
|
|||||||
>
|
>
|
||||||
<X className="h-4 w-4 sm:hidden" />
|
<X className="h-4 w-4 sm:hidden" />
|
||||||
<span className="hidden sm:flex sm:items-center">
|
<span className="hidden sm:flex sm:items-center">
|
||||||
<Filter className="mr-2 h-3.5 w-3.5" />
|
<Filter className="text-foreground mr-2 h-3.5 w-3.5" />
|
||||||
Clear filters
|
Clear filters
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -315,7 +315,7 @@ export function DataTable<TData, TValue>({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Table Content Card */}
|
{/* Table Content Card */}
|
||||||
<Card className="card-primary overflow-hidden p-0">
|
<Card className="bg-card border-border overflow-hidden border p-0">
|
||||||
<div className="w-full overflow-x-auto">
|
<div className="w-full overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -400,7 +400,7 @@ export function DataTable<TData, TValue>({
|
|||||||
|
|
||||||
{/* Pagination Bar Card */}
|
{/* Pagination Bar Card */}
|
||||||
{showPagination && (
|
{showPagination && (
|
||||||
<Card className="card-primary py-2">
|
<Card className="bg-card border-border border py-2">
|
||||||
<CardContent className="px-3 py-0">
|
<CardContent className="px-3 py-0">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -561,17 +561,17 @@ export function DataTableSkeleton({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Filter bar skeleton */}
|
{/* Filter bar skeleton */}
|
||||||
<Card className="card-primary py-2">
|
<Card className="bg-card border-border border py-2">
|
||||||
<CardContent className="px-3 py-0">
|
<CardContent className="px-3 py-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="bg-muted/30 h-9 w-full flex-1 animate-pulse rounded-md sm:max-w-sm"></div>
|
<div className="bg-muted/30 h-9 w-full flex-1 animate-pulse sm:max-w-sm"></div>
|
||||||
<div className="bg-muted/30 h-9 w-24 animate-pulse rounded-md"></div>
|
<div className="bg-muted/30 h-9 w-24 animate-pulse"></div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Table skeleton */}
|
{/* Table skeleton */}
|
||||||
<Card className="card-primary overflow-hidden p-0">
|
<Card className="bg-card border-border overflow-hidden border p-0">
|
||||||
<div className="w-full overflow-x-auto">
|
<div className="w-full overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -632,7 +632,7 @@ export function DataTableSkeleton({
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Pagination skeleton */}
|
{/* Pagination skeleton */}
|
||||||
<Card className="card-primary py-2">
|
<Card className="bg-card border-border border py-2">
|
||||||
<CardContent className="px-3 py-0">
|
<CardContent className="px-3 py-0">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function SortableItem({
|
|||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={`card-secondary rounded-lg transition-colors ${
|
className={`card-secondary transition-colors ${
|
||||||
isDragging ? "opacity-50 shadow-lg" : ""
|
isDragging ? "opacity-50 shadow-lg" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -154,7 +154,7 @@ function SortableItem({
|
|||||||
|
|
||||||
{/* Amount */}
|
{/* Amount */}
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
<div className="bg-muted/30 flex h-9 items-center rounded-md border px-3 font-medium text-emerald-600">
|
<div className="bg-muted/30 text-primary flex h-9 items-center border px-3 font-medium">
|
||||||
${item.amount.toFixed(2)}
|
${item.amount.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,7 +166,7 @@ function SortableItem({
|
|||||||
onClick={() => onRemove(index)}
|
onClick={() => onRemove(index)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-9 w-9 p-0 text-red-600 hover:bg-red-50 hover:text-red-700"
|
className="text-destructive hover:bg-destructive/10 hover:text-destructive/80 h-9 w-9 p-0"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -206,7 +206,7 @@ function SortableItem({
|
|||||||
onClick={() => onRemove(index)}
|
onClick={() => onRemove(index)}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-6 w-6 p-0 text-red-600 hover:bg-red-50 hover:text-red-700"
|
className="text-destructive hover:bg-destructive/10 hover:text-destructive/80 h-6 w-6 p-0"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -266,10 +266,10 @@ function SortableItem({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Amount */}
|
{/* Amount */}
|
||||||
<div className="bg-muted/20 rounded-md border p-3">
|
<div className="bg-muted/20 border p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-muted-foreground text-sm">Total Amount:</span>
|
<span className="text-muted-foreground text-sm">Total Amount:</span>
|
||||||
<span className="font-mono text-lg font-bold text-emerald-600">
|
<span className="text-primary font-mono text-lg font-bold">
|
||||||
${item.amount.toFixed(2)}
|
${item.amount.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -362,7 +362,7 @@ export function EditableInvoiceItems({
|
|||||||
{items.map((item, _index) => (
|
{items.map((item, _index) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="card-secondary animate-pulse rounded-lg p-4"
|
className="card-secondary animate-pulse p-4"
|
||||||
>
|
>
|
||||||
{/* Desktop Skeleton */}
|
{/* Desktop Skeleton */}
|
||||||
<div className="hidden grid-cols-12 gap-3 md:grid">
|
<div className="hidden grid-cols-12 gap-3 md:grid">
|
||||||
|
|||||||
@@ -150,12 +150,12 @@ export function InvoiceList() {
|
|||||||
<CardTitle className="flex items-center justify-between">
|
<CardTitle className="flex items-center justify-between">
|
||||||
<span className="truncate">{invoice.invoiceNumber}</span>
|
<span className="truncate">{invoice.invoiceNumber}</span>
|
||||||
<div className="flex space-x-1">
|
<div className="flex space-x-1">
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}/view`}>
|
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={`/dashboard/invoices/${invoice.id}`}>
|
<Link href={`/dashboard/invoices/${invoice.id}/edit`}>
|
||||||
<Button variant="ghost" size="sm">
|
<Button variant="ghost" size="sm">
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -171,7 +171,7 @@ export function InvoiceList() {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<StatusBadge status={invoice.status as StatusType} />
|
<StatusBadge status={invoice.status as StatusType} />
|
||||||
<span className="text-icon-green text-lg font-bold">
|
<span className="text-primary text-lg font-bold">
|
||||||
{formatCurrency(invoice.totalAmount)}
|
{formatCurrency(invoice.totalAmount)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="py-12 text-center">
|
<div className="py-12 text-center">
|
||||||
<FileText className="text-muted mx-auto mb-4 h-12 w-12" />
|
<FileText className="text-muted mx-auto mb-4 h-12 w-12" />
|
||||||
<h3 className="text-accent mb-2 text-lg font-medium">
|
<h3 className="text-foreground mb-2 text-lg font-medium">
|
||||||
Invoice not found
|
Invoice not found
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-muted mb-4">
|
<p className="text-muted mb-4">
|
||||||
@@ -169,9 +169,9 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Status Alert */}
|
{/* Status Alert */}
|
||||||
{isOverdue && (
|
{isOverdue && (
|
||||||
<Card className="border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/20">
|
<Card className="border-destructive/20 bg-destructive/10">
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-4">
|
||||||
<div className="text-error flex items-center gap-2">
|
<div className="text-destructive flex items-center gap-2">
|
||||||
<AlertCircle className="h-5 w-5" />
|
<AlertCircle className="h-5 w-5" />
|
||||||
<span className="font-medium">This invoice is overdue</span>
|
<span className="font-medium">This invoice is overdue</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,13 +183,13 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="space-y-6 lg:col-span-2">
|
<div className="space-y-6 lg:col-span-2">
|
||||||
{/* Invoice Header Card */}
|
{/* Invoice Header Card */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="rounded-lg bg-emerald-100 p-2 dark:bg-emerald-900/30">
|
<div className="bg-primary/10 p-2">
|
||||||
<FileText className="h-6 w-6 text-emerald-600 dark:text-emerald-400" />
|
<FileText className="text-primary h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
@@ -228,7 +228,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
>
|
>
|
||||||
<StatusIcon className="mr-1 h-3 w-3" />
|
<StatusIcon className="mr-1 h-3 w-3" />
|
||||||
</StatusBadge>
|
</StatusBadge>
|
||||||
<div className="text-3xl font-bold text-emerald-600 dark:text-emerald-400">
|
<div className="text-primary text-3xl font-bold">
|
||||||
{formatCurrency(invoice.totalAmount)}
|
{formatCurrency(invoice.totalAmount)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -239,7 +239,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
>
|
>
|
||||||
{isExportingPDF ? (
|
{isExportingPDF ? (
|
||||||
<>
|
<>
|
||||||
<div className="mr-2 h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
<div className="mr-2 h-4 w-4 animate-spin border-2 border-white border-t-transparent" />
|
||||||
Generating PDF...
|
Generating PDF...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -255,9 +255,9 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Client Information */}
|
{/* Client Information */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-emerald-700 dark:text-emerald-400">
|
<CardTitle className="text-primary flex items-center gap-2">
|
||||||
<User className="h-5 w-5" />
|
<User className="h-5 w-5" />
|
||||||
Bill To
|
Bill To
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
@@ -318,31 +318,31 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Invoice Items */}
|
{/* Invoice Items */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-emerald-700 dark:text-emerald-400">
|
<CardTitle className="text-primary flex items-center gap-2">
|
||||||
<Clock className="h-5 w-5" />
|
<Clock className="h-5 w-5" />
|
||||||
Invoice Items
|
Invoice Items
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
<div className="border-border overflow-hidden border">
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
<thead className="bg-gray-50 dark:bg-gray-700">
|
<thead className="bg-muted">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">
|
<th className="text-muted-foreground px-4 py-3 text-left text-sm font-semibold">
|
||||||
Date
|
Date
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left text-sm font-semibold text-gray-700 dark:text-gray-300">
|
<th className="text-muted-foreground px-4 py-3 text-left text-sm font-semibold">
|
||||||
Description
|
Description
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">
|
<th className="text-muted-foreground px-4 py-3 text-right text-sm font-semibold">
|
||||||
Hours
|
Hours
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">
|
<th className="text-muted-foreground px-4 py-3 text-right text-sm font-semibold">
|
||||||
Rate
|
Rate
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-right text-sm font-semibold text-gray-700 dark:text-gray-300">
|
<th className="text-muted-foreground px-4 py-3 text-right text-sm font-semibold">
|
||||||
Amount
|
Amount
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -351,21 +351,21 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
{invoice.items?.map((item, index) => (
|
{invoice.items?.map((item, index) => (
|
||||||
<tr
|
<tr
|
||||||
key={item.id || index}
|
key={item.id || index}
|
||||||
className="border-t border-gray-100 hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700"
|
className="border-border hover:bg-muted/50 border-t"
|
||||||
>
|
>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900 dark:text-gray-300">
|
<td className="text-foreground px-4 py-3 text-sm">
|
||||||
{formatDate(item.date)}
|
{formatDate(item.date)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-sm text-gray-900 dark:text-gray-300">
|
<td className="text-foreground px-4 py-3 text-sm">
|
||||||
{item.description}
|
{item.description}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-sm text-gray-900 dark:text-gray-300">
|
<td className="text-foreground px-4 py-3 text-right text-sm">
|
||||||
{item.hours}
|
{item.hours}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-sm text-gray-900 dark:text-gray-300">
|
<td className="text-foreground px-4 py-3 text-right text-sm">
|
||||||
{formatCurrency(item.rate)}
|
{formatCurrency(item.rate)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right text-sm font-medium text-gray-900 dark:text-gray-300">
|
<td className="text-foreground px-4 py-3 text-right text-sm font-medium">
|
||||||
{formatCurrency(item.amount)}
|
{formatCurrency(item.amount)}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -378,11 +378,9 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
|
|
||||||
{/* Notes */}
|
{/* Notes */}
|
||||||
{invoice.notes && (
|
{invoice.notes && (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-emerald-700 dark:text-emerald-400">
|
<CardTitle className="text-primary">Notes</CardTitle>
|
||||||
Notes
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="whitespace-pre-wrap text-gray-700 dark:text-gray-300">
|
<p className="whitespace-pre-wrap text-gray-700 dark:text-gray-300">
|
||||||
@@ -396,18 +394,16 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Status Actions */}
|
{/* Status Actions */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-emerald-700 dark:text-emerald-400">
|
<CardTitle className="text-primary">Status Actions</CardTitle>
|
||||||
Status Actions
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{invoice.status === "draft" && (
|
{invoice.status === "draft" && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleStatusUpdate("sent")}
|
onClick={() => handleStatusUpdate("sent")}
|
||||||
disabled={updateStatus.isPending}
|
disabled={updateStatus.isPending}
|
||||||
className="w-full bg-blue-600 text-white hover:bg-blue-700"
|
className="bg-primary text-primary-foreground hover:bg-primary/90 w-full"
|
||||||
>
|
>
|
||||||
<Send className="mr-2 h-4 w-4" />
|
<Send className="mr-2 h-4 w-4" />
|
||||||
Mark as Sent
|
Mark as Sent
|
||||||
@@ -418,7 +414,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => handleStatusUpdate("paid")}
|
onClick={() => handleStatusUpdate("paid")}
|
||||||
disabled={updateStatus.isPending}
|
disabled={updateStatus.isPending}
|
||||||
className="w-full bg-green-600 text-white hover:bg-green-700"
|
className="bg-primary text-primary-foreground hover:bg-primary/90 w-full"
|
||||||
>
|
>
|
||||||
<DollarSign className="mr-2 h-4 w-4" />
|
<DollarSign className="mr-2 h-4 w-4" />
|
||||||
Mark as Paid
|
Mark as Paid
|
||||||
@@ -429,7 +425,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => handleStatusUpdate("paid")}
|
onClick={() => handleStatusUpdate("paid")}
|
||||||
disabled={updateStatus.isPending}
|
disabled={updateStatus.isPending}
|
||||||
className="w-full bg-green-600 text-white hover:bg-green-700"
|
className="bg-primary text-primary-foreground hover:bg-primary/90 w-full"
|
||||||
>
|
>
|
||||||
<DollarSign className="mr-2 h-4 w-4" />
|
<DollarSign className="mr-2 h-4 w-4" />
|
||||||
Mark as Paid
|
Mark as Paid
|
||||||
@@ -438,21 +434,17 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
|
|
||||||
{invoice.status === "paid" && (
|
{invoice.status === "paid" && (
|
||||||
<div className="py-4 text-center">
|
<div className="py-4 text-center">
|
||||||
<DollarSign className="mx-auto mb-2 h-8 w-8 text-green-600 dark:text-green-400" />
|
<DollarSign className="text-primary mx-auto mb-2 h-8 w-8" />
|
||||||
<p className="font-medium text-green-600 dark:text-green-400">
|
<p className="text-primary font-medium">Invoice Paid</p>
|
||||||
Invoice Paid
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Invoice Summary */}
|
{/* Invoice Summary */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-emerald-700 dark:text-emerald-400">
|
<CardTitle className="text-primary">Summary</CardTitle>
|
||||||
Summary
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -471,14 +463,14 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
<Separator />
|
<Separator />
|
||||||
<div className="flex justify-between text-lg font-bold">
|
<div className="flex justify-between text-lg font-bold">
|
||||||
<span className="dark:text-white">Total</span>
|
<span className="dark:text-white">Total</span>
|
||||||
<span className="text-emerald-600 dark:text-emerald-400">
|
<span className="text-primary">
|
||||||
{formatCurrency(invoice.totalAmount)}
|
{formatCurrency(invoice.totalAmount)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-gray-200 pt-4 text-center dark:border-gray-700">
|
<div className="border-border border-t pt-4 text-center">
|
||||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
<p className="text-muted-foreground text-sm">
|
||||||
{invoice.items?.length ?? 0} item
|
{invoice.items?.length ?? 0} item
|
||||||
{invoice.items?.length !== 1 ? "s" : ""}
|
{invoice.items?.length !== 1 ? "s" : ""}
|
||||||
</p>
|
</p>
|
||||||
@@ -487,17 +479,15 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Danger Zone */}
|
{/* Danger Zone */}
|
||||||
<Card className="card-primary border-red-200 dark:border-red-800">
|
<Card className="bg-card border-border border border-destructive/20">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-red-700 dark:text-red-400">
|
<CardTitle className="text-destructive">Danger Zone</CardTitle>
|
||||||
Danger Zone
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full border-red-200 text-red-700 hover:bg-red-50 dark:border-red-800 dark:text-red-400 dark:hover:bg-red-900/20"
|
className="border-destructive/20 text-destructive hover:bg-destructive/10 w-full"
|
||||||
>
|
>
|
||||||
<Trash2 className="mr-2 h-4 w-4" />
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
Delete Invoice
|
Delete Invoice
|
||||||
@@ -509,7 +499,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
|
|
||||||
{/* Delete Confirmation Dialog */}
|
{/* Delete Confirmation Dialog */}
|
||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
<DialogContent className="card-primary">
|
<DialogContent className="bg-card border-border border">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-xl font-bold text-gray-800 dark:text-white">
|
<DialogTitle className="text-xl font-bold text-gray-800 dark:text-white">
|
||||||
Delete Invoice
|
Delete Invoice
|
||||||
@@ -524,7 +514,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setDeleteDialogOpen(false)}
|
onClick={() => setDeleteDialogOpen(false)}
|
||||||
className="border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
|
className="border-border text-muted-foreground hover:bg-muted"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
@@ -532,7 +522,7 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
|||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={confirmDelete}
|
onClick={confirmDelete}
|
||||||
disabled={deleteInvoice.isPending}
|
disabled={deleteInvoice.isPending}
|
||||||
className="bg-red-600 hover:bg-red-700"
|
className="bg-destructive hover:bg-destructive/90"
|
||||||
>
|
>
|
||||||
{deleteInvoice.isPending ? "Deleting..." : "Delete Invoice"}
|
{deleteInvoice.isPending ? "Deleting..." : "Delete Invoice"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ const variantStyles = {
|
|||||||
background: "bg-muted/50",
|
background: "bg-muted/50",
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
icon: "text-status-success",
|
icon: "text-primary",
|
||||||
background: "bg-status-success-muted",
|
background: "bg-primary/10",
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
icon: "text-status-warning",
|
icon: "text-status-warning",
|
||||||
@@ -67,9 +67,7 @@ export function StatsCard({
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm font-medium",
|
"text-sm font-medium",
|
||||||
trend.isPositive
|
trend.isPositive ? "text-primary" : "text-destructive",
|
||||||
? "text-status-success"
|
|
||||||
: "text-status-error",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{trend.isPositive ? "+" : ""}
|
{trend.isPositive ? "+" : ""}
|
||||||
@@ -82,7 +80,7 @@ export function StatsCard({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{Icon && (
|
{Icon && (
|
||||||
<div className={cn("rounded-full p-3", styles.background)}>
|
<div className={cn(" p-3", styles.background)}>
|
||||||
<Icon className={cn("h-6 w-6", styles.icon)} />
|
<Icon className={cn("h-6 w-6", styles.icon)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -94,7 +92,7 @@ export function StatsCard({
|
|||||||
|
|
||||||
export function StatsCardSkeleton() {
|
export function StatsCardSkeleton() {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="animate-pulse">
|
<div className="animate-pulse">
|
||||||
<div className="bg-muted mb-2 h-4 w-1/2 rounded"></div>
|
<div className="bg-muted mb-2 h-4 w-1/2 rounded"></div>
|
||||||
|
|||||||
@@ -19,19 +19,15 @@ interface StatusBadgeProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
const statusClassMap: Record<StatusType, string> = {
|
const statusClassMap: Record<StatusType, string> = {
|
||||||
draft:
|
draft: "border-muted-foreground/40 bg-muted text-muted-foreground shadow-sm",
|
||||||
"border-slate-400 bg-slate-100/90 text-slate-800 shadow-md dark:border-slate-600 dark:bg-slate-700/90 dark:text-slate-200",
|
sent: "border-primary/40 bg-primary/10 text-primary shadow-sm",
|
||||||
sent: "border-blue-400 bg-blue-100/90 text-blue-800 shadow-md dark:border-blue-600 dark:bg-blue-700/90 dark:text-blue-200",
|
paid: "border-primary/40 bg-primary/10 text-primary shadow-sm",
|
||||||
paid: "border-green-400 bg-green-100/90 text-green-800 shadow-md dark:border-green-600 dark:bg-green-700/90 dark:text-green-200",
|
overdue: "border-destructive/40 bg-destructive/10 text-destructive shadow-sm",
|
||||||
overdue:
|
success: "border-primary/40 bg-primary/10 text-primary shadow-sm",
|
||||||
"border-red-400 bg-red-100/90 text-red-800 shadow-md dark:border-red-600 dark:bg-red-700/90 dark:text-red-200",
|
|
||||||
success:
|
|
||||||
"border-green-400 bg-green-100/90 text-green-800 shadow-md dark:border-green-600 dark:bg-green-700/90 dark:text-green-200",
|
|
||||||
warning:
|
warning:
|
||||||
"border-yellow-400 bg-yellow-100/90 text-yellow-800 shadow-md dark:border-yellow-600 dark:bg-yellow-700/90 dark:text-yellow-200",
|
"border-muted-foreground/40 bg-muted text-muted-foreground shadow-sm",
|
||||||
error:
|
error: "border-destructive/40 bg-destructive/10 text-destructive shadow-sm",
|
||||||
"border-red-400 bg-red-100/90 text-red-800 shadow-md dark:border-red-600 dark:bg-red-700/90 dark:text-red-200",
|
info: "border-primary/40 bg-primary/10 text-primary shadow-sm",
|
||||||
info: "border-blue-400 bg-blue-100/90 text-blue-800 shadow-md dark:border-blue-600 dark:bg-blue-700/90 dark:text-blue-200",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusLabelMap: Record<StatusType, string> = {
|
const statusLabelMap: Record<StatusType, string> = {
|
||||||
|
|||||||
@@ -414,7 +414,8 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="business-form"
|
form="business-form"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="btn-brand-primary shadow-md"
|
variant="default"
|
||||||
|
className="shadow-md"
|
||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
@@ -438,11 +439,11 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
{/* Main Form Container - styled like data table */}
|
{/* Main Form Container - styled like data table */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-brand-muted flex h-10 w-10 items-center justify-center rounded-lg">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
||||||
<Building className="text-brand-light h-5 w-5" />
|
<Building className="text-muted-foreground h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Basic Information</CardTitle>
|
<CardTitle>Basic Information</CardTitle>
|
||||||
@@ -565,12 +566,12 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Address */}
|
{/* Address */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-brand-muted flex h-10 w-10 items-center justify-center rounded-lg">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
||||||
<svg
|
<svg
|
||||||
className="text-brand-light h-5 w-5"
|
className="text-muted-foreground h-5 w-5"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -613,11 +614,11 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Email Configuration */}
|
{/* Email Configuration */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-brand-muted flex h-10 w-10 items-center justify-center rounded-lg">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
||||||
<Mail className="text-brand-light h-5 w-5" />
|
<Mail className="text-muted-foreground h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Email Configuration</CardTitle>
|
<CardTitle>Email Configuration</CardTitle>
|
||||||
@@ -631,7 +632,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
{/* Current Status */}
|
{/* Current Status */}
|
||||||
{mode === "edit" && (
|
{mode === "edit" && (
|
||||||
<div className="flex items-center justify-between rounded-lg bg-gray-50 p-4">
|
<div className="flex items-center justify-between bg-gray-50 p-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
Current Status:
|
Current Status:
|
||||||
@@ -639,7 +640,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
{emailConfig?.hasApiKey && emailConfig?.resendDomain ? (
|
{emailConfig?.hasApiKey && emailConfig?.resendDomain ? (
|
||||||
<Badge
|
<Badge
|
||||||
variant="default"
|
variant="default"
|
||||||
className="bg-green-100 text-green-800"
|
className="bg-primary/10 text-primary"
|
||||||
>
|
>
|
||||||
<Key className="mr-1 h-3 w-3" />
|
<Key className="mr-1 h-3 w-3" />
|
||||||
Custom Configuration Active
|
Custom Configuration Active
|
||||||
@@ -670,7 +671,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
href="https://resend.com"
|
href="https://resend.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
resend.com
|
resend.com
|
||||||
</a>
|
</a>
|
||||||
@@ -709,7 +710,9 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
? "Enter new API key to update"
|
? "Enter new API key to update"
|
||||||
: "re_..."
|
: "re_..."
|
||||||
}
|
}
|
||||||
className={errors.resendApiKey ? "border-red-500" : ""}
|
className={
|
||||||
|
errors.resendApiKey ? "border-destructive" : ""
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -726,7 +729,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{errors.resendApiKey && (
|
{errors.resendApiKey && (
|
||||||
<p className="text-sm text-red-600">
|
<p className="text-destructive text-sm">
|
||||||
{errors.resendApiKey}
|
{errors.resendApiKey}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -749,10 +752,12 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
handleInputChange("resendDomain", e.target.value)
|
handleInputChange("resendDomain", e.target.value)
|
||||||
}
|
}
|
||||||
placeholder="yourdomain.com"
|
placeholder="yourdomain.com"
|
||||||
className={errors.resendDomain ? "border-red-500" : ""}
|
className={
|
||||||
|
errors.resendDomain ? "border-destructive" : ""
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{errors.resendDomain && (
|
{errors.resendDomain && (
|
||||||
<p className="text-sm text-red-600">
|
<p className="text-destructive text-sm">
|
||||||
{errors.resendDomain}
|
{errors.resendDomain}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -779,10 +784,12 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
handleInputChange("emailFromName", e.target.value)
|
handleInputChange("emailFromName", e.target.value)
|
||||||
}
|
}
|
||||||
placeholder={formData.name || "Your Business Name"}
|
placeholder={formData.name || "Your Business Name"}
|
||||||
className={errors.emailFromName ? "border-red-500" : ""}
|
className={
|
||||||
|
errors.emailFromName ? "border-destructive" : ""
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{errors.emailFromName && (
|
{errors.emailFromName && (
|
||||||
<p className="text-sm text-red-600">
|
<p className="text-destructive text-sm">
|
||||||
{errors.emailFromName}
|
{errors.emailFromName}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -796,11 +803,11 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Settings */}
|
{/* Settings */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="bg-brand-muted flex h-10 w-10 items-center justify-center rounded-lg">
|
<div className="bg-muted flex h-10 w-10 items-center justify-center ">
|
||||||
<Star className="text-brand-light h-5 w-5" />
|
<Star className="text-muted-foreground h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Settings</CardTitle>
|
<CardTitle>Settings</CardTitle>
|
||||||
@@ -811,7 +818,7 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="bg-brand-muted border-border/40 flex items-center justify-between rounded-xl border p-4">
|
<div className="bg-muted border-border/40 flex items-center justify-between border p-4">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label
|
<Label
|
||||||
htmlFor="isDefault"
|
htmlFor="isDefault"
|
||||||
@@ -841,8 +848,8 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<FloatingActionBar
|
<FloatingActionBar
|
||||||
leftContent={
|
leftContent={
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="rounded-lg bg-blue-100 p-2 dark:bg-blue-900/30">
|
<div className="bg-primary/10 p-2">
|
||||||
<FileText className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
@@ -873,7 +880,8 @@ export function BusinessForm({ businessId, mode }: BusinessFormProps) {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isSubmitting || !isDirty}
|
disabled={isSubmitting || !isDirty}
|
||||||
className="btn-brand-primary shadow-md"
|
variant="default"
|
||||||
|
className="shadow-md"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
|
|||||||
@@ -262,7 +262,8 @@ export function ClientForm({ clientId, mode }: ClientFormProps) {
|
|||||||
type="submit"
|
type="submit"
|
||||||
form="client-form"
|
form="client-form"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="btn-brand-primary shadow-md"
|
variant="default"
|
||||||
|
className="shadow-md"
|
||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
@@ -286,11 +287,11 @@ export function ClientForm({ clientId, mode }: ClientFormProps) {
|
|||||||
{/* Main Form Container - styled like data table */}
|
{/* Main Form Container - styled like data table */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Basic Information */}
|
{/* Basic Information */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-r from-emerald-600/10 to-teal-600/10">
|
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center ">
|
||||||
<UserPlus className="h-5 w-5 text-emerald-700 dark:text-emerald-400" />
|
<UserPlus className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Basic Information</CardTitle>
|
<CardTitle>Basic Information</CardTitle>
|
||||||
@@ -367,12 +368,12 @@ export function ClientForm({ clientId, mode }: ClientFormProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Address */}
|
{/* Address */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-r from-emerald-600/10 to-teal-600/10">
|
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center ">
|
||||||
<svg
|
<svg
|
||||||
className="h-5 w-5 text-emerald-700 dark:text-emerald-400"
|
className="text-primary h-5 w-5"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -415,11 +416,11 @@ export function ClientForm({ clientId, mode }: ClientFormProps) {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Billing Information */}
|
{/* Billing Information */}
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-r from-emerald-600/10 to-teal-600/10">
|
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center ">
|
||||||
<DollarSign className="h-5 w-5 text-emerald-700 dark:text-emerald-400" />
|
<DollarSign className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Billing Information</CardTitle>
|
<CardTitle>Billing Information</CardTitle>
|
||||||
@@ -463,8 +464,8 @@ export function ClientForm({ clientId, mode }: ClientFormProps) {
|
|||||||
<FloatingActionBar
|
<FloatingActionBar
|
||||||
leftContent={
|
leftContent={
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="rounded-lg bg-blue-100 p-2 dark:bg-blue-900/30">
|
<div className="bg-primary/10 p-2">
|
||||||
<FileText className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
@@ -495,7 +496,7 @@ export function ClientForm({ clientId, mode }: ClientFormProps) {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isSubmitting || !isDirty}
|
disabled={isSubmitting || !isDirty}
|
||||||
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-md transition-all duration-200 hover:from-emerald-700 hover:to-teal-700 hover:shadow-lg"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function EmailComposer({
|
|||||||
editorProps: {
|
editorProps: {
|
||||||
attributes: {
|
attributes: {
|
||||||
class:
|
class:
|
||||||
"prose prose-sm sm:prose lg:prose-lg xl:prose-2xl mx-auto focus:outline-none min-h-[120px] p-4 border rounded-md bg-background",
|
"prose prose-sm sm:prose lg:prose-lg xl:prose-2xl mx-auto focus:outline-none min-h-[120px] p-4 border bg-background",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -133,9 +133,9 @@ export function EmailComposer({
|
|||||||
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return (
|
return (
|
||||||
<div className="bg-muted flex h-[200px] items-center justify-center rounded-md border">
|
<div className="bg-muted flex h-[200px] items-center justify-center border">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="border-primary mx-auto mb-2 h-4 w-4 animate-spin rounded-full border-2 border-t-transparent"></div>
|
<div className="border-primary mx-auto mb-2 h-4 w-4 animate-spin border-2 border-t-transparent"></div>
|
||||||
<p className="text-muted-foreground text-sm">Loading editor...</p>
|
<p className="text-muted-foreground text-sm">Loading editor...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -145,7 +145,7 @@ export function EmailComposer({
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{/* Email Headers */}
|
{/* Email Headers */}
|
||||||
<div className="bg-muted/20 space-y-4 rounded-lg border p-4">
|
<div className="bg-muted/20 space-y-4 border p-4">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="from-email" className="text-sm font-medium">
|
<Label htmlFor="from-email" className="text-sm font-medium">
|
||||||
@@ -231,7 +231,7 @@ export function EmailComposer({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Editor Toolbar */}
|
{/* Editor Toolbar */}
|
||||||
<div className="bg-muted/20 flex flex-wrap items-center gap-1 rounded-lg border p-2">
|
<div className="bg-muted/20 flex flex-wrap items-center gap-1 border p-2">
|
||||||
<MenuButton
|
<MenuButton
|
||||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
isActive={editor.isActive("bold")}
|
isActive={editor.isActive("bold")}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export function EmailPreview({
|
|||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{/* Email Headers */}
|
{/* Email Headers */}
|
||||||
<div className="bg-muted/20 mb-4 space-y-3 rounded-lg p-4">
|
<div className="bg-muted/20 mb-4 space-y-3 p-4">
|
||||||
<div className="grid grid-cols-1 gap-3 text-sm md:grid-cols-3">
|
<div className="grid grid-cols-1 gap-3 text-sm md:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-muted-foreground block text-xs font-medium">
|
<span className="text-muted-foreground block text-xs font-medium">
|
||||||
@@ -142,7 +142,7 @@ export function EmailPreview({
|
|||||||
|
|
||||||
{/* Email Content */}
|
{/* Email Content */}
|
||||||
{emailTemplate ? (
|
{emailTemplate ? (
|
||||||
<div className="rounded-lg border bg-gray-50 p-1 shadow-sm">
|
<div className=" border bg-gray-50 p-1 shadow-sm">
|
||||||
<iframe
|
<iframe
|
||||||
srcDoc={emailTemplate.html}
|
srcDoc={emailTemplate.html}
|
||||||
className="h-[700px] w-full rounded border-0"
|
className="h-[700px] w-full rounded border-0"
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ function FilePreview({
|
|||||||
const getStatusIcon = () => {
|
const getStatusIcon = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "success":
|
case "success":
|
||||||
return <CheckCircle className="h-4 w-4 text-green-600" />;
|
return <CheckCircle className="text-primary h-4 w-4" />;
|
||||||
case "error":
|
case "error":
|
||||||
return <AlertCircle className="h-4 w-4 text-red-600" />;
|
return <AlertCircle className="text-destructive h-4 w-4" />;
|
||||||
default:
|
default:
|
||||||
return <FileText className="h-4 w-4 text-gray-400" />;
|
return <FileText className="h-4 w-4 text-gray-400" />;
|
||||||
}
|
}
|
||||||
@@ -45,9 +45,9 @@ function FilePreview({
|
|||||||
const getStatusColor = () => {
|
const getStatusColor = () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "success":
|
case "success":
|
||||||
return "border-green-200 bg-green-50";
|
return "border-primary/20 bg-primary/10";
|
||||||
case "error":
|
case "error":
|
||||||
return "border-red-200 bg-red-50";
|
return "border-destructive/20 bg-destructive/10";
|
||||||
default:
|
default:
|
||||||
return "border-gray-200 bg-gray-50";
|
return "border-gray-200 bg-gray-50";
|
||||||
}
|
}
|
||||||
@@ -56,20 +56,20 @@ function FilePreview({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-between rounded-lg border p-3",
|
"flex items-center justify-between border p-3",
|
||||||
getStatusColor(),
|
getStatusColor(),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{getStatusIcon()}
|
{getStatusIcon()}
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="truncate text-sm font-medium text-gray-900">
|
<p className="text-foreground truncate text-sm font-medium">
|
||||||
{file.name}
|
{file.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-muted-foreground text-xs">
|
||||||
{(file.size / 1024 / 1024).toFixed(2)} MB
|
{(file.size / 1024 / 1024).toFixed(2)} MB
|
||||||
</p>
|
</p>
|
||||||
{error && <p className="mt-1 text-xs text-red-600">{error}</p>}
|
{error && <p className="text-destructive mt-1 text-xs">{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -152,28 +152,28 @@ export function FileUpload({
|
|||||||
<div
|
<div
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
className={cn(
|
className={cn(
|
||||||
"cursor-pointer rounded-lg border-2 border-dashed p-8 text-center transition-colors",
|
"cursor-pointer border-2 border-dashed p-8 text-center transition-colors",
|
||||||
"hover:border-emerald-400 hover:bg-emerald-50/50",
|
"hover:border-primary/40 hover:bg-primary/10",
|
||||||
isDragActive && "border-emerald-400 bg-emerald-50/50",
|
isDragActive && "border-primary/40 bg-primary/10",
|
||||||
isDragReject && "border-red-400 bg-red-50/50",
|
isDragReject && "border-destructive/40 bg-destructive/10",
|
||||||
disabled && "cursor-not-allowed opacity-50",
|
disabled && "cursor-not-allowed opacity-50",
|
||||||
"bg-white/80 backdrop-blur-sm",
|
"bg-background/80 backdrop-blur-sm",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full p-3 transition-colors",
|
" p-3 transition-colors",
|
||||||
isDragActive ? "bg-emerald-100" : "bg-gray-100",
|
isDragActive ? "bg-primary/10" : "bg-muted",
|
||||||
isDragReject && "bg-red-100",
|
isDragReject && "bg-destructive/10",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Upload
|
<Upload
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-6 w-6 transition-colors",
|
"h-6 w-6 transition-colors",
|
||||||
isDragActive ? "text-emerald-600" : "text-gray-400",
|
isDragActive ? "text-primary" : "text-muted-foreground",
|
||||||
isDragReject && "text-red-600",
|
isDragReject && "text-destructive",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -181,8 +181,8 @@ export function FileUpload({
|
|||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-lg font-medium transition-colors",
|
"text-lg font-medium transition-colors",
|
||||||
isDragActive ? "text-emerald-600" : "text-gray-900",
|
isDragActive ? "text-primary" : "text-foreground",
|
||||||
isDragReject && "text-red-600",
|
isDragReject && "text-destructive",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isDragActive
|
{isDragActive
|
||||||
@@ -222,17 +222,17 @@ export function FileUpload({
|
|||||||
|
|
||||||
{/* Error Summary */}
|
{/* Error Summary */}
|
||||||
{Object.keys(errors).length > 0 && (
|
{Object.keys(errors).length > 0 && (
|
||||||
<div className="rounded-lg border border-red-200 bg-red-50 p-3">
|
<div className="border-destructive/20 bg-destructive/10 border p-3">
|
||||||
<div className="mb-2 flex items-center gap-2">
|
<div className="mb-2 flex items-center gap-2">
|
||||||
<AlertCircle className="h-4 w-4 text-red-600" />
|
<AlertCircle className="text-destructive h-4 w-4" />
|
||||||
<span className="text-sm font-medium text-red-800">
|
<span className="text-destructive text-sm font-medium">
|
||||||
Upload Errors
|
Upload Errors
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-1 text-sm text-red-700">
|
<ul className="text-destructive space-y-1 text-sm">
|
||||||
{Object.entries(errors).map(([fileName, error]) => (
|
{Object.entries(errors).map(([fileName, error]) => (
|
||||||
<li key={fileName} className="flex items-start gap-2">
|
<li key={fileName} className="flex items-start gap-2">
|
||||||
<span className="text-red-600">•</span>
|
<span className="text-destructive">•</span>
|
||||||
<span>
|
<span>
|
||||||
<strong>{fileName}:</strong> {error}
|
<strong>{fileName}:</strong> {error}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ function InvoiceFormSkeleton() {
|
|||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
||||||
<div className="space-y-6 lg:col-span-2">
|
<div className="space-y-6 lg:col-span-2">
|
||||||
<div className="bg-muted h-96 animate-pulse rounded-lg" />
|
<div className="bg-muted h-96 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-muted h-64 animate-pulse rounded-lg" />
|
<div className="bg-muted h-64 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -306,7 +306,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
onSuccess: (invoice) => {
|
onSuccess: (invoice) => {
|
||||||
toast.success("Invoice created successfully");
|
toast.success("Invoice created successfully");
|
||||||
void utils.invoices.getAll.invalidate();
|
void utils.invoices.getAll.invalidate();
|
||||||
router.push(`/dashboard/invoices/${invoice.id}/view`);
|
router.push(`/dashboard/invoices/${invoice.id}`);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error(error.message || "Failed to create invoice");
|
toast.error(error.message || "Failed to create invoice");
|
||||||
@@ -319,7 +319,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
await utils.invoices.getAll.invalidate();
|
await utils.invoices.getAll.invalidate();
|
||||||
// The update mutation returns { success: true }, so we use the current invoiceId
|
// The update mutation returns { success: true }, so we use the current invoiceId
|
||||||
if (invoiceId && invoiceId !== "new") {
|
if (invoiceId && invoiceId !== "new") {
|
||||||
router.push(`/dashboard/invoices/${invoiceId}/view`);
|
router.push(`/dashboard/invoices/${invoiceId}`);
|
||||||
} else {
|
} else {
|
||||||
router.push("/dashboard/invoices");
|
router.push("/dashboard/invoices");
|
||||||
}
|
}
|
||||||
@@ -462,17 +462,13 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={loading || deleteInvoice.isPending}
|
disabled={loading || deleteInvoice.isPending}
|
||||||
className="text-red-700 shadow-sm hover:bg-red-50"
|
className="text-destructive hover:bg-destructive/10 shadow-sm"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 sm:mr-2" />
|
<Trash2 className="h-4 w-4 sm:mr-2" />
|
||||||
<span className="hidden sm:inline">Delete Invoice</span>
|
<span className="hidden sm:inline">Delete Invoice</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button onClick={handleSubmit} disabled={loading} variant="default">
|
||||||
onClick={handleSubmit}
|
|
||||||
disabled={loading}
|
|
||||||
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-md transition-all duration-200 hover:from-emerald-700 hover:to-teal-700 hover:shadow-lg"
|
|
||||||
>
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<Clock className="h-4 w-4 animate-spin sm:mr-2" />
|
<Clock className="h-4 w-4 animate-spin sm:mr-2" />
|
||||||
@@ -499,7 +495,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="invoice-details">
|
<TabsContent value="invoice-details">
|
||||||
<Card className="card-primary">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<FileText className="h-5 w-5" />
|
<FileText className="h-5 w-5" />
|
||||||
@@ -657,14 +653,14 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="invoice-items">
|
<TabsContent value="invoice-items">
|
||||||
<Card className="card-primary">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<DollarSign className="h-5 w-5" />
|
<DollarSign className="h-5 w-5" />
|
||||||
Invoice Items
|
Invoice Items
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-3 py-0">
|
<CardContent className="px-3 pb-4">
|
||||||
<InvoiceLineItems
|
<InvoiceLineItems
|
||||||
items={formData.items}
|
items={formData.items}
|
||||||
onAddItem={addItem}
|
onAddItem={addItem}
|
||||||
@@ -681,7 +677,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card className="card-primary sticky top-6">
|
<Card className="sticky top-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<Check className="h-5 w-5" />
|
<Check className="h-5 w-5" />
|
||||||
@@ -747,8 +743,8 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
<FloatingActionBar
|
<FloatingActionBar
|
||||||
leftContent={
|
leftContent={
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<div className="rounded-lg bg-green-100 p-2 dark:bg-green-900/30">
|
<div className="bg-primary/10 p-2">
|
||||||
<FileText className="h-5 w-5 text-green-600 dark:text-green-400" />
|
<FileText className="text-primary h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900 dark:text-gray-100">
|
<p className="font-medium text-gray-900 dark:text-gray-100">
|
||||||
@@ -769,7 +765,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={loading || deleteInvoice.isPending}
|
disabled={loading || deleteInvoice.isPending}
|
||||||
className="text-red-700 hover:bg-red-50"
|
className="text-destructive hover:bg-destructive/10"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 sm:mr-2" />
|
<Trash2 className="h-4 w-4 sm:mr-2" />
|
||||||
<span className="hidden sm:inline">Delete</span>
|
<span className="hidden sm:inline">Delete</span>
|
||||||
@@ -778,7 +774,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-md transition-all duration-200 hover:from-emerald-700 hover:to-teal-700 hover:shadow-lg"
|
variant="default"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { DatePicker } from "~/components/ui/date-picker";
|
import { DatePicker } from "~/components/ui/date-picker";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
@@ -114,11 +115,16 @@ function SortableLineItem({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<motion.div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
|
layout
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||||
className={cn(
|
className={cn(
|
||||||
"card-secondary hidden rounded-lg p-4 md:block",
|
"bg-card border-border hidden border p-4 md:block",
|
||||||
isDragging && "opacity-50",
|
isDragging && "opacity-50",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -227,7 +233,7 @@ function SortableLineItem({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onRemove(index)}
|
onClick={() => onRemove(index)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground h-8 w-8 p-0 transition-colors hover:text-red-500",
|
"text-muted-foreground hover:text-destructive h-8 w-8 p-0 transition-colors",
|
||||||
!canRemove && "cursor-not-allowed opacity-50",
|
!canRemove && "cursor-not-allowed opacity-50",
|
||||||
)}
|
)}
|
||||||
disabled={!canRemove}
|
disabled={!canRemove}
|
||||||
@@ -238,7 +244,7 @@ function SortableLineItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,8 +260,15 @@ function MobileLineItem({
|
|||||||
isLast,
|
isLast,
|
||||||
}: LineItemRowProps) {
|
}: LineItemRowProps) {
|
||||||
return (
|
return (
|
||||||
<div className="card-secondary space-y-3 rounded-lg md:hidden">
|
<motion.div
|
||||||
<div className="space-y-3 p-4">
|
layout
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -20 }}
|
||||||
|
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||||
|
className="bg-card border-border space-y-3 border md:hidden"
|
||||||
|
>
|
||||||
|
<div className="bg-secondary space-y-3 p-4">
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-muted-foreground text-xs">Description</Label>
|
<Label className="text-muted-foreground text-xs">Description</Label>
|
||||||
@@ -344,7 +357,7 @@ function MobileLineItem({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => onRemove(index)}
|
onClick={() => onRemove(index)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-muted-foreground h-8 w-8 p-0 transition-colors hover:text-red-500",
|
"text-muted-foreground hover:text-destructive h-8 w-8 p-0 transition-colors",
|
||||||
!canRemove && "cursor-not-allowed opacity-50",
|
!canRemove && "cursor-not-allowed opacity-50",
|
||||||
)}
|
)}
|
||||||
disabled={!canRemove}
|
disabled={!canRemove}
|
||||||
@@ -367,7 +380,7 @@ function MobileLineItem({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,51 +427,57 @@ export function InvoiceLineItems({
|
|||||||
items={items.map((item) => item.id)}
|
items={items.map((item) => item.id)}
|
||||||
strategy={verticalListSortingStrategy}
|
strategy={verticalListSortingStrategy}
|
||||||
>
|
>
|
||||||
<div className="space-y-2">
|
<AnimatePresence>
|
||||||
{items.map((item, index) => (
|
<div className="space-y-2">
|
||||||
<React.Fragment key={item.id}>
|
{items.map((item, index) => (
|
||||||
{/* Desktop/Tablet Card with Drag and Drop */}
|
<React.Fragment key={item.id}>
|
||||||
<SortableLineItem
|
{/* Desktop/Tablet Card with Drag and Drop */}
|
||||||
item={item}
|
<SortableLineItem
|
||||||
index={index}
|
item={item}
|
||||||
canRemove={canRemoveItems}
|
index={index}
|
||||||
onRemove={onRemoveItem}
|
canRemove={canRemoveItems}
|
||||||
onUpdate={onUpdateItem}
|
onRemove={onRemoveItem}
|
||||||
onMoveUp={onMoveUp}
|
onUpdate={onUpdateItem}
|
||||||
onMoveDown={onMoveDown}
|
onMoveUp={onMoveUp}
|
||||||
isFirst={index === 0}
|
onMoveDown={onMoveDown}
|
||||||
isLast={index === items.length - 1}
|
isFirst={index === 0}
|
||||||
/>
|
isLast={index === items.length - 1}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Mobile Card */}
|
{/* Mobile Card */}
|
||||||
<MobileLineItem
|
<MobileLineItem
|
||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
canRemove={canRemoveItems}
|
canRemove={canRemoveItems}
|
||||||
onRemove={onRemoveItem}
|
onRemove={onRemoveItem}
|
||||||
onUpdate={onUpdateItem}
|
onUpdate={onUpdateItem}
|
||||||
onMoveUp={onMoveUp}
|
onMoveUp={onMoveUp}
|
||||||
onMoveDown={onMoveDown}
|
onMoveDown={onMoveDown}
|
||||||
isFirst={index === 0}
|
isFirst={index === 0}
|
||||||
isLast={index === items.length - 1}
|
isLast={index === items.length - 1}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</AnimatePresence>
|
||||||
</SortableContext>
|
</SortableContext>
|
||||||
</DndContext>
|
</DndContext>
|
||||||
|
|
||||||
{/* Add Item Button */}
|
{/* Add Item Button */}
|
||||||
<div className="px-3 pt-3">
|
<div className="px-3 pt-3">
|
||||||
<Button
|
<div className="border-t pt-6">
|
||||||
type="button"
|
<motion.div whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }}>
|
||||||
variant="outline"
|
<Button
|
||||||
onClick={onAddItem}
|
type="button"
|
||||||
className="w-full"
|
variant="outline"
|
||||||
>
|
onClick={onAddItem}
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
className="w-full"
|
||||||
Add Another Item
|
>
|
||||||
</Button>
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Add Line Item
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ export function SendEmailDialog({
|
|||||||
<DialogContent className="flex max-h-[90vh] max-w-4xl flex-col overflow-hidden">
|
<DialogContent className="flex max-h-[90vh] max-w-4xl flex-col overflow-hidden">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Mail className="h-5 w-5 text-green-600" />
|
<Mail className="text-primary h-5 w-5" />
|
||||||
Send Invoice Email
|
Send Invoice Email
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -419,7 +419,7 @@ export function SendEmailDialog({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSendEmail}
|
onClick={handleSendEmail}
|
||||||
disabled={!canSend || isSending}
|
disabled={!canSend || isSending}
|
||||||
className="bg-green-600 hover:bg-green-700"
|
className="bg-primary hover:bg-primary/90"
|
||||||
>
|
>
|
||||||
{isSending ? (
|
{isSending ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export function FloatingActionBar({
|
|||||||
// Base positioning - always at bottom
|
// Base positioning - always at bottom
|
||||||
"fixed right-0 left-0 z-50",
|
"fixed right-0 left-0 z-50",
|
||||||
// Safe area and sidebar adjustments
|
// Safe area and sidebar adjustments
|
||||||
"pb-safe-area-inset-bottom md:left-[276px]",
|
"pb-safe-area-inset-bottom md:left-64",
|
||||||
// Conditional centering based on dock state
|
// Conditional centering based on dock state
|
||||||
isDocked ? "flex justify-center" : "",
|
isDocked ? "flex justify-center" : "",
|
||||||
// Dynamic bottom positioning
|
// Dynamic bottom positioning
|
||||||
@@ -65,7 +65,7 @@ export function FloatingActionBar({
|
|||||||
isDocked ? "mx-auto mb-0 px-4" : "mb-4 px-4",
|
isDocked ? "mx-auto mb-0 px-4" : "mb-4 px-4",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardContent className="flex items-center justify-between p-4">
|
<CardContent className="flex items-center justify-between p-4">
|
||||||
{/* Left content */}
|
{/* Left content */}
|
||||||
{leftContent && (
|
{leftContent && (
|
||||||
|
|||||||
@@ -15,60 +15,59 @@ export function Navbar() {
|
|||||||
// const { data: currentInvoice } = api.invoices.getCurrentOpen.useQuery();
|
// const { data: currentInvoice } = api.invoices.getCurrentOpen.useQuery();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="fixed top-2 right-2 left-2 z-30 md:top-3 md:right-3 md:left-3">
|
<header className="bg-navbar border-navbar-border text-navbar-foreground fixed top-0 right-0 left-0 z-30 border-b">
|
||||||
<div className="bg-background/60 border-border/40 relative rounded-2xl border shadow-lg backdrop-blur-xl backdrop-saturate-150">
|
<div className="flex h-14 items-center justify-between px-4 md:h-16 md:px-8">
|
||||||
<div className="flex h-14 items-center justify-between px-4 md:h-16 md:px-8">
|
<div className="flex items-center gap-4 md:gap-6">
|
||||||
<div className="flex items-center gap-4 md:gap-6">
|
<SidebarTrigger
|
||||||
<SidebarTrigger
|
isOpen={isMobileNavOpen}
|
||||||
isOpen={isMobileNavOpen}
|
onToggle={() => setIsMobileNavOpen(!isMobileNavOpen)}
|
||||||
onToggle={() => setIsMobileNavOpen(!isMobileNavOpen)}
|
/>
|
||||||
/>
|
<Link href="/dashboard" className="flex items-center gap-2">
|
||||||
<Link href="/dashboard" className="flex items-center gap-2">
|
<Logo size="md" />
|
||||||
<Logo size="md" />
|
</Link>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
<div className="flex items-center gap-2 md:gap-4">
|
||||||
<div className="flex items-center gap-2 md:gap-4">
|
{status === "loading" ? (
|
||||||
{status === "loading" ? (
|
<>
|
||||||
<>
|
<Skeleton className="bg-muted/20 hidden h-5 w-20 sm:inline" />
|
||||||
<Skeleton className="bg-muted/20 hidden h-5 w-20 sm:inline" />
|
<Skeleton className="bg-muted/20 h-8 w-16" />
|
||||||
<Skeleton className="bg-muted/20 h-8 w-16" />
|
</>
|
||||||
</>
|
) : session?.user ? (
|
||||||
) : session?.user ? (
|
<>
|
||||||
<>
|
<span className="text-muted-foreground hidden text-xs font-medium sm:inline md:text-sm">
|
||||||
<span className="text-muted-foreground hidden text-xs font-medium sm:inline md:text-sm">
|
{session.user.name ?? session.user.email}
|
||||||
{session.user.name ?? session.user.email}
|
</span>
|
||||||
</span>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => signOut({ callbackUrl: "/" })}
|
||||||
|
className="text-xs md:text-sm"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link href="/auth/signin">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => signOut({ callbackUrl: "/" })}
|
className="text-xs md:text-sm"
|
||||||
className="border-border/40 hover:bg-accent/50 text-xs md:text-sm"
|
|
||||||
>
|
>
|
||||||
Sign Out
|
Sign In
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</Link>
|
||||||
) : (
|
<Link href="/auth/register">
|
||||||
<>
|
<Button
|
||||||
<Link href="/auth/signin">
|
size="sm"
|
||||||
<Button
|
variant="default"
|
||||||
variant="ghost"
|
className="text-xs font-medium md:text-sm"
|
||||||
size="sm"
|
>
|
||||||
className="hover:bg-accent/50 text-xs md:text-sm"
|
Register
|
||||||
>
|
</Button>
|
||||||
Sign In
|
</Link>
|
||||||
</Button>
|
</>
|
||||||
</Link>
|
)}
|
||||||
<Link href="/auth/register">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
className="bg-gradient-to-r from-emerald-600 to-teal-600 text-xs font-medium text-white shadow-md transition-all duration-200 hover:from-emerald-700 hover:to-teal-700 hover:shadow-lg md:text-sm"
|
|
||||||
>
|
|
||||||
Register
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export function EmptyState({
|
|||||||
return (
|
return (
|
||||||
<div className={cn("py-12 text-center", className)}>
|
<div className={cn("py-12 text-center", className)}>
|
||||||
{icon && (
|
{icon && (
|
||||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted/50">
|
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center bg-muted/50">
|
||||||
{icon}
|
{icon}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -35,10 +35,9 @@ const variantStyles = {
|
|||||||
hoverBackground: "group-hover:bg-status-warning-muted/70",
|
hoverBackground: "group-hover:bg-status-warning-muted/70",
|
||||||
},
|
},
|
||||||
purple: {
|
purple: {
|
||||||
icon: "text-purple-600",
|
icon: "text-primary",
|
||||||
background: "bg-purple-100 dark:bg-purple-900/30",
|
background: "bg-secondary",
|
||||||
hoverBackground:
|
hoverBackground: "group-hover:bg-secondary/80",
|
||||||
"group-hover:bg-purple-200 dark:group-hover:bg-purple-900/50",
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ export function QuickActionCard({
|
|||||||
<CardContent className="p-6 text-center">
|
<CardContent className="p-6 text-center">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full transition-colors",
|
"mx-auto mb-3 flex h-12 w-12 items-center justify-center transition-colors",
|
||||||
styles.background,
|
styles.background,
|
||||||
styles.hoverBackground,
|
styles.hoverBackground,
|
||||||
)}
|
)}
|
||||||
@@ -99,10 +98,10 @@ export function QuickActionCard({
|
|||||||
|
|
||||||
export function QuickActionCardSkeleton() {
|
export function QuickActionCardSkeleton() {
|
||||||
return (
|
return (
|
||||||
<Card className="card-primary">
|
<Card className="bg-card border-border border">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="animate-pulse">
|
<div className="animate-pulse">
|
||||||
<div className="bg-muted mx-auto mb-3 h-12 w-12 rounded-full"></div>
|
<div className="bg-muted mx-auto mb-3 h-12 w-12 "></div>
|
||||||
<div className="bg-muted mx-auto mb-2 h-4 w-2/3 rounded"></div>
|
<div className="bg-muted mx-auto mb-2 h-4 w-2/3 rounded"></div>
|
||||||
<div className="bg-muted mx-auto h-3 w-1/2 rounded"></div>
|
<div className="bg-muted mx-auto h-3 w-1/2 rounded"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,23 +2,25 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession, signOut } from "next-auth/react";
|
||||||
import { Skeleton } from "~/components/ui/skeleton";
|
import { Skeleton } from "~/components/ui/skeleton";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { LogOut, User } from "lucide-react";
|
||||||
import { navigationConfig } from "~/lib/navigation";
|
import { navigationConfig } from "~/lib/navigation";
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="border-border/40 bg-background/60 fixed top-[5.75rem] bottom-3 left-3 z-20 hidden w-64 flex-col justify-between rounded-2xl border p-6 shadow-lg backdrop-blur-xl backdrop-saturate-150 md:flex">
|
<aside className="bg-sidebar border-sidebar-border text-sidebar-foreground fixed top-[4rem] bottom-0 left-0 z-20 hidden w-64 flex-col justify-between border-r p-6 md:flex">
|
||||||
<nav className="flex flex-col">
|
<nav className="flex flex-col">
|
||||||
{navigationConfig.map((section, sectionIndex) => (
|
{navigationConfig.map((section, sectionIndex) => (
|
||||||
<div key={section.title} className={sectionIndex > 0 ? "mt-6" : ""}>
|
<div key={section.title} className={sectionIndex > 0 ? "mt-6" : ""}>
|
||||||
{sectionIndex > 0 && (
|
{sectionIndex > 0 && (
|
||||||
<div className="border-border/40 my-4 border-t" />
|
<div className="border-border/40 my-4 border-t" />
|
||||||
)}
|
)}
|
||||||
<div className="text-muted-foreground mb-3 text-xs font-semibold tracking-wider uppercase">
|
<div className="text-sidebar-foreground/60 mb-3 text-xs font-semibold tracking-wider uppercase">
|
||||||
{section.title}
|
{section.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
@@ -27,10 +29,10 @@ export function Sidebar() {
|
|||||||
{Array.from({ length: section.links.length }).map((_, i) => (
|
{Array.from({ length: section.links.length }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="flex items-center gap-3 rounded-lg px-3 py-2.5"
|
className="flex items-center gap-3 px-3 py-2.5"
|
||||||
>
|
>
|
||||||
<Skeleton className="bg-muted/20 h-4 w-4" />
|
<Skeleton className="bg-sidebar-accent/20 h-4 w-4" />
|
||||||
<Skeleton className="bg-muted/20 h-4 w-20" />
|
<Skeleton className="bg-sidebar-accent/20 h-4 w-20" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
@@ -42,10 +44,10 @@ export function Sidebar() {
|
|||||||
key={link.href}
|
key={link.href}
|
||||||
href={link.href}
|
href={link.href}
|
||||||
aria-current={pathname === link.href ? "page" : undefined}
|
aria-current={pathname === link.href ? "page" : undefined}
|
||||||
className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200 ${
|
className={`flex items-center gap-3 px-3 py-2.5 text-sm font-medium transition-colors ${
|
||||||
pathname === link.href
|
pathname === link.href
|
||||||
? "bg-gradient-to-r from-emerald-600/10 to-teal-600/10 text-emerald-700 shadow-sm dark:from-emerald-500/20 dark:to-teal-500/20 dark:text-emerald-400"
|
? "bg-sidebar-primary text-sidebar-primary-foreground"
|
||||||
: "text-foreground hover:bg-accent/50 hover:text-accent-foreground"
|
: "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
@@ -58,6 +60,48 @@ export function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{/* User Section */}
|
||||||
|
<div className="border-sidebar-border border-t pt-4">
|
||||||
|
{status === "loading" ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Skeleton className="bg-sidebar-accent/20 h-8 w-full" />
|
||||||
|
<Skeleton className="bg-sidebar-accent/20 h-8 w-full" />
|
||||||
|
<div className="flex items-center gap-3 p-3">
|
||||||
|
<Skeleton className="bg-sidebar-accent/20 h-8 w-8 rounded-full" />
|
||||||
|
<div className="flex-1">
|
||||||
|
<Skeleton className="bg-sidebar-accent/20 mb-1 h-4 w-24" />
|
||||||
|
<Skeleton className="bg-sidebar-accent/20 h-3 w-32" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : session?.user ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => signOut()}
|
||||||
|
className="text-sidebar-foreground/60 hover:text-sidebar-foreground hover:bg-sidebar-accent w-full justify-start px-3"
|
||||||
|
>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
<div className="flex items-center gap-3 px-3 pt-2">
|
||||||
|
<div className="bg-sidebar-accent flex h-8 w-8 items-center justify-center rounded-full">
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-sidebar-foreground truncate text-sm font-medium">
|
||||||
|
{session.user.name ?? "User"}
|
||||||
|
</p>
|
||||||
|
<p className="text-sidebar-foreground/60 truncate text-xs">
|
||||||
|
{session.user.email}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { cn } from "~/lib/utils";
|
|||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ name: "Dashboard", href: "/dashboard" },
|
{ name: "Dashboard", href: "/dashboard" },
|
||||||
{ name: "Clients", href: "/clients" },
|
{ name: "Clients", href: "/dashboard/clients" },
|
||||||
{ name: "Invoices", href: "/invoices" },
|
{ name: "Invoices", href: "/dashboard/invoices" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function Navigation() {
|
export function Navigation() {
|
||||||
@@ -24,7 +24,7 @@ export function Navigation() {
|
|||||||
"transition-colors",
|
"transition-colors",
|
||||||
pathname === item.href
|
pathname === item.href
|
||||||
? "bg-primary text-primary-foreground"
|
? "bg-primary text-primary-foreground"
|
||||||
: "hover:bg-muted"
|
: "hover:bg-muted",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ export function SidebarTrigger({ isOpen, onToggle }: SidebarTriggerProps) {
|
|||||||
size="icon"
|
size="icon"
|
||||||
aria-label="Toggle navigation"
|
aria-label="Toggle navigation"
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
className="bg-card/80 h-8 w-8 shadow-lg backdrop-blur-sm md:hidden"
|
className="h-8 w-8 md:hidden"
|
||||||
>
|
>
|
||||||
{isOpen ? <X className="h-4 w-4" /> : <MenuIcon className="h-4 w-4" />}
|
{isOpen ? <X className="h-4 w-4" /> : <MenuIcon className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Mobile dropdown navigation */}
|
{/* Mobile dropdown navigation */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="bg-background/95 border-border/40 absolute top-full right-0 left-0 z-40 mt-2 rounded-2xl border shadow-2xl backdrop-blur-xl md:hidden">
|
<div className="bg-background border-border absolute top-full right-0 left-0 z-40 mt-1 border-t">
|
||||||
{/* Navigation content */}
|
{/* Navigation content */}
|
||||||
<nav className="flex flex-col p-4">
|
<nav className="flex flex-col p-4">
|
||||||
{navigationConfig.map((section, sectionIndex) => (
|
{navigationConfig.map((section, sectionIndex) => (
|
||||||
@@ -52,7 +52,7 @@ export function SidebarTrigger({ isOpen, onToggle }: SidebarTriggerProps) {
|
|||||||
(_, i) => (
|
(_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="flex items-center gap-3 rounded-lg px-3 py-2.5"
|
className="flex items-center gap-3 px-3 py-2.5"
|
||||||
>
|
>
|
||||||
<Skeleton className="bg-muted/20 h-4 w-4" />
|
<Skeleton className="bg-muted/20 h-4 w-4" />
|
||||||
<Skeleton className="bg-muted/20 h-4 w-20" />
|
<Skeleton className="bg-muted/20 h-4 w-20" />
|
||||||
@@ -70,10 +70,10 @@ export function SidebarTrigger({ isOpen, onToggle }: SidebarTriggerProps) {
|
|||||||
aria-current={
|
aria-current={
|
||||||
pathname === link.href ? "page" : undefined
|
pathname === link.href ? "page" : undefined
|
||||||
}
|
}
|
||||||
className={`flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-200 ${
|
className={`flex items-center gap-3 px-3 py-2.5 text-sm font-medium transition-colors ${
|
||||||
pathname === link.href
|
pathname === link.href
|
||||||
? "bg-gradient-to-r from-emerald-600/10 to-teal-600/10 text-emerald-700 shadow-sm dark:from-emerald-500/20 dark:to-teal-500/20 dark:text-emerald-400"
|
? "bg-primary/10 text-primary"
|
||||||
: "text-foreground hover:bg-accent/50 hover:text-accent-foreground"
|
: "text-foreground hover:bg-muted"
|
||||||
}`}
|
}`}
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ function AlertDialogContent({
|
|||||||
<AlertDialogPrimitive.Content
|
<AlertDialogPrimitive.Content
|
||||||
data-slot="alert-dialog-content"
|
data-slot="alert-dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|||||||
import { cn } from "~/lib/utils"
|
import { cn } from "~/lib/utils"
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
"relative w-full border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
"inline-flex items-center justify-center border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -23,13 +23,11 @@ const badgeVariants = cva(
|
|||||||
info: "border-transparent bg-status-info [a&]:hover:opacity-90",
|
info: "border-transparent bg-status-info [a&]:hover:opacity-90",
|
||||||
// Outlined variants for status badges
|
// Outlined variants for status badges
|
||||||
"outline-draft":
|
"outline-draft":
|
||||||
"border-gray-400 text-gray-600 dark:border-gray-500 dark:text-gray-300 bg-transparent",
|
"border-muted-foreground/40 text-muted-foreground bg-transparent",
|
||||||
"outline-sent":
|
"outline-sent": "border-primary/40 text-primary bg-transparent",
|
||||||
"border-blue-400 text-blue-600 dark:border-blue-500 dark:text-blue-300 bg-transparent",
|
"outline-paid": "border-primary/40 text-primary bg-transparent",
|
||||||
"outline-paid":
|
|
||||||
"border-green-400 text-green-600 dark:border-green-500 dark:text-green-300 bg-transparent",
|
|
||||||
"outline-overdue":
|
"outline-overdue":
|
||||||
"border-red-400 text-red-600 dark:border-red-500 dark:text-red-300 bg-transparent",
|
"border-destructive/40 text-destructive bg-transparent",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -5,28 +5,28 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors duration-150 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors duration-150 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
||||||
brand:
|
brand:
|
||||||
"bg-brand-gradient text-white shadow-lg hover:bg-brand-gradient hover:shadow-xl font-medium",
|
"bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 hover:shadow-xl font-medium",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
outline:
|
outline:
|
||||||
"border border-border/40 bg-background/60 shadow-sm backdrop-blur-sm hover:bg-accent/50 hover:text-accent-foreground hover:border-border/60 transition-colors duration-150",
|
"border border-border/40 bg-background/60 shadow-sm backdrop-blur-sm hover:bg-accent/50 hover:text-foreground-foreground hover:border-border/60 transition-colors duration-150",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
"bg-secondary text-muted-foreground-foreground shadow-xs hover:bg-secondary/80",
|
||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-foreground-foreground dark:hover:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
sm: "h-8 gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
lg: "h-10 px-6 has-[>svg]:px-4",
|
||||||
icon: "size-9",
|
icon: "size-9",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ function Calendar({
|
|||||||
defaultClassNames.dropdowns,
|
defaultClassNames.dropdowns,
|
||||||
),
|
),
|
||||||
dropdown_root: cn(
|
dropdown_root: cn(
|
||||||
"relative has-focus:border-ring border border-input shadow-sm has-focus:ring-ring/50 has-focus:ring-2 rounded-md h-8",
|
"relative has-focus:border-ring border border-input shadow-sm has-focus:ring-ring/50 has-focus:ring-2 h-8",
|
||||||
defaultClassNames.dropdown_root,
|
defaultClassNames.dropdown_root,
|
||||||
),
|
),
|
||||||
dropdown: cn(
|
dropdown: cn(
|
||||||
@@ -275,7 +275,7 @@ function CalendarDayButton({
|
|||||||
data-range-end={modifiers.range_end}
|
data-range-end={modifiers.range_end}
|
||||||
data-range-middle={modifiers.range_middle}
|
data-range-middle={modifiers.range_middle}
|
||||||
className={cn(
|
className={cn(
|
||||||
"hover:bg-accent hover:text-accent-foreground flex aspect-square size-auto h-8 w-full min-w-8 items-center justify-center rounded-md border-0 text-sm leading-none font-normal shadow-none",
|
"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.selected && "bg-primary text-primary-foreground",
|
||||||
modifiers.today && !modifiers.selected && "bg-accent font-semibold",
|
modifiers.today && !modifiers.selected && "bg-accent font-semibold",
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background/60 text-card-foreground border-border/40 flex flex-col gap-2 rounded-2xl border py-2 shadow-lg backdrop-blur-xl backdrop-saturate-150",
|
"bg-card text-card-foreground border-border/40 flex flex-col border shadow-lg backdrop-blur-xl backdrop-saturate-150",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -20,7 +20,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card-header"
|
data-slot="card-header"
|
||||||
className={cn(
|
className={cn(
|
||||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 p-3 px-5 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-5 pt-4 pb-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -65,7 +65,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-slot="card-content"
|
data-slot="card-content"
|
||||||
className={cn("px-5 pb-3", className)}
|
className={cn("px-5 pb-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ function DialogContent({
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function DropdownMenuContent({
|
|||||||
data-slot="dropdown-menu-content"
|
data-slot="dropdown-menu-content"
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border-0 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto border-0 shadow-md",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -74,7 +74,7 @@ function DropdownMenuItem({
|
|||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
data-variant={variant}
|
data-variant={variant}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-foreground-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -92,7 +92,7 @@ function DropdownMenuCheckboxItem({
|
|||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-foreground-foreground relative flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
@@ -128,7 +128,7 @@ function DropdownMenuRadioItem({
|
|||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"focus:bg-accent focus:text-foreground-foreground relative flex cursor-default items-center gap-2 py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -211,7 +211,7 @@ function DropdownMenuSubTrigger({
|
|||||||
data-slot="dropdown-menu-sub-trigger"
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
data-inset={inset}
|
data-inset={inset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
"focus:bg-accent focus:text-foreground-foreground data-[state=open]:bg-accent data-[state=open]:text-foreground-foreground flex cursor-default items-center px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -230,7 +230,7 @@ function DropdownMenuSubContent({
|
|||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border-0 shadow-lg",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden border-0 shadow-lg",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground bg-background/50 text-foreground border-border/40 flex h-10 w-full min-w-0 rounded-md border px-3 py-2 text-sm shadow-sm backdrop-blur-sm transition-all duration-200 outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground bg-background/50 text-foreground border-border/40 flex h-10 w-full min-w-0 border px-3 py-2 text-sm shadow-sm backdrop-blur-sm transition-all duration-200 outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
"focus-visible:border-ring focus-visible:bg-background/80 focus-visible:ring-ring/20 focus-visible:ring-[3px]",
|
"focus-visible:border-ring focus-visible:bg-background/80 focus-visible:ring-ring/20 focus-visible:ring-[3px]",
|
||||||
"hover:border-border/60 hover:bg-background/60",
|
"hover:border-border/60 hover:bg-background/60",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function NavigationMenuItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
const navigationMenuTriggerStyle = cva(
|
||||||
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
|
"group inline-flex h-9 w-max items-center justify-center bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-foreground-foreground focus:bg-accent focus:text-foreground-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-foreground-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
|
||||||
)
|
)
|
||||||
|
|
||||||
function NavigationMenuTrigger({
|
function NavigationMenuTrigger({
|
||||||
@@ -91,7 +91,7 @@ function NavigationMenuContent({
|
|||||||
data-slot="navigation-menu-content"
|
data-slot="navigation-menu-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
||||||
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu: group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -112,7 +112,7 @@ function NavigationMenuViewport({
|
|||||||
<NavigationMenuPrimitive.Viewport
|
<NavigationMenuPrimitive.Viewport
|
||||||
data-slot="navigation-menu-viewport"
|
data-slot="navigation-menu-viewport"
|
||||||
className={cn(
|
className={cn(
|
||||||
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -129,7 +129,7 @@ function NavigationMenuLink({
|
|||||||
<NavigationMenuPrimitive.Link
|
<NavigationMenuPrimitive.Link
|
||||||
data-slot="navigation-menu-link"
|
data-slot="navigation-menu-link"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-foreground-foreground hover:bg-accent hover:text-foreground-foreground focus:bg-accent focus:text-foreground-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export function NumberInput({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background flex h-9 items-center justify-center rounded-md text-sm shadow-none",
|
"bg-background flex h-9 items-center justify-center text-sm shadow-none",
|
||||||
widthClass,
|
widthClass,
|
||||||
disabled && "cursor-not-allowed opacity-50",
|
disabled && "cursor-not-allowed opacity-50",
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function PopoverContent({
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) border p-4 shadow-md outline-hidden",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function Progress({
|
|||||||
<ProgressPrimitive.Root
|
<ProgressPrimitive.Root
|
||||||
data-slot="progress"
|
data-slot="progress"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
"bg-primary/20 relative h-2 w-full overflow-hidden ",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ function SelectTrigger({
|
|||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
data-size={size}
|
data-size={size}
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[placeholder]:text-muted-foreground border-input bg-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex h-10 w-full items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
"data-[placeholder]:text-muted-foreground border-input bg-background text-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex h-10 w-full items-center justify-between gap-2 border px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -66,7 +66,7 @@ function SelectContent({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border-0 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto border-0 shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className,
|
className,
|
||||||
@@ -112,7 +112,7 @@ function SelectItem({
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
className={cn(
|
className={cn(
|
||||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
"focus:bg-accent focus:text-foreground-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -210,7 +210,7 @@ function SelectContentWithSearch({
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden rounded-md border-0 shadow-md",
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden border-0 shadow-md",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className,
|
className,
|
||||||
@@ -235,7 +235,7 @@ function SelectContentWithSearch({
|
|||||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
<input
|
<input
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
className="placeholder:text-muted-foreground text-foreground flex h-8 w-full rounded-md border-0 bg-transparent py-2 text-sm outline-none focus:ring-0 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
className="placeholder:text-muted-foreground text-foreground flex h-8 w-full border-0 bg-transparent py-2 text-sm outline-none focus:ring-0 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function Skeleton({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("bg-muted animate-pulse rounded-md", className)}
|
className={cn("bg-muted animate-pulse ", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -19,10 +19,10 @@ export function DashboardStatsSkeleton() {
|
|||||||
{Array.from({ length: 4 }).map((_, i) => (
|
{Array.from({ length: 4 }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-lg border border-gray-100 bg-white p-6 shadow-sm"
|
className=" border border-gray-100 bg-white p-6 shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<Skeleton className="h-9 w-9 rounded-lg" />
|
<Skeleton className="h-9 w-9 " />
|
||||||
<Skeleton className="h-4 w-12" />
|
<Skeleton className="h-4 w-12" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -41,7 +41,7 @@ export function DashboardCardsSkeleton() {
|
|||||||
{Array.from({ length: 2 }).map((_, i) => (
|
{Array.from({ length: 2 }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="rounded-lg border border-gray-100 bg-white p-6 shadow-sm"
|
className=" border border-gray-100 bg-white p-6 shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="mb-6 flex items-center justify-between">
|
<div className="mb-6 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -69,7 +69,7 @@ export function DashboardCardsSkeleton() {
|
|||||||
|
|
||||||
export function DashboardActivitySkeleton() {
|
export function DashboardActivitySkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-gray-100 bg-white p-6 shadow-sm">
|
<div className=" border border-gray-100 bg-white p-6 shadow-sm">
|
||||||
<div className="mb-6 flex items-center justify-between">
|
<div className="mb-6 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Skeleton className="h-5 w-5 rounded" />
|
<Skeleton className="h-5 w-5 rounded" />
|
||||||
@@ -81,17 +81,17 @@ export function DashboardActivitySkeleton() {
|
|||||||
{Array.from({ length: 5 }).map((_, i) => (
|
{Array.from({ length: 5 }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="flex items-center justify-between rounded-lg border border-gray-100 p-4"
|
className="flex items-center justify-between border border-gray-100 p-4"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Skeleton className="h-8 w-8 rounded-lg" />
|
<Skeleton className="h-8 w-8 " />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Skeleton className="h-4 w-24" />
|
<Skeleton className="h-4 w-24" />
|
||||||
<Skeleton className="h-3 w-32" />
|
<Skeleton className="h-3 w-32" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Skeleton className="h-6 w-16 rounded-full" />
|
<Skeleton className="h-6 w-16 " />
|
||||||
<Skeleton className="h-4 w-16" />
|
<Skeleton className="h-4 w-16" />
|
||||||
<Skeleton className="h-8 w-8 rounded" />
|
<Skeleton className="h-8 w-8 rounded" />
|
||||||
</div>
|
</div>
|
||||||
@@ -115,14 +115,14 @@ export function DashboardHeroSkeleton() {
|
|||||||
|
|
||||||
export function QuickActionsSkeleton() {
|
export function QuickActionsSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-gray-100 bg-white p-6 shadow-sm">
|
<div className=" border border-gray-100 bg-white p-6 shadow-sm">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
<Skeleton className="h-5 w-5 rounded" />
|
<Skeleton className="h-5 w-5 rounded" />
|
||||||
<Skeleton className="h-6 w-32" />
|
<Skeleton className="h-6 w-32" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<div key={i} className="rounded-lg border border-gray-200 p-4">
|
<div key={i} className=" border border-gray-200 p-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Skeleton className="h-5 w-5" />
|
<Skeleton className="h-5 w-5" />
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ function Switch({
|
|||||||
<SwitchPrimitive.Root
|
<SwitchPrimitive.Root
|
||||||
data-slot="switch"
|
data-slot="switch"
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-emerald-600",
|
"peer data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 data-[state=checked]:bg-primary inline-flex h-5 w-9 shrink-0 items-center border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -21,7 +21,7 @@ function Switch({
|
|||||||
<SwitchPrimitive.Thumb
|
<SwitchPrimitive.Thumb
|
||||||
data-slot="switch-thumb"
|
data-slot="switch-thumb"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=checked]:bg-white data-[state=unchecked]:translate-x-0",
|
"bg-background pointer-events-none block size-4 ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=checked]:bg-white data-[state=unchecked]:translate-x-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</SwitchPrimitive.Root>
|
</SwitchPrimitive.Root>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function TabsList({
|
|||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
data-slot="tabs-list"
|
data-slot="tabs-list"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center p-[3px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -42,7 +42,7 @@ function TabsTrigger({
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
data-slot="tabs-trigger"
|
data-slot="tabs-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|||||||
<textarea
|
<textarea
|
||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive bg-background text-foreground flex field-sizing-content min-h-16 w-full resize-y rounded-md border px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive bg-background text-foreground flex field-sizing-content min-h-16 w-full resize-y border px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
// Copied from shadcn/ui documentation
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { Toaster as Sonner } from "sonner"
|
|
||||||
|
|
||||||
export function Toaster() {
|
|
||||||
return <Sonner richColors position="top-center" />
|
|
||||||
}
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { generateInvoiceEmailTemplate } from "./invoice-email";
|
export { generateInvoiceEmailTemplate } from "./invoice-email";
|
||||||
|
export { generatePasswordResetEmailTemplate } from "./password-reset-email";
|
||||||
|
|||||||
@@ -81,17 +81,17 @@ export const statusConfig = {
|
|||||||
},
|
},
|
||||||
sent: {
|
sent: {
|
||||||
label: "Sent",
|
label: "Sent",
|
||||||
color: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
|
color: "bg-primary/10 text-primary",
|
||||||
description: "Invoice sent to client",
|
description: "Invoice sent to client",
|
||||||
},
|
},
|
||||||
paid: {
|
paid: {
|
||||||
label: "Paid",
|
label: "Paid",
|
||||||
color: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300",
|
color: "bg-primary/10 text-primary",
|
||||||
description: "Payment received",
|
description: "Payment received",
|
||||||
},
|
},
|
||||||
overdue: {
|
overdue: {
|
||||||
label: "Overdue",
|
label: "Overdue",
|
||||||
color: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
|
color: "bg-destructive/10 text-destructive",
|
||||||
description: "Payment is overdue",
|
description: "Payment is overdue",
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export const users = createTable("user", (d) => ({
|
|||||||
password: d.varchar({ length: 255 }),
|
password: d.varchar({ length: 255 }),
|
||||||
emailVerified: d.timestamp().default(sql`CURRENT_TIMESTAMP`),
|
emailVerified: d.timestamp().default(sql`CURRENT_TIMESTAMP`),
|
||||||
image: d.varchar({ length: 255 }),
|
image: d.varchar({ length: 255 }),
|
||||||
|
resetToken: d.varchar({ length: 255 }),
|
||||||
|
resetTokenExpiry: d.timestamp(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const usersRelations = relations(users, ({ many }) => ({
|
export const usersRelations = relations(users, ({ many }) => ({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,72 +14,14 @@ export default {
|
|||||||
xs: "475px",
|
xs: "475px",
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: ["var(--font-geist-mono)", "monospace"],
|
||||||
"var(--font-geist-sans)",
|
mono: ["var(--font-geist-mono)", "monospace"],
|
||||||
"ui-sans-serif",
|
serif: ["var(--font-geist-mono)", "monospace"],
|
||||||
"system-ui",
|
|
||||||
"sans-serif",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
navbar: "var(--navbar)",
|
||||||
input: "hsl(var(--input))",
|
"navbar-foreground": "var(--navbar-foreground)",
|
||||||
ring: "hsl(var(--ring))",
|
"navbar-border": "var(--navbar-border)",
|
||||||
background: "hsl(var(--background))",
|
|
||||||
foreground: "hsl(var(--foreground))",
|
|
||||||
primary: {
|
|
||||||
DEFAULT: "hsl(var(--primary))",
|
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
|
||||||
},
|
|
||||||
secondary: {
|
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
|
||||||
},
|
|
||||||
destructive: {
|
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
|
||||||
},
|
|
||||||
muted: {
|
|
||||||
DEFAULT: "hsl(var(--muted))",
|
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
|
||||||
},
|
|
||||||
accent: {
|
|
||||||
DEFAULT: "hsl(var(--accent))",
|
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
|
||||||
},
|
|
||||||
popover: {
|
|
||||||
DEFAULT: "hsl(var(--popover))",
|
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
DEFAULT: "hsl(var(--card))",
|
|
||||||
foreground: "hsl(var(--card-foreground))",
|
|
||||||
},
|
|
||||||
brand: {
|
|
||||||
primary: "hsl(var(--brand-primary))",
|
|
||||||
"primary-hover": "hsl(var(--brand-primary-hover))",
|
|
||||||
secondary: "hsl(var(--brand-secondary))",
|
|
||||||
"secondary-hover": "hsl(var(--brand-secondary-hover))",
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
success: "hsl(var(--status-success))",
|
|
||||||
"success-foreground": "hsl(var(--status-success-foreground))",
|
|
||||||
"success-muted": "hsl(var(--status-success-muted))",
|
|
||||||
warning: "hsl(var(--status-warning))",
|
|
||||||
"warning-foreground": "hsl(var(--status-warning-foreground))",
|
|
||||||
"warning-muted": "hsl(var(--status-warning-muted))",
|
|
||||||
error: "hsl(var(--status-error))",
|
|
||||||
"error-foreground": "hsl(var(--status-error-foreground))",
|
|
||||||
"error-muted": "hsl(var(--status-error-muted))",
|
|
||||||
info: "hsl(var(--status-info))",
|
|
||||||
"info-foreground": "hsl(var(--status-info-foreground))",
|
|
||||||
"info-muted": "hsl(var(--status-info-muted))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
lg: "var(--radius)",
|
|
||||||
md: "calc(var(--radius) - 2px)",
|
|
||||||
sm: "calc(var(--radius) - 4px)",
|
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"accordion-down": {
|
"accordion-down": {
|
||||||
|
|||||||
Reference in New Issue
Block a user