diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..48667da --- /dev/null +++ b/.babelrc @@ -0,0 +1,17 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current", + }, + }, + ], + ["@babel/preset-react", { + "runtime": "automatic" + }], + "@babel/preset-typescript" + ], + "plugins": ["babel-plugin-transform-import-meta"] +} diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..233e901 --- /dev/null +++ b/.env.development @@ -0,0 +1,10 @@ +VITE_API_ORIGIN = https://admin.dev.dipal.ru/api/v1 +VITE_API_AUTH_ORIGIN = https://auth.dev.dipal.ru/api/v1/auth +VITE_API_AUTH_PHONENUMBER = /start-challenge +VITE_API_AUTH_LOGIN = /login +VITE_API_AUTH_REFRESH = /refresh-token +VITE_API_CREATE_MEMBER = /user_place/members +VITE_API_PLACES = /place +VITE_API_USERS = /profile +VITE_API_USERS_ACCOUNT = /account +VITE_API_USERS_PROFILE = /profile \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e0b8844 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,11 @@ +/node_modules +/public +/dist +vite.config.ts +.eslintrc.js +/build +/src/vite-env.d.ts +tailwind.config.cjs +postcss.config.cjs +fileMock.js +jest.config.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6028df8 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,83 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "settings": { + "react": { + "version": "detect" + }, + "import/resolver": { + "alias": { + "map": [["~", "./src"]], + "extensions": [".js",".ts",".tsx", ".d.ts", ".json"] + } + } + }, + "extends": [ + "airbnb", + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/recommended", + "plugin:jest/recommended", + "plugin:prettier/recommended", + "plugin:@typescript-eslint/recommended" + ], + "overrides": [], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["react", "@typescript-eslint", "jest", "prettier"], + "rules": { + "object-curly-spacing": ["error", "always"], + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], + "class-methods-use-this": "off", + "import/no-import-module-exports": "off", + "react/require-default-props": "off", + "import/prefer-default-export": "off", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/no-static-element-interactions": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-shadow": "error", + "no-shadow": "off", + "no-return-await": "off", + "import/order": [ + "error", + { + "pathGroups": [ + { + "pattern": "~/**", + "group": "external" + } + ] + } + ], + "prettier/prettier": [ + "error", + { + "usePrettierrc": true, + "jsxSingleQuote": true, + "singleQuote": true, + "endOfLine": "auto" + + } + ], + "jest/valid-title": "off", + "react/button-has-type": "off", + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx", ".ts", ".tsx"] }], + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-namespace": "off" + } +} diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..fdc2655 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm run test && npm run lint +npm run test && npm run lint diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..e54d85c --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,13 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "endOfLine":"auto", + "useTabs": false, + "semi": true, + "singleQuote": true, + "quoteProps": "as-needed", + "jsxSingleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always" +} diff --git a/jest.config.js b/jest.config.js index ee178f8..e84dfbe 100644 --- a/jest.config.js +++ b/jest.config.js @@ -8,5 +8,7 @@ export default { "\\.(css|less)$": "identity-obj-proxy", "^~(.*)$": "<rootDir>/src$1" }, - transformIgnorePatterns: ['<rootDir>/node_modules/'], + transformIgnorePatterns: [ + "node_modules/(?!(spacetime)/)" + ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 39ae599..78b9b10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7704,6 +7704,14 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "swr": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.1.5.tgz", + "integrity": "sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==", + "requires": { + "use-sync-external-store": "^1.2.0" + } + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", diff --git a/package.json b/package.json index 0dd2f09..223f7a6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "preview": "vite preview", "lint": "eslint --fix --ignore-path .eslintignore --ignore-pattern \"!**/.*\" .", - "prepare": "husky install", + "prepare": "husky install && husky add .husky/pre-push \"npm run test && npm run lint\"", "test": "jest", "tsc": "tsc" }, @@ -18,16 +18,17 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.5", - "react-router-dom": "^6.9.0" + "react-router-dom": "^6.9.0", + "swr": "^2.1.5" }, "devDependencies": { - "@babel/preset-env": "^7.20.2", + "@babel/preset-env": "^7.21.5", "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", + "@babel/preset-typescript": "^7.21.5", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.4.0", + "@types/jest": "^29.5.1", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", "@typescript-eslint/eslint-plugin": "^5.54.1", diff --git a/public/assets/icons/logo-black.svg b/public/assets/icons/logo-black.svg new file mode 100644 index 0000000..1299dee --- /dev/null +++ b/public/assets/icons/logo-black.svg @@ -0,0 +1,15 @@ +<svg width="198" height="48" viewBox="0 0 198 48" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M29.2175 33.8487H23.577C21.0802 33.8442 18.6869 32.8468 16.9213 31.075C15.1558 29.3032 14.162 26.9014 14.1575 24.3957V18.7353L13.255 17.8296C12.5503 17.1065 11.7076 16.5333 10.7773 16.1443C9.84695 15.7553 8.84804 15.5584 7.84021 15.5654C6.83237 15.5584 5.83346 15.7553 4.90313 16.1443C3.97279 16.5333 3.13011 17.1065 2.42539 17.8296L2.25617 17.9428C1.54128 18.6542 0.973841 19.5007 0.586605 20.4334C0.199369 21.3661 0 22.3664 0 23.3768C0 24.3872 0.199369 25.3876 0.586605 26.3203C0.973841 27.2529 1.54128 28.0994 2.25617 28.8109L19.1211 45.7356C20.5661 47.1651 22.507 47.9767 24.5359 47.9998C25.5437 48.0068 26.5426 47.8099 27.473 47.4209C28.4033 47.0319 29.246 46.4587 29.9507 45.7356L30.0635 45.6224C31.4998 44.1699 32.3058 42.2065 32.3058 40.1601C32.3058 38.1137 31.4998 36.1503 30.0635 34.6977L29.2175 33.8487Z" fill="#9E00C5"/> +<path d="M40.2726 16.528H33.1657V24.396C33.1584 25.9067 32.7911 27.3938 32.0944 28.733C31.3978 30.0722 30.3919 31.2247 29.1609 32.0943H40.329C42.363 32.0928 44.3133 31.2812 45.7516 29.8378C47.1898 28.3945 47.9985 26.4373 48 24.396V24.2262C47.9749 22.1826 47.1509 20.2306 45.7056 18.7907C44.2602 17.3508 42.3092 16.5382 40.2726 16.528Z" fill="#9E00C5"/> +<path d="M18.162 18.7924C17.4415 19.4996 16.8704 20.3453 16.4827 21.2789C16.0951 22.2125 15.8989 23.215 15.9058 24.2264V24.3962C15.9073 26.4374 16.716 28.3947 18.1543 29.838C19.5925 31.2814 21.5428 32.0929 23.5768 32.0944H23.746C25.7801 32.0929 27.7303 31.2814 29.1686 29.838C30.6069 28.3947 31.4156 26.4374 31.417 24.3962V16.5282H23.5768C22.569 16.5212 21.5701 16.7181 20.6398 17.1071C19.7094 17.4961 18.8667 18.0693 18.162 18.7924Z" fill="#9E00C5"/> +<path d="M16.9778 17.604C18.7246 15.8387 21.0981 14.8412 23.5771 14.8304H31.4173V7.69822C31.4158 5.65699 30.6072 3.69978 29.1689 2.25641C27.7306 0.813041 25.7804 0.00149926 23.7463 0H23.5771C21.5431 0.00149926 19.5928 0.813041 18.1546 2.25641C16.7163 3.69978 15.9076 5.65699 15.9061 7.69822V18.9059C16.2004 18.4234 16.5612 17.9851 16.9778 17.604Z" fill="#9E00C5"/> +<path d="M55 16.7575H57.7267V31.1991H55V16.7575ZM65.3874 31.1991L59.3281 25.6811C59.0732 25.429 58.8757 25.1254 58.7488 24.7907C58.6218 24.456 58.5685 24.0981 58.5923 23.7412C58.6196 23.1431 58.8659 22.5756 59.2848 22.1461L64.7815 16.7144H68.2006L61.9249 22.6635C61.5787 23.0083 61.2324 23.2239 61.2324 23.7412C61.2324 24.3447 61.6652 24.7327 62.0981 25.0776L68.9797 31.1991H65.3874Z" fill="black"/> +<path d="M83.3027 24C83.3027 29.8629 81.2685 31.4148 75.8584 31.4148C70.4483 31.4148 68.4141 29.8629 68.4141 24C68.4141 18.1371 70.4915 16.5852 75.8584 16.5852C81.2252 16.5852 83.3027 18.1371 83.3027 24ZM75.8584 29.2163C79.4074 29.2163 80.4894 28.1385 80.4894 24C80.4894 19.8615 79.4074 18.7838 75.8584 18.7838C72.3093 18.7838 71.2273 19.8615 71.2273 24C71.2273 28.1385 72.2661 29.2163 75.8584 29.2163Z" fill="black"/> +<path d="M96.2439 29.6474C95.8111 30.7682 95.4216 31.3717 94.1665 31.3717C92.9113 31.3717 92.5218 30.7682 92.089 29.6474L88.4101 19.9908C88.3668 19.8184 88.1937 19.5166 88.0639 19.5166C87.8475 19.5166 87.8475 19.9046 87.8475 19.9477L87.8908 31.1993H85.1641V18.482C85.1641 17.1456 86.073 16.5852 87.5012 16.5852C89.1459 16.5852 89.6653 17.4474 90.0548 18.4389L93.8202 28.0954C93.9501 28.3972 94.0799 28.6127 94.2097 28.6127C94.3396 28.6127 94.4694 28.3972 94.5993 28.0954L98.3647 18.4389C98.7542 17.4474 99.2736 16.5852 100.918 16.5852C102.39 16.5852 103.255 17.1456 103.255 18.482V31.1993H100.529L100.615 19.9477C100.615 19.5598 100.442 19.5166 100.356 19.5166C100.269 19.5166 100.053 19.8184 100.009 19.9908L96.2439 29.6474Z" fill="black"/> +<path d="M115.163 16.7578V17.9218H117.63C121.785 17.9218 122.607 20.1203 122.607 23.8278C122.607 27.5352 121.742 29.7768 117.63 29.7768H115.163V31.1995H112.436V29.7768H109.969C105.815 29.7768 104.992 27.5352 104.992 23.8278C104.992 20.1203 105.815 17.9218 109.969 17.9218H112.436V16.7578H115.163ZM112.436 20.1203H110.792C108.282 20.1203 107.805 21.155 107.805 23.8709C107.805 26.5868 108.282 27.7076 110.792 27.7076H112.436V20.1203ZM116.808 27.6645C119.318 27.6645 119.794 26.5436 119.794 23.8278C119.794 21.1119 119.318 20.0772 116.808 20.0772H115.163V27.6214L116.808 27.6645Z" fill="black"/> +<path d="M138.967 24C138.967 29.8629 136.933 31.4148 131.522 31.4148C126.112 31.4148 124.078 29.8629 124.078 24C124.078 18.1371 126.156 16.5852 131.522 16.5852C136.889 16.5852 138.967 18.1371 138.967 24ZM131.522 29.2163C135.071 29.2163 136.153 28.1385 136.153 24C136.153 19.8615 135.071 18.7838 131.522 18.7838C127.973 18.7838 126.891 19.8615 126.891 24C126.891 28.1385 127.93 29.2163 131.522 29.2163Z" fill="black"/> +<path d="M140.781 18.9133C140.781 17.2751 141.647 16.7578 142.902 16.7578H150.13C153.246 16.7578 153.809 18.9133 153.809 21.4136C153.809 23.914 153.246 26.1126 150.13 26.1126H143.551V31.1995H140.825V18.9133H140.781ZM148.745 23.914C150.606 23.914 150.952 23.0518 150.952 21.4136C150.952 19.7755 150.606 18.9564 148.745 18.9564H144.547C143.724 18.9564 143.508 19.1719 143.508 19.991V23.9571H148.745V23.914Z" fill="black"/> +<path d="M160.256 31.1995V18.9133H154.586V16.7578H168.652V18.9133H162.982V31.1995H160.256Z" fill="black"/> +<path d="M181.557 31.1995H172.035C170.78 31.1995 169.914 30.6821 169.914 29.044V18.9133C169.914 17.2751 170.78 16.7578 172.035 16.7578H181.47V18.9133H173.679C172.857 18.9133 172.641 19.1288 172.641 19.9479V22.75H181.254V24.9055H172.641V28.0094C172.641 28.8284 172.857 29.044 173.679 29.044H181.557V31.1995Z" fill="black"/> +<path d="M187.958 17.9647C188.218 17.3181 188.651 16.5852 190.166 16.5852C191.68 16.5852 192.113 17.275 192.373 17.9647L197.999 31.1993H195.1L190.512 19.646C190.469 19.4735 190.425 19.2149 190.166 19.2149C189.906 19.2149 189.863 19.4735 189.819 19.646L185.275 31.1993H182.375L187.958 17.9647Z" fill="black"/> +</svg> diff --git a/src/business-logic/core/places/common/entity/placeEntity.ts b/src/business-logic/core/places/common/entity/placeEntity.ts new file mode 100644 index 0000000..b171c44 --- /dev/null +++ b/src/business-logic/core/places/common/entity/placeEntity.ts @@ -0,0 +1,9 @@ +type Places = { + placeType: string; + name: string; + qr: null | string; + id: string; + parentId: string | null; +}; + +export default Places; diff --git a/src/business-logic/core/places/common/model/placesModel.ts b/src/business-logic/core/places/common/model/placesModel.ts new file mode 100644 index 0000000..9b73779 --- /dev/null +++ b/src/business-logic/core/places/common/model/placesModel.ts @@ -0,0 +1,21 @@ +import Places from '../entity/placeEntity'; + +class PlacesModel { + private placesList: Places[]; + + private modelTitle = 'places'; + + constructor(data: Places[]) { + this.placesList = data; + } + + getData(): Places[] { + return this.placesList; + } + + getTitle(): string { + return this.modelTitle; + } +} + +export default PlacesModel; diff --git a/src/business-logic/core/places/get-places/data/repository/GetPlacesRepo.ts b/src/business-logic/core/places/get-places/data/repository/GetPlacesRepo.ts new file mode 100644 index 0000000..dd35037 --- /dev/null +++ b/src/business-logic/core/places/get-places/data/repository/GetPlacesRepo.ts @@ -0,0 +1,16 @@ +import PlacesModel from '../../../common/model/placesModel'; +import getPlacesRO from '../response-object/getPlacesRO'; +import { GetPlacesResponse } from '../response-object/protocols'; + +const getPlacesRepo = async (httpHandler: () => Promise<GetPlacesResponse>) => { + // call httpHandler + const placesResponse = await httpHandler(); + // user response object to turn it to the data that we want + const placesData = getPlacesRO(placesResponse); + // make model + const placesModel = new PlacesModel(placesData); + // return the model + return placesModel; +}; + +export default getPlacesRepo; diff --git a/src/business-logic/core/places/get-places/data/repository/IGetPlacesRepo.ts b/src/business-logic/core/places/get-places/data/repository/IGetPlacesRepo.ts new file mode 100644 index 0000000..86d6e71 --- /dev/null +++ b/src/business-logic/core/places/get-places/data/repository/IGetPlacesRepo.ts @@ -0,0 +1,5 @@ +import PlacesModel from '../../../common/model/placesModel'; + +type IGetPlacesRepo = () => Promise<PlacesModel>; + +export default IGetPlacesRepo; diff --git a/src/business-logic/core/places/get-places/data/response-object/getPlacesRO.ts b/src/business-logic/core/places/get-places/data/response-object/getPlacesRO.ts new file mode 100644 index 0000000..1b63fe6 --- /dev/null +++ b/src/business-logic/core/places/get-places/data/response-object/getPlacesRO.ts @@ -0,0 +1,19 @@ +/* eslint-disable no-underscore-dangle */ +import { GetPlacesRO, GetPlacesResponse } from './protocols'; + +const getPlacesRO = (placesResponse: GetPlacesResponse): GetPlacesRO => { + return placesResponse.map((placeResponse) => { + return { + id: placeResponse._id, + placeType: placeResponse.place_type, + name: placeResponse.name, + parentId: placeResponse.place_type, + availableServices: placeResponse.available_services, + createdAt: placeResponse.createdAt, + updatedAt: placeResponse.updatedAt, + qr: null, + }; + }); +}; + +export default getPlacesRO; diff --git a/src/business-logic/core/places/get-places/data/response-object/protocols.ts b/src/business-logic/core/places/get-places/data/response-object/protocols.ts new file mode 100644 index 0000000..7455c7c --- /dev/null +++ b/src/business-logic/core/places/get-places/data/response-object/protocols.ts @@ -0,0 +1,18 @@ +import Places from '../../../common/entity/placeEntity'; + +export type GetPlacesResponse = { + _id: string; + place_type: string; + name: string; + parent_id: string | null; + createdAt: string; + updatedAt: string; + available_services: string[]; +}[]; + +export type GetPlacesRO = Places[] & + { + availableServices: string[]; + createdAt: string; + updatedAt: string; + }[]; diff --git a/src/business-logic/core/places/get-places/index.ts b/src/business-logic/core/places/get-places/index.ts new file mode 100644 index 0000000..505e616 --- /dev/null +++ b/src/business-logic/core/places/get-places/index.ts @@ -0,0 +1,3 @@ +import getPlaces from './infra/getPlacesInfra'; + +export default getPlaces; diff --git a/src/business-logic/core/places/get-places/infra/getPlacesInfra.ts b/src/business-logic/core/places/get-places/infra/getPlacesInfra.ts new file mode 100644 index 0000000..c89093a --- /dev/null +++ b/src/business-logic/core/places/get-places/infra/getPlacesInfra.ts @@ -0,0 +1,15 @@ +import getPlacesRepo from '../data/repository/GetPlacesRepo'; +import IGetPlacesRepo from '../data/repository/IGetPlacesRepo'; +import GettingPlacesUsecase from '../usecase/getPlaceUsecase'; +import { IgetPlacesInfra, getPlacesReturnType } from './protocols'; + +const getPlaces = ({ httpHandler }: IgetPlacesInfra): getPlacesReturnType => { + // get httpHandler + const repository: IGetPlacesRepo = () => getPlacesRepo(httpHandler); + // connet usecase and repository + const usecase = new GettingPlacesUsecase(repository); + // return method to use + return () => usecase.execute(); +}; + +export default getPlaces; diff --git a/src/business-logic/core/places/get-places/infra/protocols.ts b/src/business-logic/core/places/get-places/infra/protocols.ts new file mode 100644 index 0000000..92ca2e7 --- /dev/null +++ b/src/business-logic/core/places/get-places/infra/protocols.ts @@ -0,0 +1,8 @@ +import PlacesModel from '../../common/model/placesModel'; +import { GetPlacesResponse } from '../data/response-object/protocols'; + +export interface IgetPlacesInfra { + httpHandler: () => Promise<GetPlacesResponse>; +} + +export type getPlacesReturnType = () => Promise<PlacesModel>; diff --git a/src/business-logic/core/places/get-places/port.ts b/src/business-logic/core/places/get-places/port.ts new file mode 100644 index 0000000..844e02d --- /dev/null +++ b/src/business-logic/core/places/get-places/port.ts @@ -0,0 +1,5 @@ +import { getPlacesReturnType, type IgetPlacesInfra } from './infra/protocols'; + +export default IgetPlacesInfra; + +export type getPlacesReturnPort = getPlacesReturnType; diff --git a/src/business-logic/core/places/get-places/usecase/__test__/getPlacesUsecase.test.ts b/src/business-logic/core/places/get-places/usecase/__test__/getPlacesUsecase.test.ts new file mode 100644 index 0000000..947836c --- /dev/null +++ b/src/business-logic/core/places/get-places/usecase/__test__/getPlacesUsecase.test.ts @@ -0,0 +1,27 @@ +import PlacesModel from '../../../common/model/placesModel'; +import IGetPlacesRepo from '../../data/repository/IGetPlacesRepo'; +import { GetPlacesRO } from '../../data/response-object/protocols'; +import GettingPlacesUsecase from '../getPlaceUsecase'; + +const mockedRO: GetPlacesRO = { + availableServices: [''], + createdAt: 'createdAt', + id: 'id', + name: 'name', + parentId: null, + placeType: 'continent', + updatedAt: 'updatedTime', + qr: null, +}; + +const model = new PlacesModel(mockedRO); +const mockedRepo: IGetPlacesRepo = jest.fn().mockImplementation(async () => model); + +describe('getting places usecase tests', () => { + it('call repository on execute usecase', async () => { + const usecase = new GettingPlacesUsecase(mockedRepo); + await usecase.execute(); + + expect(mockedRepo).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/business-logic/core/places/get-places/usecase/getPlaceUsecase.ts b/src/business-logic/core/places/get-places/usecase/getPlaceUsecase.ts new file mode 100644 index 0000000..a69bb29 --- /dev/null +++ b/src/business-logic/core/places/get-places/usecase/getPlaceUsecase.ts @@ -0,0 +1,21 @@ +import PlacesModel from '../../common/model/placesModel'; +import IGetPlacesRepo from '../data/repository/IGetPlacesRepo'; + +/** + * this usecase is responsible for calling the repo and returning the places data from the repository + */ +class GettingPlacesUsecase { + private repository: IGetPlacesRepo; + + constructor(repository: IGetPlacesRepo) { + this.repository = repository; + } + + async execute(): Promise<PlacesModel> { + // call usecase and return the repo + const placesData = await this.repository(); + return placesData; + } +} + +export default GettingPlacesUsecase; diff --git a/src/business-logic/core/users/common/data/model/usersModel.ts b/src/business-logic/core/users/common/data/model/usersModel.ts new file mode 100644 index 0000000..22df41f --- /dev/null +++ b/src/business-logic/core/users/common/data/model/usersModel.ts @@ -0,0 +1,26 @@ +import Users from '../../entity/entity'; + +class UsersModel { + private usersList: Users[]; + + private modelTitle = 'users'; + + constructor(data: Users[]) { + this.usersList = data; + } + + getData(): Users[] { + return this.usersList; + } + + getTitle(): string { + return this.modelTitle; + } + + addUser(user: Users): Users[] { + this.usersList.push(user); + return this.usersList; + } +} + +export default UsersModel; diff --git a/src/business-logic/core/users/common/entity/entity.ts b/src/business-logic/core/users/common/entity/entity.ts new file mode 100644 index 0000000..90f9f2c --- /dev/null +++ b/src/business-logic/core/users/common/entity/entity.ts @@ -0,0 +1,6 @@ +export default interface Users { + profileId: string; + firstname: string; + lastname: string; + accountId: string; +} diff --git a/src/business-logic/core/users/create-user/create-account/data/dto/createAccountDTO.ts b/src/business-logic/core/users/create-user/create-account/data/dto/createAccountDTO.ts new file mode 100644 index 0000000..fdc699d --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/dto/createAccountDTO.ts @@ -0,0 +1,10 @@ +import { CreateAccountDTOReturnType, INewUserData } from './protocols'; + +const createAccountDTO = (newUser: INewUserData): CreateAccountDTOReturnType => ({ + enabled: true, + firstName: newUser.firstname, + lastName: newUser.lastname, + username: newUser.phonenumber, +}); + +export default createAccountDTO; diff --git a/src/business-logic/core/users/create-user/create-account/data/dto/protocols.ts b/src/business-logic/core/users/create-user/create-account/data/dto/protocols.ts new file mode 100644 index 0000000..1c74a3b --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/dto/protocols.ts @@ -0,0 +1,12 @@ +export interface INewUserData { + firstname: string; + lastname: string; + phonenumber: string; +} + +export type CreateAccountDTOReturnType = { + username: string; + firstName: string; + lastName: string; + enabled: true; +}; diff --git a/src/business-logic/core/users/create-user/create-account/data/repository/ICreateAcountRepo.ts b/src/business-logic/core/users/create-user/create-account/data/repository/ICreateAcountRepo.ts new file mode 100644 index 0000000..2a81155 --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/repository/ICreateAcountRepo.ts @@ -0,0 +1,8 @@ +import { INewUserData } from '../dto/protocols'; +import { CreateAccountROReturnType } from '../response-object/protocols'; + +interface ICreateAcountRepo { + execute: (newUser: INewUserData) => Promise<CreateAccountROReturnType>; +} + +export default ICreateAcountRepo; diff --git a/src/business-logic/core/users/create-user/create-account/data/repository/createAcountRepo.ts b/src/business-logic/core/users/create-user/create-account/data/repository/createAcountRepo.ts new file mode 100644 index 0000000..f089659 --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/repository/createAcountRepo.ts @@ -0,0 +1,24 @@ +import createAccountDTO from '../dto/createAccountDTO'; +import { INewUserData } from '../dto/protocols'; +import createAcountRO from '../response-object/createAcountRO'; +import { CreateAccountROReturnType } from '../response-object/protocols'; +import ICreateAcountRepo from './ICreateAcountRepo'; +import { HttpHandler } from './protocols'; + +export default class CreateAccountRepo implements ICreateAcountRepo { + private httpHandler: HttpHandler; + + constructor(httpHandler: HttpHandler) { + this.httpHandler = httpHandler; + } + + execute: (newUser: INewUserData) => Promise<CreateAccountROReturnType> = async (newUser: INewUserData) => { + // call dto + const dto = createAccountDTO(newUser); + // call + const newAccountResponse = await this.httpHandler(dto); + // call response object + const newAccount = createAcountRO(newAccountResponse); + return newAccount; + }; +} diff --git a/src/business-logic/core/users/create-user/create-account/data/repository/protocols.ts b/src/business-logic/core/users/create-user/create-account/data/repository/protocols.ts new file mode 100644 index 0000000..9263d97 --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/repository/protocols.ts @@ -0,0 +1,4 @@ +import { INewUserData } from '../dto/protocols'; +import { CreateAcountResponseApi } from '../response-object/protocols'; + +export type HttpHandler = (newUser: INewUserData) => Promise<CreateAcountResponseApi>; diff --git a/src/business-logic/core/users/create-user/create-account/data/response-object/createAcountRO.ts b/src/business-logic/core/users/create-user/create-account/data/response-object/createAcountRO.ts new file mode 100644 index 0000000..66c809c --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/response-object/createAcountRO.ts @@ -0,0 +1,8 @@ +import { CreateAccountROReturnType, CreateAcountResponseApi } from './protocols'; + +const createAcountRO = (apiResponse: CreateAcountResponseApi): CreateAccountROReturnType => ({ + accountId: apiResponse.id, + phonenumber: apiResponse.username, +}); + +export default createAcountRO; diff --git a/src/business-logic/core/users/create-user/create-account/data/response-object/protocols.ts b/src/business-logic/core/users/create-user/create-account/data/response-object/protocols.ts new file mode 100644 index 0000000..3ff64ba --- /dev/null +++ b/src/business-logic/core/users/create-user/create-account/data/response-object/protocols.ts @@ -0,0 +1,10 @@ +export type CreateAcountResponseApi = { + nextRequestTimestamp: number; + username: string; + id: string; +}; + +export type CreateAccountROReturnType = { + phonenumber: string; + accountId: string; +}; diff --git a/src/business-logic/core/users/create-user/create-profile/data/dto/createProfileDTO.ts b/src/business-logic/core/users/create-user/create-profile/data/dto/createProfileDTO.ts new file mode 100644 index 0000000..82ad6d3 --- /dev/null +++ b/src/business-logic/core/users/create-user/create-profile/data/dto/createProfileDTO.ts @@ -0,0 +1,9 @@ +import { CreateProfileDtoType, ICreateNewProfileData } from './protocols'; + +const createProfileDTO: CreateProfileDtoType = (userAccount: ICreateNewProfileData) => ({ + account_id: userAccount.accountId, + first_name: userAccount.firstname, + last_name: userAccount.lastname, +}); + +export default createProfileDTO; diff --git a/src/business-logic/core/users/create-user/create-profile/data/dto/protocols.ts b/src/business-logic/core/users/create-user/create-profile/data/dto/protocols.ts new file mode 100644 index 0000000..544a493 --- /dev/null +++ b/src/business-logic/core/users/create-user/create-profile/data/dto/protocols.ts @@ -0,0 +1,12 @@ +import { INewUserData } from '../../../create-account/data/dto/protocols'; +import { CreateAccountROReturnType } from '../../../create-account/data/response-object/protocols'; + +export type ICreateNewProfileData = CreateAccountROReturnType & INewUserData; + +export type CreateProfileDtoReturnType = { + account_id: string; + first_name: string; + last_name: string; +}; + +export type CreateProfileDtoType = (userAccount: ICreateNewProfileData) => CreateProfileDtoReturnType; diff --git a/src/business-logic/core/users/create-user/create-profile/data/repository/CreateRepositoryRepo.ts b/src/business-logic/core/users/create-user/create-profile/data/repository/CreateRepositoryRepo.ts new file mode 100644 index 0000000..698d85d --- /dev/null +++ b/src/business-logic/core/users/create-user/create-profile/data/repository/CreateRepositoryRepo.ts @@ -0,0 +1,22 @@ +/* eslint-disable no-useless-constructor */ +import RepositoryHandler from '~/driven/utils/helpers/repository-handler/repositoryHandler'; +import { CreateProfileDtoReturnType, ICreateNewProfileData } from '../dto/protocols'; +import createProfileDTO from '../dto/createProfileDTO'; +import ICreateProfileRepo from './ICreateProfileRepo'; +import { HttpHandler } from './protocols'; + +export default class CreateProfileRepo + extends RepositoryHandler<CreateProfileDtoReturnType, string> + implements ICreateProfileRepo +{ + constructor(httpHandler: HttpHandler) { + super(httpHandler); + } + + async execute(accountData: ICreateNewProfileData) { + // create data in dto + const dto = createProfileDTO(accountData); + // call main http handler + return await this.httpHandler(dto); + } +} diff --git a/src/business-logic/core/users/create-user/create-profile/data/repository/ICreateProfileRepo.ts b/src/business-logic/core/users/create-user/create-profile/data/repository/ICreateProfileRepo.ts new file mode 100644 index 0000000..fd73452 --- /dev/null +++ b/src/business-logic/core/users/create-user/create-profile/data/repository/ICreateProfileRepo.ts @@ -0,0 +1,5 @@ +import { ICreateNewProfileData } from '../dto/protocols'; + +export default interface ICreateProfileRepo { + execute: (accountData: ICreateNewProfileData) => Promise<string>; +} diff --git a/src/business-logic/core/users/create-user/create-profile/data/repository/protocols.ts b/src/business-logic/core/users/create-user/create-profile/data/repository/protocols.ts new file mode 100644 index 0000000..7ae431e --- /dev/null +++ b/src/business-logic/core/users/create-user/create-profile/data/repository/protocols.ts @@ -0,0 +1,3 @@ +import { CreateProfileDtoReturnType } from '../dto/protocols'; + +export type HttpHandler = (newUser: CreateProfileDtoReturnType) => Promise<string>; diff --git a/src/business-logic/core/users/create-user/index.ts b/src/business-logic/core/users/create-user/index.ts new file mode 100644 index 0000000..30c0df1 --- /dev/null +++ b/src/business-logic/core/users/create-user/index.ts @@ -0,0 +1,3 @@ +import CreateUserInfra from './infra/createUserInfra'; + +export default CreateUserInfra; diff --git a/src/business-logic/core/users/create-user/infra/createUserInfra.ts b/src/business-logic/core/users/create-user/infra/createUserInfra.ts new file mode 100644 index 0000000..6a032a7 --- /dev/null +++ b/src/business-logic/core/users/create-user/infra/createUserInfra.ts @@ -0,0 +1,29 @@ +import CreateAccountRepo from '../create-account/data/repository/createAcountRepo'; +import { HttpHandler as HttpAccountHandler } from '../create-account/data/repository/protocols'; +import CreateProfileRepo from '../create-profile/data/repository/CreateRepositoryRepo'; +import { HttpHandler as HttpProfileHandler } from '../create-profile/data/repository/protocols'; +import CreateUserUsecase from '../usecase/createUserUsecase'; + +class CreateUserInfra { + private httpAccountHandler: HttpAccountHandler; + + private httpProfileHandler: HttpProfileHandler; + + constructor(httpAccountHandler: HttpAccountHandler, httpProfileHandler: HttpProfileHandler) { + this.httpAccountHandler = httpAccountHandler; + this.httpProfileHandler = httpProfileHandler; + } + + execute() { + // make account repositroy ready + const accountRepository = new CreateAccountRepo(this.httpAccountHandler); + // make profile repository ready + const profileRepository = new CreateProfileRepo(this.httpProfileHandler); + // make usecase ready + const usecase = new CreateUserUsecase(accountRepository, profileRepository); + // return prepared method to call and create user + return usecase; + } +} + +export default CreateUserInfra; diff --git a/src/business-logic/core/users/create-user/ports.ts b/src/business-logic/core/users/create-user/ports.ts new file mode 100644 index 0000000..02ed3c0 --- /dev/null +++ b/src/business-logic/core/users/create-user/ports.ts @@ -0,0 +1,10 @@ +import { HttpHandler as HttpProfileHandler } from './create-profile/data/repository/protocols'; +import { HttpHandler as HttpAccountHandler } from './create-account/data/repository/protocols'; +import CreateUserUsecase from './usecase/createUserUsecase'; + +export default interface createUserPort { + httpAccountHandler: HttpAccountHandler; + httpProfileHandler: HttpProfileHandler; +} + +export type createUserReturnType = CreateUserUsecase; diff --git a/src/business-logic/core/users/create-user/usecase/createUserUsecase.ts b/src/business-logic/core/users/create-user/usecase/createUserUsecase.ts new file mode 100644 index 0000000..aad7d6a --- /dev/null +++ b/src/business-logic/core/users/create-user/usecase/createUserUsecase.ts @@ -0,0 +1,27 @@ +import { INewUserData } from '../create-account/data/dto/protocols'; +import ICreateAcountRepo from '../create-account/data/repository/ICreateAcountRepo'; +import ICreateProfileRepo from '../create-profile/data/repository/ICreateProfileRepo'; + +export default class CreateUserUsecase { + private accountRepository: ICreateAcountRepo; + + private profileRepository: ICreateProfileRepo; + + constructor(accountRepository: ICreateAcountRepo, profileRepository: ICreateProfileRepo) { + this.accountRepository = accountRepository; + this.profileRepository = profileRepository; + } + + async execute(newUser: INewUserData) { + // create acount + const newAccountResponse = await this.accountRepository.execute(newUser); + const newProfileData = { + ...newAccountResponse, + ...newUser, + }; + // create profile by account ID + const newProfileResponse = await this.profileRepository.execute(newProfileData); + + return newProfileResponse; + } +} diff --git a/src/business-logic/core/users/get-users/data/repository/IGetUserRepo.ts b/src/business-logic/core/users/get-users/data/repository/IGetUserRepo.ts new file mode 100644 index 0000000..7199fc9 --- /dev/null +++ b/src/business-logic/core/users/get-users/data/repository/IGetUserRepo.ts @@ -0,0 +1,5 @@ +import UsersModel from '../../../common/data/model/usersModel'; + +type IGetUsersRepo = () => Promise<UsersModel>; + +export default IGetUsersRepo; diff --git a/src/business-logic/core/users/get-users/data/repository/getUserRepo.ts b/src/business-logic/core/users/get-users/data/repository/getUserRepo.ts new file mode 100644 index 0000000..2c58291 --- /dev/null +++ b/src/business-logic/core/users/get-users/data/repository/getUserRepo.ts @@ -0,0 +1,16 @@ +import UsersModel from '../../../common/data/model/usersModel'; +import getUsersResponseObject from '../response-object/usersRO'; +import { GetUsersResponse } from '../response-object/protocols'; + +const getUsersRepo = async (httpHandler: () => Promise<GetUsersResponse>) => { + // call httpHandler + const usersResponse = await httpHandler(); + // user response object to turn it to the data that we want + const usersData = getUsersResponseObject(usersResponse); + // make model + const usersModel = new UsersModel(usersData); + // return the model + return usersModel; +}; + +export default getUsersRepo; diff --git a/src/business-logic/core/users/get-users/data/response-object/protocols.ts b/src/business-logic/core/users/get-users/data/response-object/protocols.ts new file mode 100644 index 0000000..9a4965c --- /dev/null +++ b/src/business-logic/core/users/get-users/data/response-object/protocols.ts @@ -0,0 +1,18 @@ +export type GetUsersResponse = { + _id: string; + account_type: string; + updated_at: string; + created_at: string; + subscription_method: string; + last_login_method: string; + account_number: null | number; + balance: number; + account_id: string; + user_data: { + _id: string; + first_name: string; + last_name: string; + avatar: string; + }; + __v: number; +}[]; diff --git a/src/business-logic/core/users/get-users/data/response-object/usersRO.ts b/src/business-logic/core/users/get-users/data/response-object/usersRO.ts new file mode 100644 index 0000000..df2b190 --- /dev/null +++ b/src/business-logic/core/users/get-users/data/response-object/usersRO.ts @@ -0,0 +1,16 @@ +/* eslint-disable no-underscore-dangle */ +import Users from '../../../common/entity/entity'; +import { GetUsersResponse } from './protocols'; + +const getUsersResponseObject = (apiResponse: GetUsersResponse): Users[] => { + return apiResponse.map((userItem) => { + return { + profileId: userItem._id, + firstname: userItem.user_data.first_name, + lastname: userItem.user_data.last_name, + accountId: userItem.account_id, + }; + }); +}; + +export default getUsersResponseObject; diff --git a/src/business-logic/core/users/get-users/index.ts b/src/business-logic/core/users/get-users/index.ts new file mode 100644 index 0000000..e875adf --- /dev/null +++ b/src/business-logic/core/users/get-users/index.ts @@ -0,0 +1,3 @@ +import getUsers from './infra/getUsersInfra'; + +export default getUsers; diff --git a/src/business-logic/core/users/get-users/infra/getUsersInfra.ts b/src/business-logic/core/users/get-users/infra/getUsersInfra.ts new file mode 100644 index 0000000..13e305d --- /dev/null +++ b/src/business-logic/core/users/get-users/infra/getUsersInfra.ts @@ -0,0 +1,15 @@ +import IGetUsersRepo from '../data/repository/IGetUserRepo'; +import getUsersRepo from '../data/repository/getUserRepo'; +import GettingUsersUsecase from '../usecase/getUsersUsecase'; +import { IgetusersInfra, getusersReturnType } from './protocols'; + +const getUsers = ({ httpHandler }: IgetusersInfra): getusersReturnType => { + // get httpHandler + const repository: IGetUsersRepo = () => getUsersRepo(httpHandler); + // connet usecase and repository + const usecase = new GettingUsersUsecase(repository); + // return method to use + return () => usecase.execute(); +}; + +export default getUsers; diff --git a/src/business-logic/core/users/get-users/infra/protocols.ts b/src/business-logic/core/users/get-users/infra/protocols.ts new file mode 100644 index 0000000..e26a731 --- /dev/null +++ b/src/business-logic/core/users/get-users/infra/protocols.ts @@ -0,0 +1,8 @@ +import UsersModel from '../../common/data/model/usersModel'; +import { GetUsersResponse } from '../data/response-object/protocols'; + +export interface IgetUsersInfra { + httpHandler: () => Promise<GetUsersResponse>; +} + +export type getUsersReturnType = () => Promise<UsersModel>; diff --git a/src/business-logic/core/users/get-users/ports.ts b/src/business-logic/core/users/get-users/ports.ts new file mode 100644 index 0000000..81a7ac7 --- /dev/null +++ b/src/business-logic/core/users/get-users/ports.ts @@ -0,0 +1,5 @@ +import { getUsersReturnType, type IgetUsersInfra } from './infra/protocols'; + +export default IgetUsersInfra; + +export type getUsersReturnPort = getUsersReturnType; diff --git a/src/business-logic/core/users/get-users/usecase/getUsersUsecase.ts b/src/business-logic/core/users/get-users/usecase/getUsersUsecase.ts new file mode 100644 index 0000000..ebec0cc --- /dev/null +++ b/src/business-logic/core/users/get-users/usecase/getUsersUsecase.ts @@ -0,0 +1,21 @@ +import UsersModel from '../../common/data/model/usersModel'; +import IGetUsersRepo from '../data/repository/IGetUserRepo'; + +/** + * this usecase is responsible for calling the repo and returning the users data from the repository + */ +class GettingUsersUsecase { + private repository: IGetUsersRepo; + + constructor(repository: IGetUsersRepo) { + this.repository = repository; + } + + async execute(): Promise<UsersModel> { + // call usecase and return the repo + const usersData = await this.repository(); + return usersData; + } +} + +export default GettingUsersUsecase; diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/data/dto/otpAuthDto.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/data/dto/otpAuthDto.ts new file mode 100644 index 0000000..f9547cf --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/data/dto/otpAuthDto.ts @@ -0,0 +1,14 @@ +import { AdminOtpData } from '../../usecase/otpAuthUsecase'; + +export type OtpAuthDTOReturnType = { + username: string; + password: string; + grant_type: 'otp'; +}; +const OtpAuthDTO = (dataToSend: AdminOtpData): OtpAuthDTOReturnType => ({ + grant_type: 'otp', + password: dataToSend.otp, + username: dataToSend.phonenumber, +}); + +export default OtpAuthDTO; diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/data/reponse-object/otpAuthRO.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/data/reponse-object/otpAuthRO.ts new file mode 100644 index 0000000..8bc88f0 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/data/reponse-object/otpAuthRO.ts @@ -0,0 +1,23 @@ +import AdminUser from '~/business-logic/generic/admin-user/common/entity/adminUserEntity'; + +export type OtpAuthResponse = { + access_token: string; + expires_in: number; + refresh_expires_in: number; + refresh_token: string; + token_type: 'Bearer'; +}; + +export interface IOtpAuthRO extends OtpAuthResponse { + phonenumber: string; +} +const otpAuthRO = (response: IOtpAuthRO): AdminUser => ({ + accessToken: response.access_token, + expiresIn: response.expires_in, + phonenumber: response.phonenumber, + refreshExpiresIn: response.refresh_expires_in, + refreshToken: response.refresh_token, + tokenType: response.token_type, +}); + +export default otpAuthRO; diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/data/repository/IOtpAuthRepo.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/data/repository/IOtpAuthRepo.ts new file mode 100644 index 0000000..2d10765 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/data/repository/IOtpAuthRepo.ts @@ -0,0 +1,6 @@ +import AdminUserModel from '../../../../common/data/model/adminUserModel'; +import { AdminOtpData } from '../../usecase/otpAuthUsecase'; + +export default interface IOtpAuthRepo { + execute(otpData: AdminOtpData): Promise<AdminUserModel>; +} diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/data/repository/otpAuthRepo.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/data/repository/otpAuthRepo.ts new file mode 100644 index 0000000..080794c --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/data/repository/otpAuthRepo.ts @@ -0,0 +1,32 @@ +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import { AdminOtpData } from '../../usecase/otpAuthUsecase'; +import otpAuthRO, { OtpAuthResponse } from '../reponse-object/otpAuthRO'; +import OtpAuthDTO, { OtpAuthDTOReturnType } from '../dto/otpAuthDto'; +import IOtpAuthRepo from './IOtpAuthRepo'; + +export type OtpHttpHandler = (data: OtpAuthDTOReturnType) => Promise<OtpAuthResponse>; + +export default class OtpAuthRepo implements IOtpAuthRepo { + private httpHandler: OtpHttpHandler; + + constructor(httpHandler: OtpHttpHandler) { + this.httpHandler = httpHandler; + } + + async execute(otpData: AdminOtpData): Promise<AdminUserModel> { + // call dto + const dto = OtpAuthDTO(otpData); + // call handler + const response = await this.httpHandler(dto); + // call ro + const responseObjectData = { + ...response, + phonenumber: otpData.phonenumber, + }; + const responseObject = otpAuthRO(responseObjectData); + // make model + const adminModel = new AdminUserModel(responseObject); + // return model + return adminModel; + } +} diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/index.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/index.ts new file mode 100644 index 0000000..ffeda5a --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/index.ts @@ -0,0 +1,3 @@ +import otpAuthInfra from './infra/otpAuthInfra'; + +export default otpAuthInfra; diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/infra/otpAuthInfra.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/infra/otpAuthInfra.ts new file mode 100644 index 0000000..cdacca9 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/infra/otpAuthInfra.ts @@ -0,0 +1,13 @@ +import OtpAuthRepo, { OtpHttpHandler } from '../data/repository/otpAuthRepo'; +import OtpAuthUsecase from '../usecase/otpAuthUsecase'; + +const otpAuthInfra = (httpHandler: OtpHttpHandler) => { + // make the repo ready + const repository = new OtpAuthRepo(httpHandler); + // make the usecase ready + const usecase = new OtpAuthUsecase(repository); + // return method + return usecase; +}; + +export default otpAuthInfra; diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/port.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/port.ts new file mode 100644 index 0000000..bd47f83 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/port.ts @@ -0,0 +1,8 @@ +import { OtpHttpHandler } from './data/repository/otpAuthRepo'; +import OtpAuthUsecase from './usecase/otpAuthUsecase'; + +export interface IOtpAuthDrivenPort { + httpHandler: OtpHttpHandler; +} + +export type OtpAuthDrivingPort = OtpAuthUsecase; diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpAuthUsecase.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpAuthUsecase.ts new file mode 100644 index 0000000..33f1a36 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpAuthUsecase.ts @@ -0,0 +1,35 @@ +import AdminUserModel from '../../../common/data/model/adminUserModel'; +import IOtpAuthRepo from '../data/repository/IOtpAuthRepo'; +import OtpAuthUsecaseException from './otpException'; + +export type AdminOtpData = { + otp: string; + phonenumber: string; +}; + +export interface IOtpAuthUsecase { + execute(data: AdminOtpData): Promise<AdminUserModel>; +} + +export default class OtpAuthUsecase implements IOtpAuthUsecase { + private repository: IOtpAuthRepo; + + private validLenghtOfOtp = 6; + + constructor(repository: IOtpAuthRepo) { + this.repository = repository; + } + + async execute(data: AdminOtpData): Promise<AdminUserModel> { + // check length of otp + const isOtpValid = this.isOtpValid(data.otp); + if (!isOtpValid) throw new OtpAuthUsecaseException(); + // call the repo + const updatedAdminModal = await this.repository.execute(data); + return updatedAdminModal; + } + + private isOtpValid(otp: string) { + return !!(otp.length >= this.validLenghtOfOtp); + } +} diff --git a/src/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpException.ts b/src/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpException.ts new file mode 100644 index 0000000..618fa8a --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpException.ts @@ -0,0 +1 @@ +export default class OtpAuthUsecaseException extends Error {} diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/dto/phonenumberDto.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/dto/phonenumberDto.ts new file mode 100644 index 0000000..3b76455 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/dto/phonenumberDto.ts @@ -0,0 +1,10 @@ +import { AdminData } from '../../usecase/phonenumberAuthUsecase'; + +export type PhonenumberAuthDTOReturnType = { + username: string; +}; +const phonenumberAuthDTO = (adminData: AdminData): PhonenumberAuthDTOReturnType => ({ + username: adminData.phonenumber, +}); + +export default phonenumberAuthDTO; diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/repository/IPhoneNumberAuthRepo.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/repository/IPhoneNumberAuthRepo.ts new file mode 100644 index 0000000..48e0864 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/repository/IPhoneNumberAuthRepo.ts @@ -0,0 +1,5 @@ +import { AdminData } from '../../usecase/phonenumberAuthUsecase'; + +export default interface IPhoneNumberAuthRepo { + execute(adminData: AdminData): Promise<string>; +} diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/repository/phonenumberAuthRepo.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/repository/phonenumberAuthRepo.ts new file mode 100644 index 0000000..5746d99 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/data/repository/phonenumberAuthRepo.ts @@ -0,0 +1,20 @@ +import { AdminData } from '../../usecase/phonenumberAuthUsecase'; +import phonenumberAuthDTO, { PhonenumberAuthDTOReturnType } from '../dto/phonenumberDto'; +import IPhoneNumberAuthRepo from './IPhoneNumberAuthRepo'; + +export type RepoHttpHandler = (dtoData: PhonenumberAuthDTOReturnType) => Promise<string>; +export default class PhoneNumberAuthRepo implements IPhoneNumberAuthRepo { + private httpHandler: RepoHttpHandler; + + constructor(httpHandler: RepoHttpHandler) { + this.httpHandler = httpHandler; + } + + async execute(adminData: AdminData): Promise<string> { + // make dto + const data = phonenumberAuthDTO(adminData); + // call http handler + const response = await this.httpHandler(data); + return response; + } +} diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/index.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/index.ts new file mode 100644 index 0000000..c5526dc --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/index.ts @@ -0,0 +1,3 @@ +import phonenumberAuthInfra from './infra/phonenumberAuthInfra'; + +export default phonenumberAuthInfra; diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/infra/phonenumberAuthInfra.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/infra/phonenumberAuthInfra.ts new file mode 100644 index 0000000..04d7c46 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/infra/phonenumberAuthInfra.ts @@ -0,0 +1,21 @@ +import PhoneNumberAuthRepo, { RepoHttpHandler } from '../data/repository/phonenumberAuthRepo'; +import PhonenumberAuthUsecase from '../usecase/phonenumberAuthUsecase'; + +export interface IPhonenumberAuthInfra { + httpHandler: RepoHttpHandler; + wrongPhoneNumberMessage: string; +} + +const phonenumberAuthInfra = ({ + httpHandler, + wrongPhoneNumberMessage, +}: IPhonenumberAuthInfra): PhonenumberAuthUsecase => { + // prepare repo + const repository = new PhoneNumberAuthRepo(httpHandler); + // prepare usecase + const usecase = new PhonenumberAuthUsecase(repository, wrongPhoneNumberMessage); + // return main method + return usecase; +}; + +export default phonenumberAuthInfra; diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/port.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/port.ts new file mode 100644 index 0000000..c4c8abf --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/port.ts @@ -0,0 +1,5 @@ +import { IPhonenumberAuthInfra } from './infra/phonenumberAuthInfra'; +import PhonenumberAuthUsecase from './usecase/phonenumberAuthUsecase'; + +export type IPhonenumberAuthPort = IPhonenumberAuthInfra; +export type PhonenumberReturnTypePort = Promise<PhonenumberAuthUsecase>; diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/exception.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/exception.ts new file mode 100644 index 0000000..ee325b4 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/exception.ts @@ -0,0 +1 @@ +export default class PhonenumberException extends Error {} diff --git a/src/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/phonenumberAuthUsecase.ts b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/phonenumberAuthUsecase.ts new file mode 100644 index 0000000..97b16e3 --- /dev/null +++ b/src/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/phonenumberAuthUsecase.ts @@ -0,0 +1,36 @@ +import IPhoneNumberAuthRepo from '../data/repository/IPhoneNumberAuthRepo'; +import PhonenumberException from './exception'; + +export type AdminData = { + phonenumber: string; +}; + +interface IPhoneNumberAuthUsecase { + execute(adminData: AdminData): Promise<string>; +} + +export default class PhonenumberAuthUsecase implements IPhoneNumberAuthUsecase { + private repository: IPhoneNumberAuthRepo; + + private wrongPhoneNumberMessage: string; + + constructor(repository: IPhoneNumberAuthRepo, wrongPhoneNumberMessage: string) { + this.repository = repository; + this.wrongPhoneNumberMessage = wrongPhoneNumberMessage; + } + + async execute(adminData: AdminData): Promise<string> { + // check phone number regex + const isPhoenumberValid = this.isPhoneNumberValid(adminData.phonenumber); + if (!isPhoenumberValid) throw new PhonenumberException(this.wrongPhoneNumberMessage); + + const response = await this.repository.execute(adminData); + + return response; + } + + private isPhoneNumberValid(phonenumber: string) { + const regex = /\(?([0-9]{3})\)?([ .-]?)([0-9]{3})\2([0-9]{4})/; + return regex.test(phonenumber); + } +} diff --git a/src/business-logic/generic/admin-user/common/data/model/adminUserModel.ts b/src/business-logic/generic/admin-user/common/data/model/adminUserModel.ts new file mode 100644 index 0000000..575343b --- /dev/null +++ b/src/business-logic/generic/admin-user/common/data/model/adminUserModel.ts @@ -0,0 +1,13 @@ +import AdminUser from '../../entity/adminUserEntity'; + +export default class AdminUserModel { + adminUserData: AdminUser; + + constructor(adminUserData: AdminUser) { + this.adminUserData = adminUserData; + } + + get() { + return this.adminUserData; + } +} diff --git a/src/business-logic/generic/admin-user/common/entity/adminUserEntity.ts b/src/business-logic/generic/admin-user/common/entity/adminUserEntity.ts new file mode 100644 index 0000000..1b1a6d0 --- /dev/null +++ b/src/business-logic/generic/admin-user/common/entity/adminUserEntity.ts @@ -0,0 +1,13 @@ +export default abstract class AdminUser { + abstract phonenumber: string; + + abstract accessToken: string; + + abstract refreshToken: string; + + abstract expiresIn: number; + + abstract refreshExpiresIn: number; + + abstract tokenType: 'Bearer'; +} diff --git a/src/driven/adapters/auth-admin-login/authAdminLogin.ts b/src/driven/adapters/auth-admin-login/authAdminLogin.ts new file mode 100644 index 0000000..c5d2f8d --- /dev/null +++ b/src/driven/adapters/auth-admin-login/authAdminLogin.ts @@ -0,0 +1,40 @@ +import { apiUrls } from '~/driven/utils/configs/appConfig'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import { IOtpAuthDrivenPort } from '~/business-logic/generic/admin-user/authentication/otp-auth/port'; +import { OtpAuthResponse } from '~/business-logic/generic/admin-user/authentication/otp-auth/data/reponse-object/otpAuthRO'; +import { OtpAuthDTOReturnType } from '~/business-logic/generic/admin-user/authentication/otp-auth/data/dto/otpAuthDto'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; + +const authAdminLogin = ( + userAdmin: AdminUserModel | null, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, +): IOtpAuthDrivenPort => { + const httpProvider = new HTTPPovider( + { + accessToken: userAdmin && userAdmin.adminUserData.accessToken && null, + refreshToken: userAdmin && userAdmin.adminUserData.refreshToken && null, + }, + updateAccessToken, + navigateToAuth, + ); + const httpHandler = (data: OtpAuthDTOReturnType): Promise<OtpAuthResponse> => { + const url = apiUrls.generic.authLogin; + const options: HttpOptionsType = { + url, + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + data, + }; + return httpProvider.request<OtpAuthResponse>(options); + }; + + return { + httpHandler, + }; +}; + +export default authAdminLogin; diff --git a/src/driven/adapters/auth-admin-phonenumber/authAdminPhoneNumberDriven.ts b/src/driven/adapters/auth-admin-phonenumber/authAdminPhoneNumberDriven.ts new file mode 100644 index 0000000..c708240 --- /dev/null +++ b/src/driven/adapters/auth-admin-phonenumber/authAdminPhoneNumberDriven.ts @@ -0,0 +1,44 @@ +import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import { apiUrls } from '~/driven/utils/configs/appConfig'; +import { PhonenumberAuthDTOReturnType } from '~/business-logic/generic/admin-user/authentication/phonnumber-auth/data/dto/phonenumberDto'; +import { IPhonenumberAuthPort } from '~/business-logic/generic/admin-user/authentication/phonnumber-auth/port'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; + +const authAdminPhoneNumberDriven = ( + userAdmin: AdminUserModel | null, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, +): IPhonenumberAuthPort => { + // make http handler ready for the phone number + const wrongPhoneNumberMessage = staticMessages.global.errors.phonenumber; + + const httpProvider = new HTTPPovider( + { + accessToken: userAdmin && userAdmin?.adminUserData.accessToken && null, + refreshToken: userAdmin && userAdmin?.adminUserData.refreshToken && null, + }, + updateAccessToken, + navigateToAuth, + ); + const httpHandler = (data: PhonenumberAuthDTOReturnType) => { + const url = apiUrls.generic.authPhonenumber; + const options: HttpOptionsType = { + url, + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + data, + }; + return httpProvider.request<string>(options); + }; + + return { + httpHandler, + wrongPhoneNumberMessage, + }; +}; + +export default authAdminPhoneNumberDriven; diff --git a/src/driven/adapters/create-account-adapter/createAccountAdapter.ts b/src/driven/adapters/create-account-adapter/createAccountAdapter.ts new file mode 100644 index 0000000..b16bc97 --- /dev/null +++ b/src/driven/adapters/create-account-adapter/createAccountAdapter.ts @@ -0,0 +1,42 @@ +import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; +import { CreateAcountResponseApi } from '~/business-logic/core/users/create-user/create-account/data/response-object/protocols'; +import createUserPort from '~/business-logic/core/users/create-user/ports'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import { apiUrls } from '~/driven/utils/configs/appConfig'; + +const createAccountAdapter = ( + userAdmin: AdminUserModel, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, +): createUserPort['httpAccountHandler'] => { + // make url + const url = apiUrls.core.createUserAccount; + // call http provider + const httpProvider = new HTTPPovider( + { + accessToken: userAdmin.adminUserData.accessToken, + refreshToken: userAdmin.adminUserData.refreshToken, + }, + updateAccessToken, + navigateToAuth, + ); + + const httpHandler = (newUserData: INewUserData) => { + // api options + const httpOptions: HttpOptionsType = { + url, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: newUserData, + }; + return httpProvider.request<CreateAcountResponseApi>(httpOptions); + }; + + return httpHandler; +}; + +export default createAccountAdapter; diff --git a/src/driven/adapters/create-profile-adapter/createProfileAdapter.ts b/src/driven/adapters/create-profile-adapter/createProfileAdapter.ts new file mode 100644 index 0000000..32afa07 --- /dev/null +++ b/src/driven/adapters/create-profile-adapter/createProfileAdapter.ts @@ -0,0 +1,41 @@ +import { CreateProfileDtoReturnType } from '~/business-logic/core/users/create-user/create-profile/data/dto/protocols'; +import createUserPort from '~/business-logic/core/users/create-user/ports'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import { apiUrls } from '~/driven/utils/configs/appConfig'; + +const createProfileAdapter = ( + userAdmin: AdminUserModel, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, +): createUserPort['httpProfileHandler'] => { + // make url + const url = apiUrls.core.createUserProfile; + // call http provider + const httpProvider = new HTTPPovider( + { + accessToken: userAdmin.adminUserData.accessToken, + refreshToken: userAdmin.adminUserData.refreshToken, + }, + updateAccessToken, + navigateToAuth, + ); + + const httpHandler = (newAccountData: CreateProfileDtoReturnType) => { + // api options + const httpOptions: HttpOptionsType = { + url, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: newAccountData, + }; + return httpProvider.request<string>(httpOptions); + }; + + return httpHandler; +}; + +export default createProfileAdapter; diff --git a/src/driven/adapters/get-places-adapter/getPlacesAdapter.ts b/src/driven/adapters/get-places-adapter/getPlacesAdapter.ts new file mode 100644 index 0000000..5d6b52c --- /dev/null +++ b/src/driven/adapters/get-places-adapter/getPlacesAdapter.ts @@ -0,0 +1,40 @@ +import { GetPlacesResponse } from '~/business-logic/core/places/get-places/data/response-object/protocols'; +import IGetPlacesPort from '~/business-logic/core/places/get-places/port'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import { apiUrls } from '~/driven/utils/configs/appConfig'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import { getPlacesAdapterReturnType } from './protocols'; + +const getPlacesAdapter = ( + userAdmin: AdminUserModel, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, +): IGetPlacesPort & getPlacesAdapterReturnType => { + // url of api + const url = apiUrls.core.getPlaces; + // make the options of request + const options: HttpOptionsType = { + url, + method: 'GET', + }; + // make the httpHandler + const httpProvider = new HTTPPovider( + { + accessToken: userAdmin.adminUserData.accessToken, + refreshToken: userAdmin.adminUserData.refreshToken, + }, + updateAccessToken, + navigateToAuth, + ); + + const httpHandler = async () => httpProvider.request<GetPlacesResponse>(options); + + // return the method + return { + httpHandler, + url, + }; +}; + +export default getPlacesAdapter; diff --git a/src/driven/adapters/get-places-adapter/protocols.ts b/src/driven/adapters/get-places-adapter/protocols.ts new file mode 100644 index 0000000..4a89776 --- /dev/null +++ b/src/driven/adapters/get-places-adapter/protocols.ts @@ -0,0 +1,3 @@ +export type getPlacesAdapterReturnType = { + url: string; +}; diff --git a/src/driven/adapters/get-users-adapter/getUsersAdapter.ts b/src/driven/adapters/get-users-adapter/getUsersAdapter.ts new file mode 100644 index 0000000..e46541f --- /dev/null +++ b/src/driven/adapters/get-users-adapter/getUsersAdapter.ts @@ -0,0 +1,40 @@ +import { apiUrls } from '~/driven/utils/configs/appConfig'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import { GetUsersResponse } from '~/business-logic/core/users/get-users/data/response-object/protocols'; +import IGetUsersPort from '~/business-logic/core/users/get-users/ports'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import { getUsersAdapterReturnType } from './protocols'; + +const getUsersAdapter = ( + userAdmin: AdminUserModel, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, +): IGetUsersPort & getUsersAdapterReturnType => { + // url of api + const url = apiUrls.core.getUsers; + // make the options of request + const options: HttpOptionsType = { + url, + method: 'GET', + }; + // make the httpHandler + const httpProvider = new HTTPPovider( + { + accessToken: userAdmin.adminUserData.accessToken, + refreshToken: userAdmin.adminUserData.refreshToken, + }, + updateAccessToken, + navigateToAuth, + ); + + const httpHandler = async () => httpProvider.request<GetUsersResponse>(options); + + // return the method + return { + httpHandler, + url, + }; +}; + +export default getUsersAdapter; diff --git a/src/driven/adapters/get-users-adapter/protocols.ts b/src/driven/adapters/get-users-adapter/protocols.ts new file mode 100644 index 0000000..65c2750 --- /dev/null +++ b/src/driven/adapters/get-users-adapter/protocols.ts @@ -0,0 +1,3 @@ +export type getUsersAdapterReturnType = { + url: string; +}; diff --git a/src/driven/boundaries/http-boundary/httpBoundary.ts b/src/driven/boundaries/http-boundary/httpBoundary.ts index f5f5ef5..3f65647 100644 --- a/src/driven/boundaries/http-boundary/httpBoundary.ts +++ b/src/driven/boundaries/http-boundary/httpBoundary.ts @@ -1,11 +1,88 @@ -import axios, { AxiosRequestConfig } from 'axios'; +/* eslint-disable consistent-return */ +/* eslint-disable no-param-reassign */ +import axios, { AxiosInstance } from 'axios'; import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import { ApiGlobalResponseObject } from '~/driven/utils/protocols/serviceProtocols'; +import { apiUrls } from '~/driven/utils/configs/appConfig'; +import { HttpOptionsType } from './protocols'; -export class HTTPBoundary { - async request<R>(options: AxiosRequestConfig) { - const response = await axios<R>(options); +interface IUserTokens { + accessToken: string | null | undefined; + refreshToken: string | null | undefined; +} +export class HTTPPovider { + private userTokens: IUserTokens; + + private updateAccessToken: (newAccessToken: string) => void; + + private navigateToAuth: () => void; + + constructor( + userTokens: IUserTokens, + updateAccessToken: (newAccessToken: string) => void, + navigateToAuth: () => void, + ) { + this.userTokens = userTokens; + this.updateAccessToken = updateAccessToken; + this.navigateToAuth = navigateToAuth; + } + + private initalizeAxiosInstance() { + const instance = axios.create(); + + return instance; + } + + private handleRequestInterceptor() { + const axiosInstance = this.initalizeAxiosInstance(); + axiosInstance.interceptors.request.use((config) => { + config.headers.Authorization = `Bearer ${this.userTokens.accessToken}`; + return config; + }); + + return axiosInstance; + } + + async request<R>(customOptions: HttpOptionsType) { + const axiosInstance = this.handleRequestInterceptor(); + this.responseIncepter(axiosInstance); + + const response = await axiosInstance<ApiGlobalResponseObject<R>>(customOptions); if (!response) throw new Error(staticMessages.service.errors[500]); - return response.data; + return response.data.data; + } + + /** + * @todo should be handled in business logic + */ + private async refreshAccessToken() { + try { + const response = await axios.post(apiUrls.generic.authRefresh, { + refresh_token: this.userTokens.refreshToken, + }); + this.updateAccessToken(response.data.access_token as string); + return response.data.access_token; + } catch (err) { + this.navigateToAuth(); + } + } + + private responseIncepter(axiosInstance: AxiosInstance) { + axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + const originalRequest = error.config; + + if (error.response.status === 401 && error.response.message === 'Unauthorized') { + const newAccessToken = this.refreshAccessToken().then(() => { + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + return axios(originalRequest); + }); + } + + return Promise.reject(error); + }, + ); } } diff --git a/src/driven/boundaries/http-boundary/protocols.ts b/src/driven/boundaries/http-boundary/protocols.ts new file mode 100644 index 0000000..6690666 --- /dev/null +++ b/src/driven/boundaries/http-boundary/protocols.ts @@ -0,0 +1,3 @@ +import { AxiosRequestConfig } from 'axios'; + +export type HttpOptionsType = AxiosRequestConfig; diff --git a/src/driven/boundaries/state-management-boundary/adapters/adapter.ts b/src/driven/boundaries/state-management-boundary/adapters/adapter.ts deleted file mode 100644 index 354c92b..0000000 --- a/src/driven/boundaries/state-management-boundary/adapters/adapter.ts +++ /dev/null @@ -1,8 +0,0 @@ -import store from '../store/store'; -import userSlice from '../slices/userSlice'; -import { UserState } from '../slices/protocols/userSliceProtocols'; - -export const userAdapter = { - get: store.getState().user, - update: (user: UserState) => store.dispatch(userSlice.actions.update(user)), -}; diff --git a/src/driven/boundaries/state-management-boundary/index.ts b/src/driven/boundaries/state-management-boundary/index.ts deleted file mode 100644 index e1751e0..0000000 --- a/src/driven/boundaries/state-management-boundary/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { userAdapter } from './adapters/adapter'; diff --git a/src/driven/boundaries/state-management-boundary/slices/protocols/userSliceProtocols.ts b/src/driven/boundaries/state-management-boundary/slices/protocols/userSliceProtocols.ts deleted file mode 100644 index 583c7f0..0000000 --- a/src/driven/boundaries/state-management-boundary/slices/protocols/userSliceProtocols.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { UserModel } from '~/business-logic/generic/user/common/domain/model/userModel'; - -export type UserState = UserModel; diff --git a/src/driven/boundaries/state-management-boundary/slices/userSlice.ts b/src/driven/boundaries/state-management-boundary/slices/userSlice.ts deleted file mode 100644 index 0f0d37d..0000000 --- a/src/driven/boundaries/state-management-boundary/slices/userSlice.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createSlice, SliceCaseReducers } from '@reduxjs/toolkit'; -import { UserState } from './protocols/userSliceProtocols'; - -export const userStateName = 'user'; -const userSlice = createSlice<UserState, SliceCaseReducers<UserState>>({ - name: userStateName, - initialState: null, - reducers: { - update: (state, action) => { - if (!action.payload) return state; - return { - ...state, - ...action.payload, - }; - }, - }, -}); - -export default userSlice; diff --git a/src/driven/boundaries/state-management-boundary/store/store.ts b/src/driven/boundaries/state-management-boundary/store/store.ts deleted file mode 100644 index ccf0b9c..0000000 --- a/src/driven/boundaries/state-management-boundary/store/store.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { combineReducers, configureStore } from '@reduxjs/toolkit'; -import userSlice, { userStateName } from '../slices/userSlice'; - -const combinedReducers = combineReducers({ - [userStateName]: userSlice.reducer, -}); - -const store = configureStore({ - reducer: combinedReducers, -}); - -export default store; diff --git a/src/driven/boundaries/state-management/index.ts b/src/driven/boundaries/state-management/index.ts new file mode 100644 index 0000000..7668bcb --- /dev/null +++ b/src/driven/boundaries/state-management/index.ts @@ -0,0 +1,3 @@ +import StateManagementService from './stateManagementService'; + +export default StateManagementService; diff --git a/src/driven/boundaries/state-management/stateManagementProvider.ts b/src/driven/boundaries/state-management/stateManagementProvider.ts new file mode 100644 index 0000000..c430681 --- /dev/null +++ b/src/driven/boundaries/state-management/stateManagementProvider.ts @@ -0,0 +1,6 @@ +export default abstract class StateManagementProvider { + abstract useGetQuery<DataType>( + key: string, + httpHandler: () => Promise<DataType>, + ): { data: DataType | undefined; isLoading: boolean; error?: string }; +} diff --git a/src/driven/boundaries/state-management/stateManagementService.ts b/src/driven/boundaries/state-management/stateManagementService.ts new file mode 100644 index 0000000..261c358 --- /dev/null +++ b/src/driven/boundaries/state-management/stateManagementService.ts @@ -0,0 +1,26 @@ +import StateManagementProvider from './stateManagementProvider'; +import SwrBoundary from './swrBoundary'; + +export default class StateManagementService implements StateManagementProvider { + private provider: StateManagementProvider; + + constructor(provider: StateManagementProvider) { + this.provider = provider; + } + + static swr() { + const stateManagement = new StateManagementService(new SwrBoundary()); + return stateManagement; + } + + useGetQuery<DataType>( + key: string, + httpHandler: () => Promise<DataType>, + ): { + data: DataType | undefined; + isLoading: boolean; + error?: string | undefined; + } { + return this.provider.useGetQuery(key, httpHandler); + } +} diff --git a/src/driven/boundaries/state-management/swrBoundary.ts b/src/driven/boundaries/state-management/swrBoundary.ts new file mode 100644 index 0000000..3dc3958 --- /dev/null +++ b/src/driven/boundaries/state-management/swrBoundary.ts @@ -0,0 +1,15 @@ +import useSwr from 'swr'; +import StateManagementProvider from './stateManagementProvider'; + +export default class SwrBoundary implements StateManagementProvider { + useGetQuery<DataType>( + key: string, + httpHandler: () => Promise<DataType>, + ): { + data: DataType | undefined; + isLoading: boolean; + error?: string | undefined; + } { + return useSwr(key, httpHandler); + } +} diff --git a/src/driven/boundaries/storage-boundary/index.ts b/src/driven/boundaries/storage-boundary/index.ts new file mode 100644 index 0000000..19578bc --- /dev/null +++ b/src/driven/boundaries/storage-boundary/index.ts @@ -0,0 +1,3 @@ +import StorageService from './storageService'; + +export default StorageService; diff --git a/src/driven/boundaries/storage-boundary/localStorageProvider.ts b/src/driven/boundaries/storage-boundary/localStorageProvider.ts new file mode 100644 index 0000000..96a90df --- /dev/null +++ b/src/driven/boundaries/storage-boundary/localStorageProvider.ts @@ -0,0 +1,18 @@ +import StorageProvider from './storageProvider'; + +export default class LocalStorageProvider<ValueType> implements StorageProvider<ValueType> { + setData(key: string, value: ValueType): void { + const jsonValue = JSON.stringify(value); + localStorage.setItem(key, jsonValue); + } + + getData(key: string): ValueType | null { + const jsonValue = localStorage.getItem(key); + const value: ValueType = jsonValue && JSON.parse(jsonValue); + return value; + } + + deleteData(key: string): void { + localStorage.removeItem(key); + } +} diff --git a/src/driven/boundaries/storage-boundary/storageProvider.ts b/src/driven/boundaries/storage-boundary/storageProvider.ts new file mode 100644 index 0000000..26b5cd1 --- /dev/null +++ b/src/driven/boundaries/storage-boundary/storageProvider.ts @@ -0,0 +1,7 @@ +export default abstract class StorageProvider<ValueType> { + abstract setData(key: string, value: ValueType): void; + + abstract getData(key: string): ValueType | null; + + abstract deleteData(key: string): void; +} diff --git a/src/driven/boundaries/storage-boundary/storageService.ts b/src/driven/boundaries/storage-boundary/storageService.ts new file mode 100644 index 0000000..7e73cf7 --- /dev/null +++ b/src/driven/boundaries/storage-boundary/storageService.ts @@ -0,0 +1,27 @@ +import LocalStorageProvider from './localStorageProvider'; +import StorageProvider from './storageProvider'; + +export default class StorageService<ValueType> implements StorageProvider<ValueType> { + private provider: StorageProvider<ValueType>; + + constructor(provider: StorageProvider<ValueType>) { + this.provider = provider; + } + + static localStorage<ValuType>(): StorageService<ValuType> { + const localStorageService = new StorageService<ValuType>(new LocalStorageProvider<ValuType>()); + return localStorageService; + } + + setData(key: string, value: ValueType): void { + return this.provider.setData(key, value); + } + + getData(key: string): ValueType | null { + return this.provider.getData(key); + } + + deleteData(key: string): void { + this.provider.deleteData(key); + } +} diff --git a/src/driven/utils/components/Notification/Notification.tsx b/src/driven/utils/components/Notification/Notification.tsx new file mode 100644 index 0000000..9f62aca --- /dev/null +++ b/src/driven/utils/components/Notification/Notification.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +export interface notifInterface { + type: 'error' | 'success' | 'warning'; + message: string; + time?: number; + onCloseCallback?: () => unknown; +} +export default function Notification({ message, type, time = 5, onCloseCallback }: notifInterface) { + const notifRef = React.useRef<HTMLDivElement>(null); + const el = React.useRef(document.createElement('div')); + + React.useEffect(() => { + const portal = document.getElementById('root'); + portal?.appendChild(el.current); + + setTimeout(() => { + el.current.remove(); + if (typeof onCloseCallback !== 'undefined') onCloseCallback(); + }, 1000 * time); + + return () => { + if (typeof onCloseCallback !== 'undefined') onCloseCallback(); + return el.current?.remove(); + }; + }, []); + + return ReactDOM.createPortal( + <div + ref={notifRef} + className={`fixed top-10 left-1/2 translate-x-[-50%] z-30 p-2 rounded-md text-black ${ + type === 'error' && 'bg-red-600 text-white' + } ${type === 'success' && 'bg-green-600 text-white'} ${type === 'warning' && 'bg-yellow-500'}`} + > + {message} + </div>, + el.current, + ); +} diff --git a/src/driven/utils/components/buttons/primary-button/PrimaryButton.tsx b/src/driven/utils/components/buttons/primary-button/PrimaryButton.tsx index 967f21f..d2da017 100644 --- a/src/driven/utils/components/buttons/primary-button/PrimaryButton.tsx +++ b/src/driven/utils/components/buttons/primary-button/PrimaryButton.tsx @@ -1,14 +1,21 @@ -import React from 'react' +import React from 'react'; interface IPrimaryButtonProps { title: string; onClick: (e: React.MouseEvent) => void; className?: string; + isDisabled?: boolean; } export default function PrimaryButton(props: IPrimaryButtonProps) { - const { onClick, title, className } = props; + const { onClick, title, className, isDisabled = false } = props; return ( - <button onClick={onClick} className={`py-1 px-5 transition-all hover:bg-primary-300 bg-primary-main rounded-lg text-white text-center ${className}`}>{ title }</button> - ) + <button + disabled={isDisabled} + onClick={onClick} + className={`py-1 px-5 transition-all hover:bg-primary-300 bg-primary-main rounded-lg text-white text-center ${className}`} + > + {title} + </button> + ); } diff --git a/src/driven/utils/components/inputs/simple-input/SimpleInput.tsx b/src/driven/utils/components/inputs/simple-input/SimpleInput.tsx index 4fd7158..095cef8 100644 --- a/src/driven/utils/components/inputs/simple-input/SimpleInput.tsx +++ b/src/driven/utils/components/inputs/simple-input/SimpleInput.tsx @@ -1,16 +1,41 @@ -import React from 'react' +import React from 'react'; -interface ISimpleInput { - title: string; +export type SetStateInputMethod<NameType> = (name: NameType, newValue: string) => void; + +interface ISimpleInput<NameType> { + inputData: { + title: string; + name: string; + }; className?: string; + stateHanlder: { + state: string; + setState: SetStateInputMethod<NameType>; + }; } -export default function SimpleInput(props: ISimpleInput) { - const { title, className } = props; +export default function SimpleInput<NameType>(props: ISimpleInput<NameType>) { + const { className, inputData, stateHanlder } = props; + const { name, title } = inputData; + const { setState, state } = stateHanlder; + + const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const { value, name: inputName } = e.target; + setState(inputName as NameType, value); + }; + return ( <div className={`flex flex-col ${className}`}> - <label className='mb-1 text-txt-second text-xs' htmlFor={title}>{ title }</label> - <input className='bg-bg-gray h-11 rounded-lg focus:outline-0 px-2 text-txt-medium' id={title} /> + <label className='mb-1 text-txt-second text-xs' htmlFor={title}> + {title} + </label> + <input + value={state} + onChange={handleInputChange} + name={name} + className='bg-bg-gray h-11 rounded-lg focus:outline-0 px-2 text-txt-medium w-full' + id={title} + /> </div> - ) + ); } diff --git a/src/driven/utils/components/loading/Loading.tsx b/src/driven/utils/components/loading/Loading.tsx new file mode 100644 index 0000000..cb43672 --- /dev/null +++ b/src/driven/utils/components/loading/Loading.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import style from './style.module.css'; + +export default function Loading() { + return ( + <div className={style.ldsRing}> + <div /> + <div /> + <div /> + <div /> + </div> + ); +} diff --git a/src/driven/utils/components/loading/style.module.css b/src/driven/utils/components/loading/style.module.css new file mode 100644 index 0000000..df21453 --- /dev/null +++ b/src/driven/utils/components/loading/style.module.css @@ -0,0 +1,35 @@ +.ldsRing { + display: inline-block; + position: relative; + width: 80px; + height: 80px; +} +.ldsRing div { + box-sizing: border-box; + display: block; + position: absolute; + width: 2rem; + height: 2rem; + margin: 4px; + border: 4px solid #fff; + border-radius: 50%; + animation: ldsRing 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + border-color: var(--color-primary-main) transparent transparent transparent; +} +.ldsRing div:nth-child(1) { + animation-delay: -0.45s; +} +.ldsRing div:nth-child(2) { + animation-delay: -0.3s; +} +.ldsRing div:nth-child(3) { + animation-delay: -0.15s; +} +@keyframes ldsRing { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/src/driven/utils/components/page-title/pageTitle.tsx b/src/driven/utils/components/page-title/pageTitle.tsx index ca0f4d1..519ef93 100644 --- a/src/driven/utils/components/page-title/pageTitle.tsx +++ b/src/driven/utils/components/page-title/pageTitle.tsx @@ -1,13 +1,11 @@ -import React from 'react' +import React from 'react'; interface IPageTitleProps { - title: string; - className?: string; + title: string; + className?: string; } export default function PageTitle(props: IPageTitleProps) { - const { title, className } = props; - return ( - <div className={`w-full shadow-sm shadow-txt-light font-semibold ${className}`}>{title}</div> - ) + const { title, className } = props; + return <div className={`w-full shadow-sm shadow-txt-light font-semibold ${className}`}>{title}</div>; } diff --git a/src/driven/utils/configs/appConfig.ts b/src/driven/utils/configs/appConfig.ts index dac6474..93bd570 100644 --- a/src/driven/utils/configs/appConfig.ts +++ b/src/driven/utils/configs/appConfig.ts @@ -1,27 +1,42 @@ -import { icons } from "../constants/assertUrls"; -import { staticMessages } from "../constants/staticMessages"; +import { icons } from '../constants/assertUrls'; +import { ENVs } from '../constants/envs'; +import { staticMessages } from '../constants/staticMessages'; -export const appConfig = {}; +export const appConfig = { + adminUserStorageKey: 'adminUser', +}; export const routes = { usersList: '/', - createUser: '/create-user' + createUser: '/create-user', + authentication: '/auth', }; export const routesData = { usersList: { path: routes.usersList, icon: icons.users, - title: staticMessages.global.users + title: staticMessages.global.users, }, createUser: { path: routes.createUser, icon: icons.createUser, - title: staticMessages.global.createUser - } -} - -const baseApiUrl = import.meta.env.BASE_API_URL; -export const apiUrls = { - + title: staticMessages.global.createUser, + }, +}; + +const baseApiUrl = ENVs.apiOrigin; +export const apiUrls = { + core: { + getPlaces: `${baseApiUrl}${ENVs.apiGetPlaces}`, + getUsers: `${baseApiUrl}${ENVs.apiGetUsers}`, + createUserAccount: `${baseApiUrl}${ENVs.apiCreateUserAccount}`, + createUserProfile: `${baseApiUrl}${ENVs.apiCreateUserProfile}`, + createMember: `${baseApiUrl}${ENVs.apiCreateMember}`, + }, + generic: { + authPhonenumber: `${ENVs.apiAuthOrigin}${ENVs.apiAuthPhonenumber}`, + authLogin: `${ENVs.apiAuthOrigin}${ENVs.apiAuthLogin}`, + authRefresh: `${ENVs.apiAuthOrigin}${ENVs.apiAuthRefresh}`, + }, }; diff --git a/src/driven/utils/constants/assertUrls.ts b/src/driven/utils/constants/assertUrls.ts index 0e1fdfa..66c35ac 100644 --- a/src/driven/utils/constants/assertUrls.ts +++ b/src/driven/utils/constants/assertUrls.ts @@ -1,7 +1,8 @@ const baseAssetsUrl = 'assets/'; -const baseIconsUrl = baseAssetsUrl + 'icons/'; +const baseIconsUrl = `${baseAssetsUrl}icons/`; export const icons = { - logo: baseIconsUrl + 'logo.svg', - users: baseIconsUrl + 'users.svg', - createUser: baseIconsUrl + 'createuser.svg' -} \ No newline at end of file + logo: `${baseIconsUrl}logo.svg`, + logoBlack: `${baseIconsUrl}logo-black.svg`, + users: `${baseIconsUrl}users.svg`, + createUser: `${baseIconsUrl}createuser.svg`, +}; diff --git a/src/driven/utils/constants/envs.ts b/src/driven/utils/constants/envs.ts new file mode 100644 index 0000000..102978c --- /dev/null +++ b/src/driven/utils/constants/envs.ts @@ -0,0 +1,12 @@ +export const ENVs = { + apiOrigin: process.env.VITE_API_ORIGIN, + apiAuthOrigin: process.env.VITE_API_AUTH_ORIGIN, + apiAuthPhonenumber: process.env.VITE_API_AUTH_PHONENUMBER, + apiAuthLogin: process.env.VITE_API_AUTH_LOGIN, + apiAuthRefresh: process.env.VITE_API_AUTH_REFRESH, + apiGetPlaces: process.env.VITE_API_PLACES, + apiGetUsers: process.env.VITE_API_USERS, + apiCreateUserAccount: process.env.VITE_API_USERS_ACCOUNT, + apiCreateUserProfile: process.env.VITE_API_USERS_PROFILE, + apiCreateMember: process.env.VITE_API_CREATE_MEMBER, +}; diff --git a/src/driven/utils/constants/staticMessages.ts b/src/driven/utils/constants/staticMessages.ts index 4641f23..3acc2a0 100644 --- a/src/driven/utils/constants/staticMessages.ts +++ b/src/driven/utils/constants/staticMessages.ts @@ -2,18 +2,28 @@ export const staticMessages = { global: { errors: { input: 'please fill all inputs correctly', + phonenumber: 'please fill the valid number', + otp: 'please fill the otp fields correctly', }, users: 'Users', submit: 'Submit', - fistname: 'Firstname', + firstname: 'Firstname', lastname: 'Lastname', place_id: 'Place id', title: 'title', status: 'Status', + placeType: 'Place Type', address: 'Address', qrCode: 'qrCode', createUser: 'Create user', - phoneNumber: 'Phone Number' + phonenumber: 'Phone Number', + enterPanel: 'Enter to Panel', + enterPhoneNumber: 'Enter your phone number', + enterOtpCode: 'Enter your Otp Code', + success: { + createUser: 'user created successfully', + createMember: 'member created successfully', + }, }, service: { errors: { diff --git a/src/driven/utils/helpers/contexts/userContext.tsx b/src/driven/utils/helpers/contexts/userContext.tsx new file mode 100644 index 0000000..f53331d --- /dev/null +++ b/src/driven/utils/helpers/contexts/userContext.tsx @@ -0,0 +1,17 @@ +/* eslint-disable react/jsx-no-constructed-context-values */ +import React, { useState } from 'react'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; + +export interface IUserContext { + user: AdminUserModel | null; + setUser: React.Dispatch<React.SetStateAction<AdminUserModel | null>> | null; +} +export const UserContext = React.createContext<IUserContext>({ user: null, setUser: null }); + +export function UserProvider({ children }: React.PropsWithChildren) { + const [user, setUser] = useState<AdminUserModel | null>(null); + + return <UserContext.Provider value={{ user, setUser }}>{children}</UserContext.Provider>; +} + +export const useUser = () => React.useContext(UserContext); diff --git a/src/driven/utils/helpers/globalHelpers.ts b/src/driven/utils/helpers/globalHelpers.ts index cfbe1e1..92fb772 100644 --- a/src/driven/utils/helpers/globalHelpers.ts +++ b/src/driven/utils/helpers/globalHelpers.ts @@ -1,4 +1,9 @@ +import StateManagementService from '~/driven/boundaries/state-management'; +import StorageService from '~/driven/boundaries/storage-boundary'; +import { NavigateFunction } from 'react-router-dom'; +import { appConfig, routes } from '../configs/appConfig'; import { errorHandlingStateTypes, UIErrorHandling } from './protocols/globalHelpersProtocols'; +import { IUserContext } from './contexts/userContext'; export const UIErrorHandlingFactory = <DATA_RESPONSE>({ state, @@ -13,3 +18,33 @@ export const UIErrorHandlingFactory = <DATA_RESPONSE>({ message, state, }); + +export const prepareStateManagementForVM = <ReturnType>(apiUrl: string, model: () => Promise<ReturnType>) => { + const stateManagement = StateManagementService.swr(); + + const useGetPlacesList = () => stateManagement.useGetQuery(apiUrl, model); + + return useGetPlacesList; +}; + +export const checkPhoneNumberInput = (newValue: string) => { + return (Number.isFinite(+newValue) || newValue === '+') && newValue.length <= 12; +}; + +export const updateAccessToken = (newAccessToken: string, userContext: IUserContext) => { + const { setUser, user } = userContext; + if (!user || !setUser) return; + const storage = StorageService.localStorage(); + user.adminUserData.accessToken = newAccessToken; + storage.setData(appConfig.adminUserStorageKey, user); + setUser(user); +}; + +export const navigateToAuth = (userCtx: IUserContext, navigate: NavigateFunction) => { + const { setUser } = userCtx; + const storage = StorageService.localStorage(); + storage.deleteData(appConfig.adminUserStorageKey); + if (!setUser) return; + setUser(null); + navigate(routes.authentication); +}; diff --git a/src/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator.ts b/src/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator.ts new file mode 100644 index 0000000..077d49a --- /dev/null +++ b/src/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator.ts @@ -0,0 +1,24 @@ +import { useNavigate } from 'react-router-dom'; +import { useUser } from '../contexts/userContext'; +import { navigateToAuth, updateAccessToken } from '../globalHelpers'; + +const useGetNavigatorAndTokenUpdater = () => { + const userData = useUser(); + const navigate = useNavigate(); + + const notLoginAuth = () => { + navigateToAuth(userData, navigate); + }; + + const accessTokenUpdateHandler = (newAccessToken: string) => { + updateAccessToken(newAccessToken, userData); + }; + return { + notLoginAuth, + accessTokenUpdateHandler, + userData, + navigate, + }; +}; + +export default useGetNavigatorAndTokenUpdater; diff --git a/src/driven/utils/helpers/repository-handler/repositoryHandler.ts b/src/driven/utils/helpers/repository-handler/repositoryHandler.ts new file mode 100644 index 0000000..163b4d7 --- /dev/null +++ b/src/driven/utils/helpers/repository-handler/repositoryHandler.ts @@ -0,0 +1,9 @@ +type HttpHandler<NewDataToAdd, ResponseType> = (newUser: NewDataToAdd) => Promise<ResponseType>; + +export default class RepositoryHandler<NewDataToAdd, ResponseType> { + protected httpHandler: HttpHandler<NewDataToAdd, ResponseType>; + + constructor(httpHandler: HttpHandler<NewDataToAdd, ResponseType>) { + this.httpHandler = httpHandler; + } +} diff --git a/src/driven/utils/protocols/serviceProtocols.ts b/src/driven/utils/protocols/serviceProtocols.ts index 3e7fd0b..152c306 100644 --- a/src/driven/utils/protocols/serviceProtocols.ts +++ b/src/driven/utils/protocols/serviceProtocols.ts @@ -1,9 +1,9 @@ export type RequestMethods = 'get' | 'post' | 'put' | 'delete'; -export type apiGlobalResponseObject<DataType> = { +export type ApiGlobalResponseObject<DataType> = { type: 'Success' | 'client Error' | string; status: 200 | 400 | 401 | 500 | number; message: string; description: string; data: DataType; -} \ No newline at end of file +}; diff --git a/src/driving/application/core/common/table-row/index.tsx b/src/driving/application/core/common/table-row/index.tsx index e31ec56..353b34c 100644 --- a/src/driving/application/core/common/table-row/index.tsx +++ b/src/driving/application/core/common/table-row/index.tsx @@ -1,3 +1,3 @@ -import TableRow from "./infra/TableRowInfra"; +import TableRow from './infra/TableRowInfra'; -export default TableRow; \ No newline at end of file +export default TableRow; diff --git a/src/driving/application/core/common/table-row/infra/TableRowInfra.tsx b/src/driving/application/core/common/table-row/infra/TableRowInfra.tsx index cc375cb..4ecd83b 100644 --- a/src/driving/application/core/common/table-row/infra/TableRowInfra.tsx +++ b/src/driving/application/core/common/table-row/infra/TableRowInfra.tsx @@ -1,13 +1,13 @@ -import React from 'react' -import { ITableRowInfra } from './protocols' +import React from 'react'; import useTableRowVM from '../viewmodel/tableRowVM'; import TableRowView from '../view/TableRow'; +import { ITableRowInfra } from './protocols'; export default function TableRow(props: ITableRowInfra) { const { rowData, selectedRowId, setSelectedRowId } = props; const { rowId } = rowData; - const { isRowSelected } = useTableRowVM({selectedRowId, rowId}); + const { isRowSelected } = useTableRowVM({ selectedRowId, rowId }); - return <TableRowView isSelected={isRowSelected} rowData={rowData} setSelectedRowId={setSelectedRowId}/> + return <TableRowView isSelected={isRowSelected} rowData={rowData} setSelectedRowId={setSelectedRowId} />; } diff --git a/src/driving/application/core/common/table-row/infra/protocols.ts b/src/driving/application/core/common/table-row/infra/protocols.ts index 7f47ab5..c2a94de 100644 --- a/src/driving/application/core/common/table-row/infra/protocols.ts +++ b/src/driving/application/core/common/table-row/infra/protocols.ts @@ -1,8 +1,8 @@ export interface ITableRowInfra { selectedRowId: string; rowData: { - rowItemsTitle: string[]; + rowItemsTitle: (string | null)[]; rowId: string; - } - setSelectedRowId: React.Dispatch<React.SetStateAction<string>> -} \ No newline at end of file + }; + setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; +} diff --git a/src/driving/application/core/common/table-row/view/TableRow.tsx b/src/driving/application/core/common/table-row/view/TableRow.tsx index b84f925..5156de4 100644 --- a/src/driving/application/core/common/table-row/view/TableRow.tsx +++ b/src/driving/application/core/common/table-row/view/TableRow.tsx @@ -1,4 +1,5 @@ -import React from 'react' +/* eslint-disable react/no-array-index-key */ +import React from 'react'; import RowItem from './table-row-item/view/RowItem'; import { ITableRowProps } from './protocols'; @@ -6,10 +7,15 @@ export default function TableRowView(props: ITableRowProps) { const { isSelected, setSelectedRowId, rowData } = props; const { rowId, rowItemsTitle } = rowData; const columns = rowItemsTitle.map((rowItemTitle, index) => { - return <RowItem key={rowItemTitle} hasCheckbox={index === 0} isSelected={isSelected} title={rowItemTitle} /> - }) + return ( + <RowItem + key={(rowItemTitle || 'row') + index} + hasCheckbox={index === 0} + isSelected={isSelected} + title={rowItemTitle} + /> + ); + }); - return ( - <tr onClick={() => setSelectedRowId(rowId)}>{ columns }</tr> - ) + return <tr onClick={() => setSelectedRowId(rowId)}>{columns}</tr>; } diff --git a/src/driving/application/core/common/table-row/view/protocols.ts b/src/driving/application/core/common/table-row/view/protocols.ts index db7d5dc..eeba1fb 100644 --- a/src/driving/application/core/common/table-row/view/protocols.ts +++ b/src/driving/application/core/common/table-row/view/protocols.ts @@ -1,8 +1,8 @@ export interface ITableRowProps { isSelected: boolean; rowData: { - rowItemsTitle: string[]; + rowItemsTitle: (string | null)[]; rowId: string; - } - setSelectedRowId: React.Dispatch<React.SetStateAction<string>> -} \ No newline at end of file + }; + setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; +} diff --git a/src/driving/application/core/common/table-row/view/table-row-item/view/RowItem.tsx b/src/driving/application/core/common/table-row/view/table-row-item/view/RowItem.tsx index 3a98103..e15e71b 100644 --- a/src/driving/application/core/common/table-row/view/table-row-item/view/RowItem.tsx +++ b/src/driving/application/core/common/table-row/view/table-row-item/view/RowItem.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react' +import React from 'react'; interface IRowItemProp { - title: string; + title: string | null; hasCheckbox: boolean; - isSelected: boolean; + isSelected: boolean; } export default function RowItem(props: IRowItemProp) { @@ -11,14 +11,17 @@ export default function RowItem(props: IRowItemProp) { return ( <td className={`px-1 py-2 ${isSelected ? 'bg-primary-100' : ''}`}> <div className='w-full flex'> - { - hasCheckbox && - <span className={`checkmak-container flex justify-center items-center mr-2 transition-all ${isSelected ? 'opacity-100' : 'opacity-0'}`}> - <span className={`${isSelected ? 'visible' : 'hidden'} transition-all`}>✓</span> - </span> - } - { title } + {hasCheckbox && ( + <span + className={`checkmak-container flex justify-center items-center mr-2 transition-all ${ + isSelected ? 'opacity-100' : 'opacity-0' + }`} + > + <span className={`${isSelected ? 'visible' : 'hidden'} transition-all`}>✓</span> + </span> + )} + {title} </div> </td> - ) + ); } diff --git a/src/driving/application/core/common/table-row/viewmodel/protocols.ts b/src/driving/application/core/common/table-row/viewmodel/protocols.ts index ac8d8f2..a989642 100644 --- a/src/driving/application/core/common/table-row/viewmodel/protocols.ts +++ b/src/driving/application/core/common/table-row/viewmodel/protocols.ts @@ -5,4 +5,4 @@ export interface IUserTableRowVM { export type tableRowVMReturnType = { isRowSelected: boolean; -} +}; diff --git a/src/driving/application/core/common/table-row/viewmodel/tableRowVM.ts b/src/driving/application/core/common/table-row/viewmodel/tableRowVM.ts index 340c23b..9828d8e 100644 --- a/src/driving/application/core/common/table-row/viewmodel/tableRowVM.ts +++ b/src/driving/application/core/common/table-row/viewmodel/tableRowVM.ts @@ -1,10 +1,10 @@ -import { IUserTableRowVM, tableRowVMReturnType } from "./protocols"; +import { IUserTableRowVM, tableRowVMReturnType } from './protocols'; const useTableRowVM = (dependencies: IUserTableRowVM): tableRowVMReturnType => { const { rowId, selectedRowId } = dependencies; return { - isRowSelected: rowId === selectedRowId - } -} + isRowSelected: rowId === selectedRowId, + }; +}; -export default useTableRowVM; \ No newline at end of file +export default useTableRowVM; diff --git a/src/driving/application/core/create-user/index.tsx b/src/driving/application/core/create-user/index.tsx index 1f35bc6..774a305 100644 --- a/src/driving/application/core/create-user/index.tsx +++ b/src/driving/application/core/create-user/index.tsx @@ -1,3 +1,3 @@ -import CreateUser from "./infra/CreateUser"; +import CreateUser from './infra/CreateUser'; -export default CreateUser; \ No newline at end of file +export default CreateUser; diff --git a/src/driving/application/core/create-user/infra/CreateUser.tsx b/src/driving/application/core/create-user/infra/CreateUser.tsx index efb07df..ec77918 100644 --- a/src/driving/application/core/create-user/infra/CreateUser.tsx +++ b/src/driving/application/core/create-user/infra/CreateUser.tsx @@ -1,6 +1,43 @@ -import React from 'react' -import CreateUserView from '../view/CreateUserView' +import React from 'react'; +import createAccountAdapter from '~/driven/adapters/create-account-adapter/createAccountAdapter'; +import createProfileAdapter from '~/driven/adapters/create-profile-adapter/createProfileAdapter'; +import CreateUserInfra from '~/business-logic/core/users/create-user'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator'; +import CreateUserView from '../view/CreateUserView'; +import useCreateUserVM from '../viewmodel/CreateUserVM'; +import createUserModel from '../model/createUserModel'; export default function CreateUser() { - return <CreateUserView /> + const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); + + const { user } = userData; + // get adapters from driven layer + const createAccountDrivenAdapter = createAccountAdapter( + user as AdminUserModel, + accessTokenUpdateHandler, + notLoginAuth, + ); + const createProfileDrivenAdapter = createProfileAdapter( + user as AdminUserModel, + accessTokenUpdateHandler, + notLoginAuth, + ); + // pass to the logic and get the usecase + const createUserInfra = new CreateUserInfra(createAccountDrivenAdapter, createProfileDrivenAdapter); + const createUserLogic = createUserInfra.execute(); + // pass the usecase to the model + const { handleSubmitForm } = createUserModel({ createUserLogic }); + // pass the method to the viewmodel to call on submit + const { stateHandler, onSubmit, inputNames, error, setError } = useCreateUserVM({ handleSubmitForm }); + // get all of the needed information to the user to show + return ( + <CreateUserView + error={error} + setError={setError} + stateHandler={stateHandler} + inputNames={inputNames} + onSubmit={onSubmit} + /> + ); } diff --git a/src/driving/application/core/create-user/model/createUserModel.ts b/src/driving/application/core/create-user/model/createUserModel.ts new file mode 100644 index 0000000..6c92840 --- /dev/null +++ b/src/driving/application/core/create-user/model/createUserModel.ts @@ -0,0 +1,21 @@ +import CreateUserUsecase from '~/business-logic/core/users/create-user/usecase/createUserUsecase'; +import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; +import IUseCreateUserVm from '../viewmodel/protocols'; + +interface ICreateUserModel { + createUserLogic: CreateUserUsecase; +} + +const createUserModel = (dependencies: ICreateUserModel): IUseCreateUserVm => { + const { createUserLogic } = dependencies; + + const handleSubmitForm = async (newUserData: INewUserData) => { + await createUserLogic.execute(newUserData); + }; + + return { + handleSubmitForm, + }; +}; + +export default createUserModel; diff --git a/src/driving/application/core/create-user/view/CreateUserView.tsx b/src/driving/application/core/create-user/view/CreateUserView.tsx index c80ba27..8fb33ad 100644 --- a/src/driving/application/core/create-user/view/CreateUserView.tsx +++ b/src/driving/application/core/create-user/view/CreateUserView.tsx @@ -1,19 +1,44 @@ -import React from 'react' -import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton' -import SimpleInput from '~/driven/utils/components/inputs/simple-input/SimpleInput' -import { staticMessages } from '~/driven/utils/constants/staticMessages' +import React from 'react'; +import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton'; +import SimpleInput from '~/driven/utils/components/inputs/simple-input/SimpleInput'; +import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import Notification from '~/driven/utils/components/Notification/Notification'; +import ICreateUserViewProps from './protocols'; -export default function CreateUserView() { +export default function CreateUserView(props: ICreateUserViewProps) { + const { onSubmit, inputNames, stateHandler, error, setError } = props; + const { inputStates, inputsSetStates } = stateHandler; + + const inputs = inputNames.map((inputName) => { + const title = staticMessages.global[inputName] as string; + return ( + <SimpleInput + inputData={{ + title, + name: inputName, + }} + stateHanlder={{ + setState: inputsSetStates, + state: inputStates[inputName], + }} + key={inputName} + className='mb-4 w-[48%] px-2' + /> + ); + }); return ( - <div className='px-4 my-8'> - <div className='flex flex-wrap w-full gap-4'> - <SimpleInput title={staticMessages.global.fistname} className='mb-4 w-[48%]' /> - <SimpleInput title={staticMessages.global.lastname} className='mb-4 w-[48%]'/> - <SimpleInput title={staticMessages.global.phoneNumber} className='mb-4 w-[48%]'/> - </div> + <form onSubmit={onSubmit} className='px-4 my-8'> + {Boolean(error.message) && ( + <Notification + message={error.message} + type={error.type} + onCloseCallback={() => setError({ message: '', type: 'error' })} + /> + )} + <div className='flex flex-wrap w-full gap-4'>{inputs}</div> <div className='flex'> - <PrimaryButton onClick={() => {}} title={staticMessages.global.submit} /> + <PrimaryButton onClick={() => null} title={staticMessages.global.submit} /> </div> - </div> - ) + </form> + ); } diff --git a/src/driving/application/core/create-user/view/protocols.ts b/src/driving/application/core/create-user/view/protocols.ts new file mode 100644 index 0000000..05cea73 --- /dev/null +++ b/src/driving/application/core/create-user/view/protocols.ts @@ -0,0 +1,13 @@ +import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; +import { SetStateInputMethod } from '~/driven/utils/components/inputs/simple-input/SimpleInput'; + +export default interface ICreateUserViewProps { + onSubmit: (e: React.FormEvent) => void; + stateHandler: { + inputStates: INewUserData; + inputsSetStates: SetStateInputMethod<keyof INewUserData>; + }; + inputNames: (keyof INewUserData)[]; + error: { message: string; type: 'error' | 'success' }; + setError: React.Dispatch<React.SetStateAction<{ message: string; type: 'error' | 'success' }>>; +} diff --git a/src/driving/application/core/create-user/viewmodel/CreateUserVM.ts b/src/driving/application/core/create-user/viewmodel/CreateUserVM.ts new file mode 100644 index 0000000..f2e7785 --- /dev/null +++ b/src/driving/application/core/create-user/viewmodel/CreateUserVM.ts @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import { checkPhoneNumberInput } from '~/driven/utils/helpers/globalHelpers'; +import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; +import { AxiosError } from 'axios'; +import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import ICreateUserViewProps from '../view/protocols'; +import IUseCreateUserVm from './protocols'; + +const inputStateInitialValue: INewUserData = { + firstname: '', + lastname: '', + phonenumber: '', +}; + +const inputNames: (keyof INewUserData)[] = ['firstname', 'lastname', 'phonenumber']; + +const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps => { + const [error, setError] = useState<{ message: string; type: 'error' | 'success' }>({ message: '', type: 'error' }); + + const { handleSubmitForm } = dependencies; + const [inputsValue, setInputValues] = useState<INewUserData>(inputStateInitialValue); + + const inputsSetStates = (name: keyof INewUserData, newValue: string) => { + if (name === 'phonenumber' && !checkPhoneNumberInput(newValue)) return; + setInputValues((prev) => ({ + ...prev, + [name]: newValue, + })); + }; + + const onSubmitCreateUserForm = async (e: React.FormEvent) => { + e.preventDefault(); + try { + await handleSubmitForm(inputsValue); + setError({ message: staticMessages.global.success.createUser, type: 'success' }); + } catch (errorExc) { + if (errorExc instanceof AxiosError) { + setError({ message: errorExc.response?.data?.description, type: 'error' }); + } else if (errorExc instanceof Error) { + setError({ message: errorExc.message, type: 'error' }); + } + } + }; + + const inputStates: INewUserData = { ...inputsValue }; + + return { + stateHandler: { + inputsSetStates, + inputStates, + }, + onSubmit: onSubmitCreateUserForm, + inputNames, + error, + setError, + }; +}; + +export default useCreateUserVM; diff --git a/src/driving/application/core/create-user/viewmodel/protocols.ts b/src/driving/application/core/create-user/viewmodel/protocols.ts new file mode 100644 index 0000000..d6033ac --- /dev/null +++ b/src/driving/application/core/create-user/viewmodel/protocols.ts @@ -0,0 +1,5 @@ +import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; + +export default interface IUseCreateUserVm { + handleSubmitForm: (newUserData: INewUserData) => void; +} diff --git a/src/driving/application/core/places-list/index.tsx b/src/driving/application/core/places-list/index.tsx index 2834742..63340e5 100644 --- a/src/driving/application/core/places-list/index.tsx +++ b/src/driving/application/core/places-list/index.tsx @@ -1,3 +1,3 @@ -import PlacesList from "./infra/PlacesList"; +import PlacesList from './infra/PlacesList'; -export default PlacesList; \ No newline at end of file +export default PlacesList; diff --git a/src/driving/application/core/places-list/infra/PlacesList.tsx b/src/driving/application/core/places-list/infra/PlacesList.tsx index 6ec5cf6..b3d0422 100644 --- a/src/driving/application/core/places-list/infra/PlacesList.tsx +++ b/src/driving/application/core/places-list/infra/PlacesList.tsx @@ -1,8 +1,37 @@ -import React from 'react' -import PlacesListView from '../view/PlacesListView' -import usePlacesListVM from '../viewmodel/placesListVM' +import React from 'react'; +import getPlaces from '~/business-logic/core/places/get-places'; +import getPlacesAdapter from '~/driven/adapters/get-places-adapter/getPlacesAdapter'; +import PlacesModel from '~/business-logic/core/places/common/model/placesModel'; +import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers'; +import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import PlacesListView from '../view/PlacesListView'; +import usePlacesListVM from '../viewmodel/placesListVM'; +import placesListModel from '../model/placesListModel'; -export default function PlacessList() { - const { selectedRowId, setSelectedRowId } = usePlacesListVM() - return <PlacesListView selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} /> +const prepareTheLogicForModel = () => { + const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); + + const { user } = userData; + const gettingPlacesDrivenAdapter = getPlacesAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth); + const { url } = gettingPlacesDrivenAdapter; + const getingPlacesLogic = getPlaces(gettingPlacesDrivenAdapter); + return { getingPlacesLogic, url }; +}; + +export interface IPlacesListProps { + selectedRowId: string; + setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; +} + +export default function PlacessList(props: IPlacesListProps) { + const { selectedRowId, setSelectedRowId } = props; + const { getingPlacesLogic, url } = prepareTheLogicForModel(); + const placesModel = async () => await placesListModel(getingPlacesLogic); + + const useGetPlacesList = prepareStateManagementForVM<PlacesModel>(url, placesModel); + const { placesData } = usePlacesListVM({ + useGetPlacesList, + }); + return <PlacesListView placesList={placesData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />; } diff --git a/src/driving/application/core/places-list/infra/protocols.ts b/src/driving/application/core/places-list/infra/protocols.ts deleted file mode 100644 index 6abdb94..0000000 --- a/src/driving/application/core/places-list/infra/protocols.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IPlacesListInfraProps { - -} \ No newline at end of file diff --git a/src/driving/application/core/places-list/model/placesListModel.ts b/src/driving/application/core/places-list/model/placesListModel.ts new file mode 100644 index 0000000..050e05d --- /dev/null +++ b/src/driving/application/core/places-list/model/placesListModel.ts @@ -0,0 +1,10 @@ +import { getPlacesModel } from './protocols'; + +const placesListModel: getPlacesModel = async (getPlaces) => { + // get the method for handling the logic + const places = await getPlaces(); + return places; + // handling the errors +}; + +export default placesListModel; diff --git a/src/driving/application/core/places-list/model/protocols.ts b/src/driving/application/core/places-list/model/protocols.ts new file mode 100644 index 0000000..cd68b37 --- /dev/null +++ b/src/driving/application/core/places-list/model/protocols.ts @@ -0,0 +1,4 @@ +import PlacesModel from '~/business-logic/core/places/common/model/placesModel'; +import { getPlacesReturnPort } from '~/business-logic/core/places/get-places/port'; + +export type getPlacesModel = (getPlaces: getPlacesReturnPort) => Promise<PlacesModel>; diff --git a/src/driving/application/core/places-list/view/PlacesListView.tsx b/src/driving/application/core/places-list/view/PlacesListView.tsx index 336da1d..8258417 100644 --- a/src/driving/application/core/places-list/view/PlacesListView.tsx +++ b/src/driving/application/core/places-list/view/PlacesListView.tsx @@ -1,72 +1,60 @@ -import React from 'react' -import RowItem from '../../common/table-row/view/table-row-item/view/RowItem' -import TableRow from '../../common/table-row' -import { IPlacesListProps } from './protocols' +import React, { useMemo } from 'react'; import { staticMessages } from '~/driven/utils/constants/staticMessages'; - +import Loading from '~/driven/utils/components/loading/Loading'; +import Places from '~/business-logic/core/places/common/entity/placeEntity'; +import TableRow from '../../common/table-row'; +import { IPlacesListProps } from './protocols'; export default function UsersListView(props: IPlacesListProps) { - const { selectedRowId, setSelectedRowId } = props; + const { selectedRowId, setSelectedRowId, placesList } = props; - const rows = () => { - const placesdata = [ - { - id: '1', - place_id: '6440020b89366fdcaf15a8c2', - title: 'flat demoplace ', - status: 'demo', - address: 'demoplace' - }, - { - id: '2', - place_id: '6440020b89366fdcaf15asdfa', - title: 'flat demoplace second ', - status: 'demo second', - address: 'demoplace second' - } - ] + const rows = useMemo(() => { + if (!placesList.data) return null; - const rowData = { - rowItemsTitle: placesdata.map(places => { - return [ - places.id, - places.title, - places.status, - places.address, - ] - }), - rowId: placesdata[0].id - } + return placesList.data.getData().map((places) => { + const rowData = { + rowItemsTitle: [places.name, places.placeType, places.qr], + rowId: places.id, + }; + return ( + <TableRow + key={rowData.rowId} + rowData={rowData} + selectedRowId={selectedRowId} + setSelectedRowId={setSelectedRowId} + /> + ); + }); + }, [placesList]); - return placesdata.map(places => { - const rowData = { - rowItemsTitle: [ - places.id, - places.title, - places.status, - places.address, - '' - ], - rowId: places.id, - } - return <TableRow key={rowData.rowId} rowData={rowData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} /> - }) - } + if (placesList.isLoading) + return ( + <div className='flex justify-center items-center'> + <Loading /> + </div> + ); + const tableTitles: Pick<Places, 'name' | 'placeType' | 'qr'> = { + name: staticMessages.global.title, + placeType: staticMessages.global.placeType, + qr: staticMessages.global.qrCode, + }; + const titles = Object.keys(tableTitles).map((titleKey) => { + const key = titleKey as keyof typeof tableTitles; + + const title = tableTitles[key]; + return ( + <th key={key} className='py-3'> + {title} + </th> + ); + }); return ( - <table className='table-auto rounded-md w-full text-sm'> - <thead className='text-txt-medium font-bold'> - <tr> - <th className='py-3'>{staticMessages.global.place_id}</th> - <th className='py-3'>{staticMessages.global.title}</th> - <th className='py-3'>{staticMessages.global.status}</th> - <th className='py-3'>{staticMessages.global.address}</th> - <th className='py-3'>{staticMessages.global.qrCode}</th> - </tr> - </thead> - <tbody> - { rows() } - </tbody> + <table className='table-auto rounded-md w-full text-sm h-fit'> + <thead className='text-txt-medium font-bold'> + <tr>{titles}</tr> + </thead> + <tbody>{rows}</tbody> </table> - ) + ); } diff --git a/src/driving/application/core/places-list/view/protocols.ts b/src/driving/application/core/places-list/view/protocols.ts index 90a210f..79048e3 100644 --- a/src/driving/application/core/places-list/view/protocols.ts +++ b/src/driving/application/core/places-list/view/protocols.ts @@ -1,4 +1,11 @@ +import PlacesModel from '~/business-logic/core/places/common/model/placesModel'; + export interface IPlacesListProps { + placesList: { + data: PlacesModel | undefined; + isLoading: boolean; + error?: string | undefined; + }; selectedRowId: string; setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; -} \ No newline at end of file +} diff --git a/src/driving/application/core/places-list/viewmodel/placesListVM.ts b/src/driving/application/core/places-list/viewmodel/placesListVM.ts index 9c197f8..c44eabf 100644 --- a/src/driving/application/core/places-list/viewmodel/placesListVM.ts +++ b/src/driving/application/core/places-list/viewmodel/placesListVM.ts @@ -1,13 +1,24 @@ -import { useState } from "react"; -import { placesListReturnType } from "./protocols"; +import { useState } from 'react'; +import PlacesModel from '~/business-logic/core/places/common/model/placesModel'; +import { placesListReturnType } from './protocols'; -const usePlacesListVM = (): placesListReturnType => { - const [ selectedRowId, setSelectedRowId ] = useState<string>(''); +interface IPlacesListVM { + useGetPlacesList: () => { + data: PlacesModel | undefined; + isLoading: boolean; + error?: string | undefined; + }; +} +const usePlacesListVM = (dependencies: IPlacesListVM): placesListReturnType => { + const { useGetPlacesList } = dependencies; + const placesData = useGetPlacesList(); + const [selectedRowId, setSelectedRowId] = useState<string>(''); return { selectedRowId, setSelectedRowId, - } -} + placesData, + }; +}; -export default usePlacesListVM; \ No newline at end of file +export default usePlacesListVM; diff --git a/src/driving/application/core/places-list/viewmodel/protocols.ts b/src/driving/application/core/places-list/viewmodel/protocols.ts index a84f296..cd4cf4d 100644 --- a/src/driving/application/core/places-list/viewmodel/protocols.ts +++ b/src/driving/application/core/places-list/viewmodel/protocols.ts @@ -1,9 +1,12 @@ -import React from "react"; - -export interface IPlacesListVM { -} +import React from 'react'; +import PlacesModel from '~/business-logic/core/places/common/model/placesModel'; export type placesListReturnType = { selectedRowId: string; setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; -} \ No newline at end of file + placesData: { + data: PlacesModel | undefined; + isLoading: boolean; + error?: string | undefined; + }; +}; diff --git a/src/driving/application/core/users-list/index.tsx b/src/driving/application/core/users-list/index.tsx index b029efe..304d5e9 100644 --- a/src/driving/application/core/users-list/index.tsx +++ b/src/driving/application/core/users-list/index.tsx @@ -1,3 +1,3 @@ -import UsersList from "./infra/UsersList"; +import UsersList from './infra/UsersList'; -export default UsersList; \ No newline at end of file +export default UsersList; diff --git a/src/driving/application/core/users-list/infra/UsersList.tsx b/src/driving/application/core/users-list/infra/UsersList.tsx index 505975d..5b71f59 100644 --- a/src/driving/application/core/users-list/infra/UsersList.tsx +++ b/src/driving/application/core/users-list/infra/UsersList.tsx @@ -1,8 +1,35 @@ -import React from 'react' -import useUsersListVM from '../viewmodel/usersListVM' -import UsersListView from '../view/UsersListView' +import React from 'react'; +import getUsersAdapter from '~/driven/adapters/get-users-adapter/getUsersAdapter'; +import getUsers from '~/business-logic/core/users/get-users'; +import UsersModel from '~/business-logic/core/users/common/data/model/usersModel'; +import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers'; +import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import useUsersListVM from '../viewmodel/usersListVM'; +import UsersListView from '../view/UsersListView'; +import usersListModel from '../model/usersListModel'; -export default function UsersList() { - const { selectedRowId, setSelectedRowId } = useUsersListVM() - return <UsersListView selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} /> +const usePrepareTheLogicForModel = () => { + const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); + + const { user } = userData; + const gettingUsersDrivenAdapter = getUsersAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth); + const { url } = gettingUsersDrivenAdapter; + const getingusersLogic = getUsers(gettingUsersDrivenAdapter); + return { getingusersLogic, url }; +}; + +export interface IUsersListProps { + selectedRowId: string; + setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; +} +export default function UsersList(props: IUsersListProps) { + const { selectedRowId, setSelectedRowId } = props; + const { getingusersLogic, url } = usePrepareTheLogicForModel(); + const usersModel = async () => await usersListModel(getingusersLogic); + const useGetusersList = prepareStateManagementForVM<UsersModel>(url, usersModel); + const { usersData } = useUsersListVM({ + useGetusersList, + }); + return <UsersListView usersList={usersData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />; } diff --git a/src/driving/application/core/users-list/infra/protocols.ts b/src/driving/application/core/users-list/infra/protocols.ts deleted file mode 100644 index 88b4866..0000000 --- a/src/driving/application/core/users-list/infra/protocols.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface IUsersListInfraProps { - -} \ No newline at end of file diff --git a/src/driving/application/core/users-list/model/protocols.ts b/src/driving/application/core/users-list/model/protocols.ts new file mode 100644 index 0000000..45c4c6d --- /dev/null +++ b/src/driving/application/core/users-list/model/protocols.ts @@ -0,0 +1,4 @@ +import UsersModel from '~/business-logic/core/users/common/data/model/usersModel'; +import { getUsersReturnPort } from '~/business-logic/core/users/get-users/ports'; + +export type getUsersModel = (getUsers: getUsersReturnPort) => Promise<UsersModel>; diff --git a/src/driving/application/core/users-list/model/usersListModel.ts b/src/driving/application/core/users-list/model/usersListModel.ts new file mode 100644 index 0000000..cc3cf69 --- /dev/null +++ b/src/driving/application/core/users-list/model/usersListModel.ts @@ -0,0 +1,10 @@ +import { getUsersModel } from './protocols'; + +const usersListModel: getUsersModel = async (getUsers) => { + // get the method for handling the logic + const users = await getUsers(); + return users; + // handling the errors +}; + +export default usersListModel; diff --git a/src/driving/application/core/users-list/view/UsersListView.tsx b/src/driving/application/core/users-list/view/UsersListView.tsx index ad8bb9c..18ef379 100644 --- a/src/driving/application/core/users-list/view/UsersListView.tsx +++ b/src/driving/application/core/users-list/view/UsersListView.tsx @@ -1,60 +1,60 @@ -import React from 'react' -import RowItem from '../../common/table-row/view/table-row-item/view/RowItem' -import TableRow from '../../common/table-row' -import { IUserListProps } from './protocols' +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useMemo } from 'react'; import { staticMessages } from '~/driven/utils/constants/staticMessages'; - +import Users from '~/business-logic/core/users/common/entity/entity'; +import Loading from '~/driven/utils/components/loading/Loading'; +import TableRow from '../../common/table-row'; +import { IUserListProps } from './protocols'; export default function UsersListView(props: IUserListProps) { - const { selectedRowId, setSelectedRowId } = props; + const { selectedRowId, setSelectedRowId, usersList } = props; + const rows = useMemo(() => { + if (!usersList.data) return null; - const rows = () => { - const userdata = [ - { - id: '1', - firstname: 'behnam', - lastname: 'rahimpour' - }, - { - id: '2', - firstname: 'Salar', - lastname: 'Sali' - } - ] + return usersList.data.getData().map((user) => { + const rowData = { + rowItemsTitle: [user.firstname, user.lastname], + rowId: user.accountId, + }; + return ( + <TableRow + key={rowData.rowId} + rowData={rowData} + selectedRowId={selectedRowId} + setSelectedRowId={setSelectedRowId} + /> + ); + }); + }, [usersList]); + const tableTitles: Pick<Users, 'firstname' | 'lastname'> = { + firstname: staticMessages.global.firstname, + lastname: staticMessages.global.lastname, + }; - const rowData = { - rowItemsTitle: userdata.map(user => { - return [ - user.firstname, - user.lastname - ] - }), - rowId: userdata[0].id - } + const titles = Object.keys(tableTitles).map((titleKey) => { + const key = titleKey as keyof typeof tableTitles; - return userdata.map(user => { - const rowData = { - rowItemsTitle: [ - user.firstname, - user.lastname - ], - rowId: user.id, - } - return <TableRow key={rowData.rowId} rowData={rowData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} /> - }) - } + const title = tableTitles[key]; + return ( + <th key={key} className='py-3'> + {title} + </th> + ); + }); + + if (usersList.isLoading) + return ( + <div className='flex justify-center items-center'> + <Loading /> + </div> + ); return ( - <table className='table-auto rounded-md w-full text-sm'> - <thead className='text-txt-medium font-bold'> - <tr> - <th className='py-3'>{staticMessages.global.fistname}</th> - <th className='py-3'>{staticMessages.global.lastname}</th> - </tr> - </thead> - <tbody> - { rows() } - </tbody> + <table className='table-auto rounded-md w-full text-sm h-fit'> + <thead className='text-txt-medium font-bold'> + <tr>{titles}</tr> + </thead> + <tbody>{rows}</tbody> </table> - ) + ); } diff --git a/src/driving/application/core/users-list/view/protocols.ts b/src/driving/application/core/users-list/view/protocols.ts index 1690a6c..2064d84 100644 --- a/src/driving/application/core/users-list/view/protocols.ts +++ b/src/driving/application/core/users-list/view/protocols.ts @@ -1,4 +1,11 @@ +import UsersModel from '~/business-logic/core/users/common/data/model/usersModel'; + export interface IUserListProps { selectedRowId: string; setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; -} \ No newline at end of file + usersList: { + data: UsersModel | undefined; + isLoading: boolean; + error?: string | undefined; + }; +} diff --git a/src/driving/application/core/users-list/viewmodel/protocols.ts b/src/driving/application/core/users-list/viewmodel/protocols.ts index 86df5aa..00963e1 100644 --- a/src/driving/application/core/users-list/viewmodel/protocols.ts +++ b/src/driving/application/core/users-list/viewmodel/protocols.ts @@ -1,9 +1,12 @@ -import React from "react"; - -export interface IUsersListVM { -} +import React from 'react'; +import UsersModel from '~/business-logic/core/users/common/data/model/usersModel'; export type userListReturnType = { selectedRowId: string; setSelectedRowId: React.Dispatch<React.SetStateAction<string>>; -} \ No newline at end of file + usersData: { + data: UsersModel | undefined; + isLoading: boolean; + error?: string | undefined; + }; +}; diff --git a/src/driving/application/core/users-list/viewmodel/usersListVM.ts b/src/driving/application/core/users-list/viewmodel/usersListVM.ts index 6cdbfb8..b8c78db 100644 --- a/src/driving/application/core/users-list/viewmodel/usersListVM.ts +++ b/src/driving/application/core/users-list/viewmodel/usersListVM.ts @@ -1,13 +1,24 @@ -import { useState } from "react"; -import { userListReturnType } from "./protocols"; +import { useState } from 'react'; +import UsersModel from '~/business-logic/core/users/common/data/model/usersModel'; +import { userListReturnType } from './protocols'; -const useUsersListVM = (): userListReturnType => { - const [ selectedRowId, setSelectedRowId ] = useState<string>(''); +interface IUsersListVM { + useGetusersList: () => { + data: UsersModel | undefined; + isLoading: boolean; + error?: string | undefined; + }; +} +const useUsersListVM = (dependencies: IUsersListVM): userListReturnType => { + const { useGetusersList } = dependencies; + const [selectedRowId, setSelectedRowId] = useState<string>(''); + const usersData = useGetusersList(); return { selectedRowId, setSelectedRowId, - } -} + usersData, + }; +}; -export default useUsersListVM; \ No newline at end of file +export default useUsersListVM; diff --git a/src/driving/application/generic/authentication/index.tsx b/src/driving/application/generic/authentication/index.tsx new file mode 100644 index 0000000..51151e3 --- /dev/null +++ b/src/driving/application/generic/authentication/index.tsx @@ -0,0 +1,3 @@ +import Authentication from './infra/Authentication'; + +export default Authentication; diff --git a/src/driving/application/generic/authentication/infra/Authentication.tsx b/src/driving/application/generic/authentication/infra/Authentication.tsx new file mode 100644 index 0000000..76ff8aa --- /dev/null +++ b/src/driving/application/generic/authentication/infra/Authentication.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import authAdminPhoneNumberDriven from '~/driven/adapters/auth-admin-phonenumber/authAdminPhoneNumberDriven'; +import phonenumberAuthInfra from '~/business-logic/generic/admin-user/authentication/phonnumber-auth'; +import authAdminLogin from '~/driven/adapters/auth-admin-login/authAdminLogin'; +import otpAuthInfra from '~/business-logic/generic/admin-user/authentication/otp-auth'; +import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator'; +import AuthenticationView from '../view/AuthenticationView'; + +export default function Authentication() { + const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); + const { user } = userData; + + const authPhonenumberDriven = authAdminPhoneNumberDriven(user, accessTokenUpdateHandler, notLoginAuth); + const authPhoneLogic = phonenumberAuthInfra(authPhonenumberDriven); + + const authLoginDriven = authAdminLogin(user, accessTokenUpdateHandler, notLoginAuth); + const otpAuthLogic = otpAuthInfra(authLoginDriven.httpHandler); + + return <AuthenticationView authPhone={authPhoneLogic} otpAuth={otpAuthLogic} />; +} diff --git a/src/driving/application/generic/authentication/otp-code-inputs/index.tsx b/src/driving/application/generic/authentication/otp-code-inputs/index.tsx new file mode 100644 index 0000000..6162c1a --- /dev/null +++ b/src/driving/application/generic/authentication/otp-code-inputs/index.tsx @@ -0,0 +1,3 @@ +import OtpCode from './infra/OtpCode'; + +export default OtpCode; diff --git a/src/driving/application/generic/authentication/otp-code-inputs/infra/OtpCode.tsx b/src/driving/application/generic/authentication/otp-code-inputs/infra/OtpCode.tsx new file mode 100644 index 0000000..982d05d --- /dev/null +++ b/src/driving/application/generic/authentication/otp-code-inputs/infra/OtpCode.tsx @@ -0,0 +1,18 @@ +/* eslint-disable react/display-name */ +import React from 'react'; +import OtpCodeView from '../view/OtpCodeView'; +import useOtpCodeVm from '../viewmodel/OtpCodeVM'; + +const OtpCode = React.forwardRef<HTMLInputElement[]>((_props, otpCharRef) => { + const { eventHandlers } = useOtpCodeVm({ + otpChar: otpCharRef as unknown as React.MutableRefObject<HTMLInputElement[]>, + }); + return ( + <OtpCodeView + eventHandlers={eventHandlers} + otpChar={otpCharRef as unknown as React.MutableRefObject<HTMLInputElement[]>} + /> + ); +}); + +export default OtpCode; diff --git a/src/driving/application/generic/authentication/otp-code-inputs/view/OtpCodeView.tsx b/src/driving/application/generic/authentication/otp-code-inputs/view/OtpCodeView.tsx new file mode 100644 index 0000000..0f7b7ec --- /dev/null +++ b/src/driving/application/generic/authentication/otp-code-inputs/view/OtpCodeView.tsx @@ -0,0 +1,38 @@ +/* eslint-disable consistent-return */ +/* eslint-disable no-return-assign */ +/* eslint-disable react/no-array-index-key */ +import React from 'react'; +import { staticMessages } from '~/driven/utils/constants/staticMessages'; + +export interface IOtpCodeView { + otpChar: React.MutableRefObject<HTMLInputElement[]>; + eventHandlers: { + handleFocusInput: (e: React.FocusEvent) => void; + handleKeyPressInput: (e: React.KeyboardEvent) => void; + }; +} + +export default function OtpCodeView(props: IOtpCodeView) { + const { eventHandlers, otpChar } = props; + const { handleFocusInput, handleKeyPressInput } = eventHandlers; + const otpInputs = Array.from({ length: 6 }).map((digit, i) => ( + <input + tabIndex={i + 1} + ref={(el: HTMLInputElement) => (otpChar.current[i] = el)} + key={`otp_char_${i}`} + className='font-bold text-base inline-block w-5 md:w-6 lg:w-8 xl:w-10 bg-transparent text-center focus:outline-none' + maxLength={1} + defaultValue='_' + placeholder='_' + onClick={(e) => e.stopPropagation()} + onFocus={handleFocusInput} + onKeyDown={handleKeyPressInput} + /> + )); + return ( + <div className='mb-9 justify-end items-center text-xs self-start'> + <div className='text-xs mb-9 self-start text-txt-medium'>{staticMessages.global.enterOtpCode}</div> + <div className='w-full flex gap-4'>{otpInputs}</div> + </div> + ); +} diff --git a/src/driving/application/generic/authentication/otp-code-inputs/viewmodel/OtpCodeVM.ts b/src/driving/application/generic/authentication/otp-code-inputs/viewmodel/OtpCodeVM.ts new file mode 100644 index 0000000..3d05da3 --- /dev/null +++ b/src/driving/application/generic/authentication/otp-code-inputs/viewmodel/OtpCodeVM.ts @@ -0,0 +1,114 @@ +import { useEffect } from 'react'; +import { IOtpCodeView } from '../view/OtpCodeView'; + +interface IOtpCodeVm { + otpChar: React.MutableRefObject<HTMLInputElement[]>; +} + +type useOtpCodeReturnType = Pick<IOtpCodeView, 'eventHandlers'>; + +const useOtpCodeVm = (dependencies: IOtpCodeVm): useOtpCodeReturnType => { + const { otpChar } = dependencies; + + function focusToInput(target: HTMLInputElement, inputTabIndex: number) { + const input = target.parentElement?.querySelector(`input[tabindex="${inputTabIndex}"]`) as + | HTMLInputElement + | undefined; + + if (!input) return; + + setTimeout(() => input.focus(), 150); + } + + const handleFocusFirstInput = () => { + if (!otpChar.current.length) return; + + otpChar.current[0].focus(); + }; + + useEffect(() => { + handleFocusFirstInput(); + }, []); + + const handleFocusInput = (e: React.FocusEvent) => { + const target = e.target as HTMLInputElement; + + // check previous inputs are not empty + const currentIndex = target.getAttribute('tabindex'); + + if (!currentIndex || +currentIndex === 1) { + target.select(); + return; + } + + // get first previous empty + let isFindEmptyInput = false; + const firstEmptyInput = otpChar.current.find((item) => { + const otpItemIndex = item.getAttribute('tabindex'); + + if (!otpItemIndex) return false; + + const isInputEmpty = !item.value.trim() || item.value.trim() === '_'; + + if (+otpItemIndex < +currentIndex && isInputEmpty && !isFindEmptyInput) { + isFindEmptyInput = true; + return true; + } + + return false; + }); + + if (firstEmptyInput) { + firstEmptyInput.select(); + return; + } + + // focus to it + target.select(); + }; + + function goToPreviousInput(target: HTMLInputElement, currentIndex: number) { + const preIndex = +currentIndex - 1; + + if (!preIndex) return; + + focusToInput(target, preIndex); + } + + function goToNextInput(target: HTMLInputElement, currentIndex: number) { + // get next index + const nextIndex = +currentIndex + 1; + + if (!nextIndex) return; + + focusToInput(target, nextIndex); + } + + const handleKeyPressInput = (e: React.KeyboardEvent) => { + // target + const target = e.target as HTMLInputElement; + + // get current index + const currentIndex = target.getAttribute('tabindex'); + + if (!currentIndex) return; + + const isRemoveChar = e.key.toLowerCase() === 'backspace'; + + if (isRemoveChar && +currentIndex !== 1) { + goToPreviousInput(target, +currentIndex); + return; + } + + goToNextInput(target, +currentIndex); + }; + + return { + eventHandlers: { + handleFocusInput, + handleKeyPressInput, + }, + }; +}; + +export default useOtpCodeVm; diff --git a/src/driving/application/generic/authentication/view/AuthenticationView.tsx b/src/driving/application/generic/authentication/view/AuthenticationView.tsx new file mode 100644 index 0000000..2b02379 --- /dev/null +++ b/src/driving/application/generic/authentication/view/AuthenticationView.tsx @@ -0,0 +1,85 @@ +import React, { useRef, useState } from 'react'; +import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton'; +import { icons } from '~/driven/utils/constants/assertUrls'; +import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import OtpAuthUsecase from '~/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpAuthUsecase'; +import PhonenumberAuthUsecase from '~/business-logic/generic/admin-user/authentication/phonnumber-auth/usecase/phonenumberAuthUsecase'; +import StorageService from '~/driven/boundaries/storage-boundary'; +import { appConfig } from '~/driven/utils/configs/appConfig'; +import Notification from '~/driven/utils/components/Notification/Notification'; +import OtpAuthUsecaseException from '~/business-logic/generic/admin-user/authentication/otp-auth/usecase/otpException'; +import { AxiosError } from 'axios'; +import { useUser } from '~/driven/utils/helpers/contexts/userContext'; +import OtpCode from '../otp-code-inputs'; +import PhoneNumberAuth from './PhoneNumberAuth'; + +interface IAuthenticationView { + authPhone: PhonenumberAuthUsecase; + otpAuth: OtpAuthUsecase; +} + +/** + * @todo should seperate logic and ui logic from the view + */ +export default function AuthenticationView(props: IAuthenticationView) { + const { authPhone, otpAuth } = props; + const [phoneNumberValue, setPhoneNumberValue] = useState(''); + const [error, setError] = useState(''); + const { setUser } = useUser(); + const otpChar = useRef<HTMLInputElement[]>([]); + const statesName = { + phonenumber: <PhoneNumberAuth stateData={{ setState: setPhoneNumberValue, stateValue: phoneNumberValue }} />, + otp: <OtpCode ref={otpChar} />, + }; + const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber'); + + const submitForm = async (e: React.FormEvent) => { + e.preventDefault(); + try { + if (authState === 'phonenumber') { + await authPhone.execute({ phonenumber: phoneNumberValue }); + setAuthState('otp'); + } + if (authState === 'otp') { + const otp = otpChar.current + .map((inputItem) => { + return inputItem.value; + }) + .join(''); + const userModel = await otpAuth.execute({ otp, phonenumber: phoneNumberValue }); + const storage = StorageService.localStorage(); + storage.setData(appConfig.adminUserStorageKey, userModel); + if (setUser) setUser(userModel); + } + } catch (errorException) { + if (errorException instanceof OtpAuthUsecaseException) { + setError(staticMessages.global.errors.otp); + } else if (errorException instanceof AxiosError) { + setError(errorException.response?.data?.description); + } else if (errorException instanceof Error) { + setError(errorException.message); + } + } + }; + return ( + <div className='main-auth flex flex-nowrap justify-start flex-row-reverse h-screen w-screen'> + {Boolean(error) && <Notification message={error} type='error' onCloseCallback={() => setError('')} />} + <form + onSubmit={submitForm} + className='w-full px-7 md:px-20 md:w-[50%] lg:w-[35%] min-w-[10rem] h-full shadow-lg shadow-slate-400 flex flex-col items-center justify-start pt-12' + > + <div className='w-48 h-[35%]'> + <img src={icons.logoBlack} className='w-full h-12' alt='page icon' /> + </div> + <div className='font-normal mb-4 text-lg self-start'>{staticMessages.global.enterPanel}</div> + {statesName[authState]} + <PrimaryButton + onClick={() => null} + title={staticMessages.global.submit} + className='[background:var(--color-gradient-button)] hover:brightness-90 transition-all w-full h-11' + /> + </form> + <div className='hidden md:flex md:w-[50%] lg:w-[65%] h-full' /> + </div> + ); +} diff --git a/src/driving/application/generic/authentication/view/PhoneNumberAuth.tsx b/src/driving/application/generic/authentication/view/PhoneNumberAuth.tsx new file mode 100644 index 0000000..aeac495 --- /dev/null +++ b/src/driving/application/generic/authentication/view/PhoneNumberAuth.tsx @@ -0,0 +1,32 @@ +import SimpleInput, { SetStateInputMethod } from '~/driven/utils/components/inputs/simple-input/SimpleInput'; +import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import { checkPhoneNumberInput } from '~/driven/utils/helpers/globalHelpers'; + +interface IPhoneNumberAuth { + stateData: { + stateValue: string; + setState: React.Dispatch<React.SetStateAction<string>>; + }; +} +export default function PhoneNumberAuth(props: IPhoneNumberAuth) { + const { stateData } = props; + const { setState, stateValue } = stateData; + const onChangeInput: SetStateInputMethod<typeof staticMessages.global.phonenumber> = ( + _name: typeof staticMessages.global.phonenumber, + newValue: string, + ) => { + if (!checkPhoneNumberInput(newValue)) return; + setState(newValue); + }; + + return ( + <> + <div className='text-txt-medium text-xs mb-9 self-start'>{staticMessages.global.enterPhoneNumber}</div> + <SimpleInput + inputData={{ name: staticMessages.global.phonenumber, title: staticMessages.global.phonenumber }} + stateHanlder={{ setState: onChangeInput, state: stateValue }} + className='mb-9 w-full self-start' + /> + </> + ); +} diff --git a/src/driving/application/support/sidebar/index.tsx b/src/driving/application/support/sidebar/index.tsx index c6910ce..0004865 100644 --- a/src/driving/application/support/sidebar/index.tsx +++ b/src/driving/application/support/sidebar/index.tsx @@ -1,3 +1,3 @@ -import Sidebar from "./view/Sidebar"; +import Sidebar from './view/Sidebar'; -export default Sidebar; \ No newline at end of file +export default Sidebar; diff --git a/src/driving/application/support/sidebar/view/Sidebar.tsx b/src/driving/application/support/sidebar/view/Sidebar.tsx index 0af298d..f3af15d 100644 --- a/src/driving/application/support/sidebar/view/Sidebar.tsx +++ b/src/driving/application/support/sidebar/view/Sidebar.tsx @@ -1,29 +1,41 @@ -import React from 'react' -import { Link, useLocation } from 'react-router-dom' -import { routesData } from '~/driven/utils/configs/appConfig' -import { icons } from '~/driven/utils/constants/assertUrls' +import React from 'react'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { routesData } from '~/driven/utils/configs/appConfig'; +import { icons } from '~/driven/utils/constants/assertUrls'; +import { useUser } from '~/driven/utils/helpers/contexts/userContext'; +import { navigateToAuth } from '~/driven/utils/helpers/globalHelpers'; export default function Sidebar() { - const isCurrentPage = useLocation() - - const pages = Object.keys(routesData).map(routeKey => { - const key = routeKey as keyof typeof routesData + const isCurrentPage = useLocation(); + const userCTX = useUser(); + const navigator = useNavigate(); + const pages = Object.keys(routesData).map((routeKey) => { + const key = routeKey as keyof typeof routesData; return ( - <Link key={key} to={routesData[key].path} className={`flex text-white mb-6 text-sm w-full py-2 pl-2 rounded-lg ${ isCurrentPage.pathname === routesData[key].path ? 'bg-primary-300' : ''}`}> - <img src={routesData[key].icon} className='mr-2'/> - <div>{routesData[key].title}</div> - </Link> - ) - }) + <Link + key={key} + to={routesData[key].path} + className={`flex text-white mb-6 text-sm w-full py-2 pl-2 rounded-lg ${ + isCurrentPage.pathname === routesData[key].path ? 'bg-primary-300' : '' + }`} + > + <img src={routesData[key].icon} alt='page icon' className='mr-2' /> + <div>{routesData[key].title}</div> + </Link> + ); + }); return ( <aside className='w-[15rem] min-w-[10rem] [background:var(--color-gradient-primary-dark)] p-4 pt-6'> - <div className='logo'> - <img src={icons.logo} alt="logo icon" /> - </div> - <div className='mt-14 flex flex-col items-baseline'> - { pages } + <div className='logo'> + <img src={icons.logo} alt='logo icon' /> + </div> + <div className='mt-14 flex flex-col items-baseline'> + {pages} + <div className='mt-auto text-white px-3 absolute bottom-5'> + <button onClick={() => navigateToAuth(userCTX, navigator)}>Log out</button> </div> + </div> </aside> - ) + ); } diff --git a/src/driving/main/App.tsx b/src/driving/main/App.tsx index 9ae6997..649fe0f 100644 --- a/src/driving/main/App.tsx +++ b/src/driving/main/App.tsx @@ -1,13 +1,21 @@ -import { BrowserRouter as RouterWrapper } from 'react-router-dom'; -import Router from './Router/Router'; +import { useUser } from '~/driven/utils/helpers/contexts/userContext'; import './style/App.css'; +import { useEffect } from 'react'; +import { appConfig } from '~/driven/utils/configs/appConfig'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import StorageService from '~/driven/boundaries/storage-boundary'; +import Router from './Router/Router'; function App() { - return ( - <RouterWrapper> - <Router /> - </RouterWrapper> - ); + const data = useUser(); + const { setUser } = data; + + useEffect(() => { + const storage = StorageService.localStorage<AdminUserModel>(); + const currentUser = storage.getData(appConfig.adminUserStorageKey); + if (currentUser && currentUser.adminUserData.accessToken && setUser) setUser(currentUser); + }, []); + return <Router />; } export default App; diff --git a/src/driving/main/Router/Router.tsx b/src/driving/main/Router/Router.tsx index c2506cc..d674476 100644 --- a/src/driving/main/Router/Router.tsx +++ b/src/driving/main/Router/Router.tsx @@ -3,16 +3,21 @@ import Home from '~/driving/main/pages'; import { routes } from '~/driven/utils/configs/appConfig'; import CreateUser from '../pages/CreateUser'; import MainPageLayout from '../pages/layouts/MainPageLayout'; +import AuthenticationPage from '../pages/Authentication'; +import UserLoginLayout from '../pages/layouts/UserLoginLayout'; export default function Router() { const location = useLocation(); return ( <Routes location={location} key={location.key}> - <Route element={<MainPageLayout />} > + <Route element={<MainPageLayout />}> <Route index element={<Home />} /> <Route path={routes.createUser} element={<CreateUser />} /> </Route> + <Route element={<UserLoginLayout />}> + <Route path={routes.authentication} element={<AuthenticationPage />} /> + </Route> <Route path='*' element={<Navigate to={routes.usersList} replace />} /> </Routes> ); diff --git a/src/driving/main/index.tsx b/src/driving/main/index.tsx index 49a87aa..240b5cf 100644 --- a/src/driving/main/index.tsx +++ b/src/driving/main/index.tsx @@ -1,10 +1,12 @@ import ReactDOM from 'react-dom/client'; -import { Provider } from 'react-redux'; +import { UserProvider } from '~/driven/utils/helpers/contexts/userContext'; +import { BrowserRouter as RouterWrapper } from 'react-router-dom'; import App from './App'; -import store from '~/driven/boundaries/state-management-boundary/store/store'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - <Provider store={store}> - <App /> - </Provider>, + <UserProvider> + <RouterWrapper> + <App /> + </RouterWrapper> + </UserProvider>, ); diff --git a/src/driving/main/pages/Authentication.tsx b/src/driving/main/pages/Authentication.tsx new file mode 100644 index 0000000..c4dcb47 --- /dev/null +++ b/src/driving/main/pages/Authentication.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import Authentication from '~/driving/application/generic/authentication'; + +export default function AuthenticationPage() { + return <Authentication />; +} diff --git a/src/driving/main/pages/CreateUser.tsx b/src/driving/main/pages/CreateUser.tsx index 62fcc0a..a984828 100644 --- a/src/driving/main/pages/CreateUser.tsx +++ b/src/driving/main/pages/CreateUser.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import PageTitle from '~/driven/utils/components/page-title/pageTitle' -import { staticMessages } from '~/driven/utils/constants/staticMessages' -import CreateUser from '~/driving/application/core/create-user' +import React from 'react'; +import PageTitle from '~/driven/utils/components/page-title/pageTitle'; +import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import CreateUser from '~/driving/application/core/create-user'; export default function CreateUserPage() { return ( @@ -9,5 +9,5 @@ export default function CreateUserPage() { <PageTitle className='px-4 py-5' title={staticMessages.global.createUser} /> <CreateUser /> </> - ) + ); } diff --git a/src/driving/main/pages/index.tsx b/src/driving/main/pages/index.tsx index 008ac54..8573c65 100644 --- a/src/driving/main/pages/index.tsx +++ b/src/driving/main/pages/index.tsx @@ -1,23 +1,86 @@ -import React from 'react'; +import { AxiosError } from 'axios'; +import React, { useEffect, useState } from 'react'; +import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; +import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; +import Notification from '~/driven/utils/components/Notification/Notification'; import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton'; import PageTitle from '~/driven/utils/components/page-title/pageTitle'; +import { apiUrls } from '~/driven/utils/configs/appConfig'; import { staticMessages } from '~/driven/utils/constants/staticMessages'; +import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator'; import PlacesList from '~/driving/application/core/places-list'; import UsersList from '~/driving/application/core/users-list'; -import Sidebar from '~/driving/application/support/sidebar'; export default function index() { + const [selectedUserRowId, setSelectedUserRowId] = useState<string>(''); + const [selectedPlaceRowId, setSelectedPlaceRowId] = useState<string>(''); + const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); + const [error, setError] = useState<{ message: string; type: 'error' | 'success' }>({ message: '', type: 'error' }); + const [isSubmitDisabled, setIsSubmitDisabled] = useState(true); + const onSubmitMember = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const url = apiUrls.core.createMember; + const data = { + place_id: selectedPlaceRowId, + account_id: selectedUserRowId, + }; + + const options: HttpOptionsType = { + url, + data, + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }; + + const userTokens = { + accessToken: userData.user?.adminUserData.accessToken || null, + refreshToken: userData.user?.adminUserData.refreshToken || null, + }; + const httpProvider = new HTTPPovider(userTokens, accessTokenUpdateHandler, notLoginAuth); + + await httpProvider.request(options); + setError({ message: staticMessages.global.success.createMember, type: 'success' }); + } catch (errorExc) { + if (errorExc instanceof AxiosError) { + setError({ message: errorExc.response?.data?.description, type: 'error' }); + } else if (errorExc instanceof Error) { + setError({ message: errorExc.message, type: 'error' }); + } + } + }; + + useEffect(() => { + if (selectedUserRowId && selectedPlaceRowId) setIsSubmitDisabled(false); + else setIsSubmitDisabled(true); + }, [selectedUserRowId, selectedPlaceRowId]); + return ( <> - <PageTitle className='px-4 py-5' title={staticMessages.global.users} /><div className='container mx-auto px-4'> - <div className='w-full flex flex-row-reverse items-center py-2'> - <PrimaryButton className='text-sm' title={staticMessages.global.submit} onClick={() => { } } /> + {Boolean(error.message) && ( + <Notification + message={error.message} + type={error.type} + onCloseCallback={() => setError({ message: '', type: 'error' })} + /> + )} + <PageTitle className='px-4 py-5' title={staticMessages.global.users} /> + <div className='container mx-auto px-4'> + <form onSubmit={onSubmitMember} className='w-full flex flex-row-reverse items-center py-2 sticky top-0'> + <PrimaryButton + isDisabled={isSubmitDisabled} + className='text-sm disabled:opacity-25' + title={staticMessages.global.submit} + onClick={() => null} + /> + </form> + <div className='md:grid-cols-2 gap-x-4 grid grid-cols-1 mx-auto'> + <UsersList selectedRowId={selectedUserRowId} setSelectedRowId={setSelectedUserRowId} /> + <PlacesList selectedRowId={selectedPlaceRowId} setSelectedRowId={setSelectedPlaceRowId} /> + </div> </div> - <div className='md:grid-cols-2 gap-x-4 grid grid-cols-1 mx-auto'> - <UsersList /> - <PlacesList /> - </div> - </div> - </> + </> ); } diff --git a/src/driving/main/pages/layouts/MainPageLayout.tsx b/src/driving/main/pages/layouts/MainPageLayout.tsx index 33fe105..9447647 100644 --- a/src/driving/main/pages/layouts/MainPageLayout.tsx +++ b/src/driving/main/pages/layouts/MainPageLayout.tsx @@ -1,14 +1,29 @@ -import React from 'react' -import { Outlet } from 'react-router-dom' -import Sidebar from '~/driving/application/support/sidebar' +import React, { useEffect } from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; +import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel'; +import StorageService from '~/driven/boundaries/storage-boundary'; +import { appConfig, routes } from '~/driven/utils/configs/appConfig'; +import { useUser } from '~/driven/utils/helpers/contexts/userContext'; +import Sidebar from '~/driving/application/support/sidebar'; +/** + * @todo should move to the logic and seperate the ui from the main logics + */ export default function MainPageLayout() { + const { user, setUser } = useUser(); + useEffect(() => { + const storage = StorageService.localStorage<AdminUserModel>(); + const currentUser = storage.getData(appConfig.adminUserStorageKey); + if (!currentUser && setUser) setUser(null); + }); + + if (!user || !user.adminUserData.accessToken) return <Navigate to={routes.authentication} replace />; return ( - <div className='flex flex-nowrap h-screen'> + <div className='flex flex-nowrap min-h-screen'> <Sidebar /> - <main className='dipal-panel w-full text-black bg-white h-fit'> + <main className='dipal-panel w-full text-black bg-white h-screen overflow-auto'> <Outlet /> - </main> + </main> </div> - ) + ); } diff --git a/src/driving/main/pages/layouts/UserLoginLayout.tsx b/src/driving/main/pages/layouts/UserLoginLayout.tsx new file mode 100644 index 0000000..dfeee28 --- /dev/null +++ b/src/driving/main/pages/layouts/UserLoginLayout.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; +import { routes } from '~/driven/utils/configs/appConfig'; +import { useUser } from '~/driven/utils/helpers/contexts/userContext'; + +export default function UserLoginLayout() { + const { user } = useUser(); + if (user && user.adminUserData.accessToken) return <Navigate to={routes.usersList} replace />; + return <Outlet />; +} diff --git a/src/driving/main/style/App.css b/src/driving/main/style/App.css index 3e72895..db02ae3 100644 --- a/src/driving/main/style/App.css +++ b/src/driving/main/style/App.css @@ -20,6 +20,7 @@ html,body { --color-primary-300: #0461B8; --color-primary-200: #80C8EF; --color-separated-border: #D4D4D4; + --color-gradient-button: linear-gradient(90deg, #00A6FE -10.23%, #D700FE 114.77%); } th, diff --git a/tailwind.config.cjs b/tailwind.config.cjs index a979fbd..4469893 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -27,6 +27,7 @@ module.exports = { }, gradient: { primary: 'var(--color-gradient-primary-dark)', + button: 'var(--color-gradient-button)' }, separated: { border: 'var(--color-separated-border)' diff --git a/tsconfig.json b/tsconfig.json index 54166f4..17621e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "target": "esnext", "module": "esnext", "types": [ - "node" + "node", + "jest" ], "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], diff --git a/vite.config.ts b/vite.config.ts index 09fa318..eaee4e3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,16 +1,32 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import {defineConfig, loadEnv} from 'vite'; +import react from '@vitejs/plugin-react'; import path from 'path'; // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - resolve: { - alias: { - '~': path.resolve(__dirname, './src'), +export default defineConfig(({mode}) => { + const env = loadEnv(mode, process.cwd()) + + // expose .env as process.env instead of import.meta since jest does not import meta yet + const envWithProcessPrefix = Object.entries(env).reduce( + (prev, [key, val]) => { + return { + ...prev, + [ key]: `${val}`, + } }, - }, - server: { - host: true, - }, -}) + {}, + ) + + return { + plugins: [react()], + resolve: { + alias: { + '~': path.resolve(__dirname, './src'), + }, + }, + server: { + host: true, + }, + define: {'process.env': {...envWithProcessPrefix}} + } +}); diff --git a/yarn.lock b/yarn.lock index 77e163e..ecc5a77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,11 +22,23 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5": +"@babel/code-frame@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5": version "7.21.0" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz" integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== +"@babel/compat-data@^7.21.5": + version "7.21.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" + integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== + "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.20.12": version "7.21.0" resolved "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz" @@ -58,6 +70,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" + integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== + dependencies: + "@babel/types" "^7.21.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz" @@ -73,7 +95,7 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7": version "7.20.7" resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz" integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== @@ -84,6 +106,17 @@ lru-cache "^5.1.1" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" + integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== + dependencies: + "@babel/compat-data" "^7.21.5" + "@babel/helper-validator-option" "^7.21.0" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": version "7.21.0" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz" @@ -123,6 +156,11 @@ resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" + integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== + "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz" @@ -159,7 +197,14 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.0", "@babel/helper-module-transforms@^7.21.2": +"@babel/helper-module-imports@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" + integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== + dependencies: + "@babel/types" "^7.21.4" + +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.0": version "7.21.2" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz" integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== @@ -173,6 +218,20 @@ "@babel/traverse" "^7.21.2" "@babel/types" "^7.21.2" +"@babel/helper-module-transforms@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" + integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== + dependencies: + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-simple-access" "^7.21.5" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz" @@ -185,6 +244,11 @@ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz" integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" + integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== + "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz" @@ -214,6 +278,13 @@ dependencies: "@babel/types" "^7.20.2" +"@babel/helper-simple-access@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" + integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== + dependencies: + "@babel/types" "^7.21.5" + "@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz" @@ -233,6 +304,11 @@ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz" integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" + integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" @@ -276,6 +352,11 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz" integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ== +"@babel/parser@^7.21.5": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" + integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" @@ -283,18 +364,18 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7": version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1" integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== dependencies: "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-proposal-optional-chaining" "^7.20.7" -"@babel/plugin-proposal-async-generator-functions@^7.20.1": +"@babel/plugin-proposal-async-generator-functions@^7.20.7": version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== dependencies: "@babel/helper-environment-visitor" "^7.18.9" @@ -310,9 +391,9 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-class-static-block@^7.18.6": +"@babel/plugin-proposal-class-static-block@^7.21.0": version "7.21.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d" integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw== dependencies: "@babel/helper-create-class-features-plugin" "^7.21.0" @@ -343,9 +424,9 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": +"@babel/plugin-proposal-logical-assignment-operators@^7.20.7": version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -367,9 +448,9 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.20.2": +"@babel/plugin-proposal-object-rest-spread@^7.20.7": version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== dependencies: "@babel/compat-data" "^7.20.5" @@ -386,7 +467,7 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.18.9", "@babel/plugin-proposal-optional-chaining@^7.20.7": +"@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0": version "7.21.0" resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz" integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== @@ -403,9 +484,9 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-private-property-in-object@^7.18.6": +"@babel/plugin-proposal-private-property-in-object@^7.21.0": version "7.21.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc" integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -470,7 +551,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -491,6 +572,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-syntax-jsx@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" + integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" @@ -554,16 +642,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz" - integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== +"@babel/plugin-transform-arrow-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" + integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-async-to-generator@^7.18.6": +"@babel/plugin-transform-async-to-generator@^7.20.7": version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== dependencies: "@babel/helper-module-imports" "^7.18.6" @@ -577,16 +665,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.20.2": +"@babel/plugin-transform-block-scoping@^7.21.0": version "7.21.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-classes@^7.20.2": +"@babel/plugin-transform-classes@^7.21.0": version "7.21.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" @@ -599,18 +687,18 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz" - integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== +"@babel/plugin-transform-computed-properties@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" + integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" -"@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz" - integrity sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA== +"@babel/plugin-transform-destructuring@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" + integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -637,12 +725,12 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-for-of@^7.18.8": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz" - integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== +"@babel/plugin-transform-for-of@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" + integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -667,26 +755,26 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-amd@^7.19.6": +"@babel/plugin-transform-modules-amd@^7.20.11": version "7.20.11" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== dependencies: "@babel/helper-module-transforms" "^7.20.11" "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.21.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz" - integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA== +"@babel/plugin-transform-modules-commonjs@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" + integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== dependencies: - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-simple-access" "^7.21.5" -"@babel/plugin-transform-modules-systemjs@^7.19.6": +"@babel/plugin-transform-modules-systemjs@^7.20.11": version "7.20.11" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== dependencies: "@babel/helper-hoist-variables" "^7.18.6" @@ -702,9 +790,9 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": +"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5": version "7.20.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== dependencies: "@babel/helper-create-regexp-features-plugin" "^7.20.5" @@ -725,13 +813,20 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.20.1", "@babel/plugin-transform-parameters@^7.20.7": +"@babel/plugin-transform-parameters@^7.20.7": version "7.20.7" resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz" integrity sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-transform-parameters@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db" + integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz" @@ -786,12 +881,12 @@ "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.20.5" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== +"@babel/plugin-transform-regenerator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" + integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" regenerator-transform "^0.15.1" "@babel/plugin-transform-reserved-words@^7.18.6": @@ -808,9 +903,9 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.19.0": +"@babel/plugin-transform-spread@^7.20.7": version "7.20.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -837,21 +932,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.21.0": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz" - integrity sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg== +"@babel/plugin-transform-typescript@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz#316c5be579856ea890a57ebc5116c5d064658f2b" + integrity sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw== dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-create-class-features-plugin" "^7.21.0" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== +"@babel/plugin-transform-unicode-escapes@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" + integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -861,31 +957,31 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz" - integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== +"@babel/preset-env@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb" + integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg== dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-option" "^7.18.6" + "@babel/compat-data" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.20.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7" + "@babel/plugin-proposal-async-generator-functions" "^7.20.7" "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.21.0" "@babel/plugin-proposal-dynamic-import" "^7.18.6" "@babel/plugin-proposal-export-namespace-from" "^7.18.9" "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-logical-assignment-operators" "^7.20.7" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.2" + "@babel/plugin-proposal-object-rest-spread" "^7.20.7" "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.21.0" "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.21.0" "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -893,6 +989,7 @@ "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -902,40 +999,40 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.21.5" + "@babel/plugin-transform-async-to-generator" "^7.20.7" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.20.2" - "@babel/plugin-transform-classes" "^7.20.2" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.20.2" + "@babel/plugin-transform-block-scoping" "^7.21.0" + "@babel/plugin-transform-classes" "^7.21.0" + "@babel/plugin-transform-computed-properties" "^7.21.5" + "@babel/plugin-transform-destructuring" "^7.21.3" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-for-of" "^7.21.5" "@babel/plugin-transform-function-name" "^7.18.9" "@babel/plugin-transform-literals" "^7.18.9" "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.19.6" - "@babel/plugin-transform-modules-commonjs" "^7.19.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.6" + "@babel/plugin-transform-modules-amd" "^7.20.11" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" + "@babel/plugin-transform-modules-systemjs" "^7.20.11" "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5" "@babel/plugin-transform-new-target" "^7.18.6" "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-parameters" "^7.21.3" "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.21.5" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-spread" "^7.20.7" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-escapes" "^7.21.5" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.20.2" + "@babel/types" "^7.21.5" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -955,7 +1052,7 @@ "@babel/preset-react@^7.18.6": version "7.18.6" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.18.6.tgz" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== dependencies: "@babel/helper-plugin-utils" "^7.18.6" @@ -965,14 +1062,16 @@ "@babel/plugin-transform-react-jsx-development" "^7.18.6" "@babel/plugin-transform-react-pure-annotations" "^7.18.6" -"@babel/preset-typescript@^7.21.0": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz" - integrity sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg== +"@babel/preset-typescript@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" + integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" - "@babel/plugin-transform-typescript" "^7.21.0" + "@babel/plugin-syntax-jsx" "^7.21.4" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" + "@babel/plugin-transform-typescript" "^7.21.3" "@babel/regjsgen@^0.8.0": version "0.8.0" @@ -1011,6 +1110,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" + integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== + dependencies: + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.5" + "@babel/types" "^7.21.5" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.21.2" resolved "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz" @@ -1020,6 +1135,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.21.4", "@babel/types@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" + integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== + dependencies: + "@babel/helper-string-parser" "^7.21.5" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" @@ -1631,7 +1755,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^29.4.0": +"@types/jest@*": version "29.4.0" resolved "https://registry.npmjs.org/@types/jest/-/jest-29.4.0.tgz" integrity sha512-VaywcGQ9tPorCX/Jkkni7RWGFfI11whqzs8dvxF41P17Z+z872thvEvlIbznjPJ02kl1HMX3LmLOonsj2n7HeQ== @@ -1639,6 +1763,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^29.5.1": + version "29.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" + integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/jsdom@^20.0.0": version "20.0.1" resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz" @@ -5244,6 +5376,13 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swr@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.1.5.tgz#688effa719c03f6d35c66decbb0f8e79c7190399" + integrity sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw== + dependencies: + use-sync-external-store "^1.2.0" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" @@ -5457,7 +5596,7 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" -use-sync-external-store@^1.0.0: +use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==