feature/get-places-api #2
@ -26,11 +26,10 @@ export default class OtpAuthUsecase implements IOtpAuthUsecase {
|
|||||||
if (!isOtpValid) throw new OtpAuthUsecaseException();
|
if (!isOtpValid) throw new OtpAuthUsecaseException();
|
||||||
// call the repo
|
// call the repo
|
||||||
const updatedAdminModal = await this.repository.execute(data);
|
const updatedAdminModal = await this.repository.execute(data);
|
||||||
|
|
||||||
return updatedAdminModal;
|
return updatedAdminModal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isOtpValid(otp: string) {
|
private isOtpValid(otp: string) {
|
||||||
return !!(otp.length < this.validLenghtOfOtp);
|
return !!(otp.length >= this.validLenghtOfOtp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@ export interface IPhonenumberAuthInfra {
|
|||||||
wrongPhoneNumberMessage: string;
|
wrongPhoneNumberMessage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const phonenumberAuthInfra = async (
|
const phonenumberAuthInfra = ({
|
||||||
httpHandler: RepoHttpHandler,
|
httpHandler,
|
||||||
wrongPhoneNumberMessage: string,
|
wrongPhoneNumberMessage,
|
||||||
): Promise<PhonenumberAuthUsecase> => {
|
}: IPhonenumberAuthInfra): PhonenumberAuthUsecase => {
|
||||||
// prepare repo
|
// prepare repo
|
||||||
const repository = new PhoneNumberAuthRepo(httpHandler);
|
const repository = new PhoneNumberAuthRepo(httpHandler);
|
||||||
// prepare usecase
|
// prepare usecase
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import AdminUser from '../../entity/adminUserEntity';
|
import AdminUser from '../../entity/adminUserEntity';
|
||||||
|
|
||||||
export default class AdminUserModel {
|
export default class AdminUserModel {
|
||||||
private adminUserData: AdminUser;
|
adminUserData: AdminUser;
|
||||||
|
|
||||||
constructor(adminUserData: AdminUser) {
|
constructor(adminUserData: AdminUser) {
|
||||||
this.adminUserData = adminUserData;
|
this.adminUserData = adminUserData;
|
||||||
|
@ -14,6 +14,7 @@ const authAdminLogin = (): IOtpAuthDrivenPort => {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
return httpProvider.request<OtpAuthResponse>(options);
|
return httpProvider.request<OtpAuthResponse>(options);
|
||||||
|
@ -17,6 +17,7 @@ const authAdminPhoneNumberDriven = (): IPhonenumberAuthPort => {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
method: 'POST',
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
return httpProvider.request<string>(options);
|
return httpProvider.request<string>(options);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,9 @@ 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: '/',
|
||||||
|
@ -3,6 +3,7 @@ export const staticMessages = {
|
|||||||
errors: {
|
errors: {
|
||||||
input: 'please fill all inputs correctly',
|
input: 'please fill all inputs correctly',
|
||||||
phonenumber: 'please fill the valid number',
|
phonenumber: 'please fill the valid number',
|
||||||
|
otp: 'please fill the otp fields correctly',
|
||||||
},
|
},
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
submit: 'Submit',
|
submit: 'Submit',
|
||||||
|
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';
|
||||||
|
|
||||||
|
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,6 +1,16 @@
|
|||||||
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 AuthenticationView from '../view/AuthenticationView';
|
import AuthenticationView from '../view/AuthenticationView';
|
||||||
|
|
||||||
export default function Authentication() {
|
export default function Authentication() {
|
||||||
return <AuthenticationView />;
|
const authPhonenumberDriven = authAdminPhoneNumberDriven();
|
||||||
|
const authPhoneLogic = phonenumberAuthInfra(authPhonenumberDriven);
|
||||||
|
|
||||||
|
const authLoginDriven = authAdminLogin();
|
||||||
|
const otpAuthLogic = otpAuthInfra(authLoginDriven.httpHandler);
|
||||||
|
|
||||||
|
return <AuthenticationView authPhone={authPhoneLogic} otpAuth={otpAuthLogic} />;
|
||||||
}
|
}
|
||||||
|
@ -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-10 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}
|
maxLength={1}
|
||||||
defaultValue='_'
|
defaultValue='_'
|
||||||
placeholder='_'
|
placeholder='_'
|
||||||
|
@ -2,11 +2,30 @@ 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';
|
||||||
|
|
||||||
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 [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 }} />,
|
||||||
@ -14,12 +33,37 @@ export default function AuthenticationView() {
|
|||||||
};
|
};
|
||||||
const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber');
|
const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber');
|
||||||
|
|
||||||
const submitForm = (e: React.FormEvent) => {
|
const submitForm = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
if (authState === 'phonenumber') {
|
||||||
|
await authPhone.execute({ phonenumber: phoneNumberValue });
|
||||||
setAuthState('otp');
|
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'
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import { BrowserRouter as RouterWrapper } from 'react-router-dom';
|
import { useUser } from '~/driven/utils/helpers/contexts/userContext';
|
||||||
import Router from './Router/Router';
|
|
||||||
import './style/App.css';
|
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() {
|
function App() {
|
||||||
return (
|
const data = useUser();
|
||||||
<RouterWrapper>
|
const { setUser } = data;
|
||||||
<Router />
|
|
||||||
</RouterWrapper>
|
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;
|
export default App;
|
||||||
|
@ -4,6 +4,7 @@ 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();
|
||||||
@ -14,7 +15,9 @@ 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>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
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(<App />);
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
|
<UserProvider>
|
||||||
|
<RouterWrapper>
|
||||||
|
<App />
|
||||||
|
</RouterWrapper>
|
||||||
|
</UserProvider>,
|
||||||
|
);
|
||||||
|
@ -1,8 +1,24 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
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';
|
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 />
|
||||||
|
11
src/driving/main/pages/layouts/UserLoginLayout.tsx
Normal file
11
src/driving/main/pages/layouts/UserLoginLayout.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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) return <Navigate to={routes.usersList} replace />;
|
||||||
|
return <Outlet />;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user