diff --git a/README.md b/README.md index 6783622..33fc597 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # HRIStudio -A modern web application for managing human-robot interaction studies, built with Next.js 14, TypeScript, and the App Router. +A modern web application for managing human-robot interaction studies, built with Next.js 15, TypeScript, and the App Router. ## Tech Stack -- **Framework**: Next.js 14 with App Router +- **Framework**: Next.js 15 with App Router - **Language**: TypeScript - **Authentication**: NextAuth.js - **Database**: PostgreSQL with Drizzle ORM diff --git a/bun.lock b/bun.lock index 34ad6b3..b84c91b 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ "@aws-sdk/lib-storage": "^3.735.0", "@aws-sdk/s3-request-presigner": "^3.735.0", "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.5", @@ -18,26 +19,27 @@ "@radix-ui/react-select": "^2.1.5", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slider": "^1.2.2", - "@radix-ui/react-slot": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.2", - "@radix-ui/react-toast": "^1.2.5", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.7", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", "@trpc/client": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446", + "@types/nodemailer": "^6.4.14", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^4.1.0", - "drizzle-orm": "^0.33.0", + "drizzle-orm": "^0.39.3", "framer-motion": "^12.0.6", "geist": "^1.3.1", "lucide-react": "^0.474.0", - "next": "^15.0.1", + "next": "^15.1.7", "next-auth": "^4.24.11", + "nodemailer": "^6.10.0", "postgres": "^3.4.4", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -58,7 +60,7 @@ "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", - "drizzle-kit": "^0.24.0", + "drizzle-kit": "^0.30.4", "eslint": "^8.57.0", "eslint-config-next": "^15.0.1", "eslint-plugin-drizzle": "^0.2.3", @@ -289,25 +291,25 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], - "@next/env": ["@next/env@15.1.6", "", {}, "sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w=="], + "@next/env": ["@next/env@15.1.7", "", {}, "sha512-d9jnRrkuOH7Mhi+LHav2XW91HOgTAWHxjMPkXMGBc9B2b7614P7kjt8tAplRvJpbSt4nbO1lugcT/kAaWzjlLQ=="], "@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.1.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-+slMxhTgILUntZDGNgsKEYHUvpn72WP1YTlkmEhS51vnVd7S9jEEy0n9YAMcI21vUG4akTw9voWH02lrClt/yw=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.1.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hPFwzPJDpA8FGj7IKV3Yf1web3oz2YsR8du4amKw8d+jAOHfYHYFpMkoF6vgSY4W6vB29RtZEklK9ayinGiCmQ=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.1.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-2qoas+fO3OQKkU0PBUfwTiw/EYpN+kdAx62cePRyY1LqKtP09Vp5UcUntfZYajop5fDFTjSxCHfZVRxzi+9FYQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-sKLLwDX709mPdzxMnRIXLIT9zaX2w0GUlkLYQnKGoXeWUhcvpCrK+yevcwCJPdTdxZEUA0mOXGLdPsGkudGdnA=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.1.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-zblK1OQbQWdC8fxdX4fpsHDw+VSpBPGEUX4PhSE9hkaWPrWoeIJn+baX53vbsbDRaDKd7bBNcXRovY1hEhFd7w=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-GOzXutxuLvLHFDAPsMP2zDBMl1vfUHHpdNpFGhxu90jEzH6nNIgmtw/s1MDwpTOiM+MT5V8+I1hmVFeAUhkbgQ=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.1.7", "", { "os": "linux", "cpu": "x64" }, "sha512-WrZ7jBhR7ATW1z5iEQ0ZJfE2twCNSXbpCSaAunF3BKcVeHFADSI/AW1y5Xt3DzTqPF1FzQlwQTewqetAABhZRQ=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.1.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-LDnj1f3OVbou1BqvvXVqouJZKcwq++mV2F+oFHptToZtScIEnhNRJAhJzqAtTE2dB31qDYL45xJwrc+bLeKM2Q=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.1.7", "", { "os": "win32", "cpu": "x64" }, "sha512-dC01f1quuf97viOfW05/K8XYv2iuBgAxJZl7mbCKEjMgdQl5JjAKJ0D2qMKZCgPWDeFbFT0Q0nYWwytEW0DWTQ=="], "@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=="], @@ -325,6 +327,8 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="], + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ=="], + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="], "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.2", "", { "dependencies": { "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GaC7bXQZ5VgZvVvsJ5mu/AEbjYLnhhkoidOboC50Z6FFlLA03wG2ianUoH+zgDQ31/9gCF59bE4+2bBgTyMiig=="], @@ -363,9 +367,9 @@ "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="], - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw=="], "@radix-ui/react-select": ["@radix-ui/react-select@2.1.5", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA=="], @@ -373,11 +377,9 @@ "@radix-ui/react-slider": ["@radix-ui/react-slider@1.2.2", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sNlU06ii1/ZcbHf8I9En54ZPW0Vil/yPVg4vQMcFNjrIx51jsHbFl1HYHQvCIWJSr1q0ZmA+iIs/ZTv8h7HHSA=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="], - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-roving-focus": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ=="], - - "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZzUsAaOx8NdXZZKcFNDhbSlbsCUy8qQWmzTdgrlrhhZAOx2ofLtKrBDW9fkqhFvXgmtv560Uj16pkLkqML7SHA=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng=="], "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.1.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ss0s80BC0+g0+Zc53MvilcnTYSOi4mSuFWBPYPuTOFGjx+pUU+ZrmamMNwS56t8MTFlniA5ocjd4jYm/CdhbOg=="], @@ -533,6 +535,8 @@ "@types/node": ["@types/node@20.17.16", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw=="], + "@types/nodemailer": ["@types/nodemailer@6.4.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww=="], + "@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="], "@types/react": ["@types/react@18.3.18", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ=="], @@ -699,9 +703,9 @@ "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], - "drizzle-kit": ["drizzle-kit@0.24.2", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.1", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-nXOaTSFiuIaTMhS8WJC2d4EBeIcN9OSt2A2cyFbQYBAZbi7lRsVGJNqDpEwPqYfJz38yxbY/UtbvBBahBfnExQ=="], + "drizzle-kit": ["drizzle-kit@0.30.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-B2oJN5UkvwwNHscPWXDG5KqAixu7AUzZ3qbe++KU9SsQ+cZWR4DXEPYcvWplyFAno0dhRJECNEhNxiDmFaPGyQ=="], - "drizzle-orm": ["drizzle-orm@0.33.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", "@electric-sql/pglite": ">=0.1.1", "@libsql/client": "*", "@neondatabase/serverless": ">=0.1", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=13.2.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/react", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "react", "sql.js", "sqlite3"] }, "sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA=="], + "drizzle-orm": ["drizzle-orm@0.39.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -993,10 +997,12 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - "next": ["next@15.1.6", "", { "dependencies": { "@next/env": "15.1.6", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.1.6", "@next/swc-darwin-x64": "15.1.6", "@next/swc-linux-arm64-gnu": "15.1.6", "@next/swc-linux-arm64-musl": "15.1.6", "@next/swc-linux-x64-gnu": "15.1.6", "@next/swc-linux-x64-musl": "15.1.6", "@next/swc-win32-arm64-msvc": "15.1.6", "@next/swc-win32-x64-msvc": "15.1.6", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "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-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q=="], + "next": ["next@15.1.7", "", { "dependencies": { "@next/env": "15.1.7", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.1.7", "@next/swc-darwin-x64": "15.1.7", "@next/swc-linux-arm64-gnu": "15.1.7", "@next/swc-linux-arm64-musl": "15.1.7", "@next/swc-linux-x64-gnu": "15.1.7", "@next/swc-linux-x64-musl": "15.1.7", "@next/swc-win32-arm64-msvc": "15.1.7", "@next/swc-win32-x64-msvc": "15.1.7", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "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-GNeINPGS9c6OZKCvKypbL8GTsT5GhWPp4DM0fzkXJuXMilOO2EeFxuAY6JZbtk6XIl6Ws10ag3xRINDjSO5+wg=="], "next-auth": ["next-auth@4.24.11", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.7.0", "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" }, "peerDependencies": { "@auth/core": "0.34.2", "next": "^12.2.5 || ^13 || ^14 || ^15", "nodemailer": "^6.6.5", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, "optionalPeers": ["@auth/core", "nodemailer"] }, "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw=="], + "nodemailer": ["nodemailer@6.10.0", "", {}, "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA=="], + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], "normalize-wheel": ["normalize-wheel@1.0.1", "", {}, "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="], @@ -1321,6 +1327,60 @@ "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], + "@radix-ui/react-alert-dialog/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="], + + "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-avatar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-menu/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw=="], + + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="], + + "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-slider/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + "@smithy/middleware-retry/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -1329,6 +1389,8 @@ "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="], + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -1421,8 +1483,40 @@ "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "@radix-ui/react-alert-dialog/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="], + + "@radix-ui/react-alert-dialog/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA=="], + + "@radix-ui/react-alert-dialog/@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="], + + "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-avatar/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-label/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-separator/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-slider/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "cmdk/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..eaa8fcf --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,5 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 34c75c3..b2cc19b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/lib-storage": "^3.735.0", "@aws-sdk/s3-request-presigner": "^3.735.0", "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.5", @@ -22,26 +23,28 @@ "@radix-ui/react-select": "^2.1.5", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slider": "^1.2.2", - "@radix-ui/react-slot": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.2", - "@radix-ui/react-toast": "^1.2.5", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.7", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", "@trpc/client": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446", + "@types/nodemailer": "^6.4.14", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^4.1.0", - "drizzle-orm": "^0.33.0", + "drizzle-orm": "^0.39.3", "framer-motion": "^12.0.6", "geist": "^1.3.1", "lucide-react": "^0.474.0", - "next": "^15.0.1", + "next": "^15.1.7", "next-auth": "^4.24.11", + "nodemailer": "^6.10.0", "postgres": "^3.4.4", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -62,7 +65,7 @@ "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", - "drizzle-kit": "^0.24.0", + "drizzle-kit": "^0.30.4", "eslint": "^8.57.0", "eslint-config-next": "^15.0.1", "eslint-plugin-drizzle": "^0.2.3", @@ -1346,7 +1349,9 @@ } }, "node_modules/@next/env": { - "version": "15.1.6", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.7.tgz", + "integrity": "sha512-d9jnRrkuOH7Mhi+LHav2XW91HOgTAWHxjMPkXMGBc9B2b7614P7kjt8tAplRvJpbSt4nbO1lugcT/kAaWzjlLQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1384,7 +1389,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.1.6", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.7.tgz", + "integrity": "sha512-hPFwzPJDpA8FGj7IKV3Yf1web3oz2YsR8du4amKw8d+jAOHfYHYFpMkoF6vgSY4W6vB29RtZEklK9ayinGiCmQ==", "cpu": [ "arm64" ], @@ -1398,12 +1405,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.6.tgz", - "integrity": "sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.7.tgz", + "integrity": "sha512-2qoas+fO3OQKkU0PBUfwTiw/EYpN+kdAx62cePRyY1LqKtP09Vp5UcUntfZYajop5fDFTjSxCHfZVRxzi+9FYQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1413,12 +1421,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.6.tgz", - "integrity": "sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.7.tgz", + "integrity": "sha512-sKLLwDX709mPdzxMnRIXLIT9zaX2w0GUlkLYQnKGoXeWUhcvpCrK+yevcwCJPdTdxZEUA0mOXGLdPsGkudGdnA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1428,12 +1437,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.6.tgz", - "integrity": "sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.7.tgz", + "integrity": "sha512-zblK1OQbQWdC8fxdX4fpsHDw+VSpBPGEUX4PhSE9hkaWPrWoeIJn+baX53vbsbDRaDKd7bBNcXRovY1hEhFd7w==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1443,12 +1453,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.6.tgz", - "integrity": "sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.7.tgz", + "integrity": "sha512-GOzXutxuLvLHFDAPsMP2zDBMl1vfUHHpdNpFGhxu90jEzH6nNIgmtw/s1MDwpTOiM+MT5V8+I1hmVFeAUhkbgQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1458,12 +1469,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.6.tgz", - "integrity": "sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.7.tgz", + "integrity": "sha512-WrZ7jBhR7ATW1z5iEQ0ZJfE2twCNSXbpCSaAunF3BKcVeHFADSI/AW1y5Xt3DzTqPF1FzQlwQTewqetAABhZRQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1473,12 +1485,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.6.tgz", - "integrity": "sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.7.tgz", + "integrity": "sha512-LDnj1f3OVbou1BqvvXVqouJZKcwq++mV2F+oFHptToZtScIEnhNRJAhJzqAtTE2dB31qDYL45xJwrc+bLeKM2Q==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1488,12 +1501,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.6.tgz", - "integrity": "sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.7.tgz", + "integrity": "sha512-dC01f1quuf97viOfW05/K8XYv2iuBgAxJZl7mbCKEjMgdQl5JjAKJ0D2qMKZCgPWDeFbFT0Q0nYWwytEW0DWTQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1562,6 +1576,57 @@ "version": "1.1.1", "license": "MIT" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.6.tgz", + "integrity": "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.6", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.1", "license": "MIT", @@ -1659,6 +1724,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.1", "license": "MIT", @@ -1686,23 +1769,124 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.5", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", + "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.2" + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz", + "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz", + "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz", + "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1895,6 +2079,24 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.5", "license": "MIT", @@ -1930,6 +2132,24 @@ } } }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.1", "license": "MIT", @@ -2025,6 +2245,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.1", "license": "MIT", @@ -2095,6 +2333,24 @@ } } }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.1", "license": "MIT", @@ -2150,7 +2406,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.1.1", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" @@ -2165,10 +2423,62 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz", + "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz", - "integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz", + "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", @@ -2176,8 +2486,8 @@ "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.1", - "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { @@ -2195,24 +2505,70 @@ } } }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.5.tgz", - "integrity": "sha512-ZzUsAaOx8NdXZZKcFNDhbSlbsCUy8qQWmzTdgrlrhhZAOx2ofLtKrBDW9fkqhFvXgmtv560Uj16pkLkqML7SHA==", + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz", + "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", + "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", + "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.1", - "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.4", - "@radix-ui/react-portal": "1.1.3", - "@radix-ui/react-presence": "1.1.2", - "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", - "@radix-ui/react-use-controllable-state": "1.1.0", - "@radix-ui/react-use-layout-effect": "1.1.0", - "@radix-ui/react-visually-hidden": "1.1.1" + "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -2261,6 +2617,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "license": "MIT", @@ -3164,12 +3538,20 @@ }, "node_modules/@types/node": { "version": "20.17.16", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.14", "devOptional": true, @@ -4154,11 +4536,13 @@ } }, "node_modules/drizzle-kit": { - "version": "0.24.2", + "version": "0.30.4", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.4.tgz", + "integrity": "sha512-B2oJN5UkvwwNHscPWXDG5KqAixu7AUzZ3qbe++KU9SsQ+cZWR4DXEPYcvWplyFAno0dhRJECNEhNxiDmFaPGyQ==", "dev": true, "license": "MIT", "dependencies": { - "@drizzle-team/brocli": "^0.10.1", + "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0" @@ -4168,14 +4552,17 @@ } }, "node_modules/drizzle-orm": { - "version": "0.33.0", + "version": "0.39.3", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.39.3.tgz", + "integrity": "sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw==", "license": "Apache-2.0", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", - "@cloudflare/workers-types": ">=3", - "@electric-sql/pglite": ">=0.1.1", - "@libsql/client": "*", - "@neondatabase/serverless": ">=0.1", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", @@ -4183,19 +4570,17 @@ "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", - "@types/react": ">=18", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", - "expo-sqlite": ">=13.2.0", + "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", - "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, @@ -4212,6 +4597,9 @@ "@libsql/client": { "optional": true }, + "@libsql/client-wasm": { + "optional": true + }, "@neondatabase/serverless": { "optional": true }, @@ -4236,9 +4624,6 @@ "@types/pg": { "optional": true }, - "@types/react": { - "optional": true - }, "@types/sql.js": { "optional": true }, @@ -4275,9 +4660,6 @@ "prisma": { "optional": true }, - "react": { - "optional": true - }, "sql.js": { "optional": true }, @@ -6180,10 +6562,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.1.6", + "version": "15.1.7", + "resolved": "https://registry.npmjs.org/next/-/next-15.1.7.tgz", + "integrity": "sha512-GNeINPGS9c6OZKCvKypbL8GTsT5GhWPp4DM0fzkXJuXMilOO2EeFxuAY6JZbtk6XIl6Ws10ag3xRINDjSO5+wg==", "license": "MIT", "dependencies": { - "@next/env": "15.1.6", + "@next/env": "15.1.7", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", @@ -6198,14 +6582,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.1.6", - "@next/swc-darwin-x64": "15.1.6", - "@next/swc-linux-arm64-gnu": "15.1.6", - "@next/swc-linux-arm64-musl": "15.1.6", - "@next/swc-linux-x64-gnu": "15.1.6", - "@next/swc-linux-x64-musl": "15.1.6", - "@next/swc-win32-arm64-msvc": "15.1.6", - "@next/swc-win32-x64-msvc": "15.1.6", + "@next/swc-darwin-arm64": "15.1.7", + "@next/swc-darwin-x64": "15.1.7", + "@next/swc-linux-arm64-gnu": "15.1.7", + "@next/swc-linux-arm64-musl": "15.1.7", + "@next/swc-linux-x64-gnu": "15.1.7", + "@next/swc-linux-x64-musl": "15.1.7", + "@next/swc-win32-arm64-msvc": "15.1.7", + "@next/swc-win32-x64-msvc": "15.1.7", "sharp": "^0.33.5" }, "peerDependencies": { @@ -6287,6 +6671,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/nodemailer": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "license": "MIT", @@ -8042,7 +8435,6 @@ }, "node_modules/undici-types": { "version": "6.19.8", - "dev": true, "license": "MIT" }, "node_modules/uri-js": { diff --git a/package.json b/package.json index c20318a..a1c6b84 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@aws-sdk/lib-storage": "^3.735.0", "@aws-sdk/s3-request-presigner": "^3.735.0", "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-alert-dialog": "^1.1.6", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.5", @@ -37,24 +38,28 @@ "@radix-ui/react-select": "^2.1.5", "@radix-ui/react-separator": "^1.1.1", "@radix-ui/react-slider": "^1.2.2", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", + "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.7", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/react-query": "^5.50.0", "@trpc/client": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446", + "@types/nodemailer": "^6.4.14", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", "date-fns": "^4.1.0", - "drizzle-orm": "^0.33.0", + "drizzle-orm": "^0.39.3", "framer-motion": "^12.0.6", "geist": "^1.3.1", "lucide-react": "^0.474.0", - "next": "^15.0.1", + "next": "^15.1.7", "next-auth": "^4.24.11", + "nodemailer": "^6.10.0", "postgres": "^3.4.4", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -75,7 +80,7 @@ "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", - "drizzle-kit": "^0.24.0", + "drizzle-kit": "^0.30.4", "eslint": "^8.57.0", "eslint-config-next": "^15.0.1", "eslint-plugin-drizzle": "^0.2.3", diff --git a/src/app/_components/auth/sign-up-form.tsx b/src/app/_components/auth/sign-up-form.tsx deleted file mode 100644 index e55d4fe..0000000 --- a/src/app/_components/auth/sign-up-form.tsx +++ /dev/null @@ -1,149 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { signIn } from "next-auth/react"; -import { useRouter } from "next/navigation"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { Button } from "~/components/ui/button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "~/components/ui/form"; -import { Input } from "~/components/ui/input"; -import { useToast } from "~/components/ui/use-toast"; -import { api } from "~/trpc/react"; - -const formSchema = z.object({ - email: z.string().email(), - password: z.string().min(8), - name: z.string().min(1).max(256).optional(), -}); - -type FormValues = z.infer; - -export function SignUpForm() { - const router = useRouter(); - const { toast } = useToast(); - const [isLoading, setIsLoading] = useState(false); - const createUser = api.user.create.useMutation(); - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - email: "", - password: "", - name: "", - }, - }); - - async function onSubmit(data: FormValues) { - setIsLoading(true); - - try { - await createUser.mutateAsync(data); - - const result = await signIn("credentials", { - redirect: false, - email: data.email, - password: data.password, - }); - - if (result?.error) { - toast({ - variant: "destructive", - title: "Error", - description: "Something went wrong. Please try again.", - }); - return; - } - - router.refresh(); - router.push("/"); - } catch (error) { - if (error instanceof Error) { - toast({ - variant: "destructive", - title: "Error", - description: error.message, - }); - } else { - toast({ - variant: "destructive", - title: "Error", - description: "Something went wrong. Please try again.", - }); - } - } finally { - setIsLoading(false); - } - } - - return ( -
- - ( - - Email - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - ( - - Name - - - - - - )} - /> - - - - ); -} \ No newline at end of file diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index 728f7a6..2309044 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -3,6 +3,7 @@ import { NextResponse } from "next/server"; import { z } from "zod"; import { db } from "~/server/db"; import { users } from "~/server/db/schema"; +import { randomUUID } from "crypto"; const registerSchema = z.object({ firstName: z.string().min(1, "First name is required"), @@ -45,13 +46,14 @@ export async function POST(req: Request) { const hashedPassword = await hash(password, 10); await db.insert(users).values({ + id: randomUUID(), firstName, lastName, email, password: hashedPassword, }); - return NextResponse.redirect(new URL("/login", req.url)); + return NextResponse.json({ success: true }); } catch (error) { console.error(error); return NextResponse.json( diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index aaa63cd..8eb5f8a 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -1,67 +1,77 @@ -import { type Metadata } from "next"; +import type { Metadata } from "next"; import Link from "next/link"; -import { SignInForm } from "~/app/_components/auth/sign-in-form"; -import { buttonVariants } from "~/components/ui/button"; -import { cn } from "~/lib/utils"; +import { redirect } from "next/navigation"; +import { getServerAuthSession } from "~/server/auth"; + +import { + Card, + CardContent, + CardDescription, + CardTitle, +} from "~/components/ui/card"; +import { SignInForm } from "~/components/auth/sign-in-form"; +import { Logo } from "~/components/logo"; export const metadata: Metadata = { - title: "Sign In", + title: "Sign In | HRIStudio", description: "Sign in to your account", }; -export default function SignInPage() { +export default async function SignInPage({ + searchParams, +}: { + searchParams: Promise> +}) { + const session = await getServerAuthSession(); + if (session) { + redirect("/dashboard"); + } + + const params = await searchParams; + const error = params?.error ? String(params.error) : null; + const showError = error === "CredentialsSignin"; + return ( -
-
-
-
- + +
+ + +
+
+ + Welcome back + + + Sign in to your account to continue + +
+ +
+
+
+
+ +
+
+ + +

+ Don't have an account?{" "} + - - - HRI Studio -

-
-
-

- “HRI Studio has revolutionized how we conduct human-robot interaction studies.” -

-
Sofia Dewar
-
-
-
-
-
-
-

- Welcome back -

-

- Enter your email to sign in to your account -

-
- -

- - Don't have an account? Sign Up - -

-
+ Sign up + +

); diff --git a/src/app/auth/signup/page.tsx b/src/app/auth/signup/page.tsx index dfb746c..8d84fcf 100644 --- a/src/app/auth/signup/page.tsx +++ b/src/app/auth/signup/page.tsx @@ -1,67 +1,77 @@ -import { type Metadata } from "next"; +import type { Metadata } from "next"; import Link from "next/link"; -import { SignUpForm } from "~/app/_components/auth/sign-up-form"; -import { buttonVariants } from "~/components/ui/button"; -import { cn } from "~/lib/utils"; +import { redirect } from "next/navigation"; +import { getServerAuthSession } from "~/server/auth"; + +import { + Card, + CardContent, + CardDescription, + CardTitle, +} from "~/components/ui/card"; +import { SignUpForm } from "~/components/auth/sign-up-form"; +import { Logo } from "~/components/logo"; export const metadata: Metadata = { - title: "Sign Up", + title: "Sign Up | HRIStudio", description: "Create a new account", }; -export default function SignUpPage() { +export default async function SignUpPage({ + searchParams, +}: { + searchParams: Promise> +}) { + const session = await getServerAuthSession(); + if (session) { + redirect("/dashboard"); + } + + const params = await searchParams; + const error = params?.error ? String(params.error) : null; + const showError = error === "CredentialsSignin"; + return ( -
-
-
-
- + +
+ + +
+
+ + Create an account + + + Get started with HRIStudio + +
+ +
+
+
+
+ +
+
+ + +

+ Already have an account?{" "} + - - - HRI Studio -

-
-
-

- “HRI Studio has revolutionized how we conduct human-robot interaction studies.” -

-
Sofia Dewar
-
-
-
-
-
-
-

- Create an account -

-

- Enter your email below to create your account -

-
- -

- - Already have an account? Sign In - -

-
+ Sign in + +

); diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index d8dd40b..e8ffd60 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react" import { useRouter } from "next/navigation" import { useSession } from "next-auth/react" +import { api } from "~/trpc/react" import { AppSidebar } from "~/components/navigation/app-sidebar" import { Header } from "~/components/navigation/header" @@ -18,14 +19,29 @@ export default function DashboardLayout({ const { data: session, status } = useSession() const router = useRouter() + // Get user's studies + const { data: studies, isLoading: isLoadingStudies } = api.study.getMyStudies.useQuery( + undefined, + { + enabled: status === "authenticated", + } + ); + useEffect(() => { if (status === "unauthenticated") { - router.replace("/login") + router.replace("/auth/signin") } }, [status, router]) + useEffect(() => { + // Only redirect if we've loaded studies and user has none + if (!isLoadingStudies && studies && studies.length === 0) { + router.replace("/onboarding") + } + }, [studies, isLoadingStudies, router]) + // Show nothing while loading - if (status === "loading") { + if (status === "loading" || isLoadingStudies) { return null } @@ -34,6 +50,11 @@ export default function DashboardLayout({ return null } + // Show nothing if no studies (will redirect to onboarding) + if (studies && studies.length === 0) { + return null + } + return ( diff --git a/src/app/dashboard/studies/[id]/delete-study-button.tsx b/src/app/dashboard/studies/[id]/delete-study-button.tsx new file mode 100644 index 0000000..ce1e29c --- /dev/null +++ b/src/app/dashboard/studies/[id]/delete-study-button.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { Trash2 } from "lucide-react"; +import { Button } from "~/components/ui/button"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "~/components/ui/alert-dialog"; +import { api } from "~/trpc/react"; + +interface DeleteStudyButtonProps { + id: number; +} + +export default function DeleteStudyButton({ id }: DeleteStudyButtonProps) { + const [open, setOpen] = useState(false); + const router = useRouter(); + const { mutate: deleteStudy, isLoading } = api.study.delete.useMutation({ + onSuccess: () => { + router.push("/studies"); + router.refresh(); + }, + }); + + return ( + + + + + + + Are you sure? + + This action cannot be undone. This will permanently delete the study + and all associated data. + + + + Cancel + deleteStudy({ id })} + disabled={isLoading} + > + {isLoading ? "Deleting..." : "Delete"} + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/[id]/edit/page.tsx b/src/app/dashboard/studies/[id]/edit/page.tsx new file mode 100644 index 0000000..d47904c --- /dev/null +++ b/src/app/dashboard/studies/[id]/edit/page.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { StudyForm, type StudyFormValues } from "~/components/studies/study-form"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { use } from "react"; + +export default function EditStudyPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter(); + const resolvedParams = use(params); + const id = Number(resolvedParams.id); + + const { data: study, isLoading: isLoadingStudy } = api.study.getById.useQuery( + { id } + ); + + const { mutate: updateStudy, isPending: isUpdating } = api.study.update.useMutation({ + onSuccess: () => { + router.push(`/dashboard/studies/${id}`); + router.refresh(); + }, + }); + + function onSubmit(data: StudyFormValues) { + updateStudy({ id, ...data }); + } + + if (isLoadingStudy) { + return
Loading...
; + } + + if (!study) { + return
Study not found
; + } + + return ( + <> + + + + + Study Details + + Update the information for your study. + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/[id]/page.tsx b/src/app/dashboard/studies/[id]/page.tsx new file mode 100644 index 0000000..855c11d --- /dev/null +++ b/src/app/dashboard/studies/[id]/page.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { api } from "~/trpc/react"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Button } from "~/components/ui/button"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs"; +import { Pencil as PencilIcon } from "lucide-react"; +import { use } from "react"; +import { StudyOverview } from "~/components/studies/study-overview"; +import { StudyParticipants } from "~/components/studies/study-participants"; +import { StudyMembers } from "~/components/studies/study-members"; +import { StudyMetadata } from "~/components/studies/study-metadata"; +import { StudyActivity } from "~/components/studies/study-activity"; + +export default function StudyPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const resolvedParams = use(params); + const id = Number(resolvedParams.id); + const activeTab = searchParams.get("tab") ?? "overview"; + + const { data: study, isLoading: isLoadingStudy } = api.study.getById.useQuery({ id }); + + if (isLoadingStudy) { + return
Loading...
; + } + + if (!study) { + return
Study not found
; + } + + const canEdit = study.role === "admin"; + + return ( + <> + + {canEdit && ( + + )} + + + + + Overview + Participants + Members + Metadata + Activity + + + + + + + + + + + + + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/[id]/participants/[participantId]/edit/page.tsx b/src/app/dashboard/studies/[id]/participants/[participantId]/edit/page.tsx new file mode 100644 index 0000000..29b13ed --- /dev/null +++ b/src/app/dashboard/studies/[id]/participants/[participantId]/edit/page.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { ParticipantForm, type ParticipantFormValues } from "~/components/participants/participant-form"; +import { use } from "react"; +import { useToast } from "~/hooks/use-toast"; +import { ROLES } from "~/lib/permissions/constants"; + +export default function EditParticipantPage({ + params, +}: { + params: Promise<{ id: string; participantId: string }>; +}) { + const router = useRouter(); + const { toast } = useToast(); + const resolvedParams = use(params); + const studyId = Number(resolvedParams.id); + const participantId = Number(resolvedParams.participantId); + + const { data: study } = api.study.getById.useQuery({ id: studyId }); + const { data: participant, isLoading } = api.participant.getById.useQuery({ id: participantId }); + + const { mutate: updateParticipant, isPending: isUpdating } = api.participant.update.useMutation({ + onSuccess: () => { + toast({ + title: "Success", + description: "Participant updated successfully", + }); + router.push(`/dashboard/studies/${studyId}/participants/${participantId}`); + router.refresh(); + }, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + }); + + function onSubmit(data: ParticipantFormValues) { + updateParticipant({ + id: participantId, + ...data, + }); + } + + if (isLoading) { + return
Loading...
; + } + + if (!study || !participant) { + return
Not found
; + } + + // Check if user has permission to edit participants + const canManageParticipants = [ROLES.OWNER, ROLES.ADMIN, ROLES.PRINCIPAL_INVESTIGATOR] + .map(r => r.toLowerCase()) + .includes(study.role.toLowerCase()); + + if (!canManageParticipants) { + return
You do not have permission to edit participants in this study.
; + } + + return ( + <> + + + + + Participant Details + + Update the participant's information. Fields marked with * are required. + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/[id]/participants/[participantId]/page.tsx b/src/app/dashboard/studies/[id]/participants/[participantId]/page.tsx new file mode 100644 index 0000000..78b851d --- /dev/null +++ b/src/app/dashboard/studies/[id]/participants/[participantId]/page.tsx @@ -0,0 +1,182 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Button } from "~/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { Pencil as PencilIcon, Trash as TrashIcon } from "lucide-react"; +import { use } from "react"; +import { Badge } from "~/components/ui/badge"; +import { useToast } from "~/hooks/use-toast"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "~/components/ui/alert-dialog"; +import { ROLES } from "~/lib/permissions/constants"; + +export default function ParticipantDetailsPage({ + params, +}: { + params: Promise<{ id: string; participantId: string }>; +}) { + const router = useRouter(); + const { toast } = useToast(); + const resolvedParams = use(params); + const studyId = Number(resolvedParams.id); + const participantId = Number(resolvedParams.participantId); + + const { data: study } = api.study.getById.useQuery({ id: studyId }); + const { data: participant, isLoading } = api.participant.getById.useQuery({ id: participantId }); + + const { mutate: deleteParticipant, isPending: isDeleting } = api.participant.delete.useMutation({ + onSuccess: () => { + toast({ + title: "Success", + description: "Participant deleted successfully", + }); + router.push(`/dashboard/studies/${studyId}/participants`); + router.refresh(); + }, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + }); + + if (isLoading) { + return
Loading...
; + } + + if (!study || !participant) { + return
Not found
; + } + + const canViewIdentifiableInfo = [ROLES.OWNER, ROLES.ADMIN, ROLES.PRINCIPAL_INVESTIGATOR] + .map(r => r.toLowerCase()) + .includes(study.role.toLowerCase()); + const canManageParticipants = [ROLES.OWNER, ROLES.ADMIN, ROLES.PRINCIPAL_INVESTIGATOR] + .map(r => r.toLowerCase()) + .includes(study.role.toLowerCase()); + + return ( + <> + + {canManageParticipants && ( +
+ + + + + + + + Are you sure? + + This action cannot be undone. This will permanently delete the participant and all + associated data. + + + + Cancel + deleteParticipant({ id: participantId })} + disabled={isDeleting} + > + {isDeleting ? "Deleting..." : "Delete"} + + + + +
+ )} +
+ +
+ + + Basic Information + {!canViewIdentifiableInfo && ( + + Some information is redacted based on your role. + + )} + + +
+
+
Identifier
+
+ {canViewIdentifiableInfo ? participant.identifier || "—" : "REDACTED"} +
+
+
+
Status
+
+ + {participant.status} + +
+
+
+
Name
+
+ {canViewIdentifiableInfo + ? participant.firstName && participant.lastName + ? `${participant.firstName} ${participant.lastName}` + : "—" + : "REDACTED"} +
+
+
+
Email
+
+ {canViewIdentifiableInfo ? participant.email || "—" : "REDACTED"} +
+
+
+
+
+ + + + Notes + Additional information about this participant + + +

+ {participant.notes || "No notes available."} +

+
+
+
+
+ + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/[id]/participants/new/page.tsx b/src/app/dashboard/studies/[id]/participants/new/page.tsx new file mode 100644 index 0000000..a206dcb --- /dev/null +++ b/src/app/dashboard/studies/[id]/participants/new/page.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { ParticipantForm, type ParticipantFormValues } from "~/components/participants/participant-form"; +import { use } from "react"; +import { useToast } from "~/hooks/use-toast"; +import { ROLES } from "~/lib/permissions/constants"; + +function generateIdentifier(studyId: number, count: number) { + // Format: P001, P002, etc. with study prefix + const paddedCount = String(count + 1).padStart(3, '0'); + return `P${paddedCount}`; +} + +export default function NewParticipantPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter(); + const { toast } = useToast(); + const resolvedParams = use(params); + const studyId = Number(resolvedParams.id); + + const { data: study } = api.study.getById.useQuery({ id: studyId }); + const { data: participantCount = 0 } = api.participant.getCount.useQuery( + { studyId }, + { enabled: !!study } + ); + + const { mutate: createParticipant, isPending: isCreating } = api.participant.create.useMutation({ + onSuccess: () => { + toast({ + title: "Success", + description: "Participant added successfully", + }); + router.push(`/dashboard/studies/${studyId}/participants`); + router.refresh(); + }, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + }); + + function onSubmit(data: ParticipantFormValues) { + createParticipant({ + studyId, + ...data, + }); + } + + if (!study) { + return
Study not found
; + } + + // Check if user has permission to add participants + const canAddParticipants = [ROLES.OWNER, ROLES.ADMIN, ROLES.PRINCIPAL_INVESTIGATOR] + .map(r => r.toLowerCase()) + .includes(study.role.toLowerCase()); + + if (!canAddParticipants) { + return
You do not have permission to add participants to this study.
; + } + + return ( + <> + + + + + Participant Details + + Enter the participant's information. Fields marked with * are required. + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/[id]/participants/page.tsx b/src/app/dashboard/studies/[id]/participants/page.tsx new file mode 100644 index 0000000..2d953b1 --- /dev/null +++ b/src/app/dashboard/studies/[id]/participants/page.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { use } from "react"; +import { StudyParticipants } from "~/components/studies/study-participants"; + +export default function ParticipantsPage({ params }: { params: Promise<{ id: string }> }) { + const router = useRouter(); + const resolvedParams = use(params); + const studyId = Number(resolvedParams.id); + + const { data: study, isLoading } = api.study.getById.useQuery({ id: studyId }); + + if (isLoading) { + return
Loading...
; + } + + if (!study) { + return
Study not found
; + } + + return ( + <> + + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/new/page.tsx b/src/app/dashboard/studies/new/page.tsx new file mode 100644 index 0000000..a11ab11 --- /dev/null +++ b/src/app/dashboard/studies/new/page.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { StudyForm, type StudyFormValues } from "~/components/studies/study-form"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; + +export default function NewStudyPage() { + const router = useRouter(); + + const { mutate: createStudy, isPending: isCreating } = api.study.create.useMutation({ + onSuccess: (data) => { + router.push(`/dashboard/studies/${data.id}`); + router.refresh(); + }, + }); + + function onSubmit(data: StudyFormValues) { + createStudy(data); + } + + return ( + <> + + + + + Study Details + + Enter the information for your new study. + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/app/dashboard/studies/page.tsx b/src/app/dashboard/studies/page.tsx new file mode 100644 index 0000000..9386859 --- /dev/null +++ b/src/app/dashboard/studies/page.tsx @@ -0,0 +1,69 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { api } from "~/trpc/react"; +import { PageHeader } from "~/components/layout/page-header"; +import { PageContent } from "~/components/layout/page-content"; +import { Button } from "~/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { Plus as PlusIcon } from "lucide-react"; + +export default function StudiesPage() { + const router = useRouter(); + const { data: studies, isLoading } = api.study.getMyStudies.useQuery(); + + if (isLoading) { + return
Loading...
; + } + + return ( + <> + + + + +
+ {!studies || studies.length === 0 ? ( + + + No Studies + + You haven't created any studies yet. Click the button above to create your first study. + + + + ) : ( + studies.map((study) => ( + router.push(`/dashboard/studies/${study.id}`)} + > + + {study.title} + + {study.description || "No description provided"} + + + +
+ Your role: {study.role} +
+
+
+ )) + )} +
+
+ + ); +} \ No newline at end of file diff --git a/src/app/invite/page.tsx b/src/app/invite/page.tsx new file mode 100644 index 0000000..8cf2046 --- /dev/null +++ b/src/app/invite/page.tsx @@ -0,0 +1,328 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useSession, signOut } from "next-auth/react"; +import { api } from "~/trpc/react"; +import { Button } from "~/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { useToast } from "~/hooks/use-toast"; +import Link from "next/link"; +import { format } from "date-fns"; +import { Logo } from "~/components/logo"; + +export default function InvitePage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { data: session, status } = useSession(); + const { toast } = useToast(); + + const token = searchParams.get("token"); + + // Don't fetch invitation data until we're authenticated + const { data: invitation, isLoading: isLoadingInvitation } = api.study.getInvitation.useQuery( + { token: token! }, + { + enabled: !!token && status === "authenticated", + retry: false, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + } + ); + + const { mutate: acceptInvitation, isLoading: isAccepting } = api.study.acceptInvitation.useMutation({ + onSuccess: () => { + toast({ + title: "Success", + description: "You have successfully joined the study.", + }); + router.push(`/dashboard/studies/${invitation?.studyId}`); + router.refresh(); + }, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Show loading state for missing token + if (!token) { + return ( +
+ +
+ + +
+
+ + Invalid Invitation + + + No invitation token provided. Please check your invitation link. + +
+ +
+
+
+
+ +
+
+ + +
+
+ ); + } + + // Show authentication required state + if (status === "unauthenticated") { + return ( +
+ +
+ + +
+
+ + Study Invitation + + + Sign in or create an account to view and accept this invitation. + +
+
+ +
+
+ +
+
+ + or + +
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ ); + } + + // Show loading state while checking authentication + if (status === "loading") { + return ( +
+ +
+ + +
+
+ + Loading... + + + Please wait while we load your invitation. + +
+
+
+
+
+ +
+
+ + +
+
+ ); + } + + // Show error state for invalid invitation + if (!invitation) { + return ( +
+ +
+ + +
+
+ + Invalid Invitation + + + This invitation link appears to be invalid or has expired. Please request a new invitation. + +
+ +
+
+
+
+ +
+
+ + +
+
+ ); + } + + return ( +
+ +
+ + +
+
+ + Study Invitation + + + You've been invited to join {invitation.study.title} as a {invitation.role}. + +
+
+
+
+ Study: + {invitation.study.title} +
+ {invitation.study.description && ( +
+ Description: + {invitation.study.description} +
+ )} +
+ Role: + {invitation.role} +
+
+ Invited by: + + {invitation.creator.firstName} {invitation.creator.lastName} + +
+
+ Expires: + {format(new Date(invitation.expiresAt), "PPp")} +
+
+ + {session.user.email === invitation.email ? ( + + ) : ( +
+

+ This invitation was sent to {invitation.email}, but you're signed in with a different + email address ({session.user.email}). +

+
+ + +
+
+ )} +
+
+
+
+
+ +
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c64be2e..439e5c2 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,5 @@ import "~/styles/globals.css"; -import { Inter } from "next/font/google"; import { GeistSans } from 'geist/font/sans'; import { headers } from "next/headers"; @@ -9,12 +8,6 @@ import { cn } from "~/lib/utils"; import { Providers } from "~/components/providers"; import DatabaseCheck from "~/components/db-check"; -const inter = Inter({ - subsets: ["latin"], - variable: "--font-sans", -}); - - export const metadata = { title: "HRIStudio", description: "A platform for managing human research studies and participant interactions.", @@ -30,7 +23,7 @@ export default function RootLayout({ diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx deleted file mode 100644 index 1843415..0000000 --- a/src/app/login/login-form.tsx +++ /dev/null @@ -1,103 +0,0 @@ -'use client'; - -import { signIn } from "next-auth/react"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { Button } from "~/components/ui/button"; -import { Input } from "~/components/ui/input"; -import { Label } from "~/components/ui/label"; -import Link from "next/link"; -import { Loader2 } from "lucide-react"; - -export function LoginForm({ error }: { error: boolean }) { - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - - async function onSubmit(e: React.FormEvent) { - e.preventDefault(); - setIsLoading(true); - - const formData = new FormData(e.currentTarget); - const response = await signIn("credentials", { - email: formData.get("email"), - password: formData.get("password"), - redirect: false, - }); - - setIsLoading(false); - - if (!response?.error) { - router.push("/dashboard"); - router.refresh(); - } else { - router.push("/login?error=CredentialsSignin"); - router.refresh(); - } - } - - return ( -
- {error && ( -
-

Invalid email or password

-
- )} -
-
- - -
-
-
- - - Forgot password? - -
- -
- -
-
- Don't have an account?{" "} - - Sign up - -
-
- ); -} \ No newline at end of file diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx deleted file mode 100644 index 312da35..0000000 --- a/src/app/login/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import type { Metadata } from "next"; -import Link from "next/link"; -import { redirect } from "next/navigation"; -import { getServerAuthSession } from "~/server/auth"; - -import { - Card, - CardContent, - CardDescription, - CardTitle, -} from "~/components/ui/card"; -import { LoginForm } from "./login-form"; -import { Logo } from "~/components/logo"; - -export const metadata: Metadata = { - title: "Login | HRIStudio", - description: "Login to your account", -}; - -export default async function LoginPage({ - searchParams, -}: { - searchParams: Promise> -}) { - const session = await getServerAuthSession(); - if (session) { - redirect("/dashboard"); - } - - const params = await searchParams; - const error = params?.error ? String(params.error) : null; - const showError = error === "CredentialsSignin"; - - return ( -
- -
- - -
-
- - Welcome back - - - Sign in to your account to continue - -
- -
-
-
-
- -
-
- - -

- By signing in, you agree to our{" "} - - Terms of Service - {" "} - and{" "} - - Privacy Policy - - . -

-
-
- ); -} \ No newline at end of file diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx new file mode 100644 index 0000000..f950274 --- /dev/null +++ b/src/app/onboarding/page.tsx @@ -0,0 +1,537 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useSession } from "next-auth/react"; +import { api } from "~/trpc/react"; +import { Button } from "~/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { useToast } from "~/hooks/use-toast"; +import Link from "next/link"; +import { Logo } from "~/components/logo"; +import { StudyForm, type StudyFormValues } from "~/components/studies/study-form"; +import { useState } from "react"; +import { ArrowLeft, ArrowRight, Bot, Users, Microscope, Beaker, GitBranch } from "lucide-react"; +import { LucideIcon } from "lucide-react"; +import { motion, AnimatePresence } from "framer-motion"; + +interface OnboardingStep { + id: string; + title: string; + description: string; + icon: LucideIcon; + content?: React.ReactNode; +} + +// Define the onboarding steps +const ONBOARDING_STEPS: OnboardingStep[] = [ + { + id: "welcome", + title: "Welcome to HRIStudio", + description: "Your platform for human-robot interaction research", + icon: Bot, + content: ( +
+

+ HRIStudio is a comprehensive platform designed to help researchers: +

+
    +
  • Design and run Wizard-of-Oz experiments
  • +
  • Manage research participants and data collection
  • +
  • Collaborate with team members in real-time
  • +
  • Analyze and export research data
  • +
+
+ ), + }, + { + id: "roles", + title: "Understanding Roles", + description: "Different roles for different responsibilities", + icon: Users, + content: ( +
+

+ HRIStudio supports various team roles: +

+
    +
  • Owner & Admin: Manage study settings and team
  • +
  • Principal Investigator: Oversee research design
  • +
  • Wizard: Control robot behavior during experiments
  • +
  • Researcher: Analyze data and results
  • +
  • Observer: View and annotate sessions
  • +
+
+ ), + }, + { + id: "studies", + title: "Managing Studies", + description: "Organize your research effectively", + icon: Microscope, + content: ( +
+

+ Studies are the core of HRIStudio: +

+
    +
  • Create multiple studies for different research projects
  • +
  • Invite team members with specific roles
  • +
  • Manage participant recruitment and data
  • +
  • Configure experiment protocols and settings
  • +
+
+ ), + }, + { + id: "hierarchy", + title: "Study Structure", + description: "Understanding the experiment hierarchy", + icon: GitBranch, + content: ( +
+
+ {/* Study Level */} + +
Study
+
Research Project
+
+ + {/* Connecting Line */} + + + {/* Experiments Level */} + +
Experiments
+
Study Protocols
+
+ + {/* Connecting Line */} + + + {/* Trials Level */} + +
Trials
+
Individual Sessions
+
+ + {/* Connecting Line */} + + + {/* Steps Level */} + +
Steps
+
Trial Procedures
+
+ + {/* Connecting Line */} + + + {/* Actions Level */} + +
Actions
+
Individual Operations
+
+
+ +
+

The experiment structure flows from top to bottom:

+
    +
  • Study: Contains experiments and team members
  • +
  • Experiments: Define reusable protocols
  • +
  • Trials: Individual sessions with participants
  • +
  • Steps: Ordered procedures within a trial
  • +
  • Actions: Specific operations (movement, speech, etc.)
  • +
+
+
+ ), + }, + { + id: "experiments", + title: "Running Experiments", + description: "Conduct Wizard-of-Oz studies seamlessly", + icon: Beaker, + content: ( +
+

+ Design and execute experiments with ease: +

+
    +
  • Create reusable experiment templates
  • +
  • Define robot behaviors and interactions
  • +
  • Record and annotate sessions in real-time
  • +
  • Collect and analyze participant data
  • +
+
+ ), + }, + { + id: "setup", + title: "Let's Get Started", + description: "Create your first study or join an existing one", + icon: Bot, + }, +]; + +// Update slideVariants +const slideVariants = { + enter: (direction: number) => ({ + x: direction > 0 ? 50 : -50, + opacity: 0 + }), + center: { + zIndex: 1, + x: 0, + opacity: 1 + }, + exit: (direction: number) => ({ + zIndex: 0, + x: direction < 0 ? 50 : -50, + opacity: 0 + }) +}; + +export default function OnboardingPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const { data: session, status } = useSession(); + const { toast } = useToast(); + const [currentStep, setCurrentStep] = useState(0); + const [direction, setDirection] = useState(0); + + // Get invitation token if it exists + const token = searchParams.get("token"); + + // Fetch invitation if token exists + const { data: invitation } = api.study.getInvitation.useQuery( + { token: token! }, + { + enabled: !!token && status === "authenticated", + retry: false, + } + ); + + // Mutation for accepting invitation + const { mutate: acceptInvitation, isPending: isAccepting } = api.study.acceptInvitation.useMutation({ + onSuccess: () => { + toast({ + title: "Success", + description: "You have successfully joined the study.", + }); + router.push(`/dashboard/studies/${invitation?.studyId}`); + router.refresh(); + }, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Mutation for creating a new study + const { mutate: createStudy, isPending: isCreating } = api.study.create.useMutation({ + onSuccess: (data) => { + toast({ + title: "Success", + description: "Your study has been created successfully.", + }); + router.push(`/dashboard/studies/${data.id}`); + router.refresh(); + }, + onError: (error) => { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + }, + }); + + // Handle study creation + function onCreateStudy(data: StudyFormValues) { + createStudy(data); + } + + // Navigation functions + const nextStep = () => { + if (currentStep < ONBOARDING_STEPS.length - 1) { + setDirection(1); + setCurrentStep(currentStep + 1); + } + }; + + const prevStep = () => { + if (currentStep > 0) { + setDirection(-1); + setCurrentStep(currentStep - 1); + } + }; + + // Ensure currentStep is within bounds + const safeStep = Math.min(Math.max(0, currentStep), ONBOARDING_STEPS.length - 1); + const currentStepData = ONBOARDING_STEPS[safeStep]!; + const Icon = currentStepData.icon; + + // Show loading state while checking authentication + if (status === "loading") { + return ( +
+ +
+ + +
+
+ + Loading... + + + Please wait while we set up your account. + +
+
+
+
+
+ +
+
+ + +
+
+ ); + } + + // Redirect to sign in if not authenticated + if (status === "unauthenticated") { + router.push("/auth/signin"); + return null; + } + + // If user has an invitation and we're on the final step + if (token && invitation && safeStep === ONBOARDING_STEPS.length - 1) { + return ( +
+ +
+ + +
+
+ + Join {invitation.study.title} + + + You've been invited to join as a {invitation.role}. + +
+
+ {session?.user.email === invitation.email ? ( + + ) : ( +
+

+ This invitation was sent to {invitation.email}, but you're signed in with a different + email address ({session?.user.email}). +

+ +
+ )} +
+
+
+
+
+ +
+
+ + +
+
+ ); + } + + return ( +
+ +
+ + +
+
+ + + + + {currentStepData.title} + + + {currentStepData.description} + +
+ +
+ + +
+
+ {safeStep === ONBOARDING_STEPS.length - 1 ? ( + + ) : ( +
+ {currentStepData.content} +
+ )} +
+
+
+
+
+ +
+ + + + + + +
+
+ +
+
+
+ +
+
+
+ {ONBOARDING_STEPS.map((step, index) => ( +
+ ))} +
+
+
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 23d19b6..000cc1c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -19,10 +19,10 @@ export default async function Home() { {!session && ( <> )} @@ -47,7 +47,7 @@ export default async function Home() {
{!session ? ( ) : ( -
-
- Already have an account?{" "} - - Sign in - -
- -
-
-
-
- -
-
- - -

- By creating an account, you agree to our{" "} - - Terms of Service - {" "} - and{" "} - - Privacy Policy - - . -

-
-
- ); -} \ No newline at end of file diff --git a/src/app/studies/[id]/page.tsx b/src/app/studies/[id]/page.tsx deleted file mode 100644 index 209defa..0000000 --- a/src/app/studies/[id]/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export default async function StudyPage({ params }: { params: { id: string } }) { - const study = await db.query.studies.findFirst({ - where: (studies, { eq }) => eq(studies.id, params.id), - with: { experiments: true } - }) - - return ( -
- - }> - - -
- ) -} \ No newline at end of file diff --git a/src/app/_components/auth/sign-in-form.tsx b/src/components/auth/sign-in-form.tsx similarity index 74% rename from src/app/_components/auth/sign-in-form.tsx rename to src/components/auth/sign-in-form.tsx index 799d794..682a657 100644 --- a/src/app/_components/auth/sign-in-form.tsx +++ b/src/components/auth/sign-in-form.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { signIn } from "next-auth/react"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; @@ -16,29 +16,46 @@ import { FormMessage, } from "~/components/ui/form"; import { Input } from "~/components/ui/input"; -import { useToast } from "~/components/ui/use-toast"; +import { useToast } from "~/hooks/use-toast"; +import React from "react"; -const formSchema = z.object({ +const signInSchema = z.object({ email: z.string().email(), password: z.string().min(8), }); -type FormValues = z.infer; +type SignInValues = z.infer; -export function SignInForm() { +interface SignInFormProps { + error?: boolean; +} + +export function SignInForm({ error }: SignInFormProps) { const router = useRouter(); + const searchParams = useSearchParams(); const { toast } = useToast(); const [isLoading, setIsLoading] = useState(false); - const form = useForm({ - resolver: zodResolver(formSchema), + // Show error toast if credentials are invalid + React.useEffect(() => { + if (error) { + toast({ + title: "Error", + description: "Invalid email or password", + variant: "destructive", + }); + } + }, [error, toast]); + + const form = useForm({ + resolver: zodResolver(signInSchema), defaultValues: { - email: "", + email: searchParams.get("email") ?? "", password: "", }, }); - async function onSubmit(data: FormValues) { + async function onSubmit(data: SignInValues) { setIsLoading(true); try { @@ -50,20 +67,21 @@ export function SignInForm() { if (result?.error) { toast({ - variant: "destructive", title: "Error", description: "Invalid email or password", + variant: "destructive", }); return; } + const callbackUrl = searchParams.get("callbackUrl") ?? "/dashboard"; + router.push(callbackUrl); router.refresh(); - router.push("/"); } catch (error) { toast({ - variant: "destructive", title: "Error", description: "Something went wrong. Please try again.", + variant: "destructive", }); } finally { setIsLoading(false); @@ -91,6 +109,7 @@ export function SignInForm() { )} /> + )} /> + diff --git a/src/components/auth/sign-up-form.tsx b/src/components/auth/sign-up-form.tsx new file mode 100644 index 0000000..e4cc5a9 --- /dev/null +++ b/src/components/auth/sign-up-form.tsx @@ -0,0 +1,210 @@ +"use client"; + +import { useState } from "react"; +import { signIn } from "next-auth/react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Button } from "~/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "~/components/ui/form"; +import { Input } from "~/components/ui/input"; +import { useToast } from "~/hooks/use-toast"; +import React from "react"; + +const signUpSchema = z.object({ + firstName: z.string().min(1, "First name is required"), + lastName: z.string().min(1, "Last name is required"), + email: z.string().email(), + password: z.string().min(8), +}); + +type SignUpValues = z.infer; + +interface SignUpFormProps { + error?: boolean; +} + +export function SignUpForm({ error }: SignUpFormProps) { + const router = useRouter(); + const searchParams = useSearchParams(); + const { toast } = useToast(); + const [isLoading, setIsLoading] = useState(false); + + // Show error toast if credentials are invalid + React.useEffect(() => { + if (error) { + toast({ + title: "Error", + description: "Something went wrong. Please try again.", + variant: "destructive", + }); + } + }, [error, toast]); + + const form = useForm({ + resolver: zodResolver(signUpSchema), + defaultValues: { + firstName: "", + lastName: "", + email: searchParams.get("email") ?? "", + password: "", + }, + }); + + async function onSubmit(data: SignUpValues) { + setIsLoading(true); + + try { + const formData = new FormData(); + formData.append("firstName", data.firstName); + formData.append("lastName", data.lastName); + formData.append("email", data.email); + formData.append("password", data.password); + + const response = await fetch("/api/auth/register", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error ?? "Something went wrong"); + } + + const result = await signIn("credentials", { + redirect: false, + email: data.email, + password: data.password, + }); + + if (result?.error) { + toast({ + title: "Error", + description: "Something went wrong. Please try again.", + variant: "destructive", + }); + return; + } + + // Get the invitation token if it exists + const token = searchParams.get("token"); + + // Redirect to onboarding with token if it exists + const onboardingUrl = token + ? `/onboarding?token=${token}` + : "/onboarding"; + + router.push(onboardingUrl); + router.refresh(); + } catch (error) { + if (error instanceof Error) { + toast({ + title: "Error", + description: error.message, + variant: "destructive", + }); + } else { + toast({ + title: "Error", + description: "Something went wrong. Please try again.", + variant: "destructive", + }); + } + } finally { + setIsLoading(false); + } + } + + return ( +
+ +
+ ( + + First Name + + + + + + )} + /> + ( + + Last Name + + + + + + )} + /> +
+ + ( + + Email + + + + + + )} + /> + + ( + + Password + + + + + + )} + /> + + + + + ); +} \ No newline at end of file diff --git a/src/components/navigation/app-sidebar.tsx b/src/components/navigation/app-sidebar.tsx index c3eee23..7d17d50 100644 --- a/src/components/navigation/app-sidebar.tsx +++ b/src/components/navigation/app-sidebar.tsx @@ -4,10 +4,14 @@ import { Beaker, Home, Settings2, - User + User, + Microscope, + Users, + Plus } from "lucide-react" import * as React from "react" import { useSession } from "next-auth/react" +import { useStudy } from "~/components/providers/study-provider" import { StudySwitcher } from "~/components/auth/study-switcher" import { @@ -20,18 +24,23 @@ import { import { NavMain } from "~/components/navigation/nav-main" import { NavUser } from "~/components/navigation/nav-user" -const data = { - navMain: [ +export function AppSidebar({ ...props }: React.ComponentProps) { + const { data: session } = useSession() + const { activeStudy } = useStudy() + + if (!session) return null + + // Base navigation items that are always shown + const baseNavItems = [ { title: "Overview", url: "/dashboard", icon: Home, - isActive: true, }, { title: "Studies", url: "/dashboard/studies", - icon: Beaker, + icon: Microscope, items: [ { title: "All Studies", @@ -43,6 +52,33 @@ const data = { }, ], }, + ] + + // Study-specific navigation items that are only shown when a study is active + const studyNavItems = activeStudy + ? [ + { + title: "Participants", + url: `/dashboard/studies/${activeStudy.id}/participants`, + icon: Users, + items: [ + { + title: "All Participants", + url: `/dashboard/studies/${activeStudy.id}/participants`, + }, + { + title: "Add Participant", + url: `/dashboard/studies/${activeStudy.id}/participants/new`, + // Only show if user is admin + hidden: activeStudy.role !== "ADMIN", + }, + ], + }, + ] + : [] + + // Settings navigation items + const settingsNavItems = [ { title: "Settings", url: "/dashboard/settings", @@ -63,12 +99,9 @@ const data = { }, ], }, - ], -} + ] -export function AppSidebar({ ...props }: React.ComponentProps) { - const { data: session } = useSession() - if (!session) return null + const navItems = [...baseNavItems, ...studyNavItems, ...settingsNavItems] return ( ) { - + diff --git a/src/components/navigation/breadcrumb-nav.tsx b/src/components/navigation/breadcrumb-nav.tsx index a336282..18ac6ab 100644 --- a/src/components/navigation/breadcrumb-nav.tsx +++ b/src/components/navigation/breadcrumb-nav.tsx @@ -31,6 +31,39 @@ export function BreadcrumbNav() { label: "Create Study", current: true, }) + } else if (paths[2]) { + items.push({ + label: "Study Details", + href: `/dashboard/studies/${paths[2]}`, + current: paths.length === 3, + }) + + if (paths[3] === "participants") { + items.push({ + label: "Participants", + href: `/dashboard/studies/${paths[2]}/participants`, + current: paths.length === 4, + }) + + if (paths[4] === "new") { + items.push({ + label: "Add Participant", + current: true, + }) + } else if (paths[4]) { + items.push({ + label: "Participant Details", + current: paths.length === 5, + }) + + if (paths[5] === "edit") { + items.push({ + label: "Edit", + current: true, + }) + } + } + } } } @@ -41,11 +74,11 @@ export function BreadcrumbNav() { return (