feature/get-places-api #2
@ -26,11 +26,10 @@ export default class OtpAuthUsecase implements IOtpAuthUsecase {
|
||||
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);
|
||||
return !!(otp.length >= this.validLenghtOfOtp);
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ export interface IPhonenumberAuthInfra {
|
||||
wrongPhoneNumberMessage: string;
|
||||
}
|
||||
|
||||
const phonenumberAuthInfra = async (
|
||||
httpHandler: RepoHttpHandler,
|
||||
wrongPhoneNumberMessage: string,
|
||||
): Promise<PhonenumberAuthUsecase> => {
|
||||
const phonenumberAuthInfra = ({
|
||||
httpHandler,
|
||||
wrongPhoneNumberMessage,
|
||||
}: IPhonenumberAuthInfra): PhonenumberAuthUsecase => {
|
||||
// prepare repo
|
||||
const repository = new PhoneNumberAuthRepo(httpHandler);
|
||||
// prepare usecase
|
||||
|
@ -1,7 +1,7 @@
|
||||
import AdminUser from '../../entity/adminUserEntity';
|
||||
|
||||
export default class AdminUserModel {
|
||||
private adminUserData: AdminUser;
|
||||
adminUserData: AdminUser;
|
||||
|
||||
constructor(adminUserData: AdminUser) {
|
||||
this.adminUserData = adminUserData;
|
||||
|
@ -14,6 +14,7 @@ const authAdminLogin = (): IOtpAuthDrivenPort => {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
data,
|
||||
};
|
||||
return httpProvider.request<OtpAuthResponse>(options);
|
||||
|
@ -17,6 +17,7 @@ const authAdminPhoneNumberDriven = (): IPhonenumberAuthPort => {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
data,
|
||||
};
|
||||
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 { staticMessages } from '../constants/staticMessages';
|
||||
|
||||
export const appConfig = {};
|
||||
export const appConfig = {
|
||||
adminUserStorageKey: 'adminUser',
|
||||
};
|
||||
|
||||
export const routes = {
|
||||
usersList: '/',
|
||||
|
@ -3,6 +3,7 @@ export const staticMessages = {
|
||||
errors: {
|
||||
input: 'please fill all inputs correctly',
|
||||
phonenumber: 'please fill the valid number',
|
||||
otp: 'please fill the otp fields correctly',
|
||||
},
|
||||
users: 'Users',
|
||||
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 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';
|
||||
|
||||
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}
|
||||
ref={(el: HTMLInputElement) => (otpChar.current[i] = el)}
|
||||
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}
|
||||
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();
|
||||
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,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 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,8 +1,24 @@
|
||||
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 />
|
||||
|
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