diff --git a/.env.example b/.env.example index 4cc714a..ace0fb3 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -DATABASE_URL= \ No newline at end of file +DATABASE_URL= +RESEND_API_KEY= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 625ff7d..5602671 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,28 +9,37 @@ "version": "0.1.0", "dependencies": { "@floating-ui/dom": "^1.7.4", + "@hookform/resolvers": "^5.2.2", "@mantine/core": "^8.3.4", "@mantine/dates": "^8.3.5", "@mantine/hooks": "^8.3.4", "@neondatabase/serverless": "^1.0.2", "@next/eslint-plugin-next": "^15.5.4", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-table": "^8.21.3", + "better-auth": "^1.4.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "d3": "^7.9.0", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", + "input-otp": "^1.4.2", "lucide": "^0.544.0", "lucide-react": "^0.545.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", "react-excel-renderer": "^1.1.0", + "react-hook-form": "^7.67.0", "react-icons": "^5.5.0", + "resend": "^6.6.0", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "vercel": "^48.2.0", - "xlsx": "^0.18.5" + "xlsx": "^0.18.5", + "zod": "^4.1.13" }, "devDependencies": { "@eslint/js": "^9.37.0", @@ -38,12 +47,13 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "drizzle-kit": "^0.31.6", + "drizzle-kit": "^0.31.7", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react": "^7.37.5", "globals": "^16.4.0", "husky": "^9.1.7", + "knip": "^5.79.0", "lint-staged": "^16.2.3", "prettier": "3.6.2", "tailwindcss": "^4", @@ -73,6 +83,17 @@ "node": ">=6.9.0" } }, + "node_modules/@better-auth/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==", + "license": "MIT" + }, + "node_modules/@better-fetch/fetch": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz", + "integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1278,6 +1299,18 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2049,15 +2082,19 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@neondatabase/serverless": { @@ -2225,6 +2262,30 @@ "node": ">= 10" } }, + "node_modules/@noble/ciphers": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.0.1.tgz", + "integrity": "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", + "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2278,6 +2339,289 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.16.2.tgz", + "integrity": "sha512-lVJbvydLQIDZHKUb6Zs9Rq80QVTQ9xdCQE30eC9/cjg4wsMoEOg65QZPymUAIVJotpUAWJD0XYcwE7ugfxx5kQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.16.2.tgz", + "integrity": "sha512-fEk+g/g2rJ6LnBVPqeLcx+/alWZ/Db1UlXG+ZVivip0NdrnOzRL48PAmnxTMGOrLwsH1UDJkwY3wOjrrQltCqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.16.2.tgz", + "integrity": "sha512-Pkbp1qi7kdUX6k3Fk1PvAg6p7ruwaWKg1AhOlDgrg2vLXjtv9ZHo7IAQN6kLj0W771dPJZWqNxoqTPacp2oYWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.16.2.tgz", + "integrity": "sha512-FYCGcU1iSoPkADGLfQbuj0HWzS+0ItjDCt9PKtu2Hzy6T0dxO4Y1enKeCOxCweOlmLEkSxUlW5UPT4wvT3LnAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.16.2.tgz", + "integrity": "sha512-1zHCoK6fMcBjE54P2EG/z70rTjcRxvyKfvk4E/QVrWLxNahuGDFZIxoEoo4kGnnEcmPj41F0c2PkrQbqlpja5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.16.2.tgz", + "integrity": "sha512-+ucLYz8EO5FDp6kZ4o1uDmhoP+M98ysqiUW4hI3NmfiOJQWLrAzQjqaTdPfIOzlCXBU9IHp5Cgxu6wPjVb8dbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.16.2.tgz", + "integrity": "sha512-qq+TpNXyw1odDgoONRpMLzH4hzhwnEw55398dL8rhKGvvYbio71WrJ00jE+hGlEi7H1Gkl11KoPJRaPlRAVGPw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.16.2.tgz", + "integrity": "sha512-xlMh4gNtplNQEwuF5icm69udC7un0WyzT5ywOeHrPMEsghKnLjXok2wZgAA7ocTm9+JsI+nVXIQa5XO1x+HPQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.16.2.tgz", + "integrity": "sha512-OZs33QTMi0xmHv/4P0+RAKXJTBk7UcMH5tpTaCytWRXls/DGaJ48jOHmriQGK2YwUqXl+oneuNyPOUO0obJ+Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.16.2.tgz", + "integrity": "sha512-UVyuhaV32dJGtF6fDofOcBstg9JwB2Jfnjfb8jGlu3xcG+TsubHRhuTwQ6JZ1sColNT1nMxBiu7zdKUEZi1kwg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.16.2.tgz", + "integrity": "sha512-YZZS0yv2q5nE1uL/Fk4Y7m9018DSEmDNSG8oJzy1TJjA1jx5HL52hEPxi98XhU6OYhSO/vC1jdkJeE8TIHugug==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.16.2.tgz", + "integrity": "sha512-9VYuypwtx4kt1lUcwJAH4dPmgJySh4/KxtAPdRoX2BTaZxVm/yEXHq0mnl/8SEarjzMvXKbf7Cm6UBgptm3DZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.16.2.tgz", + "integrity": "sha512-3gbwQ+xlL5gpyzgSDdC8B4qIM4mZaPDLaFOi3c/GV7CqIdVJc5EZXW4V3T6xwtPBOpXPXfqQLbhTnUD4SqwJtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.16.2.tgz", + "integrity": "sha512-m0WcK0j54tSwWa+hQaJMScZdWneqE7xixp/vpFqlkbhuKW9dRHykPAFvSYg1YJ3MJgu9ZzVNpYHhPKJiEQq57Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.16.2.tgz", + "integrity": "sha512-ZjUm3w96P2t47nWywGwj1A2mAVBI/8IoS7XHhcogWCfXnEI3M6NPIRQPYAZW4s5/u3u6w1uPtgOwffj2XIOb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.16.2.tgz", + "integrity": "sha512-OFVQ2x3VenTp13nIl6HcQ/7dmhFmM9dg2EjKfHcOtYfrVLQdNR6THFU7GkMdmc8DdY1zLUeilHwBIsyxv5hkwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.16.2.tgz", + "integrity": "sha512-+O1sY3RrGyA2AqDnd3yaDCsqZqCblSTEpY7TbbaOaw0X7iIbGjjRLdrQk9StG3QSiZuBy9FdFwotIiSXtwvbAQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.16.2.tgz", + "integrity": "sha512-jMrMJL+fkx6xoSMFPOeyQ1ctTFjavWPOSZEKUY5PebDwQmC9cqEr4LhdTnGsOtFrWYLXlEU4xWeMdBoc/XKkOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.16.2.tgz", + "integrity": "sha512-tl0xDA5dcQplG2yg2ZhgVT578dhRFafaCfyqMEAXq8KNpor85nJ53C3PLpfxD2NKzPioFgWEexNsjqRi+kW2Mg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.16.2.tgz", + "integrity": "sha512-M7z0xjYQq1HdJk2DxTSLMvRMyBSI4wn4FXGcVQBsbAihgXevAReqwMdb593nmCK/OiFwSNcOaGIzUvzyzQ+95w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2288,6 +2632,108 @@ "node": ">=14" } }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "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-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "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-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "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-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "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-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.35", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.35.tgz", @@ -2519,6 +2965,24 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -2933,7 +3397,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -3336,6 +3800,15 @@ "zod": "3.22.4" } }, + "node_modules/@vercel/express/node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@vercel/fastify": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/@vercel/fastify/-/fastify-0.1.7.tgz", @@ -3519,6 +3992,15 @@ "zod": "3.22.4" } }, + "node_modules/@vercel/hono/node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@vercel/hydrogen": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@vercel/hydrogen/-/hydrogen-1.3.2.tgz", @@ -3539,6 +4021,15 @@ "zod": "3.22.4" } }, + "node_modules/@vercel/introspection/node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@vercel/next": { "version": "4.15.3", "resolved": "https://registry.npmjs.org/@vercel/next/-/next-4.15.3.tgz", @@ -4749,6 +5240,103 @@ ], "license": "MIT" }, + "node_modules/better-auth": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.4.1.tgz", + "integrity": "sha512-HDVE69Nw6Y1FPTcmFEmPolfsjMfVB5U823Ij9yWBoM8MdHZ2lA3JVus4xQJ2oRE1riJTlcSLFcgJKWGD7V7hmw==", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.4.1", + "@better-auth/telemetry": "1.4.1", + "@better-auth/utils": "0.3.0", + "@better-fetch/fetch": "1.1.18", + "@noble/ciphers": "^2.0.0", + "@noble/hashes": "^2.0.0", + "@standard-schema/spec": "^1.0.0", + "better-call": "1.1.0", + "defu": "^6.1.4", + "jose": "^6.1.0", + "kysely": "^0.28.5", + "nanostores": "^1.0.1", + "zod": "^4.1.12" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/better-auth/node_modules/@better-auth/core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.4.1.tgz", + "integrity": "sha512-N4kyRdA472WGLoCjsJpUeYdZZvpoBDgP65hUeQQxTQYwBTqD9O17Tokax9CdNbkb4g34sTfxaJCfcncE3Hy4SA==", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "zod": "^4.1.12" + }, + "peerDependencies": { + "@better-auth/utils": "0.3.0", + "@better-fetch/fetch": "1.1.18", + "better-call": "1.1.0", + "jose": "^6.1.0", + "kysely": "^0.28.5", + "nanostores": "^1.0.1" + } + }, + "node_modules/better-auth/node_modules/@better-auth/telemetry": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.4.1.tgz", + "integrity": "sha512-yNeazXYvMbyuCe1AA6tYWsJEKgcS7gF9PmmACmrPVhVBe1ncDhVfWMZ++YCmA2h8hjkR9755ZyofiYRPbj+kXQ==", + "dependencies": { + "@better-auth/utils": "0.3.0", + "@better-fetch/fetch": "1.1.18" + }, + "peerDependencies": { + "@better-auth/core": "1.4.1" + } + }, + "node_modules/better-auth/node_modules/jose": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.2.tgz", + "integrity": "sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/better-call": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.1.0.tgz", + "integrity": "sha512-7CecYG+yN8J1uBJni/Mpjryp8bW/YySYsrGEWgFe048ORASjq17keGjbKI2kHEOSc6u8pi11UxzkJ7jIovQw6w==", + "license": "MIT", + "dependencies": { + "@better-auth/utils": "^0.3.0", + "@better-fetch/fetch": "^1.1.4", + "rou3": "^0.5.1", + "set-cookie-parser": "^2.7.1" + } + }, "node_modules/big.js": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", @@ -6171,6 +6759,12 @@ "node": ">=0.10.0" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, "node_modules/delaunator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", @@ -6768,6 +7362,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -7646,6 +8246,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -7655,6 +8261,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -7810,6 +8426,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, "node_modules/frac": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", @@ -8555,6 +9187,16 @@ "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==", "license": "ISC" }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -9302,6 +9944,113 @@ "node": ">=0.10.0" } }, + "node_modules/knip": { + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.79.0.tgz", + "integrity": "sha512-rcg+mNdqm6UiTuRVyy6UuuHw1n4ABMpNXDtrfGaCeUtJoRBAvAENIebr8YMtOz6XE7iVHZ8+rY7skgEtosczhQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "@nodelib/fs.walk": "^1.2.3", + "fast-glob": "^3.3.3", + "formatly": "^0.3.0", + "jiti": "^2.6.0", + "js-yaml": "^4.1.1", + "minimist": "^1.2.8", + "oxc-resolver": "^11.15.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.1", + "smol-toml": "^1.5.2", + "strip-json-comments": "5.0.3", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4 <7" + } + }, + "node_modules/knip/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/knip/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/knip/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kysely": { + "version": "0.28.8", + "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.8.tgz", + "integrity": "sha512-QUOgl5ZrS9IRuhq5FvOKFSsD/3+IA6MLE81/bOOTRA/YQpKDza2sFdN5g6JCB9BOpqMJDGefLCQ9F12hRS13TA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10140,6 +10889,21 @@ "node": ">=0.10.0" } }, + "node_modules/nanostores": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.1.0.tgz", + "integrity": "sha512-yJBmDJr18xy47dbNVlHcgdPrulSn1nhSE6Ns9vTG+Nx9VPT6iV1MD6aQFp/t52zpf82FhLLTXAXr30NuCnxvwA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10605,6 +11369,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxc-resolver": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.16.2.tgz", + "integrity": "sha512-Uy76u47vwhhF7VAmVY61Srn+ouiOobf45MU9vGct9GD2ARy6hKoqEElyHDB0L+4JOM6VLuZ431KiLwyjI/A21g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.16.2", + "@oxc-resolver/binding-android-arm64": "11.16.2", + "@oxc-resolver/binding-darwin-arm64": "11.16.2", + "@oxc-resolver/binding-darwin-x64": "11.16.2", + "@oxc-resolver/binding-freebsd-x64": "11.16.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.16.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.16.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.16.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-x64-musl": "11.16.2", + "@oxc-resolver/binding-openharmony-arm64": "11.16.2", + "@oxc-resolver/binding-wasm32-wasi": "11.16.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.16.2", + "@oxc-resolver/binding-win32-ia32-msvc": "11.16.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.16.2" + } + }, "node_modules/p-finally": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", @@ -11277,6 +12073,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11491,6 +12293,22 @@ "node": ">=0.8" } }, + "node_modules/react-hook-form": { + "version": "7.67.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.67.0.tgz", + "integrity": "sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -11748,6 +12566,32 @@ "node": ">=0.10.0" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/resend": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.6.0.tgz", + "integrity": "sha512-d1WoOqSxj5x76JtQMrieNAG1kZkh4NU4f+Je1yq4++JsDpLddhEwnJlNfvkCzvUuZy9ZquWmMMAm2mENd2JvRw==", + "license": "MIT", + "dependencies": { + "svix": "1.76.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -11949,6 +12793,12 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.35" } }, + "node_modules/rou3": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz", + "integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==", + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12115,6 +12965,12 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -12430,6 +13286,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -13109,6 +13978,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svix": { + "version": "1.76.1", + "resolved": "https://registry.npmjs.org/svix/-/svix-1.76.1.tgz", + "integrity": "sha512-CRuDWBTgYfDnBLRaZdKp9VuoPcNUq9An14c/k+4YJ15Qc5Grvf66vp0jvTltd4t7OIRj+8lM1DAgvSgvf7hdLw==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "@types/node": "^22.7.5", + "es6-promise": "^4.2.8", + "fast-sha256": "^1.3.0", + "url-parse": "^1.5.10", + "uuid": "^10.0.0" + } + }, + "node_modules/svix/node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/tabbable": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", @@ -14221,6 +15113,16 @@ "node": ">= 0.4" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url/node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -14345,6 +15247,19 @@ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "license": "ISC" }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -14393,6 +15308,16 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "license": "MIT" }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", @@ -15465,9 +16390,9 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 19b4460..9fd6d93 100644 --- a/package.json +++ b/package.json @@ -13,28 +13,37 @@ }, "dependencies": { "@floating-ui/dom": "^1.7.4", + "@hookform/resolvers": "^5.2.2", "@mantine/core": "^8.3.4", "@mantine/dates": "^8.3.5", "@mantine/hooks": "^8.3.4", "@neondatabase/serverless": "^1.0.2", "@next/eslint-plugin-next": "^15.5.4", - "d3": "^7.9.0", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-table": "^8.21.3", + "better-auth": "^1.4.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "d3": "^7.9.0", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", + "input-otp": "^1.4.2", "lucide": "^0.544.0", "lucide-react": "^0.545.0", "next": "15.5.4", "react": "19.1.0", "react-dom": "19.1.0", "react-excel-renderer": "^1.1.0", + "react-hook-form": "^7.67.0", + "react-icons": "^5.5.0", + "resend": "^6.6.0", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", - "react-icons": "^5.5.0", "vercel": "^48.2.0", - "xlsx": "^0.18.5" + "xlsx": "^0.18.5", + "zod": "^4.1.13" }, "devDependencies": { "@eslint/js": "^9.37.0", @@ -42,12 +51,13 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "drizzle-kit": "^0.31.6", + "drizzle-kit": "^0.31.7", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-react": "^7.37.5", "globals": "^16.4.0", "husky": "^9.1.7", + "knip": "^5.79.0", "lint-staged": "^16.2.3", "prettier": "3.6.2", "tailwindcss": "^4", diff --git a/public/mhs-logo.png b/public/mhs-logo.png new file mode 100644 index 0000000..08fdfcc Binary files /dev/null and b/public/mhs-logo.png differ diff --git a/src/app/api/auth/[...all]/route.ts b/src/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..7cbe91b --- /dev/null +++ b/src/app/api/auth/[...all]/route.ts @@ -0,0 +1,4 @@ +import { auth } from "@/lib/auth"; +import { toNextJsHandler } from "better-auth/next-js"; + +export const { POST, GET } = toNextJsHandler(auth); diff --git a/src/app/dashboard/dummyPage.tsx b/src/app/dashboard/dummyPage.tsx new file mode 100644 index 0000000..acf1135 --- /dev/null +++ b/src/app/dashboard/dummyPage.tsx @@ -0,0 +1,7 @@ +export default function DummyPage() { + return ( +
+

Hello World

+
+ ); +} diff --git a/src/app/signin/page.tsx b/src/app/signin/page.tsx new file mode 100644 index 0000000..78857f9 --- /dev/null +++ b/src/app/signin/page.tsx @@ -0,0 +1,18 @@ +import AuthForm from "@/components/AuthForm"; +import WarpShader from "@/components/WarpShader"; + +export default function SignInPage() { + return ( +
+
+ +
+ +
+
+
+ ); +} diff --git a/src/components/AuthForm.tsx b/src/components/AuthForm.tsx new file mode 100644 index 0000000..a83d9b9 --- /dev/null +++ b/src/components/AuthForm.tsx @@ -0,0 +1,187 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { authClient } from "@/lib/auth-client"; +import { useRouter } from "next/navigation"; +import Image from "next/image"; + +export default function AuthForm() { + const [step, setStep] = useState<"email" | "otp">("email"); + const [email, setEmail] = useState(""); + const [otp, setOtp] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(""); + + const router = useRouter(); + + async function handleSendCode(e: React.FormEvent) { + e.preventDefault(); + setIsLoading(true); + setError(""); // TO DO: Change error handling to toast + try { + await authClient.emailOtp.sendVerificationOtp({ + email, + type: "sign-in", + }); + setStep("otp"); + } catch (error) { + setError("Failed to send verification code. Please try again."); + console.error(error); + } finally { + setIsLoading(false); + } + } + + async function handleVerifyOtp(e: React.FormEvent) { + e.preventDefault(); + setIsLoading(true); + setError(""); + try { + await authClient.signIn.emailOtp({ + email, + otp, + }); + router.push("/"); + } catch (error) { + setError("Invalid or expired code. Please try again."); + console.error(error); + } finally { + setIsLoading(false); + } + } + + async function handleResendCode() { + setIsLoading(true); + setError(""); + try { + await authClient.emailOtp.sendVerificationOtp({ + email, + type: "sign-in", + }); + setOtp(""); + } catch (err) { + setError("Failed to resend code. Please try again."); + console.error(err); + } finally { + setIsLoading(false); + } + } + + return ( +
+ MHS Logo Image +
+

Sign In

+

+ Enter your email and a one time password will be sent to + you. If you do not have an account yet, speak to an + appropriate administrator at MHD. +

+
+ +
+ {step === "email" ? ( +
+
+ + setEmail(e.target.value)} + placeholder="your@email.com" + required + /> +
+ {error && ( +
+ {error} +
+ )} + +
+ ) : ( +
+
+
+ + + setOtp( + e.target.value + .replace(/\D/g, "") + .slice(0, 6), + ) + } + placeholder="123456" + maxLength={6} + required + className="text-center text-2xl tracking-widest" + /> +
+ +
+
+ + +
+
+ )} +
+
+

Created with ❤️ by JumboCode

+
+
+ ); +} diff --git a/src/components/WarpShader.tsx b/src/components/WarpShader.tsx new file mode 100644 index 0000000..8c07a28 --- /dev/null +++ b/src/components/WarpShader.tsx @@ -0,0 +1,251 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +interface WarpShaderProps { + className?: string; + colorBack?: { r: number; g: number; b: number; a: number }; + colorFront?: { r: number; g: number; b: number; a: number }; +} + +export default function WarpShader({ + className = "", + colorBack = { r: 0, g: 0, b: 0, a: 1 }, + colorFront = { r: 1, g: 1, b: 1, a: 1 }, +}: WarpShaderProps) { + const canvasRef = useRef(null); + const animationFrameRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const gl = canvas.getContext("webgl2"); + if (!gl) { + console.error("WebGL 2.0 not supported"); + return; + } + + // Vertex Shader + const vertexShaderSource = `#version 300 es +precision mediump float; +layout(location = 0) in vec4 a_position; +void main() { gl_Position = a_position; }`; + + // Fragment Shader + const fragmentShaderSource = `#version 300 es +precision mediump float; + +uniform float u_time; +uniform vec2 u_resolution; +uniform float u_pixelRatio; +uniform vec4 u_colorBack; +uniform vec4 u_colorFront; + +out vec4 fragColor; + +#define TWO_PI 6.28318530718 +#define PI 3.14159265358979323846 + +const int bayer8x8[64] = int[64]( + 0, 32, 8, 40, 2, 34, 10, 42, + 48, 16, 56, 24, 50, 18, 58, 26, + 12, 44, 4, 36, 14, 46, 6, 38, + 60, 28, 52, 20, 62, 30, 54, 22, + 3, 35, 11, 43, 1, 33, 9, 41, + 51, 19, 59, 27, 49, 17, 57, 25, + 15, 47, 7, 39, 13, 45, 5, 37, + 63, 31, 55, 23, 61, 29, 53, 21 +); + +float getBayerValue(vec2 uv) { + ivec2 pos = ivec2(mod(uv, 8.0)); + int index = pos.y * 8 + pos.x; + return float(bayer8x8[index]) / 64.0; +} + +void main() { + float t = .5 * u_time; + vec2 uv = gl_FragCoord.xy / u_resolution.xy; + + // Pixelization + float pxSize = 1.00 * u_pixelRatio; + vec2 pxSizeUv = gl_FragCoord.xy; + pxSizeUv -= .5 * u_resolution; + pxSizeUv /= pxSize; + uv = floor(pxSizeUv) * pxSize / u_resolution.xy + .5 - .5; + + // Pattern UV + float r = 0.00 * PI / 180.; + mat2 rot = mat2(cos(r), sin(r), -sin(r), cos(r)); + vec2 shape_uv = uv + vec2(0.00, 0.00); + shape_uv *= u_resolution.xy / u_pixelRatio / 0.25; + shape_uv = rot * shape_uv + .5; + vec2 ditheringNoise_uv = uv * u_resolution; + + // Warp + shape_uv *= 0.002; + for (float i = 1.0; i < 10.0; i++) { + shape_uv.x += 0.7 / i * cos(i * 2.5 * shape_uv.y + t); + shape_uv.y += 0.7 / i * cos(i * 1.5 * shape_uv.x + t); + } + float shape = .3 / abs(sin(t - shape_uv.y - shape_uv.x)); + shape = smoothstep(0.45, 0.75, shape); + + float dithering = getBayerValue(pxSizeUv) - 0.5; + float res = step(.5, shape + dithering); + + vec3 fgColor = u_colorFront.rgb * u_colorFront.a; + vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + vec3 color = fgColor * res + bgColor * (1. - u_colorFront.a * res); + float opacity = u_colorFront.a * res + u_colorBack.a * (1. - u_colorFront.a * res); + + fragColor = vec4(color, opacity); +}`; + + // Compile shader + function compileShader( + gl: WebGL2RenderingContext, + source: string, + type: number, + ): WebGLShader | null { + const shader = gl.createShader(type); + if (!shader) return null; + + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.error( + "Shader compilation error:", + gl.getShaderInfoLog(shader), + ); + gl.deleteShader(shader); + return null; + } + + return shader; + } + + // Create program + const vertexShader = compileShader( + gl, + vertexShaderSource, + gl.VERTEX_SHADER, + ); + const fragmentShader = compileShader( + gl, + fragmentShaderSource, + gl.FRAGMENT_SHADER, + ); + + if (!vertexShader || !fragmentShader) return; + + const program = gl.createProgram(); + if (!program) return; + + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + console.error( + "Program linking error:", + gl.getProgramInfoLog(program), + ); + return; + } + + // Set up geometry (full-screen quad) + const positions = new Float32Array([ + -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, + ]); + + const positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); + + const vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + gl.enableVertexAttribArray(0); + gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); + + // Get uniform locations + const u_time = gl.getUniformLocation(program, "u_time"); + const u_resolution = gl.getUniformLocation(program, "u_resolution"); + const u_pixelRatio = gl.getUniformLocation(program, "u_pixelRatio"); + const u_colorBack = gl.getUniformLocation(program, "u_colorBack"); + const u_colorFront = gl.getUniformLocation(program, "u_colorFront"); + + // Resize handler + function resize() { + if (!canvas || !gl) return; + const dpr = window.devicePixelRatio || 1; + canvas.width = canvas.clientWidth * dpr; + canvas.height = canvas.clientHeight * dpr; + gl.viewport(0, 0, canvas.width, canvas.height); + } + + resize(); + window.addEventListener("resize", resize); + + // Animation loop + const startTime = Date.now(); + function render() { + if (!canvas || !gl) return; + + const time = (Date.now() - startTime) / 1000; + + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.useProgram(program); + gl.bindVertexArray(vao); + + gl.uniform1f(u_time, time); + gl.uniform2f(u_resolution, canvas.width, canvas.height); + gl.uniform1f(u_pixelRatio, window.devicePixelRatio || 1); + gl.uniform4f( + u_colorBack, + colorBack.r, + colorBack.g, + colorBack.b, + colorBack.a, + ); + gl.uniform4f( + u_colorFront, + colorFront.r, + colorFront.g, + colorFront.b, + colorFront.a, + ); + + gl.drawArrays(gl.TRIANGLES, 0, 6); + + animationFrameRef.current = requestAnimationFrame(render); + } + + render(); + + // Cleanup + return () => { + window.removeEventListener("resize", resize); + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + gl.deleteProgram(program); + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + gl.deleteBuffer(positionBuffer); + gl.deleteVertexArray(vao); + }; + }, [colorBack, colorFront]); + + return ( + + ); +} diff --git a/src/components/signin-form.tsx b/src/components/signin-form.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/signup-form.tsx b/src/components/signup-form.tsx new file mode 100644 index 0000000..81a3bcf --- /dev/null +++ b/src/components/signup-form.tsx @@ -0,0 +1,218 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; + +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Field, + FieldDescription, + FieldGroup, + FieldLabel, + FieldSeparator, +} from "@/components/ui/field"; + +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; + +import { Input } from "@/components/ui/input"; +import { signUp } from "@/server/users"; + +import { z } from "zod"; + +const formSchema = z.object({ + em: z.email(), + pw: z.string().min(8), +}); + +export function SignupForm({ + className, + ...props +}: React.ComponentProps<"div">) { + // 1. Define your form. + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + em: "", + pw: "", + }, + }); + + // 2. Define a submit handler. + function onSubmit(values: z.infer) { + signUp(values.em, values.pw); + } + return ( +
+ + +
+ + +
+

+ Create your account +

+

+ Enter your email below to create your + account +

+
+ + ( + + Email + + + + + + )} + /> + + We'll use this to contact you. We + will not share your email with anyone + else. + + + + + + ( + + + Password + + + + + + + )} + /> + + + ( + + + Confirm Password + + + + + + + )} + /> + + + + Must be at least 8 characters long. + + + + + + + Or continue with + + + + + + + + Already have an account?{" "} + Sign in + +
+
+ +
+ Image +
+
+
+ + By clicking continue, you agree to our{" "} + Terms of Service and{" "} + Privacy Policy. + +
+ ); +} diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..5301880 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..f1ebb15 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx new file mode 100644 index 0000000..4f889a6 --- /dev/null +++ b/src/components/ui/field.tsx @@ -0,0 +1,244 @@ +"use client"; + +import { useMemo } from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", + className, + )} + {...props} + /> + ); +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ); +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-slot=field-group]]:gap-4", + className, + )} + {...props} + /> + ); +} + +const fieldVariants = cva( + "group/field data-[invalid=true]:text-destructive flex w-full gap-3", + { + variants: { + orientation: { + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], + horizontal: [ + "flex-row items-center", + "[&>[data-slot=field-label]]:flex-auto", + "has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px has-[>[data-slot=field-content]]:items-start", + ], + responsive: [ + "@md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto flex-col [&>*]:w-full [&>.sr-only]:w-auto", + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + }, +); + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +