feature/get-places-api #2

Merged
behnam merged 26 commits from feature/get-places-api into develop 2023-05-23 09:23:56 +00:00
162 changed files with 2927 additions and 493 deletions

17
.babelrc Normal file
View File

@ -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"]
}

10
.env.development Normal file
View File

@ -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

11
.eslintignore Normal file
View File

@ -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

83
.eslintrc.json Normal file
View File

@ -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"
}
}

5
.husky/pre-push Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run test && npm run lint
npm run test && npm run lint

13
.prettierrc.json Normal file
View File

@ -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"
}

View File

@ -8,5 +8,7 @@ export default {
"\\.(css|less)$": "identity-obj-proxy",
"^~(.*)$": "<rootDir>/src$1"
},
transformIgnorePatterns: ['<rootDir>/node_modules/'],
transformIgnorePatterns: [
"node_modules/(?!(spacetime)/)"
]
}

8
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,9 @@
type Places = {
placeType: string;
name: string;
qr: null | string;
id: string;
parentId: string | null;
};
export default Places;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,5 @@
import PlacesModel from '../../../common/model/placesModel';
type IGetPlacesRepo = () => Promise<PlacesModel>;
export default IGetPlacesRepo;

View File

@ -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;

View File

@ -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;
}[];

View File

@ -0,0 +1,3 @@
import getPlaces from './infra/getPlacesInfra';
export default getPlaces;

View File

@ -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;

View File

@ -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>;

View File

@ -0,0 +1,5 @@
import { getPlacesReturnType, type IgetPlacesInfra } from './infra/protocols';
export default IgetPlacesInfra;
export type getPlacesReturnPort = getPlacesReturnType;

View File

@ -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);
});
});

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,6 @@
export default interface Users {
profileId: string;
firstname: string;
lastname: string;
accountId: string;
}

View File

@ -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;

View File

@ -0,0 +1,12 @@
export interface INewUserData {
firstname: string;
lastname: string;
phonenumber: string;
}
export type CreateAccountDTOReturnType = {
username: string;
firstName: string;
lastName: string;
enabled: true;
};

View File

@ -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;

View File

@ -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;
};
}

View File

@ -0,0 +1,4 @@
import { INewUserData } from '../dto/protocols';
import { CreateAcountResponseApi } from '../response-object/protocols';
export type HttpHandler = (newUser: INewUserData) => Promise<CreateAcountResponseApi>;

View File

@ -0,0 +1,8 @@
import { CreateAccountROReturnType, CreateAcountResponseApi } from './protocols';
const createAcountRO = (apiResponse: CreateAcountResponseApi): CreateAccountROReturnType => ({
accountId: apiResponse.id,
phonenumber: apiResponse.username,
});
export default createAcountRO;

View File

@ -0,0 +1,10 @@
export type CreateAcountResponseApi = {
nextRequestTimestamp: number;
username: string;
id: string;
};
export type CreateAccountROReturnType = {
phonenumber: string;
accountId: string;
};

View File

@ -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;

View File

@ -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;

View File

@ -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);
}
}

View File

@ -0,0 +1,5 @@
import { ICreateNewProfileData } from '../dto/protocols';
export default interface ICreateProfileRepo {
execute: (accountData: ICreateNewProfileData) => Promise<string>;
}

View File

@ -0,0 +1,3 @@
import { CreateProfileDtoReturnType } from '../dto/protocols';
export type HttpHandler = (newUser: CreateProfileDtoReturnType) => Promise<string>;

View File

@ -0,0 +1,3 @@
import CreateUserInfra from './infra/createUserInfra';
export default CreateUserInfra;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}

View File

@ -0,0 +1,5 @@
import UsersModel from '../../../common/data/model/usersModel';
type IGetUsersRepo = () => Promise<UsersModel>;
export default IGetUsersRepo;

View File

@ -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;

View File

@ -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;
}[];

View File

@ -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;

View File

@ -0,0 +1,3 @@
import getUsers from './infra/getUsersInfra';
export default getUsers;

View File

@ -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;

View File

@ -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>;

View File

@ -0,0 +1,5 @@
import { getUsersReturnType, type IgetUsersInfra } from './infra/protocols';
export default IgetUsersInfra;
export type getUsersReturnPort = getUsersReturnType;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
import otpAuthInfra from './infra/otpAuthInfra';
export default otpAuthInfra;

View File

@ -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;

View File

@ -0,0 +1,8 @@
import { OtpHttpHandler } from './data/repository/otpAuthRepo';
import OtpAuthUsecase from './usecase/otpAuthUsecase';
export interface IOtpAuthDrivenPort {
httpHandler: OtpHttpHandler;
}
export type OtpAuthDrivingPort = OtpAuthUsecase;

View File

@ -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);
}
}

View File

@ -0,0 +1 @@
export default class OtpAuthUsecaseException extends Error {}

View File

@ -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;

View File

@ -0,0 +1,5 @@
import { AdminData } from '../../usecase/phonenumberAuthUsecase';
export default interface IPhoneNumberAuthRepo {
execute(adminData: AdminData): Promise<string>;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
import phonenumberAuthInfra from './infra/phonenumberAuthInfra';
export default phonenumberAuthInfra;

View File

@ -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;

View File

@ -0,0 +1,5 @@
import { IPhonenumberAuthInfra } from './infra/phonenumberAuthInfra';
import PhonenumberAuthUsecase from './usecase/phonenumberAuthUsecase';
export type IPhonenumberAuthPort = IPhonenumberAuthInfra;
export type PhonenumberReturnTypePort = Promise<PhonenumberAuthUsecase>;

View File

@ -0,0 +1 @@
export default class PhonenumberException extends Error {}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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';
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,3 @@
export type getPlacesAdapterReturnType = {
url: string;
};

View File

@ -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;

View File

@ -0,0 +1,3 @@
export type getUsersAdapterReturnType = {
url: string;
};

View File

@ -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);
},
);
}
}

View File

@ -0,0 +1,3 @@
import { AxiosRequestConfig } from 'axios';
export type HttpOptionsType = AxiosRequestConfig;

View File

@ -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)),
};

View File

@ -1 +0,0 @@
export { userAdapter } from './adapters/adapter';

View File

@ -1,3 +0,0 @@
import { UserModel } from '~/business-logic/generic/user/common/domain/model/userModel';
export type UserState = UserModel;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,3 @@
import StateManagementService from './stateManagementService';
export default StateManagementService;

View File

@ -0,0 +1,6 @@
export default abstract class StateManagementProvider {
abstract useGetQuery<DataType>(
key: string,
httpHandler: () => Promise<DataType>,
): { data: DataType | undefined; isLoading: boolean; error?: string };
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,3 @@
import StorageService from './storageService';
export default StorageService;

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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,
);
}

View File

@ -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>
);
}

View File

@ -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>
)
);
}

View File

@ -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>
);
}

View File

@ -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);
}
}

View File

@ -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>;
}

View File

@ -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}`,
},
};

View File

@ -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'
}
logo: `${baseIconsUrl}logo.svg`,
logoBlack: `${baseIconsUrl}logo-black.svg`,
users: `${baseIconsUrl}users.svg`,
createUser: `${baseIconsUrl}createuser.svg`,
};

View File

@ -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,
};

Some files were not shown because too many files have changed in this diff Show More