From 31a7b0ff064e50d4d07e1c13dc3b0e6c97941454 Mon Sep 17 00:00:00 2001 From: Stavros Date: Fri, 9 May 2025 16:49:54 +0300 Subject: [PATCH] feat: app context --- frontend/bun.lock | 47 ++++++++++++++++++++- frontend/eslint.config.js | 27 ++++++------ frontend/package.json | 3 ++ frontend/src/context/app-context.tsx | 37 ++++++++++++++++ frontend/src/lib/utils.ts | 4 ++ frontend/src/main.tsx | 14 ++++-- frontend/src/pages/continue-page.tsx | 13 +++--- frontend/src/pages/forgot-password-page.tsx | 7 ++- frontend/src/pages/login-page.tsx | 4 +- frontend/src/pages/logout-page.tsx | 5 ++- frontend/src/pages/unauthorized-page.tsx | 5 ++- frontend/src/schemas/app-context-schema.ts | 13 ++++++ frontend/src/schemas/user-context-schema.ts | 13 ++++++ frontend/src/utils/utils.ts | 3 -- 14 files changed, 158 insertions(+), 37 deletions(-) create mode 100644 frontend/src/context/app-context.tsx create mode 100644 frontend/src/schemas/app-context-schema.ts create mode 100644 frontend/src/schemas/user-context-schema.ts delete mode 100644 frontend/src/utils/utils.ts diff --git a/frontend/bun.lock b/frontend/bun.lock index d8a422b..fe296d9 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -10,6 +10,8 @@ "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.2", "@tailwindcss/vite": "^4.1.4", + "@tanstack/react-query": "^5.75.6", + "axios": "^1.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "i18next": "^25.0.2", @@ -29,6 +31,7 @@ }, "devDependencies": { "@eslint/js": "^9.22.0", + "@tanstack/eslint-plugin-query": "^5.74.7", "@types/node": "^22.15.3", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", @@ -318,6 +321,12 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.5", "", { "dependencies": { "@tailwindcss/node": "4.1.5", "@tailwindcss/oxide": "4.1.5", "tailwindcss": "4.1.5" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-FE1stRoqdHSb7RxesMfCXE8icwI1W6zGE/512ae3ZDrpkQYTTYeSyUJPRCjZd8CwVAhpDUbi1YR8pcZioFJQ/w=="], + "@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.74.7", "", { "dependencies": { "@typescript-eslint/utils": "^8.18.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-EeHuaaYiCOD+XOGyB7LMNEx9OEByAa5lkgP+S3ZggjKJpmIO6iRWeoIYYDKo2F8uc3qXcVhTfC7pn7NddQiNtA=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.75.6", "", {}, "sha512-r+WQ/z30KZF0TFkzCT7C8gWgrLuv7/t+gEjqeEp69JAIUS/omuieaHeIkoscjEcT6yaWebXGTyjdiIxJkYzEXg=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.75.6", "", { "dependencies": { "@tanstack/query-core": "5.75.6" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0d97uvpeHUNN5eTEsJzYgxLgAEyUM+XaJAWFFAaXH+dQgDN//TTDNbei/YVzN5BUJcWcnQ7YaQDtLLzSe+IvTg=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], @@ -382,6 +391,10 @@ "aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="], + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -424,6 +437,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -450,6 +465,8 @@ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], @@ -476,6 +493,8 @@ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -542,6 +561,10 @@ "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], + + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -572,6 +595,8 @@ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], @@ -746,9 +771,9 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], @@ -804,6 +829,8 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], @@ -998,14 +1025,22 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + "accepts/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + "express/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "send/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + + "type-is/mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -1024,6 +1059,14 @@ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "send/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "type-is/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="], diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 092408a..1cf529b 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,28 +1,31 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; +import pluginQuery from "@tanstack/eslint-plugin-query"; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ["dist"] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + "@tanstack/query": pluginQuery, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], + "@tanstack/query/exhaustive-deps": "error", }, }, -) +); diff --git a/frontend/package.json b/frontend/package.json index cc50bf4..9ed153d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,8 @@ "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.2", "@tailwindcss/vite": "^4.1.4", + "@tanstack/react-query": "^5.75.6", + "axios": "^1.9.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "i18next": "^25.0.2", @@ -35,6 +37,7 @@ }, "devDependencies": { "@eslint/js": "^9.22.0", + "@tanstack/eslint-plugin-query": "^5.74.7", "@types/node": "^22.15.3", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", diff --git a/frontend/src/context/app-context.tsx b/frontend/src/context/app-context.tsx new file mode 100644 index 0000000..347a06a --- /dev/null +++ b/frontend/src/context/app-context.tsx @@ -0,0 +1,37 @@ +import { AppContextSchema } from "@/schemas/app-context-schema"; +import { createContext, useContext } from "react"; +import { useQuery } from "@tanstack/react-query"; +import axios from "axios"; + +const AppContext = createContext(null); + +export const AppContextProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const { isPending, isError, data, error } = useQuery({ + queryKey: ["status"], + queryFn: () => axios.get("/api/app").then((res) => res.data), + }); + + if (isPending) { + return; + } + + if (isError) { + throw error; + } + + return {children}; +}; + +export const useAppContext = () => { + const context = useContext(AppContext); + + if (!context) { + throw new Error("useAppContext must be used within an AppContextProvider"); + } + + return context; +}; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 7dee9b7..a220096 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -12,4 +12,8 @@ export const isValidUrl = (url: string) => { } catch (e) { return false } +} + +export const capitalize = (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1); } \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 8186566..edf6c55 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,6 +12,8 @@ import { TotpPage } from "./pages/totp-page.tsx"; import { ForgotPasswordPage } from "./pages/forgot-password-page.tsx"; import { LogoutPage } from "./pages/logout-page.tsx"; import { UnauthorizedPage } from "./pages/unauthorized-page.tsx"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { AppContextProvider } from "./context/app-context.tsx"; const router = createBrowserRouter([ { @@ -56,10 +58,16 @@ const router = createBrowserRouter([ }, ]); +const queryClient = new QueryClient(); + createRoot(document.getElementById("root")!).render( - - - + + + + + + + , ); diff --git a/frontend/src/pages/continue-page.tsx b/frontend/src/pages/continue-page.tsx index db2ec43..323bd5f 100644 --- a/frontend/src/pages/continue-page.tsx +++ b/frontend/src/pages/continue-page.tsx @@ -7,22 +7,21 @@ import { CardTitle, } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; +import { useAppContext } from "@/context/app-context"; import { isValidUrl } from "@/lib/utils"; import { Trans, useTranslation } from "react-i18next"; import { Navigate, useNavigate } from "react-router"; export const ContinuePage = () => { - const { t } = useTranslation(); - const navigate = useNavigate(); const params = new URLSearchParams(window.location.search); + const redirectURI = params.get("redirect_uri"); - const redirectURI = params.get("redirect_uri") ?? ""; + const { domain, disableContinue } = useAppContext(); + const { t } = useTranslation(); - //psuedo - const domain = "127.0.0.1"; - const disableContinue = false; + const navigate = useNavigate(); - if (redirectURI === "") { + if (!redirectURI) { return ; } diff --git a/frontend/src/pages/forgot-password-page.tsx b/frontend/src/pages/forgot-password-page.tsx index 96e6518..c98ff5b 100644 --- a/frontend/src/pages/forgot-password-page.tsx +++ b/frontend/src/pages/forgot-password-page.tsx @@ -4,10 +4,12 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { useAppContext } from "@/context/app-context"; import { useTranslation } from "react-i18next"; import Markdown from "react-markdown"; export const ForgotPasswordPage = () => { + const { forgotPasswordMessage } = useAppContext(); const { t } = useTranslation(); return ( @@ -15,10 +17,7 @@ export const ForgotPasswordPage = () => { {t("forgotPasswordTitle")} - - You can reset your password by changing the `USERS` environment - variable. - + {forgotPasswordMessage} diff --git a/frontend/src/pages/login-page.tsx b/frontend/src/pages/login-page.tsx index acbf0d8..1278226 100644 --- a/frontend/src/pages/login-page.tsx +++ b/frontend/src/pages/login-page.tsx @@ -11,12 +11,12 @@ import { } from "@/components/ui/card"; import { OAuthButton } from "@/components/ui/oauth-button"; import { SeperatorWithChildren } from "@/components/ui/separator"; +import { useAppContext } from "@/context/app-context"; import { useTranslation } from "react-i18next"; export const LoginPage = () => { + const { configuredProviders, title } = useAppContext(); const { t } = useTranslation(); - const configuredProviders = ["google", "github", "generic", "username"]; - const title = "Tinyauth"; const oauthConfigured = configuredProviders.filter((provider) => provider !== "username").length > diff --git a/frontend/src/pages/logout-page.tsx b/frontend/src/pages/logout-page.tsx index 0e1434c..2e65e54 100644 --- a/frontend/src/pages/logout-page.tsx +++ b/frontend/src/pages/logout-page.tsx @@ -7,14 +7,15 @@ import { CardTitle, } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; -import { capitalize } from "@/utils/utils"; +import { useAppContext } from "@/context/app-context"; +import { capitalize } from "@/lib/utils"; import { Trans, useTranslation } from "react-i18next"; export const LogoutPage = () => { + const { genericName } = useAppContext(); const { t } = useTranslation(); const provider = "google"; - const genericName = "generic"; const username = "username"; const email = "smbd@example.com"; diff --git a/frontend/src/pages/unauthorized-page.tsx b/frontend/src/pages/unauthorized-page.tsx index 540e20f..fc4c484 100644 --- a/frontend/src/pages/unauthorized-page.tsx +++ b/frontend/src/pages/unauthorized-page.tsx @@ -8,7 +8,7 @@ import { } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; import { Trans, useTranslation } from "react-i18next"; -import { Navigate } from "react-router"; +import { Navigate, useNavigate } from "react-router"; export const UnauthorizedPage = () => { const searchParams = new URLSearchParams(window.location.search); @@ -17,6 +17,7 @@ export const UnauthorizedPage = () => { const groupErr = searchParams.get("groupErr"); const { t } = useTranslation(); + const navigate = useNavigate(); if (!username) { return ; @@ -51,7 +52,7 @@ export const UnauthorizedPage = () => { - diff --git a/frontend/src/schemas/app-context-schema.ts b/frontend/src/schemas/app-context-schema.ts new file mode 100644 index 0000000..a59e2c8 --- /dev/null +++ b/frontend/src/schemas/app-context-schema.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const appContextSchema = z.object({ + configuredProviders: z.array(z.string()), + disableContinue: z.boolean(), + title: z.string(), + genericName: z.string(), + domain: z.string(), + forgotPasswordMessage: z.string(), + oauthAutoRedirect: z.string(), +}) + +export type AppContextSchema = z.infer; \ No newline at end of file diff --git a/frontend/src/schemas/user-context-schema.ts b/frontend/src/schemas/user-context-schema.ts new file mode 100644 index 0000000..08b0b32 --- /dev/null +++ b/frontend/src/schemas/user-context-schema.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const userContextSchema = z.object({ + isLoggedIn: z.boolean(), + username: z.string(), + name: z.string(), + email: z.string(), + provider: z.string(), + oauth: z.boolean(), + totpPending: z.boolean(), +}) + +export type UserContextSchema = z.infer; \ No newline at end of file diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts deleted file mode 100644 index dbf7d10..0000000 --- a/frontend/src/utils/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const capitalize = (str: string) => { - return str.charAt(0).toUpperCase() + str.slice(1); -} \ No newline at end of file