feat: auth system
This commit is contained in:
parent
ed94a8de34
commit
5faf40474f
25 changed files with 778 additions and 9 deletions
27
db/pb_migrations/1754317478_updated_users.js
Normal file
27
db/pb_migrations/1754317478_updated_users.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
|
||||
|
||||
// update collection data
|
||||
unmarshal({
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
|
||||
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''",
|
||||
"CREATE UNIQUE INDEX `idx_GloaDPerfv` ON `users` (`name`)"
|
||||
]
|
||||
}, collection)
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
|
||||
|
||||
// update collection data
|
||||
unmarshal({
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
|
||||
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''"
|
||||
]
|
||||
}, collection)
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
42
db/pb_migrations/1754380807_updated_users.js
Normal file
42
db/pb_migrations/1754380807_updated_users.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
|
||||
|
||||
// update field
|
||||
collection.fields.addAt(6, new Field({
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 255,
|
||||
"min": 3,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
|
||||
|
||||
// update field
|
||||
collection.fields.addAt(6, new Field({
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 255,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
11
package.json
11
package.json
|
|
@ -9,7 +9,7 @@
|
|||
"db_dev": "./db/pocketbase serve --dir ./db/pb_data",
|
||||
"vite_build": "vite build",
|
||||
"db_copy": "cp ./db/pocketbase ./out && cp -r ./db/pb_migrations ./out",
|
||||
"preview": "./out/pocketbase serve --dir ./out/pb_data --publicDir ./out/build",
|
||||
"preview": "ENV=PROD ./out/pocketbase serve --dir ./out/pb_data --publicDir ./out/build",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
|
|
@ -19,16 +19,19 @@
|
|||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@lucide/svelte": "^0.536.0",
|
||||
"@internationalized/date": "^3.8.2",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/adapter-static": "^3.0.8",
|
||||
"@sveltejs/kit": "^2.22.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"bits-ui": "^2.9.1",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"pocketbase": "^0.26.2",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
|
|
@ -44,7 +47,11 @@
|
|||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@tailwindcss/oxide",
|
||||
"esbuild"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^4.0.14"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
130
pnpm-lock.yaml
generated
130
pnpm-lock.yaml
generated
|
|
@ -7,6 +7,10 @@ settings:
|
|||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
zod:
|
||||
specifier: ^4.0.14
|
||||
version: 4.0.14
|
||||
devDependencies:
|
||||
'@eslint/compat':
|
||||
specifier: ^1.2.5
|
||||
|
|
@ -14,9 +18,12 @@ importers:
|
|||
'@eslint/js':
|
||||
specifier: ^9.18.0
|
||||
version: 9.32.0
|
||||
'@internationalized/date':
|
||||
specifier: ^3.8.2
|
||||
version: 3.8.2
|
||||
'@lucide/svelte':
|
||||
specifier: ^0.536.0
|
||||
version: 0.536.0(svelte@5.37.3)
|
||||
specifier: ^0.515.0
|
||||
version: 0.515.0(svelte@5.37.3)
|
||||
'@sveltejs/adapter-static':
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.8(@sveltejs/kit@2.27.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.37.3)(vite@7.0.6(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)(vite@7.0.6(jiti@2.5.1)(lightningcss@1.30.1)))
|
||||
|
|
@ -29,6 +36,9 @@ importers:
|
|||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.0
|
||||
version: 4.1.11(vite@7.0.6(jiti@2.5.1)(lightningcss@1.30.1))
|
||||
bits-ui:
|
||||
specifier: ^2.9.1
|
||||
version: 2.9.1(@internationalized/date@3.8.2)(svelte@5.37.3)
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
|
|
@ -44,6 +54,9 @@ importers:
|
|||
globals:
|
||||
specifier: ^16.0.0
|
||||
version: 16.3.0
|
||||
pocketbase:
|
||||
specifier: ^0.26.2
|
||||
version: 0.26.2
|
||||
prettier:
|
||||
specifier: ^3.4.2
|
||||
version: 3.6.2
|
||||
|
|
@ -290,6 +303,15 @@ packages:
|
|||
resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@floating-ui/core@1.7.3':
|
||||
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
|
||||
|
||||
'@floating-ui/dom@1.7.3':
|
||||
resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==}
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
|
@ -310,6 +332,9 @@ packages:
|
|||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@internationalized/date@3.8.2':
|
||||
resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==}
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
|
@ -327,8 +352,8 @@ packages:
|
|||
'@jridgewell/trace-mapping@0.3.29':
|
||||
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
|
||||
|
||||
'@lucide/svelte@0.536.0':
|
||||
resolution: {integrity: sha512-YAeoWU+0B/RriFZZ3wHno1FMkbrVrFdityuo2B0YuphD0vtJWXStzZkWLGVhT3jMb7zhugmhayIg+gI4+AZu1g==}
|
||||
'@lucide/svelte@0.515.0':
|
||||
resolution: {integrity: sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==}
|
||||
peerDependencies:
|
||||
svelte: ^5
|
||||
|
||||
|
|
@ -484,6 +509,9 @@ packages:
|
|||
svelte: ^5.0.0
|
||||
vite: ^6.3.0 || ^7.0.0
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
|
||||
|
||||
'@tailwindcss/node@4.1.11':
|
||||
resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==}
|
||||
|
||||
|
|
@ -673,6 +701,13 @@ packages:
|
|||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
bits-ui@2.9.1:
|
||||
resolution: {integrity: sha512-Fdtc1TKppfEio6fl0XzkrDG8DSvfbYJdrOh4j5KzaGh0kYA6BskMGIXL4JmsZxUS+O5ESFypNjrQ9z9qs3xAdQ==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
'@internationalized/date': ^3.8.1
|
||||
svelte: ^5.33.0
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
|
||||
|
|
@ -916,6 +951,9 @@ packages:
|
|||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
inline-style-parser@0.2.4:
|
||||
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -1128,6 +1166,9 @@ packages:
|
|||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pocketbase@0.26.2:
|
||||
resolution: {integrity: sha512-WA8EOBc3QnSJh8rJ3iYoi9DmmPOMFIgVfAmIGux7wwruUEIzXgvrO4u0W2htfQjGIcyezJkdZOy5Xmh7SxAftw==}
|
||||
|
||||
postcss-load-config@3.1.4:
|
||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
||||
engines: {node: '>= 10'}
|
||||
|
|
@ -1263,6 +1304,11 @@ packages:
|
|||
run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
|
||||
runed@0.29.2:
|
||||
resolution: {integrity: sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA==}
|
||||
peerDependencies:
|
||||
svelte: ^5.7.0
|
||||
|
||||
sade@1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -1295,6 +1341,9 @@ packages:
|
|||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
style-to-object@1.0.9:
|
||||
resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -1316,10 +1365,19 @@ packages:
|
|||
svelte:
|
||||
optional: true
|
||||
|
||||
svelte-toolbelt@0.9.3:
|
||||
resolution: {integrity: sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
peerDependencies:
|
||||
svelte: ^5.30.2
|
||||
|
||||
svelte@5.37.3:
|
||||
resolution: {integrity: sha512-7t/ejshehHd+95z3Z7ebS7wsqHDQxi/8nBTuTRwpMgNegfRBfuitCSKTUDKIBOExqfT2+DhQ2VLG8Xn+cBXoaQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tabbable@6.2.0:
|
||||
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
||||
|
||||
tailwind-merge@3.0.2:
|
||||
resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
|
||||
|
||||
|
|
@ -1361,6 +1419,9 @@ packages:
|
|||
peerDependencies:
|
||||
typescript: '>=4.8.4'
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tw-animate-css@1.3.6:
|
||||
resolution: {integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==}
|
||||
|
||||
|
|
@ -1458,6 +1519,9 @@ packages:
|
|||
zimmerframe@1.1.2:
|
||||
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
|
||||
|
||||
zod@4.0.14:
|
||||
resolution: {integrity: sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
|
|
@ -1591,6 +1655,17 @@ snapshots:
|
|||
'@eslint/core': 0.15.1
|
||||
levn: 0.4.1
|
||||
|
||||
'@floating-ui/core@1.7.3':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.3':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.6':
|
||||
|
|
@ -1604,6 +1679,10 @@ snapshots:
|
|||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@internationalized/date@3.8.2':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.17
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
dependencies:
|
||||
minipass: 7.1.2
|
||||
|
|
@ -1622,7 +1701,7 @@ snapshots:
|
|||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.4
|
||||
|
||||
'@lucide/svelte@0.536.0(svelte@5.37.3)':
|
||||
'@lucide/svelte@0.515.0(svelte@5.37.3)':
|
||||
dependencies:
|
||||
svelte: 5.37.3
|
||||
|
||||
|
|
@ -1751,6 +1830,10 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tailwindcss/node@4.1.11':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
|
|
@ -1946,6 +2029,17 @@ snapshots:
|
|||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bits-ui@2.9.1(@internationalized/date@3.8.2)(svelte@5.37.3):
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/dom': 1.7.3
|
||||
'@internationalized/date': 3.8.2
|
||||
esm-env: 1.2.2
|
||||
runed: 0.29.2(svelte@5.37.3)
|
||||
svelte: 5.37.3
|
||||
svelte-toolbelt: 0.9.3(svelte@5.37.3)
|
||||
tabbable: 6.2.0
|
||||
|
||||
brace-expansion@1.1.12:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
|
@ -2211,6 +2305,8 @@ snapshots:
|
|||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
inline-style-parser@0.2.4: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
|
|
@ -2373,6 +2469,8 @@ snapshots:
|
|||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pocketbase@0.26.2: {}
|
||||
|
||||
postcss-load-config@3.1.4(postcss@8.5.6):
|
||||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
|
|
@ -2454,6 +2552,11 @@ snapshots:
|
|||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
runed@0.29.2(svelte@5.37.3):
|
||||
dependencies:
|
||||
esm-env: 1.2.2
|
||||
svelte: 5.37.3
|
||||
|
||||
sade@1.8.1:
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
|
|
@ -2478,6 +2581,10 @@ snapshots:
|
|||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
style-to-object@1.0.9:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
|
@ -2505,6 +2612,13 @@ snapshots:
|
|||
optionalDependencies:
|
||||
svelte: 5.37.3
|
||||
|
||||
svelte-toolbelt@0.9.3(svelte@5.37.3):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
runed: 0.29.2(svelte@5.37.3)
|
||||
style-to-object: 1.0.9
|
||||
svelte: 5.37.3
|
||||
|
||||
svelte@5.37.3:
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
|
|
@ -2522,6 +2636,8 @@ snapshots:
|
|||
magic-string: 0.30.17
|
||||
zimmerframe: 1.1.2
|
||||
|
||||
tabbable@6.2.0: {}
|
||||
|
||||
tailwind-merge@3.0.2: {}
|
||||
|
||||
tailwind-merge@3.3.1: {}
|
||||
|
|
@ -2559,6 +2675,8 @@ snapshots:
|
|||
dependencies:
|
||||
typescript: 5.9.2
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tw-animate-css@1.3.6: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
|
|
@ -2614,3 +2732,5 @@ snapshots:
|
|||
yocto-queue@0.1.0: {}
|
||||
|
||||
zimmerframe@1.1.2: {}
|
||||
|
||||
zod@4.0.14: {}
|
||||
|
|
|
|||
29
src/lib/components/Navigation.svelte
Normal file
29
src/lib/components/Navigation.svelte
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
import { pb } from '$lib/db.svelte';
|
||||
import Button from './ui/button/button.svelte';
|
||||
</script>
|
||||
|
||||
<a class="absolute top-[50%] left-4 translate-y-[-50%] text-5xl" href="/">Heddit</a>
|
||||
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search on heddit..."
|
||||
class="absolute top-[50%] left-[50%] w-[40%] translate-[-50%]"
|
||||
/>
|
||||
|
||||
<div class="absolute top-[50%] right-4 flex w-fit translate-y-[-50%] gap-4">
|
||||
{#if pb.authStore.record?.name}
|
||||
<Button
|
||||
class="cursor-pointer"
|
||||
onclick={async () => {
|
||||
pb.authStore.clear();
|
||||
window.location.reload();
|
||||
}}>Logout</Button
|
||||
>
|
||||
{:else}
|
||||
<Button variant="secondary" href="/auth/login">Login</Button>
|
||||
<Button href="/auth/register">Register</Button>
|
||||
{/if}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card-action.svelte
Normal file
20
src/lib/components/ui/card/card-action.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-action"
|
||||
class={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
15
src/lib/components/ui/card/card-content.svelte
Normal file
15
src/lib/components/ui/card/card-content.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div bind:this={ref} data-slot="card-content" class={cn("px-6", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card-description.svelte
Normal file
20
src/lib/components/ui/card/card-description.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
|
||||
</script>
|
||||
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</p>
|
||||
20
src/lib/components/ui/card/card-footer.svelte
Normal file
20
src/lib/components/ui/card/card-footer.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn("[.border-t]:pt-6 flex items-center px-6", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
src/lib/components/ui/card/card-header.svelte
Normal file
23
src/lib/components/ui/card/card-header.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
"@container/card-header has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6 grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
20
src/lib/components/ui/card/card-title.svelte
Normal file
20
src/lib/components/ui/card/card-title.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card-title"
|
||||
class={cn("font-semibold leading-none", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
23
src/lib/components/ui/card/card.svelte
Normal file
23
src/lib/components/ui/card/card.svelte
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="card"
|
||||
class={cn(
|
||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
25
src/lib/components/ui/card/index.ts
Normal file
25
src/lib/components/ui/card/index.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
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";
|
||||
import Action from "./card-action.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Content,
|
||||
Description,
|
||||
Footer,
|
||||
Header,
|
||||
Title,
|
||||
Action,
|
||||
//
|
||||
Root as Card,
|
||||
Content as CardContent,
|
||||
Description as CardDescription,
|
||||
Footer as CardFooter,
|
||||
Header as CardHeader,
|
||||
Title as CardTitle,
|
||||
Action as CardAction,
|
||||
};
|
||||
7
src/lib/components/ui/input/index.ts
Normal file
7
src/lib/components/ui/input/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Root from "./input.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
51
src/lib/components/ui/input/input.svelte
Normal file
51
src/lib/components/ui/input/input.svelte
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<script lang="ts">
|
||||
import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
|
||||
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||
|
||||
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
|
||||
|
||||
type Props = WithElementRef<
|
||||
Omit<HTMLInputAttributes, "type"> &
|
||||
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
|
||||
>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
type,
|
||||
files = $bindable(),
|
||||
class: className,
|
||||
...restProps
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if type === "file"}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot="input"
|
||||
class={cn(
|
||||
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
bind:files
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
bind:this={ref}
|
||||
data-slot="input"
|
||||
class={cn(
|
||||
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
bind:value
|
||||
{...restProps}
|
||||
/>
|
||||
{/if}
|
||||
7
src/lib/components/ui/label/index.ts
Normal file
7
src/lib/components/ui/label/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Root from "./label.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Label,
|
||||
};
|
||||
20
src/lib/components/ui/label/label.svelte
Normal file
20
src/lib/components/ui/label/label.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { Label as LabelPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: LabelPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<LabelPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="label"
|
||||
class={cn(
|
||||
"flex select-none items-center gap-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
18
src/lib/db.svelte.ts
Normal file
18
src/lib/db.svelte.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import PocketBase from 'pocketbase';
|
||||
|
||||
const db_url = import.meta.env.PROD ? '/' : 'http://localhost:8090';
|
||||
console.log(import.meta.env.PROD, db_url);
|
||||
|
||||
export const pb = new PocketBase(db_url);
|
||||
|
||||
async function refreshAuth() {
|
||||
if (pb.authStore.token) {
|
||||
try {
|
||||
await pb.collection('users').authRefresh();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshAuth();
|
||||
|
|
@ -1 +0,0 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
6
src/lib/schemas/login.ts
Normal file
6
src/lib/schemas/login.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import * as z from 'zod';
|
||||
|
||||
export const loginSchema = z.object({
|
||||
email: z.email(),
|
||||
password: z.string().min(8).max(255)
|
||||
})
|
||||
8
src/lib/schemas/register.ts
Normal file
8
src/lib/schemas/register.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import * as z from 'zod';
|
||||
|
||||
export const registerSchema = z.object({
|
||||
email: z.email(),
|
||||
name: z.string().min(3).max(255),
|
||||
password: z.string().min(8).max(255),
|
||||
passwordConfirm: z.string().min(8).max(255)
|
||||
})
|
||||
|
|
@ -1,12 +1,19 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
import Navigation from '$lib/components/Navigation.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="icon" href={favicon} />
|
||||
<title>Heddit</title>
|
||||
</svelte:head>
|
||||
|
||||
<header class="absolute top-0 left-0 w-full h-16">
|
||||
<Navigation></Navigation>
|
||||
</header>
|
||||
<main class="absolute top-16 left-0 w-full h-[calc(100%-4rem)] p-4">
|
||||
{@render children?.()}
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<script>
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import { pb } from '$lib/db.svelte';
|
||||
</script>
|
||||
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>
|
||||
Visit <Button variant="link" href="https://svelte.dev/docs/kits">svelte.dev/docs/kit</Button> to read
|
||||
the documentation
|
||||
{pb.authStore.record?.name}
|
||||
</p>
|
||||
|
|
|
|||
102
src/routes/auth/login/+page.svelte
Normal file
102
src/routes/auth/login/+page.svelte
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { pb } from '$lib/db.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { loginSchema } from '$lib/schemas/login';
|
||||
import { ZodError } from 'zod';
|
||||
import { ClientResponseError } from 'pocketbase';
|
||||
|
||||
let error = '';
|
||||
|
||||
const form = {
|
||||
email: '',
|
||||
password: ''
|
||||
};
|
||||
|
||||
interface FormErrors {
|
||||
email: boolean;
|
||||
password: boolean;
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
const formErrors = {
|
||||
email: false,
|
||||
password: false
|
||||
} as FormErrors;
|
||||
|
||||
function validateForm() {
|
||||
try {
|
||||
loginSchema.parse(form);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof ZodError) {
|
||||
error = e.issues[0].message;
|
||||
formErrors[e.issues[0].path[0] as string] = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleDBErrors(e: ClientResponseError) {
|
||||
const element = Object.keys(e.data.data)[0];
|
||||
formErrors[element] = true;
|
||||
const dbError = e.data.data[element];
|
||||
error = dbError.message;
|
||||
}
|
||||
|
||||
async function login() {
|
||||
// reset all errors
|
||||
error = '';
|
||||
Object.keys(formErrors).forEach((i) => {
|
||||
formErrors[i] = false;
|
||||
});
|
||||
|
||||
if (validateForm()) {
|
||||
try {
|
||||
await pb.collection('users').authWithPassword(form.email, form.password);
|
||||
await goto('/');
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError) {
|
||||
handleDBErrors(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root class="absolute top-[50%] left-[50%] w-full max-w-sm translate-[-50%]">
|
||||
<Card.Header>
|
||||
<Card.Title>Login to your account</Card.Title>
|
||||
<Card.Description>Enter your email below to login</Card.Description>
|
||||
<Card.Action>
|
||||
<Button variant="link" href="/auth/register">Register</Button>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="grid gap-2">
|
||||
<Label for="email">Email</Label>
|
||||
<Input id="email" type="email" placeholder="m@example.com" required />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center">
|
||||
<Label for="password">Password</Label>
|
||||
<a href="##" class="ml-auto inline-block text-sm underline-offset-4 hover:underline">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
<Input id="password" type="password" required />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
<Card.Footer class="flex-col gap-2">
|
||||
<Button type="submit" class="w-full cursor-pointer" onclick={login}>Login</Button>
|
||||
<p class="mt-4 text-red-700">{error}</p>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
151
src/routes/auth/register/+page.svelte
Normal file
151
src/routes/auth/register/+page.svelte
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { Input } from '$lib/components/ui/input/index.js';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import { pb } from '$lib/db.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { registerSchema } from '$lib/schemas/register';
|
||||
import { ZodError } from 'zod';
|
||||
import { ClientResponseError } from 'pocketbase';
|
||||
|
||||
let error = '';
|
||||
|
||||
const form = {
|
||||
email: '',
|
||||
name: '',
|
||||
password: '',
|
||||
passwordConfirm: ''
|
||||
};
|
||||
|
||||
interface FormErrors {
|
||||
email: boolean;
|
||||
name: boolean;
|
||||
password: boolean;
|
||||
passwordConfirm: boolean;
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
const formErrors = {
|
||||
email: false,
|
||||
name: false,
|
||||
password: false,
|
||||
passwordConfirm: false
|
||||
} as FormErrors;
|
||||
|
||||
function validateForm() {
|
||||
try {
|
||||
registerSchema.parse(form);
|
||||
if (form.password !== form.passwordConfirm) {
|
||||
formErrors.password = true;
|
||||
formErrors.passwordConfirm = true;
|
||||
error = "Passwords don't match";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof ZodError) {
|
||||
error = e.issues[0].message;
|
||||
formErrors[e.issues[0].path[0] as string] = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleDBErrors(e: ClientResponseError) {
|
||||
const element = Object.keys(e.data.data)[0];
|
||||
formErrors[element] = true;
|
||||
const dbError = e.data.data[element];
|
||||
if (dbError.code === 'validation_not_unique') {
|
||||
error = `This ${element} is already used by someone. Maybe try logging in if it's you.`;
|
||||
} else error = dbError.message;
|
||||
}
|
||||
|
||||
async function register() {
|
||||
// reset all errors
|
||||
error = '';
|
||||
Object.keys(formErrors).forEach((i) => {
|
||||
formErrors[i] = false;
|
||||
});
|
||||
|
||||
if (validateForm()) {
|
||||
try {
|
||||
await pb.collection('users').create(form);
|
||||
|
||||
await pb.collection('users').authWithPassword(form.email, form.password);
|
||||
await goto('/');
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError) {
|
||||
handleDBErrors(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card.Root class="absolute top-[50%] left-[50%] w-full max-w-sm translate-[-50%]">
|
||||
<Card.Header>
|
||||
<Card.Title>Register to heddit</Card.Title>
|
||||
<Card.Description>Enter your email below to register</Card.Description>
|
||||
<Card.Action>
|
||||
<Button variant="link" href="/auth/login">Login</Button>
|
||||
</Card.Action>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form>
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="grid gap-2">
|
||||
<Label for="email">Email</Label>
|
||||
<Input
|
||||
aria-invalid={formErrors.email}
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="someone@example.com"
|
||||
required
|
||||
bind:value={form.email}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">Username</Label>
|
||||
<Input
|
||||
aria-invalid={formErrors.name}
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Someone"
|
||||
required
|
||||
bind:value={form.name}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center">
|
||||
<Label for="password">Password</Label>
|
||||
</div>
|
||||
<Input
|
||||
aria-invalid={formErrors.password}
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
bind:value={form.password}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<div class="flex items-center">
|
||||
<Label for="passwordConfirm">Confirm your password</Label>
|
||||
</div>
|
||||
<Input
|
||||
aria-invalid={formErrors.passwordConfirm}
|
||||
id="passwordConfirm"
|
||||
type="password"
|
||||
required
|
||||
bind:value={form.passwordConfirm}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
<Card.Footer class="flex-col gap-2">
|
||||
<Button type="submit" class="w-full cursor-pointer" onclick={register}>Register</Button>
|
||||
<p class="mt-4 text-red-700">{error}</p>
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
Loading…
Add table
Add a link
Reference in a new issue