Compare commits
8 Commits
d68a0113b2
...
7bc5fcb249
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bc5fcb249 | ||
|
|
d4d384ba2b | ||
|
|
a2ae7d5b5a | ||
|
|
e45772e2a9 | ||
|
|
51c7b9f86d | ||
|
|
83b86ab0f0 | ||
|
|
5d178709ef | ||
|
|
775133281e |
35
.vscode/settings.json
vendored
Normal file
35
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"files.readonlyInclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[javascriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"[css]": {
|
||||||
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
|
},
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports.biome": "explicit"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,13 +6,14 @@
|
|||||||
"useIgnoreFile": false
|
"useIgnoreFile": false
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false,
|
"ignoreUnknown": true,
|
||||||
"includes": [
|
"includes": [
|
||||||
"**/src/**/*",
|
"**/src/**/*",
|
||||||
"**/.vscode/**/*",
|
"**/.vscode/**/*",
|
||||||
"**/index.html",
|
"**/index.html",
|
||||||
"**/vite.config.js",
|
"**/vite.config.js",
|
||||||
"!**/src/routeTree.gen.ts"
|
"!**/src/routeTree.gen.ts",
|
||||||
|
"!**/node_modules/**/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
|
|||||||
195
package-lock.json
generated
195
package-lock.json
generated
@ -7,6 +7,8 @@
|
|||||||
"name": "template",
|
"name": "template",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "^2.8.2",
|
"@heroui/react": "^2.8.2",
|
||||||
|
"@supabase/ssr": "^0.6.1",
|
||||||
|
"@supabase/supabase-js": "^2.53.1",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tanstack/react-query": "^5.84.1",
|
"@tanstack/react-query": "^5.84.1",
|
||||||
"@tanstack/react-query-devtools": "^5.84.1",
|
"@tanstack/react-query-devtools": "^5.84.1",
|
||||||
@ -18,8 +20,10 @@
|
|||||||
"framer-motion": "^12.23.12",
|
"framer-motion": "^12.23.12",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.1.3",
|
"@biomejs/biome": "2.1.3",
|
||||||
@ -3660,6 +3664,15 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@netlify/zip-it-and-ship-it/node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -6020,6 +6033,114 @@
|
|||||||
"integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==",
|
"integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==",
|
||||||
"license": "CC0-1.0"
|
"license": "CC0-1.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/@supabase/auth-js": {
|
||||||
|
"version": "2.71.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.71.1.tgz",
|
||||||
|
"integrity": "sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "^2.6.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/functions-js": {
|
||||||
|
"version": "2.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.5.tgz",
|
||||||
|
"integrity": "sha512-v5GSqb9zbosquTo6gBwIiq7W9eQ7rE5QazsK/ezNiQXdCbY+bH8D9qEaBIkhVvX4ZRW5rP03gEfw5yw9tiq4EQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "^2.6.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/node-fetch": {
|
||||||
|
"version": "2.6.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz",
|
||||||
|
"integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/node-fetch/node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/node-fetch/node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/node-fetch/node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/postgrest-js": {
|
||||||
|
"version": "1.19.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz",
|
||||||
|
"integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "^2.6.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/realtime-js": {
|
||||||
|
"version": "2.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.15.0.tgz",
|
||||||
|
"integrity": "sha512-SEIWApsxyoAe68WU2/5PCCuBwa11LL4Bb8K3r2FHCt3ROpaTthmDiWEhnLMGayP05N4QeYrMk0kyTZOwid/Hjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "^2.6.13",
|
||||||
|
"@types/phoenix": "^1.6.6",
|
||||||
|
"@types/ws": "^8.18.1",
|
||||||
|
"ws": "^8.18.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/ssr": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-QtQgEMvaDzr77Mk3vZ3jWg2/y+D8tExYF7vcJT+wQ8ysuvOeGGjYbZlvj5bHYsj/SpC0bihcisnwPrM4Gp5G4g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@supabase/supabase-js": "^2.43.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/storage-js": {
|
||||||
|
"version": "2.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.10.4.tgz",
|
||||||
|
"integrity": "sha512-cvL02GarJVFcNoWe36VBybQqTVRq6wQSOCvTS64C+eyuxOruFIm1utZAY0xi2qKtHJO3EjKaj8iWJKySusDmAQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/node-fetch": "^2.6.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@supabase/supabase-js": {
|
||||||
|
"version": "2.54.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.54.0.tgz",
|
||||||
|
"integrity": "sha512-DLw83YwBfAaFiL3oWV26+sHRdeCGtxmIKccjh/Pndze3BWM4fZghzYKhk3ElOQU8Bluq4AkkCJ5bM5Szl/sfRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@supabase/auth-js": "2.71.1",
|
||||||
|
"@supabase/functions-js": "2.4.5",
|
||||||
|
"@supabase/node-fetch": "2.6.15",
|
||||||
|
"@supabase/postgrest-js": "1.19.4",
|
||||||
|
"@supabase/realtime-js": "2.15.0",
|
||||||
|
"@supabase/storage-js": "^2.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.17",
|
"version": "0.5.17",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||||
@ -6521,6 +6642,15 @@
|
|||||||
"vite": ">=6.0.0"
|
"vite": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-start-plugin/node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/react-start-server": {
|
"node_modules/@tanstack/react-start-server": {
|
||||||
"version": "1.130.12",
|
"version": "1.130.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.130.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.130.12.tgz",
|
||||||
@ -6656,6 +6786,15 @@
|
|||||||
"url": "https://github.com/sponsors/tannerlinsley"
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/router-generator/node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/router-plugin": {
|
"node_modules/@tanstack/router-plugin": {
|
||||||
"version": "1.130.15",
|
"version": "1.130.15",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.130.15.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.130.15.tgz",
|
||||||
@ -6709,6 +6848,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/router-plugin/node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/router-utils": {
|
"node_modules/@tanstack/router-utils": {
|
||||||
"version": "1.130.12",
|
"version": "1.130.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.130.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.130.12.tgz",
|
||||||
@ -6827,6 +6975,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/start-plugin-core/node_modules/zod": {
|
||||||
|
"version": "3.25.76",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tanstack/start-server-core": {
|
"node_modules/@tanstack/start-server-core": {
|
||||||
"version": "1.130.12",
|
"version": "1.130.12",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.130.12.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.130.12.tgz",
|
||||||
@ -7009,7 +7166,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
|
||||||
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.10.0"
|
"undici-types": "~7.10.0"
|
||||||
}
|
}
|
||||||
@ -7020,6 +7176,12 @@
|
|||||||
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/phoenix": {
|
||||||
|
"version": "1.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz",
|
||||||
|
"integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.9",
|
"version": "19.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz",
|
||||||
@ -7052,6 +7214,15 @@
|
|||||||
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
|
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
|
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/yauzl": {
|
"node_modules/@types/yauzl": {
|
||||||
"version": "2.10.3",
|
"version": "2.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||||
@ -12739,6 +12910,16 @@
|
|||||||
"seroval-plugins": "~1.3.0"
|
"seroval-plugins": "~1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sonner": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.7.6",
|
"version": "0.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
|
||||||
@ -13443,8 +13624,7 @@
|
|||||||
"version": "7.10.0",
|
"version": "7.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/unenv": {
|
"node_modules/unenv": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
@ -14411,7 +14591,6 @@
|
|||||||
"version": "8.18.3",
|
"version": "8.18.3",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
@ -14607,9 +14786,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.25.76",
|
"version": "4.0.17",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
|||||||
@ -13,6 +13,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "^2.8.2",
|
"@heroui/react": "^2.8.2",
|
||||||
|
"@supabase/ssr": "^0.6.1",
|
||||||
|
"@supabase/supabase-js": "^2.53.1",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@tanstack/react-query": "^5.84.1",
|
"@tanstack/react-query": "^5.84.1",
|
||||||
"@tanstack/react-query-devtools": "^5.84.1",
|
"@tanstack/react-query-devtools": "^5.84.1",
|
||||||
@ -24,8 +26,10 @@
|
|||||||
"framer-motion": "^12.23.12",
|
"framer-motion": "^12.23.12",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
"sonner": "^2.0.7",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.1.3",
|
"@biomejs/biome": "2.1.3",
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
## Inicio
|
## Supabase
|
||||||
|
Usuarios de prueba demo12 - juan.penalver@outlook.com
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { HeroUIProvider as HeroProvider } from "@heroui/react"
|
import { HeroUIProvider as HeroProvider } from "@heroui/react"
|
||||||
|
|
||||||
export const HeroUIProvider = ({ children }: { children: React.ReactNode }) => {
|
export const HeroUIProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
return <HeroProvider>{children}</HeroProvider>
|
return <HeroProvider validationBehavior="native">{children}</HeroProvider>
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/integrations/sonner/provider.tsx
Normal file
13
src/integrations/sonner/provider.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Toaster } from "sonner"
|
||||||
|
|
||||||
|
export const SonnerProvider = () => {
|
||||||
|
return (
|
||||||
|
<Toaster
|
||||||
|
position="top-right"
|
||||||
|
duration={3000}
|
||||||
|
closeButton
|
||||||
|
richColors
|
||||||
|
visibleToasts={3}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
24
src/integrations/supabase/supabase.ts
Normal file
24
src/integrations/supabase/supabase.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { createServerClient } from "@supabase/ssr"
|
||||||
|
import { parseCookies, setCookie } from "@tanstack/react-start/server"
|
||||||
|
|
||||||
|
export function getSupabaseServerClient() {
|
||||||
|
return createServerClient(
|
||||||
|
process.env.SUPABASE_URL as string,
|
||||||
|
process.env.SUPABASE_ANON_KEY as string,
|
||||||
|
{
|
||||||
|
cookies: {
|
||||||
|
getAll() {
|
||||||
|
return Object.entries(parseCookies()).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
setAll(cookies) {
|
||||||
|
cookies.forEach((cookie) => {
|
||||||
|
setCookie(cookie.name, cookie.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
84
src/lib/db/user.ts
Normal file
84
src/lib/db/user.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { redirect } from "@tanstack/react-router"
|
||||||
|
import { createServerFn } from "@tanstack/react-start"
|
||||||
|
import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
|
||||||
|
import { loginFormSchema, signupFormSchema } from "../validation/user"
|
||||||
|
|
||||||
|
export const getUser = createServerFn().handler(async () => {
|
||||||
|
const supabase = getSupabaseServerClient()
|
||||||
|
const { data, error } = await supabase.auth.getUser()
|
||||||
|
if (error || !data.user) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: error?.message ?? "Unknown error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: data.user.id,
|
||||||
|
email: data.user.email,
|
||||||
|
name: data.user.user_metadata.name || ""
|
||||||
|
},
|
||||||
|
error: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const loginUser = createServerFn({
|
||||||
|
method: "POST"
|
||||||
|
})
|
||||||
|
.validator(loginFormSchema)
|
||||||
|
.handler(async ({ data }) => {
|
||||||
|
const supabase = getSupabaseServerClient()
|
||||||
|
const login = await supabase.auth.signInWithPassword({
|
||||||
|
email: data.email,
|
||||||
|
password: data.password
|
||||||
|
})
|
||||||
|
if (login.error) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: login.error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const logoutUser = createServerFn().handler(async () => {
|
||||||
|
const supabase = getSupabaseServerClient()
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.signOut()
|
||||||
|
if (error) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw redirect({
|
||||||
|
to: "/",
|
||||||
|
viewTransition: true,
|
||||||
|
replace: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const signupUser = createServerFn({ method: "POST" })
|
||||||
|
.validator(signupFormSchema)
|
||||||
|
.handler(async ({ data }) => {
|
||||||
|
const supabase = getSupabaseServerClient()
|
||||||
|
const { error } = await supabase.auth.signUp({
|
||||||
|
email: data.email,
|
||||||
|
password: data.password
|
||||||
|
})
|
||||||
|
if (error) {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw redirect({
|
||||||
|
href: data.redirectUrl || "/"
|
||||||
|
})
|
||||||
|
})
|
||||||
39
src/lib/hooks/useValidation.tsx
Normal file
39
src/lib/hooks/useValidation.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
type FormDataValidation = Record<string, FormDataEntryValue>
|
||||||
|
|
||||||
|
export const useValidation = <T,>({
|
||||||
|
defaultSchema
|
||||||
|
}: {
|
||||||
|
defaultSchema?: z.ZodSchema<T>
|
||||||
|
}) => {
|
||||||
|
const [errors, setErrors] = useState<T>()
|
||||||
|
|
||||||
|
const validate = ({
|
||||||
|
formData,
|
||||||
|
schema
|
||||||
|
}: {
|
||||||
|
formData: FormDataValidation
|
||||||
|
schema?: z.ZodType<T>
|
||||||
|
}) => {
|
||||||
|
const result =
|
||||||
|
schema?.safeParse(formData) ?? defaultSchema?.safeParse(formData)
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error("No schema provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
//FIXME: Flatten errors, new in zod v4
|
||||||
|
// setErrors(result.error.flatten().fieldErrors as T)
|
||||||
|
setErrors(z.flattenError(result.error).fieldErrors as T)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors(undefined)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return { errors, validate }
|
||||||
|
}
|
||||||
59
src/lib/hooks/user/useLogin.tsx
Normal file
59
src/lib/hooks/user/useLogin.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query"
|
||||||
|
import { useNavigate } from "@tanstack/react-router"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import type z from "zod"
|
||||||
|
import { loginUser } from "@/lib/db/user"
|
||||||
|
import { loginFormSchema } from "@/lib/validation/user"
|
||||||
|
import { useValidation } from "../useValidation"
|
||||||
|
|
||||||
|
type TLoginForm = z.infer<typeof loginFormSchema>
|
||||||
|
|
||||||
|
export const useLogin = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { errors, validate } = useValidation({
|
||||||
|
defaultSchema: loginFormSchema
|
||||||
|
})
|
||||||
|
const loginMutation = useMutation({
|
||||||
|
mutationKey: ["login"],
|
||||||
|
mutationFn: async (data: TLoginForm) => {
|
||||||
|
const response = await loginUser({
|
||||||
|
data
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(response.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onMutate: () => {
|
||||||
|
toast.loading("Logging in...", { id: "login" })
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Login successful! Redirecting to posts..", { id: "login" })
|
||||||
|
navigate({
|
||||||
|
to: "/post"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message, { id: "login" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const validateLogin = (formData: TLoginForm) => {
|
||||||
|
const isValid = validate({
|
||||||
|
formData
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
toast.error("Error en el formulario.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
loginMutation.mutate(formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
login: validateLogin,
|
||||||
|
isPending: loginMutation.isPending,
|
||||||
|
errors: errors
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/lib/hooks/user/useSignup.tsx
Normal file
44
src/lib/hooks/user/useSignup.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query"
|
||||||
|
import { useNavigate } from "@tanstack/react-router"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import type z from "zod"
|
||||||
|
import { signupUser } from "@/lib/db/user"
|
||||||
|
import { signupFormSchema } from "@/lib/validation/user"
|
||||||
|
import { useValidation } from "../useValidation"
|
||||||
|
|
||||||
|
type TSignupForm = z.infer<typeof signupFormSchema>
|
||||||
|
|
||||||
|
export const useSignup = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { validate, errors } = useValidation({
|
||||||
|
defaultSchema: signupFormSchema
|
||||||
|
})
|
||||||
|
const signup = useMutation({
|
||||||
|
mutationKey: ["signup"],
|
||||||
|
mutationFn: async (data: TSignupForm) => signupUser({ data }),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success("Signup successful! Redirecting to login...", {
|
||||||
|
id: "signup"
|
||||||
|
})
|
||||||
|
navigate({
|
||||||
|
to: "/login"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const validateSignup = (formData: TSignupForm) => {
|
||||||
|
const isValid = validate({ formData })
|
||||||
|
if (!isValid) {
|
||||||
|
toast.error("Signup failed. Please check your input.", {
|
||||||
|
id: "signup"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
signup.mutate(formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
signup: validateSignup,
|
||||||
|
errors,
|
||||||
|
isPending: signup.isPending
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/lib/validation/user.ts
Normal file
12
src/lib/validation/user.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import z from "zod"
|
||||||
|
|
||||||
|
export const loginFormSchema = z.object({
|
||||||
|
email: z.email("Invalid email address"),
|
||||||
|
password: z.string().min(1, "Password must be at least 1 character long")
|
||||||
|
})
|
||||||
|
|
||||||
|
export const signupFormSchema = z.object({
|
||||||
|
email: z.email("Invalid email address"),
|
||||||
|
password: z.string().min(6, "Password must be at least 6 characters long"),
|
||||||
|
redirectUrl: z.string().optional()
|
||||||
|
})
|
||||||
@ -9,38 +9,119 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as SignupRouteImport } from './routes/signup'
|
||||||
|
import { Route as LogoutRouteImport } from './routes/logout'
|
||||||
|
import { Route as LoginRouteImport } from './routes/login'
|
||||||
|
import { Route as AuthedRouteImport } from './routes/_authed'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
|
import { Route as AuthedPostRouteImport } from './routes/_authed/post'
|
||||||
|
|
||||||
|
const SignupRoute = SignupRouteImport.update({
|
||||||
|
id: '/signup',
|
||||||
|
path: '/signup',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const LogoutRoute = LogoutRouteImport.update({
|
||||||
|
id: '/logout',
|
||||||
|
path: '/logout',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const LoginRoute = LoginRouteImport.update({
|
||||||
|
id: '/login',
|
||||||
|
path: '/login',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const AuthedRoute = AuthedRouteImport.update({
|
||||||
|
id: '/_authed',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const IndexRoute = IndexRouteImport.update({
|
const IndexRoute = IndexRouteImport.update({
|
||||||
id: '/',
|
id: '/',
|
||||||
path: '/',
|
path: '/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthedPostRoute = AuthedPostRouteImport.update({
|
||||||
|
id: '/post',
|
||||||
|
path: '/post',
|
||||||
|
getParentRoute: () => AuthedRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
|
'/logout': typeof LogoutRoute
|
||||||
|
'/signup': typeof SignupRoute
|
||||||
|
'/post': typeof AuthedPostRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/login': typeof LoginRoute
|
||||||
|
'/logout': typeof LogoutRoute
|
||||||
|
'/signup': typeof SignupRoute
|
||||||
|
'/post': typeof AuthedPostRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/_authed': typeof AuthedRouteWithChildren
|
||||||
|
'/login': typeof LoginRoute
|
||||||
|
'/logout': typeof LogoutRoute
|
||||||
|
'/signup': typeof SignupRoute
|
||||||
|
'/_authed/post': typeof AuthedPostRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths: '/'
|
fullPaths: '/' | '/login' | '/logout' | '/signup' | '/post'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to: '/'
|
to: '/' | '/login' | '/logout' | '/signup' | '/post'
|
||||||
id: '__root__' | '/'
|
id:
|
||||||
|
| '__root__'
|
||||||
|
| '/'
|
||||||
|
| '/_authed'
|
||||||
|
| '/login'
|
||||||
|
| '/logout'
|
||||||
|
| '/signup'
|
||||||
|
| '/_authed/post'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
|
AuthedRoute: typeof AuthedRouteWithChildren
|
||||||
|
LoginRoute: typeof LoginRoute
|
||||||
|
LogoutRoute: typeof LogoutRoute
|
||||||
|
SignupRoute: typeof SignupRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
|
'/signup': {
|
||||||
|
id: '/signup'
|
||||||
|
path: '/signup'
|
||||||
|
fullPath: '/signup'
|
||||||
|
preLoaderRoute: typeof SignupRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/logout': {
|
||||||
|
id: '/logout'
|
||||||
|
path: '/logout'
|
||||||
|
fullPath: '/logout'
|
||||||
|
preLoaderRoute: typeof LogoutRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/login': {
|
||||||
|
id: '/login'
|
||||||
|
path: '/login'
|
||||||
|
fullPath: '/login'
|
||||||
|
preLoaderRoute: typeof LoginRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/_authed': {
|
||||||
|
id: '/_authed'
|
||||||
|
path: ''
|
||||||
|
fullPath: ''
|
||||||
|
preLoaderRoute: typeof AuthedRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/': {
|
'/': {
|
||||||
id: '/'
|
id: '/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@ -48,11 +129,33 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof IndexRouteImport
|
preLoaderRoute: typeof IndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
|
'/_authed/post': {
|
||||||
|
id: '/_authed/post'
|
||||||
|
path: '/post'
|
||||||
|
fullPath: '/post'
|
||||||
|
preLoaderRoute: typeof AuthedPostRouteImport
|
||||||
|
parentRoute: typeof AuthedRoute
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AuthedRouteChildren {
|
||||||
|
AuthedPostRoute: typeof AuthedPostRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthedRouteChildren: AuthedRouteChildren = {
|
||||||
|
AuthedPostRoute: AuthedPostRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthedRouteWithChildren =
|
||||||
|
AuthedRoute._addFileChildren(AuthedRouteChildren)
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
|
AuthedRoute: AuthedRouteWithChildren,
|
||||||
|
LoginRoute: LoginRoute,
|
||||||
|
LogoutRoute: LogoutRoute,
|
||||||
|
SignupRoute: SignupRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export const createRouter = () => {
|
|||||||
return routerWithQueryClient(
|
return routerWithQueryClient(
|
||||||
createTanstackRouter({
|
createTanstackRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
context: { ...rqContext },
|
context: { ...rqContext, user: null },
|
||||||
defaultPreload: "intent",
|
defaultPreload: "intent",
|
||||||
Wrap: (props: { children: React.ReactNode }) => {
|
Wrap: (props: { children: React.ReactNode }) => {
|
||||||
return <QueryProvider {...rqContext}>{props.children}</QueryProvider>
|
return <QueryProvider {...rqContext}>{props.children}</QueryProvider>
|
||||||
|
|||||||
@ -7,12 +7,21 @@ import {
|
|||||||
} from "@tanstack/react-router"
|
} from "@tanstack/react-router"
|
||||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"
|
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"
|
||||||
import { HeroUIProvider } from "@/integrations/heroui/provider"
|
import { HeroUIProvider } from "@/integrations/heroui/provider"
|
||||||
|
import { SonnerProvider } from "@/integrations/sonner/provider"
|
||||||
|
import { getUser } from "@/lib/db/user"
|
||||||
|
|
||||||
interface MyRouterContext {
|
interface MyRouterContext {
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
|
user: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||||
|
beforeLoad: async () => {
|
||||||
|
const user = await getUser()
|
||||||
|
return {
|
||||||
|
...user
|
||||||
|
}
|
||||||
|
},
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
@ -45,6 +54,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<HeroUIProvider>{children}</HeroUIProvider>
|
<HeroUIProvider>{children}</HeroUIProvider>
|
||||||
|
<SonnerProvider />
|
||||||
<TanStackRouterDevtools />
|
<TanStackRouterDevtools />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
20
src/routes/_authed.tsx
Normal file
20
src/routes/_authed.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authed")({
|
||||||
|
beforeLoad: ({ context }) => {
|
||||||
|
if (context.error) {
|
||||||
|
throw new Error("Not authenticated")
|
||||||
|
// TODO: Redirect to login page
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorComponent: ({ error }) => {
|
||||||
|
if (error.message === "Not authenticated") {
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
Not authenticated. Please <a href="/login">login</a>.
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
})
|
||||||
27
src/routes/_authed/post.tsx
Normal file
27
src/routes/_authed/post.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Button } from "@heroui/react"
|
||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/_authed/post")({
|
||||||
|
component: RouteComponent
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
const navigate = Route.useNavigate()
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Hello "/_authed/post"!{" "}
|
||||||
|
<div>
|
||||||
|
{" "}
|
||||||
|
<Button
|
||||||
|
onPress={() =>
|
||||||
|
navigate({
|
||||||
|
to: "/logout"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import logo from "@assets/logo.svg"
|
import logo from "@assets/logo.svg"
|
||||||
|
import { Button } from "@heroui/react"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
@ -6,6 +7,7 @@ export const Route = createFileRoute("/")({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const navigate = Route.useNavigate()
|
||||||
return (
|
return (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
|
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
|
||||||
@ -17,22 +19,26 @@ function App() {
|
|||||||
<p>
|
<p>
|
||||||
Edit <code>src/routes/index.tsx</code> and save to reload.
|
Edit <code>src/routes/index.tsx</code> and save to reload.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<div className="grid grid-cols-2 gap-4 mt-4">
|
||||||
className="text-[#61dafb] hover:underline"
|
<Button
|
||||||
href="https://reactjs.org"
|
onPress={() =>
|
||||||
target="_blank"
|
navigate({
|
||||||
rel="noopener noreferrer"
|
to: "/login"
|
||||||
>
|
})
|
||||||
Learn React
|
}
|
||||||
</a>
|
>
|
||||||
<a
|
Login
|
||||||
className="text-[#61dafb] hover:underline"
|
</Button>
|
||||||
href="https://tanstack.com"
|
<Button
|
||||||
target="_blank"
|
onPress={() => {
|
||||||
rel="noopener noreferrer"
|
navigate({
|
||||||
>
|
to: "/signup"
|
||||||
Learn TanStack
|
})
|
||||||
</a>
|
}}
|
||||||
|
>
|
||||||
|
Signup
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
39
src/routes/login.tsx
Normal file
39
src/routes/login.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Button, Form, Input } from "@heroui/react"
|
||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import type { FormEvent } from "react"
|
||||||
|
import { useLogin } from "@/lib/hooks/user/useLogin"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/login")({
|
||||||
|
component: LoginComp
|
||||||
|
})
|
||||||
|
|
||||||
|
function LoginComp() {
|
||||||
|
const { errors, isPending, login } = useLogin()
|
||||||
|
|
||||||
|
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const formData = new FormData(e.currentTarget)
|
||||||
|
|
||||||
|
login({
|
||||||
|
email: formData.get("email") as string,
|
||||||
|
password: formData.get("password") as string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center flex-col h-screen">
|
||||||
|
<p className="font-semibold mb-3">Login</p>
|
||||||
|
<Form
|
||||||
|
validationErrors={errors}
|
||||||
|
className="grid gap-2 max-w-sm w-full"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Input name="email" label="Email" />
|
||||||
|
<Input name="password" type="password" label="Password" />
|
||||||
|
<Button type="submit" isLoading={isPending}>
|
||||||
|
Entrar
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/routes/logout.tsx
Normal file
7
src/routes/logout.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import { logoutUser } from "@/lib/db/user"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/logout")({
|
||||||
|
preload: false,
|
||||||
|
loader: () => logoutUser()
|
||||||
|
})
|
||||||
40
src/routes/signup.tsx
Normal file
40
src/routes/signup.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Button, Form, Input } from "@heroui/react"
|
||||||
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
|
import type { FormEvent } from "react"
|
||||||
|
import { useSignup } from "@/lib/hooks/user/useSignup"
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/signup")({
|
||||||
|
component: SignupComp
|
||||||
|
})
|
||||||
|
|
||||||
|
function SignupComp() {
|
||||||
|
const { signup, errors, isPending } = useSignup()
|
||||||
|
|
||||||
|
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const formData = new FormData(e.currentTarget)
|
||||||
|
|
||||||
|
signup({
|
||||||
|
email: formData.get("email") as string,
|
||||||
|
password: formData.get("password") as string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center items-center flex-col h-screen">
|
||||||
|
<p className="font-semibold mb-3">Signup</p>
|
||||||
|
<Form
|
||||||
|
validationErrors={errors}
|
||||||
|
className="grid gap-2 max-w-sm w-full"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<Input name="email" type="email" label="Email" />
|
||||||
|
<Input name="password" type="password" label="Password" />
|
||||||
|
<Button type="submit" isLoading={isPending}>
|
||||||
|
Enviar
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user