@fadyshawky/react-native-magic 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +1 -1
  2. package/template/App.tsx +28 -19
  3. package/template/ios/reactnativemagic/AppDelegate.mm +5 -0
  4. package/template/src/common/components/Background.tsx +6 -4
  5. package/template/src/common/components/Container.tsx +6 -9
  6. package/template/src/common/components/OTPInput.tsx +3 -2
  7. package/template/src/common/components/PrimaryButton.tsx +23 -23
  8. package/template/src/common/components/PrimaryTextInput.tsx +189 -199
  9. package/template/src/common/components/RadioIcon.tsx +4 -4
  10. package/template/src/common/components/SafeText.tsx +41 -0
  11. package/template/src/common/components/SearchBar.tsx +19 -17
  12. package/template/src/common/components/TryAgain.tsx +3 -3
  13. package/template/src/common/localization/LocalizationProvider.tsx +14 -17
  14. package/template/src/common/localization/RTLInitializer.tsx +90 -0
  15. package/template/src/common/localization/localization.ts +8 -0
  16. package/template/src/common/localization/translations/commonLocalization.ts +33 -6
  17. package/template/src/common/localization/translations/emptyLocalization.ts +6 -2
  18. package/template/src/common/localization/translations/errorsLocalization.ts +33 -13
  19. package/template/src/common/localization/translations/homeLocalization.ts +6 -2
  20. package/template/src/common/localization/translations/loginLocalization.ts +32 -9
  21. package/template/src/common/localization/translations/mainNavigationLocalization.ts +30 -0
  22. package/template/src/common/localization/translations/navigationLocalization.ts +48 -0
  23. package/template/src/common/localization/translations/onboardingLocalization.ts +40 -9
  24. package/template/src/common/localization/translations/otpLocalization.ts +28 -0
  25. package/template/src/common/localization/translations/pagesLocalization.ts +13 -1
  26. package/template/src/common/localization/translations/passwordLocalization.ts +54 -0
  27. package/template/src/common/localization/translations/profileLocalization.ts +4 -4
  28. package/template/src/common/utils/FeesCaalculation.tsx +37 -0
  29. package/template/src/common/utils/index.tsx +11 -0
  30. package/template/src/common/utils/printData.tsx +161 -0
  31. package/template/src/common/validations/errorValidations.ts +3 -2
  32. package/template/src/core/api/serverHeaders.ts +62 -1
  33. package/template/src/core/store/Categories/categoryActions.ts +33 -0
  34. package/template/src/core/store/Categories/categorySlice.ts +75 -0
  35. package/template/src/core/store/Categories/categoryState.ts +41 -0
  36. package/template/src/core/store/Providers/providersActions.ts +102 -0
  37. package/template/src/core/store/Providers/providersSlice.ts +136 -0
  38. package/template/src/core/store/Providers/providersState.ts +37 -0
  39. package/template/src/core/store/Services/servicesActions.ts +191 -0
  40. package/template/src/core/store/Services/servicesSlice.ts +205 -0
  41. package/template/src/core/store/Services/servicesState.ts +466 -0
  42. package/template/src/core/store/app/appSlice.ts +13 -5
  43. package/template/src/core/store/app/appState.ts +10 -2
  44. package/template/src/core/store/rootReducer.ts +6 -1
  45. package/template/src/core/store/store.tsx +55 -2
  46. package/template/src/core/store/user/userActions.ts +164 -26
  47. package/template/src/core/store/user/userSlice.ts +193 -21
  48. package/template/src/core/store/user/userState.ts +148 -25
  49. package/template/src/core/theme/colors.ts +12 -0
  50. package/template/src/core/theme/themes.ts +1 -1
  51. package/template/src/core/utils/stringUtils.ts +114 -0
  52. package/template/src/navigation/AuthStack.tsx +8 -0
  53. package/template/src/navigation/HeaderComponents.tsx +52 -1
  54. package/template/src/navigation/MainNavigation.tsx +3 -1
  55. package/template/src/navigation/MainStack.tsx +39 -56
  56. package/template/src/navigation/TabBar.tsx +111 -59
  57. package/template/src/navigation/types.ts +24 -0
  58. package/template/src/screens/Login/Login.tsx +83 -85
  59. package/template/src/screens/OTP/OTPScreen.tsx +169 -0
  60. package/template/src/screens/home/Components/PayByCode.tsx +129 -0
  61. package/template/src/screens/home/HomeScreen.tsx +1 -103
  62. package/template/src/screens/home/hooks/useHomeData.ts +32 -38
  63. package/template/src/screens/index.tsx +24 -0
  64. package/template/src/common/components/Stepper.tsx +0 -153
  65. package/template/src/common/components/Svg.tsx +0 -25
  66. package/template/src/common/hooks/useDebounce.ts +0 -17
  67. package/template/src/common/hooks/usePrevious.ts +0 -11
  68. package/template/src/common/urls/emailUrl.ts +0 -20
  69. package/template/src/common/urls/mapUrl.ts +0 -22
  70. package/template/src/common/utils/listHandlers.ts +0 -30
  71. package/template/src/common/utils/serializeQueryParams.ts +0 -10
  72. package/template/src/common/validations/hooks/useDatesError.ts +0 -40
  73. package/template/src/common/validations/profileValidations.ts +0 -30
  74. package/template/src/navigation/TopTabBar.tsx +0 -77
  75. package/template/src/screens/Settings/Settings.tsx +0 -5
  76. package/template/src/screens/home/components/CarouselSection.tsx +0 -79
  77. package/template/src/screens/home/components/FeaturedCarousel.tsx +0 -128
  78. package/template/src/screens/main/Main.tsx +0 -5
  79. package/template/src/screens/registration/RegistrationScreen.tsx +0 -198
  80. package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +0 -129
@@ -1,13 +1,26 @@
1
- import React from 'react';
2
- import {View, Text, TouchableOpacity, Image, StyleSheet} from 'react-native';
3
- import {CommonStyles} from '../core/theme/commonStyles';
4
1
  import {
5
2
  BottomTabBarProps,
6
3
  BottomTabNavigationOptions,
7
4
  } from '@react-navigation/bottom-tabs';
8
- import {ImageSourcePropType} from 'react-native';
9
5
  import {toString} from 'lodash';
10
- import {NaturalColors, PrimaryColors} from '../core/theme/colors';
6
+ import React from 'react';
7
+ import {
8
+ Image,
9
+ ImageSourcePropType,
10
+ StyleSheet,
11
+ TouchableOpacity,
12
+ } from 'react-native';
13
+ import {RTLAwareText} from '../common/components/RTLAwareText';
14
+ import {RTLAwareView} from '../common/components/RTLAwareView';
15
+ import {
16
+ useRTL,
17
+ useTranslation,
18
+ } from '../common/localization/LocalizationProvider';
19
+ import {BlackColors, NewColors} from '../core/theme/colors';
20
+ import {CommonSizes} from '../core/theme/commonSizes';
21
+ import {CommonStyles} from '../core/theme/commonStyles';
22
+ import {scaleHeight, scaleWidth} from '../core/theme/scaling';
23
+ import {useTheme} from '../core/theme/ThemeProvider';
11
24
 
12
25
  interface TabBarOptions extends BottomTabNavigationOptions {
13
26
  selectedIcon: ImageSourcePropType;
@@ -16,58 +29,94 @@ interface TabBarOptions extends BottomTabNavigationOptions {
16
29
  }
17
30
 
18
31
  export function TabBar({state, descriptors, navigation}: BottomTabBarProps) {
19
- return (
20
- <View style={styles.container}>
21
- {state.routes.map((route, index) => {
22
- const {options} = descriptors[route.key] as unknown as {
23
- options: TabBarOptions;
24
- };
25
- const label =
26
- options.tabBarLabel !== undefined
27
- ? options.tabBarLabel
28
- : options.title !== undefined
29
- ? options.title
30
- : route.name;
32
+ const {theme} = useTheme();
33
+ const t = useTranslation();
34
+ const isRTL = useRTL();
35
+ const tabArray = ['Main', 'Financials', 'Account'];
31
36
 
32
- const isFocused = state.index === index;
37
+ // Create a copy of routes array to avoid modifying the original
38
+ const routesToRender = [...state.routes].filter(r =>
39
+ tabArray.includes(r.name),
40
+ );
41
+
42
+ // If RTL, reverse the order of tabs
43
+ if (isRTL) {
44
+ routesToRender.reverse();
45
+ }
46
+
47
+ return (
48
+ state.index <= 3 && (
49
+ <RTLAwareView style={styles.container}>
50
+ {routesToRender.map((route, index) => {
51
+ // Calculate the correct index in the original array for focused state
52
+ const originalIndex = isRTL ? state.routes.length - 1 - index : index;
53
+ const isFocused = state.index === originalIndex;
33
54
 
34
- const onPress = () => {
35
- const event = navigation.emit({
36
- type: 'tabPress',
37
- target: route.key,
38
- canPreventDefault: true,
39
- });
55
+ const {options} = descriptors[route.key] as unknown as {
56
+ options: TabBarOptions;
57
+ };
40
58
 
41
- if (!isFocused && !event.defaultPrevented) {
42
- navigation.navigate(route.name, route.params);
59
+ // Get localized tab name
60
+ let label = route.name;
61
+ if (options.tabBarLabel !== undefined) {
62
+ label = options.tabBarLabel as string;
63
+ } else if (options.title !== undefined) {
64
+ label = options.title;
65
+ } else {
66
+ // Try to get localized name from mainNavigation translations
67
+ const localizedName = t(`tabs.${route.name}`, 'mainNavigation');
68
+ if (localizedName !== `tabs.${route.name}`) {
69
+ label = localizedName;
70
+ }
43
71
  }
44
- };
45
72
 
46
- const onLongPress = () => {
47
- navigation.emit({
48
- type: 'tabLongPress',
49
- target: route.key,
50
- });
51
- };
73
+ const onPress = () => {
74
+ const event = navigation.emit({
75
+ type: 'tabPress',
76
+ target: route.key,
77
+ canPreventDefault: true,
78
+ });
52
79
 
53
- return (
54
- <TouchableOpacity
55
- key={route.key}
56
- accessibilityRole="button"
57
- accessibilityState={isFocused ? {selected: true} : {}}
58
- accessibilityLabel={options.tabBarAccessibilityLabel}
59
- testID={options.tabBarTestID}
60
- onPress={onPress}
61
- onLongPress={onLongPress}
62
- style={styles.tabButton}>
63
- <Image source={isFocused ? options.selectedIcon : options.icon} />
64
- <Text style={[styles.label, isFocused && styles.labelFocused]}>
65
- {toString(label)}
66
- </Text>
67
- </TouchableOpacity>
68
- );
69
- })}
70
- </View>
80
+ if (!isFocused && !event.defaultPrevented) {
81
+ navigation.navigate(route.name, route.params);
82
+ }
83
+ };
84
+
85
+ const onLongPress = () => {
86
+ navigation.emit({
87
+ type: 'tabLongPress',
88
+ target: route.key,
89
+ });
90
+ };
91
+
92
+ return (
93
+ <TouchableOpacity
94
+ key={route.key}
95
+ accessibilityRole="button"
96
+ accessibilityState={isFocused ? {selected: true} : {}}
97
+ accessibilityLabel={
98
+ options.tabBarAccessibilityLabel || label?.toString()
99
+ }
100
+ testID={options.tabBarTestID}
101
+ onPressIn={onPress}
102
+ onLongPress={onLongPress}
103
+ style={styles.tabButton}>
104
+ <Image
105
+ style={{
106
+ width: scaleWidth(57),
107
+ height: scaleHeight(63),
108
+ resizeMode: 'contain',
109
+ }}
110
+ source={isFocused ? options.selectedIcon : options.icon}
111
+ />
112
+ <RTLAwareText style={[theme.text.navBar]}>
113
+ {toString(label)}
114
+ </RTLAwareText>
115
+ </TouchableOpacity>
116
+ );
117
+ })}
118
+ </RTLAwareView>
119
+ )
71
120
  );
72
121
  }
73
122
 
@@ -75,20 +124,23 @@ const styles = StyleSheet.create({
75
124
  container: {
76
125
  flexDirection: 'row',
77
126
  alignItems: 'center',
78
- height: 100,
79
- borderTopLeftRadius: 50,
80
- borderTopRightRadius: 50,
127
+ height: scaleHeight(130),
128
+ borderTopLeftRadius: CommonSizes.borderRadius.huge,
129
+ borderTopRightRadius: CommonSizes.borderRadius.huge,
81
130
  justifyContent: 'space-evenly',
82
- ...CommonStyles.shadow,
131
+ ...CommonStyles.dropShadow,
132
+ backgroundColor: BlackColors.indigoBlue,
133
+ position: 'absolute',
134
+ bottom: 0,
135
+ left: 0,
136
+ right: 0,
83
137
  },
84
138
  tabButton: {
85
139
  flex: 1,
86
140
  alignItems: 'center',
87
141
  },
88
- label: {
89
- color: NaturalColors.grayScale_50,
90
- },
142
+ label: {},
91
143
  labelFocused: {
92
- color: PrimaryColors.PlatinateBlue_700,
144
+ color: NewColors.blueNormalActive,
93
145
  },
94
146
  });
@@ -8,4 +8,28 @@ export type RootStackParamList = {
8
8
  Profile: undefined;
9
9
  Settings: undefined;
10
10
  Splash: undefined;
11
+ OTP: {phone?: string};
12
+ ForceChangePassword: undefined;
13
+ Services: {providerID: string};
14
+ SingleService: {serviceID?: string};
15
+ InquiredBill: undefined;
16
+ Loading: undefined;
17
+ PaymentConfirmation: {
18
+ paymentData: {
19
+ amountToBePaid: string;
20
+ billInfo: any;
21
+ fees: number;
22
+ currencyCode: string;
23
+ billRec: {
24
+ BillingAcct: string;
25
+ BillerId: string;
26
+ BillTypeCode: number;
27
+ BillRefNumber: string;
28
+ BillStatus: string;
29
+ };
30
+ };
31
+ };
32
+ Categories: undefined;
33
+ Providers: {categoryID: string};
34
+ ReceiptScreen: {type: 'print' | 'history'; historyID?: string};
11
35
  };
@@ -1,39 +1,44 @@
1
1
  import {useNavigation} from '@react-navigation/native';
2
2
  import type {NativeStackNavigationProp} from '@react-navigation/native-stack';
3
- import {toLower} from 'lodash';
4
3
  import React, {useRef, useState} from 'react';
5
- import {
6
- findNodeHandle,
7
- NativeSyntheticEvent,
8
- StyleSheet,
9
- Text,
10
- TextInputFocusEventData,
11
- } from 'react-native';
4
+ import {StyleSheet} from 'react-native';
12
5
  import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
13
6
  import {ButtonType} from '../../../types';
7
+ import {Container} from '../../common/components/Container';
14
8
  import {PrimaryButton} from '../../common/components/PrimaryButton';
15
9
  import {PrimaryTextInput} from '../../common/components/PrimaryTextInput';
16
- import {localization} from '../../common/localization/localization';
10
+ import {RTLAwareText} from '../../common/components/RTLAwareText';
11
+ import {RTLAwareView} from '../../common/components/RTLAwareView';
12
+ import {
13
+ useRTL,
14
+ useTranslation,
15
+ } from '../../common/localization/LocalizationProvider';
16
+ import {phoneValidations} from '../../common/validations/authValidations';
17
17
  import {emptyValidation} from '../../common/validations/commonValidations';
18
18
  import {useInputError} from '../../common/validations/hooks/useInputError';
19
- import {emailValidations} from '../../common/validations/profileValidations';
20
19
  import {useAppDispatch} from '../../core/store/reduxHelpers';
21
20
  import {userLogin} from '../../core/store/user/userActions';
22
21
  import {CommonSizes} from '../../core/theme/commonSizes';
23
- import {CommonStyles} from '../../core/theme/commonStyles';
22
+ import {Fonts} from '../../core/theme/fonts';
23
+ import {useTheme} from '../../core/theme/ThemeProvider';
24
+ import {Header} from '../../navigation/HeaderComponents';
24
25
  import type {RootStackParamList} from '../../navigation/types';
25
26
 
26
27
  export function Login(): JSX.Element {
27
28
  const dispatch = useAppDispatch();
28
- const [email, setEmail] = useState('');
29
+ const [phone, setPhone] = useState('');
29
30
  const [password, setPassword] = useState('');
30
31
  const [loading, setLoading] = useState(false);
31
32
  const navigation =
32
33
  useNavigation<NativeStackNavigationProp<RootStackParamList>>();
34
+ const scroll = useRef<KeyboardAwareScrollView>(null);
35
+ const {theme} = useTheme();
36
+ const t = useTranslation();
37
+ const isRTL = useRTL();
33
38
 
34
- const {error: emailError, recheckValue: recheckEmail} = useInputError(
35
- email,
36
- emailValidations,
39
+ const {error: phoneError, recheckValue: recheckPhone} = useInputError(
40
+ phone,
41
+ phoneValidations,
37
42
  );
38
43
  const {error: passwordError, recheckValue: recheckPassword} = useInputError(
39
44
  password,
@@ -41,111 +46,104 @@ export function Login(): JSX.Element {
41
46
  );
42
47
 
43
48
  async function loginUser() {
44
- const emailValid = recheckEmail() === null;
49
+ const phoneValid = recheckPhone() === null;
45
50
  const passwordValid = recheckPassword() === null;
46
51
 
47
- if (!emailValid || !passwordValid) {
52
+ if (!phoneValid || !passwordValid) {
48
53
  return;
49
54
  }
55
+ try {
56
+ setLoading(true);
57
+ const result = await dispatch(
58
+ userLogin({
59
+ phone: phone,
60
+ password,
61
+ }),
62
+ );
50
63
 
51
- setLoading(true);
52
- await dispatch(
53
- userLogin({
54
- email: toLower(email),
55
- password,
56
- }),
57
- );
58
- setLoading(false);
59
- }
60
- const scroll = useRef<KeyboardAwareScrollView>(null);
61
- function scrollToInput(reactNode: any) {
62
- // Add a 'scroll' ref to your ScrollView
63
-
64
- // setTimeout(() => {
65
- scroll.current?.scrollToFocusedInput(reactNode);
66
- // }, 500);
64
+ if (userLogin.fulfilled.match(result)) {
65
+ navigation.navigate('OTP', {phone: phone});
66
+ }
67
+ } catch (error) {
68
+ } finally {
69
+ setLoading(false);
70
+ }
67
71
  }
68
72
 
69
- const goToRegistration = () => {
70
- navigation.navigate('Registration');
71
- };
72
-
73
73
  const goToForgotPassword = () => {
74
74
  navigation.navigate('ForgotPassword');
75
75
  };
76
76
 
77
77
  return (
78
- <KeyboardAwareScrollView
78
+ <Container
79
79
  ref={scroll}
80
- resetScrollToCoords={{x: 0, y: 0}}
81
- scrollEnabled={true}
82
- enableOnAndroid={true}
83
- testID={'MainPageID'}
80
+ testID={'LoginScreenID'}
84
81
  contentContainerStyle={styles.contentContainer}
85
- contentInsetAdjustmentBehavior={'automatic'}
86
- style={styles.container}>
87
- <Text style={CommonStyles.h1_semiBold}>{localization.login.Login}</Text>
82
+ style={styles.container}
83
+ backgroundImage={0}
84
+ withoutPadding
85
+ extendedBackground
86
+ backgroundColor={theme.colors.background_2}>
87
+ <Header />
88
+ <RTLAwareText style={{...theme.text.header1, textAlign: 'center'}}>
89
+ {t('welcome', 'login')}
90
+ </RTLAwareText>
88
91
  <PrimaryTextInput
89
- onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
90
- scrollToInput(findNodeHandle(event.target));
91
- }}
92
- autoCapitalize="none"
93
- value={email}
94
- onChangeText={setEmail}
95
- containerStyle={CommonStyles.textInputContainer}
96
- label={localization.login.Email}
97
- placeholder={localization.login.EnterEmail}
98
- error={emailError}
92
+ value={phone}
93
+ onChangeText={setPhone}
94
+ error={phoneError}
95
+ keyboardType="numeric"
96
+ placeholder={t('EnterPhone', 'login')}
99
97
  />
100
98
  <PrimaryTextInput
101
- onFocus={(event: NativeSyntheticEvent<TextInputFocusEventData>) => {
102
- scrollToInput(findNodeHandle(event.target));
103
- }}
104
- clearButtonMode="never"
105
99
  value={password}
106
- secureTextEntry
107
100
  onChangeText={setPassword}
108
- containerStyle={CommonStyles.textInputContainer}
109
- label={localization.login.Password}
110
- placeholder={localization.login.EnterPassword}
111
101
  error={passwordError}
102
+ secureTextEntry={true}
103
+ keyboardType="numeric"
104
+ placeholder={t('EnterPassword', 'login')}
112
105
  />
106
+
107
+ <RTLAwareView
108
+ style={{width: '100%', alignItems: isRTL ? 'flex-start' : 'flex-end'}}>
109
+ <PrimaryButton
110
+ label={t('forgetPassword', 'login')}
111
+ onPressIn={goToForgotPassword}
112
+ type={ButtonType.borderless}
113
+ />
114
+ </RTLAwareView>
113
115
  <PrimaryButton
116
+ label={t('Login', 'login')}
117
+ onPressIn={loginUser}
114
118
  isLoading={loading}
115
- onPress={loginUser}
116
- label={localization.login.continue}
119
+ disabled={loading}
117
120
  type={ButtonType.solid}
118
121
  />
119
- </KeyboardAwareScrollView>
122
+ </Container>
120
123
  );
121
124
  }
122
125
 
123
126
  const styles = StyleSheet.create({
124
- imageContainer: {
125
- ...CommonStyles.flex1,
126
- justifyContent: 'flex-end',
127
- },
128
127
  container: {
129
128
  flexGrow: 1,
130
- borderTopRightRadius: CommonSizes.borderRadius.lg,
131
- borderTopLeftRadius: CommonSizes.borderRadius.lg,
129
+ borderTopRightRadius: CommonSizes.spacing.large,
130
+ borderTopLeftRadius: CommonSizes.spacing.large,
131
+ paddingHorizontal: CommonSizes.spacing.large,
132
+ gap: CommonSizes.spacing.xl,
133
+ justifyContent: 'flex-start',
132
134
  },
133
135
  contentContainer: {
134
- justifyContent: 'center',
135
- alignItems: 'center',
136
- paddingHorizontal: CommonSizes.spacing.lg,
137
- paddingVertical: 26,
138
- gap: 16,
136
+ flexGrow: 1,
139
137
  },
140
- forgotPassword: {
141
- ...CommonStyles.normalText,
142
- textAlign: 'right',
143
- marginTop: 8,
144
- marginBottom: 24,
138
+ formContainer: {
139
+ alignItems: 'center',
140
+ paddingHorizontal: CommonSizes.spacing.large,
141
+ gap: CommonSizes.spacing.large,
145
142
  },
146
- registerLink: {
147
- ...CommonStyles.normalText,
143
+ title: {
148
144
  textAlign: 'center',
149
- marginTop: 16,
145
+ fontSize: CommonSizes.font.bodyLarge,
146
+ fontWeight: 'bold',
147
+ fontFamily: Fonts.regular,
150
148
  },
151
149
  });
@@ -0,0 +1,169 @@
1
+ import {useNavigation, useRoute} from '@react-navigation/native';
2
+ import type {NativeStackNavigationProp} from '@react-navigation/native-stack';
3
+ import React, {useRef, useState} from 'react';
4
+ import {StyleSheet, View} from 'react-native';
5
+ import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
6
+ import {ButtonType} from '../../../types';
7
+ import {Container} from '../../common/components/Container';
8
+ import {OTPInput} from '../../common/components/OTPInput';
9
+ import {PrimaryButton} from '../../common/components/PrimaryButton';
10
+ import {RTLAwareText} from '../../common/components/RTLAwareText';
11
+ import {RTLAwareView} from '../../common/components/RTLAwareView';
12
+ import {
13
+ useRTL,
14
+ useTranslation,
15
+ } from '../../common/localization/LocalizationProvider';
16
+ import {useInputError} from '../../common/validations/hooks/useInputError';
17
+ import {useAppDispatch} from '../../core/store/reduxHelpers';
18
+ import {verifyOTP} from '../../core/store/user/userActions';
19
+ import {CommonSizes} from '../../core/theme/commonSizes';
20
+ import {useTheme} from '../../core/theme/ThemeProvider';
21
+ import {HeaderBack} from '../../navigation/HeaderComponents';
22
+ import type {RootStackParamList} from '../../navigation/types';
23
+
24
+ export function OTPScreen(): JSX.Element {
25
+ const dispatch = useAppDispatch();
26
+ const [otp, setOTP] = useState('');
27
+ const [loading, setLoading] = useState(false);
28
+ const [resendDisabled, setResendDisabled] = useState(false);
29
+ const [timer, setTimer] = useState(60);
30
+ const navigation =
31
+ useNavigation<NativeStackNavigationProp<RootStackParamList>>();
32
+ const route = useRoute();
33
+ const scroll = useRef<KeyboardAwareScrollView>(null);
34
+ const {theme} = useTheme();
35
+ const {phone} = route.params as {phone: string};
36
+ const t = useTranslation();
37
+ const isRTL = useRTL();
38
+
39
+ const {error: otpError, recheckValue: recheckOTP} = useInputError(
40
+ otp,
41
+ value => (value.length === 6 ? null : 'Please enter a valid OTP code'),
42
+ );
43
+
44
+ async function handleVerifyOTP() {
45
+ const otpValid = recheckOTP() === null;
46
+
47
+ if (!otpValid) {
48
+ return;
49
+ }
50
+
51
+ try {
52
+ setLoading(true);
53
+
54
+ const result = await dispatch(
55
+ verifyOTP({
56
+ verification_code: otp?.toString(),
57
+ mobile_number: phone,
58
+ device_token: undefined,
59
+ scheme_id: 1,
60
+ }),
61
+ );
62
+ } catch (error) {
63
+ } finally {
64
+ setLoading(false);
65
+ }
66
+ }
67
+
68
+ const handleResendOTP = () => {
69
+ if (resendDisabled) return;
70
+
71
+ setResendDisabled(true);
72
+ setTimer(60);
73
+
74
+ // Add your resend OTP logic here
75
+ // dispatch(resendOTP());
76
+
77
+ const interval = setInterval(() => {
78
+ setTimer(prevTimer => {
79
+ if (prevTimer <= 1) {
80
+ clearInterval(interval);
81
+ setResendDisabled(false);
82
+ return 0;
83
+ }
84
+ return prevTimer - 1;
85
+ });
86
+ }, 1000);
87
+ };
88
+
89
+ return (
90
+ <Container
91
+ ref={scroll}
92
+ testID={'OTPScreenID'}
93
+ contentContainerStyle={styles.contentContainer}
94
+ style={styles.container}
95
+ backgroundImage={0}
96
+ withoutPadding
97
+ extendedBackground
98
+ backgroundColor={theme.colors.background_2}>
99
+ <HeaderBack onPress={() => navigation.goBack()} />
100
+ <RTLAwareText style={{...theme.text.header1, textAlign: 'center'}}>
101
+ {t('title', 'otp')}
102
+ </RTLAwareText>
103
+ <RTLAwareText style={{...theme.text.body2, textAlign: 'center'}}>
104
+ {t('subtitle', 'otp')}
105
+ </RTLAwareText>
106
+ <View style={styles.inputContainer}>
107
+ <OTPInput value={otp} onChange={setOTP} error={otpError} />
108
+ {otpError && (
109
+ <RTLAwareText style={[theme.text.body2, styles.errorText]}>
110
+ {otpError}
111
+ </RTLAwareText>
112
+ )}
113
+ </View>
114
+ <RTLAwareView style={styles.resendContainer}>
115
+ <RTLAwareText style={{...theme.text.body2}}>
116
+ {resendDisabled
117
+ ? t('resendIn', 'otp').replace('{0}', timer?.toString())
118
+ : t('didntReceiveCode', 'otp')}
119
+ </RTLAwareText>
120
+ <PrimaryButton
121
+ label={t('resend', 'otp')}
122
+ onPressIn={handleResendOTP}
123
+ disabled={resendDisabled}
124
+ type={ButtonType.borderless}
125
+ style={styles.resendButton}
126
+ />
127
+ </RTLAwareView>
128
+ <PrimaryButton
129
+ label={t('verify', 'otp')}
130
+ onPressIn={handleVerifyOTP}
131
+ isLoading={loading}
132
+ disabled={loading || otp.length < 4}
133
+ type={ButtonType.solid}
134
+ />
135
+ </Container>
136
+ );
137
+ }
138
+
139
+ const styles = StyleSheet.create({
140
+ container: {
141
+ flexGrow: 1,
142
+ borderTopRightRadius: CommonSizes.spacing.large,
143
+ borderTopLeftRadius: CommonSizes.spacing.large,
144
+ paddingHorizontal: CommonSizes.spacing.medium,
145
+ gap: 8,
146
+ justifyContent: 'flex-start',
147
+ },
148
+ contentContainer: {
149
+ flexGrow: 1,
150
+ },
151
+ inputContainer: {
152
+ width: '100%',
153
+ marginTop: CommonSizes.spacing.large,
154
+ },
155
+ resendContainer: {
156
+ flexDirection: 'row',
157
+ alignItems: 'center',
158
+ justifyContent: 'center',
159
+ marginTop: CommonSizes.spacing.small,
160
+ },
161
+ resendButton: {
162
+ marginLeft: CommonSizes.spacing.small,
163
+ },
164
+ errorText: {
165
+ color: '#FF4444',
166
+ marginTop: CommonSizes.spacing.small,
167
+ textAlign: 'center',
168
+ },
169
+ });