feature/get-places-api #2
@ -11,7 +11,7 @@ export class HTTPPovider {
|
||||
...customOptions.headers,
|
||||
mode: 'cors',
|
||||
credentials: 'include',
|
||||
Authorization: `Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4NXh0WnA5eThxVDBXVDkwUFpuUkRja3N4LWw0clVyM0tHQW5JSU9DckJNIn0.eyJleHAiOjE2ODQ2Njc2MTEsImlhdCI6MTY4NDU4MTIxMSwianRpIjoiN2VlNzQ5ZTMtMjdhOC00ZTc1LWE4MTAtOTU0MGY5NDdmNjlkIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9kaXBhbF9kZXYiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiY2RmYzY3YzQtZGJkOC00NGVhLWI0OWEtYjQ3MjZhMzNmOTAxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY29tZm9ydGVjaCIsInNlc3Npb25fc3RhdGUiOiI3YTFlZDk2OS1lNWY2LTQzZTctOThhMy05OGQ3Zjk3YWM1NDgiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1tYXN0ZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiY29tZm9ydGVjaCI6eyJyb2xlcyI6WyJ1c2VyIiwib3BlcmF0b3IiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjdhMWVkOTY5LWU1ZjYtNDNlNy05OGEzLTk4ZDdmOTdhYzU0OCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiKzc3Nzc3Nzc3Nzc3In0.qp1GetUEy2LOZWy6Aaiwf_0d0U8wBqXJQhhuSgIO4RkkEgUnwYZ5fFupkp1iTXZMpqWAsmbRp-C0Z8nYT8Hor6XjnE73XwAVVY0Jbx6HSxtcTBOqo2IT0SmVm6z-TFpgYnErHiFZZgsqP4KYkc12xlQH4SrpN-h-oXN4ZtwuOIG65ixt2yKC-8KTyZzfZGa_8llAtnthQBtxX00MdivFpRP-NU1KfCtJqHSTKn40RNs-Nt8Gi_x7vWv9OKD8h-IIp27oOCJZNyL4aa237cuPw9IWbdiDuUAOgxkPw30i9LIDPA70GvdpRKWgLq0-itcT_hpf2RguuALDafaqoGgoGQ`,
|
||||
Authorization: `Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4NXh0WnA5eThxVDBXVDkwUFpuUkRja3N4LWw0clVyM0tHQW5JSU9DckJNIn0.eyJleHAiOjE2ODQ4MzM5NzcsImlhdCI6MTY4NDc0NzU3NywianRpIjoiYjE5MDEyNGItYzRjNC00NzlkLThkYWItN2VjODc1MjljZWQyIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9kaXBhbF9kZXYiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiY2RmYzY3YzQtZGJkOC00NGVhLWI0OWEtYjQ3MjZhMzNmOTAxIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY29tZm9ydGVjaCIsInNlc3Npb25fc3RhdGUiOiJiYjFmNjc3OC0xMzlhLTRmNzItOWM3Ny01NjAwMWU0NDYzNjQiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1tYXN0ZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiY29tZm9ydGVjaCI6eyJyb2xlcyI6WyJ1c2VyIiwib3BlcmF0b3IiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6ImJiMWY2Nzc4LTEzOWEtNGY3Mi05Yzc3LTU2MDAxZTQ0NjM2NCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiKzc3Nzc3Nzc3Nzc3In0.qJS5_c9g2AADwusGpquWw7zMvc42tzJ0yUMcM6jI6F2MNH2tFDqMhvG0nEnCwXIxJA54DZL8HHPDoxkhq_xyP2SSRKEU-S7pncpa2acNzOYT68pLxBD6s3W-akQxxJVlr92RtegqaHf2BAZMwdMJl4VreX_avPCrEdPzv2dEMX7a2wxteYgzQJsYtaaVyCO4QADMiNVMWgXE00Hnn5Rxuhpe9Y7Kl9cWCO5JY63gYXGFC9yUBEqEYl6o9d6XKMkuiaLJRE2l4k5ycKuJWUjhvCaL7J_f68vJzNhkiuMqmX5q08SDlgktNHzyKTVXkndKz2EpQemzM6SXPLnohPwjAg`,
|
||||
},
|
||||
};
|
||||
const response = await axios<ApiGlobalResponseObject<R>>(options);
|
||||
|
40
src/driven/utils/components/Notification/Notification.tsx
Normal file
40
src/driven/utils/components/Notification/Notification.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
export interface notifInterface {
|
||||
type: 'error' | 'success' | 'warning';
|
||||
message: string;
|
||||
time?: number;
|
||||
onCloseCallback?: () => unknown;
|
||||
}
|
||||
export default function Notification({ message, type, time = 5, onCloseCallback }: notifInterface) {
|
||||
const notifRef = React.useRef<HTMLDivElement>(null);
|
||||
const el = React.useRef(document.createElement('div'));
|
||||
|
||||
React.useEffect(() => {
|
||||
const portal = document.getElementById('root');
|
||||
portal?.appendChild(el.current);
|
||||
|
||||
setTimeout(() => {
|
||||
el.current.remove();
|
||||
if (typeof onCloseCallback !== 'undefined') onCloseCallback();
|
||||
}, 1000 * time);
|
||||
|
||||
return () => {
|
||||
if (typeof onCloseCallback !== 'undefined') onCloseCallback();
|
||||
return el.current?.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div
|
||||
ref={notifRef}
|
||||
className={`fixed top-10 left-1/2 translate-x-[-50%] z-30 p-2 rounded-md text-black ${
|
||||
type === 'error' && 'bg-red-600 text-white'
|
||||
} ${type === 'success' && 'bg-green-600 text-white'} ${type === 'warning' && 'bg-yellow-500'}`}
|
||||
>
|
||||
{message}
|
||||
</div>,
|
||||
el.current,
|
||||
);
|
||||
}
|
@ -17,6 +17,7 @@ export const staticMessages = {
|
||||
phonenumber: 'Phone Number',
|
||||
enterPanel: 'Enter to Panel',
|
||||
enterPhoneNumber: 'Enter your phone number',
|
||||
enterOtpCode: 'Enter your Otp Code',
|
||||
},
|
||||
service: {
|
||||
errors: {
|
||||
|
@ -0,0 +1,3 @@
|
||||
import OtpCode from './infra/OtpCode';
|
||||
|
||||
export default OtpCode;
|
@ -0,0 +1,18 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import React from 'react';
|
||||
import OtpCodeView from '../view/OtpCodeView';
|
||||
import useOtpCodeVm from '../viewmodel/OtpCodeVM';
|
||||
|
||||
const OtpCode = React.forwardRef<HTMLInputElement[]>((_props, otpCharRef) => {
|
||||
const { eventHandlers } = useOtpCodeVm({
|
||||
otpChar: otpCharRef as unknown as React.MutableRefObject<HTMLInputElement[]>,
|
||||
});
|
||||
return (
|
||||
<OtpCodeView
|
||||
eventHandlers={eventHandlers}
|
||||
otpChar={otpCharRef as unknown as React.MutableRefObject<HTMLInputElement[]>}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default OtpCode;
|
@ -0,0 +1,38 @@
|
||||
/* eslint-disable consistent-return */
|
||||
/* eslint-disable no-return-assign */
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
import React from 'react';
|
||||
import { staticMessages } from '~/driven/utils/constants/staticMessages';
|
||||
|
||||
export interface IOtpCodeView {
|
||||
otpChar: React.MutableRefObject<HTMLInputElement[]>;
|
||||
eventHandlers: {
|
||||
handleFocusInput: (e: React.FocusEvent) => void;
|
||||
handleKeyPressInput: (e: React.KeyboardEvent) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export default function OtpCodeView(props: IOtpCodeView) {
|
||||
const { eventHandlers, otpChar } = props;
|
||||
const { handleFocusInput, handleKeyPressInput } = eventHandlers;
|
||||
const otpInputs = Array.from({ length: 6 }).map((digit, i) => (
|
||||
<input
|
||||
tabIndex={i + 1}
|
||||
ref={(el: HTMLInputElement) => (otpChar.current[i] = el)}
|
||||
key={`otp_char_${i}`}
|
||||
className='font-bold inline-block w-5 bg-transparent text-center focus:outline-none'
|
||||
maxLength={1}
|
||||
defaultValue='_'
|
||||
placeholder='_'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onFocus={handleFocusInput}
|
||||
onKeyDown={handleKeyPressInput}
|
||||
/>
|
||||
));
|
||||
return (
|
||||
<div className='mb-9 justify-end items-center text-xs self-start'>
|
||||
<div className='text-xs mb-9 self-start text-txt-medium'>{staticMessages.global.enterOtpCode}</div>
|
||||
<div className='w-full flex gap-4'>{otpInputs}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
import { useEffect } from 'react';
|
||||
import { IOtpCodeView } from '../view/OtpCodeView';
|
||||
|
||||
interface IOtpCodeVm {
|
||||
otpChar: React.MutableRefObject<HTMLInputElement[]>;
|
||||
}
|
||||
|
||||
type useOtpCodeReturnType = Pick<IOtpCodeView, 'eventHandlers'>;
|
||||
|
||||
const useOtpCodeVm = (dependencies: IOtpCodeVm): useOtpCodeReturnType => {
|
||||
const { otpChar } = dependencies;
|
||||
|
||||
function focusToInput(target: HTMLInputElement, inputTabIndex: number) {
|
||||
const input = target.parentElement?.querySelector(`input[tabindex="${inputTabIndex}"]`) as
|
||||
| HTMLInputElement
|
||||
| undefined;
|
||||
|
||||
if (!input) return;
|
||||
|
||||
setTimeout(() => input.focus(), 150);
|
||||
}
|
||||
|
||||
const handleFocusFirstInput = () => {
|
||||
if (!otpChar.current.length) return;
|
||||
|
||||
otpChar.current[0].focus();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleFocusFirstInput();
|
||||
}, []);
|
||||
|
||||
const handleFocusInput = (e: React.FocusEvent) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
// check previous inputs are not empty
|
||||
const currentIndex = target.getAttribute('tabindex');
|
||||
|
||||
if (!currentIndex || +currentIndex === 1) {
|
||||
target.select();
|
||||
return;
|
||||
}
|
||||
|
||||
// get first previous empty
|
||||
let isFindEmptyInput = false;
|
||||
const firstEmptyInput = otpChar.current.find((item) => {
|
||||
const otpItemIndex = item.getAttribute('tabindex');
|
||||
|
||||
if (!otpItemIndex) return false;
|
||||
|
||||
const isInputEmpty = !item.value.trim() || item.value.trim() === '_';
|
||||
|
||||
if (+otpItemIndex < +currentIndex && isInputEmpty && !isFindEmptyInput) {
|
||||
isFindEmptyInput = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (firstEmptyInput) {
|
||||
firstEmptyInput.select();
|
||||
return;
|
||||
}
|
||||
|
||||
// focus to it
|
||||
target.select();
|
||||
};
|
||||
|
||||
function goToPreviousInput(target: HTMLInputElement, currentIndex: number) {
|
||||
const preIndex = +currentIndex - 1;
|
||||
|
||||
if (!preIndex) return;
|
||||
|
||||
focusToInput(target, preIndex);
|
||||
}
|
||||
|
||||
function goToNextInput(target: HTMLInputElement, currentIndex: number) {
|
||||
// get next index
|
||||
const nextIndex = +currentIndex + 1;
|
||||
|
||||
if (!nextIndex) return;
|
||||
|
||||
focusToInput(target, nextIndex);
|
||||
}
|
||||
|
||||
const handleKeyPressInput = (e: React.KeyboardEvent) => {
|
||||
// target
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
||||
// get current index
|
||||
const currentIndex = target.getAttribute('tabindex');
|
||||
|
||||
if (!currentIndex) return;
|
||||
|
||||
const isRemoveChar = e.key.toLowerCase() === 'backspace';
|
||||
|
||||
if (isRemoveChar && +currentIndex !== 1) {
|
||||
goToPreviousInput(target, +currentIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
goToNextInput(target, +currentIndex);
|
||||
};
|
||||
|
||||
return {
|
||||
eventHandlers: {
|
||||
handleFocusInput,
|
||||
handleKeyPressInput,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default useOtpCodeVm;
|
@ -1,27 +1,38 @@
|
||||
import React from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import PrimaryButton from '~/driven/utils/components/buttons/primary-button/PrimaryButton';
|
||||
import SimpleInput from '~/driven/utils/components/inputs/simple-input/SimpleInput';
|
||||
import { icons } from '~/driven/utils/constants/assertUrls';
|
||||
import { staticMessages } from '~/driven/utils/constants/staticMessages';
|
||||
import OtpCode from '../otp-code-inputs';
|
||||
import PhoneNumberAuth from './PhoneNumberAuth';
|
||||
|
||||
export default function AuthenticationView() {
|
||||
const [phoneNumberValue, setPhoneNumberValue] = useState('');
|
||||
const otpChar = useRef<HTMLInputElement[]>([]);
|
||||
const statesName = {
|
||||
phonenumber: <PhoneNumberAuth stateData={{ setState: setPhoneNumberValue, stateValue: phoneNumberValue }} />,
|
||||
otp: <OtpCode ref={otpChar} />,
|
||||
};
|
||||
const [authState, setAuthState] = useState<keyof typeof statesName>('phonenumber');
|
||||
|
||||
const submitForm = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setAuthState('otp');
|
||||
};
|
||||
return (
|
||||
<div className='main-auth flex flex-nowrap justify-start flex-row-reverse h-screen w-screen'>
|
||||
<form 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'>
|
||||
<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'
|
||||
>
|
||||
<div className='w-48 h-[35%]'>
|
||||
<img src={icons.logoBlack} className='w-full h-12' alt='page icon' />
|
||||
</div>
|
||||
<div className='font-normal mb-4 text-lg self-start'>{staticMessages.global.enterPanel}</div>
|
||||
<div className='text-txt-medium text-xs mb-9 self-start'>{staticMessages.global.enterPhoneNumber}</div>
|
||||
<SimpleInput
|
||||
inputData={{ name: 'phoneNumber', title: staticMessages.global.phonenumber }}
|
||||
stateHanlder={() => null}
|
||||
className='mb-9 w-full self-start'
|
||||
/>
|
||||
{statesName[authState]}
|
||||
<PrimaryButton
|
||||
onClick={() => null}
|
||||
title={staticMessages.global.submit}
|
||||
className='bg-gradient-button w-full h-11'
|
||||
className='[background:var(--color-gradient-button)] hover:brightness-90 transition-all w-full h-11'
|
||||
/>
|
||||
</form>
|
||||
<div className='hidden md:flex md:w-[50%] lg:w-[65%] h-full' />
|
||||
|
@ -0,0 +1,32 @@
|
||||
import SimpleInput, { SetStateInputMethod } from '~/driven/utils/components/inputs/simple-input/SimpleInput';
|
||||
import { staticMessages } from '~/driven/utils/constants/staticMessages';
|
||||
import { checkPhoneNumberInput } from '~/driven/utils/helpers/globalHelpers';
|
||||
|
||||
interface IPhoneNumberAuth {
|
||||
stateData: {
|
||||
stateValue: string;
|
||||
setState: React.Dispatch<React.SetStateAction<string>>;
|
||||
};
|
||||
}
|
||||
export default function PhoneNumberAuth(props: IPhoneNumberAuth) {
|
||||
const { stateData } = props;
|
||||
const { setState, stateValue } = stateData;
|
||||
const onChangeInput: SetStateInputMethod<typeof staticMessages.global.phonenumber> = (
|
||||
_name: typeof staticMessages.global.phonenumber,
|
||||
newValue: string,
|
||||
) => {
|
||||
if (!checkPhoneNumberInput(newValue)) return;
|
||||
setState(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='text-txt-medium text-xs mb-9 self-start'>{staticMessages.global.enterPhoneNumber}</div>
|
||||
<SimpleInput
|
||||
inputData={{ name: staticMessages.global.phonenumber, title: staticMessages.global.phonenumber }}
|
||||
stateHanlder={{ setState: onChangeInput, state: stateValue }}
|
||||
className='mb-9 w-full self-start'
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user