diff --git a/package-lock.json b/package-lock.json index 62cb8a1..12fe99f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^5.2.2", - "@tanstack/react-query": "^5.100.8", + "@tanstack/react-query": "^5.100.9", + "@tauri-apps/api": "^2.11.0", + "@tauri-apps/plugin-opener": "^2.5.4", "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", "class-variance-authority": "^0.7.1", @@ -17,23 +19,24 @@ "date-fns": "^4.1.0", "lucide-react": "^0.577.0", "markdown-to-jsx": "^9.7.16", + "next-themes": "^0.4.6", "radix-ui": "^1.4.3", - "react": "^19.2.5", + "react": "19.2.6", "react-day-picker": "^9.14.0", - "react-dom": "^19.2.5", - "react-hook-form": "^7.73.1", + "react-dom": "19.2.6", + "react-hook-form": "^7.75.0", "react-icons": "^5.6.0", - "react-router": "^7.14.2", - "react-router-dom": "^7.14.2", + "react-router": "^7.15.0", + "react-router-dom": "^7.15.0", "recharts": "^3.8.1", "tailwind-merge": "^3.5.0", - "zod": "^4.3.6" + "zod": "^4.4.3" }, "devDependencies": { "@eslint/js": "^9.39.4", "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.2.4", - "@tauri-apps/cli": "^2.10.1", + "@tailwindcss/vite": "^4.3.0", + "@tauri-apps/cli": "^2.11.1", "@types/markdown-to-jsx": "^7.0.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", @@ -42,14 +45,14 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.5.0", + "globals": "^17.6.0", "prettier": "^3.8.3", "shadcn": "^3.8.5", - "tailwindcss": "^4.2.4", + "tailwindcss": "^4.3.0", "tw-animate-css": "^1.4.0", "typescript": "~5.8.3", - "typescript-eslint": "^8.59.0", - "vite": "^7.3.2" + "typescript-eslint": "^8.59.2", + "vite": "^7.3.3" } }, "node_modules/@antfu/ni": { @@ -3720,49 +3723,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", - "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", + "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.4" + "tailwindcss": "4.3.0" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", - "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-x64": "4.2.4", - "@tailwindcss/oxide-freebsd-x64": "4.2.4", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-x64-musl": "4.2.4", - "@tailwindcss/oxide-wasm32-wasi": "4.2.4", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", - "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", "cpu": [ "arm64" ], @@ -3777,9 +3780,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", - "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", "cpu": [ "arm64" ], @@ -3794,9 +3797,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", - "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", "cpu": [ "x64" ], @@ -3811,9 +3814,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", - "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", "cpu": [ "x64" ], @@ -3828,9 +3831,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", - "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", "cpu": [ "arm" ], @@ -3845,13 +3848,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", - "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3862,13 +3868,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", - "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3879,13 +3888,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", - "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3896,13 +3908,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", - "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3913,9 +3928,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", - "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3931,10 +3946,10 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, @@ -3943,9 +3958,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", - "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", "cpu": [ "arm64" ], @@ -3960,9 +3975,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", - "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", "cpu": [ "x64" ], @@ -3990,15 +4005,15 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz", - "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.2.4", - "@tailwindcss/oxide": "4.2.4", - "tailwindcss": "4.2.4" + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" @@ -4257,6 +4272,15 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/plugin-opener": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz", + "integrity": "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.11.0" + } + }, "node_modules/@tauri-apps/plugin-process": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz", @@ -5865,9 +5889,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", - "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz", + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8095,6 +8119,16 @@ "node": ">= 0.6" } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -8772,9 +8806,9 @@ } }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8803,15 +8837,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.5" + "react": "^19.2.6" } }, "node_modules/react-hook-form": { @@ -9680,9 +9714,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", - "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", "dev": true, "license": "MIT" }, @@ -10119,9 +10153,9 @@ } }, "node_modules/vite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", - "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 02af832..aa0bdb4 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "markdown-to-jsx": "^9.7.16", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", - "react": "^19.2.6", + "react": "19.2.6", "react-day-picker": "^9.14.0", - "react-dom": "^19.2.6", + "react-dom": "19.2.6", "react-hook-form": "^7.75.0", "react-icons": "^5.6.0", "react-router": "^7.15.0", diff --git a/src/App.css b/src/App.css index 7fa4e2e..6610d51 100644 --- a/src/App.css +++ b/src/App.css @@ -1,9 +1,9 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); + @import "tailwindcss"; @import "tw-animate-css"; @import "shadcn/tailwind.css"; -@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); - @custom-variant dark (&:is(.dark *)); @plugin "@tailwindcss/typography"; @@ -11,6 +11,7 @@ html { color-scheme: light; } + html.dark { color-scheme: dark; } @@ -91,7 +92,9 @@ body { --color-cd-primary-subtle: var(--cd-primary-subtle); --color-cd-primary-text: var(--cd-primary-text); --color-cd-secondary: var(--cd-secondary); + --color-cd-secondary-subtle: var(--cd-secondary-subtle); --color-cd-accent: var(--cd-accent); + --color-cd-accent-subtle: var(--cd-accent-subtle); --color-cd-success: var(--cd-success); --color-cd-success-subtle: var(--cd-success-subtle); --color-cd-warning: var(--cd-warning); @@ -143,17 +146,19 @@ body { --cd-surface: #ffffff; --cd-surface-2: #f1f5f9; --cd-surface-3: #eef2f7; - --cd-border: #e2e8f0; - --cd-border-subtle: #f1f5f9; + --cd-border: #d8e1ee; + --cd-border-subtle: #e9eef6; --cd-text: #1e293b; - --cd-text-2: #64748b; - --cd-text-muted: #94a3b8; + --cd-text-2: #53657d; + --cd-text-muted: #8392a8; --cd-primary: #2563eb; --cd-primary-hover: #1d4ed8; --cd-primary-subtle: #eff6ff; --cd-primary-text: #1d4ed8; --cd-secondary: #9333ea; + --cd-secondary-subtle: #f5f3ff; --cd-accent: #06b6d4; + --cd-accent-subtle: #ecfeff; --cd-success: #16a34a; --cd-success-subtle: #f0fdf4; --cd-warning: #ca8a04; @@ -201,12 +206,12 @@ body { --sidebar-ring: #3b82f6; /* CommDesk design tokens – dark */ - --cd-bg: #0f1320; - --cd-surface: #1a1f2e; - --cd-surface-2: #151929; - --cd-surface-3: #1e293b; - --cd-border: rgba(255, 255, 255, 0.08); - --cd-border-subtle: rgba(255, 255, 255, 0.04); + --cd-bg: #0b0d12; + --cd-surface: #11151d; + --cd-surface-2: #171c25; + --cd-surface-3: #202632; + --cd-border: rgba(255, 255, 255, 0.1); + --cd-border-subtle: rgba(255, 255, 255, 0.065); --cd-text: #f8fafc; --cd-text-2: #94a3b8; --cd-text-muted: #64748b; @@ -215,14 +220,16 @@ body { --cd-primary-subtle: rgba(59, 130, 246, 0.12); --cd-primary-text: #93c5fd; --cd-secondary: #a855f7; + --cd-secondary-subtle: rgba(168, 85, 247, 0.12); --cd-accent: #22d3ee; + --cd-accent-subtle: rgba(34, 211, 238, 0.12); --cd-success: #4ade80; --cd-success-subtle: rgba(74, 222, 128, 0.1); --cd-warning: #facc15; --cd-warning-subtle: rgba(250, 204, 21, 0.1); --cd-danger: #f87171; --cd-danger-subtle: rgba(248, 113, 113, 0.1); - --cd-hover: rgba(255, 255, 255, 0.05); + --cd-hover: rgba(255, 255, 255, 0.045); --cd-shadow: rgba(0, 0, 0, 0.3); --cd-shadow-md: rgba(0, 0, 0, 0.4); } @@ -236,6 +243,7 @@ body { border-color 0.2s ease, color 0.15s ease; } + body { @apply bg-background text-foreground; } @@ -243,6 +251,7 @@ body { /* ─── CommDesk utility classes ───────────────────────────────────────────── */ @layer utilities { + /* Cards */ .cd-card { background-color: var(--cd-surface); @@ -254,9 +263,11 @@ body { box-shadow 0.2s ease, transform 0.2s ease; } + .cd-card:hover { box-shadow: 0 4px 12px var(--cd-shadow-md); } + .cd-card-hover:hover { transform: translateY(-2px); } @@ -285,22 +296,27 @@ body { font-weight: 500; line-height: 1.4; } + .cd-badge-success { background-color: var(--cd-success-subtle); color: var(--cd-success); } + .cd-badge-warning { background-color: var(--cd-warning-subtle); color: var(--cd-warning); } + .cd-badge-danger { background-color: var(--cd-danger-subtle); color: var(--cd-danger); } + .cd-badge-primary { background-color: var(--cd-primary-subtle); color: var(--cd-primary-text); } + .cd-badge-neutral { background-color: var(--cd-surface-2); color: var(--cd-text-2); @@ -320,41 +336,50 @@ body { transition: all 0.15s ease; border: 1px solid transparent; } + .cd-btn:disabled { opacity: 0.5; cursor: not-allowed; } + .cd-btn-primary { background-color: var(--cd-primary); color: #ffffff; border-color: var(--cd-primary); } + .cd-btn-primary:hover:not(:disabled) { background-color: var(--cd-primary-hover); border-color: var(--cd-primary-hover); } + .cd-btn-secondary { background-color: var(--cd-surface-2); color: var(--cd-text); border-color: var(--cd-border); } + .cd-btn-secondary:hover:not(:disabled) { background-color: var(--cd-hover); } + .cd-btn-ghost { background-color: transparent; color: var(--cd-text-2); border-color: transparent; } + .cd-btn-ghost:hover:not(:disabled) { background-color: var(--cd-hover); color: var(--cd-text); } + .cd-btn-danger { background-color: var(--cd-danger-subtle); color: var(--cd-danger); border-color: var(--cd-danger-subtle); } + .cd-btn-danger:hover:not(:disabled) { background-color: var(--cd-danger); color: #ffffff; @@ -374,13 +399,16 @@ body { border-color 0.15s ease, box-shadow 0.15s ease; } + .cd-input::placeholder { color: var(--cd-text-muted); } + .cd-input:focus { border-color: var(--cd-primary); box-shadow: 0 0 0 3px var(--cd-primary-subtle); } + .cd-input:disabled { opacity: 0.5; cursor: not-allowed; @@ -391,9 +419,11 @@ body { width: 100%; border-collapse: collapse; } + .cd-table thead { background-color: var(--cd-surface-2); } + .cd-table th { padding: 0.75rem 1.25rem; text-align: left; @@ -404,15 +434,18 @@ body { letter-spacing: 0.05em; border-bottom: 1px solid var(--cd-border); } + .cd-table td { padding: 0.875rem 1.25rem; font-size: 0.875rem; color: var(--cd-text); border-bottom: 1px solid var(--cd-border-subtle); } + .cd-table tbody tr:hover { background-color: var(--cd-hover); } + .cd-table tbody tr:last-child td { border-bottom: none; } @@ -436,10 +469,12 @@ body { transition: all 0.15s ease; text-decoration: none; } + .cd-nav-link:hover { background-color: var(--cd-hover); color: var(--cd-text); } + .cd-nav-link.active { background-color: var(--cd-primary-subtle); color: var(--cd-primary-text); @@ -478,16 +513,19 @@ body { box-shadow 0.2s ease, transform 0.2s ease; } + .section-title { font-size: 1rem; font-weight: 600; color: var(--cd-text); margin-bottom: 1rem; } + .text-muted { font-size: 0.875rem; color: var(--cd-text-2); } + .badge-success { display: inline-flex; align-items: center; @@ -499,6 +537,7 @@ body { background-color: var(--cd-success-subtle); color: var(--cd-success); } + .badge-warning { display: inline-flex; align-items: center; @@ -510,6 +549,7 @@ body { background-color: var(--cd-warning-subtle); color: var(--cd-warning); } + .badge-default { display: inline-flex; align-items: center; @@ -521,6 +561,16 @@ body { background-color: var(--cd-surface-2); color: var(--cd-text-2); } + + @keyframes shimmer { + 100% { + transform: translateX(100%); + } + } + + .animate-\[shimmer_1\.4s_infinite\] { + animation: shimmer 1.4s infinite; + } } /* ─── Smooth theme transition ────────────────────────────────────────────── */ @@ -540,13 +590,35 @@ html { width: 6px; height: 6px; } + ::-webkit-scrollbar-track { background: transparent; } + ::-webkit-scrollbar-thumb { background-color: var(--cd-border); border-radius: 9999px; } + ::-webkit-scrollbar-thumb:hover { background-color: var(--cd-text-muted); } + +/* ─── Custom Scrollbar Utility ───────────────────────────────────────────── */ +.custom-scrollbar::-webkit-scrollbar { + width: 5px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: transparent; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background-color: var(--cd-border); + border-radius: 10px; + border: 1px solid transparent; +} + +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background-color: var(--cd-primary); +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index a06e4ed..4738fbf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import "./App.css"; import { dashboardData } from "./features/Member/v1/mock/dashboardData"; import { startAutoUpdater } from "./system/updater/autoUpdater"; -import { ThemeProvider } from "next-themes"; +import { ThemeProvider } from "./theme"; import OrgRoute from "./routes/OrgRoute"; import MemberRoutes from "./routes/MemberRoutes"; @@ -19,7 +19,7 @@ function App() { const user = dashboardData.user; return ( - + diff --git a/src/config/them.config.ts b/src/config/them.config.ts index 1299107..61ee864 100644 --- a/src/config/them.config.ts +++ b/src/config/them.config.ts @@ -3,8 +3,9 @@ import { theme, type ThemeMode, type ThemeTokens } from "../theme/theme.config"; export type { ThemeMode }; -export type Theme = ThemeTokens[ThemeMode]; +export type Theme = ThemeTokens; -export function getTheme(mode: ThemeMode): Theme { - return theme[mode]; +/** Returns the single flat token object — CSS variables resolve per mode automatically. */ +export function getTheme(_mode?: ThemeMode): Theme { + return theme; } diff --git a/src/features/Dashboard/components/AISuggestions.tsx b/src/features/Dashboard/components/AISuggestions.tsx index 20fb459..56d2686 100644 --- a/src/features/Dashboard/components/AISuggestions.tsx +++ b/src/features/Dashboard/components/AISuggestions.tsx @@ -1,5 +1,5 @@ import { Sparkles } from "lucide-react"; -import { Task } from "@/features/Dashboard/types/dashboard"; +import { Task } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { getAISuggestions } from "@/utils/aisuggestions"; interface Props { diff --git a/src/features/Dashboard/components/ActivityFeed.tsx b/src/features/Dashboard/components/ActivityFeed.tsx index db5fa55..f9c9c54 100644 --- a/src/features/Dashboard/components/ActivityFeed.tsx +++ b/src/features/Dashboard/components/ActivityFeed.tsx @@ -1,5 +1,5 @@ import { CheckCircle, MessageSquare, Bell } from "lucide-react"; -import { ActivityItem } from "@/features/Dashboard/types/dashboard"; +import { ActivityItem } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { useTheme } from "@/theme"; interface Props { diff --git a/src/features/Dashboard/components/BudgetCard.tsx b/src/features/Dashboard/components/BudgetCard.tsx index ca1023e..2f86334 100644 --- a/src/features/Dashboard/components/BudgetCard.tsx +++ b/src/features/Dashboard/components/BudgetCard.tsx @@ -1,4 +1,4 @@ -import { Rewards } from "@/features/Dashboard/types/dashboard"; +import { Rewards } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { Gift, Star, Wallet } from "lucide-react"; import { useTheme } from "@/theme"; diff --git a/src/features/Dashboard/components/CalendarWidget.tsx b/src/features/Dashboard/components/CalendarWidget.tsx index 140d9aa..ff979ff 100644 --- a/src/features/Dashboard/components/CalendarWidget.tsx +++ b/src/features/Dashboard/components/CalendarWidget.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; import { ChevronLeft, ChevronRight, CalendarDays } from "lucide-react"; -import { CalendarData } from "@/features/Dashboard/types/dashboard"; +import { CalendarData } from "@/features/Dashboard/Member/v1/Type/dashboard"; interface Props { data: CalendarData; diff --git a/src/features/Dashboard/components/CommunityStats.tsx b/src/features/Dashboard/components/CommunityStats.tsx index a376cb3..d55f8e7 100644 --- a/src/features/Dashboard/components/CommunityStats.tsx +++ b/src/features/Dashboard/components/CommunityStats.tsx @@ -1,4 +1,4 @@ -import { CommunityStats } from "@/features/Dashboard/types/dashboard"; +import { CommunityStats } from "@/features/Dashboard/Member/v1/Type/dashboard"; interface Props { data: CommunityStats; diff --git a/src/features/Dashboard/components/RecentTasks.tsx b/src/features/Dashboard/components/RecentTasks.tsx index c2048ff..d99719f 100644 --- a/src/features/Dashboard/components/RecentTasks.tsx +++ b/src/features/Dashboard/components/RecentTasks.tsx @@ -1,4 +1,4 @@ -import { Task } from "../types/dashboard"; +import { Task } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { Check, Eye } from "lucide-react"; interface Props { diff --git a/src/features/Dashboard/components/SmartReminders.tsx b/src/features/Dashboard/components/SmartReminders.tsx index d3a1dca..bbc1db5 100644 --- a/src/features/Dashboard/components/SmartReminders.tsx +++ b/src/features/Dashboard/components/SmartReminders.tsx @@ -1,5 +1,5 @@ import { AlertTriangle, Clock } from "lucide-react"; -import { Task } from "@/features/Dashboard/types/dashboard"; +import { Task } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { getSmartReminders } from "@/utils/reminders"; interface Props { diff --git a/src/features/Dashboard/components/TaskOverview.tsx b/src/features/Dashboard/components/TaskOverview.tsx index 5400baf..5a9b9dd 100644 --- a/src/features/Dashboard/components/TaskOverview.tsx +++ b/src/features/Dashboard/components/TaskOverview.tsx @@ -1,5 +1,5 @@ import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts"; -import { Task } from "../types/dashboard"; +import { Task } from "@/features/Dashboard/Member/v1/Type/dashboard"; interface Props { tasks: Task[]; diff --git a/src/features/Dashboard/components/TaskRow.tsx b/src/features/Dashboard/components/TaskRow.tsx index c604e51..bfcd3f3 100644 --- a/src/features/Dashboard/components/TaskRow.tsx +++ b/src/features/Dashboard/components/TaskRow.tsx @@ -1,4 +1,4 @@ -import { Task } from "@/features/Dashboard/types/dashboard"; +import { Task } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { formatDueLabel } from "@/utils/task.utils"; import { Check } from "lucide-react"; diff --git a/src/features/Dashboard/components/UpcomingUrgentTasks.tsx b/src/features/Dashboard/components/UpcomingUrgentTasks.tsx index 5019b23..0130483 100644 --- a/src/features/Dashboard/components/UpcomingUrgentTasks.tsx +++ b/src/features/Dashboard/components/UpcomingUrgentTasks.tsx @@ -1,4 +1,4 @@ -import { Task } from "@/features/Dashboard/types/dashboard"; +import { Task } from "@/features/Dashboard/Member/v1/Type/dashboard"; import { categorizeTasks } from "@/utils/task.utils"; import TaskRow from "@/features/Dashboard/components/TaskRow"; diff --git a/src/features/SideBar/v1/Section/BotamNavBar.tsx b/src/features/SideBar/v1/Section/BotamNavBar.tsx index 98a87f6..b0c3b2b 100644 --- a/src/features/SideBar/v1/Section/BotamNavBar.tsx +++ b/src/features/SideBar/v1/Section/BotamNavBar.tsx @@ -1,6 +1,6 @@ import { RiContactsBookFill } from "react-icons/ri"; import { type IconType } from "react-icons"; -import { MdDashboard, MdEvent, MdGroup } from "react-icons/md"; +import { MdDashboard, MdEvent, MdGroup, MdAssignment } from "react-icons/md"; import { Link, useLocation } from "react-router-dom"; import { useTheme } from "@/theme"; @@ -30,6 +30,12 @@ const navItems: NavItem[] = [ link: "/org/events", isActive: (p) => p.startsWith("/org/events") || p.startsWith("/org/create-event"), }, + { + icon: MdAssignment, + text: "Tasks", + link: "/tasks", + isActive: (pathname) => pathname.startsWith("/tasks"), + }, { icon: RiContactsBookFill, text: "Support", diff --git a/src/features/SideBar/v1/Section/SideBar.tsx b/src/features/SideBar/v1/Section/SideBar.tsx index fac37a1..cd4ef5e 100644 --- a/src/features/SideBar/v1/Section/SideBar.tsx +++ b/src/features/SideBar/v1/Section/SideBar.tsx @@ -1,5 +1,5 @@ import { RiContactsBookFill } from "react-icons/ri"; -import { MdDashboard, MdEvent, MdGroup, MdSettings, MdWork } from "react-icons/md"; +import { MdAssignment, MdDashboard, MdEvent, MdGroup, MdSettings, MdWork } from "react-icons/md"; import { useTheme } from "@/theme"; import { ThemeToggle } from "@/Component/ui/ThemeToggle"; @@ -48,6 +48,7 @@ const SideBar = () => { } text="Projects" link="/org/projects" /> } text="Teams" link="/org/member" /> } text="Events" link="/org/events" /> + } text="Tasks" link="/org/tasks" /> } text="Contact Submissions" link="/org/contact" /> {/* Footer */} diff --git a/src/features/Tasks/v1/Task.types.ts b/src/features/Tasks/v1/Task.types.ts new file mode 100644 index 0000000..4b0ee5f --- /dev/null +++ b/src/features/Tasks/v1/Task.types.ts @@ -0,0 +1,123 @@ +export type TaskStatus = "todo" | "in-progress" | "completed"; +export type TaskPriority = "low" | "medium" | "high"; +export type SubmissionType = "file" | "github" | "link" | "all"; +export type SubmissionStatus = "not-submitted" | "submitted" | "reviewed"; +export type ReviewDecision = "approved" | "rejected" | "pending"; +export type EventType = "hackathon" | "workshop" | "internal" | "community"; + +// ─── Technology tag ─────────────────────────────────────────────────────────── +export interface TechTag { + id: string; + label: string; + color: string; // Tailwind bg class e.g. "bg-sky-100 text-sky-700" +} + +export interface EventOption { + id: string; + name: string; + subtitle: string; + type: EventType; + status: "Live" | "Upcoming" | "Completed"; + startDate: string; + endDate: string; +} + +export interface MemberOption { + id: string; + name: string; + avatar: string; + role: string; + email: string; +} + +export interface Task { + id: string; + eventId: string; + title: string; + description: string; + status: TaskStatus; + priority: TaskPriority; + deadline: string; + assignedTo: MemberOption[]; + submissionType: SubmissionType; + submissionStatus: SubmissionStatus; + isMandatory: boolean; + points?: number; + allowLateSubmission: boolean; + maxSubmissions?: number; + attachments?: string[]; + technologies?: TechTag[]; // NEW + createdAt: string; + updatedAt: string; + createdBy: string; +} + +export interface Submission { + id: string; + taskId: string; + submittedBy: MemberOption; + fileUrl?: string; + githubUrl?: string; + linkUrl?: string; + notes?: string; + submittedAt: string; + review?: SubmissionReview; +} + +export interface SubmissionReview { + decision: ReviewDecision; + score?: number; + feedback?: string; + reviewedBy: string; + reviewedAt: string; +} + +export interface ActivityEvent { + id: string; + type: "created" | "updated" | "submitted" | "reviewed" | "commented"; + actor: string; + description: string; + timestamp: string; +} + +export interface TaskComment { + id: string; + taskId: string; + author: string; + avatar: string; + text: string; + createdAt: string; +} + +export interface TaskFilters { + status: TaskStatus | "all"; + priority: TaskPriority | "all"; + time: "all" | "upcoming" | "past" | "completed"; + members: string[]; + search: string; +} + +export interface CreateTaskPayload { + eventId: string; + title: string; + description: string; + assignedTo: string[]; + deadline: string; + priority: TaskPriority; + submissionType: SubmissionType; + isMandatory: boolean; + points?: number; + allowLateSubmission: boolean; + maxSubmissions?: number; + technologies?: TechTag[]; +} + +export interface UpdateTaskPayload extends Partial { + status?: TaskStatus; +} + +export interface ReviewSubmissionPayload { + decision: ReviewDecision; + score?: number; + feedback?: string; +} \ No newline at end of file diff --git a/src/features/Tasks/v1/components/common/Avatar.tsx b/src/features/Tasks/v1/components/common/Avatar.tsx new file mode 100644 index 0000000..6656584 --- /dev/null +++ b/src/features/Tasks/v1/components/common/Avatar.tsx @@ -0,0 +1,145 @@ +import { useState } from "react"; + +interface AvatarProps { + name: string; + src: string; + role?: string; + size?: "xs" | "sm" | "md" | "lg"; + showTooltip?: boolean; + status?: "online" | "offline" | "busy" | null; + ring?: boolean; + ringColor?: string; +} + +// Color map for initials fallback — derived from name char code +const BG_COLORS = [ + "bg-indigo-500", "bg-pink-500", "bg-orange-500", + "bg-emerald-500", "bg-sky-500", "bg-violet-500", + "bg-rose-500", "bg-amber-500","bg-teal-500", +]; + +function getColor(name: string): string { + const code = name.charCodeAt(0) + (name.charCodeAt(1) || 0); + return BG_COLORS[code % BG_COLORS.length]; +} + +function getInitials(name: string): string { + return name.split(" ").map(w => w[0]).join("").slice(0, 2).toUpperCase(); +} + +const SIZE_MAP = { + xs: { outer: "w-6 h-6", text: "text-[9px]", dot: "w-1.5 h-1.5 -right-0 -bottom-0", ring: "ring-1" }, + sm: { outer: "w-7 h-7", text: "text-[10px]", dot: "w-2 h-2 -right-0 -bottom-0", ring: "ring-2" }, + md: { outer: "w-9 h-9", text: "text-xs", dot: "w-2.5 h-2.5 right-0 bottom-0", ring: "ring-2" }, + lg: { outer: "w-12 h-12", text: "text-sm", dot: "w-3 h-3 right-0.5 bottom-0.5", ring: "ring-2" }, +}; + +const STATUS_COLOR = { + online: "bg-emerald-400", + offline: "bg-gray-300", + busy: "bg-red-400", +}; + +export default function Avatar({ + name, src, role, size = "sm", + showTooltip = true, status = null, ring = false, ringColor = "ring-[var(--cd-surface)]", +}: AvatarProps) { + const [imgError, setImgError] = useState(false); + const [hovered, setHovered] = useState(false); + const sz = SIZE_MAP[size]; + const initials = getInitials(name); + const bgColor = getColor(name); + + return ( +
setHovered(true)} + onMouseLeave={() => setHovered(false)} + > + {/* Avatar circle */} +
+ {!imgError ? ( + {name} setImgError(true)} + className="w-full h-full object-cover" + /> + ) : ( + /* Initials fallback */ +
+ {initials} +
+ )} +
+ + {/* Status dot */} + {status && ( + + )} + + {/* Tooltip */} + {showTooltip && hovered && ( +
+
+

{name}

+ {role &&

{role}

} +
+ {/* Arrow */} +
+
+
+
+ )} +
+ ); +} + +// ─── Avatar group (stacked, with +N overflow) ───────────────────────────────── +interface AvatarGroupProps { + members: { id: string; name: string; avatar: string; role?: string }[]; + max?: number; + size?: "xs" | "sm" | "md"; +} + +export function AvatarGroup({ members, max = 3, size = "sm" }: AvatarGroupProps) { + const visible = members.slice(0, max); + const overflow = members.length - max; + const sz = SIZE_MAP[size]; + + return ( +
+ {visible.map((m) => ( + + ))} + {overflow > 0 && ( +
+ +{overflow} +
+ )} +
+ ); +} diff --git a/src/features/Tasks/v1/components/common/CommentsSection.tsx b/src/features/Tasks/v1/components/common/CommentsSection.tsx new file mode 100644 index 0000000..5df2bb0 --- /dev/null +++ b/src/features/Tasks/v1/components/common/CommentsSection.tsx @@ -0,0 +1,86 @@ +import { useState } from "react"; +import { formatDistanceToNow, parseISO } from "date-fns"; +import { Loader2, MessageCircle, Send } from "lucide-react"; +import { useComments, useAddComment } from "../../hooks/useComments"; + +interface Props { taskId: string; } + +export default function CommentsSection({ taskId }: Props) { + const { data: comments = [], isLoading } = useComments(taskId); + const addComment = useAddComment(); + const [text, setText] = useState(""); + const [error, setError] = useState(""); + + const handleSubmit = async () => { + if (!text.trim()) { setError("Comment cannot be empty."); return; } + setError(""); + await addComment.mutateAsync({ taskId, text: text.trim() }); + setText(""); + }; + + return ( +
+ {/* Header */} +
+ +

Comments

+ + {comments.length} + +
+ + {/* Comment list */} + {isLoading ? ( +

Loading comments…

+ ) : comments.length === 0 ? ( +
+

No comments yet. Be the first to start the conversation!

+
+ ) : ( +
+ {comments.map((c) => ( +
+ {c.author} +
+
+ {c.author} + + {formatDistanceToNow(parseISO(c.createdAt), { addSuffix: true })} + +
+

{c.text}

+
+
+ ))} +
+ )} + + {/* Add comment */} +
+
+