From eca78dbfdb49348ddd07af8fd600c2d9b6b994c0 Mon Sep 17 00:00:00 2001 From: Amir Kedis <88613195+amir-kedis@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:05:49 +0200 Subject: [PATCH 001/136] fix(ops): backend integration with cors and cookies (#33) --- .../authentication/login/services/apiLogin.ts | 5 +- .../logout/services/apiLogout.ts | 4 +- .../services/GetProfileSettings.ts | 1 + .../services/UpdateProfileSettings.ts | 1 + docs/flows/integrate-with-back.md | 55 +++++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 docs/flows/integrate-with-back.md diff --git a/app/src/features/authentication/login/services/apiLogin.ts b/app/src/features/authentication/login/services/apiLogin.ts index f1632f7a..ab88294f 100644 --- a/app/src/features/authentication/login/services/apiLogin.ts +++ b/app/src/features/authentication/login/services/apiLogin.ts @@ -1,12 +1,15 @@ +import { API_URL } from "@constants"; import { User } from "../LoginForm/LoginForm"; export async function login(user: User) { - const res = await fetch("auth/login", { + const res = await fetch(`${API_URL}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json", + "Access-Control-Allow-credentials": "true", }, body: JSON.stringify(user), + credentials: "include", }); const data = await res.json(); diff --git a/app/src/features/authentication/logout/services/apiLogout.ts b/app/src/features/authentication/logout/services/apiLogout.ts index 939ac582..8cc6f84b 100644 --- a/app/src/features/authentication/logout/services/apiLogout.ts +++ b/app/src/features/authentication/logout/services/apiLogout.ts @@ -1,6 +1,8 @@ +import { API_URL } from "@constants"; + export async function logout() { try { - const res = await fetch("/auth/logout", { + const res = await fetch(`${API_URL}/auth/logout`, { method: "POST", credentials: "include", }); diff --git a/app/src/features/profile-settings/services/GetProfileSettings.ts b/app/src/features/profile-settings/services/GetProfileSettings.ts index c4085023..b4abe4f5 100644 --- a/app/src/features/profile-settings/services/GetProfileSettings.ts +++ b/app/src/features/profile-settings/services/GetProfileSettings.ts @@ -6,6 +6,7 @@ async function GetProfileSettings() { headers: { "Content-Type": "application/json", }, + credentials: "include", }); const data = await res.json(); diff --git a/app/src/features/profile-settings/services/UpdateProfileSettings.ts b/app/src/features/profile-settings/services/UpdateProfileSettings.ts index d15cf1d6..2ba986c3 100644 --- a/app/src/features/profile-settings/services/UpdateProfileSettings.ts +++ b/app/src/features/profile-settings/services/UpdateProfileSettings.ts @@ -8,6 +8,7 @@ async function UpdateProfileSettings(newProfileSettings: EditProfileForm) { "Content-Type": "application/json", }, body: JSON.stringify(newProfileSettings), + credentials: "include", }); const data = await res.json(); diff --git a/docs/flows/integrate-with-back.md b/docs/flows/integrate-with-back.md new file mode 100644 index 00000000..a63a4162 --- /dev/null +++ b/docs/flows/integrate-with-back.md @@ -0,0 +1,55 @@ +## 🔥 Time to integrate + +1. first get out of `telware-frontend` directory by typing `cd ..` + +```sh +# folder structure +root +├── telware-frontend +``` + +2. Now, clone the backend repository. + +```sh +git clone +``` + +3. Now, get the `.env` file and place it in the backend folder. + +``` +root +├── telware-frontend +├── telware-backend + ├── .env +``` + +4. now go to the backend directory and run the following commands. + +```sh +cd telware-backend +./run.sh +``` + +> [!TIP] +> Go make some coffee, it will take some time to install the dependencies. + +5. Now, go to the frontend directory and change your `API_URL` + +```sh +VITE_BACKEND_API=http://localhost:3000/api/v1/ +VITE_ENV=testing +VITE_REACT_APP_SITE_KEY=secret +VITE_SITE_SECRET=secret +VITE_GITHUB_CLIENT_ID=secret +VITE_GOOGLE_CLIENT_ID=secret +VITE_PORT=5174 +``` + +6. Now, run the frontend server. + +```sh +npm run dev +``` + +> [!IMPORTANT] +> It's important here to use the `http://localhost:5174` as the backend will allow this url only. From 65371f6ad56a3be4ae39f989e03f431b5f6f1f91 Mon Sep 17 00:00:00 2001 From: Asmaa-204 <130288326+Asmaa-204@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:29:30 +0200 Subject: [PATCH 002/136] =?UTF-8?q?=F0=9F=90=9B=20(Login):=20fix=20bugs=20?= =?UTF-8?q?(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Fix data-testid overriding * 🐛 Fix resetting the form after submit * 🐛 Consistent error messages in password & email fields * 🐛 Resetting email & pass in case of a successful login * 🐛 Added api-url to requests' endpoints * 🔇 Removed logs --- app/package-lock.json | 121 ++++++++++++++++-- app/package.json | 2 +- app/public/oauth/facebook.png | Bin 997 -> 0 bytes .../inputs/input-field/InputField.tsx | 7 +- .../login/LoginForm/LoginForm.test.tsx | 4 +- .../login/LoginForm/LoginForm.tsx | 16 ++- .../authentication/login/OauthOptions.tsx | 6 - .../login/hooks/useAuthCheck.ts | 5 +- .../oauth/hooks/useOauthFacebook.ts | 18 --- .../oauth/services/apiFacebookOauth.ts | 17 --- app/src/mocks/oauth/oauth.ts | 19 --- 11 files changed, 127 insertions(+), 88 deletions(-) delete mode 100644 app/public/oauth/facebook.png delete mode 100644 app/src/features/authentication/oauth/hooks/useOauthFacebook.ts delete mode 100644 app/src/features/authentication/oauth/services/apiFacebookOauth.ts diff --git a/app/package-lock.json b/app/package-lock.json index f06a05bf..10f0c69d 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -17,7 +17,6 @@ "@tanstack/react-query": "^5.59.13", "@tanstack/react-query-devtools": "^5.59.15", "@types/react-google-recaptcha": "^2.1.9", - "jest-fixed-jsdom": "^0.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", @@ -53,6 +52,7 @@ "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-fixed-jsdom": "^0.0.7", "msw": "^2.4.11", "prettier": "^3.3.3", "react-redux": "9.1.2", @@ -2971,6 +2971,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.7.0", @@ -3013,6 +3014,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -3142,6 +3144,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -3228,6 +3231,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -3864,12 +3868,14 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" @@ -3879,6 +3885,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -4056,6 +4063,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, "engines": { "node": ">= 10" } @@ -4165,12 +4173,14 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -4180,6 +4190,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -4227,6 +4238,7 @@ "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", @@ -4249,6 +4261,7 @@ "version": "22.8.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.0.tgz", "integrity": "sha512-84rafSBHC/z1i1E3p0cJwKA+CfYDNSXX9WSZBRopjIzLET8oNt6ht2tei4C7izwDeEiLLfdeSVBv1egOH916hg==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.8" @@ -4326,6 +4339,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, "license": "MIT" }, "node_modules/@types/statuses": { @@ -4345,6 +4359,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, "license": "MIT" }, "node_modules/@types/use-sync-external-store": { @@ -4358,6 +4373,7 @@ "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -4367,6 +4383,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -4654,12 +4671,14 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead" + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true }, "node_modules/acorn": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4672,6 +4691,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -4691,6 +4711,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, "dependencies": { "acorn": "^8.11.0" }, @@ -4745,6 +4766,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4805,7 +4827,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/babel-jest": { "version": "29.7.0", @@ -5000,6 +5023,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -5121,6 +5145,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -5147,6 +5172,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -5238,6 +5264,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5250,12 +5277,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5389,7 +5418,8 @@ "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true }, "node_modules/csstype": { "version": "3.1.3", @@ -5417,7 +5447,8 @@ "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true }, "node_modules/dedent": { "version": "1.5.3", @@ -5455,6 +5486,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -5519,6 +5551,7 @@ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "deprecated": "Use your platform's native DOMException instead", + "dev": true, "dependencies": { "webidl-conversions": "^7.0.0" }, @@ -5581,6 +5614,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "engines": { "node": ">=0.12" }, @@ -5662,6 +5696,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -5682,6 +5717,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "license": "BSD-3-Clause", "optional": true, "engines": { @@ -5873,6 +5909,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -5912,6 +5949,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5921,6 +5959,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -6094,6 +6133,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -6156,6 +6196,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6320,6 +6361,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -6349,6 +6391,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6409,6 +6452,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -6608,6 +6652,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -6616,7 +6661,8 @@ "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true }, "node_modules/is-stream": { "version": "2.0.1", @@ -7074,6 +7120,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -7100,6 +7147,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "dependencies": { "debug": "4" }, @@ -7111,6 +7159,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, "dependencies": { "cssom": "~0.3.6" }, @@ -7121,12 +7170,14 @@ "node_modules/jest-environment-jsdom/node_modules/cssstyle/node_modules/cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true }, "node_modules/jest-environment-jsdom/node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", @@ -7140,6 +7191,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, "dependencies": { "whatwg-encoding": "^2.0.0" }, @@ -7151,6 +7203,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -7164,6 +7217,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -7176,6 +7230,7 @@ "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -7220,6 +7275,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, "dependencies": { "punycode": "^2.1.1" }, @@ -7231,6 +7287,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, "dependencies": { "xml-name-validator": "^4.0.0" }, @@ -7242,6 +7299,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -7253,6 +7311,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, "engines": { "node": ">=12" } @@ -7261,6 +7320,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" @@ -7273,6 +7333,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, "engines": { "node": ">=12" } @@ -7296,9 +7357,10 @@ } }, "node_modules/jest-fixed-jsdom": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/jest-fixed-jsdom/-/jest-fixed-jsdom-0.0.6.tgz", - "integrity": "sha512-qhGsErwo+qOPaIkG6q3rhYvsc89dnljrGcsLERgplfxID42Ynv8nrxhjNVOs3ibKSbA4gcVxi8ZuDjcpXrA0ag==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/jest-fixed-jsdom/-/jest-fixed-jsdom-0.0.7.tgz", + "integrity": "sha512-g0GNGpwycfkMx9SdYIFCLF2bKeCopDKzRAHkQgsyxBym3azF5m7gGR6ZQvQKgba3kl8Zx2GEgOQbwqNVHABWcw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -7433,6 +7495,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", @@ -7453,6 +7516,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -7465,6 +7529,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -7479,6 +7544,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -7718,6 +7784,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -8120,6 +8187,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -8133,6 +8201,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -8141,6 +8210,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -8327,7 +8397,8 @@ "node_modules/nwsapi": { "version": "2.2.13", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.13.tgz", - "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==" + "integrity": "sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==", + "dev": true }, "node_modules/object-assign": { "version": "4.1.1", @@ -8477,6 +8548,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz", "integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==", + "dev": true, "dependencies": { "entities": "^4.5.0" }, @@ -8577,6 +8649,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -8817,12 +8890,14 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true, "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8849,6 +8924,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, "license": "MIT" }, "node_modules/queue-microtask": { @@ -9163,6 +9239,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, "license": "MIT" }, "node_modules/reselect": { @@ -9296,12 +9373,14 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -9375,6 +9454,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9430,6 +9510,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -9442,6 +9523,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9617,6 +9699,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -9640,7 +9723,8 @@ "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true }, "node_modules/test-exclude": { "version": "6.0.0", @@ -9703,6 +9787,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -9721,6 +9806,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "psl": "^1.1.33", @@ -9891,6 +9977,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9967,6 +10054,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -10013,6 +10101,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4.0.0" @@ -10062,6 +10151,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "license": "MIT", "dependencies": { "querystringify": "^2.1.1", @@ -10281,6 +10371,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, "engines": { "node": ">=12" } @@ -10351,6 +10442,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, "engines": { "node": ">=10.0.0" }, @@ -10370,7 +10462,8 @@ "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true }, "node_modules/y18n": { "version": "5.0.8", diff --git a/app/package.json b/app/package.json index a51bddd5..c718252b 100644 --- a/app/package.json +++ b/app/package.json @@ -23,7 +23,6 @@ "@tanstack/react-query": "^5.59.13", "@tanstack/react-query-devtools": "^5.59.15", "@types/react-google-recaptcha": "^2.1.9", - "jest-fixed-jsdom": "^0.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", @@ -59,6 +58,7 @@ "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-fixed-jsdom": "^0.0.7", "msw": "^2.4.11", "prettier": "^3.3.3", "react-redux": "9.1.2", diff --git a/app/public/oauth/facebook.png b/app/public/oauth/facebook.png deleted file mode 100644 index 05f3b437dfc95b43358f2338ceb2033e701c1907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 997 zcmVPx&pGibPRA>dwnm=#TKor3H5xP)cHYR2k_DXz#!YA-Ipi6vSP&*v#{Xlx44=t zqIMbvc<>^?={CZoAK)}WIHl8#^bt;X;hv=^T|sej*VFKBj{)I>h^&HeQ(^OhtB+fl z^gsbO0>!i+4c2Bgt`=MY+-(d9%?CiZUkltZ8mzhbiljGas)YwVg+_%y9`5UVw+;m6 z9Nb6=4c1wTk`;-5^_v2G`ox2Fl|swLXhmdIU*d{8ZvzAew1#2xd?5!o&qz*IVL)-R zT%qs!OAurTr;xnd2hW~yZ*4#yb%ylmhjaS&$3L-c>|C&G(DvZjsdPLxqnRX8pAZ1B zu`{CN{FyJ=X2f{Q_Q zk<)+i5aG0|^B|pjz@1Egx)x#MM`XPyeRg)!)&OV|kXoy-3e?jq6}~>xGDb3_$-(h; zpKs5{BJBxq+eN_vx_jjzV5E+1IYp0!C?PIr+Nv46$X;4JgZQ`V=Pg9w_5FY|$vX*7%r=^OyDG&$=E`MR~s|s!iq+1X^ ze}dv{JgM+Z|f{tbVd_jo*2O#*klGm{5@mxXj36N`F zwnwr3bUZhZxMp=x1_Eui@iG^Xm~5%!mA2c!cn5KX;Dn}b<`bkcdvn2T_9hjw50C(p zq;hT7M)-0cC<%gyD8IFP0NJfAKgvS6oTE4mCsl8CscjF!a)j9jPZtp0cLFr~hZ+kI zut4(w9E9)Li&>n8Eu3zH>wVt8>}4-c`4=qqLiK6?vX@yC{y-BJR@3 - + {label} {error} diff --git a/app/src/features/authentication/login/LoginForm/LoginForm.test.tsx b/app/src/features/authentication/login/LoginForm/LoginForm.test.tsx index 7f4763fa..be11446f 100644 --- a/app/src/features/authentication/login/LoginForm/LoginForm.test.tsx +++ b/app/src/features/authentication/login/LoginForm/LoginForm.test.tsx @@ -114,8 +114,8 @@ describe("LoginForm", () => { }); it("resets the form after a successful login", async () => { - const loginSuccess = jest.fn((_, { onSettled }) => { - onSettled(); + const loginSuccess = jest.fn((_, { onSuccess }) => { + onSuccess(); }); (useLogin as jest.Mock).mockReturnValue({ diff --git a/app/src/features/authentication/login/LoginForm/LoginForm.tsx b/app/src/features/authentication/login/LoginForm/LoginForm.tsx index 9c5a0f4f..fb831f3d 100644 --- a/app/src/features/authentication/login/LoginForm/LoginForm.tsx +++ b/app/src/features/authentication/login/LoginForm/LoginForm.tsx @@ -14,6 +14,9 @@ import InputField from "@components/inputs/input-field/InputField"; import PasswordInputField from "@components/inputs/password-input-field/PasswordInputField"; import SpinnerMini from "@components/SpinnerMini"; +const MAX_PASSWORD_LENGTH = 128; +const MAX_EMAIL_LENGTH = 254; + export type User = { email: string; password: string; @@ -82,16 +85,26 @@ export default function LoginForm() { register, handleSubmit, reset, - formState: { errors }, } = useForm({ resolver: yupResolver(schema), }); const onSubmit: SubmitHandler = function (data) { + if ( + data.email.length > MAX_EMAIL_LENGTH || + data.password.length > MAX_PASSWORD_LENGTH + ) { + setError("Invalid email or password"); + return; + } + login(data, { onSettled: (_, error) => { setError(error ? error.message : ""); + }, + + onSuccess: () => { reset(); }, }); @@ -108,7 +121,6 @@ export default function LoginForm() { { const urlParams = new URLSearchParams(window.location.search); @@ -48,8 +45,6 @@ function OauthOptions() { if (code) { if (window.location.href.includes("google")) { loginWithGoogle(code); - } else if (window.location.href.includes("facebook")) { - loginWithFacebook(code); } else { loginWithGithub(code); } @@ -75,7 +70,6 @@ function OauthOptions() { - diff --git a/app/src/features/authentication/login/hooks/useAuthCheck.ts b/app/src/features/authentication/login/hooks/useAuthCheck.ts index 75d748e3..3fac0431 100644 --- a/app/src/features/authentication/login/hooks/useAuthCheck.ts +++ b/app/src/features/authentication/login/hooks/useAuthCheck.ts @@ -14,12 +14,11 @@ const useAuthCheck = (path: Path) => { const res = await fetch(`${API_URL}/auth/me`, { method: "GET", credentials: "include", - }); - + }); if (res.ok) { setIsAuthenticated(true); navigate("/", { replace: true }); - } else { + } else { setIsAuthenticated(false); navigate(path); } diff --git a/app/src/features/authentication/oauth/hooks/useOauthFacebook.ts b/app/src/features/authentication/oauth/hooks/useOauthFacebook.ts deleted file mode 100644 index 1bdadc38..00000000 --- a/app/src/features/authentication/oauth/hooks/useOauthFacebook.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { useNavigate } from "react-router-dom"; -import { apiFacebookOauth } from "../services/apiFacebookOauth"; - -export function useOauthFacebook() { - const queryClient = useQueryClient(); - const navigate = useNavigate(); - - const { mutate: loginWithFacebook, isPending } = useMutation({ - mutationFn: apiFacebookOauth, - onSuccess: (data) => { - queryClient.setQueryData(["user"], { user: data.user }); - navigate("/", { replace: true }); - }, - }); - - return { loginWithFacebook, isPending }; -} diff --git a/app/src/features/authentication/oauth/services/apiFacebookOauth.ts b/app/src/features/authentication/oauth/services/apiFacebookOauth.ts deleted file mode 100644 index d6836638..00000000 --- a/app/src/features/authentication/oauth/services/apiFacebookOauth.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { API_URL } from "@constants"; - -export async function apiFacebookOauth(code: string) { - const res = await fetch(`${API_URL}/auth/oauth/facebook`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ code }), - }); - - const data = await res.json(); - - if (data.status !== "success") { - throw new Error(data.message); - } - - return data.data; -} diff --git a/app/src/mocks/oauth/oauth.ts b/app/src/mocks/oauth/oauth.ts index 27fa22c8..96d9517f 100644 --- a/app/src/mocks/oauth/oauth.ts +++ b/app/src/mocks/oauth/oauth.ts @@ -60,23 +60,4 @@ export const OauthMock = [ } ); }), - - http.post<{}, RequestBody, ResponseBody>("/auth/oauth/facebook", async () => { - return HttpResponse.json( - { - message: "Successful login", - status: "success", - data: { - user: MOCK_USER, - sessionID: TOKEN, - }, - }, - { - status: 201, - headers: { - "Set-Cookie": `sessionID=${TOKEN}; HttpOnly; SameSite=Strict; Path=/`, - }, - } - ); - }), ]; From e2d6b9dae35971b17b5efa3cfeec1eda99a90e39 Mon Sep 17 00:00:00 2001 From: Abdelruhman Sami <121282837+AbdelruhmanSamy@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:32:34 +0200 Subject: [PATCH 003/136] :bug: (privacy-settings): fixing bugs (#34) * :bug: (sidebar): adding data-testid in row items * :bug: (sidebar): fixing responsiveness * :recycle: (sidebar-row): refactoring key element & data-testid --- app/src/components/side-bar/SideBar.tsx | 1 + .../side-bar/settings/OptionsList.tsx | 12 +++++++++- .../settings/side-bar-row/SideBarRow.tsx | 24 ++++++++++++++++--- app/src/data/deviceSize.ts | 2 +- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/src/components/side-bar/SideBar.tsx b/app/src/components/side-bar/SideBar.tsx index a3b6696d..91ef1266 100644 --- a/app/src/components/side-bar/SideBar.tsx +++ b/app/src/components/side-bar/SideBar.tsx @@ -29,6 +29,7 @@ const fadeOut = keyframes` const StyledSidebar = styled.aside<{ $isExiting: boolean }>` height: 100vh; + min-width: 300px; background-color: var(--color-background); overflow: hidden; position: relative; diff --git a/app/src/components/side-bar/settings/OptionsList.tsx b/app/src/components/side-bar/settings/OptionsList.tsx index d7c43f95..93a78801 100644 --- a/app/src/components/side-bar/settings/OptionsList.tsx +++ b/app/src/components/side-bar/settings/OptionsList.tsx @@ -2,6 +2,7 @@ import styled from "styled-components"; import SideBarRow, { SideBarRowProps } from "./side-bar-row/SideBarRow"; import Heading from "@components/Heading"; import { useAppSelector } from "@hooks/useGlobalState"; +import { statusMap } from "@data/sideBar"; const StyledOptionsList = styled.div` display: flex; @@ -29,7 +30,16 @@ function OptionsList({ rows }: { rows: SideBarRowProps[] }) { {title} {rows.map((item, index) => ( - + ))} )} diff --git a/app/src/components/side-bar/settings/side-bar-row/SideBarRow.tsx b/app/src/components/side-bar/settings/side-bar-row/SideBarRow.tsx index c54e42ca..4426bcef 100644 --- a/app/src/components/side-bar/settings/side-bar-row/SideBarRow.tsx +++ b/app/src/components/side-bar/settings/side-bar-row/SideBarRow.tsx @@ -11,7 +11,11 @@ import { getIcon, iconStrings } from "@data/icons"; import { useEffect, useState } from "react"; import { statusMap } from "@data/sideBar"; import { RadioOptionInterface } from "@components/inputs/radio-input/RadioInput"; -import { activeStates } from "types/user"; +import { + activeStates, + activitySettingsInterface, + privacySettingsInterface, +} from "types/user"; const StyledSideBarRow = styled.div` height: 4rem; @@ -60,12 +64,13 @@ interface SideBarRowProps { privacyStatus?: privacySettingsID; count?: number; redirect?: number; + id: number; } function ExtractData( privacyStatus: privacySettingsID | undefined, activityStatus: activitySettingsID | undefined, - currStatus: string | undefined, + currStatus: string | undefined ) { let keyOptions; let valueOptions: string[]; @@ -127,8 +132,20 @@ function SideBarRow({ const [currStatus, setCurrStatus] = useState(undefined); + let key: + | { + id: keyof privacySettingsInterface; + name: string; + subtitle: string; + } + | { + id: keyof activitySettingsInterface; + name: string; + subtitle: string; + } + | undefined; + useEffect(() => { - let key; if (privacyStatus !== undefined) { key = statusMap.privacy[privacyStatus]; setCurrStatus(userData?.privacySettings?.[key.id] || "everyone"); @@ -147,6 +164,7 @@ function SideBarRow({ onClick={() => redirect && dispatch(updateSideBarView({ redirect, data })) } + data-testid={key && `menu-item-${key.id}`} > {renderedIcon} diff --git a/app/src/data/deviceSize.ts b/app/src/data/deviceSize.ts index 9240a7e5..17124266 100644 --- a/app/src/data/deviceSize.ts +++ b/app/src/data/deviceSize.ts @@ -1,6 +1,6 @@ const sizes = { mobile: "480px", - desktop: "1024px", + desktop: "600px", }; const media = { From 2ad0f67702e8fb995e30d02b78de37a38338c521 Mon Sep 17 00:00:00 2001 From: Amir Kedis <88613195+amir-kedis@users.noreply.github.com> Date: Sat, 2 Nov 2024 22:51:58 +0200 Subject: [PATCH 004/136] fix(profile-settings): fix integration with backend (#35) --- app/package-lock.json | 26 +++++++++++++++++++ app/package.json | 1 + app/src/App.tsx | 3 +++ .../profile-settings/ProfileSettings.tsx | 9 +++++-- .../hooks/useUpdateProfileSettings.ts | 4 +-- .../schema/EditProfileSchema.ts | 2 +- .../services/GetProfileSettings.ts | 18 +++++++------ .../services/UpdateProfileSettings.ts | 11 +++++++- 8 files changed, 60 insertions(+), 14 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 10f0c69d..2d67b3ce 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -21,6 +21,7 @@ "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.0", + "react-hot-toast": "^2.4.1", "react-international-phone": "^4.3.0", "react-router-dom": "^6.27.0", "redux-mock-store": "^1.5.5", @@ -6357,6 +6358,15 @@ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -9012,6 +9022,22 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "license": "MIT", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-international-phone": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/react-international-phone/-/react-international-phone-4.3.0.tgz", diff --git a/app/package.json b/app/package.json index c718252b..9f7d825b 100644 --- a/app/package.json +++ b/app/package.json @@ -27,6 +27,7 @@ "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.0", + "react-hot-toast": "^2.4.1", "react-international-phone": "^4.3.0", "react-router-dom": "^6.27.0", "redux-mock-store": "^1.5.5", diff --git a/app/src/App.tsx b/app/src/App.tsx index c6585bb2..e4680f34 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -2,6 +2,7 @@ import { useEffect } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { Toaster } from "react-hot-toast"; import GlobalStyles from "./styles/GlobalStyles"; @@ -32,6 +33,8 @@ function App() { return ( + + diff --git a/app/src/features/profile-settings/ProfileSettings.tsx b/app/src/features/profile-settings/ProfileSettings.tsx index 10148d57..207422d7 100644 --- a/app/src/features/profile-settings/ProfileSettings.tsx +++ b/app/src/features/profile-settings/ProfileSettings.tsx @@ -11,6 +11,7 @@ import { useProfileSettings } from "./hooks/useProfileSettings"; import { useUpdateProfileSettings } from "./hooks/useUpdateProfileSettings"; import { useEffect, useState } from "react"; import SettingsSideBarHeader from "@components/side-bar/settings/SettingsSideBarHeader"; +import toast from "react-hot-toast"; const SideBarContainer = styled.div` overflow-y: auto; @@ -152,6 +153,7 @@ function ProfileSettings() { reset, formState: { errors, isSubmitting, isDirty }, } = useForm({ + // @ts-expect-error - yupResolver type mismatch resolver: yupResolver(ValidationSchema), mode: "onChange", defaultValues: initialProfileSettings || { @@ -169,7 +171,7 @@ function ProfileSettings() { } }, [initialProfileSettings, reset]); - const userHandle = `https://telware.online/${watch("username") || "username"}`; + const userHandle = `https://telware.tech/${watch("username") || "username"}`; const handleImageUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; @@ -185,8 +187,11 @@ function ProfileSettings() { data.profilePicture = selectedImage; } updateProfileSettings(data); + toast.success("updated profile settings successfully"); } catch (error) { - console.error(error); + toast.error( + (error as Error).message || "Failed to update profile settings", + ); } }; const firstName = watch("firstName") || ""; diff --git a/app/src/features/profile-settings/hooks/useUpdateProfileSettings.ts b/app/src/features/profile-settings/hooks/useUpdateProfileSettings.ts index 5520b156..20fbb1f1 100644 --- a/app/src/features/profile-settings/hooks/useUpdateProfileSettings.ts +++ b/app/src/features/profile-settings/hooks/useUpdateProfileSettings.ts @@ -12,8 +12,8 @@ function useUpdateProfileSettings() { isPending, } = useMutation({ mutationFn: UpdateProfileSettingsAPI, - onSuccess: (data) => { - queryClient.setQueryData(["profileSettings"], data); + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["profileSettings"] }); }, }); diff --git a/app/src/features/profile-settings/schema/EditProfileSchema.ts b/app/src/features/profile-settings/schema/EditProfileSchema.ts index d7fb0e06..be7642f6 100644 --- a/app/src/features/profile-settings/schema/EditProfileSchema.ts +++ b/app/src/features/profile-settings/schema/EditProfileSchema.ts @@ -16,7 +16,7 @@ const ValidationSchema = yup.object({ .string() .required("Email is required") .email("Invalid email address"), - phone: yup.string().matches(/^[0-9]*$/, "Phone number must be numeric"), + phone: yup.string(), }); export { ValidationSchema, BIO_MAX_LENGTH }; diff --git a/app/src/features/profile-settings/services/GetProfileSettings.ts b/app/src/features/profile-settings/services/GetProfileSettings.ts index b4abe4f5..5177539c 100644 --- a/app/src/features/profile-settings/services/GetProfileSettings.ts +++ b/app/src/features/profile-settings/services/GetProfileSettings.ts @@ -15,15 +15,17 @@ async function GetProfileSettings() { throw new Error(data.message); } + const user = data.data?.user; + const profileSettings = { - profilePicture: data.data.photo, - firstName: data.data.firstName, - lastName: data.data.lastName, - bio: data.data.bio, - username: data.data.username, - email: data.data.email, - phone: data.data.phoneNumber, - lastSeen: data.data.status, + profilePicture: user?.photo, + firstName: user?.screenFirstName, + lastName: user?.screenLastName, + bio: user?.bio, + username: user?.username, + email: user?.email, + phone: user?.phoneNumber, + lastSeen: user?.status, }; return profileSettings; diff --git a/app/src/features/profile-settings/services/UpdateProfileSettings.ts b/app/src/features/profile-settings/services/UpdateProfileSettings.ts index 2ba986c3..d84335d6 100644 --- a/app/src/features/profile-settings/services/UpdateProfileSettings.ts +++ b/app/src/features/profile-settings/services/UpdateProfileSettings.ts @@ -2,12 +2,21 @@ import { API_URL } from "@constants"; import { EditProfileForm } from "../ProfileSettings"; async function UpdateProfileSettings(newProfileSettings: EditProfileForm) { + const formattedNewProfileSettings = { + screenFirstName: newProfileSettings.firstName, + screenLastName: newProfileSettings.lastName, + username: newProfileSettings.username, + bio: newProfileSettings.bio, + phoneNumber: newProfileSettings.phone, + email: newProfileSettings.email, + }; + const res = await fetch(`${API_URL}/users/me`, { method: "PATCH", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(newProfileSettings), + body: JSON.stringify(formattedNewProfileSettings), credentials: "include", }); From 483e97bd38538b048373a705a9eba991853d83ec Mon Sep 17 00:00:00 2001 From: Sarah Kamal <143711089+sarah-kamall@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:33:48 +0200 Subject: [PATCH 005/136] (fix) integration with backend signup (#36) * fix captcha and confirm password * bug fixes on sign up * fix signup integration * requested changess --------- Co-authored-by: sarah kamal --- app/package-lock.json | 5 ++- app/package.json | 2 +- app/src/data/icons.tsx | 4 +- .../login/LoginForm/LoginForm.tsx | 32 ++++++++++---- .../login/hooks/useAuthCheck.ts | 6 +-- .../reset-password/ResetPasswordModal.tsx | 5 +-- .../signup/ConfirmationEmailModal.tsx | 35 +++++++++------ .../authentication/signup/SignupForm.tsx | 43 +++++++------------ .../authentication/signup/schema/signup.ts | 2 +- .../signup/services/apiConfirmCode.ts | 2 +- .../signup/services/apiSignup.ts | 2 +- .../signup/services/apiVerfiyCode.ts | 6 +-- app/src/mocks/userauth/resetpassword.ts | 18 ++++---- app/src/mocks/userauth/signup.ts | 8 ++-- app/src/mocks/userauth/verfiyEmail.ts | 20 ++++----- app/src/pages/Signup.tsx | 4 +- 16 files changed, 103 insertions(+), 91 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 2d67b3ce..ed9bdba5 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -21,7 +21,6 @@ "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.0", - "react-hot-toast": "^2.4.1", "react-international-phone": "^4.3.0", "react-router-dom": "^6.27.0", "redux-mock-store": "^1.5.5", @@ -56,6 +55,7 @@ "jest-fixed-jsdom": "^0.0.7", "msw": "^2.4.11", "prettier": "^3.3.3", + "react-hot-toast": "^2.4.1", "react-redux": "9.1.2", "redux": "5.0.1", "ts-jest": "^29.2.5", @@ -6362,6 +6362,7 @@ "version": "2.1.16", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "dev": true, "license": "MIT", "peerDependencies": { "csstype": "^3.0.10" @@ -9026,7 +9027,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", - "license": "MIT", + "dev": true, "dependencies": { "goober": "^2.1.10" }, diff --git a/app/package.json b/app/package.json index 9f7d825b..957b8f5f 100644 --- a/app/package.json +++ b/app/package.json @@ -27,7 +27,6 @@ "react-dom": "^18.3.1", "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.0", - "react-hot-toast": "^2.4.1", "react-international-phone": "^4.3.0", "react-router-dom": "^6.27.0", "redux-mock-store": "^1.5.5", @@ -62,6 +61,7 @@ "jest-fixed-jsdom": "^0.0.7", "msw": "^2.4.11", "prettier": "^3.3.3", + "react-hot-toast": "^2.4.1", "react-redux": "9.1.2", "redux": "5.0.1", "ts-jest": "^29.2.5", diff --git a/app/src/data/icons.tsx b/app/src/data/icons.tsx index b6f7a7dc..dd162a8a 100644 --- a/app/src/data/icons.tsx +++ b/app/src/data/icons.tsx @@ -128,13 +128,13 @@ const iconMap: { [K in iconStrings]: React.ReactNode } = { ), Show: ( ), Hide: ( ), diff --git a/app/src/features/authentication/login/LoginForm/LoginForm.tsx b/app/src/features/authentication/login/LoginForm/LoginForm.tsx index fb831f3d..e78fcda7 100644 --- a/app/src/features/authentication/login/LoginForm/LoginForm.tsx +++ b/app/src/features/authentication/login/LoginForm/LoginForm.tsx @@ -13,6 +13,7 @@ import Button from "@components/Button"; import InputField from "@components/inputs/input-field/InputField"; import PasswordInputField from "@components/inputs/password-input-field/PasswordInputField"; import SpinnerMini from "@components/SpinnerMini"; +import ConfirmationEmailModal from "@features/authentication/signup/ConfirmationEmailModal"; const MAX_PASSWORD_LENGTH = 128; const MAX_EMAIL_LENGTH = 254; @@ -75,11 +76,10 @@ export default function LoginForm() { const { login, isPending } = useLogin(); const [error, setError] = useState(""); - const [isOpenModal, setIsOpenModal] = useState(false); - - function handleOpenModal() { - setIsOpenModal(true); - } + const [isOpenForgetPasswordModal, setIsOpenForgetPasswordModal] = + useState(false); + const [isOpenConfirmEmailModal, setIsOpenConfirmEmailModal] = useState(false); + const [email, setEmail] = useState(""); const { register, @@ -103,6 +103,14 @@ export default function LoginForm() { onSettled: (_, error) => { setError(error ? error.message : ""); }, + onError: (error) => { + setError(error ? error.message : ""); + const errorMessage = error.message.toLowerCase(); + if (errorMessage.includes("email") || errorMessage.includes("verify")) { + setIsOpenConfirmEmailModal(true); + setEmail(data.email); + } + }, onSuccess: () => { reset(); @@ -113,8 +121,13 @@ export default function LoginForm() { return ( <> setIsOpenModal(false)} + isOpen={isOpenForgetPasswordModal} + onClose={() => setIsOpenForgetPasswordModal(false)} + /> + setIsOpenConfirmEmailModal(false)} + email={email} />
@@ -139,7 +152,10 @@ export default function LoginForm() { /> - + setIsOpenForgetPasswordModal(true)} + > Forgot password? diff --git a/app/src/features/authentication/login/hooks/useAuthCheck.ts b/app/src/features/authentication/login/hooks/useAuthCheck.ts index 3fac0431..05ac92d5 100644 --- a/app/src/features/authentication/login/hooks/useAuthCheck.ts +++ b/app/src/features/authentication/login/hooks/useAuthCheck.ts @@ -14,11 +14,11 @@ const useAuthCheck = (path: Path) => { const res = await fetch(`${API_URL}/auth/me`, { method: "GET", credentials: "include", - }); + }); if (res.ok) { setIsAuthenticated(true); navigate("/", { replace: true }); - } else { + } else { setIsAuthenticated(false); navigate(path); } @@ -29,7 +29,7 @@ const useAuthCheck = (path: Path) => { } checkAuth(); - }, [navigate]); + }, [navigate, path]); return isAuthenticated; }; diff --git a/app/src/features/authentication/reset-password/ResetPasswordModal.tsx b/app/src/features/authentication/reset-password/ResetPasswordModal.tsx index c75e6ddd..f7df9059 100644 --- a/app/src/features/authentication/reset-password/ResetPasswordModal.tsx +++ b/app/src/features/authentication/reset-password/ResetPasswordModal.tsx @@ -70,6 +70,8 @@ const Inputs = styled.div` function ResetPasswordModal() { const { resetPassword, isPending, isSuccess, isError } = useResetPassword(); + const { token } = useParams(); + useAuthCheck(`/password-reset/${token}`); const navigate = useNavigate(); const { register, @@ -81,14 +83,11 @@ function ResetPasswordModal() { resolver: yupResolver(schema), }); - const { token } = useParams(); if (!token) { console.error("Token is missing in the URL."); return null; } - useAuthCheck(`/password-reset/${token}`); - const handleResetPassword = () => { resetPassword({ token: token!, diff --git a/app/src/features/authentication/signup/ConfirmationEmailModal.tsx b/app/src/features/authentication/signup/ConfirmationEmailModal.tsx index 2df1f0ab..3c15e73c 100644 --- a/app/src/features/authentication/signup/ConfirmationEmailModal.tsx +++ b/app/src/features/authentication/signup/ConfirmationEmailModal.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; import { UseVerifyEmail } from "./hooks/useVerifyEmail"; @@ -27,6 +27,11 @@ const ModalContainer = styled.div` text-align: center; width: 400px; `; +const CodeWrapper = styled.div` + display: flex; + gap: 0.5rem; + margin: 1rem 0; +`; const Button = styled.button<{ disabled?: boolean }>` border: none; @@ -50,7 +55,7 @@ const ModalTitle = styled.h2` `; const ModalMessage = styled.span` - margin-bottom: 2rem; + margin-bottom: 3em; color: var(--color-text); `; @@ -106,14 +111,20 @@ function ConfirmationEmailModal({ } = UseVerifyEmail(); const { SendConfirmationCode, isSuccess: isCodeRensent } = UseSendConfirmationEmail(); + const navigate = useNavigate(); + useEffect(() => { + SendConfirmationCode(email); + }, [email, SendConfirmationCode]); if (!isOpen) return null; function handleConfirmCode() { + setError(""); const code = codeDisplay.join(""); verifyCode( - { email, code }, + { email, verificationCode: code }, { onSuccess: () => { + onClose(); setTimeout(() => { navigate("/login", { replace: true }); }, 500); @@ -141,11 +152,13 @@ function ConfirmationEmailModal({ A confirmation email has been sent to your email address. Please check your inbox to verify your account. - + + + + - ) : ( - {initials} )} - {initials?.length == 0 && ( - - - - )} - - + <> + + + {getIcon("AddPhoto")} + + {selectedImageFile ? ( + + ) : !photoChanged && initialProfileSettings?.photo ? ( + + ) : ( + {initials} + )} + + id="firstName" label="First Name (required)" @@ -315,7 +399,7 @@ function ProfileSettings() { @@ -326,4 +410,3 @@ function ProfileSettings() { } export default ProfileSettings; -export type { EditProfileForm }; diff --git a/app/src/features/profile-settings/hooks/useDeleteProfilePicture.ts b/app/src/features/profile-settings/hooks/useDeleteProfilePicture.ts new file mode 100644 index 00000000..69019f49 --- /dev/null +++ b/app/src/features/profile-settings/hooks/useDeleteProfilePicture.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { DeleteProfilePicture as DeleteProfilePictureAPI } from "../services/DeleteProfilePicture"; + +function useDeleteProfilePicture() { + const queryClient = useQueryClient(); + + const { + mutate: deleteProfilePicture, + data, + error, + isPending, + } = useMutation({ + mutationFn: DeleteProfilePictureAPI, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["profilePicture"] }); + }, + }); + + return { deleteProfilePicture, data, error, isPending }; +} + +export { useDeleteProfilePicture }; diff --git a/app/src/features/profile-settings/hooks/useUpdateProfilePicture.ts b/app/src/features/profile-settings/hooks/useUpdateProfilePicture.ts new file mode 100644 index 00000000..514f02be --- /dev/null +++ b/app/src/features/profile-settings/hooks/useUpdateProfilePicture.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { UpdateProfilePicture as UpdateProfilePictureAPI } from "../services/UpdateProfilePicture"; + +function useUpdateProfilePicture() { + const queryClient = useQueryClient(); + + const { + mutate: updateProfilePicture, + data, + error, + isPending, + } = useMutation({ + mutationFn: UpdateProfilePictureAPI, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["profilePicture"] }); + }, + }); + + return { updateProfilePicture, data, error, isPending }; +} + +export { useUpdateProfilePicture }; diff --git a/app/src/features/profile-settings/services/DeleteProfilePicture.ts b/app/src/features/profile-settings/services/DeleteProfilePicture.ts new file mode 100644 index 00000000..1181b42d --- /dev/null +++ b/app/src/features/profile-settings/services/DeleteProfilePicture.ts @@ -0,0 +1,18 @@ +import { API_URL } from "@constants"; + +async function DeleteProfilePicture() { + const res = await fetch(`${API_URL}/users/picture`, { + method: "DELETE", + credentials: "include", + }); + + const data = await res.json(); + + if (data.status !== "success") { + throw new Error(data.message); + } + + return data; +} + +export { DeleteProfilePicture }; diff --git a/app/src/features/profile-settings/services/GetProfileSettings.ts b/app/src/features/profile-settings/services/GetProfileSettings.ts index 5177539c..705b6985 100644 --- a/app/src/features/profile-settings/services/GetProfileSettings.ts +++ b/app/src/features/profile-settings/services/GetProfileSettings.ts @@ -18,7 +18,7 @@ async function GetProfileSettings() { const user = data.data?.user; const profileSettings = { - profilePicture: user?.photo, + photo: user?.photo, firstName: user?.screenFirstName, lastName: user?.screenLastName, bio: user?.bio, diff --git a/app/src/features/profile-settings/services/UpdateProfilePicture.ts b/app/src/features/profile-settings/services/UpdateProfilePicture.ts new file mode 100644 index 00000000..c90fde4b --- /dev/null +++ b/app/src/features/profile-settings/services/UpdateProfilePicture.ts @@ -0,0 +1,26 @@ +import { API_URL } from "@constants"; + +async function UpdateProfilePicture(imageFile: File) { + const formData = new FormData(); + formData.append("file", imageFile); + + try { + const response = await fetch(`${API_URL}/users/picture`, { + method: "PATCH", + body: formData, + credentials: "include", + }); + + if (!response.ok) { + throw new Error("Failed to upload image"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Image upload error:", error); + throw error; + } +} + +export { UpdateProfilePicture }; diff --git a/app/src/features/stories/components/ContactStoryIcon.tsx b/app/src/features/stories/components/ContactStoryIcon.tsx new file mode 100644 index 00000000..86b6405a --- /dev/null +++ b/app/src/features/stories/components/ContactStoryIcon.tsx @@ -0,0 +1,80 @@ +import { STATIC_MEDIA_URL } from "@constants"; +import styled from "styled-components"; +import { story } from "types/story"; + +interface StoryIconProps { + isMe: boolean; + stories: story[]; + seenCounter: number; + avatar?: string; + name: string; +} + +const StyledContainer = styled.div<{ + $seenCounter: number; + $storiesCounter: number; + $isMe: boolean; +}>` + ${({ $isMe }) => $isMe && "align-self: flex-start;"} + display: flex; + flex-direction: column; + align-items: center; + padding: 0.5rem; + cursor: pointer; + position: relative; + + &:hover { + background-color: rgba(0, 0, 0, 0.1); + } + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + border: ${(props) => + props.$seenCounter === props.$storiesCounter + ? "2px solid gray" + : "2px solid #f39c12"}; + } +`; + +const StyledImage = styled.img` + width: 64px; + height: 64px; + border-radius: 50%; + object-fit: cover; + border: 2px solid white; +`; + +const StyledName = styled.p` + margin-top: 0.5rem; + font-size: 0.875rem; + color: #333; + text-align: center; +`; + +function ContactStoryIcon(props: StoryIconProps) { + const { isMe, stories, avatar, name } = props; + const storiesCounter = stories.length; + const seenCounter = stories.filter((story: story) => story.viewed).length; + + return ( + + + {isMe ? "Your Story" : name} + + ); +} + +export default ContactStoryIcon; diff --git a/app/src/hooks/useRedux.ts b/app/src/hooks/useRedux.ts deleted file mode 100644 index b6a474bf..00000000 --- a/app/src/hooks/useRedux.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { useDispatch, useSelector } from "react-redux"; -import { AppDispatch, RootState } from "../state/store"; - -export const useAppDispatch = useDispatch.withTypes(); -export const useAppSelector = useSelector.withTypes(); diff --git a/app/src/mocks/handlers.ts b/app/src/mocks/handlers.ts index 5ac1a4d8..c1bce5a1 100644 --- a/app/src/mocks/handlers.ts +++ b/app/src/mocks/handlers.ts @@ -7,6 +7,8 @@ import { logoutMock } from "./userauth/logout"; import { privacySettingsMock } from "./privacySettings"; import { OauthMock } from "./oauth/oauth"; import { profileSettingsMock } from "./profile-settings/profile-settings"; +import { profilePictureMock } from "./profile-settings/profile-picture"; +import { storiesMock } from "./stories/stories"; export default [ ...loginMock, @@ -19,4 +21,6 @@ export default [ ...privacySettingsMock, ...logoutMock, ...profileSettingsMock, + ...profilePictureMock, + ...storiesMock, ]; diff --git a/app/src/mocks/mockData.ts b/app/src/mocks/mockData.ts index 2fc13261..3bcd7097 100644 --- a/app/src/mocks/mockData.ts +++ b/app/src/mocks/mockData.ts @@ -5,11 +5,57 @@ export const MOCK_USER = { lastName: "Doe", bio: "Hello, I'm John Doe", photo: - "https://media-hbe1-1.cdn.whatsapp.net/v/t61.24694-24/462460819_518473281043631_6485009024565374350_n.jpg?ccb=11-4&oh=01_Q5AaINdhN3wt4c6ZnmGni8RNhM8fIvquSRicC2QT82X6ddeB&oe=6727186F&_nc_sid=5e03e0&_nc_cat=100", + "https://i.pinimg.com/564x/26/76/a1/2676a1898da6edae9fc648c94332903f.jpg", username: "johndoe", phoneNumber: "0123456789", status: "Online", }; +export const MOCK_MY_STORIES = [ + { + content: + "https://i.pinimg.com/564x/26/76/a1/2676a1898da6edae9fc648c94332903f.jpg", + timestamp: "2021-09-01T12:00:00Z", + caption: "Hello, I'm John Doe", + views: 100, + }, + { + content: "https://cdn.britannica.com/52/128652-050-14AD19CA/Maki-zushi.jpg", + timestamp: "2021-09-01T11:00:00Z", + caption: "I love sushi", + views: 10, + }, + { + content: + "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSyDcH_MxdsTsK6KMVon-Ybfa2WiT-R70ZjWw&s", + timestamp: "2021-09-01T10:00:00Z", + caption: "I love burgers", + views: 20, + }, +]; +export const MOCK_OTHER_USER_STORIES = [ + { + content: + "https://i.pinimg.com/564x/26/76/a1/2676a1898da6edae9fc648c94332903f.jpg", + timestamp: "2021-09-01T12:00:00Z", + caption: "Hello, I'm John Doe", + viewed: true, + }, + { + content: + "data:image/jpeg;base64,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", + timestamp: "2021-09-01T11:00:00Z", + caption: "I love pizza", + viewed: false, + }, + { + content: + "https://mcprod.hyperone.com.eg/media/catalog/product/cache/1ca275941aea0ae98b372dcb44b7c67a/6/2/6224011241366-300ml.jpg", + timestamp: "2021-09-01T10:00:00Z", + caption: "I love cola", + viewed: false, + }, +]; + export const TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik1vY2tVc2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; diff --git a/app/src/mocks/profile-settings/profile-picture.ts b/app/src/mocks/profile-settings/profile-picture.ts new file mode 100644 index 00000000..97dee514 --- /dev/null +++ b/app/src/mocks/profile-settings/profile-picture.ts @@ -0,0 +1,51 @@ +import { STATIC_MEDIA_URL } from "@constants"; +import { http, HttpResponse } from "msw"; + +export const profilePictureMock = [ + http.get(/.*\.(png|jpg|jpeg|gif|svg)$/, async () => { + return undefined; + }), + + http.delete("/users/picture", async () => { + return HttpResponse.json( + { + status: "success", + data: {}, + }, + { status: 200 } + ); + }), + http.patch("/users/picture", async ({ request }) => { + const formData = await request.formData(); + const file = formData.get("file"); + + if (file instanceof File && /\.(png|jpg|jpeg|gif|svg)$/i.test(file.name)) { + return HttpResponse.json( + { + status: "success", + message: "Image uploaded successfully", + data: { + imageUrl: `${STATIC_MEDIA_URL}/${file.name}`, + }, + }, + { + status: 201, + headers: { + "Content-Type": "application/json", + }, + } + ); + } else { + return HttpResponse.json( + { + status: "fail", + message: "Invalid file type or missing file", + data: {}, + }, + { + status: 400, + } + ); + } + }), +]; diff --git a/app/src/mocks/profile-settings/profile-settings.ts b/app/src/mocks/profile-settings/profile-settings.ts index 0d435cb6..f81d39be 100644 --- a/app/src/mocks/profile-settings/profile-settings.ts +++ b/app/src/mocks/profile-settings/profile-settings.ts @@ -12,19 +12,18 @@ export const profileSettingsMock = [ status: "success", data: MOCK_USER, }, - { status: 200 }, + { status: 200 } ); }), http.patch("/users/me", async ({ request }) => { const newProfileSettings = await request.json(); - return HttpResponse.json( { status: "success", data: newProfileSettings, }, - { status: 200 }, + { status: 200 } ); }), ]; diff --git a/app/src/mocks/stories/stories.ts b/app/src/mocks/stories/stories.ts new file mode 100644 index 00000000..b5b53ac4 --- /dev/null +++ b/app/src/mocks/stories/stories.ts @@ -0,0 +1,139 @@ +import { http, HttpResponse } from "msw"; +import { MOCK_MY_STORIES, MOCK_OTHER_USER_STORIES } from "@mocks/mockData"; + +type AddStoryRequestBody = { + file: File; + caption: string; +}; + +type AddStoryResponseBodySuccess = { + status: "success"; + message: string; + data: { + user: { + id: string; + name: string; + avatarUrl: string; + }; + sessionID: string; + }; +}; + +type AddStoryResponseBodyFail = { + status: "fail" | "error"; + message: string; + data: {}; +}; + +type AddStoryResponseBody = + | AddStoryResponseBodySuccess + | AddStoryResponseBodyFail; + +export const storiesMock = [ + http.get(/.*\.(png|jpg|jpeg|gif|svg)$/, async () => { + return undefined; + }), + + http.get("/users/stories", async () => { + return HttpResponse.json( + { + status: "success", + data: MOCK_MY_STORIES, + }, + { status: 200 } + ); + }), + + http.post<{}, AddStoryRequestBody, AddStoryResponseBody>( + "/users/stories", + async ({ request }) => { + const { file, caption } = await request.json(); + + if (file && caption) { + return HttpResponse.json( + { + status: "success", + message: "Successful Add Story", + data: { + user: { + id: "12345", + name: "John Doe", + avatarUrl: "/avatars/john_doe.png", + }, + sessionID: "abcdef123456", + }, + }, + { + status: 201, + headers: { + "Content-Type": "application/json", + }, + } + ); + } else { + return HttpResponse.json( + { + status: "fail", + message: "File or caption missing", + data: {}, + }, + { + status: 400, + } + ); + } + } + ), + + http.delete("/users/stories/:storyId", async ({ params }) => { + const { storyId } = params; + if (storyId) { + return HttpResponse.json( + { + status: "success", + message: "Story deleted", + data: {}, + }, + { + status: 200, + } + ); + } else { + return HttpResponse.json( + { + status: "fail", + message: "Story ID missing", + data: {}, + }, + { + status: 400, + } + ); + } + }), + http.get("/users/:userId/stories", async ({ params }) => { + const { userId } = params; + if (userId) { + return HttpResponse.json( + { + status: "success", + data: MOCK_OTHER_USER_STORIES, + }, + { + status: 200, + } + ); + } else { + return HttpResponse.json( + { + status: "fail", + message: "User ID missing", + data: {}, + }, + { + status: 400, + } + ); + } + }), +]; diff --git a/app/src/styles/GlobalStyles.tsx b/app/src/styles/GlobalStyles.tsx index fb428979..7f6e2b1d 100644 --- a/app/src/styles/GlobalStyles.tsx +++ b/app/src/styles/GlobalStyles.tsx @@ -156,7 +156,9 @@ const GlobalStyles = createGlobalStyle` --color-borders-input: rgb(218, 220, 224); + --color-error: #e53935; + --color-error-shade: #c62828; --color-success: rgb(0, 199, 62); font-size: 16px; diff --git a/app/src/types/story.ts b/app/src/types/story.ts new file mode 100644 index 00000000..5f1e552f --- /dev/null +++ b/app/src/types/story.ts @@ -0,0 +1,13 @@ +import { otherUserInfoInterface } from "./user"; + +interface story { + id: string; + user: otherUserInfoInterface; + content: string; + timestamp: string; + caption?: string; + views?: otherUserInfoInterface[]; + viewed?: boolean; +} + +export type { story }; diff --git a/app/src/types/user.ts b/app/src/types/user.ts index f8ccef62..830d245d 100644 --- a/app/src/types/user.ts +++ b/app/src/types/user.ts @@ -1,4 +1,11 @@ -import { privacyStates, activeStates, permissionStates, permissionStatesStrings, activeStatesStrings, privacyStatesStrings } from "./sideBar"; +import { + privacyStates, + activeStates, + permissionStates, + permissionStatesStrings, + activeStatesStrings, + privacyStatesStrings, +} from "./sideBar"; interface privacySettingsInterface { storiesSeenPrivacy: privacyStates; @@ -38,6 +45,16 @@ interface updateActivityInterface { key: keyof activitySettingsInterface; value: activeStatesStrings; } +interface otherUserInfoInterface { + username: string; + phoneNumber: string; + screenFirstName: string; + screenLastName: string; + email: string; + photo: string | undefined; + status: string; + bio: string; +} interface updatePermissionInterface { key: keyof permissionsSettingsInterface; @@ -52,6 +69,9 @@ export type { updateInfoInterface, updatePrivacyInterface, userInfoInterface, + otherUserInfoInterface, permissionsSettingsInterface, updatePermissionInterface, + privacyStatesStrings, + activeStatesStrings, }; From 645730cf77a80d2775e90ec52d88227f97777065 Mon Sep 17 00:00:00 2001 From: Asmaa Abozaid <130288326+Asmaa-204@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:37:56 +0200 Subject: [PATCH 010/136] =?UTF-8?q?=F0=9F=8E=89(oauth):=20Integrated=20wit?= =?UTF-8?q?h=20backend=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 💄 (messaging): Added chats layout (No messages) * 🐛 (protecting routes): Fix not navigating to chat * ♻️ (protected routes): Refacored protecting routes * ♻️ (chats): Refactored chats * ♻️ (aouth): Refactored aouth depending on backend changes * 🐛 (oauth): Fix request type in google oauth * 🎉 (oauth): Integrated with backend --- app/src/App.tsx | 5 +- app/src/components/AppLayout.tsx | 42 +++++-- app/src/components/ExpandingTextArea.tsx | 43 +++++++ app/src/components/Icon.tsx | 25 ++++ app/src/components/Main.tsx | 2 +- .../protected-route/ProtectedRoute.tsx | 21 +++- app/src/components/side-bar/SideBar.tsx | 29 ++++- .../side-bar/chats/ChatsSideBarHeader.tsx | 8 +- app/src/data/deviceSize.ts | 2 +- app/src/data/icons.tsx | 67 ++++++++++ app/src/features/Chats/Avatar.tsx | 35 ++++++ app/src/features/Chats/ChatBox.tsx | 17 +++ app/src/features/Chats/ChatInput.tsx | 84 +++++++++++++ app/src/features/Chats/ChatItem.tsx | 87 +++++++++++++ app/src/features/Chats/ChatsList.tsx | 26 ++++ app/src/features/Chats/Topbar.tsx | 84 +++++++++++++ app/src/features/Chats/hooks/useChat.ts | 16 +++ app/src/features/Chats/hooks/useChats.ts | 12 ++ .../features/Chats/services/apiGetChats.ts | 37 ++++++ .../login/LoginForm/LoginForm.tsx | 5 +- .../authentication/login/LoginSection.tsx | 2 +- .../authentication/login/OauthOptions.tsx | 79 ------------ .../login/hooks/useAuthCheck.ts | 34 +---- .../login/hooks/useAuthStatus.ts | 12 ++ .../authentication/login/hooks/useLogin.ts | 4 +- .../login/services/checkAuth.ts | 13 ++ .../authentication/logout/hooks/useLogout.ts | 1 + .../{login => oauth}/LoginWith.test.tsx | 2 +- .../{login => oauth}/LoginWith.tsx | 0 .../authentication/oauth/OauthOptions.tsx | 50 ++++++++ .../authentication/oauth/hooks/useOauth.ts | 15 +++ .../oauth/hooks/useOauthGoogle.ts | 19 --- .../{useOauthGithub.ts => useOauthLogin.ts} | 12 +- .../oauth/services/apiGithubOauth.ts | 17 --- .../oauth/services/apiGoogleOauth.ts | 17 --- .../authentication/oauth/services/apiOauth.ts | 10 ++ .../authentication/oauth/services/apiUser.ts | 16 +++ .../reset-password/ResetPasswordModal.tsx | 10 +- .../authentication/signup/SignupForm.tsx | 4 + .../authentication/signup/SignupSection.tsx | 1 + app/src/hooks/useCloseChat.ts | 20 +++ app/src/mocks/chats/chat.ts | 26 ++++ app/src/mocks/data/Chats.ts | 119 ++++++++++++++++++ app/src/mocks/handlers.ts | 2 + app/src/mocks/userauth/resetpassword.ts | 4 +- app/src/mocks/userauth/signup.ts | 2 +- app/src/mocks/userauth/verfiyEmail.ts | 4 +- app/src/pages/Login.tsx | 3 - app/src/pages/Signup.tsx | 3 - app/src/styles/GlobalStyles.tsx | 43 +++++-- 50 files changed, 970 insertions(+), 221 deletions(-) create mode 100644 app/src/components/ExpandingTextArea.tsx create mode 100644 app/src/components/Icon.tsx create mode 100644 app/src/features/Chats/Avatar.tsx create mode 100644 app/src/features/Chats/ChatBox.tsx create mode 100644 app/src/features/Chats/ChatInput.tsx create mode 100644 app/src/features/Chats/ChatItem.tsx create mode 100644 app/src/features/Chats/ChatsList.tsx create mode 100644 app/src/features/Chats/Topbar.tsx create mode 100644 app/src/features/Chats/hooks/useChat.ts create mode 100644 app/src/features/Chats/hooks/useChats.ts create mode 100644 app/src/features/Chats/services/apiGetChats.ts delete mode 100644 app/src/features/authentication/login/OauthOptions.tsx create mode 100644 app/src/features/authentication/login/hooks/useAuthStatus.ts create mode 100644 app/src/features/authentication/login/services/checkAuth.ts rename app/src/features/authentication/{login => oauth}/LoginWith.test.tsx (94%) rename app/src/features/authentication/{login => oauth}/LoginWith.tsx (100%) create mode 100644 app/src/features/authentication/oauth/OauthOptions.tsx create mode 100644 app/src/features/authentication/oauth/hooks/useOauth.ts delete mode 100644 app/src/features/authentication/oauth/hooks/useOauthGoogle.ts rename app/src/features/authentication/oauth/hooks/{useOauthGithub.ts => useOauthLogin.ts} (57%) delete mode 100644 app/src/features/authentication/oauth/services/apiGithubOauth.ts delete mode 100644 app/src/features/authentication/oauth/services/apiGoogleOauth.ts create mode 100644 app/src/features/authentication/oauth/services/apiOauth.ts create mode 100644 app/src/features/authentication/oauth/services/apiUser.ts create mode 100644 app/src/hooks/useCloseChat.ts create mode 100644 app/src/mocks/chats/chat.ts create mode 100644 app/src/mocks/data/Chats.ts diff --git a/app/src/App.tsx b/app/src/App.tsx index 7e07bbe8..88909013 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -14,6 +14,7 @@ import Signup from "./pages/Signup"; import ResetPasswordModal from "@features/authentication/reset-password/ResetPasswordModal"; import ProtectedRoute from "@components/protected-route/ProtectedRoute"; import AppLayout from "@components/AppLayout"; +import ChatBox from "@features/Chats/ChatBox"; const queryClient = new QueryClient({ defaultOptions: { @@ -45,7 +46,9 @@ function App() { } - /> + > + } /> + } /> ` + @media ${MOBILE_VIEW} { + & > main { + display: ${({ $isChatOpen }) => ($isChatOpen ? "contents" : "none")}; + } - & > main { - display: block; + & > aside { + display: ${({ $isChatOpen }) => ($isChatOpen ? "none" : "contents")}; + } } - @media ${MOBILE_VIEW} { - display: block; + + @media ${media.desktop} { + display: grid; + grid-template-columns: 2fr 5fr; + & > main { - display: none; + display: block; + } + + & > aside { + display: block; } } `; + function AppLayout() { + const { chatId } = useParams(); + + const isChatOpen = !!chatId; + return ( - + -
+
+ +
); } diff --git a/app/src/components/ExpandingTextArea.tsx b/app/src/components/ExpandingTextArea.tsx new file mode 100644 index 00000000..50379a70 --- /dev/null +++ b/app/src/components/ExpandingTextArea.tsx @@ -0,0 +1,43 @@ +import { useRef } from "react"; +import styled from "styled-components"; + +const Textarea = styled.textarea` + outline: none; + border: none; + + flex: 1; + align-self: center; + + caret-color: var(--accent-color); + color: var(--color-text); + + resize: none; + overflow: hidden; + + font-size: 1rem; + line-height: 1.5; + + max-height: 300px; +`; + +function ExpandingTextArea() { + const ref = useRef(null); + + function handleInput() { + if (ref.current) { + ref.current.style.height = "auto"; + ref.current.style.height = `${ref.current.scrollHeight}px`; + } + } + + return ( +