feature/get-places-api #2

Merged
behnam merged 26 commits from feature/get-places-api into develop 2023-05-23 09:23:56 +00:00
20 changed files with 200 additions and 24 deletions
Showing only changes of commit 0fc508c56b - Show all commits

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
import StorageProvider from './storageProvider';
export default class LocalStorageProvider<ValueType> implements StorageProvider<ValueType> {
setData(key: string, value: ValueType): void {
const jsonValue = JSON.stringify(value);
localStorage.setItem(key, jsonValue);
}
getData(key: string): ValueType | null {
const jsonValue = localStorage.getItem(key);
const value: ValueType = jsonValue && JSON.parse(jsonValue);
return value;
}
deleteData(key: string): void {
localStorage.removeItem(key);
}
}

View File

@ -0,0 +1,7 @@
export default abstract class StorageProvider<ValueType> {
abstract setData(key: string, value: ValueType): void;
abstract getData(key: string): ValueType | null;
abstract deleteData(key: string): void;
}

View File

@ -0,0 +1,27 @@
import LocalStorageProvider from './localStorageProvider';
import StorageProvider from './storageProvider';
export default class StorageService<ValueType> implements StorageProvider<ValueType> {
private provider: StorageProvider<ValueType>;
constructor(provider: StorageProvider<ValueType>) {
this.provider = provider;
}
static localStorage<ValuType>(): StorageService<ValuType> {
const localStorageService = new StorageService<ValuType>(new LocalStorageProvider<ValuType>());
return localStorageService;
}
setData(key: string, value: ValueType): void {
return this.provider.setData(key, value);
}
getData(key: string): ValueType | null {
return this.provider.getData(key);
}
deleteData(key: string): void {
this.provider.deleteData(key);
}
}

View File

@ -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: '/',

View File

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

View 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);

View File

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

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-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='_'

View File

@ -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();
setAuthState('otp'); try {
if (authState === 'phonenumber') {
await authPhone.execute({ phonenumber: phoneNumberValue });
setAuthState('otp');
}
if (authState === 'otp') {
const otp = otpChar.current
.map((inputItem) => {
return inputItem.value;
})
.join('');
const userModel = await otpAuth.execute({ otp, phonenumber: phoneNumberValue });
const storage = StorageService.localStorage();
storage.setData(appConfig.adminUserStorageKey, userModel);
if (setUser) setUser(userModel);
}
} catch (errorException) {
if (errorException instanceof OtpAuthUsecaseException) {
setError(staticMessages.global.errors.otp);
} else if (errorException instanceof AxiosError) {
setError(errorException.response?.data?.description);
} else if (errorException instanceof Error) {
setError(errorException.message);
}
}
}; };
return ( 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,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;

View File

@ -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 path={routes.authentication} element={<AuthenticationPage />} /> <Route element={<UserLoginLayout />}>
<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,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>,
);

View File

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

View 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 />;
}