⬆️ Upgrade Biome to the latest version (#1861)

This commit is contained in:
Alejandra
2025-09-09 14:45:10 +02:00
committed by GitHub
parent 61174f1806
commit f813161912
39 changed files with 1962 additions and 2134 deletions

View File

@@ -28,10 +28,10 @@ repos:
hooks: hooks:
- id: local-biome-check - id: local-biome-check
name: biome check name: biome check
entry: npx biome check --write --files-ignore-unknown=true --no-errors-on-unmatched entry: bash -c 'cd frontend && npm run lint'
language: system language: system
types: [text] types: [text]
files: "\\.(jsx?|tsx?|c(js|ts)|m(js|ts)|d\\.(ts|cts|mts)|jsonc?|css|svelte|vue|astro|graphql|gql)$" files: ^frontend/
ci: ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks

View File

@@ -1,14 +1,16 @@
{ {
"$schema": "https://biomejs.dev/schemas/1.6.1/schema.json", "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json",
"organizeImports": { "assist": { "actions": { "source": { "organizeImports": "on" } } },
"enabled": true
},
"files": { "files": {
"ignore": [ "includes": [
"node_modules", "**",
"src/routeTree.gen.ts", "!**/dist/**/*",
"playwright.config.ts", "!**/node_modules/**/*",
"playwright-report" "!**/src/routeTree.gen.ts",
"!**/src/client/**/*",
"!**/src/components/ui/**/*",
"!**/playwright-report",
"!**/playwright.config.ts"
] ]
}, },
"linter": { "linter": {
@@ -20,7 +22,10 @@
"noArrayIndexKey": "off" "noArrayIndexKey": "off"
}, },
"style": { "style": {
"noNonNullAssertion": "off" "noNonNullAssertion": "off",
"noParameterAssign": "error",
"useSelfClosingElements": "error",
"noUselessElse": "error"
} }
} }
}, },

View File

@@ -13,9 +13,9 @@ export default defineConfig({
operationId: true, operationId: true,
classNameBuilder: "{{name}}Service", classNameBuilder: "{{name}}Service",
methodNameBuilder: (operation) => { methodNameBuilder: (operation) => {
// @ts-ignore // @ts-expect-error
let name: string = operation.name let name: string = operation.name
// @ts-ignore // @ts-expect-error
const service: string = operation.service const service: string = operation.service
if (service && name.toLowerCase().startsWith(service.toLowerCase())) { if (service && name.toLowerCase().startsWith(service.toLowerCase())) {
@@ -30,4 +30,4 @@ export default defineConfig({
type: "json", type: "json",
}, },
], ],
}) })

View File

@@ -23,7 +23,7 @@
"react-icons": "^5.5.0" "react-icons": "^5.5.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "^2.2.3",
"@hey-api/openapi-ts": "0.73.0", "@hey-api/openapi-ts": "0.73.0",
"@playwright/test": "^1.55.0", "@playwright/test": "^1.55.0",
"@tanstack/router-devtools": "^1.131.36", "@tanstack/router-devtools": "^1.131.36",
@@ -552,11 +552,10 @@
} }
}, },
"node_modules/@biomejs/biome": { "node_modules/@biomejs/biome": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.3.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", "integrity": "sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==",
"dev": true, "dev": true,
"hasInstallScript": true,
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"bin": { "bin": {
"biome": "bin/biome" "biome": "bin/biome"
@@ -569,20 +568,20 @@
"url": "https://opencollective.com/biome" "url": "https://opencollective.com/biome"
}, },
"optionalDependencies": { "optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-arm64": "2.2.3",
"@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-darwin-x64": "2.2.3",
"@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64": "2.2.3",
"@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-arm64-musl": "2.2.3",
"@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64": "2.2.3",
"@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-linux-x64-musl": "2.2.3",
"@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-arm64": "2.2.3",
"@biomejs/cli-win32-x64": "1.9.4" "@biomejs/cli-win32-x64": "2.2.3"
} }
}, },
"node_modules/@biomejs/cli-darwin-arm64": { "node_modules/@biomejs/cli-darwin-arm64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.3.tgz",
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", "integrity": "sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -597,9 +596,9 @@
} }
}, },
"node_modules/@biomejs/cli-darwin-x64": { "node_modules/@biomejs/cli-darwin-x64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.3.tgz",
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", "integrity": "sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -614,9 +613,9 @@
} }
}, },
"node_modules/@biomejs/cli-linux-arm64": { "node_modules/@biomejs/cli-linux-arm64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.3.tgz",
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", "integrity": "sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -631,9 +630,9 @@
} }
}, },
"node_modules/@biomejs/cli-linux-arm64-musl": { "node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.3.tgz",
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", "integrity": "sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -648,9 +647,9 @@
} }
}, },
"node_modules/@biomejs/cli-linux-x64": { "node_modules/@biomejs/cli-linux-x64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.3.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", "integrity": "sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -665,9 +664,9 @@
} }
}, },
"node_modules/@biomejs/cli-linux-x64-musl": { "node_modules/@biomejs/cli-linux-x64-musl": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.3.tgz",
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", "integrity": "sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -682,9 +681,9 @@
} }
}, },
"node_modules/@biomejs/cli-win32-arm64": { "node_modules/@biomejs/cli-win32-arm64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.3.tgz",
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", "integrity": "sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -699,9 +698,9 @@
} }
}, },
"node_modules/@biomejs/cli-win32-x64": { "node_modules/@biomejs/cli-win32-x64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.3.tgz",
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", "integrity": "sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -5973,74 +5972,74 @@
} }
}, },
"@biomejs/biome": { "@biomejs/biome": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.3.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", "integrity": "sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-arm64": "2.2.3",
"@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-darwin-x64": "2.2.3",
"@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64": "2.2.3",
"@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-arm64-musl": "2.2.3",
"@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64": "2.2.3",
"@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-linux-x64-musl": "2.2.3",
"@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-arm64": "2.2.3",
"@biomejs/cli-win32-x64": "1.9.4" "@biomejs/cli-win32-x64": "2.2.3"
} }
}, },
"@biomejs/cli-darwin-arm64": { "@biomejs/cli-darwin-arm64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.3.tgz",
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", "integrity": "sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-darwin-x64": { "@biomejs/cli-darwin-x64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.3.tgz",
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", "integrity": "sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-linux-arm64": { "@biomejs/cli-linux-arm64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.3.tgz",
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", "integrity": "sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-linux-arm64-musl": { "@biomejs/cli-linux-arm64-musl": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.3.tgz",
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", "integrity": "sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-linux-x64": { "@biomejs/cli-linux-x64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.3.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", "integrity": "sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-linux-x64-musl": { "@biomejs/cli-linux-x64-musl": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.3.tgz",
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", "integrity": "sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-win32-arm64": { "@biomejs/cli-win32-arm64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.3.tgz",
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", "integrity": "sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"@biomejs/cli-win32-x64": { "@biomejs/cli-win32-x64": {
"version": "1.9.4", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.3.tgz",
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", "integrity": "sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -p tsconfig.build.json && vite build", "build": "tsc -p tsconfig.build.json && vite build",
"lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", "lint": "biome check --write --unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./",
"preview": "vite preview", "preview": "vite preview",
"generate-client": "openapi-ts" "generate-client": "openapi-ts"
}, },
@@ -26,7 +26,7 @@
"react-icons": "^5.5.0" "react-icons": "^5.5.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "^2.2.3",
"@hey-api/openapi-ts": "0.73.0", "@hey-api/openapi-ts": "0.73.0",
"@playwright/test": "^1.55.0", "@playwright/test": "^1.55.0",
"@tanstack/router-devtools": "^1.131.36", "@tanstack/router-devtools": "^1.131.36",

View File

@@ -1,25 +1,21 @@
import type { ApiRequestOptions } from "./ApiRequestOptions" import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from "./ApiResult" import type { ApiResult } from './ApiResult';
export class ApiError extends Error { export class ApiError extends Error {
public readonly url: string public readonly url: string;
public readonly status: number public readonly status: number;
public readonly statusText: string public readonly statusText: string;
public readonly body: unknown public readonly body: unknown;
public readonly request: ApiRequestOptions public readonly request: ApiRequestOptions;
constructor( constructor(request: ApiRequestOptions, response: ApiResult, message: string) {
request: ApiRequestOptions, super(message);
response: ApiResult,
message: string,
) {
super(message)
this.name = "ApiError" this.name = 'ApiError';
this.url = response.url this.url = response.url;
this.status = response.status this.status = response.status;
this.statusText = response.statusText this.statusText = response.statusText;
this.body = response.body this.body = response.body;
this.request = request this.request = request;
} }
} }

View File

@@ -1,21 +1,21 @@
export type ApiRequestOptions<T = unknown> = { export type ApiRequestOptions<T = unknown> = {
readonly body?: any readonly body?: any;
readonly cookies?: Record<string, unknown> readonly cookies?: Record<string, unknown>;
readonly errors?: Record<number | string, string> readonly errors?: Record<number | string, string>;
readonly formData?: Record<string, unknown> | any[] | Blob | File readonly formData?: Record<string, unknown> | any[] | Blob | File;
readonly headers?: Record<string, unknown> readonly headers?: Record<string, unknown>;
readonly mediaType?: string readonly mediaType?: string;
readonly method: readonly method:
| "DELETE" | 'DELETE'
| "GET" | 'GET'
| "HEAD" | 'HEAD'
| "OPTIONS" | 'OPTIONS'
| "PATCH" | 'PATCH'
| "POST" | 'POST'
| "PUT" | 'PUT';
readonly path?: Record<string, unknown> readonly path?: Record<string, unknown>;
readonly query?: Record<string, unknown> readonly query?: Record<string, unknown>;
readonly responseHeader?: string readonly responseHeader?: string;
readonly responseTransformer?: (data: unknown) => Promise<T> readonly responseTransformer?: (data: unknown) => Promise<T>;
readonly url: string readonly url: string;
} };

View File

@@ -1,7 +1,7 @@
export type ApiResult<TData = any> = { export type ApiResult<TData = any> = {
readonly body: TData readonly body: TData;
readonly ok: boolean readonly ok: boolean;
readonly status: number readonly status: number;
readonly statusText: string readonly statusText: string;
readonly url: string readonly url: string;
} };

View File

@@ -1,126 +1,126 @@
export class CancelError extends Error { export class CancelError extends Error {
constructor(message: string) { constructor(message: string) {
super(message) super(message);
this.name = "CancelError" this.name = 'CancelError';
} }
public get isCancelled(): boolean { public get isCancelled(): boolean {
return true return true;
} }
} }
export interface OnCancel { export interface OnCancel {
readonly isResolved: boolean readonly isResolved: boolean;
readonly isRejected: boolean readonly isRejected: boolean;
readonly isCancelled: boolean readonly isCancelled: boolean;
(cancelHandler: () => void): void (cancelHandler: () => void): void;
} }
export class CancelablePromise<T> implements Promise<T> { export class CancelablePromise<T> implements Promise<T> {
private _isResolved: boolean private _isResolved: boolean;
private _isRejected: boolean private _isRejected: boolean;
private _isCancelled: boolean private _isCancelled: boolean;
readonly cancelHandlers: (() => void)[] readonly cancelHandlers: (() => void)[];
readonly promise: Promise<T> readonly promise: Promise<T>;
private _resolve?: (value: T | PromiseLike<T>) => void private _resolve?: (value: T | PromiseLike<T>) => void;
private _reject?: (reason?: unknown) => void private _reject?: (reason?: unknown) => void;
constructor( constructor(
executor: ( executor: (
resolve: (value: T | PromiseLike<T>) => void, resolve: (value: T | PromiseLike<T>) => void,
reject: (reason?: unknown) => void, reject: (reason?: unknown) => void,
onCancel: OnCancel, onCancel: OnCancel
) => void, ) => void
) { ) {
this._isResolved = false this._isResolved = false;
this._isRejected = false this._isRejected = false;
this._isCancelled = false this._isCancelled = false;
this.cancelHandlers = [] this.cancelHandlers = [];
this.promise = new Promise<T>((resolve, reject) => { this.promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve this._resolve = resolve;
this._reject = reject this._reject = reject;
const onResolve = (value: T | PromiseLike<T>): void => { const onResolve = (value: T | PromiseLike<T>): void => {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return return;
} }
this._isResolved = true this._isResolved = true;
if (this._resolve) this._resolve(value) if (this._resolve) this._resolve(value);
} };
const onReject = (reason?: unknown): void => { const onReject = (reason?: unknown): void => {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return return;
} }
this._isRejected = true this._isRejected = true;
if (this._reject) this._reject(reason) if (this._reject) this._reject(reason);
} };
const onCancel = (cancelHandler: () => void): void => { const onCancel = (cancelHandler: () => void): void => {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return return;
} }
this.cancelHandlers.push(cancelHandler) this.cancelHandlers.push(cancelHandler);
} };
Object.defineProperty(onCancel, "isResolved", { Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this._isResolved, get: (): boolean => this._isResolved,
}) });
Object.defineProperty(onCancel, "isRejected", { Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this._isRejected, get: (): boolean => this._isRejected,
}) });
Object.defineProperty(onCancel, "isCancelled", { Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this._isCancelled, get: (): boolean => this._isCancelled,
}) });
return executor(onResolve, onReject, onCancel as OnCancel) return executor(onResolve, onReject, onCancel as OnCancel);
}) });
} }
get [Symbol.toStringTag]() { get [Symbol.toStringTag]() {
return "Cancellable Promise" return "Cancellable Promise";
} }
public then<TResult1 = T, TResult2 = never>( public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null, onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> { ): Promise<TResult1 | TResult2> {
return this.promise.then(onFulfilled, onRejected) return this.promise.then(onFulfilled, onRejected);
} }
public catch<TResult = never>( public catch<TResult = never>(
onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null, onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> { ): Promise<T | TResult> {
return this.promise.catch(onRejected) return this.promise.catch(onRejected);
} }
public finally(onFinally?: (() => void) | null): Promise<T> { public finally(onFinally?: (() => void) | null): Promise<T> {
return this.promise.finally(onFinally) return this.promise.finally(onFinally);
} }
public cancel(): void { public cancel(): void {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return return;
} }
this._isCancelled = true this._isCancelled = true;
if (this.cancelHandlers.length) { if (this.cancelHandlers.length) {
try { try {
for (const cancelHandler of this.cancelHandlers) { for (const cancelHandler of this.cancelHandlers) {
cancelHandler() cancelHandler();
} }
} catch (error) { } catch (error) {
console.warn("Cancellation threw an error", error) console.warn('Cancellation threw an error', error);
return return;
} }
} }
this.cancelHandlers.length = 0 this.cancelHandlers.length = 0;
if (this._reject) this._reject(new CancelError("Request aborted")) if (this._reject) this._reject(new CancelError('Request aborted'));
} }
public get isCancelled(): boolean { public get isCancelled(): boolean {
return this._isCancelled return this._isCancelled;
} }
} }

View File

@@ -1,57 +1,57 @@
import type { AxiosRequestConfig, AxiosResponse } from "axios" import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { ApiRequestOptions } from "./ApiRequestOptions" import type { ApiRequestOptions } from './ApiRequestOptions';
type Headers = Record<string, string> type Headers = Record<string, string>;
type Middleware<T> = (value: T) => T | Promise<T> type Middleware<T> = (value: T) => T | Promise<T>;
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T> type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export class Interceptors<T> { export class Interceptors<T> {
_fns: Middleware<T>[] _fns: Middleware<T>[];
constructor() { constructor() {
this._fns = [] this._fns = [];
} }
eject(fn: Middleware<T>): void { eject(fn: Middleware<T>): void {
const index = this._fns.indexOf(fn) const index = this._fns.indexOf(fn);
if (index !== -1) { if (index !== -1) {
this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)] this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)];
} }
} }
use(fn: Middleware<T>): void { use(fn: Middleware<T>): void {
this._fns = [...this._fns, fn] this._fns = [...this._fns, fn];
} }
} }
export type OpenAPIConfig = { export type OpenAPIConfig = {
BASE: string BASE: string;
CREDENTIALS: "include" | "omit" | "same-origin" CREDENTIALS: 'include' | 'omit' | 'same-origin';
ENCODE_PATH?: ((path: string) => string) | undefined ENCODE_PATH?: ((path: string) => string) | undefined;
HEADERS?: Headers | Resolver<Headers> | undefined HEADERS?: Headers | Resolver<Headers> | undefined;
PASSWORD?: string | Resolver<string> | undefined PASSWORD?: string | Resolver<string> | undefined;
TOKEN?: string | Resolver<string> | undefined TOKEN?: string | Resolver<string> | undefined;
USERNAME?: string | Resolver<string> | undefined USERNAME?: string | Resolver<string> | undefined;
VERSION: string VERSION: string;
WITH_CREDENTIALS: boolean WITH_CREDENTIALS: boolean;
interceptors: { interceptors: {
request: Interceptors<AxiosRequestConfig> request: Interceptors<AxiosRequestConfig>;
response: Interceptors<AxiosResponse> response: Interceptors<AxiosResponse>;
} };
} };
export const OpenAPI: OpenAPIConfig = { export const OpenAPI: OpenAPIConfig = {
BASE: "", BASE: '',
CREDENTIALS: "include", CREDENTIALS: 'include',
ENCODE_PATH: undefined, ENCODE_PATH: undefined,
HEADERS: undefined, HEADERS: undefined,
PASSWORD: undefined, PASSWORD: undefined,
TOKEN: undefined, TOKEN: undefined,
USERNAME: undefined, USERNAME: undefined,
VERSION: "0.1.0", VERSION: '0.1.0',
WITH_CREDENTIALS: false, WITH_CREDENTIALS: false,
interceptors: { interceptors: {
request: new Interceptors(), request: new Interceptors(),
response: new Interceptors(), response: new Interceptors(),
}, },
} };

View File

@@ -1,325 +1,301 @@
import axios from "axios" import axios from 'axios';
import type { import type { AxiosError, AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
AxiosError,
AxiosRequestConfig,
AxiosResponse,
AxiosInstance,
} from "axios"
import { ApiError } from "./ApiError" import { ApiError } from './ApiError';
import type { ApiRequestOptions } from "./ApiRequestOptions" import type { ApiRequestOptions } from './ApiRequestOptions';
import type { ApiResult } from "./ApiResult" import type { ApiResult } from './ApiResult';
import { CancelablePromise } from "./CancelablePromise" import { CancelablePromise } from './CancelablePromise';
import type { OnCancel } from "./CancelablePromise" import type { OnCancel } from './CancelablePromise';
import type { OpenAPIConfig } from "./OpenAPI" import type { OpenAPIConfig } from './OpenAPI';
export const isString = (value: unknown): value is string => { export const isString = (value: unknown): value is string => {
return typeof value === "string" return typeof value === 'string';
} };
export const isStringWithValue = (value: unknown): value is string => { export const isStringWithValue = (value: unknown): value is string => {
return isString(value) && value !== "" return isString(value) && value !== '';
} };
export const isBlob = (value: any): value is Blob => { export const isBlob = (value: any): value is Blob => {
return value instanceof Blob return value instanceof Blob;
} };
export const isFormData = (value: unknown): value is FormData => { export const isFormData = (value: unknown): value is FormData => {
return value instanceof FormData return value instanceof FormData;
} };
export const isSuccess = (status: number): boolean => { export const isSuccess = (status: number): boolean => {
return status >= 200 && status < 300 return status >= 200 && status < 300;
} };
export const base64 = (str: string): string => { export const base64 = (str: string): string => {
try { try {
return btoa(str) return btoa(str);
} catch (err) { } catch (err) {
// @ts-ignore // @ts-ignore
return Buffer.from(str).toString("base64") return Buffer.from(str).toString('base64');
} }
} };
export const getQueryString = (params: Record<string, unknown>): string => { export const getQueryString = (params: Record<string, unknown>): string => {
const qs: string[] = [] const qs: string[] = [];
const append = (key: string, value: unknown) => { const append = (key: string, value: unknown) => {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
} };
const encodePair = (key: string, value: unknown) => { const encodePair = (key: string, value: unknown) => {
if (value === undefined || value === null) { if (value === undefined || value === null) {
return return;
} }
if (value instanceof Date) { if (value instanceof Date) {
append(key, value.toISOString()) append(key, value.toISOString());
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
value.forEach((v) => encodePair(key, v)) value.forEach(v => encodePair(key, v));
} else if (typeof value === "object") { } else if (typeof value === 'object') {
Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)) Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v));
} else { } else {
append(key, value) append(key, value);
} }
} };
Object.entries(params).forEach(([key, value]) => encodePair(key, value)) Object.entries(params).forEach(([key, value]) => encodePair(key, value));
return qs.length ? `?${qs.join("&")}` : "" return qs.length ? `?${qs.join('&')}` : '';
} };
const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => {
const encoder = config.ENCODE_PATH || encodeURI const encoder = config.ENCODE_PATH || encodeURI;
const path = options.url const path = options.url
.replace("{api-version}", config.VERSION) .replace('{api-version}', config.VERSION)
.replace(/{(.*?)}/g, (substring: string, group: string) => { .replace(/{(.*?)}/g, (substring: string, group: string) => {
if (options.path?.hasOwnProperty(group)) { if (options.path?.hasOwnProperty(group)) {
return encoder(String(options.path[group])) return encoder(String(options.path[group]));
} }
return substring return substring;
}) });
const url = config.BASE + path const url = config.BASE + path;
return options.query ? url + getQueryString(options.query) : url return options.query ? url + getQueryString(options.query) : url;
} };
export const getFormData = ( export const getFormData = (options: ApiRequestOptions): FormData | undefined => {
options: ApiRequestOptions, if (options.formData) {
): FormData | undefined => { const formData = new FormData();
if (options.formData) {
const formData = new FormData()
const process = (key: string, value: unknown) => { const process = (key: string, value: unknown) => {
if (isString(value) || isBlob(value)) { if (isString(value) || isBlob(value)) {
formData.append(key, value) formData.append(key, value);
} else { } else {
formData.append(key, JSON.stringify(value)) formData.append(key, JSON.stringify(value));
} }
} };
Object.entries(options.formData) Object.entries(options.formData)
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.forEach(([key, value]) => { .forEach(([key, value]) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((v) => process(key, v)) value.forEach(v => process(key, v));
} else { } else {
process(key, value) process(key, value);
} }
}) });
return formData return formData;
} }
return undefined return undefined;
} };
type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T> type Resolver<T> = (options: ApiRequestOptions<T>) => Promise<T>;
export const resolve = async <T>( export const resolve = async <T>(options: ApiRequestOptions<T>, resolver?: T | Resolver<T>): Promise<T | undefined> => {
options: ApiRequestOptions<T>, if (typeof resolver === 'function') {
resolver?: T | Resolver<T>, return (resolver as Resolver<T>)(options);
): Promise<T | undefined> => { }
if (typeof resolver === "function") { return resolver;
return (resolver as Resolver<T>)(options) };
}
return resolver
}
export const getHeaders = async <T>( export const getHeaders = async <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>): Promise<Record<string, string>> => {
config: OpenAPIConfig, const [token, username, password, additionalHeaders] = await Promise.all([
options: ApiRequestOptions<T>, // @ts-ignore
): Promise<Record<string, string>> => { resolve(options, config.TOKEN),
const [token, username, password, additionalHeaders] = await Promise.all([ // @ts-ignore
// @ts-ignore resolve(options, config.USERNAME),
resolve(options, config.TOKEN), // @ts-ignore
// @ts-ignore resolve(options, config.PASSWORD),
resolve(options, config.USERNAME), // @ts-ignore
// @ts-ignore resolve(options, config.HEADERS),
resolve(options, config.PASSWORD), ]);
// @ts-ignore
resolve(options, config.HEADERS),
])
const headers = Object.entries({ const headers = Object.entries({
Accept: "application/json", Accept: 'application/json',
...additionalHeaders, ...additionalHeaders,
...options.headers, ...options.headers,
}) })
.filter(([, value]) => value !== undefined && value !== null) .filter(([, value]) => value !== undefined && value !== null)
.reduce( .reduce((headers, [key, value]) => ({
(headers, [key, value]) => ({ ...headers,
...headers, [key]: String(value),
[key]: String(value), }), {} as Record<string, string>);
}),
{} as Record<string, string>,
)
if (isStringWithValue(token)) { if (isStringWithValue(token)) {
headers["Authorization"] = `Bearer ${token}` headers['Authorization'] = `Bearer ${token}`;
} }
if (isStringWithValue(username) && isStringWithValue(password)) { if (isStringWithValue(username) && isStringWithValue(password)) {
const credentials = base64(`${username}:${password}`) const credentials = base64(`${username}:${password}`);
headers["Authorization"] = `Basic ${credentials}` headers['Authorization'] = `Basic ${credentials}`;
} }
if (options.body !== undefined) { if (options.body !== undefined) {
if (options.mediaType) { if (options.mediaType) {
headers["Content-Type"] = options.mediaType headers['Content-Type'] = options.mediaType;
} else if (isBlob(options.body)) { } else if (isBlob(options.body)) {
headers["Content-Type"] = options.body.type || "application/octet-stream" headers['Content-Type'] = options.body.type || 'application/octet-stream';
} else if (isString(options.body)) { } else if (isString(options.body)) {
headers["Content-Type"] = "text/plain" headers['Content-Type'] = 'text/plain';
} else if (!isFormData(options.body)) { } else if (!isFormData(options.body)) {
headers["Content-Type"] = "application/json" headers['Content-Type'] = 'application/json';
} }
} else if (options.formData !== undefined) { } else if (options.formData !== undefined) {
if (options.mediaType) { if (options.mediaType) {
headers["Content-Type"] = options.mediaType headers['Content-Type'] = options.mediaType;
} }
} }
return headers return headers;
} };
export const getRequestBody = (options: ApiRequestOptions): unknown => { export const getRequestBody = (options: ApiRequestOptions): unknown => {
if (options.body) { if (options.body) {
return options.body return options.body;
} }
return undefined return undefined;
} };
export const sendRequest = async <T>( export const sendRequest = async <T>(
config: OpenAPIConfig, config: OpenAPIConfig,
options: ApiRequestOptions<T>, options: ApiRequestOptions<T>,
url: string, url: string,
body: unknown, body: unknown,
formData: FormData | undefined, formData: FormData | undefined,
headers: Record<string, string>, headers: Record<string, string>,
onCancel: OnCancel, onCancel: OnCancel,
axiosClient: AxiosInstance, axiosClient: AxiosInstance
): Promise<AxiosResponse<T>> => { ): Promise<AxiosResponse<T>> => {
const controller = new AbortController() const controller = new AbortController();
let requestConfig: AxiosRequestConfig = { let requestConfig: AxiosRequestConfig = {
data: body ?? formData, data: body ?? formData,
headers, headers,
method: options.method, method: options.method,
signal: controller.signal, signal: controller.signal,
url, url,
withCredentials: config.WITH_CREDENTIALS, withCredentials: config.WITH_CREDENTIALS,
} };
onCancel(() => controller.abort()) onCancel(() => controller.abort());
for (const fn of config.interceptors.request._fns) { for (const fn of config.interceptors.request._fns) {
requestConfig = await fn(requestConfig) requestConfig = await fn(requestConfig);
} }
try { try {
return await axiosClient.request(requestConfig) return await axiosClient.request(requestConfig);
} catch (error) { } catch (error) {
const axiosError = error as AxiosError<T> const axiosError = error as AxiosError<T>;
if (axiosError.response) { if (axiosError.response) {
return axiosError.response return axiosError.response;
} }
throw error throw error;
} }
} };
export const getResponseHeader = ( export const getResponseHeader = (response: AxiosResponse<unknown>, responseHeader?: string): string | undefined => {
response: AxiosResponse<unknown>, if (responseHeader) {
responseHeader?: string, const content = response.headers[responseHeader];
): string | undefined => { if (isString(content)) {
if (responseHeader) { return content;
const content = response.headers[responseHeader] }
if (isString(content)) { }
return content return undefined;
} };
}
return undefined
}
export const getResponseBody = (response: AxiosResponse<unknown>): unknown => { export const getResponseBody = (response: AxiosResponse<unknown>): unknown => {
if (response.status !== 204) { if (response.status !== 204) {
return response.data return response.data;
} }
return undefined return undefined;
} };
export const catchErrorCodes = ( export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => {
options: ApiRequestOptions, const errors: Record<number, string> = {
result: ApiResult, 400: 'Bad Request',
): void => { 401: 'Unauthorized',
const errors: Record<number, string> = { 402: 'Payment Required',
400: "Bad Request", 403: 'Forbidden',
401: "Unauthorized", 404: 'Not Found',
402: "Payment Required", 405: 'Method Not Allowed',
403: "Forbidden", 406: 'Not Acceptable',
404: "Not Found", 407: 'Proxy Authentication Required',
405: "Method Not Allowed", 408: 'Request Timeout',
406: "Not Acceptable", 409: 'Conflict',
407: "Proxy Authentication Required", 410: 'Gone',
408: "Request Timeout", 411: 'Length Required',
409: "Conflict", 412: 'Precondition Failed',
410: "Gone", 413: 'Payload Too Large',
411: "Length Required", 414: 'URI Too Long',
412: "Precondition Failed", 415: 'Unsupported Media Type',
413: "Payload Too Large", 416: 'Range Not Satisfiable',
414: "URI Too Long", 417: 'Expectation Failed',
415: "Unsupported Media Type", 418: 'Im a teapot',
416: "Range Not Satisfiable", 421: 'Misdirected Request',
417: "Expectation Failed", 422: 'Unprocessable Content',
418: "Im a teapot", 423: 'Locked',
421: "Misdirected Request", 424: 'Failed Dependency',
422: "Unprocessable Content", 425: 'Too Early',
423: "Locked", 426: 'Upgrade Required',
424: "Failed Dependency", 428: 'Precondition Required',
425: "Too Early", 429: 'Too Many Requests',
426: "Upgrade Required", 431: 'Request Header Fields Too Large',
428: "Precondition Required", 451: 'Unavailable For Legal Reasons',
429: "Too Many Requests", 500: 'Internal Server Error',
431: "Request Header Fields Too Large", 501: 'Not Implemented',
451: "Unavailable For Legal Reasons", 502: 'Bad Gateway',
500: "Internal Server Error", 503: 'Service Unavailable',
501: "Not Implemented", 504: 'Gateway Timeout',
502: "Bad Gateway", 505: 'HTTP Version Not Supported',
503: "Service Unavailable", 506: 'Variant Also Negotiates',
504: "Gateway Timeout", 507: 'Insufficient Storage',
505: "HTTP Version Not Supported", 508: 'Loop Detected',
506: "Variant Also Negotiates", 510: 'Not Extended',
507: "Insufficient Storage", 511: 'Network Authentication Required',
508: "Loop Detected", ...options.errors,
510: "Not Extended", }
511: "Network Authentication Required",
...options.errors,
}
const error = errors[result.status] const error = errors[result.status];
if (error) { if (error) {
throw new ApiError(options, result, error) throw new ApiError(options, result, error);
} }
if (!result.ok) { if (!result.ok) {
const errorStatus = result.status ?? "unknown" const errorStatus = result.status ?? 'unknown';
const errorStatusText = result.statusText ?? "unknown" const errorStatusText = result.statusText ?? 'unknown';
const errorBody = (() => { const errorBody = (() => {
try { try {
return JSON.stringify(result.body, null, 2) return JSON.stringify(result.body, null, 2);
} catch (e) { } catch (e) {
return undefined return undefined;
} }
})() })();
throw new ApiError( throw new ApiError(options, result,
options, `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
result, );
`Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, }
) };
}
}
/** /**
* Request method * Request method
@@ -329,59 +305,43 @@ export const catchErrorCodes = (
* @returns CancelablePromise<T> * @returns CancelablePromise<T>
* @throws ApiError * @throws ApiError
*/ */
export const request = <T>( export const request = <T>(config: OpenAPIConfig, options: ApiRequestOptions<T>, axiosClient: AxiosInstance = axios): CancelablePromise<T> => {
config: OpenAPIConfig, return new CancelablePromise(async (resolve, reject, onCancel) => {
options: ApiRequestOptions<T>, try {
axiosClient: AxiosInstance = axios, const url = getUrl(config, options);
): CancelablePromise<T> => { const formData = getFormData(options);
return new CancelablePromise(async (resolve, reject, onCancel) => { const body = getRequestBody(options);
try { const headers = await getHeaders(config, options);
const url = getUrl(config, options)
const formData = getFormData(options)
const body = getRequestBody(options)
const headers = await getHeaders(config, options)
if (!onCancel.isCancelled) { if (!onCancel.isCancelled) {
let response = await sendRequest<T>( let response = await sendRequest<T>(config, options, url, body, formData, headers, onCancel, axiosClient);
config,
options,
url,
body,
formData,
headers,
onCancel,
axiosClient,
)
for (const fn of config.interceptors.response._fns) { for (const fn of config.interceptors.response._fns) {
response = await fn(response) response = await fn(response);
} }
const responseBody = getResponseBody(response) const responseBody = getResponseBody(response);
const responseHeader = getResponseHeader( const responseHeader = getResponseHeader(response, options.responseHeader);
response,
options.responseHeader,
)
let transformedBody = responseBody let transformedBody = responseBody;
if (options.responseTransformer && isSuccess(response.status)) { if (options.responseTransformer && isSuccess(response.status)) {
transformedBody = await options.responseTransformer(responseBody) transformedBody = await options.responseTransformer(responseBody)
} }
const result: ApiResult = { const result: ApiResult = {
url, url,
ok: isSuccess(response.status), ok: isSuccess(response.status),
status: response.status, status: response.status,
statusText: response.statusText, statusText: response.statusText,
body: responseHeader ?? transformedBody, body: responseHeader ?? transformedBody,
} };
catchErrorCodes(options, result) catchErrorCodes(options, result);
resolve(result.body) resolve(result.body);
} }
} catch (error) { } catch (error) {
reject(error) reject(error);
} }
}) });
} };

View File

@@ -1,6 +1,6 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export { ApiError } from "./core/ApiError" export { ApiError } from './core/ApiError';
export { CancelablePromise, CancelError } from "./core/CancelablePromise" export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI" export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI';
export * from "./sdk.gen" export * from './sdk.gen';
export * from "./types.gen" export * from './types.gen';

View File

@@ -1,526 +1,526 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export const Body_login_login_access_tokenSchema = { export const Body_login_login_access_tokenSchema = {
properties: { properties: {
grant_type: { grant_type: {
anyOf: [ anyOf: [
{ {
type: "string", type: 'string',
pattern: "password", pattern: 'password'
},
{
type: 'null'
}
],
title: 'Grant Type'
}, },
{ username: {
type: "null", type: 'string',
title: 'Username'
}, },
], password: {
title: "Grant Type", type: 'string',
}, title: 'Password'
username: {
type: "string",
title: "Username",
},
password: {
type: "string",
title: "Password",
},
scope: {
type: "string",
title: "Scope",
default: "",
},
client_id: {
anyOf: [
{
type: "string",
}, },
{ scope: {
type: "null", type: 'string',
title: 'Scope',
default: ''
}, },
], client_id: {
title: "Client Id", anyOf: [
{
type: 'string'
},
{
type: 'null'
}
],
title: 'Client Id'
},
client_secret: {
anyOf: [
{
type: 'string'
},
{
type: 'null'
}
],
title: 'Client Secret'
}
}, },
client_secret: { type: 'object',
anyOf: [ required: ['username', 'password'],
{ title: 'Body_login-login_access_token'
type: "string", } as const;
},
{
type: "null",
},
],
title: "Client Secret",
},
},
type: "object",
required: ["username", "password"],
title: "Body_login-login_access_token",
} as const
export const HTTPValidationErrorSchema = { export const HTTPValidationErrorSchema = {
properties: { properties: {
detail: { detail: {
items: { items: {
$ref: "#/components/schemas/ValidationError", '$ref': '#/components/schemas/ValidationError'
}, },
type: "array", type: 'array',
title: "Detail", title: 'Detail'
}
}, },
}, type: 'object',
type: "object", title: 'HTTPValidationError'
title: "HTTPValidationError", } as const;
} as const
export const ItemCreateSchema = { export const ItemCreateSchema = {
properties: { properties: {
title: { title: {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
minLength: 1, minLength: 1,
title: "Title", title: 'Title'
},
description: {
anyOf: [
{
type: "string",
maxLength: 255,
}, },
{ description: {
type: "null", anyOf: [
}, {
], type: 'string',
title: "Description", maxLength: 255
},
{
type: 'null'
}
],
title: 'Description'
}
}, },
}, type: 'object',
type: "object", required: ['title'],
required: ["title"], title: 'ItemCreate'
title: "ItemCreate", } as const;
} as const
export const ItemPublicSchema = { export const ItemPublicSchema = {
properties: { properties: {
title: { title: {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
minLength: 1, minLength: 1,
title: "Title", title: 'Title'
},
description: {
anyOf: [
{
type: "string",
maxLength: 255,
}, },
{ description: {
type: "null", anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Description'
}, },
], id: {
title: "Description", type: 'string',
format: 'uuid',
title: 'Id'
},
owner_id: {
type: 'string',
format: 'uuid',
title: 'Owner Id'
}
}, },
id: { type: 'object',
type: "string", required: ['title', 'id', 'owner_id'],
format: "uuid", title: 'ItemPublic'
title: "Id", } as const;
},
owner_id: {
type: "string",
format: "uuid",
title: "Owner Id",
},
},
type: "object",
required: ["title", "id", "owner_id"],
title: "ItemPublic",
} as const
export const ItemUpdateSchema = { export const ItemUpdateSchema = {
properties: { properties: {
title: { title: {
anyOf: [ anyOf: [
{ {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
minLength: 1, minLength: 1
},
{
type: 'null'
}
],
title: 'Title'
}, },
{ description: {
type: "null", anyOf: [
}, {
], type: 'string',
title: "Title", maxLength: 255
},
{
type: 'null'
}
],
title: 'Description'
}
}, },
description: { type: 'object',
anyOf: [ title: 'ItemUpdate'
{ } as const;
type: "string",
maxLength: 255,
},
{
type: "null",
},
],
title: "Description",
},
},
type: "object",
title: "ItemUpdate",
} as const
export const ItemsPublicSchema = { export const ItemsPublicSchema = {
properties: { properties: {
data: { data: {
items: { items: {
$ref: "#/components/schemas/ItemPublic", '$ref': '#/components/schemas/ItemPublic'
}, },
type: "array", type: 'array',
title: "Data", title: 'Data'
},
count: {
type: 'integer',
title: 'Count'
}
}, },
count: { type: 'object',
type: "integer", required: ['data', 'count'],
title: "Count", title: 'ItemsPublic'
}, } as const;
},
type: "object",
required: ["data", "count"],
title: "ItemsPublic",
} as const
export const MessageSchema = { export const MessageSchema = {
properties: { properties: {
message: { message: {
type: "string", type: 'string',
title: "Message", title: 'Message'
}
}, },
}, type: 'object',
type: "object", required: ['message'],
required: ["message"], title: 'Message'
title: "Message", } as const;
} as const
export const NewPasswordSchema = { export const NewPasswordSchema = {
properties: { properties: {
token: { token: {
type: "string", type: 'string',
title: "Token", title: 'Token'
},
new_password: {
type: 'string',
maxLength: 40,
minLength: 8,
title: 'New Password'
}
}, },
new_password: { type: 'object',
type: "string", required: ['token', 'new_password'],
maxLength: 40, title: 'NewPassword'
minLength: 8, } as const;
title: "New Password",
},
},
type: "object",
required: ["token", "new_password"],
title: "NewPassword",
} as const
export const PrivateUserCreateSchema = { export const PrivateUserCreateSchema = {
properties: { properties: {
email: { email: {
type: "string", type: 'string',
title: "Email", title: 'Email'
},
password: {
type: 'string',
title: 'Password'
},
full_name: {
type: 'string',
title: 'Full Name'
},
is_verified: {
type: 'boolean',
title: 'Is Verified',
default: false
}
}, },
password: { type: 'object',
type: "string", required: ['email', 'password', 'full_name'],
title: "Password", title: 'PrivateUserCreate'
}, } as const;
full_name: {
type: "string",
title: "Full Name",
},
is_verified: {
type: "boolean",
title: "Is Verified",
default: false,
},
},
type: "object",
required: ["email", "password", "full_name"],
title: "PrivateUserCreate",
} as const
export const TokenSchema = { export const TokenSchema = {
properties: { properties: {
access_token: { access_token: {
type: "string", type: 'string',
title: "Access Token", title: 'Access Token'
},
token_type: {
type: 'string',
title: 'Token Type',
default: 'bearer'
}
}, },
token_type: { type: 'object',
type: "string", required: ['access_token'],
title: "Token Type", title: 'Token'
default: "bearer", } as const;
},
},
type: "object",
required: ["access_token"],
title: "Token",
} as const
export const UpdatePasswordSchema = { export const UpdatePasswordSchema = {
properties: { properties: {
current_password: { current_password: {
type: "string", type: 'string',
maxLength: 40, maxLength: 40,
minLength: 8, minLength: 8,
title: "Current Password", title: 'Current Password'
},
new_password: {
type: 'string',
maxLength: 40,
minLength: 8,
title: 'New Password'
}
}, },
new_password: { type: 'object',
type: "string", required: ['current_password', 'new_password'],
maxLength: 40, title: 'UpdatePassword'
minLength: 8, } as const;
title: "New Password",
},
},
type: "object",
required: ["current_password", "new_password"],
title: "UpdatePassword",
} as const
export const UserCreateSchema = { export const UserCreateSchema = {
properties: { properties: {
email: { email: {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
format: "email", format: 'email',
title: "Email", title: 'Email'
},
is_active: {
type: "boolean",
title: "Is Active",
default: true,
},
is_superuser: {
type: "boolean",
title: "Is Superuser",
default: false,
},
full_name: {
anyOf: [
{
type: "string",
maxLength: 255,
}, },
{ is_active: {
type: "null", type: 'boolean',
title: 'Is Active',
default: true
}, },
], is_superuser: {
title: "Full Name", type: 'boolean',
title: 'Is Superuser',
default: false
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
password: {
type: 'string',
maxLength: 40,
minLength: 8,
title: 'Password'
}
}, },
password: { type: 'object',
type: "string", required: ['email', 'password'],
maxLength: 40, title: 'UserCreate'
minLength: 8, } as const;
title: "Password",
},
},
type: "object",
required: ["email", "password"],
title: "UserCreate",
} as const
export const UserPublicSchema = { export const UserPublicSchema = {
properties: { properties: {
email: { email: {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
format: "email", format: 'email',
title: "Email", title: 'Email'
},
is_active: {
type: "boolean",
title: "Is Active",
default: true,
},
is_superuser: {
type: "boolean",
title: "Is Superuser",
default: false,
},
full_name: {
anyOf: [
{
type: "string",
maxLength: 255,
}, },
{ is_active: {
type: "null", type: 'boolean',
title: 'Is Active',
default: true
}, },
], is_superuser: {
title: "Full Name", type: 'boolean',
title: 'Is Superuser',
default: false
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
id: {
type: 'string',
format: 'uuid',
title: 'Id'
}
}, },
id: { type: 'object',
type: "string", required: ['email', 'id'],
format: "uuid", title: 'UserPublic'
title: "Id", } as const;
},
},
type: "object",
required: ["email", "id"],
title: "UserPublic",
} as const
export const UserRegisterSchema = { export const UserRegisterSchema = {
properties: { properties: {
email: { email: {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
format: "email", format: 'email',
title: "Email", title: 'Email'
},
password: {
type: "string",
maxLength: 40,
minLength: 8,
title: "Password",
},
full_name: {
anyOf: [
{
type: "string",
maxLength: 255,
}, },
{ password: {
type: "null", type: 'string',
maxLength: 40,
minLength: 8,
title: 'Password'
}, },
], full_name: {
title: "Full Name", anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
}
}, },
}, type: 'object',
type: "object", required: ['email', 'password'],
required: ["email", "password"], title: 'UserRegister'
title: "UserRegister", } as const;
} as const
export const UserUpdateSchema = { export const UserUpdateSchema = {
properties: { properties: {
email: { email: {
anyOf: [ anyOf: [
{ {
type: "string", type: 'string',
maxLength: 255, maxLength: 255,
format: "email", format: 'email'
},
{
type: 'null'
}
],
title: 'Email'
}, },
{ is_active: {
type: "null", type: 'boolean',
title: 'Is Active',
default: true
}, },
], is_superuser: {
title: "Email", type: 'boolean',
title: 'Is Superuser',
default: false
},
full_name: {
anyOf: [
{
type: 'string',
maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
},
password: {
anyOf: [
{
type: 'string',
maxLength: 40,
minLength: 8
},
{
type: 'null'
}
],
title: 'Password'
}
}, },
is_active: { type: 'object',
type: "boolean", title: 'UserUpdate'
title: "Is Active", } as const;
default: true,
},
is_superuser: {
type: "boolean",
title: "Is Superuser",
default: false,
},
full_name: {
anyOf: [
{
type: "string",
maxLength: 255,
},
{
type: "null",
},
],
title: "Full Name",
},
password: {
anyOf: [
{
type: "string",
maxLength: 40,
minLength: 8,
},
{
type: "null",
},
],
title: "Password",
},
},
type: "object",
title: "UserUpdate",
} as const
export const UserUpdateMeSchema = { export const UserUpdateMeSchema = {
properties: { properties: {
full_name: { full_name: {
anyOf: [ anyOf: [
{ {
type: "string", type: 'string',
maxLength: 255, maxLength: 255
},
{
type: 'null'
}
],
title: 'Full Name'
}, },
{ email: {
type: "null", anyOf: [
}, {
], type: 'string',
title: "Full Name", maxLength: 255,
format: 'email'
},
{
type: 'null'
}
],
title: 'Email'
}
}, },
email: { type: 'object',
anyOf: [ title: 'UserUpdateMe'
{ } as const;
type: "string",
maxLength: 255,
format: "email",
},
{
type: "null",
},
],
title: "Email",
},
},
type: "object",
title: "UserUpdateMe",
} as const
export const UsersPublicSchema = { export const UsersPublicSchema = {
properties: { properties: {
data: { data: {
items: { items: {
$ref: "#/components/schemas/UserPublic", '$ref': '#/components/schemas/UserPublic'
}, },
type: "array", type: 'array',
title: "Data", title: 'Data'
},
count: {
type: 'integer',
title: 'Count'
}
}, },
count: { type: 'object',
type: "integer", required: ['data', 'count'],
title: "Count", title: 'UsersPublic'
}, } as const;
},
type: "object",
required: ["data", "count"],
title: "UsersPublic",
} as const
export const ValidationErrorSchema = { export const ValidationErrorSchema = {
properties: { properties: {
loc: { loc: {
items: { items: {
anyOf: [ anyOf: [
{ {
type: "string", type: 'string'
}, },
{ {
type: "integer", type: 'integer'
}, }
], ]
}, },
type: "array", type: 'array',
title: "Location", title: 'Location'
},
msg: {
type: 'string',
title: 'Message'
},
type: {
type: 'string',
title: 'Error Type'
}
}, },
msg: { type: 'object',
type: "string", required: ['loc', 'msg', 'type'],
title: "Message", title: 'ValidationError'
}, } as const;
type: {
type: "string",
title: "Error Type",
},
},
type: "object",
required: ["loc", "msg", "type"],
title: "ValidationError",
} as const

File diff suppressed because it is too large Load Diff

View File

@@ -1,234 +1,234 @@
// This file is auto-generated by @hey-api/openapi-ts // This file is auto-generated by @hey-api/openapi-ts
export type Body_login_login_access_token = { export type Body_login_login_access_token = {
grant_type?: string | null grant_type?: (string | null);
username: string username: string;
password: string password: string;
scope?: string scope?: string;
client_id?: string | null client_id?: (string | null);
client_secret?: string | null client_secret?: (string | null);
} };
export type HTTPValidationError = { export type HTTPValidationError = {
detail?: Array<ValidationError> detail?: Array<ValidationError>;
} };
export type ItemCreate = { export type ItemCreate = {
title: string title: string;
description?: string | null description?: (string | null);
} };
export type ItemPublic = { export type ItemPublic = {
title: string title: string;
description?: string | null description?: (string | null);
id: string id: string;
owner_id: string owner_id: string;
} };
export type ItemsPublic = { export type ItemsPublic = {
data: Array<ItemPublic> data: Array<ItemPublic>;
count: number count: number;
} };
export type ItemUpdate = { export type ItemUpdate = {
title?: string | null title?: (string | null);
description?: string | null description?: (string | null);
} };
export type Message = { export type Message = {
message: string message: string;
} };
export type NewPassword = { export type NewPassword = {
token: string token: string;
new_password: string new_password: string;
} };
export type PrivateUserCreate = { export type PrivateUserCreate = {
email: string email: string;
password: string password: string;
full_name: string full_name: string;
is_verified?: boolean is_verified?: boolean;
} };
export type Token = { export type Token = {
access_token: string access_token: string;
token_type?: string token_type?: string;
} };
export type UpdatePassword = { export type UpdatePassword = {
current_password: string current_password: string;
new_password: string new_password: string;
} };
export type UserCreate = { export type UserCreate = {
email: string email: string;
is_active?: boolean is_active?: boolean;
is_superuser?: boolean is_superuser?: boolean;
full_name?: string | null full_name?: (string | null);
password: string password: string;
} };
export type UserPublic = { export type UserPublic = {
email: string email: string;
is_active?: boolean is_active?: boolean;
is_superuser?: boolean is_superuser?: boolean;
full_name?: string | null full_name?: (string | null);
id: string id: string;
} };
export type UserRegister = { export type UserRegister = {
email: string email: string;
password: string password: string;
full_name?: string | null full_name?: (string | null);
} };
export type UsersPublic = { export type UsersPublic = {
data: Array<UserPublic> data: Array<UserPublic>;
count: number count: number;
} };
export type UserUpdate = { export type UserUpdate = {
email?: string | null email?: (string | null);
is_active?: boolean is_active?: boolean;
is_superuser?: boolean is_superuser?: boolean;
full_name?: string | null full_name?: (string | null);
password?: string | null password?: (string | null);
} };
export type UserUpdateMe = { export type UserUpdateMe = {
full_name?: string | null full_name?: (string | null);
email?: string | null email?: (string | null);
} };
export type ValidationError = { export type ValidationError = {
loc: Array<string | number> loc: Array<(string | number)>;
msg: string msg: string;
type: string type: string;
} };
export type ItemsReadItemsData = { export type ItemsReadItemsData = {
limit?: number limit?: number;
skip?: number skip?: number;
} };
export type ItemsReadItemsResponse = ItemsPublic export type ItemsReadItemsResponse = (ItemsPublic);
export type ItemsCreateItemData = { export type ItemsCreateItemData = {
requestBody: ItemCreate requestBody: ItemCreate;
} };
export type ItemsCreateItemResponse = ItemPublic export type ItemsCreateItemResponse = (ItemPublic);
export type ItemsReadItemData = { export type ItemsReadItemData = {
id: string id: string;
} };
export type ItemsReadItemResponse = ItemPublic export type ItemsReadItemResponse = (ItemPublic);
export type ItemsUpdateItemData = { export type ItemsUpdateItemData = {
id: string id: string;
requestBody: ItemUpdate requestBody: ItemUpdate;
} };
export type ItemsUpdateItemResponse = ItemPublic export type ItemsUpdateItemResponse = (ItemPublic);
export type ItemsDeleteItemData = { export type ItemsDeleteItemData = {
id: string id: string;
} };
export type ItemsDeleteItemResponse = Message export type ItemsDeleteItemResponse = (Message);
export type LoginLoginAccessTokenData = { export type LoginLoginAccessTokenData = {
formData: Body_login_login_access_token formData: Body_login_login_access_token;
} };
export type LoginLoginAccessTokenResponse = Token export type LoginLoginAccessTokenResponse = (Token);
export type LoginTestTokenResponse = UserPublic export type LoginTestTokenResponse = (UserPublic);
export type LoginRecoverPasswordData = { export type LoginRecoverPasswordData = {
email: string email: string;
} };
export type LoginRecoverPasswordResponse = Message export type LoginRecoverPasswordResponse = (Message);
export type LoginResetPasswordData = { export type LoginResetPasswordData = {
requestBody: NewPassword requestBody: NewPassword;
} };
export type LoginResetPasswordResponse = Message export type LoginResetPasswordResponse = (Message);
export type LoginRecoverPasswordHtmlContentData = { export type LoginRecoverPasswordHtmlContentData = {
email: string email: string;
} };
export type LoginRecoverPasswordHtmlContentResponse = string export type LoginRecoverPasswordHtmlContentResponse = (string);
export type PrivateCreateUserData = { export type PrivateCreateUserData = {
requestBody: PrivateUserCreate requestBody: PrivateUserCreate;
} };
export type PrivateCreateUserResponse = UserPublic export type PrivateCreateUserResponse = (UserPublic);
export type UsersReadUsersData = { export type UsersReadUsersData = {
limit?: number limit?: number;
skip?: number skip?: number;
} };
export type UsersReadUsersResponse = UsersPublic export type UsersReadUsersResponse = (UsersPublic);
export type UsersCreateUserData = { export type UsersCreateUserData = {
requestBody: UserCreate requestBody: UserCreate;
} };
export type UsersCreateUserResponse = UserPublic export type UsersCreateUserResponse = (UserPublic);
export type UsersReadUserMeResponse = UserPublic export type UsersReadUserMeResponse = (UserPublic);
export type UsersDeleteUserMeResponse = Message export type UsersDeleteUserMeResponse = (Message);
export type UsersUpdateUserMeData = { export type UsersUpdateUserMeData = {
requestBody: UserUpdateMe requestBody: UserUpdateMe;
} };
export type UsersUpdateUserMeResponse = UserPublic export type UsersUpdateUserMeResponse = (UserPublic);
export type UsersUpdatePasswordMeData = { export type UsersUpdatePasswordMeData = {
requestBody: UpdatePassword requestBody: UpdatePassword;
} };
export type UsersUpdatePasswordMeResponse = Message export type UsersUpdatePasswordMeResponse = (Message);
export type UsersRegisterUserData = { export type UsersRegisterUserData = {
requestBody: UserRegister requestBody: UserRegister;
} };
export type UsersRegisterUserResponse = UserPublic export type UsersRegisterUserResponse = (UserPublic);
export type UsersReadUserByIdData = { export type UsersReadUserByIdData = {
userId: string userId: string;
} };
export type UsersReadUserByIdResponse = UserPublic export type UsersReadUserByIdResponse = (UserPublic);
export type UsersUpdateUserData = { export type UsersUpdateUserData = {
requestBody: UserUpdate requestBody: UserUpdate;
userId: string userId: string;
} };
export type UsersUpdateUserResponse = UserPublic export type UsersUpdateUserResponse = (UserPublic);
export type UsersDeleteUserData = { export type UsersDeleteUserData = {
userId: string userId: string;
} };
export type UsersDeleteUserResponse = Message export type UsersDeleteUserResponse = (Message);
export type UtilsTestEmailData = { export type UtilsTestEmailData = {
emailTo: string emailTo: string;
} };
export type UtilsTestEmailResponse = Message export type UtilsTestEmailResponse = (Message);
export type UtilsHealthCheckResponse = boolean export type UtilsHealthCheckResponse = (boolean);

View File

@@ -1,10 +1,3 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
import { type UserCreate, UsersService } from "@/client"
import type { ApiError } from "@/client/core/ApiError"
import useCustomToast from "@/hooks/useCustomToast"
import { emailPattern, handleError } from "@/utils"
import { import {
Button, Button,
DialogActionTrigger, DialogActionTrigger,
@@ -14,8 +7,14 @@ import {
Text, Text,
VStack, VStack,
} from "@chakra-ui/react" } from "@chakra-ui/react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { useState } from "react" import { useState } from "react"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
import { FaPlus } from "react-icons/fa" import { FaPlus } from "react-icons/fa"
import { type UserCreate, UsersService } from "@/client"
import type { ApiError } from "@/client/core/ApiError"
import useCustomToast from "@/hooks/useCustomToast"
import { emailPattern, handleError } from "@/utils"
import { Checkbox } from "../ui/checkbox" import { Checkbox } from "../ui/checkbox"
import { import {
DialogBody, DialogBody,
@@ -106,7 +105,6 @@ const AddUser = () => {
label="Email" label="Email"
> >
<Input <Input
id="email"
{...register("email", { {...register("email", {
required: "Email is required", required: "Email is required",
pattern: emailPattern, pattern: emailPattern,
@@ -122,7 +120,6 @@ const AddUser = () => {
label="Full Name" label="Full Name"
> >
<Input <Input
id="name"
{...register("full_name")} {...register("full_name")}
placeholder="Full name" placeholder="Full name"
type="text" type="text"
@@ -136,7 +133,6 @@ const AddUser = () => {
label="Set Password" label="Set Password"
> >
<Input <Input
id="password"
{...register("password", { {...register("password", {
required: "Password is required", required: "Password is required",
minLength: { minLength: {
@@ -156,7 +152,6 @@ const AddUser = () => {
label="Confirm Password" label="Confirm Password"
> >
<Input <Input
id="confirm_password"
{...register("confirm_password", { {...register("confirm_password", {
required: "Please confirm your password", required: "Please confirm your password",
validate: (value) => validate: (value) =>

View File

@@ -1,6 +1,3 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
import { import {
Button, Button,
DialogActionTrigger, DialogActionTrigger,
@@ -11,10 +8,12 @@ import {
Text, Text,
VStack, VStack,
} from "@chakra-ui/react" } from "@chakra-ui/react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { useState } from "react" import { useState } from "react"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
import { FaExchangeAlt } from "react-icons/fa" import { FaExchangeAlt } from "react-icons/fa"
import { type UserPublic, type UserUpdate, UsersService } from "@/client" import { type UserPublic, UsersService, type UserUpdate } from "@/client"
import type { ApiError } from "@/client/core/ApiError" import type { ApiError } from "@/client/core/ApiError"
import useCustomToast from "@/hooks/useCustomToast" import useCustomToast from "@/hooks/useCustomToast"
import { emailPattern, handleError } from "@/utils" import { emailPattern, handleError } from "@/utils"
@@ -105,7 +104,6 @@ const EditUser = ({ user }: EditUserProps) => {
label="Email" label="Email"
> >
<Input <Input
id="email"
{...register("email", { {...register("email", {
required: "Email is required", required: "Email is required",
pattern: emailPattern, pattern: emailPattern,
@@ -121,7 +119,6 @@ const EditUser = ({ user }: EditUserProps) => {
label="Full Name" label="Full Name"
> >
<Input <Input
id="name"
{...register("full_name")} {...register("full_name")}
placeholder="Full name" placeholder="Full name"
type="text" type="text"
@@ -134,7 +131,6 @@ const EditUser = ({ user }: EditUserProps) => {
label="Set Password" label="Set Password"
> >
<Input <Input
id="password"
{...register("password", { {...register("password", {
minLength: { minLength: {
value: 8, value: 8,
@@ -152,7 +148,6 @@ const EditUser = ({ user }: EditUserProps) => {
label="Confirm Password" label="Confirm Password"
> >
<Input <Input
id="confirm_password"
{...register("confirm_password", { {...register("confirm_password", {
validate: (value) => validate: (value) =>
value === getValues().password || value === getValues().password ||

View File

@@ -1,10 +1,9 @@
import { IconButton } from "@chakra-ui/react" import { IconButton } from "@chakra-ui/react"
import { BsThreeDotsVertical } from "react-icons/bs" import { BsThreeDotsVertical } from "react-icons/bs"
import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu"
import type { ItemPublic } from "@/client" import type { ItemPublic } from "@/client"
import DeleteItem from "../Items/DeleteItem" import DeleteItem from "../Items/DeleteItem"
import EditItem from "../Items/EditItem" import EditItem from "../Items/EditItem"
import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu"
interface ItemActionsMenuProps { interface ItemActionsMenuProps {
item: ItemPublic item: ItemPublic

View File

@@ -3,54 +3,41 @@ import { Link } from "@tanstack/react-router"
const NotFound = () => { const NotFound = () => {
return ( return (
<> <Flex
<Flex height="100vh"
height="100vh" align="center"
align="center" justify="center"
justify="center" flexDir="column"
flexDir="column" data-testid="not-found"
data-testid="not-found" p={4}
p={4} >
> <Flex alignItems="center" zIndex={1}>
<Flex alignItems="center" zIndex={1}> <Flex flexDir="column" ml={4} align="center" justify="center" p={4}>
<Flex flexDir="column" ml={4} align="center" justify="center" p={4}> <Text
<Text fontSize={{ base: "6xl", md: "8xl" }}
fontSize={{ base: "6xl", md: "8xl" }} fontWeight="bold"
fontWeight="bold" lineHeight="1"
lineHeight="1" mb={4}
mb={4} >
> 404
404 </Text>
</Text> <Text fontSize="2xl" fontWeight="bold" mb={2}>
<Text fontSize="2xl" fontWeight="bold" mb={2}> Oops!
Oops! </Text>
</Text>
</Flex>
</Flex> </Flex>
<Text
fontSize="lg"
color="gray.600"
mb={4}
textAlign="center"
zIndex={1}
>
The page you are looking for was not found.
</Text>
<Center zIndex={1}>
<Link to="/">
<Button
variant="solid"
colorScheme="teal"
mt={4}
alignSelf="center"
>
Go Back
</Button>
</Link>
</Center>
</Flex> </Flex>
</>
<Text fontSize="lg" color="gray.600" mb={4} textAlign="center" zIndex={1}>
The page you are looking for was not found.
</Text>
<Center zIndex={1}>
<Link to="/">
<Button variant="solid" colorScheme="teal" mt={4} alignSelf="center">
Go Back
</Button>
</Link>
</Center>
</Flex>
) )
} }

View File

@@ -1,10 +1,9 @@
import { IconButton } from "@chakra-ui/react" import { IconButton } from "@chakra-ui/react"
import { BsThreeDotsVertical } from "react-icons/bs" import { BsThreeDotsVertical } from "react-icons/bs"
import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu"
import type { UserPublic } from "@/client" import type { UserPublic } from "@/client"
import DeleteUser from "../Admin/DeleteUser" import DeleteUser from "../Admin/DeleteUser"
import EditUser from "../Admin/EditUser" import EditUser from "../Admin/EditUser"
import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu"
interface UserActionsMenuProps { interface UserActionsMenuProps {
user: UserPublic user: UserPublic

View File

@@ -1,6 +1,3 @@
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { type SubmitHandler, useForm } from "react-hook-form"
import { import {
Button, Button,
DialogActionTrigger, DialogActionTrigger,
@@ -9,7 +6,9 @@ import {
Text, Text,
VStack, VStack,
} from "@chakra-ui/react" } from "@chakra-ui/react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { useState } from "react" import { useState } from "react"
import { type SubmitHandler, useForm } from "react-hook-form"
import { FaPlus } from "react-icons/fa" import { FaPlus } from "react-icons/fa"
import { type ItemCreate, ItemsService } from "@/client" import { type ItemCreate, ItemsService } from "@/client"
@@ -93,7 +92,6 @@ const AddItem = () => {
label="Title" label="Title"
> >
<Input <Input
id="title"
{...register("title", { {...register("title", {
required: "Title is required.", required: "Title is required.",
})} })}
@@ -108,7 +106,6 @@ const AddItem = () => {
label="Description" label="Description"
> >
<Input <Input
id="description"
{...register("description")} {...register("description")}
placeholder="Description" placeholder="Description"
type="text" type="text"

View File

@@ -101,7 +101,6 @@ const EditItem = ({ item }: EditItemProps) => {
label="Title" label="Title"
> >
<Input <Input
id="title"
{...register("title", { {...register("title", {
required: "Title is required", required: "Title is required",
})} })}
@@ -116,7 +115,6 @@ const EditItem = ({ item }: EditItemProps) => {
label="Description" label="Description"
> >
<Input <Input
id="description"
{...register("description")} {...register("description")}
placeholder="Description" placeholder="Description"
type="text" type="text"

View File

@@ -7,25 +7,23 @@ const Appearance = () => {
const { theme, setTheme } = useTheme() const { theme, setTheme } = useTheme()
return ( return (
<> <Container maxW="full">
<Container maxW="full"> <Heading size="sm" py={4}>
<Heading size="sm" py={4}> Appearance
Appearance </Heading>
</Heading>
<RadioGroup <RadioGroup
onValueChange={(e) => setTheme(e.value ?? "system")} onValueChange={(e) => setTheme(e.value ?? "system")}
value={theme} value={theme}
colorPalette="teal" colorPalette="teal"
> >
<Stack> <Stack>
<Radio value="system">System</Radio> <Radio value="system">System</Radio>
<Radio value="light">Light Mode</Radio> <Radio value="light">Light Mode</Radio>
<Radio value="dark">Dark Mode</Radio> <Radio value="dark">Dark Mode</Radio>
</Stack> </Stack>
</RadioGroup> </RadioGroup>
</Container> </Container>
</>
) )
} }
export default Appearance export default Appearance

View File

@@ -42,46 +42,39 @@ const ChangePassword = () => {
} }
return ( return (
<> <Container maxW="full">
<Container maxW="full"> <Heading size="sm" py={4}>
<Heading size="sm" py={4}> Change Password
Change Password </Heading>
</Heading> <Box as="form" onSubmit={handleSubmit(onSubmit)}>
<Box as="form" onSubmit={handleSubmit(onSubmit)}> <VStack gap={4} w={{ base: "100%", md: "sm" }}>
<VStack gap={4} w={{ base: "100%", md: "sm" }}> <PasswordInput
<PasswordInput type="current_password"
type="current_password" startElement={<FiLock />}
startElement={<FiLock />} {...register("current_password", passwordRules())}
{...register("current_password", passwordRules())} placeholder="Current Password"
placeholder="Current Password" errors={errors}
errors={errors} />
/> <PasswordInput
<PasswordInput type="new_password"
type="new_password" startElement={<FiLock />}
startElement={<FiLock />} {...register("new_password", passwordRules())}
{...register("new_password", passwordRules())} placeholder="New Password"
placeholder="New Password" errors={errors}
errors={errors} />
/> <PasswordInput
<PasswordInput type="confirm_password"
type="confirm_password" startElement={<FiLock />}
startElement={<FiLock />} {...register("confirm_password", confirmPasswordRules(getValues))}
{...register("confirm_password", confirmPasswordRules(getValues))} placeholder="Confirm Password"
placeholder="Confirm Password" errors={errors}
errors={errors} />
/> </VStack>
</VStack> <Button variant="solid" mt={4} type="submit" loading={isSubmitting}>
<Button Save
variant="solid" </Button>
mt={4} </Box>
type="submit" </Container>
loading={isSubmitting}
>
Save
</Button>
</Box>
</Container>
</>
) )
} }
export default ChangePassword export default ChangePassword

View File

@@ -49,60 +49,58 @@ const DeleteConfirmation = () => {
} }
return ( return (
<> <DialogRoot
<DialogRoot size={{ base: "xs", md: "md" }}
size={{ base: "xs", md: "md" }} role="alertdialog"
role="alertdialog" placement="center"
placement="center" open={isOpen}
open={isOpen} onOpenChange={({ open }) => setIsOpen(open)}
onOpenChange={({ open }) => setIsOpen(open)} >
> <DialogTrigger asChild>
<DialogTrigger asChild> <Button variant="solid" colorPalette="red" mt={4}>
<Button variant="solid" colorPalette="red" mt={4}> Delete
Delete </Button>
</Button> </DialogTrigger>
</DialogTrigger>
<DialogContent> <DialogContent>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<DialogCloseTrigger /> <DialogCloseTrigger />
<DialogHeader> <DialogHeader>
<DialogTitle>Confirmation Required</DialogTitle> <DialogTitle>Confirmation Required</DialogTitle>
</DialogHeader> </DialogHeader>
<DialogBody> <DialogBody>
<Text mb={4}> <Text mb={4}>
All your account data will be{" "} All your account data will be{" "}
<strong>permanently deleted.</strong> If you are sure, please <strong>permanently deleted.</strong> If you are sure, please
click <strong>"Confirm"</strong> to proceed. This action cannot click <strong>"Confirm"</strong> to proceed. This action cannot be
be undone. undone.
</Text> </Text>
</DialogBody> </DialogBody>
<DialogFooter gap={2}> <DialogFooter gap={2}>
<ButtonGroup> <ButtonGroup>
<DialogActionTrigger asChild> <DialogActionTrigger asChild>
<Button
variant="subtle"
colorPalette="gray"
disabled={isSubmitting}
>
Cancel
</Button>
</DialogActionTrigger>
<Button <Button
variant="solid" variant="subtle"
colorPalette="red" colorPalette="gray"
type="submit" disabled={isSubmitting}
loading={isSubmitting}
> >
Delete Cancel
</Button> </Button>
</ButtonGroup> </DialogActionTrigger>
</DialogFooter> <Button
</form> variant="solid"
</DialogContent> colorPalette="red"
</DialogRoot> type="submit"
</> loading={isSubmitting}
>
Delete
</Button>
</ButtonGroup>
</DialogFooter>
</form>
</DialogContent>
</DialogRoot>
) )
} }

View File

@@ -14,8 +14,8 @@ import { type SubmitHandler, useForm } from "react-hook-form"
import { import {
type ApiError, type ApiError,
type UserPublic, type UserPublic,
type UserUpdateMe,
UsersService, UsersService,
type UserUpdateMe,
} from "@/client" } from "@/client"
import useAuth from "@/hooks/useAuth" import useAuth from "@/hooks/useAuth"
import useCustomToast from "@/hooks/useCustomToast" import useCustomToast from "@/hooks/useCustomToast"
@@ -70,80 +70,78 @@ const UserInformation = () => {
} }
return ( return (
<> <Container maxW="full">
<Container maxW="full"> <Heading size="sm" py={4}>
<Heading size="sm" py={4}> User Information
User Information </Heading>
</Heading> <Box
<Box w={{ sm: "full", md: "sm" }}
w={{ sm: "full", md: "sm" }} as="form"
as="form" onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(onSubmit)} >
> <Field label="Full name">
<Field label="Full name"> {editMode ? (
{editMode ? ( <Input
<Input {...register("full_name", { maxLength: 30 })}
{...register("full_name", { maxLength: 30 })} type="text"
type="text" size="md"
size="md" />
/> ) : (
) : ( <Text
<Text fontSize="md"
fontSize="md" py={2}
py={2} color={!currentUser?.full_name ? "gray" : "inherit"}
color={!currentUser?.full_name ? "gray" : "inherit"} truncate
truncate maxW="sm"
maxW="sm"
>
{currentUser?.full_name || "N/A"}
</Text>
)}
</Field>
<Field
mt={4}
label="Email"
invalid={!!errors.email}
errorText={errors.email?.message}
>
{editMode ? (
<Input
{...register("email", {
required: "Email is required",
pattern: emailPattern,
})}
type="email"
size="md"
/>
) : (
<Text fontSize="md" py={2} truncate maxW="sm">
{currentUser?.email}
</Text>
)}
</Field>
<Flex mt={4} gap={3}>
<Button
variant="solid"
onClick={toggleEditMode}
type={editMode ? "button" : "submit"}
loading={editMode ? isSubmitting : false}
disabled={editMode ? !isDirty || !getValues("email") : false}
> >
{editMode ? "Save" : "Edit"} {currentUser?.full_name || "N/A"}
</Text>
)}
</Field>
<Field
mt={4}
label="Email"
invalid={!!errors.email}
errorText={errors.email?.message}
>
{editMode ? (
<Input
{...register("email", {
required: "Email is required",
pattern: emailPattern,
})}
type="email"
size="md"
/>
) : (
<Text fontSize="md" py={2} truncate maxW="sm">
{currentUser?.email}
</Text>
)}
</Field>
<Flex mt={4} gap={3}>
<Button
variant="solid"
onClick={toggleEditMode}
type={editMode ? "button" : "submit"}
loading={editMode ? isSubmitting : false}
disabled={editMode ? !isDirty || !getValues("email") : false}
>
{editMode ? "Save" : "Edit"}
</Button>
{editMode && (
<Button
variant="subtle"
colorPalette="gray"
onClick={onCancel}
disabled={isSubmitting}
>
Cancel
</Button> </Button>
{editMode && ( )}
<Button </Flex>
variant="subtle" </Box>
colorPalette="gray" </Container>
onClick={onCancel}
disabled={isSubmitting}
>
Cancel
</Button>
)}
</Flex>
</Box>
</Container>
</>
) )
} }

View File

@@ -4,13 +4,12 @@ import {
QueryClient, QueryClient,
QueryClientProvider, QueryClientProvider,
} from "@tanstack/react-query" } from "@tanstack/react-query"
import { RouterProvider, createRouter } from "@tanstack/react-router" import { createRouter, RouterProvider } from "@tanstack/react-router"
import { StrictMode } from "react" import { StrictMode } from "react"
import ReactDOM from "react-dom/client" import ReactDOM from "react-dom/client"
import { routeTree } from "./routeTree.gen"
import { ApiError, OpenAPI } from "./client" import { ApiError, OpenAPI } from "./client"
import { CustomProvider } from "./components/ui/provider" import { CustomProvider } from "./components/ui/provider"
import { routeTree } from "./routeTree.gen"
OpenAPI.BASE = import.meta.env.VITE_API_URL OpenAPI.BASE = import.meta.env.VITE_API_URL
OpenAPI.TOKEN = async () => { OpenAPI.TOKEN = async () => {

View File

@@ -1,4 +1,4 @@
import { Outlet, createRootRoute } from "@tanstack/react-router" import { createRootRoute, Outlet } from "@tanstack/react-router"
import React, { Suspense } from "react" import React, { Suspense } from "react"
import NotFound from "@/components/Common/NotFound" import NotFound from "@/components/Common/NotFound"

View File

@@ -1,5 +1,5 @@
import { Flex } from "@chakra-ui/react" import { Flex } from "@chakra-ui/react"
import { Outlet, createFileRoute, redirect } from "@tanstack/react-router" import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
import Navbar from "@/components/Common/Navbar" import Navbar from "@/components/Common/Navbar"
import Sidebar from "@/components/Common/Sidebar" import Sidebar from "@/components/Common/Sidebar"

View File

@@ -11,15 +11,13 @@ function Dashboard() {
const { user: currentUser } = useAuth() const { user: currentUser } = useAuth()
return ( return (
<> <Container maxW="full">
<Container maxW="full"> <Box pt={12} m={4}>
<Box pt={12} m={4}> <Text fontSize="2xl" truncate maxW="sm">
<Text fontSize="2xl" truncate maxW="sm"> Hi, {currentUser?.full_name || currentUser?.email} 👋🏼
Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 </Text>
</Text> <Text>Welcome back, nice to see you again!</Text>
<Text>Welcome back, nice to see you again!</Text> </Box>
</Box> </Container>
</Container>
</>
) )
} }

View File

@@ -1,7 +1,7 @@
import { Container, Image, Input, Text } from "@chakra-ui/react" import { Container, Image, Input, Text } from "@chakra-ui/react"
import { import {
Link as RouterLink,
createFileRoute, createFileRoute,
Link as RouterLink,
redirect, redirect,
} from "@tanstack/react-router" } from "@tanstack/react-router"
import { type SubmitHandler, useForm } from "react-hook-form" import { type SubmitHandler, useForm } from "react-hook-form"
@@ -55,61 +55,58 @@ function Login() {
} }
return ( return (
<> <Container
<Container as="form"
as="form" onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(onSubmit)} h="100vh"
h="100vh" maxW="sm"
maxW="sm" alignItems="stretch"
alignItems="stretch" justifyContent="center"
justifyContent="center" gap={4}
gap={4} centerContent
centerContent >
<Image
src={Logo}
alt="FastAPI logo"
height="auto"
maxW="2xs"
alignSelf="center"
mb={4}
/>
<Field
invalid={!!errors.username}
errorText={errors.username?.message || !!error}
> >
<Image <InputGroup w="100%" startElement={<FiMail />}>
src={Logo} <Input
alt="FastAPI logo" {...register("username", {
height="auto" required: "Username is required",
maxW="2xs" pattern: emailPattern,
alignSelf="center" })}
mb={4} placeholder="Email"
/> type="email"
<Field />
invalid={!!errors.username} </InputGroup>
errorText={errors.username?.message || !!error} </Field>
> <PasswordInput
<InputGroup w="100%" startElement={<FiMail />}> type="password"
<Input startElement={<FiLock />}
id="username" {...register("password", passwordRules())}
{...register("username", { placeholder="Password"
required: "Username is required", errors={errors}
pattern: emailPattern, />
})} <RouterLink to="/recover-password" className="main-link">
placeholder="Email" Forgot Password?
type="email" </RouterLink>
/> <Button variant="solid" type="submit" loading={isSubmitting} size="md">
</InputGroup> Log In
</Field> </Button>
<PasswordInput <Text>
type="password" Don't have an account?{" "}
startElement={<FiLock />} <RouterLink to="/signup" className="main-link">
{...register("password", passwordRules())} Sign Up
placeholder="Password"
errors={errors}
/>
<RouterLink to="/recover-password" className="main-link">
Forgot Password?
</RouterLink> </RouterLink>
<Button variant="solid" type="submit" loading={isSubmitting} size="md"> </Text>
Log In </Container>
</Button>
<Text>
Don't have an account?{" "}
<RouterLink to="/signup" className="main-link">
Sign Up
</RouterLink>
</Text>
</Container>
</>
) )
} }

View File

@@ -77,7 +77,6 @@ function RecoverPassword() {
<Field invalid={!!errors.email} errorText={errors.email?.message}> <Field invalid={!!errors.email} errorText={errors.email?.message}>
<InputGroup w="100%" startElement={<FiMail />}> <InputGroup w="100%" startElement={<FiMail />}>
<Input <Input
id="email"
{...register("email", { {...register("email", {
required: "Email is required", required: "Email is required",
pattern: emailPattern, pattern: emailPattern,

View File

@@ -1,7 +1,7 @@
import { Container, Flex, Image, Input, Text } from "@chakra-ui/react" import { Container, Flex, Image, Input, Text } from "@chakra-ui/react"
import { import {
Link as RouterLink,
createFileRoute, createFileRoute,
Link as RouterLink,
redirect, redirect,
} from "@tanstack/react-router" } from "@tanstack/react-router"
import { type SubmitHandler, useForm } from "react-hook-form" import { type SubmitHandler, useForm } from "react-hook-form"
@@ -54,82 +54,78 @@ function SignUp() {
} }
return ( return (
<> <Flex flexDir={{ base: "column", md: "row" }} justify="center" h="100vh">
<Flex flexDir={{ base: "column", md: "row" }} justify="center" h="100vh"> <Container
<Container as="form"
as="form" onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(onSubmit)} h="100vh"
h="100vh" maxW="sm"
maxW="sm" alignItems="stretch"
alignItems="stretch" justifyContent="center"
justifyContent="center" gap={4}
gap={4} centerContent
centerContent >
<Image
src={Logo}
alt="FastAPI logo"
height="auto"
maxW="2xs"
alignSelf="center"
mb={4}
/>
<Field
invalid={!!errors.full_name}
errorText={errors.full_name?.message}
> >
<Image <InputGroup w="100%" startElement={<FiUser />}>
src={Logo} <Input
alt="FastAPI logo" minLength={3}
height="auto" {...register("full_name", {
maxW="2xs" required: "Full Name is required",
alignSelf="center" })}
mb={4} placeholder="Full Name"
/> type="text"
<Field />
invalid={!!errors.full_name} </InputGroup>
errorText={errors.full_name?.message} </Field>
>
<InputGroup w="100%" startElement={<FiUser />}>
<Input
id="full_name"
minLength={3}
{...register("full_name", {
required: "Full Name is required",
})}
placeholder="Full Name"
type="text"
/>
</InputGroup>
</Field>
<Field invalid={!!errors.email} errorText={errors.email?.message}> <Field invalid={!!errors.email} errorText={errors.email?.message}>
<InputGroup w="100%" startElement={<FiUser />}> <InputGroup w="100%" startElement={<FiUser />}>
<Input <Input
id="email" {...register("email", {
{...register("email", { required: "Email is required",
required: "Email is required", pattern: emailPattern,
pattern: emailPattern, })}
})} placeholder="Email"
placeholder="Email" type="email"
type="email" />
/> </InputGroup>
</InputGroup> </Field>
</Field> <PasswordInput
<PasswordInput type="password"
type="password" startElement={<FiLock />}
startElement={<FiLock />} {...register("password", passwordRules())}
{...register("password", passwordRules())} placeholder="Password"
placeholder="Password" errors={errors}
errors={errors} />
/> <PasswordInput
<PasswordInput type="confirm_password"
type="confirm_password" startElement={<FiLock />}
startElement={<FiLock />} {...register("confirm_password", confirmPasswordRules(getValues))}
{...register("confirm_password", confirmPasswordRules(getValues))} placeholder="Confirm Password"
placeholder="Confirm Password" errors={errors}
errors={errors} />
/> <Button variant="solid" type="submit" loading={isSubmitting}>
<Button variant="solid" type="submit" loading={isSubmitting}> Sign Up
Sign Up </Button>
</Button> <Text>
<Text> Already have an account?{" "}
Already have an account?{" "} <RouterLink to="/login" className="main-link">
<RouterLink to="/login" className="main-link"> Log In
Log In </RouterLink>
</RouterLink> </Text>
</Text> </Container>
</Container> </Flex>
</Flex>
</>
) )
} }

View File

@@ -1,4 +1,4 @@
import { type Page, expect, test } from "@playwright/test" import { expect, type Page, test } from "@playwright/test"
import { firstSuperuser, firstSuperuserPassword } from "./config.ts" import { firstSuperuser, firstSuperuserPassword } from "./config.ts"
import { randomPassword } from "./utils/random.ts" import { randomPassword } from "./utils/random.ts"

View File

@@ -1,4 +1,4 @@
import { type Page, expect, test } from "@playwright/test" import { expect, type Page, test } from "@playwright/test"
import { randomEmail, randomPassword } from "./utils/random" import { randomEmail, randomPassword } from "./utils/random"

View File

@@ -9,7 +9,10 @@ type Email = {
async function findEmail({ async function findEmail({
request, request,
filter, filter,
}: { request: APIRequestContext; filter?: (email: Email) => boolean }) { }: {
request: APIRequestContext
filter?: (email: Email) => boolean
}) {
const response = await request.get(`${process.env.MAILCATCHER_HOST}/messages`) const response = await request.get(`${process.env.MAILCATCHER_HOST}/messages`)
let emails = await response.json() let emails = await response.json()

View File

@@ -1,4 +1,4 @@
import { type Page, expect } from "@playwright/test" import { expect, type Page } from "@playwright/test"
export async function signUpNewUser( export async function signUpNewUser(
page: Page, page: Page,

View File

@@ -1,5 +1,5 @@
import path from "node:path" import path from "node:path"
import { tanstackRouter } from '@tanstack/router-plugin/vite' import { tanstackRouter } from "@tanstack/router-plugin/vite"
import react from "@vitejs/plugin-react-swc" import react from "@vitejs/plugin-react-swc"
import { defineConfig } from "vite" import { defineConfig } from "vite"
@@ -15,5 +15,6 @@ export default defineConfig({
target: "react", target: "react",
autoCodeSplitting: true, autoCodeSplitting: true,
}), }),
react()], react(),
],
}) })

View File

@@ -8,5 +8,4 @@ python -c "import app.main; import json; print(json.dumps(app.main.app.openapi()
cd .. cd ..
mv openapi.json frontend/ mv openapi.json frontend/
cd frontend cd frontend
npm run generate-client npm run generate-client
npx biome format --write ./src/client