Compare commits

..

No commits in common. "d9bfa52188658027c18dccd56362fe54bec63d46" and "832220365e8727578f3745ec935319bed44bc16d" have entirely different histories.

55 changed files with 65 additions and 916 deletions

View File

@ -1,9 +1,4 @@
VITE_API_ORIGIN = https://admin.dev.dipal.ru/api/v1 VITE_API_ORIGIN = http://176.53.196.42:6001/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_PLACES = /place
VITE_API_USERS = /profile VITE_API_USERS = /profile
VITE_API_USERS_ACCOUNT = /account VITE_API_USERS_ACCOUNT = /account

View File

@ -1,14 +0,0 @@
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

@ -1,23 +0,0 @@
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

@ -1,6 +0,0 @@
import AdminUserModel from '../../../../common/data/model/adminUserModel';
import { AdminOtpData } from '../../usecase/otpAuthUsecase';
export default interface IOtpAuthRepo {
execute(otpData: AdminOtpData): Promise<AdminUserModel>;
}

View File

@ -1,32 +0,0 @@
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

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

View File

@ -1,13 +0,0 @@
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

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

View File

@ -1,35 +0,0 @@
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

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

View File

@ -1,10 +0,0 @@
import { AdminData } from '../../usecase/phonenumberAuthUsecase';
export type PhonenumberAuthDTOReturnType = {
username: string;
};
const phonenumberAuthDTO = (adminData: AdminData): PhonenumberAuthDTOReturnType => ({
username: adminData.phonenumber,
});
export default phonenumberAuthDTO;

View File

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

View File

@ -1,20 +0,0 @@
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

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

View File

@ -1,21 +0,0 @@
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

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

View File

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

View File

@ -1,36 +0,0 @@
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

@ -1,13 +0,0 @@
import AdminUser from '../../entity/adminUserEntity';
export default class AdminUserModel {
adminUserData: AdminUser;
constructor(adminUserData: AdminUser) {
this.adminUserData = adminUserData;
}
get() {
return this.adminUserData;
}
}

View File

@ -1,13 +0,0 @@
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

@ -1,40 +0,0 @@
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

@ -1,44 +0,0 @@
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

@ -1,27 +1,15 @@
import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; 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 { 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 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 { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig'; import { apiUrls } from '~/driven/utils/configs/appConfig';
const createAccountAdapter = ( const createAccountAdapter = (): createUserPort['httpAccountHandler'] => {
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): createUserPort['httpAccountHandler'] => {
// make url // make url
const url = apiUrls.core.createUserAccount; const url = apiUrls.core.createUserAccount;
// call http provider // 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) => { const httpHandler = (newUserData: INewUserData) => {
// api options // api options

View File

@ -1,26 +1,14 @@
import { CreateProfileDtoReturnType } from '~/business-logic/core/users/create-user/create-profile/data/dto/protocols'; 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 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 { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig'; import { apiUrls } from '~/driven/utils/configs/appConfig';
const createProfileAdapter = ( const createProfileAdapter = (): createUserPort['httpProfileHandler'] => {
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): createUserPort['httpProfileHandler'] => {
// make url // make url
const url = apiUrls.core.createUserProfile; const url = apiUrls.core.createUserProfile;
// call http provider // 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) => { const httpHandler = (newAccountData: CreateProfileDtoReturnType) => {
// api options // api options

View File

@ -3,14 +3,9 @@ import IGetPlacesPort from '~/business-logic/core/places/get-places/port';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols'; import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { apiUrls } from '~/driven/utils/configs/appConfig'; import { apiUrls } from '~/driven/utils/configs/appConfig';
import AdminUserModel from '~/business-logic/generic/admin-user/common/data/model/adminUserModel';
import { getPlacesAdapterReturnType } from './protocols'; import { getPlacesAdapterReturnType } from './protocols';
const getPlacesAdapter = ( const getPlacesAdapter = (): IGetPlacesPort & getPlacesAdapterReturnType => {
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): IGetPlacesPort & getPlacesAdapterReturnType => {
// url of api // url of api
const url = apiUrls.core.getPlaces; const url = apiUrls.core.getPlaces;
// make the options of request // make the options of request
@ -19,14 +14,7 @@ const getPlacesAdapter = (
method: 'GET', method: 'GET',
}; };
// make the httpHandler // 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); const httpHandler = async () => httpProvider.request<GetPlacesResponse>(options);

View File

@ -3,14 +3,9 @@ import { HttpOptionsType } from '~/driven/boundaries/http-boundary/protocols';
import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary'; import { HTTPPovider } from '~/driven/boundaries/http-boundary/httpBoundary';
import { GetUsersResponse } from '~/business-logic/core/users/get-users/data/response-object/protocols'; import { GetUsersResponse } from '~/business-logic/core/users/get-users/data/response-object/protocols';
import IGetUsersPort from '~/business-logic/core/users/get-users/ports'; 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'; import { getUsersAdapterReturnType } from './protocols';
const getUsersAdapter = ( const getUsersAdapter = (): IGetUsersPort & getUsersAdapterReturnType => {
userAdmin: AdminUserModel,
updateAccessToken: (newAccessToken: string) => void,
navigateToAuth: () => void,
): IGetUsersPort & getUsersAdapterReturnType => {
// url of api // url of api
const url = apiUrls.core.getUsers; const url = apiUrls.core.getUsers;
// make the options of request // make the options of request
@ -19,14 +14,7 @@ const getUsersAdapter = (
method: 'GET', method: 'GET',
}; };
// make the httpHandler // 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); const httpHandler = async () => httpProvider.request<GetUsersResponse>(options);

View File

@ -1,88 +1,22 @@
/* eslint-disable consistent-return */ import axios from 'axios';
/* eslint-disable no-param-reassign */
import axios, { AxiosInstance } from 'axios';
import { staticMessages } from '~/driven/utils/constants/staticMessages'; import { staticMessages } from '~/driven/utils/constants/staticMessages';
import { ApiGlobalResponseObject } from '~/driven/utils/protocols/serviceProtocols'; import { ApiGlobalResponseObject } from '~/driven/utils/protocols/serviceProtocols';
import { apiUrls } from '~/driven/utils/configs/appConfig';
import { HttpOptionsType } from './protocols'; import { HttpOptionsType } from './protocols';
interface IUserTokens {
accessToken: string | null | undefined;
refreshToken: string | null | undefined;
}
export class HTTPPovider { 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) { async request<R>(customOptions: HttpOptionsType) {
const axiosInstance = this.handleRequestInterceptor(); const options: HttpOptionsType = {
this.responseIncepter(axiosInstance); ...customOptions,
headers: {
const response = await axiosInstance<ApiGlobalResponseObject<R>>(customOptions); ...customOptions.headers,
mode: 'cors',
credentials: 'include',
Authorization: `Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4NXh0WnA5eThxVDBXVDkwUFpuUkRja3N4LWw0clVyM0tHQW5JSU9DckJNIn0.eyJleHAiOjE2ODQ4MzM5NzcsImlhdCI6MTY4NDc0NzU3NywianRpIjoiYjE5MDEyNGItYzRjNC00NzlkLThkYWItN2VjODc1MjljZWQyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9kaXBhbF9kZXYiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiY2RmYzY3YzQtZGJkOC00NGVhLWI0OWEtYjQ3MjZhMzNmOTAxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY29tZm9ydGVjaCIsInNlc3Npb25fc3RhdGUiOiJiYjFmNjc3OC0xMzlhLTRmNzItOWM3Ny01NjAwMWU0NDYzNjQiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1tYXN0ZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiY29tZm9ydGVjaCI6eyJyb2xlcyI6WyJ1c2VyIiwib3BlcmF0b3IiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6ImJiMWY2Nzc4LTEzOWEtNGY3Mi05Yzc3LTU2MDAxZTQ0NjM2NCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiKzc3Nzc3Nzc3Nzc3In0.qJS5_c9g2AADwusGpquWw7zMvc42tzJ0yUMcM6jI6F2MNH2tFDqMhvG0nEnCwXIxJA54DZL8HHPDoxkhq_xyP2SSRKEU-S7pncpa2acNzOYT68pLxBD6s3W-akQxxJVlr92RtegqaHf2BAZMwdMJl4VreX_avPCrEdPzv2dEMX7a2wxteYgzQJsYtaaVyCO4QADMiNVMWgXE00Hnn5Rxuhpe9Y7Kl9cWCO5JY63gYXGFC9yUBEqEYl6o9d6XKMkuiaLJRE2l4k5ycKuJWUjhvCaL7J_f68vJzNhkiuMqmX5q08SDlgktNHzyKTVXkndKz2EpQemzM6SXPLnohPwjAg`,
},
};
const response = await axios<ApiGlobalResponseObject<R>>(options);
if (!response) throw new Error(staticMessages.service.errors[500]); if (!response) throw new Error(staticMessages.service.errors[500]);
return response.data.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

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

View File

@ -1,18 +0,0 @@
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

@ -1,7 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -4,14 +4,12 @@ interface IPrimaryButtonProps {
title: string; title: string;
onClick: (e: React.MouseEvent) => void; onClick: (e: React.MouseEvent) => void;
className?: string; className?: string;
isDisabled?: boolean;
} }
export default function PrimaryButton(props: IPrimaryButtonProps) { export default function PrimaryButton(props: IPrimaryButtonProps) {
const { onClick, title, className, isDisabled = false } = props; const { onClick, title, className } = props;
return ( return (
<button <button
disabled={isDisabled}
onClick={onClick} onClick={onClick}
className={`py-1 px-5 transition-all hover:bg-primary-300 bg-primary-main rounded-lg text-white text-center ${className}`} className={`py-1 px-5 transition-all hover:bg-primary-300 bg-primary-main rounded-lg text-white text-center ${className}`}
> >

View File

@ -2,9 +2,7 @@ import { icons } from '../constants/assertUrls';
import { ENVs } from '../constants/envs'; import { ENVs } from '../constants/envs';
import { staticMessages } from '../constants/staticMessages'; import { staticMessages } from '../constants/staticMessages';
export const appConfig = { export const appConfig = {};
adminUserStorageKey: 'adminUser',
};
export const routes = { export const routes = {
usersList: '/', usersList: '/',
@ -32,11 +30,5 @@ export const apiUrls = {
getUsers: `${baseApiUrl}${ENVs.apiGetUsers}`, getUsers: `${baseApiUrl}${ENVs.apiGetUsers}`,
createUserAccount: `${baseApiUrl}${ENVs.apiCreateUserAccount}`, createUserAccount: `${baseApiUrl}${ENVs.apiCreateUserAccount}`,
createUserProfile: `${baseApiUrl}${ENVs.apiCreateUserProfile}`, 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,12 +1,7 @@
export const ENVs = { export const ENVs = {
apiOrigin: process.env.VITE_API_ORIGIN, 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, apiGetPlaces: process.env.VITE_API_PLACES,
apiGetUsers: process.env.VITE_API_USERS, apiGetUsers: process.env.VITE_API_USERS,
apiCreateUserAccount: process.env.VITE_API_USERS_ACCOUNT, apiCreateUserAccount: process.env.VITE_API_USERS_ACCOUNT,
apiCreateUserProfile: process.env.VITE_API_USERS_PROFILE, apiCreateUserProfile: process.env.VITE_API_USERS_PROFILE,
apiCreateMember: process.env.VITE_API_CREATE_MEMBER,
}; };

View File

@ -2,8 +2,6 @@ export const staticMessages = {
global: { global: {
errors: { errors: {
input: 'please fill all inputs correctly', input: 'please fill all inputs correctly',
phonenumber: 'please fill the valid number',
otp: 'please fill the otp fields correctly',
}, },
users: 'Users', users: 'Users',
submit: 'Submit', submit: 'Submit',
@ -20,10 +18,6 @@ export const staticMessages = {
enterPanel: 'Enter to Panel', enterPanel: 'Enter to Panel',
enterPhoneNumber: 'Enter your phone number', enterPhoneNumber: 'Enter your phone number',
enterOtpCode: 'Enter your Otp Code', enterOtpCode: 'Enter your Otp Code',
success: {
createUser: 'user created successfully',
createMember: 'member created successfully',
},
}, },
service: { service: {
errors: { errors: {

View File

@ -1,17 +0,0 @@
/* 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);

View File

@ -1,9 +1,5 @@
import StateManagementService from '~/driven/boundaries/state-management'; 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 { errorHandlingStateTypes, UIErrorHandling } from './protocols/globalHelpersProtocols';
import { IUserContext } from './contexts/userContext';
export const UIErrorHandlingFactory = <DATA_RESPONSE>({ export const UIErrorHandlingFactory = <DATA_RESPONSE>({
state, state,
@ -30,21 +26,3 @@ export const prepareStateManagementForVM = <ReturnType>(apiUrl: string, model: (
export const checkPhoneNumberInput = (newValue: string) => { export const checkPhoneNumberInput = (newValue: string) => {
return (Number.isFinite(+newValue) || newValue === '+') && newValue.length <= 12; 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);
};

View File

@ -1,24 +0,0 @@
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;

View File

@ -1,4 +1,3 @@
/* eslint-disable react/no-array-index-key */
import React from 'react'; import React from 'react';
import RowItem from './table-row-item/view/RowItem'; import RowItem from './table-row-item/view/RowItem';
import { ITableRowProps } from './protocols'; import { ITableRowProps } from './protocols';
@ -7,14 +6,7 @@ export default function TableRowView(props: ITableRowProps) {
const { isSelected, setSelectedRowId, rowData } = props; const { isSelected, setSelectedRowId, rowData } = props;
const { rowId, rowItemsTitle } = rowData; const { rowId, rowItemsTitle } = rowData;
const columns = rowItemsTitle.map((rowItemTitle, index) => { const columns = rowItemsTitle.map((rowItemTitle, index) => {
return ( return <RowItem key={rowItemTitle} hasCheckbox={index === 0} isSelected={isSelected} title={rowItemTitle} />;
<RowItem
key={(rowItemTitle || 'row') + index}
hasCheckbox={index === 0}
isSelected={isSelected}
title={rowItemTitle}
/>
);
}); });
return <tr onClick={() => setSelectedRowId(rowId)}>{columns}</tr>; return <tr onClick={() => setSelectedRowId(rowId)}>{columns}</tr>;

View File

@ -2,42 +2,21 @@ import React from 'react';
import createAccountAdapter from '~/driven/adapters/create-account-adapter/createAccountAdapter'; import createAccountAdapter from '~/driven/adapters/create-account-adapter/createAccountAdapter';
import createProfileAdapter from '~/driven/adapters/create-profile-adapter/createProfileAdapter'; import createProfileAdapter from '~/driven/adapters/create-profile-adapter/createProfileAdapter';
import CreateUserInfra from '~/business-logic/core/users/create-user'; 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 CreateUserView from '../view/CreateUserView';
import useCreateUserVM from '../viewmodel/CreateUserVM'; import useCreateUserVM from '../viewmodel/CreateUserVM';
import createUserModel from '../model/createUserModel'; import createUserModel from '../model/createUserModel';
export default function CreateUser() { export default function CreateUser() {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater();
const { user } = userData;
// get adapters from driven layer // get adapters from driven layer
const createAccountDrivenAdapter = createAccountAdapter( const createAccountDrivenAdapter = createAccountAdapter();
user as AdminUserModel, const createProfileDrivenAdapter = createProfileAdapter();
accessTokenUpdateHandler,
notLoginAuth,
);
const createProfileDrivenAdapter = createProfileAdapter(
user as AdminUserModel,
accessTokenUpdateHandler,
notLoginAuth,
);
// pass to the logic and get the usecase // pass to the logic and get the usecase
const createUserInfra = new CreateUserInfra(createAccountDrivenAdapter, createProfileDrivenAdapter); const createUserInfra = new CreateUserInfra(createAccountDrivenAdapter, createProfileDrivenAdapter);
const createUserLogic = createUserInfra.execute(); const createUserLogic = createUserInfra.execute();
// pass the usecase to the model // pass the usecase to the model
const { handleSubmitForm } = createUserModel({ createUserLogic }); const { handleSubmitForm } = createUserModel({ createUserLogic });
// pass the method to the viewmodel to call on submit // pass the method to the viewmodel to call on submit
const { stateHandler, onSubmit, inputNames, error, setError } = useCreateUserVM({ handleSubmitForm }); const { stateHandler, onSubmit, inputNames } = useCreateUserVM({ handleSubmitForm });
// get all of the needed information to the user to show // get all of the needed information to the user to show
return ( return <CreateUserView stateHandler={stateHandler} inputNames={inputNames} onSubmit={onSubmit} />;
<CreateUserView
error={error}
setError={setError}
stateHandler={stateHandler}
inputNames={inputNames}
onSubmit={onSubmit}
/>
);
} }

View File

@ -2,11 +2,10 @@ import React from 'react';
import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton'; import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton';
import SimpleInput from '~/driven/utils/components/inputs/simple-input/SimpleInput'; import SimpleInput from '~/driven/utils/components/inputs/simple-input/SimpleInput';
import { staticMessages } from '~/driven/utils/constants/staticMessages'; import { staticMessages } from '~/driven/utils/constants/staticMessages';
import Notification from '~/driven/utils/components/Notification/Notification';
import ICreateUserViewProps from './protocols'; import ICreateUserViewProps from './protocols';
export default function CreateUserView(props: ICreateUserViewProps) { export default function CreateUserView(props: ICreateUserViewProps) {
const { onSubmit, inputNames, stateHandler, error, setError } = props; const { onSubmit, inputNames, stateHandler } = props;
const { inputStates, inputsSetStates } = stateHandler; const { inputStates, inputsSetStates } = stateHandler;
const inputs = inputNames.map((inputName) => { const inputs = inputNames.map((inputName) => {
@ -28,13 +27,6 @@ export default function CreateUserView(props: ICreateUserViewProps) {
}); });
return ( return (
<form onSubmit={onSubmit} className='px-4 my-8'> <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 flex-wrap w-full gap-4'>{inputs}</div>
<div className='flex'> <div className='flex'>
<PrimaryButton onClick={() => null} title={staticMessages.global.submit} /> <PrimaryButton onClick={() => null} title={staticMessages.global.submit} />

View File

@ -8,6 +8,4 @@ export default interface ICreateUserViewProps {
inputsSetStates: SetStateInputMethod<keyof INewUserData>; inputsSetStates: SetStateInputMethod<keyof INewUserData>;
}; };
inputNames: (keyof INewUserData)[]; inputNames: (keyof INewUserData)[];
error: { message: string; type: 'error' | 'success' };
setError: React.Dispatch<React.SetStateAction<{ message: string; type: 'error' | 'success' }>>;
} }

View File

@ -1,8 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { checkPhoneNumberInput } from '~/driven/utils/helpers/globalHelpers'; import { checkPhoneNumberInput } from '~/driven/utils/helpers/globalHelpers';
import { INewUserData } from '~/business-logic/core/users/create-user/create-account/data/dto/protocols'; 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 ICreateUserViewProps from '../view/protocols';
import IUseCreateUserVm from './protocols'; import IUseCreateUserVm from './protocols';
@ -15,8 +13,6 @@ const inputStateInitialValue: INewUserData = {
const inputNames: (keyof INewUserData)[] = ['firstname', 'lastname', 'phonenumber']; const inputNames: (keyof INewUserData)[] = ['firstname', 'lastname', 'phonenumber'];
const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps => { const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps => {
const [error, setError] = useState<{ message: string; type: 'error' | 'success' }>({ message: '', type: 'error' });
const { handleSubmitForm } = dependencies; const { handleSubmitForm } = dependencies;
const [inputsValue, setInputValues] = useState<INewUserData>(inputStateInitialValue); const [inputsValue, setInputValues] = useState<INewUserData>(inputStateInitialValue);
@ -28,18 +24,10 @@ const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps =
})); }));
}; };
const onSubmitCreateUserForm = async (e: React.FormEvent) => { const onSubmitCreateUserForm = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
try { console.log('submit user', inputsValue);
await handleSubmitForm(inputsValue); 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 }; const inputStates: INewUserData = { ...inputsValue };
@ -51,8 +39,6 @@ const useCreateUserVM = (dependencies: IUseCreateUserVm): ICreateUserViewProps =
}, },
onSubmit: onSubmitCreateUserForm, onSubmit: onSubmitCreateUserForm,
inputNames, inputNames,
error,
setError,
}; };
}; };

View File

@ -3,34 +3,23 @@ import getPlaces from '~/business-logic/core/places/get-places';
import getPlacesAdapter from '~/driven/adapters/get-places-adapter/getPlacesAdapter'; import getPlacesAdapter from '~/driven/adapters/get-places-adapter/getPlacesAdapter';
import PlacesModel from '~/business-logic/core/places/common/model/placesModel'; import PlacesModel from '~/business-logic/core/places/common/model/placesModel';
import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers'; 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 PlacesListView from '../view/PlacesListView';
import usePlacesListVM from '../viewmodel/placesListVM'; import usePlacesListVM from '../viewmodel/placesListVM';
import placesListModel from '../model/placesListModel'; import placesListModel from '../model/placesListModel';
const prepareTheLogicForModel = () => { const prepareTheLogicForModel = () => {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); const gettingPlacesDrivenAdapter = getPlacesAdapter();
const { user } = userData;
const gettingPlacesDrivenAdapter = getPlacesAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth);
const { url } = gettingPlacesDrivenAdapter; const { url } = gettingPlacesDrivenAdapter;
const getingPlacesLogic = getPlaces(gettingPlacesDrivenAdapter); const getingPlacesLogic = getPlaces(gettingPlacesDrivenAdapter);
return { getingPlacesLogic, url }; return { getingPlacesLogic, url };
}; };
export interface IPlacesListProps { export default function PlacessList() {
selectedRowId: string;
setSelectedRowId: React.Dispatch<React.SetStateAction<string>>;
}
export default function PlacessList(props: IPlacesListProps) {
const { selectedRowId, setSelectedRowId } = props;
const { getingPlacesLogic, url } = prepareTheLogicForModel(); const { getingPlacesLogic, url } = prepareTheLogicForModel();
const placesModel = async () => await placesListModel(getingPlacesLogic); const placesModel = async () => await placesListModel(getingPlacesLogic);
const useGetPlacesList = prepareStateManagementForVM<PlacesModel>(url, placesModel); const useGetPlacesList = prepareStateManagementForVM<PlacesModel>(url, placesModel);
const { placesData } = usePlacesListVM({ const { selectedRowId, setSelectedRowId, placesData } = usePlacesListVM({
useGetPlacesList, useGetPlacesList,
}); });
return <PlacesListView placesList={placesData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />; return <PlacesListView placesList={placesData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />;

View File

@ -3,32 +3,22 @@ import getUsersAdapter from '~/driven/adapters/get-users-adapter/getUsersAdapter
import getUsers from '~/business-logic/core/users/get-users'; import getUsers from '~/business-logic/core/users/get-users';
import UsersModel from '~/business-logic/core/users/common/data/model/usersModel'; import UsersModel from '~/business-logic/core/users/common/data/model/usersModel';
import { prepareStateManagementForVM } from '~/driven/utils/helpers/globalHelpers'; 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 useUsersListVM from '../viewmodel/usersListVM';
import UsersListView from '../view/UsersListView'; import UsersListView from '../view/UsersListView';
import usersListModel from '../model/usersListModel'; import usersListModel from '../model/usersListModel';
const usePrepareTheLogicForModel = () => { const prepareTheLogicForModel = () => {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); const gettingUsersDrivenAdapter = getUsersAdapter();
const { user } = userData;
const gettingUsersDrivenAdapter = getUsersAdapter(user as AdminUserModel, accessTokenUpdateHandler, notLoginAuth);
const { url } = gettingUsersDrivenAdapter; const { url } = gettingUsersDrivenAdapter;
const getingusersLogic = getUsers(gettingUsersDrivenAdapter); const getingusersLogic = getUsers(gettingUsersDrivenAdapter);
return { getingusersLogic, url }; return { getingusersLogic, url };
}; };
export interface IUsersListProps { export default function UsersList() {
selectedRowId: string; const { getingusersLogic, url } = prepareTheLogicForModel();
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 usersModel = async () => await usersListModel(getingusersLogic);
const useGetusersList = prepareStateManagementForVM<UsersModel>(url, usersModel); const useGetusersList = prepareStateManagementForVM<UsersModel>(url, usersModel);
const { usersData } = useUsersListVM({ const { selectedRowId, setSelectedRowId, usersData } = useUsersListVM({
useGetusersList, useGetusersList,
}); });
return <UsersListView usersList={usersData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />; return <UsersListView usersList={usersData} selectedRowId={selectedRowId} setSelectedRowId={setSelectedRowId} />;

View File

@ -1,20 +1,6 @@
import React from 'react'; 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'; import AuthenticationView from '../view/AuthenticationView';
export default function Authentication() { export default function Authentication() {
const { accessTokenUpdateHandler, notLoginAuth, userData } = useGetNavigatorAndTokenUpdater(); return <AuthenticationView />;
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} />;
} }

View File

@ -20,7 +20,7 @@ export default function OtpCodeView(props: IOtpCodeView) {
tabIndex={i + 1} tabIndex={i + 1}
ref={(el: HTMLInputElement) => (otpChar.current[i] = el)} ref={(el: HTMLInputElement) => (otpChar.current[i] = el)}
key={`otp_char_${i}`} key={`otp_char_${i}`}
className='font-bold text-base inline-block w-5 md:w-6 lg:w-8 xl:w-10 bg-transparent text-center focus:outline-none' className='font-bold inline-block w-5 bg-transparent text-center focus:outline-none'
maxLength={1} maxLength={1}
defaultValue='_' defaultValue='_'
placeholder='_' placeholder='_'

View File

@ -2,30 +2,11 @@ import React, { useRef, useState } from 'react';
import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton'; import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton';
import { icons } from '~/driven/utils/constants/assertUrls'; import { icons } from '~/driven/utils/constants/assertUrls';
import { staticMessages } from '~/driven/utils/constants/staticMessages'; 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 OtpCode from '../otp-code-inputs';
import PhoneNumberAuth from './PhoneNumberAuth'; import PhoneNumberAuth from './PhoneNumberAuth';
interface IAuthenticationView { export default function AuthenticationView() {
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 [phoneNumberValue, setPhoneNumberValue] = useState('');
const [error, setError] = useState('');
const { setUser } = useUser();
const otpChar = useRef<HTMLInputElement[]>([]); const otpChar = useRef<HTMLInputElement[]>([]);
const statesName = { const statesName = {
phonenumber: <PhoneNumberAuth stateData={{ setState: setPhoneNumberValue, stateValue: phoneNumberValue }} />, phonenumber: <PhoneNumberAuth stateData={{ setState: setPhoneNumberValue, stateValue: phoneNumberValue }} />,
@ -33,37 +14,12 @@ export default function AuthenticationView(props: IAuthenticationView) {
}; };
const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber'); const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber');
const submitForm = async (e: React.FormEvent) => { const submitForm = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
try { setAuthState('otp');
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 ( return (
<div className='main-auth flex flex-nowrap justify-start flex-row-reverse h-screen w-screen'> <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 <form
onSubmit={submitForm} 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' 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'

View File

@ -1,14 +1,11 @@
import React from 'react'; import React from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { routesData } from '~/driven/utils/configs/appConfig'; import { routesData } from '~/driven/utils/configs/appConfig';
import { icons } from '~/driven/utils/constants/assertUrls'; 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() { export default function Sidebar() {
const isCurrentPage = useLocation(); const isCurrentPage = useLocation();
const userCTX = useUser();
const navigator = useNavigate();
const pages = Object.keys(routesData).map((routeKey) => { const pages = Object.keys(routesData).map((routeKey) => {
const key = routeKey as keyof typeof routesData; const key = routeKey as keyof typeof routesData;
return ( return (
@ -30,12 +27,7 @@ export default function Sidebar() {
<div className='logo'> <div className='logo'>
<img src={icons.logo} alt='logo icon' /> <img src={icons.logo} alt='logo icon' />
</div> </div>
<div className='mt-14 flex flex-col items-baseline'> <div className='mt-14 flex flex-col items-baseline'>{pages}</div>
{pages}
<div className='mt-auto text-white px-3 absolute bottom-5'>
<button onClick={() => navigateToAuth(userCTX, navigator)}>Log out</button>
</div>
</div>
</aside> </aside>
); );
} }

View File

@ -1,21 +1,13 @@
import { useUser } from '~/driven/utils/helpers/contexts/userContext'; import { BrowserRouter as RouterWrapper } from 'react-router-dom';
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'; import Router from './Router/Router';
import './style/App.css';
function App() { function App() {
const data = useUser(); return (
const { setUser } = data; <RouterWrapper>
<Router />
useEffect(() => { </RouterWrapper>
const storage = StorageService.localStorage<AdminUserModel>(); );
const currentUser = storage.getData(appConfig.adminUserStorageKey);
if (currentUser && currentUser.adminUserData.accessToken && setUser) setUser(currentUser);
}, []);
return <Router />;
} }
export default App; export default App;

View File

@ -4,7 +4,6 @@ import { routes } from '~/driven/utils/configs/appConfig';
import CreateUser from '../pages/CreateUser'; import CreateUser from '../pages/CreateUser';
import MainPageLayout from '../pages/layouts/MainPageLayout'; import MainPageLayout from '../pages/layouts/MainPageLayout';
import AuthenticationPage from '../pages/Authentication'; import AuthenticationPage from '../pages/Authentication';
import UserLoginLayout from '../pages/layouts/UserLoginLayout';
export default function Router() { export default function Router() {
const location = useLocation(); const location = useLocation();
@ -15,9 +14,7 @@ export default function Router() {
<Route index element={<Home />} /> <Route index element={<Home />} />
<Route path={routes.createUser} element={<CreateUser />} /> <Route path={routes.createUser} element={<CreateUser />} />
</Route> </Route>
<Route element={<UserLoginLayout />}> <Route path={routes.authentication} element={<AuthenticationPage />} />
<Route path={routes.authentication} element={<AuthenticationPage />} />
</Route>
<Route path='*' element={<Navigate to={routes.usersList} replace />} /> <Route path='*' element={<Navigate to={routes.usersList} replace />} />
</Routes> </Routes>
); );

View File

@ -1,12 +1,4 @@
import ReactDOM from 'react-dom/client'; 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'; import App from './App';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />);
<UserProvider>
<RouterWrapper>
<App />
</RouterWrapper>
</UserProvider>,
);

View File

@ -1,84 +1,21 @@
import { AxiosError } from 'axios'; import React from 'react';
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 PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton';
import PageTitle from '~/driven/utils/components/page-title/pageTitle'; import PageTitle from '~/driven/utils/components/page-title/pageTitle';
import { apiUrls } from '~/driven/utils/configs/appConfig';
import { staticMessages } from '~/driven/utils/constants/staticMessages'; import { staticMessages } from '~/driven/utils/constants/staticMessages';
import useGetNavigatorAndTokenUpdater from '~/driven/utils/helpers/hooks/getNavigatorAndAccessTokenUpdator';
import PlacesList from '~/driving/application/core/places-list'; import PlacesList from '~/driving/application/core/places-list';
import UsersList from '~/driving/application/core/users-list'; import UsersList from '~/driving/application/core/users-list';
export default function index() { 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 ( 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} /> <PageTitle className='px-4 py-5' title={staticMessages.global.users} />
<div className='container mx-auto px-4'> <div className='container mx-auto px-4'>
<form onSubmit={onSubmitMember} className='w-full flex flex-row-reverse items-center py-2 sticky top-0'> <div className='w-full flex flex-row-reverse items-center py-2'>
<PrimaryButton <PrimaryButton className='text-sm' title={staticMessages.global.submit} onClick={() => null} />
isDisabled={isSubmitDisabled} </div>
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'> <div className='md:grid-cols-2 gap-x-4 grid grid-cols-1 mx-auto'>
<UsersList selectedRowId={selectedUserRowId} setSelectedRowId={setSelectedUserRowId} /> <UsersList />
<PlacesList selectedRowId={selectedPlaceRowId} setSelectedRowId={setSelectedPlaceRowId} /> <PlacesList />
</div> </div>
</div> </div>
</> </>

View File

@ -1,27 +1,12 @@
import React, { useEffect } from 'react'; import React from 'react';
import { Navigate, Outlet } from 'react-router-dom'; import { 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'; 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() { 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 ( return (
<div className='flex flex-nowrap min-h-screen'> <div className='flex flex-nowrap min-h-screen'>
<Sidebar /> <Sidebar />
<main className='dipal-panel w-full text-black bg-white h-screen overflow-auto'> <main className='dipal-panel w-full text-black bg-white h-fit'>
<Outlet /> <Outlet />
</main> </main>
</div> </div>

View File

@ -1,10 +0,0 @@
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 />;
}