diff --git a/.gitignore b/.gitignore index ab44528..7c6722a 100644 --- a/.gitignore +++ b/.gitignore @@ -47,9 +47,6 @@ error-log # vercel .vercel -# rust -rust-ai/target/ - # python browser-use-service/venv/ browser-use-service/__pycache__/ @@ -58,3 +55,12 @@ browser-use-service/.env # typescript *.tsbuildinfo next-env.d.ts +.git +.git_bak +node_modules +target +# rust build artifacts +rust-ai/target/ + +# browser-use-service generated files +browser-use-service/*.txt diff --git a/database/migrations/run_all.sql b/database/migrations/run_all.sql index ec98d2e..2ec9138 100644 --- a/database/migrations/run_all.sql +++ b/database/migrations/run_all.sql @@ -34,11 +34,5 @@ BEGIN; \echo '=== Running 009_settings.sql (Company Settings + User Preferences) ===' \i 009_settings.sql -\echo '=== Running 013_security_upgrade.sql (Security Architecture: RLS, Encryption, Master Keys, Backups) ===' -\i 013_security_upgrade.sql - -\echo '=== Running 014_bug_reports.sql (Bug Reporting System) ===' -\i 014_bug_reports.sql - \echo '=== Migration Complete ===' COMMIT; diff --git a/next.config.ts b/next.config.ts index 99d534e..2e27cc2 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,12 +1,4 @@ import type { NextConfig } from "next" -import crypto from "crypto" - -// In development, generate a random JWT secret on every server start. -// This invalidates all previously issued JWT tokens, ensuring the user -// must re-authenticate after each dev server restart. -if (process.env.NODE_ENV === "development") { - process.env.JWT_SECRET = crypto.randomBytes(32).toString("hex") -} const nextConfig: NextConfig = { eslint: { @@ -20,7 +12,6 @@ const nextConfig: NextConfig = { }, ], }, - } export default nextConfig diff --git a/package-lock.json b/package-lock.json index ef86181..ca086b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,8 +25,6 @@ "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", - "@supabase/ssr": "^0.12.0", - "@supabase/supabase-js": "^2.108.2", "@tanstack/react-table": "^8.20.6", "autoprefixer": "^10.4.20", "bcryptjs": "^3.0.3", @@ -42,8 +40,6 @@ "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", "recharts": "^2.15.0", - "socket.io": "^4.8.3", - "socket.io-client": "^4.8.3", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", "vaul": "^1.1.2", @@ -1941,108 +1937,6 @@ "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", "dev": true }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, - "node_modules/@supabase/auth-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.108.2.tgz", - "integrity": "sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/functions-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.108.2.tgz", - "integrity": "sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/phoenix": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.4.tgz", - "integrity": "sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==", - "license": "MIT" - }, - "node_modules/@supabase/postgrest-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.108.2.tgz", - "integrity": "sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==", - "license": "MIT", - "dependencies": { - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/realtime-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.108.2.tgz", - "integrity": "sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==", - "license": "MIT", - "dependencies": { - "@supabase/phoenix": "^0.4.2", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/ssr": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.12.0.tgz", - "integrity": "sha512-d9XV5XzJvzzZbeAIM7fWTCUYxQJZ2Ru6ny3dJHmHGp/LIrJ+o9FpD7N9Rf/UhhWEvHXSoDe8SI32Z2ouOdMjBg==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.2" - }, - "peerDependencies": { - "@supabase/supabase-js": "^2.108.0" - } - }, - "node_modules/@supabase/storage-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.108.2.tgz", - "integrity": "sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==", - "license": "MIT", - "dependencies": { - "iceberg-js": "^0.8.1", - "tslib": "2.8.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@supabase/supabase-js": { - "version": "2.108.2", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.108.2.tgz", - "integrity": "sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==", - "license": "MIT", - "dependencies": { - "@supabase/auth-js": "2.108.2", - "@supabase/functions-js": "2.108.2", - "@supabase/postgrest-js": "2.108.2", - "@supabase/realtime-js": "2.108.2", - "@supabase/storage-js": "2.108.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -2099,15 +1993,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -2184,6 +2069,7 @@ "version": "20.19.43", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2224,15 +2110,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.61.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz", @@ -2791,19 +2668,6 @@ "win32" ] }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -3161,15 +3025,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, "node_modules/baseline-browser-mapping": { "version": "2.10.37", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", @@ -3549,36 +3404,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3781,6 +3606,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -3918,58 +3744,6 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/engine.io": { - "version": "6.6.9", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", - "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", - "license": "MIT", - "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "@types/ws": "^8.5.12", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.21.0" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-client": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", - "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.21.0", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -5028,15 +4802,6 @@ "node": ">= 0.4" } }, - "node_modules/iceberg-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", - "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5756,27 +5521,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -5814,7 +5558,8 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, "node_modules/mz": { "version": "2.7.0", @@ -5865,15 +5610,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/next": { "version": "15.0.4", "resolved": "https://registry.npmjs.org/next/-/next-15.0.4.tgz", @@ -7210,62 +6946,6 @@ "is-arrayish": "^0.3.1" } }, - "node_modules/socket.io": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", - "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.4.1", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", - "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", - "license": "MIT", - "dependencies": { - "debug": "~4.4.1", - "ws": "~8.21.0" - } - }, - "node_modules/socket.io-client": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", - "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/sonner": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", @@ -7899,7 +7579,8 @@ "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true }, "node_modules/unrs-resolver": { "version": "1.12.2", @@ -8023,15 +7704,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vaul": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", @@ -8205,35 +7877,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/rust-ai/src/main.rs b/rust-ai/src/main.rs index 3f6ea0c..ef33b3f 100644 --- a/rust-ai/src/main.rs +++ b/rust-ai/src/main.rs @@ -446,7 +446,7 @@ async fn main() { let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); let ollama_url = std::env::var("OLLAMA_BASE_URL").unwrap_or_else(|_| "http://localhost:11434".to_string()); - let model = std::env::var("AI_MODEL").unwrap_or_else(|_| "llama3.2:3b".to_string()); + let model = std::env::var("AI_MODEL").unwrap_or_else(|_| "dolphin-phi".to_string()); let host = std::env::var("AI_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); let port: u16 = std::env::var("AI_PORT").unwrap_or_else(|_| "3001".to_string()).parse().expect("AI_PORT must be a number"); diff --git a/src/app/(dashboard)/chats/page.tsx b/src/app/(dashboard)/chats/page.tsx index 9a06a09..deef9aa 100644 --- a/src/app/(dashboard)/chats/page.tsx +++ b/src/app/(dashboard)/chats/page.tsx @@ -15,7 +15,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { - Search, Send, Phone, MoreHorizontal, Paperclip, + Search, Send, Phone, Video, MoreHorizontal, Paperclip, Smile, Flag, Ban, Trash2, Image, FileIcon, X, Mic, Square, Play, Pause, Check, CheckCheck, CornerDownRight, Forward, Pencil, Download, Undo2, } from "lucide-react" @@ -156,7 +156,6 @@ export default function ChatsPage() { const [searchResults, setSearchResults] = useState([]) const [searchingUsers, setSearchingUsers] = useState(false) const [unreadMap, setUnreadMap] = useState>(new Map()) - const [isCallModalOpen, setIsCallModalOpen] = useState(false) const [previewAvatarUrl, setPreviewAvatarUrl] = useState(null) const fileInputRef = useRef(null) const textareaRef = useRef(null) @@ -841,9 +840,12 @@ const formatPreviewContent = (content: string) => {
- +
) } diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 4ce3b8d..61c4ba7 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -24,6 +24,8 @@ import { SelectValue, } from "@/components/ui/select" import { DashboardStats } from "@/types" +import { CrossedLightsabers } from "@/components/dashboard/crossed-lightsabers" +import { StarField } from "@/components/dashboard/star-field" export default function DashboardPage() { const [period, setPeriod] = useState("6months") @@ -63,47 +65,55 @@ export default function DashboardPage() { return (
- {/* Daily Bugle watermark */} -
- - DAILY BUGLE - + +
+
+ Dashboard} + description="Overview of your sales pipeline" + > + + Trust the Force, Find your Path + + + + +
+ +

Pipeline Overview

+ +
+ {stats + ? statCards.map((card, i) => ( + + )) + : Array.from({ length: 6 }).map((_, i) => ) + } +
+ +

Analytics

+ +
+ + +
+ +
- - Dashboard} description="Overview of your sales pipeline"> - - - -

Pipeline Overview

- -
- {stats - ? statCards.map((card, i) => ( - - )) - : Array.from({ length: 6 }).map((_, i) => ) - } -
- -

Analytics

- -
- - -
- -
) } diff --git a/src/app/api/ai/chat/route.ts b/src/app/api/ai/chat/route.ts index 51ad7be..839e5cb 100644 --- a/src/app/api/ai/chat/route.ts +++ b/src/app/api/ai/chat/route.ts @@ -1,12 +1,14 @@ import { NextRequest, NextResponse } from "next/server" -import { chatWithAI } from "@/lib/ai" import { getSessionUser } from "@/lib/auth" +import { chatWithAI } from "@/lib/ai" export async function POST(request: NextRequest) { try { const user = await getSessionUser() - if (!user) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + + if (!["sales", "admin", "super_admin"].includes(user.role)) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }) } const { message } = await request.json() @@ -14,16 +16,15 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "Message is required" }, { status: 400 }) } - const sessionCookie = request.cookies.get("session") - const jwtToken = sessionCookie?.value - if (!jwtToken) { - return NextResponse.json({ error: "No session token" }, { status: 401 }) - } + // Forward the JWT from the session cookie to the Rust backend + const sessionCookie = request.cookies.get("session")?.value + if (!sessionCookie) return NextResponse.json({ error: "No session" }, { status: 401 }) + + const response = await chatWithAI(message, sessionCookie) - const response = await chatWithAI(message, jwtToken) return NextResponse.json({ response }) - } catch (error: any) { + } catch (error) { console.error("AI chat error:", error) - return NextResponse.json({ error: error.message || "AI service error" }, { status: 500 }) + return NextResponse.json({ error: "AI service unavailable" }, { status: 503 }) } } diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 5f24e89..e7ad058 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -9,7 +9,6 @@ import { resetFailedAttempts, isAccountLocked, createSession, - setSessionContext, } from "@/lib/auth" export async function POST(request: NextRequest) { @@ -107,7 +106,6 @@ export async function POST(request: NextRequest) { ) await createSession(dbUser.id, dbUser.role_name) - await setSessionContext(dbUser.id, ipAddress) const user = mapDbUserToSessionUser(dbUser) diff --git a/src/app/api/conversations/route.ts b/src/app/api/conversations/route.ts index aff4f0b..a62c57c 100644 --- a/src/app/api/conversations/route.ts +++ b/src/app/api/conversations/route.ts @@ -13,11 +13,10 @@ export async function GET() { c.id, c.updated_at, cp_me.last_read_at, - u.id AS other_user_id, - u.first_name || ' ' || u.last_name AS other_user_name, - u.email AS other_user_email, - u.phone AS other_user_phone, - u.avatar_url AS other_user_avatar_url, + u.id AS other_user_id, + u.first_name || ' ' || u.last_name AS other_user_name, + u.email AS other_user_email, + u.avatar_url AS other_user_avatar_url, (SELECT content FROM messages WHERE conversation_id = c.id ORDER BY created_at DESC LIMIT 1) AS last_message, (SELECT created_at FROM messages WHERE conversation_id = c.id ORDER BY created_at DESC LIMIT 1) AS last_message_time, (SELECT count(*) FROM messages WHERE conversation_id = c.id AND sender_id != $1 AND created_at > COALESCE(cp_me.last_read_at, '1970-01-01')) AS unread @@ -40,7 +39,6 @@ export async function GET() { id: row.other_user_id, name: row.other_user_name, email: row.other_user_email, - phone: row.other_user_phone || "", avatar: avatarSvgUrl(row.other_user_name), }, lastMessage: row.last_message || "", @@ -92,7 +90,7 @@ export async function POST(request: NextRequest) { ) const otherUser = await query( - `SELECT id, first_name || ' ' || last_name AS name, email, phone, avatar_url + `SELECT id, first_name || ' ' || last_name AS name, email, avatar_url FROM users WHERE id = $1`, [userId], ) @@ -107,7 +105,6 @@ export async function POST(request: NextRequest) { id: other.id, name: other.name, email: other.email, - phone: other.phone || "", avatar: avatarSvgUrl(other.name), }, lastMessage: "", diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts index ad2964b..4f0be95 100644 --- a/src/app/api/users/route.ts +++ b/src/app/api/users/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server" import { query } from "@/lib/db" -import { hashPassword, getSessionUser, encryptPassword, setSessionContext } from "@/lib/auth" +import { hashPassword, getSessionUser } from "@/lib/auth" import { avatarSvgUrl } from "@/lib/avatar" export async function GET(request: NextRequest) { @@ -63,20 +63,17 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "Invalid role" }, { status: 400 }) } - await setSessionContext(sessionUser.id) - const nameParts = name.trim().split(/\s+/) const firstName = nameParts[0] const lastName = nameParts.slice(1).join(" ") || firstName const username = email.split("@")[0] const passwordHash = await hashPassword(password) - const passwordEncrypted = await encryptPassword(password) const result = await query( - `INSERT INTO users (username, email, password_hash, password_encrypted, first_name, last_name, is_active, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + `INSERT INTO users (username, email, password_hash, first_name, last_name, is_active, created_by) + VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`, - [username.toLowerCase(), email.toLowerCase(), passwordHash, passwordEncrypted, firstName, lastName, active ?? true, sessionUser.id] + [username.toLowerCase(), email.toLowerCase(), passwordHash, firstName, lastName, active ?? true, sessionUser.id] ) const roleId = ( diff --git a/src/app/globals.css b/src/app/globals.css index bc54320..97ea711 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,51 +1,115 @@ -@import url('https://fonts.googleapis.com/css2?family=Bangers&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Bangers&family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Audiowide&display=swap'); @tailwind base; @tailwind components; @tailwind utilities; +.text-glow { + text-shadow: 0 0 15px #39ff14, 0 0 30px #39ff14, 0 0 50px #39ff14, 0 0 80px rgba(57, 255, 20, 0.6); +} + +.dark .text-glow { + text-shadow: 0 0 10px #39ff14, 0 0 20px #39ff14, 0 0 35px #39ff14; +} + @keyframes loading { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } } +@keyframes pulse-glow-red { + 0%, 100% { opacity: 0.12; } + 50% { opacity: 0.5; } +} + +@keyframes pulse-glow-blue { + 0%, 100% { opacity: 0.12; } + 50% { opacity: 0.5; } +} + +@keyframes pulse-glow-intersection { + 0%, 100% { opacity: 0.15; } + 50% { opacity: 0.65; } +} + +.saber-outer { stroke-width: 30; } +.dark .saber-outer { stroke-width: 40; } +.saber-mid { stroke-width: 18; } +.dark .saber-mid { stroke-width: 24; } +.saber-core { stroke-width: 6; } +.dark .saber-core { stroke-width: 8; } + +.saber-blue-outer { stroke-width: 24; } +.dark .saber-blue-outer { stroke-width: 40; } +.saber-blue-mid { stroke-width: 13; } +.dark .saber-blue-mid { stroke-width: 24; } +.saber-blue-core { stroke-width: 4; } +.dark .saber-blue-core { stroke-width: 8; } + +.animate-pulse-red { + animation: pulse-glow-red 2.5s ease-in-out infinite; +} + +.animate-pulse-blue { + animation: pulse-glow-blue 2.8s ease-in-out infinite; +} + +.animate-pulse-intersection { + animation: pulse-glow-intersection 2.5s ease-in-out infinite; +} + +@keyframes star-twinkle { + 0%, 100% { opacity: 0.1; } + 50% { opacity: 0.6; } +} + +@keyframes star-drift { + 0% { transform: translate(0, 0); } + 50% { transform: translate(3px, -2px); } + 100% { transform: translate(0, 0); } +} + +.star-twinkle { + animation: star-twinkle 4s ease-in-out infinite, star-drift 20s ease-in-out infinite; +} + :root { - --background: 210 40% 98%; + --background: 220 14% 84%; --foreground: 222.2 84% 4.9%; - --card: 0 0% 100%; + --card: 220 10% 91%; --card-foreground: 222.2 84% 4.9%; - --popover: 0 0% 100%; + --popover: 220 14% 96%; --popover-foreground: 222.2 84% 4.9%; - --primary: 0 100% 40%; - --primary-foreground: 210 40% 98%; - --secondary: 210 40% 96.1%; + --primary: 120 100% 50%; + --primary-foreground: 0 0% 100%; + --secondary: 220 14% 88%; --secondary-foreground: 222.2 47.4% 11.2%; - --muted: 210 40% 96.1%; + --muted: 220 14% 88%; --muted-foreground: 215.4 16.3% 46.9%; - --accent: 210 40% 96.1%; + --accent: 220 14% 88%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 221.2 83.2% 53.3%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 217.2 91.2% 59.8%; + --border: 220 14% 85%; + --input: 220 14% 85%; + --ring: 120 100% 50%; + --sidebar: 220 14% 74%; + --sidebar-foreground: 222.2 84% 4.9%; + --sidebar-primary: 145 65% 50%; --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 224.3 76.3% 48%; + --sidebar-accent: 210 40% 96%; + --sidebar-accent-foreground: 222.2 84% 4.9%; + --sidebar-border: 220 9% 46%; + --sidebar-ring: 120 100% 50%; --radius: 0.5rem; } .dark { - --background: 222.2 84% 4.9%; + --background: 0 0% 10%; --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; + --card: 0 0% 9%; --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; + --popover: 222.2 84% 11%; --popover-foreground: 210 40% 98%; --primary: 0 100% 53%; --primary-foreground: 222.2 47.4% 11.2%; @@ -60,9 +124,9 @@ --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 0 100% 53%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 0 100% 53%; + --sidebar-primary: 145 65% 50%; --sidebar-primary-foreground: 0 0% 100%; --sidebar-accent: 217.2 32.6% 17.5%; --sidebar-accent-foreground: 210 40% 98%; @@ -88,7 +152,7 @@ --primary: 187 75% 50%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 187 75% 50%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 187 75% 55%; --sidebar-primary-foreground: 0 0% 100%; @@ -116,7 +180,7 @@ --primary: 142 76% 44%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 142 76% 44%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 142 76% 50%; --sidebar-primary-foreground: 0 0% 100%; @@ -144,7 +208,7 @@ --primary: 24 95% 58%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 24 95% 58%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 24 95% 65%; --sidebar-primary-foreground: 0 0% 100%; @@ -172,7 +236,7 @@ --primary: 230 75% 62%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 230 75% 62%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 230 75% 65%; --sidebar-primary-foreground: 0 0% 100%; @@ -200,7 +264,7 @@ --primary: 346 77% 58%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 346 77% 58%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 346 77% 60%; --sidebar-primary-foreground: 0 0% 100%; @@ -228,7 +292,7 @@ --primary: 38 92% 56%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 38 92% 56%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 38 92% 60%; --sidebar-primary-foreground: 0 0% 100%; @@ -256,7 +320,7 @@ --primary: 262 83% 65%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 262 83% 65%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 262 83% 68%; --sidebar-primary-foreground: 0 0% 100%; @@ -284,7 +348,7 @@ --primary: 215 20% 60%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 215 20% 60%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 215 20% 65%; --sidebar-primary-foreground: 0 0% 100%; @@ -312,259 +376,7 @@ --primary: 351 85% 52%; --primary-foreground: 222.2 47.4% 11.2%; --ring: 351 85% 52%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 351 85% 55%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 351 85% 52%; -} - -.ocean { - --primary: 187 75% 42%; - --primary-foreground: 210 40% 98%; - --ring: 187 75% 42%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 187 75% 42%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 187 30% 94%; - --sidebar-accent-foreground: 187 50% 20%; - --sidebar-border: 187 20% 88%; - --sidebar-ring: 187 75% 42%; -} - -.dark.ocean { - --primary: 187 75% 50%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 187 75% 50%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 187 75% 55%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 187 75% 50%; -} - -.forest { - --primary: 142 76% 36%; - --primary-foreground: 210 40% 98%; - --ring: 142 76% 36%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 142 76% 36%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 142 30% 94%; - --sidebar-accent-foreground: 142 50% 20%; - --sidebar-border: 142 20% 88%; - --sidebar-ring: 142 76% 36%; -} - -.dark.forest { - --primary: 142 76% 44%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 142 76% 44%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 142 76% 50%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 142 76% 44%; -} - -.sunset { - --primary: 24 95% 53%; - --primary-foreground: 210 40% 98%; - --ring: 24 95% 53%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 24 95% 53%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 24 30% 94%; - --sidebar-accent-foreground: 24 50% 20%; - --sidebar-border: 24 20% 88%; - --sidebar-ring: 24 95% 53%; -} - -.dark.sunset { - --primary: 24 95% 58%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 24 95% 58%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 24 95% 65%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 24 95% 58%; -} - -.midnight { - --primary: 230 75% 55%; - --primary-foreground: 210 40% 98%; - --ring: 230 75% 55%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 230 75% 55%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 230 30% 94%; - --sidebar-accent-foreground: 230 50% 20%; - --sidebar-border: 230 20% 88%; - --sidebar-ring: 230 75% 55%; -} - -.dark.midnight { - --primary: 230 75% 62%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 230 75% 62%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 230 75% 65%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 230 75% 62%; -} - -.rose { - --primary: 346 77% 50%; - --primary-foreground: 210 40% 98%; - --ring: 346 77% 50%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 346 77% 50%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 346 30% 94%; - --sidebar-accent-foreground: 346 50% 20%; - --sidebar-border: 346 20% 88%; - --sidebar-ring: 346 77% 50%; -} - -.dark.rose { - --primary: 346 77% 58%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 346 77% 58%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 346 77% 60%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 346 77% 58%; -} - -.amber { - --primary: 38 92% 50%; - --primary-foreground: 210 40% 98%; - --ring: 38 92% 50%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 38 92% 50%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 38 30% 94%; - --sidebar-accent-foreground: 38 50% 20%; - --sidebar-border: 38 20% 88%; - --sidebar-ring: 38 92% 50%; -} - -.dark.amber { - --primary: 38 92% 56%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 38 92% 56%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 38 92% 60%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 38 92% 56%; -} - -.violet { - --primary: 262 83% 58%; - --primary-foreground: 210 40% 98%; - --ring: 262 83% 58%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 262 83% 58%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 262 30% 94%; - --sidebar-accent-foreground: 262 50% 20%; - --sidebar-border: 262 20% 88%; - --sidebar-ring: 262 83% 58%; -} - -.dark.violet { - --primary: 262 83% 65%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 262 83% 65%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 262 83% 68%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 262 83% 65%; -} - -.slate { - --primary: 215 20% 45%; - --primary-foreground: 210 40% 98%; - --ring: 215 20% 45%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 215 20% 45%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 215 20% 94%; - --sidebar-accent-foreground: 215 50% 20%; - --sidebar-border: 215 20% 88%; - --sidebar-ring: 215 20% 45%; -} - -.dark.slate { - --primary: 215 20% 60%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 215 20% 60%; - --sidebar: 222.2 84% 4.9%; - --sidebar-foreground: 210 40% 98%; - --sidebar-primary: 215 20% 65%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 217.2 32.6% 17.5%; - --sidebar-accent-foreground: 210 40% 98%; - --sidebar-border: 217.2 32.6% 17.5%; - --sidebar-ring: 215 20% 60%; -} - -.ruby { - --primary: 351 85% 45%; - --primary-foreground: 210 40% 98%; - --ring: 351 85% 45%; - --sidebar: 0 0% 100%; - --sidebar-foreground: 222.2 84% 4.9%; - --sidebar-primary: 351 85% 45%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 351 30% 94%; - --sidebar-accent-foreground: 351 50% 20%; - --sidebar-border: 351 20% 88%; - --sidebar-ring: 351 85% 45%; -} - -.dark.ruby { - --primary: 351 85% 52%; - --primary-foreground: 222.2 47.4% 11.2%; - --ring: 351 85% 52%; - --sidebar: 222.2 84% 4.9%; + --sidebar: 0 0% 11%; --sidebar-foreground: 210 40% 98%; --sidebar-primary: 351 85% 55%; --sidebar-primary-foreground: 0 0% 100%; @@ -585,6 +397,49 @@ } } +[data-radix-select-trigger] { + background-color: hsl(0 0% 100%); +} + +main { + position: relative; + z-index: 0; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + z-index: -1; + pointer-events: none; + background-image: + radial-gradient(1px 1px at 10% 20%, rgba(100,100,120,0.4) 0%, transparent 100%), + radial-gradient(1px 1px at 20% 40%, rgba(100,100,120,0.3) 0%, transparent 100%), + radial-gradient(1.5px 1.5px at 30% 10%, rgba(80,80,100,0.5) 0%, transparent 100%), + radial-gradient(1px 1px at 40% 60%, rgba(100,100,120,0.3) 0%, transparent 100%), + radial-gradient(1px 1px at 50% 80%, rgba(90,90,110,0.4) 0%, transparent 100%), + radial-gradient(1.5px 1.5px at 60% 30%, rgba(80,80,100,0.5) 0%, transparent 100%), + radial-gradient(1px 1px at 70% 50%, rgba(100,100,120,0.3) 0%, transparent 100%), + radial-gradient(1px 1px at 80% 90%, rgba(90,90,110,0.4) 0%, transparent 100%), + radial-gradient(1.5px 1.5px at 90% 15%, rgba(80,80,100,0.5) 0%, transparent 100%), + radial-gradient(1px 1px at 15% 70%, rgba(100,100,120,0.3) 0%, transparent 100%), + radial-gradient(1px 1px at 25% 90%, rgba(90,90,110,0.4) 0%, transparent 100%), + radial-gradient(1.5px 1.5px at 35% 35%, rgba(80,80,100,0.5) 0%, transparent 100%), + radial-gradient(1px 1px at 45% 15%, rgba(100,100,120,0.3) 0%, transparent 100%), + radial-gradient(1px 1px at 55% 75%, rgba(90,90,110,0.4) 0%, transparent 100%), + radial-gradient(1.5px 1.5px at 65% 55%, rgba(80,80,100,0.5) 0%, transparent 100%), + radial-gradient(1px 1px at 75% 25%, rgba(100,100,120,0.3) 0%, transparent 100%), + radial-gradient(1px 1px at 85% 65%, rgba(90,90,110,0.4) 0%, transparent 100%), + radial-gradient(1.5px 1.5px at 95% 45%, rgba(80,80,100,0.5) 0%, transparent 100%); + background-size: 200% 200%; + animation: drift 60s linear infinite; +} + +@keyframes drift { + 0% { background-position: 0% 0%; } + 100% { background-position: 100% 100%; } +} + /* Login page custom styles */ .left-panel { background: #0a0a0f; @@ -830,6 +685,10 @@ display: block; margin-bottom: 6px; } +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-5px); } +} .body-text { color: rgba(232,232,239,0.5); } .subheading-text { color: rgba(232,232,239,0.38); } .checkbox-text { color: rgba(232,232,239,0.38); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a64d357..08e274e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,6 @@ import type { Metadata, Viewport } from "next" import { Inter } from "next/font/google" import { ThemeProvider } from "@/providers/theme-provider" -import { WebsiteThemeProvider } from "@/providers/website-theme-provider" import { Toaster } from "@/components/ui/sonner" import "./globals.css" @@ -32,10 +31,8 @@ export default function RootLayout({ - - {children} - - + {children} + diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 18ccddf..3f027d6 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useEffect, useRef } from "react" -import { useSearchParams } from "next/navigation" +import { useRouter } from "next/navigation" import { COMPANY_NAME } from "@/lib/constants" import { Eye, EyeOff, Loader2 } from "lucide-react" @@ -14,8 +14,7 @@ const waves = [ ] export default function LoginPage() { - const searchParams = useSearchParams() - const redirectTo = searchParams.get("redirect") || "/dashboard" + const router = useRouter() const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [showPassword, setShowPassword] = useState(false) @@ -216,7 +215,7 @@ export default function LoginPage() { }) if (res.ok) { - window.location.href = redirectTo + router.push("/dashboard") } else { const data = await res.json().catch(() => ({})) setError(data.error || "Invalid email or password.") diff --git a/src/components/ai/ai-chat.tsx b/src/components/ai/ai-chat.tsx index 14081ea..6adfe9d 100644 --- a/src/components/ai/ai-chat.tsx +++ b/src/components/ai/ai-chat.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useRef, useEffect, Fragment } from "react" -import { Send, Bot, User, RefreshCw, AlertCircle, Check, Terminal } from "lucide-react" +import { Send, Loader2, Bot, User, RefreshCw, AlertCircle } from "lucide-react" function linkifyText(text: string) { const urlRegex = /(https?:\/\/[^\s<]+[^\s<.,;:!?)\]}>])/ @@ -24,26 +24,9 @@ export function AIChat() { const [input, setInput] = useState("") const [loading, setLoading] = useState(false) const [error, setError] = useState("") - const [bootState, setBootState] = useState<"booting" | "ready" | "error">("booting") + const [ollamaStatus, setOllamaStatus] = useState(null) const messagesEndRef = useRef(null) - const checkServer = async () => { - try { - const res = await fetch("/api/ai/chat", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: "ping" }), - }) - if (res.status !== 503) { - setBootState("ready") - } else { - setTimeout(checkServer, 2000) - } - } catch { - setTimeout(checkServer, 2000) - } - } - useEffect(() => { fetch("/api/ai/jobs") .then((r) => r.json()) @@ -73,13 +56,29 @@ export function AIChat() { }, ]) }) - checkServer() }, []) + + useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) }, [messages]) + const checkOllama = async () => { + try { + const res = await fetch("/api/ai/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message: "__ping__" }), + }) + setOllamaStatus(res.status !== 503) + } catch { + setOllamaStatus(false) + } + } + + useEffect(() => { checkOllama() }, []) + const sendMessage = async () => { const msg = input.trim() if (!msg || loading) return @@ -97,8 +96,8 @@ export function AIChat() { }) if (!res.ok) { - const data = await res.json().catch(() => ({})) - throw new Error(data.error || `Error ${res.status}`) + const data = await res.json() + throw new Error(data.error || "Failed to get response") } const data = await res.json() @@ -122,86 +121,17 @@ export function AIChat() { } } - const bootOverlay = ( -
- -
-

Servers booting...

-
-
- - - - - - - - - - - - -
-
-
-
- ) - - const readyOverlay = ( -
-
-
- -
-
-

Try these commands:

-
- lists - leads -
-
-
-
- ) - - if (bootState === "booting") return bootOverlay - return (
- {bootState === "ready" && readyOverlay} + {ollamaStatus === false && ( +
+ + Ollama not responding. Start it with ollama serve + +
+ )}
{messages.map((msg, i) => ( @@ -233,10 +163,7 @@ export function AIChat() {
- - - - +
)} @@ -265,15 +192,10 @@ export function AIChat() { disabled={loading || !input.trim()} className="h-9 w-9 rounded-lg bg-[#1BB0CE] hover:bg-[#1BB0CE]/80 disabled:opacity-40 flex items-center justify-center flex-none transition-colors" > - {loading ? ( - - - - - ) : } + {loading ? : }
) -} \ No newline at end of file +} diff --git a/src/components/dashboard/crossed-lightsabers.tsx b/src/components/dashboard/crossed-lightsabers.tsx new file mode 100644 index 0000000..b431e51 --- /dev/null +++ b/src/components/dashboard/crossed-lightsabers.tsx @@ -0,0 +1,49 @@ +"use client" + +export function CrossedLightsabers() { + return ( + + ) +} diff --git a/src/components/dashboard/lead-status-chart.tsx b/src/components/dashboard/lead-status-chart.tsx index decc2c0..e3f2ca3 100644 --- a/src/components/dashboard/lead-status-chart.tsx +++ b/src/components/dashboard/lead-status-chart.tsx @@ -69,19 +69,6 @@ export function LeadStatusChart({ data }: LeadStatusChartProps) { className="h-full" > - {/* Spider watermark */} -
- - - - - - - - - -
- Lead Status diff --git a/src/components/dashboard/star-field.tsx b/src/components/dashboard/star-field.tsx new file mode 100644 index 0000000..3571565 --- /dev/null +++ b/src/components/dashboard/star-field.tsx @@ -0,0 +1,47 @@ +"use client" + +import { useMemo } from "react" + +interface Star { + left: number + top: number + size: number + delay: number + duration: number +} + +export function StarField() { + const stars = useMemo(() => { + const result: Star[] = [] + for (let i = 0; i < 160; i++) { + result.push({ + left: Math.random() * 100, + top: Math.random() * 100, + size: 1.5 + Math.random() * 2, + delay: Math.random() * 6, + duration: 3 + Math.random() * 4, + }) + } + return result + }, []) + + return ( +
+ {stars.map((s, i) => ( +
+ ))} +
+ ) +} diff --git a/src/components/dashboard/stat-card-skeleton.tsx b/src/components/dashboard/stat-card-skeleton.tsx index eec1d4c..f74f8d5 100644 --- a/src/components/dashboard/stat-card-skeleton.tsx +++ b/src/components/dashboard/stat-card-skeleton.tsx @@ -3,14 +3,11 @@ export function StatCardSkeleton() { return (
-

- THWIP! -

-

+

Loading your data...

-
+
) diff --git a/src/components/dashboard/stat-card.tsx b/src/components/dashboard/stat-card.tsx index 6d00d9c..4e8e4e5 100644 --- a/src/components/dashboard/stat-card.tsx +++ b/src/components/dashboard/stat-card.tsx @@ -118,37 +118,6 @@ export function StatCard({ title, value, icon: Icon, description, index = 0, tre transition={{ duration: 0.3, delay: index * 0.05 }} className="relative" > - {/* Web overlay decorative */} -
- - {Array.from({ length: 6 }).map((_, i) => ( - - ))} - {Array.from({ length: 6 }).map((_, i) => ( - - ))} - {Array.from({ length: 6 }).map((_, i) => ( - - ))} - -
- - {/* Comic action lines on hover */} -
- - - - - - - - - - - - -
- {/* Red/Blue gradient top strip */}
diff --git a/src/components/layout/app-shell.tsx b/src/components/layout/app-shell.tsx index 8459dce..d17fc83 100644 --- a/src/components/layout/app-shell.tsx +++ b/src/components/layout/app-shell.tsx @@ -34,51 +34,7 @@ export function AppShell({ children }: AppShellProps) { } return ( -
- {/* Spider-Man top gradient bar */} -
- - {/* Corner spider webs */} -
- - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - -
+