oops i forgot to commit as I go again

This commit is contained in:
2025-01-24 17:18:04 -06:00
parent a09e8245c0
commit 567aa4cb71
35 changed files with 1455 additions and 37 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"style": "default",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app.css",
"baseColor": "neutral"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils"
},
"typescript": true
}
+364 -24
View File
@@ -7,6 +7,13 @@
"": { "": {
"name": "identity", "name": "identity",
"version": "0.0.1", "version": "0.0.1",
"dependencies": {
"@prisma/client": "^6.2.1",
"bcryptjs": "^2.4.3",
"mode-watcher": "^0.5.1",
"prisma": "^6.2.1",
"zod": "^3.24.1"
},
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.2.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0", "@eslint/js": "^9.18.0",
@@ -15,7 +22,11 @@
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^22.10.10",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"bits-ui": "^0.22.0",
"clsx": "^2.1.1",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1", "eslint-plugin-svelte": "^2.46.1",
@@ -24,6 +35,8 @@
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
@@ -47,7 +60,6 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
@@ -641,6 +653,34 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.6.9",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"dev": true,
"license": "MIT"
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -707,6 +747,16 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@internationalized/date": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz",
"integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
}
},
"node_modules/@isaacs/cliui": { "node_modules/@isaacs/cliui": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -729,7 +779,6 @@
"version": "0.3.8", "version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.2.1", "@jridgewell/set-array": "^1.2.1",
@@ -744,7 +793,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -754,7 +802,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -764,14 +811,12 @@
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25", "version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@@ -834,6 +879,69 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/client": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.2.1.tgz",
"integrity": "sha512-msKY2iRLISN8t5X0Tj7hU0UWet1u0KuxSPHWuf3IRkB4J95mCvGpyQBfQ6ufcmvKNOMQSq90O2iUmJEN2e5fiA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"peerDependencies": {
"prisma": "*"
},
"peerDependenciesMeta": {
"prisma": {
"optional": true
}
}
},
"node_modules/@prisma/debug": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.2.1.tgz",
"integrity": "sha512-0KItvt39CmQxWkEw6oW+RQMD6RZ43SJWgEUnzxN8VC9ixMysa7MzZCZf22LCK5DSooiLNf8vM3LHZm/I/Ni7bQ==",
"license": "Apache-2.0"
},
"node_modules/@prisma/engines": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.2.1.tgz",
"integrity": "sha512-lTBNLJBCxVT9iP5I7Mn6GlwqAxTpS5qMERrhebkUhtXpGVkBNd/jHnNJBZQW4kGDCKaQg/r2vlJYkzOHnAb7ZQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.2.1",
"@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69",
"@prisma/fetch-engine": "6.2.1",
"@prisma/get-platform": "6.2.1"
}
},
"node_modules/@prisma/engines-version": {
"version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69.tgz",
"integrity": "sha512-7tw1qs/9GWSX6qbZs4He09TOTg1ff3gYsB3ubaVNN0Pp1zLm9NC5C5MZShtkz7TyQjx7blhpknB7HwEhlG+PrQ==",
"license": "Apache-2.0"
},
"node_modules/@prisma/fetch-engine": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.2.1.tgz",
"integrity": "sha512-OO7O9d6Mrx2F9i+Gu1LW+DGXXyUFkP7OE5aj9iBfA/2jjDXEJjqa9X0ZmM9NZNo8Uo7ql6zKm6yjDcbAcRrw1A==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.2.1",
"@prisma/engines-version": "6.2.0-14.4123509d24aa4dede1e864b46351bf2790323b69",
"@prisma/get-platform": "6.2.1"
}
},
"node_modules/@prisma/get-platform": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.2.1.tgz",
"integrity": "sha512-zp53yvroPl5m5/gXYLz7tGCNG33bhG+JYCm74ohxOq1pPnrL47VQYFfF3RbTZ7TzGWCrR3EtoiYMywUBw7UK6Q==",
"license": "Apache-2.0",
"dependencies": {
"@prisma/debug": "6.2.1"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.32.0", "version": "4.32.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.0.tgz",
@@ -1184,6 +1292,16 @@
"vite": "^6.0.0" "vite": "^6.0.0"
} }
}, },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@tailwindcss/forms": { "node_modules/@tailwindcss/forms": {
"version": "0.5.10", "version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz",
@@ -1213,6 +1331,13 @@
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
} }
}, },
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/cookie": { "node_modules/@types/cookie": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@@ -1224,7 +1349,6 @@
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
@@ -1234,6 +1358,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": {
"version": "22.10.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.10.tgz",
"integrity": "sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.21.0", "version": "8.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.21.0.tgz",
@@ -1444,7 +1578,6 @@
"version": "8.14.0", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@@ -1467,7 +1600,6 @@
"version": "1.4.13", "version": "1.4.13",
"resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
"integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"acorn": ">=8.9.0" "acorn": ">=8.9.0"
@@ -1540,6 +1672,19 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/anymatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/arg": { "node_modules/arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -1558,7 +1703,6 @@
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1606,7 +1750,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1619,6 +1762,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT"
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -1632,6 +1781,61 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/bits-ui": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.22.0.tgz",
"integrity": "sha512-r7Fw1HNgA4YxZBRcozl7oP0bheQ8EHh+kfMBZJgyFISix8t4p/nqDcHLmBgIiJ3T5XjYnJRorYDjIWaCfhb5fw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@internationalized/date": "^3.5.1",
"@melt-ui/svelte": "0.76.2",
"nanoid": "^5.0.5"
},
"funding": {
"url": "https://github.com/sponsors/huntabyte"
},
"peerDependencies": {
"svelte": "^4.0.0 || ^5.0.0"
}
},
"node_modules/bits-ui/node_modules/@melt-ui/svelte": {
"version": "0.76.2",
"resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.76.2.tgz",
"integrity": "sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.3.1",
"@floating-ui/dom": "^1.4.5",
"@internationalized/date": "^3.5.0",
"dequal": "^2.0.3",
"focus-trap": "^7.5.2",
"nanoid": "^5.0.4"
},
"peerDependencies": {
"svelte": ">=3 <5"
}
},
"node_modules/bits-ui/node_modules/nanoid": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz",
"integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1767,7 +1971,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -1883,6 +2086,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/devalue": { "node_modules/devalue": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
@@ -2161,7 +2374,6 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/espree": { "node_modules/espree": {
@@ -2199,7 +2411,6 @@
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz", "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz",
"integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==", "integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"
@@ -2378,6 +2589,16 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/focus-trap": {
"version": "7.6.4",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz",
"integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==",
"dev": true,
"license": "MIT",
"dependencies": {
"tabbable": "^6.2.0"
}
},
"node_modules/foreground-child": { "node_modules/foreground-child": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
@@ -2413,7 +2634,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@@ -2661,7 +2881,6 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "^1.0.6" "@types/estree": "^1.0.6"
@@ -2796,7 +3015,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/locate-path": { "node_modules/locate-path": {
@@ -2847,7 +3065,6 @@
"version": "0.30.17", "version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0" "@jridgewell/sourcemap-codec": "^1.5.0"
@@ -2877,6 +3094,19 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/mini-svg-data-uri": { "node_modules/mini-svg-data-uri": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
@@ -2910,6 +3140,15 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/mode-watcher": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.5.1.tgz",
"integrity": "sha512-adEC6T7TMX/kzQlaO/MtiQOSFekZfQu4MC+lXyoceQG+U5sKpJWZ4yKXqw846ExIuWJgedkOIPqAYYRk/xHm+w==",
"license": "MIT",
"peerDependencies": {
"svelte": "^4.0.0 || ^5.0.0-next.1"
}
},
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -3144,13 +3383,15 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=12"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
@@ -3425,6 +3666,25 @@
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
} }
}, },
"node_modules/prisma": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.2.1.tgz",
"integrity": "sha512-hhyM0H13pQleQ+br4CkzGizS5I0oInoeTw3JfLw1BRZduBSQxPILlJLwi+46wZzj9Je7ndyQEMGw/n5cN2fknA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@prisma/engines": "6.2.1"
},
"bin": {
"prisma": "build/index.js"
},
"engines": {
"node": ">=18.18"
},
"optionalDependencies": {
"fsevents": "2.3.3"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3849,7 +4109,6 @@
"version": "5.19.2", "version": "5.19.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.2.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.2.tgz",
"integrity": "sha512-Ww1uLgdX5MdQrAO5zfU1dWUh6zqiPR6uIbwqm8a+4eQ+tNEYHRPgypvKKfHh9lmTkmJ30PWZ2O5qX8aS+PblRQ==", "integrity": "sha512-Ww1uLgdX5MdQrAO5zfU1dWUh6zqiPR6uIbwqm8a+4eQ+tNEYHRPgypvKKfHh9lmTkmJ30PWZ2O5qX8aS+PblRQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.3.0", "@ampproject/remapping": "^2.3.0",
@@ -3971,6 +4230,52 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
"dev": true,
"license": "MIT"
},
"node_modules/tailwind-merge": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwind-variants": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.1.tgz",
"integrity": "sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"tailwind-merge": "2.5.4"
},
"engines": {
"node": ">=16.x",
"pnpm": ">=7.x"
},
"peerDependencies": {
"tailwindcss": "*"
}
},
"node_modules/tailwind-variants/node_modules/tailwind-merge": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz",
"integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==",
"dev": true,
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "3.4.17", "version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
@@ -4060,6 +4365,19 @@
"url": "https://github.com/sponsors/antonk52" "url": "https://github.com/sponsors/antonk52"
} }
}, },
"node_modules/tailwindcss/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tailwindcss/node_modules/postcss-load-config": { "node_modules/tailwindcss/node_modules/postcss-load-config": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
@@ -4189,6 +4507,13 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4239,6 +4564,13 @@
"typescript": ">=4.8.4 <5.8.0" "typescript": ">=4.8.4 <5.8.0"
} }
}, },
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
@@ -4529,8 +4861,16 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==",
"dev": true,
"license": "MIT" "license": "MIT"
},
"node_modules/zod": {
"version": "3.24.1",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }
+13
View File
@@ -21,7 +21,11 @@
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@types/bcryptjs": "^2.4.6",
"@types/node": "^22.10.10",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"bits-ui": "^0.22.0",
"clsx": "^2.1.1",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1", "eslint-plugin-svelte": "^2.46.1",
@@ -30,9 +34,18 @@
"prettier-plugin-svelte": "^3.3.3", "prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^4.0.0", "svelte-check": "^4.0.0",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
"vite": "^6.0.0" "vite": "^6.0.0"
},
"dependencies": {
"@prisma/client": "^6.2.1",
"bcryptjs": "^2.4.3",
"mode-watcher": "^0.5.1",
"prisma": "^6.2.1",
"zod": "^3.24.1"
} }
} }
@@ -0,0 +1,57 @@
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"email" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"firstName" TEXT,
"lastName" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Role" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL
);
-- CreateTable
CREATE TABLE "_UserRoles" (
"A" INTEGER NOT NULL,
"B" INTEGER NOT NULL,
CONSTRAINT "_UserRoles_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Role_name_key" ON "Role"("name");
-- CreateIndex
CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id");
-- CreateIndex
CREATE INDEX "_UserRoles_B_index" ON "_UserRoles"("B");
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_UserRoles" ADD CONSTRAINT "_UserRoles_A_fkey" FOREIGN KEY ("A") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_UserRoles" ADD CONSTRAINT "_UserRoles_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,8 @@
-- CreateTable
CREATE TABLE "Application" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"domain" TEXT NOT NULL,
CONSTRAINT "Application_pkey" PRIMARY KEY ("id")
);
@@ -0,0 +1,17 @@
/*
Warnings:
- You are about to drop the `Application` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropTable
DROP TABLE "Application";
-- CreateTable
CREATE TABLE "App" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"domain" TEXT NOT NULL,
CONSTRAINT "App_pkey" PRIMARY KEY ("id")
);
@@ -0,0 +1,11 @@
/*
Warnings:
- The primary key for the `App` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `id` on the `App` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "App" DROP CONSTRAINT "App_pkey",
DROP COLUMN "id",
ADD CONSTRAINT "App_pkey" PRIMARY KEY ("domain");
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
+45
View File
@@ -0,0 +1,45 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
passwordHash String
firstName String?
lastName String?
roles Role[] @relation("UserRoles")
sessions Session[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Role {
id Int @id @default(autoincrement())
name String @unique
users User[] @relation("UserRoles")
}
model Session {
id String @unique
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model App {
name String
domain String @id
}
+78 -3
View File
@@ -1,3 +1,78 @@
@import 'tailwindcss/base'; @tailwind base;
@import 'tailwindcss/components'; @tailwind components;
@import 'tailwindcss/utilities'; @tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 83.1%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@@ -0,0 +1,25 @@
<script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui";
import { type Events, type Props, buttonVariants } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = Props;
type $$Events = Events;
let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default";
export let size: $$Props["size"] = "default";
export let builders: $$Props["builders"] = [];
export { className as class };
</script>
<ButtonPrimitive.Root
{builders}
class={cn(buttonVariants({ variant, size, className }))}
type="button"
{...$$restProps}
on:click
on:keydown
>
<slot />
</ButtonPrimitive.Root>
+49
View File
@@ -0,0 +1,49 @@
import { type VariantProps, tv } from "tailwind-variants";
import type { Button as ButtonPrimitive } from "bits-ui";
import Root from "./button.svelte";
const buttonVariants = tv({
base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border-input bg-background hover:bg-accent hover:text-accent-foreground border",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export {
Root,
type Props,
type Events,
//
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants,
};
@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("p-6", className)} {...$$restProps}>
<slot />
</div>
@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLParagraphElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<p class={cn("text-muted-foreground text-sm", className)} {...$$restProps}>
<slot />
</p>
@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex items-center p-6 pt-0", className)} {...$$restProps}>
<slot />
</div>
@@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 p-6 pb-0", className)} {...$$restProps}>
<slot />
</div>
@@ -0,0 +1,21 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import type { HeadingLevel } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
tag?: HeadingLevel;
};
let className: $$Props["class"] = undefined;
export let tag: $$Props["tag"] = "h3";
export { className as class };
</script>
<svelte:element
this={tag}
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</svelte:element>
+16
View File
@@ -0,0 +1,16 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("bg-card text-card-foreground rounded-lg border shadow-sm", className)}
{...$$restProps}
>
<slot />
</div>
+24
View File
@@ -0,0 +1,24 @@
import Root from "./card.svelte";
import Content from "./card-content.svelte";
import Description from "./card-description.svelte";
import Footer from "./card-footer.svelte";
import Header from "./card-header.svelte";
import Title from "./card-title.svelte";
export {
Root,
Content,
Description,
Footer,
Header,
Title,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle,
};
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
+29
View File
@@ -0,0 +1,29 @@
import Root from "./input.svelte";
export type FormInputEvent<T extends Event = Event> = T & {
currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
blur: FormInputEvent<FocusEvent>;
change: FormInputEvent<Event>;
click: FormInputEvent<MouseEvent>;
focus: FormInputEvent<FocusEvent>;
focusin: FormInputEvent<FocusEvent>;
focusout: FormInputEvent<FocusEvent>;
keydown: FormInputEvent<KeyboardEvent>;
keypress: FormInputEvent<KeyboardEvent>;
keyup: FormInputEvent<KeyboardEvent>;
mouseover: FormInputEvent<MouseEvent>;
mouseenter: FormInputEvent<MouseEvent>;
mouseleave: FormInputEvent<MouseEvent>;
mousemove: FormInputEvent<MouseEvent>;
paste: FormInputEvent<ClipboardEvent>;
input: FormInputEvent<InputEvent>;
wheel: FormInputEvent<WheelEvent>;
};
export {
Root,
//
Root as Input,
};
+42
View File
@@ -0,0 +1,42 @@
<script lang="ts">
import type { HTMLInputAttributes } from "svelte/elements";
import type { InputEvents } from "./index.js";
import { cn } from "$lib/utils.js";
type $$Props = HTMLInputAttributes;
type $$Events = InputEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
// Workaround for https://github.com/sveltejs/svelte/issues/9305
// Fixed in Svelte 5, but not backported to 4.x.
export let readonly: $$Props["readonly"] = undefined;
</script>
<input
class={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:value
{readonly}
on:blur
on:change
on:click
on:focus
on:focusin
on:focusout
on:keydown
on:keypress
on:keyup
on:mouseover
on:mouseenter
on:mouseleave
on:mousemove
on:paste
on:input
on:wheel|passive
{...$$restProps}
/>
+7
View File
@@ -0,0 +1,7 @@
import Root from "./label.svelte";
export {
Root,
//
Root as Label,
};
+21
View File
@@ -0,0 +1,21 @@
<script lang="ts">
import { Label as LabelPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
type $$Props = LabelPrimitive.Props;
type $$Events = LabelPrimitive.Events;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<LabelPrimitive.Root
class={cn(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
className
)}
{...$$restProps}
on:mousedown
>
<slot />
</LabelPrimitive.Root>
+3
View File
@@ -0,0 +1,3 @@
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();
+62
View File
@@ -0,0 +1,62 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { cubicOut } from "svelte/easing";
import type { TransitionConfig } from "svelte/transition";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
type FlyAndScaleParams = {
y?: number;
x?: number;
start?: number;
duration?: number;
};
export const flyAndScale = (
node: Element,
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => {
const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform;
const scaleConversion = (
valueA: number,
scaleA: [number, number],
scaleB: [number, number]
) => {
const [minA, maxA] = scaleA;
const [minB, maxB] = scaleB;
const percentage = (valueA - minA) / (maxA - minA);
const valueB = percentage * (maxB - minB) + minB;
return valueB;
};
const styleToString = (
style: Record<string, number | string | undefined>
): string => {
return Object.keys(style).reduce((str, key) => {
if (style[key] === undefined) return str;
return str + `${key}:${style[key]};`;
}, "");
};
return {
duration: params.duration ?? 200,
delay: 0,
css: (t) => {
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
return styleToString({
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
opacity: t
});
},
easing: cubicOut
};
};
+10
View File
@@ -0,0 +1,10 @@
<script>
import { page } from '$app/stores';
</script>
<div class="flex flex-riow gap-4">
{#if $page.error}
<span class="text-red-500 font-bold">{$page.status}:</span>
<p>{$page.error.message}</p>
{/if}
</div>
+6 -1
View File
@@ -1,6 +1,11 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
let { children } = $props(); let { children } = $props();
import { ModeWatcher } from 'mode-watcher';
</script> </script>
{@render children()} <ModeWatcher />
<div class="w-screen h-screen flex flex-col items-center justify-center">
{@render children()}
</div>
+77
View File
@@ -0,0 +1,77 @@
// src/routes/api/login/+server.ts
import { prisma } from '$lib/prisma';
import bcrypt from 'bcryptjs';
import { randomBytes } from 'crypto'; // For generating a random session ID
import { z } from 'zod';
import { json, type RequestEvent } from '@sveltejs/kit';
import { serialize } from 'cookie'; // For cookie serialization
// Define the schema for validation using Zod
const loginSchema = z.object({
email: z.string().email().min(5, 'Email is required'),
password: z.string().min(8, 'Password must be at least 8 characters')
});
export const POST = async ({ request }: RequestEvent) => {
try {
// Parse the request body
const formData = await request.formData();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
// Validate the incoming data
const parsed = loginSchema.safeParse({ email, password });
if (!parsed.success) {
return json({ message: parsed.error.errors[0].message }, { status: 400 });
}
// Find the user in the database by email
const user = await prisma.user.findUnique({
where: { email }
});
if (!user) {
return json({ message: 'Invalid email or password' }, { status: 400 });
}
// Compare the provided password with the hashed password in the database
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
if (!isPasswordValid) {
return json({ message: 'Invalid email or password' }, { status: 400 });
}
// Generate a secure random session ID using crypto
const sessionId = randomBytes(16).toString('hex'); // 64-byte hex session ID
// Create a new session in the database
await prisma.session.create({
data: {
id: sessionId, // Session ID is generated randomly
userId: user.id // Associate session with user
}
});
// Set the session ID in an HttpOnly cookie
const cookie = serialize('session_id', sessionId, {
httpOnly: true, // Ensures the cookie is not accessible via JavaScript
secure: process.env.NODE_ENV === 'production', // Set secure flag in production
maxAge: 60 * 60, // 1 hour expiry
path: '/' // Make the cookie available throughout the site
});
// Respond with the session token
return json(
{ message: 'Login successful' },
{
status: 200,
headers: { 'Set-Cookie': cookie } // Set cookie header
}
);
} catch (error) {
console.error(error);
return json({ message: 'Internal server error' }, { status: 500 });
}
};
+42
View File
@@ -0,0 +1,42 @@
import { json } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export const GET = async ({ cookies }: RequestEvent) => {
// Retrieve the session_id cookie
const sessionId = cookies.get('session_id');
// If there's no session_id cookie, return an empty object
if (!sessionId) {
return json({ message: 'No session cookie!' }, { status: 500 });
}
try {
// Find the session and include the associated user
const session = await prisma.session.findUnique({
where: { id: sessionId },
include: { user: { include: { roles: true } } }
});
// If no session is found, return an empty object
if (!session) {
return json({ message: 'Session doesnt exist!' }, { status: 500 });
}
// Sanitize the user data by removing the passwordHash and sanitizing the session
const sanitizedUser = {
...session.user,
passwordHash: undefined, // Remove the password hash
sessions: undefined // Remove sessions if included in the model
};
// Return the sanitized user data
return json(sanitizedUser);
} catch (error) {
// Log the error and return an empty object
console.error('Error fetching session:', error);
return json({ message: 'Internal server error' }, { status: 500 });
}
};
+74
View File
@@ -0,0 +1,74 @@
import { prisma } from '$lib/prisma'; // Your Prisma client instance
import bcrypt from 'bcryptjs';
import { z } from 'zod';
import { type RequestHandler } from '@sveltejs/kit';
// Define the schema for validation using Zod
const registerSchema = z.object({
email: z.string().email().min(5, 'Email is required'),
password: z.string().min(8, 'Password must be at least 8 characters'),
firstName: z.string().optional(),
lastName: z.string().optional()
});
// Define the POST handler with explicit typing for the request
export const POST: RequestHandler = async ({ request }) => {
try {
// Parse form data
const formData = await request.formData();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
// Validate the incoming data
const parsed = registerSchema.safeParse({
email,
password,
firstName: formData.get('firstName') as string | null,
lastName: formData.get('lastName') as string | null
});
if (!parsed.success) {
return new Response(JSON.stringify({ message: parsed.error.errors[0].message }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
// Check if the email already exists
const existingUser = await prisma.user.findUnique({
where: { email }
});
if (existingUser) {
return new Response(JSON.stringify({ message: 'Email already registered' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
// Hash the password before storing it
const hashedPassword = await bcrypt.hash(password, 10);
// Create the new user in the database
await prisma.user.create({
data: {
email,
passwordHash: hashedPassword,
firstName: parsed.data.firstName || undefined, // Store firstName if provided
lastName: parsed.data.lastName || undefined // Store lastName if provided
}
});
// Redirect to the login page
return new Response(JSON.stringify({ message: 'Successfully created an account' }), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
// Handle unexpected errors
return new Response(JSON.stringify({ message: 'Internal server error', error: error }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
};
+56
View File
@@ -0,0 +1,56 @@
import { prisma } from '$lib/prisma';
import { parse } from 'cookie';
import { redirect, error } from '@sveltejs/kit';
import type { ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = async ({ request, url }) => {
const cookies = parse(request.headers.get('cookie') || '');
const sessionId = cookies['session_id'];
const redirectURL = url.searchParams.get('redirect');
if (!redirectURL) throw error(400, 'Invalid redirect domain');
if (sessionId) {
// Validate the session by checking if the session ID exists in the database
const session = await prisma.session.findUnique({
where: { id: sessionId },
include: { user: true }
});
if (session) {
// Session is valid, check if there's a redirectURL query parameter
if (redirectURL) {
let redirectWithToken;
try {
redirectWithToken = new URL(redirectURL);
// Extract the domain from the redirect URL
const redirectDomain = redirectWithToken.hostname;
// Check if an application exists with the provided domain
const app = await prisma.app.findFirst({
where: { domain: redirectDomain }
});
if (!app) {
// If no application with the domain exists, throw an error
throw error(400, 'Invalid redirect domain');
}
// Append the session ID or token to the redirect URL
redirectWithToken.searchParams.append('token', session.id);
// Perform the redirect with the session token attached
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
// Handle malformed URL or other errors
throw error(400, 'Invalid redirect domain!');
}
redirect(302, redirectWithToken.toString());
}
}
}
// If no session exists, allow the user to stay on the login page or show the login form
return {};
};
+72
View File
@@ -0,0 +1,72 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
let redirectUrl: string | null = page.url.searchParams.get('redirect');
let email = '';
let password = '';
let successMessage: string | null = null;
let errorMessage: string | null = null;
const login = async (event: Event) => {
event.preventDefault();
successMessage = null;
errorMessage = null;
try {
// Send login request
const response = await fetch('/api/login', {
method: 'POST',
body: new URLSearchParams({ email, password })
});
if (response.ok) {
successMessage = 'Login successful! Redirecting...';
setTimeout(() => {
location.reload(); // Reload the page to reflect the logged-in state
}, 2000); // Add slight delay to show success message
} else {
errorMessage = 'Login failed: Invalid email or password.';
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
errorMessage = 'An error occurred while trying to log in. Please try again later.';
}
};
</script>
<Card.Root class="w-[350px]">
<form on:submit={login}>
<Card.Header>
<Card.Title>Login to Account</Card.Title>
<Card.Description>Please enter your credentials below to login.</Card.Description>
<Card.Description>You will be redirected to .</Card.Description>
</Card.Header>
<Card.Content>
<!-- Display success or error message -->
{#if successMessage}
<p class="text-green-500 mb-4">{successMessage}</p>
{:else if errorMessage}
<p class="text-red-500 mb-4">{errorMessage}</p>
{/if}
<Label for="email">Email:</Label>
<Input id="email" type="email" bind:value={email} required />
<Label for="password">Password:</Label>
<Input id="password" type="password" bind:value={password} required />
<!-- Submit button within the form -->
</Card.Content>
<Card.Footer class="flex justify-between">
<Button variant="outline" on:click={() => goto(`/register?redirect=${redirectUrl}`)}
>Register</Button
>
<Button type="submit">Login</Button>
</Card.Footer>
</form>
</Card.Root>
+13
View File
@@ -0,0 +1,13 @@
import { redirect } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';
export const GET = async ({ cookies, url }: RequestEvent) => {
// Delete the cookie
cookies.delete('session_id', { path: '/' });
// Get the redirect URL from the query parameter
const redirectUrl = url.searchParams.get('redirect') || 'https://rummage.cc';
// Redirect to the specified URL
throw redirect(302, redirectUrl);
};
+84
View File
@@ -0,0 +1,84 @@
<script lang="ts">
import { Button } from '$lib/components/ui/button/index';
import * as Card from '$lib/components/ui/card/index';
import { Input } from '$lib/components/ui/input/index';
import { Label } from '$lib/components/ui/label/index';
import { page } from '$app/state';
import { goto } from '$app/navigation'; // Import for redirecting to the login page
let redirectUrl: string | null = page.url.searchParams.get('redirect');
let email = '';
let password = '';
let firstName = '';
let lastName = '';
let errorMessage = '';
let successMessage = '';
// Form submission handler
async function submitForm() {
// Reset messages
errorMessage = '';
successMessage = '';
const formData = new FormData();
formData.append('email', email);
formData.append('password', password);
formData.append('firstName', firstName);
formData.append('lastName', lastName);
try {
const response = await fetch('/api/register', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
successMessage = data.message;
setTimeout(() => {
goto(`/login?redirect=${redirectUrl}`); // Redirect to login page on success
}, 1000); // Delay redirect to show success message
} else {
errorMessage = data.message;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
errorMessage = 'An error occurred. Please try again.';
}
}
</script>
<Card.Root class="w-[350px]">
<form on:submit|preventDefault={submitForm}>
<Card.Header>
<Card.Title>Register Account</Card.Title>
<Card.Description>Please fill in the details below to create an account.</Card.Description>
</Card.Header>
<Card.Content>
{#if successMessage}
<p class="text-green-500">{successMessage}</p>
{:else if errorMessage}
<p class="text-red-500">{errorMessage}</p>
{/if}
<Label for="email">Email:</Label>
<Input id="email" type="email" bind:value={email} required />
<Label for="password">Password:</Label>
<Input id="password" type="password" bind:value={password} required />
<Label for="firstName">First Name:</Label>
<Input id="firstName" type="text" bind:value={firstName} />
<Label for="lastName">Last Name:</Label>
<Input id="lastName" type="text" bind:value={lastName} />
</Card.Content>
<Card.Footer class="flex justify-between">
<Button variant="outline" on:click={() => goto(`/login?redirect=${redirectUrl}`)}
>Login</Button
>
<Button type="submit">Register</Button>
</Card.Footer>
</form>
</Card.Root>
+60 -9
View File
@@ -1,13 +1,64 @@
import forms from '@tailwindcss/forms'; import { fontFamily } from "tailwindcss/defaultTheme";
import typography from '@tailwindcss/typography'; import type { Config } from "tailwindcss";
import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
const config: Config = {
darkMode: ["class"],
content: ["./src/**/*.{html,js,svelte,ts}"],
safelist: ["dark"],
theme: { theme: {
extend: {} container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px"
}
},
extend: {
colors: {
border: "hsl(var(--border) / <alpha-value>)",
input: "hsl(var(--input) / <alpha-value>)",
ring: "hsl(var(--ring) / <alpha-value>)",
background: "hsl(var(--background) / <alpha-value>)",
foreground: "hsl(var(--foreground) / <alpha-value>)",
primary: {
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
},
secondary: {
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
},
destructive: {
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
},
muted: {
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
},
accent: {
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
},
popover: {
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
},
card: {
DEFAULT: "hsl(var(--card) / <alpha-value>)",
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
}
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)"
},
fontFamily: {
sans: [...fontFamily.sans]
}
}
}, },
};
plugins: [typography, forms] export default config;
} satisfies Config;