diff --git a/App.tsx b/App.tsx index 281e3289..dc47f524 100644 --- a/App.tsx +++ b/App.tsx @@ -8,50 +8,18 @@ import store, {persistor} from './src/store/store'; import {PersistGate} from 'redux-persist/integration/react'; import {NavigationContainer} from '@react-navigation/native'; -import {createNativeStackNavigator} from '@react-navigation/native-stack'; -import Widget from './src/components/form'; import {navigationRef} from './src/components/utlis/navigationUtlis'; import FullScreenLoader from './RN-UI-LIB/src/components/FullScreenLoader'; - -function HomeScreen() { - return ( - - - - - - ); -} - -const Stack = createNativeStackNavigator(); +import ProtectedRouter from './ProtectedRouter'; const App = () => { - // const [hasBeenManuplated, setHasBeenManuplated] = useState(false); return ( } persistor={persistor}> - - null, - }} - /> - {Object.keys(data.widgets).map(key => ( - null, - }} - /> - ))} - + diff --git a/ProtectedRouter.tsx b/ProtectedRouter.tsx new file mode 100644 index 00000000..7717428e --- /dev/null +++ b/ProtectedRouter.tsx @@ -0,0 +1,86 @@ +import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import React from 'react'; +import {View} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import {useSelector} from 'react-redux'; +import Widget from './src/components/form'; +import RenderingEngine from './src/components/formRenderingEngine'; +import Login from './src/components/login'; +import OtpInput from './src/components/login/OtpInput'; +import {RootState} from './src/store/store'; +import data from './src/data/templateData.json'; +import userData from './src/data/userData.json'; +import {getUniqueId} from 'react-native-device-info'; +import {useAppDispatch} from './src/hooks'; +import {setAuthData} from './src/reducer/commonSlice'; + +const Stack = createNativeStackNavigator(); + +const LoginScreen = () => ; + +const OTPScreen = () => ; + +const HomeScreen = () => ( + + + + + +); + +const ProtectedRouter = () => { + const {isLoggedIn, deviceId} = useSelector( + (state: RootState) => state.common.userData, + ); + + const dispatch = useAppDispatch(); + + if (!deviceId) { + getUniqueId().then(id => dispatch(setAuthData({deviceId: id}))); + } + + return ( + + {isLoggedIn ? ( + <> + null, + }} + /> + {Object.keys(data.widgets).map(key => ( + null, + }} + /> + ))} + + ) : ( + <> + null, + }} + /> + null, + }} + /> + + )} + + ); +}; + +export default ProtectedRouter; diff --git a/package.json b/package.json index 80b107f3..d1a02577 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,20 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "1.17.11", - "@react-native-firebase/app": "^16.4.6", - "@react-native-firebase/database": "^16.4.6", - "@react-navigation/native": "^6.0.16", - "@react-navigation/native-stack": "^6.9.4", + "@react-native-firebase/app": "16.4.6", + "@react-native-firebase/database": "16.4.6", + "@react-navigation/native": "6.0.16", + "@react-navigation/native-stack": "6.9.4", "@reduxjs/toolkit": "1.9.1", + "axios": "1.2.1", "react": "18.1.0", "react-hook-form": "7.40.0", "react-native": "0.70.6", + "react-native-device-info": "10.3.0", "react-native-image-picker": "4.10.2", "react-native-pager-view": "6.1.2", - "react-native-safe-area-context": "^4.4.1", - "react-native-screens": "^3.18.2", + "react-native-safe-area-context": "4.4.1", + "react-native-screens": "3.18.2", "react-native-svg": "13.6.0", "react-native-tab-view": "3.3.2", "react-native-toast-message": "2.1.5", diff --git a/src/action/authActions.ts b/src/action/authActions.ts new file mode 100644 index 00000000..46b40dc7 --- /dev/null +++ b/src/action/authActions.ts @@ -0,0 +1,69 @@ +import {setAuthData} from '../reducer/commonSlice'; +import axiosInstance, {ApiKeys, getApiUrl} from '../components/utlis/apiHelper'; +import { + setFormLoading, + setGetOTPError, + setShowOTPScreen, + setVerifyOTPError, + setVerifyOTPSuccess, +} from '../components/login/loginSlice'; +import {Dispatch} from '@reduxjs/toolkit'; +import {navigateToScreen} from '../components/utlis/navigationUtlis'; + +export interface GenerateOTPPayload { + phoneNumber: string; +} + +export interface VerifyOTPPayload { + otp: string; + otpToken: string; +} + +export const generateOTP = + ({phoneNumber}: GenerateOTPPayload, isResendOTP?: boolean) => + (dispatch: Dispatch) => { + const url = getApiUrl(ApiKeys.GENERATE_OTP); + dispatch(setFormLoading(true)); + axiosInstance + .post(url, {phoneNumber: Number(phoneNumber)}) + .then(response => { + if (response.status === 200) { + if (response?.data?.data?.otpToken) { + dispatch( + setShowOTPScreen({ + phoneNumber, + otpToken: response?.data?.data?.otpToken, + }), + ); + !isResendOTP && navigateToScreen('OTP'); + } + } + }) + .catch(err => { + if (err.response.status === 404) { + dispatch( + setGetOTPError('Enter a registered mobile number'), + ); + } else { + dispatch(setGetOTPError(err.response?.data?.message)); + } + }); + }; + +export const verifyOTP = + ({otp, otpToken}: VerifyOTPPayload) => + (dispatch: Dispatch) => { + const url = getApiUrl(ApiKeys.VERIFY_OTP); + + dispatch(setFormLoading(true)); + axiosInstance + .post(url, {otp, otpToken}, {headers: {donotHandleError: true}}) + .then((response: any) => { + const {sessionToken} = response?.data?.data; + dispatch(setAuthData({sessionToken, isLoggedIn: true})); + dispatch(setVerifyOTPSuccess('OTP verified')); + }) + .catch(err => { + dispatch(setVerifyOTPError('Invalid OTP, try again')); + }); + }; diff --git a/src/components/countdown/index.tsx b/src/components/countdown/index.tsx new file mode 100644 index 00000000..68b98b2e --- /dev/null +++ b/src/components/countdown/index.tsx @@ -0,0 +1,44 @@ +import {useEffect, useRef, useState} from 'react'; + +interface CountdownProps { + startFrom: number; + stopAt?: number; + interval?: number; + decrementBy?: number; + step?: (current: number) => void; + onComplete?: () => void; +} + +export const Countdown = (props: CountdownProps) => { + const { + startFrom = 0, + stopAt = 0, + interval = 1000, + decrementBy = 1, + step, + onComplete, + } = props; + const [ticker, setTicker] = useState(startFrom); + const intervalRef = useRef(); + useEffect(() => { + const tick = () => { + const newTickerValue = ticker - decrementBy; + + if (newTickerValue < stopAt) { + clearTimeout(intervalRef.current); + onComplete && onComplete(); + return; + } + + step && step(newTickerValue); + setTicker(newTickerValue); + }; + + clearTimeout(intervalRef.current); + intervalRef.current = setTimeout(tick, interval); + + return () => clearTimeout(intervalRef.current); + }, [ticker]); + + return <>{ticker}; +}; diff --git a/src/components/login/OtpInput.tsx b/src/components/login/OtpInput.tsx new file mode 100644 index 00000000..64adbca7 --- /dev/null +++ b/src/components/login/OtpInput.tsx @@ -0,0 +1,103 @@ +import React from 'react'; +import {Controller, useForm} from 'react-hook-form'; +import {StyleSheet} from 'react-native'; +import {SafeAreaView} from 'react-native-safe-area-context'; +import {useSelector} from 'react-redux'; +import Button from '../../../RN-UI-LIB/src/components/Button'; +import Heading from '../../../RN-UI-LIB/src/components/Heading'; +import Text from '../../../RN-UI-LIB/src/components/Text'; +import TextInput from '../../../RN-UI-LIB/src/components/TextInput'; +import BackArrowIcon from '../../../RN-UI-LIB/src/Icons/BackArrowIcon'; +import {GenericStyles} from '../../../RN-UI-LIB/src/styles'; +import {COLORS} from '../../../RN-UI-LIB/src/styles/colors'; +import { + verifyOTP, + VerifyOTPPayload, +} from '../../action/authActions'; +import {useAppDispatch} from '../../hooks'; +import {RootState} from '../../store/store'; +import {navigateToScreen} from '../utlis/navigationUtlis'; +import OtpText from './OtpText'; + +interface IOtpForm { + otp: string; +} + +const OtpInput = () => { + const { + handleSubmit, + control, + formState: {errors, isValid}, + } = useForm(); + + const {phoneNumber, otpToken, verifyOTPError, isLoading} = useSelector( + (state: RootState) => state.loginInfo, + ); + + const dispatch = useAppDispatch(); + + const handleVerifyOTP = (data: IOtpForm) => { + const payload: VerifyOTPPayload = { + otp: data.otp, + otpToken, + }; + dispatch(verifyOTP(payload)); + }; + + const handleBackClick = () => { + navigateToScreen('Login'); + }; + + return ( + + + Enter OTP + + OTP sent to {phoneNumber}{' '} + + Change + + + ( + onChange(value)} + value={value} + maxLength={4} + /> + )} + name="otp" + rules={{required: true, minLength: 4}} + /> + +