Compare commits
10 Commits
832220365e
...
d9bfa52188
Author | SHA1 | Date | |
---|---|---|---|
d9bfa52188 | |||
8b0687d945 | |||
5cd3d98b3d | |||
33fbedacf4 | |||
c30186778d | |||
0fc508c56b | |||
bc1d5fe198 | |||
3ad216f11c | |||
8ceb2e6989 | |||
15d7b85712 |
@ -1,4 +1,9 @@
|
||||
VITE_API_ORIGIN = http://176.53.196.42:6001/api/v1
|
||||
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
|
||||
|
@ -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;
|
@ -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;
|
@ -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>;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import otpAuthInfra from './infra/otpAuthInfra';
|
||||
|
||||
export default otpAuthInfra;
|
@ -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;
|
@ -0,0 +1,8 @@
|
||||
import { OtpHttpHandler } from './data/repository/otpAuthRepo';
|
||||
import OtpAuthUsecase from './usecase/otpAuthUsecase';
|
||||
|
||||
export interface IOtpAuthDrivenPort {
|
||||
httpHandler: OtpHttpHandler;
|
||||
}
|
||||
|
||||
export type OtpAuthDrivingPort = OtpAuthUsecase;
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export default class OtpAuthUsecaseException extends Error {}
|
@ -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;
|
@ -0,0 +1,5 @@
|
||||
import { AdminData } from '../../usecase/phonenumberAuthUsecase';
|
||||
|
||||
export default interface IPhoneNumberAuthRepo {
|
||||
execute(adminData: AdminData): Promise<string>;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import phonenumberAuthInfra from './infra/phonenumberAuthInfra';
|
||||
|
||||
export default phonenumberAuthInfra;
|
@ -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;
|
@ -0,0 +1,5 @@
|
||||
import { IPhonenumberAuthInfra } from './infra/phonenumberAuthInfra';
|
||||
import PhonenumberAuthUsecase from './usecase/phonenumberAuthUsecase';
|
||||
|
||||
export type IPhonenumberAuthPort = IPhonenumberAuthInfra;
|
||||
export type PhonenumberReturnTypePort = Promise<PhonenumberAuthUsecase>;
|
@ -0,0 +1 @@
|
||||
export default class PhonenumberException extends Error {}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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';
|
||||
}
|
40
src/driven/adapters/auth-admin-login/authAdminLogin.ts
Normal file
40
src/driven/adapters/auth-admin-login/authAdminLogin.ts
Normal 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;
|
@ -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;
|
@ -1,15 +1,27 @@
|
||||
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 = (): createUserPort['httpAccountHandler'] => {
|
||||
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();
|
||||
const httpProvider = new HTTPPovider(
|
||||
{
|
||||
accessToken: userAdmin.adminUserData.accessToken,
|
||||
refreshToken: userAdmin.adminUserData.refreshToken,
|
||||
},
|
||||
updateAccessToken,
|
||||
navigateToAuth,
|
||||
);
|
||||
|
||||
const httpHandler = (newUserData: INewUserData) => {
|
||||
// api options
|
||||
|
@ -1,14 +1,26 @@
|
||||
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 = (): createUserPort['httpProfileHandler'] => {
|
||||
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();
|
||||
const httpProvider = new HTTPPovider(
|
||||
{
|
||||
accessToken: userAdmin.adminUserData.accessToken,
|
||||
refreshToken: userAdmin.adminUserData.refreshToken,
|
||||
},
|
||||
updateAccessToken,
|
||||
navigateToAuth,
|
||||
);
|
||||
|
||||
const httpHandler = (newAccountData: CreateProfileDtoReturnType) => {
|
||||
// api options
|
||||
|
@ -3,9 +3,14 @@ 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 = (): IGetPlacesPort & getPlacesAdapterReturnType => {
|
||||
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
|
||||
@ -14,7 +19,14 @@ const getPlacesAdapter = (): IGetPlacesPort & getPlacesAdapterReturnType => {
|
||||
method: 'GET',
|
||||
};
|
||||
// make the httpHandler
|
||||
const httpProvider = new HTTPPovider();
|
||||
const httpProvider = new HTTPPovider(
|
||||
{
|
||||
accessToken: userAdmin.adminUserData.accessToken,
|
||||
refreshToken: userAdmin.adminUserData.refreshToken,
|
||||
},
|
||||
updateAccessToken,
|
||||
navigateToAuth,
|
||||
);
|
||||
|
||||
const httpHandler = async () => httpProvider.request<GetPlacesResponse>(options);
|
||||
|
||||
|
@ -3,9 +3,14 @@ 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 = (): IGetUsersPort & getUsersAdapterReturnType => {
|
||||
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
|
||||
@ -14,7 +19,14 @@ const getUsersAdapter = (): IGetUsersPort & getUsersAdapterReturnType => {
|
||||
method: 'GET',
|
||||
};
|
||||
// make the httpHandler
|
||||
const httpProvider = new HTTPPovider();
|
||||
const httpProvider = new HTTPPovider(
|
||||
{
|
||||
accessToken: userAdmin.adminUserData.accessToken,
|
||||
refreshToken: userAdmin.adminUserData.refreshToken,
|
||||
},
|
||||
updateAccessToken,
|
||||
navigateToAuth,
|
||||
);
|
||||
|
||||
const httpHandler = async () => httpProvider.request<GetUsersResponse>(options);
|
||||
|
||||
|
@ -1,22 +1,88 @@
|
||||
import axios 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';
|
||||
|
||||
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 options: HttpOptionsType = {
|
||||
...customOptions,
|
||||
headers: {
|
||||
...customOptions.headers,
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
Authorization: `Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4NXh0WnA5eThxVDBXVDkwUFpuUkRja3N4LWw0clVyM0tHQW5JSU9DckJNIn0.eyJleHAiOjE2ODQ4MzM5NzcsImlhdCI6MTY4NDc0NzU3NywianRpIjoiYjE5MDEyNGItYzRjNC00NzlkLThkYWItN2VjODc1MjljZWQyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9kaXBhbF9kZXYiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiY2RmYzY3YzQtZGJkOC00NGVhLWI0OWEtYjQ3MjZhMzNmOTAxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY29tZm9ydGVjaCIsInNlc3Npb25fc3RhdGUiOiJiYjFmNjc3OC0xMzlhLTRmNzItOWM3Ny01NjAwMWU0NDYzNjQiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1tYXN0ZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiY29tZm9ydGVjaCI6eyJyb2xlcyI6WyJ1c2VyIiwib3BlcmF0b3IiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6ImJiMWY2Nzc4LTEzOWEtNGY3Mi05Yzc3LTU2MDAxZTQ0NjM2NCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiKzc3Nzc3Nzc3Nzc3In0.qJS5_c9g2AADwusGpquWw7zMvc42tzJ0yUMcM6jI6F2MNH2tFDqMhvG0nEnCwXIxJA54DZL8HHPDoxkhq_xyP2SSRKEU-S7pncpa2acNzOYT68pLxBD6s3W-akQxxJVlr92RtegqaHf2BAZMwdMJl4VreX_avPCrEdPzv2dEMX7a2wxteYgzQJsYtaaVyCO4QADMiNVMWgXE00Hnn5Rxuhpe9Y7Kl9cWCO5JY63gYXGFC9yUBEqEYl6o9d6XKMkuiaLJRE2l4k5ycKuJWUjhvCaL7J_f68vJzNhkiuMqmX5q08SDlgktNHzyKTVXkndKz2EpQemzM6SXPLnohPwjAg`,
|
||||
},
|
||||
};
|
||||
const response = await axios<ApiGlobalResponseObject<R>>(options);
|
||||
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.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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
3
src/driven/boundaries/storage-boundary/index.ts
Normal file
3
src/driven/boundaries/storage-boundary/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import StorageService from './storageService';
|
||||
|
||||
export default StorageService;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
27
src/driven/boundaries/storage-boundary/storageService.ts
Normal file
27
src/driven/boundaries/storage-boundary/storageService.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@ 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
|
||||
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}`}
|
||||
>
|
||||
|
@ -2,7 +2,9 @@ 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: '/',
|
||||
@ -30,5 +32,11 @@ export const apiUrls = {
|
||||
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}`,
|
||||
},
|
||||
};
|
||||
|
@ -1,7 +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,
|
||||
};
|
||||
|
@ -2,6 +2,8 @@ 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',
|
||||
@ -18,6 +20,10 @@ export const staticMessages = {
|
||||
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: {
|
||||
|
17
src/driven/utils/helpers/contexts/userContext.tsx
Normal file
17
src/driven/utils/helpers/contexts/userContext.tsx
Normal file
@ -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);
|
@ -1,5 +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,
|
||||
@ -26,3 +30,21 @@ export const prepareStateManagementForVM = <ReturnType>(apiUrl: string, model: (
|
||||
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);
|
||||
};
|
||||
|
@ -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;
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import RowItem from './table-row-item/view/RowItem';
|
||||
import { ITableRowProps } from './protocols';
|
||||
@ -6,7 +7,14 @@ 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>;
|
||||
|
@ -2,21 +2,42 @@ 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() {
|
||||
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
|
||||
|
||||
const { user } = userData;
|
||||
// get adapters from driven layer
|
||||
const createAccountDrivenAdapter = createAccountAdapter();
|
||||
const createProfileDrivenAdapter = createProfileAdapter();
|
||||
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 } = useCreateUserVM({ handleSubmitForm });
|
||||
const { stateHandler, onSubmit, inputNames, error, setError } = useCreateUserVM({ handleSubmitForm });
|
||||
// get all of the needed information to the user to show
|
||||
return <CreateUserView stateHandler={stateHandler} inputNames={inputNames} onSubmit={onSubmit} />;
|
||||
return (
|
||||
<CreateUserView
|
||||
error={error}
|
||||
setError={setError}
|
||||
stateHandler={stateHandler}
|
||||
inputNames={inputNames}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -2,10 +2,11 @@ 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(props: ICreateUserViewProps) {
|
||||
const { onSubmit, inputNames, stateHandler } = props;
|
||||
const { onSubmit, inputNames, stateHandler, error, setError } = props;
|
||||
const { inputStates, inputsSetStates } = stateHandler;
|
||||
|
||||
const inputs = inputNames.map((inputName) => {
|
||||
@ -27,6 +28,13 @@ export default function CreateUserView(props: ICreateUserViewProps) {
|
||||
});
|
||||
return (
|
||||
<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={() => null} title={staticMessages.global.submit} />
|
||||
|
@ -8,4 +8,6 @@ export default interface ICreateUserViewProps {
|
||||
inputsSetStates: SetStateInputMethod<keyof INewUserData>;
|
||||
};
|
||||
inputNames: (keyof INewUserData)[];
|
||||
error: { message: string; type: 'error' | 'success' };
|
||||
setError: React.Dispatch<React.SetStateAction<{ message: string; type: 'error' | 'success' }>>;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
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';
|
||||
|
||||
@ -13,6 +15,8 @@ const inputStateInitialValue: INewUserData = {
|
||||
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);
|
||||
|
||||
@ -24,10 +28,18 @@ const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps =
|
||||
}));
|
||||
};
|
||||
|
||||
const onSubmitCreateUserForm = (e: React.FormEvent) => {
|
||||
const onSubmitCreateUserForm = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
console.log('submit user', inputsValue);
|
||||
handleSubmitForm(inputsValue);
|
||||
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 };
|
||||
@ -39,6 +51,8 @@ const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps =
|
||||
},
|
||||
onSubmit: onSubmitCreateUserForm,
|
||||
inputNames,
|
||||
error,
|
||||
setError,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -3,23 +3,34 @@ 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';
|
||||
|
||||
const prepareTheLogicForModel = () => {
|
||||
const gettingPlacesDrivenAdapter = getPlacesAdapter();
|
||||
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 default function PlacessList() {
|
||||
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 { selectedRowId, setSelectedRowId, placesData } = usePlacesListVM({
|
||||
const { placesData } = usePlacesListVM({
|
||||
useGetPlacesList,
|
||||
});
|
||||
return <PlacesListView placesList={placesData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />;
|
||||
|
@ -3,22 +3,32 @@ 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';
|
||||
|
||||
const prepareTheLogicForModel = () => {
|
||||
const gettingUsersDrivenAdapter = getUsersAdapter();
|
||||
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 default function UsersList() {
|
||||
const { getingusersLogic, url } = prepareTheLogicForModel();
|
||||
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 { selectedRowId, setSelectedRowId, usersData } = useUsersListVM({
|
||||
const { usersData } = useUsersListVM({
|
||||
useGetusersList,
|
||||
});
|
||||
return <UsersListView usersList={usersData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />;
|
||||
|
@ -1,6 +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() {
|
||||
return <AuthenticationView />;
|
||||
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} />;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export default function OtpCodeView(props: IOtpCodeView) {
|
||||
tabIndex={i + 1}
|
||||
ref={(el: HTMLInputElement) => (otpChar.current[i] = el)}
|
||||
key={`otp_char_${i}`}
|
||||
className='font-bold inline-block w-5 bg-transparent text-center focus:outline-none'
|
||||
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='_'
|
||||
|
@ -2,11 +2,30 @@ 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';
|
||||
|
||||
export default function AuthenticationView() {
|
||||
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 }} />,
|
||||
@ -14,12 +33,37 @@ export default function AuthenticationView() {
|
||||
};
|
||||
const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber');
|
||||
|
||||
const submitForm = (e: React.FormEvent) => {
|
||||
const submitForm = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setAuthState('otp');
|
||||
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'
|
||||
|
@ -1,11 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
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 userCTX = useUser();
|
||||
const navigator = useNavigate();
|
||||
const pages = Object.keys(routesData).map((routeKey) => {
|
||||
const key = routeKey as keyof typeof routesData;
|
||||
return (
|
||||
@ -27,7 +30,12 @@ export default function Sidebar() {
|
||||
<div className='logo'>
|
||||
<img src={icons.logo} alt='logo icon' />
|
||||
</div>
|
||||
<div className='mt-14 flex flex-col items-baseline'>{pages}</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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -4,6 +4,7 @@ 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();
|
||||
@ -14,7 +15,9 @@ export default function Router() {
|
||||
<Route index element={<Home />} />
|
||||
<Route path={routes.createUser} element={<CreateUser />} />
|
||||
</Route>
|
||||
<Route path={routes.authentication} element={<AuthenticationPage />} />
|
||||
<Route element={<UserLoginLayout />}>
|
||||
<Route path={routes.authentication} element={<AuthenticationPage />} />
|
||||
</Route>
|
||||
<Route path='*' element={<Navigate to={routes.usersList} replace />} />
|
||||
</Routes>
|
||||
);
|
||||
|
@ -1,4 +1,12 @@
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { UserProvider } from '~/driven/utils/helpers/contexts/userContext';
|
||||
import { BrowserRouter as RouterWrapper } from 'react-router-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<UserProvider>
|
||||
<RouterWrapper>
|
||||
<App />
|
||||
</RouterWrapper>
|
||||
</UserProvider>,
|
||||
);
|
||||
|
@ -1,21 +1,84 @@
|
||||
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';
|
||||
|
||||
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 (
|
||||
<>
|
||||
{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'>
|
||||
<div className='w-full flex flex-row-reverse items-center py-2'>
|
||||
<PrimaryButton className='text-sm' title={staticMessages.global.submit} onClick={() => null} />
|
||||
</div>
|
||||
<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 />
|
||||
<PlacesList />
|
||||
<UsersList selectedRowId={selectedUserRowId} setSelectedRowId={setSelectedUserRowId} />
|
||||
<PlacesList selectedRowId={selectedPlaceRowId} setSelectedRowId={setSelectedPlaceRowId} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
@ -1,12 +1,27 @@
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
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 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>
|
||||
</div>
|
||||
|
10
src/driving/main/pages/layouts/UserLoginLayout.tsx
Normal file
10
src/driving/main/pages/layouts/UserLoginLayout.tsx
Normal file
@ -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 />;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user