@fadyshawky/react-native-magic 1.0.0

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 (165) hide show
  1. package/.vscode/settings.json +7 -0
  2. package/README.md +269 -0
  3. package/package.json +36 -0
  4. package/template/.bundle/config +2 -0
  5. package/template/.env.development +5 -0
  6. package/template/.env.production +5 -0
  7. package/template/.env.staging +5 -0
  8. package/template/.eslintrc.js +4 -0
  9. package/template/.prettierrc.js +7 -0
  10. package/template/.watchmanconfig +1 -0
  11. package/template/App.tsx +34 -0
  12. package/template/Gemfile +9 -0
  13. package/template/Gemfile.lock +117 -0
  14. package/template/README.md +79 -0
  15. package/template/__tests__/App.test.tsx +17 -0
  16. package/template/android/app/build.gradle +128 -0
  17. package/template/android/app/debug.keystore +0 -0
  18. package/template/android/app/proguard-rules.pro +10 -0
  19. package/template/android/app/src/debug/AndroidManifest.xml +9 -0
  20. package/template/android/app/src/main/AndroidManifest.xml +26 -0
  21. package/template/android/app/src/main/java/com/reactnativemagic/MainActivity.kt +22 -0
  22. package/template/android/app/src/main/java/com/reactnativemagic/MainApplication.kt +44 -0
  23. package/template/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  24. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  25. package/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  26. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  27. package/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  28. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  29. package/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  30. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  31. package/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  32. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  33. package/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  34. package/template/android/app/src/main/res/values/strings.xml +3 -0
  35. package/template/android/app/src/main/res/values/styles.xml +9 -0
  36. package/template/android/build.gradle +21 -0
  37. package/template/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  38. package/template/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  39. package/template/android/gradle.properties +39 -0
  40. package/template/android/gradlew +252 -0
  41. package/template/android/gradlew.bat +94 -0
  42. package/template/android/settings.gradle +6 -0
  43. package/template/app.json +4 -0
  44. package/template/babel.config.js +3 -0
  45. package/template/index.js +9 -0
  46. package/template/install-dev.sh +1 -0
  47. package/template/install.sh +1 -0
  48. package/template/ios/.xcode.env +11 -0
  49. package/template/ios/Podfile +42 -0
  50. package/template/ios/Podfile.lock +2461 -0
  51. package/template/ios/reactnativemagic/AppDelegate.h +6 -0
  52. package/template/ios/reactnativemagic/AppDelegate.mm +31 -0
  53. package/template/ios/reactnativemagic/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  54. package/template/ios/reactnativemagic/Images.xcassets/Contents.json +6 -0
  55. package/template/ios/reactnativemagic/Info.plist +52 -0
  56. package/template/ios/reactnativemagic/LaunchScreen.storyboard +47 -0
  57. package/template/ios/reactnativemagic/PrivacyInfo.xcprivacy +46 -0
  58. package/template/ios/reactnativemagic/main.m +10 -0
  59. package/template/ios/reactnativemagic copy-Info.plist +52 -0
  60. package/template/ios/reactnativemagic.xcodeproj/project.pbxproj +836 -0
  61. package/template/ios/reactnativemagic.xcodeproj/xcshareddata/xcschemes/reactnativemagic.xcscheme +88 -0
  62. package/template/ios/reactnativemagic.xcworkspace/contents.xcworkspacedata +10 -0
  63. package/template/ios/reactnativemagic.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  64. package/template/ios/reactnativemagicTests/Info.plist +24 -0
  65. package/template/ios/reactnativemagicTests/reactnativemagicTests.m +66 -0
  66. package/template/ios/tmp.xcconfig +2 -0
  67. package/template/jest.config.js +3 -0
  68. package/template/metro.config.js +11 -0
  69. package/template/package-lock.json +18315 -0
  70. package/template/package.json +125 -0
  71. package/template/resources/symbols/SFSymbols.ts +2614 -0
  72. package/template/src/common/ImageResources.g.ts +14 -0
  73. package/template/src/common/components/Background.tsx +34 -0
  74. package/template/src/common/components/EmptyView.tsx +31 -0
  75. package/template/src/common/components/FlatListWrapper.tsx +66 -0
  76. package/template/src/common/components/IconPlatform.tsx +17 -0
  77. package/template/src/common/components/ImageCropPickerButton.tsx +108 -0
  78. package/template/src/common/components/LoadingComponent.tsx +16 -0
  79. package/template/src/common/components/PhotoTakingButton.tsx +99 -0
  80. package/template/src/common/components/PrimaryButton.tsx +305 -0
  81. package/template/src/common/components/PrimaryTextInput.tsx +287 -0
  82. package/template/src/common/components/RadioButton.tsx +73 -0
  83. package/template/src/common/components/RadioIcon.tsx +63 -0
  84. package/template/src/common/components/Separator.tsx +39 -0
  85. package/template/src/common/components/Svg.tsx +25 -0
  86. package/template/src/common/components/TouchablePlatform.tsx +70 -0
  87. package/template/src/common/components/TryAgain.tsx +56 -0
  88. package/template/src/common/helpers/arrayHelpers.ts +29 -0
  89. package/template/src/common/helpers/calculatePage.ts +16 -0
  90. package/template/src/common/helpers/colorHelpers.ts +34 -0
  91. package/template/src/common/helpers/defaultKeyIdExtractor.ts +5 -0
  92. package/template/src/common/helpers/dialogsHelpers.ts +66 -0
  93. package/template/src/common/helpers/imageHelpers.ts +5 -0
  94. package/template/src/common/helpers/inAppReviewHelper.ts +31 -0
  95. package/template/src/common/helpers/netInfoHelpers.ts +42 -0
  96. package/template/src/common/helpers/orientationHelpers.ts +25 -0
  97. package/template/src/common/helpers/regexHelpers.ts +7 -0
  98. package/template/src/common/helpers/shareHelpers.ts +47 -0
  99. package/template/src/common/helpers/stringsHelpers.ts +15 -0
  100. package/template/src/common/hooks/useBackHandler.ts +10 -0
  101. package/template/src/common/hooks/useDebounce.ts +17 -0
  102. package/template/src/common/hooks/useEventRegister.ts +50 -0
  103. package/template/src/common/hooks/useFlatListActions.ts +31 -0
  104. package/template/src/common/hooks/usePrevious.ts +11 -0
  105. package/template/src/common/hooks/useWhyDidYouUpdate.ts +27 -0
  106. package/template/src/common/localization/dateFormatter.ts +108 -0
  107. package/template/src/common/localization/intlFormatter.ts +37 -0
  108. package/template/src/common/localization/localization.ts +51 -0
  109. package/template/src/common/localization/translations/commonLocalization.ts +29 -0
  110. package/template/src/common/localization/translations/emptyLocalization.ts +6 -0
  111. package/template/src/common/localization/translations/errorsLocalization.ts +22 -0
  112. package/template/src/common/localization/translations/homeLocalization.ts +5 -0
  113. package/template/src/common/localization/translations/loginLocalization.ts +14 -0
  114. package/template/src/common/localization/translations/onboardingLocalization.ts +13 -0
  115. package/template/src/common/localization/translations/pagesLocalization.ts +14 -0
  116. package/template/src/common/localization/translations/profileLocalization.ts +6 -0
  117. package/template/src/common/urls/baseUrlOpener.ts +31 -0
  118. package/template/src/common/urls/emailUrl.ts +20 -0
  119. package/template/src/common/urls/httpUrl.ts +19 -0
  120. package/template/src/common/urls/mapUrl.ts +22 -0
  121. package/template/src/common/urls/phoneUrl.ts +16 -0
  122. package/template/src/common/utils/createPerfectSize.ts +15 -0
  123. package/template/src/common/utils/listHandlers.ts +30 -0
  124. package/template/src/common/utils/newState.ts +5 -0
  125. package/template/src/common/utils/serializeQueryParams.ts +10 -0
  126. package/template/src/common/validations/authValidations.ts +15 -0
  127. package/template/src/common/validations/commonValidations.ts +39 -0
  128. package/template/src/common/validations/errorValidations.ts +72 -0
  129. package/template/src/common/validations/hooks/useDatesError.ts +40 -0
  130. package/template/src/common/validations/hooks/useInputError.ts +30 -0
  131. package/template/src/common/validations/profileValidations.ts +30 -0
  132. package/template/src/common/validations/validationConstants.ts +20 -0
  133. package/template/src/core/api/responseHandlers.ts +43 -0
  134. package/template/src/core/api/serverHeaders.ts +39 -0
  135. package/template/src/core/store/app/appSlice.ts +12 -0
  136. package/template/src/core/store/app/appState.ts +3 -0
  137. package/template/src/core/store/reduxHelpers.ts +11 -0
  138. package/template/src/core/store/rootReducer.ts +10 -0
  139. package/template/src/core/store/store.tsx +41 -0
  140. package/template/src/core/store/user/userActions.ts +31 -0
  141. package/template/src/core/store/user/userSlice.ts +62 -0
  142. package/template/src/core/store/user/userState.ts +44 -0
  143. package/template/src/core/theme/colors.ts +117 -0
  144. package/template/src/core/theme/commonConsts.ts +45 -0
  145. package/template/src/core/theme/commonSizes.ts +70 -0
  146. package/template/src/core/theme/commonStyles.ts +228 -0
  147. package/template/src/core/theme/fonts.ts +12 -0
  148. package/template/src/navigation/AuthStack.tsx +39 -0
  149. package/template/src/navigation/HeaderComponents.tsx +104 -0
  150. package/template/src/navigation/MainNavigation.tsx +55 -0
  151. package/template/src/navigation/MainStack.tsx +99 -0
  152. package/template/src/navigation/RootNavigation.tsx +33 -0
  153. package/template/src/navigation/TabBar.tsx +94 -0
  154. package/template/src/navigation/TopTabBar.tsx +75 -0
  155. package/template/src/navigation/types.ts +5 -0
  156. package/template/src/screens/Login/Login.tsx +114 -0
  157. package/template/src/screens/Settings/Settings.tsx +5 -0
  158. package/template/src/screens/main/Main.tsx +5 -0
  159. package/template/src/screens/profile/Profile.tsx +5 -0
  160. package/template/src/screens/splash/Splash.tsx +19 -0
  161. package/template/src/sheetManager/sheets.tsx +14 -0
  162. package/template/tsconfig.json +3 -0
  163. package/template/types/index.ts +108 -0
  164. package/template/types/react-native-config.d.ts +19 -0
  165. package/template.config.js +4 -0
@@ -0,0 +1,66 @@
1
+ import {isAndroid, isIos} from '../../core/theme/commonConsts';
2
+ import {
3
+ ActionSheetIOS,
4
+ ActionSheetIOSOptions,
5
+ Alert,
6
+ AlertButton,
7
+ AlertOptions,
8
+ } from 'react-native';
9
+ import {localization} from '../localization/localization';
10
+
11
+ export function showActionSheet(
12
+ options: ActionSheetIOSOptions,
13
+ onOptionSelected: (optionIndex: number) => void,
14
+ ) {
15
+ if (isIos) {
16
+ ActionSheetIOS.showActionSheetWithOptions(options, onOptionSelected);
17
+ } else if (isAndroid) {
18
+ const buttons: AlertButton[] = options.options.map((value, index) => {
19
+ return {
20
+ text: value,
21
+ onPress: () => {
22
+ onOptionSelected(index);
23
+ },
24
+ style: 'default',
25
+ };
26
+ });
27
+
28
+ Alert.alert(options.title || '', options.message, buttons, {
29
+ cancelable: true,
30
+ });
31
+ }
32
+ }
33
+
34
+ export function showAlert(
35
+ title: string,
36
+ message?: string,
37
+ buttons?: AlertButton[],
38
+ options?: AlertOptions,
39
+ ) {
40
+ Alert.alert(title, message, buttons, options || {cancelable: true});
41
+ }
42
+
43
+ export function showCommonDialog(
44
+ title: string,
45
+ message: string,
46
+ onAcceptPress: () => void,
47
+ ) {
48
+ Alert.alert(
49
+ title,
50
+ message,
51
+ [
52
+ {
53
+ text: localization.common.yes,
54
+ onPress: onAcceptPress,
55
+ style: 'default',
56
+ },
57
+ {
58
+ text: localization.common.no,
59
+ style: 'default',
60
+ },
61
+ ],
62
+ {
63
+ cancelable: true,
64
+ },
65
+ );
66
+ }
@@ -0,0 +1,5 @@
1
+ import {Image as CropperImage} from 'react-native-image-crop-picker';
2
+
3
+ export function convertCropperImageToBase64(image: CropperImage): string {
4
+ return `data:${image.mime};base64,${image.data}`;
5
+ }
@@ -0,0 +1,31 @@
1
+ import InAppReview from 'react-native-in-app-review';
2
+
3
+ export function showInAppReview(
4
+ successAction?: () => void,
5
+ failAction?: (error: Error) => void,
6
+ onReviewNotAvailable?: () => void,
7
+ ) {
8
+ if (InAppReview.isAvailable()) {
9
+ InAppReview.RequestInAppReview()
10
+ .then(hasFlowFinishedSuccessfully => {
11
+ // for Android:
12
+ // The flow has finished. The API does not indicate whether the user
13
+ // reviewed or not, or even whether the review dialog was shown. Thus, no
14
+ // matter the result, we continue our app flow.
15
+
16
+ // for iOS
17
+ // the flow launched successfully, The API does not indicate whether the user
18
+ // reviewed or not, or he/she closed flow yet as Android, Thus, no
19
+ // matter the result, we continue our app flow.
20
+ if (hasFlowFinishedSuccessfully) {
21
+ successAction?.();
22
+ }
23
+ })
24
+ .catch(error => {
25
+ failAction?.(error);
26
+ });
27
+ } else {
28
+ console.error('Review is not available for this device/account');
29
+ onReviewNotAvailable?.();
30
+ }
31
+ }
@@ -0,0 +1,42 @@
1
+ import NetInfo, {
2
+ NetInfoState,
3
+ NetInfoSubscription,
4
+ } from '@react-native-community/netinfo';
5
+ import {showAlert} from './dialogsHelpers';
6
+ import {Linking} from 'react-native';
7
+ import Config from 'react-native-config';
8
+ import {localization} from '../localization/localization';
9
+
10
+ let netInfoUnsubscribe: undefined | NetInfoSubscription;
11
+ let hasDialogBeenShown: boolean = false;
12
+
13
+ function listenerCallback(state: NetInfoState) {
14
+ if (state.isInternetReachable === false && !hasDialogBeenShown) {
15
+ hasDialogBeenShown = true;
16
+ showAlert(
17
+ localization.errors.mobileDataIsTurnedOff,
18
+ localization.errors.turnOnMobileData,
19
+ [
20
+ {
21
+ text: localization.common.settings,
22
+ onPress: Linking.openSettings,
23
+ style: 'default',
24
+ },
25
+ {
26
+ text: localization.common.ok.toUpperCase(),
27
+ style: 'default',
28
+ },
29
+ ],
30
+ );
31
+ }
32
+ }
33
+
34
+ export function startListeningToNetInfo() {
35
+ if (netInfoUnsubscribe == null && !Config.IGNORE_NET_INFO) {
36
+ netInfoUnsubscribe = NetInfo.addEventListener(listenerCallback);
37
+ }
38
+ }
39
+
40
+ export function unsubscribeFromNetInfo() {
41
+ netInfoUnsubscribe && netInfoUnsubscribe();
42
+ }
@@ -0,0 +1,25 @@
1
+ import Orientation, {OrientationType} from 'react-native-orientation-locker';
2
+ import {isTablet} from '../../core/theme/commonConsts';
3
+
4
+ export function getCurrentOrientation(forceOnPhone?: boolean): OrientationType {
5
+ if (isTablet || forceOnPhone) {
6
+ let result = Orientation.getInitialOrientation();
7
+ Orientation.getOrientation(orientation => {
8
+ if (orientation != null) {
9
+ result = orientation;
10
+ }
11
+ });
12
+
13
+ return result;
14
+ } else {
15
+ return 'PORTRAIT' as OrientationType;
16
+ }
17
+ }
18
+
19
+ export function setDefaultOrientation(): void {
20
+ if (isTablet) {
21
+ Orientation.unlockAllOrientations();
22
+ } else {
23
+ Orientation.lockToPortrait();
24
+ }
25
+ }
@@ -0,0 +1,7 @@
1
+ export function isEmail(email: string): boolean {
2
+ const emailRegex =
3
+ // eslint-disable-next-line max-len
4
+ /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
5
+
6
+ return emailRegex.test(email);
7
+ }
@@ -0,0 +1,47 @@
1
+ import {Linking} from 'react-native';
2
+ import Share, {ShareOptions} from 'react-native-share';
3
+ import {isAndroid, isIos} from '../../core/theme/commonConsts';
4
+ import {
5
+ ShareOpenResult,
6
+ ShareSingleOptions,
7
+ ShareSingleResult,
8
+ } from 'react-native-share/lib/typescript/src/types';
9
+
10
+ export async function showShareDialog(
11
+ options: ShareOptions,
12
+ completedCallback?: (result: ShareOpenResult) => void,
13
+ errorCallback?: (error: Error | unknown) => void,
14
+ ) {
15
+ try {
16
+ const result = await Share.open(options);
17
+ completedCallback?.(result);
18
+ } catch (error: Error | unknown) {
19
+ errorCallback?.(error);
20
+ }
21
+ }
22
+
23
+ export async function showShareSocialDialog(
24
+ options: ShareSingleOptions,
25
+ completedCallback?: (result: ShareSingleResult) => void,
26
+ errorCallback?: (error: Error | unknown) => void,
27
+ ) {
28
+ try {
29
+ const result = await Share.shareSingle(options);
30
+ completedCallback?.(result);
31
+ } catch (error: Error | unknown) {
32
+ errorCallback?.(error);
33
+ }
34
+ }
35
+
36
+ export async function isPackageInstalled(
37
+ androidPackageName?: string,
38
+ iosUrl?: string,
39
+ ): Promise<boolean> {
40
+ if (isAndroid && androidPackageName) {
41
+ return (await Share.isPackageInstalled(androidPackageName)).isInstalled;
42
+ } else if (isIos && iosUrl) {
43
+ return Linking.canOpenURL(iosUrl);
44
+ } else {
45
+ throw new Error('No arguments were given or the platform is not supported');
46
+ }
47
+ }
@@ -0,0 +1,15 @@
1
+ export function capitalizeFirstLetter(word: string): string {
2
+ return word.charAt(0).toUpperCase() + word.slice(1);
3
+ }
4
+
5
+ export function removeHtmlTags(text: string): string {
6
+ return text.replace(/<br>/g, '\n').replace(/<\/?[^>]+(>|$)/g, '');
7
+ }
8
+
9
+ export function removeEmojis(text: string): string {
10
+ return text.replace(
11
+ // eslint-disable-next-line max-len
12
+ /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|[\ud83c\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|[\ud83c\ude32-\ude3a]|[\ud83c\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g,
13
+ '',
14
+ );
15
+ }
@@ -0,0 +1,10 @@
1
+ import {DependencyList, useEffect} from 'react';
2
+ import {BackHandler} from 'react-native';
3
+
4
+ export function useBackHandler(handler: () => boolean, deps?: DependencyList) {
5
+ useEffect(() => {
6
+ BackHandler.addEventListener('hardwareBackPress', handler);
7
+
8
+ return () => BackHandler.removeEventListener('hardwareBackPress', handler);
9
+ }, [deps, handler]);
10
+ }
@@ -0,0 +1,17 @@
1
+ import {useEffect, useState} from 'react';
2
+
3
+ export function useDebounce<T>(value: T, delay: number): T {
4
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
5
+
6
+ useEffect(() => {
7
+ const handler = setTimeout(() => {
8
+ setDebouncedValue(value);
9
+ }, delay);
10
+
11
+ return () => {
12
+ clearTimeout(handler);
13
+ };
14
+ }, [value, delay]);
15
+
16
+ return debouncedValue;
17
+ }
@@ -0,0 +1,50 @@
1
+ import {useEffect} from 'react';
2
+
3
+ interface IListenerHandler {
4
+ callback: (data: any) => void;
5
+ id: string;
6
+ }
7
+
8
+ const listeners = new Map<string, IListenerHandler[]>();
9
+ const listenersToEvent = new Map<string, string>();
10
+
11
+ export function useEventRegister(
12
+ eventName: string,
13
+ callback: (data: any) => void,
14
+ ) {
15
+ useEffect(() => {
16
+ const callbacks = getOrCreateListeners(eventName);
17
+ const id = eventName + callbacks.length;
18
+
19
+ callbacks.push({id, callback});
20
+ listenersToEvent.set(id, eventName);
21
+
22
+ return () => {
23
+ listeners.set(
24
+ eventName,
25
+ callbacks.filter(i => i.id != id),
26
+ );
27
+ };
28
+ }, [eventName, callback]);
29
+ }
30
+
31
+ export function removeAllListeners(eventName: string): void {
32
+ listeners.delete(eventName);
33
+ }
34
+
35
+ export function emitEvent(eventName: string, data?: any): void {
36
+ const listenersList = listeners.get(eventName);
37
+ if (listenersList != null) {
38
+ listenersList.forEach((i: IListenerHandler) => i.callback(data));
39
+ }
40
+ }
41
+
42
+ function getOrCreateListeners(eventName: string): IListenerHandler[] {
43
+ let listenersList = listeners.get(eventName);
44
+ if (listenersList == null) {
45
+ listenersList = [];
46
+ listeners.set(eventName, listenersList);
47
+ }
48
+
49
+ return listenersList;
50
+ }
@@ -0,0 +1,31 @@
1
+ import {useCallback} from 'react';
2
+ import {LoadState} from '../../types';
3
+ import {AsyncThunk} from '@reduxjs/toolkit';
4
+ import {useAppDispatch} from '../../core/store/reduxHelpers';
5
+
6
+ interface IResultActions {
7
+ loadMore: () => void;
8
+ tryAgain: () => void;
9
+ pullToRefresh: () => void;
10
+ }
11
+
12
+ export function useFlatListActions(
13
+ request: AsyncThunk<any, any, any>,
14
+ loadState: LoadState,
15
+ ): IResultActions {
16
+ const dispatch = useAppDispatch();
17
+
18
+ const loadMore = useCallback(() => {
19
+ loadState == LoadState.idle && dispatch(request(LoadState.loadingMore));
20
+ }, [dispatch, request, loadState]);
21
+
22
+ const tryAgain = useCallback(() => {
23
+ dispatch(request(LoadState.firstLoad));
24
+ }, [dispatch, request]);
25
+
26
+ const pullToRefresh = useCallback(() => {
27
+ dispatch(request(LoadState.pullToRefresh));
28
+ }, [dispatch, request]);
29
+
30
+ return {loadMore, tryAgain, pullToRefresh};
31
+ }
@@ -0,0 +1,11 @@
1
+ import {useEffect, useRef} from 'react';
2
+
3
+ export function usePrevious<T>(value: T): T | undefined {
4
+ const ref = useRef<T>();
5
+
6
+ useEffect(() => {
7
+ ref.current = value;
8
+ }, [value]);
9
+
10
+ return ref.current;
11
+ }
@@ -0,0 +1,27 @@
1
+ import {useEffect, useRef} from 'react';
2
+
3
+ export function useWhyDidYouUpdate<T>(name: string, props: T): void {
4
+ const previousProps = useRef<T>();
5
+
6
+ useEffect(() => {
7
+ if (previousProps.current) {
8
+ const allKeys = Object.keys({...previousProps.current, ...props});
9
+ const changesObj: Partial<T> = {};
10
+
11
+ allKeys.forEach(key => {
12
+ if ((previousProps.current as any)[key] !== (props as any)[key]) {
13
+ (changesObj as any)[key] = {
14
+ from: (previousProps.current as any)[key],
15
+ to: (props as any)[key],
16
+ };
17
+ }
18
+ });
19
+
20
+ if (Object.keys(changesObj).length) {
21
+ console.warn('[why-did-you-update]', name, changesObj);
22
+ }
23
+ }
24
+
25
+ previousProps.current = props;
26
+ });
27
+ }
@@ -0,0 +1,108 @@
1
+ import dayjs from 'dayjs';
2
+ // eslint-disable-next-line import/no-unassigned-import
3
+ import 'dayjs/locale/en';
4
+ import calendar from 'dayjs/plugin/calendar';
5
+ import {ICalendarSpec} from '../../../types';
6
+
7
+ dayjs.extend(calendar);
8
+
9
+ export enum DateFormat {
10
+ dayMonthShortYear = 'DD.MM.YYYY',
11
+ dayShortMonthYear = 'DD MMM YYYY',
12
+ dayMonthWeekdayShort = 'DD MMMM, dd',
13
+ dayMonthWeekdayFull = 'DD MMMM, dddd',
14
+ monthFullAndDay = 'MMMM, DD',
15
+ dayAndMonth = 'DD MMMM',
16
+ dateAndTime = 'DD.MM HH:mm',
17
+ dayAndMonthShort = 'DD.MM',
18
+ time = 'HH:mm',
19
+ year = 'YYYY',
20
+ shortMonth = 'MMM',
21
+ dayNumber = 'DD',
22
+ shortMonthYear = 'MMM YYYY',
23
+ dayMonthYear = 'DD MMMM YYYY',
24
+ full = 'YYYY-MM-DD HH:mm:ss',
25
+ yearDateTime = 'YYYY.MM.DD HH:mm',
26
+ yearMonthDay = 'YYYY.MM.DD',
27
+ }
28
+
29
+ const calendarFormat: ICalendarSpec = {
30
+ sameDay: '[Today]',
31
+ nextDay: '[Tomorrow]',
32
+ lastDay: '[Yesterday]',
33
+ nextWeek: 'dddd',
34
+ lastWeek: '[Last] dddd',
35
+ sameElse: DateFormat.dayMonthShortYear,
36
+ };
37
+
38
+ export function dateFromString(obj: string | Date): Date {
39
+ if (obj instanceof Date) {
40
+ return obj;
41
+ }
42
+
43
+ return new Date(obj);
44
+ }
45
+
46
+ export function dateFromUnknown(
47
+ obj: string | Date | null | number | undefined,
48
+ ): Date | null {
49
+ if (obj == null) {
50
+ return null;
51
+ }
52
+ if (obj instanceof Date) {
53
+ return obj;
54
+ }
55
+
56
+ return new Date(obj);
57
+ }
58
+
59
+ export function dateFromFormat(
60
+ date: Date | number | null | undefined | string,
61
+ format: DateFormat,
62
+ ): string {
63
+ let result = '';
64
+
65
+ const formattedDate: Date | null = dateFromUnknown(date);
66
+ if (formattedDate) {
67
+ result = dayjs(formattedDate).format(format);
68
+ }
69
+
70
+ return result;
71
+ }
72
+
73
+ export function calendarDate(
74
+ date: Date | null | number | undefined | string,
75
+ withFormat?: boolean,
76
+ ): string {
77
+ let result = '';
78
+
79
+ const formattedDate: Date | null = dateFromUnknown(date);
80
+ if (formattedDate) {
81
+ result = withFormat
82
+ ? dayjs(formattedDate).calendar(null, calendarFormat)
83
+ : dayjs(formattedDate).calendar();
84
+ }
85
+
86
+ return result;
87
+ }
88
+
89
+ export function setDateLocale(locale?: string): void {
90
+ dayjs.locale(locale);
91
+ }
92
+
93
+ export function getUnixDate(
94
+ date: Date | null | number | undefined | string,
95
+ ): number {
96
+ return dayjs(date).unix() * 1000;
97
+ }
98
+
99
+ export function getInitialDate(
100
+ date: Date | null | number | undefined | string,
101
+ defaultDate?: Date,
102
+ ): Date {
103
+ if (date != null) {
104
+ return dayjs(date).toDate();
105
+ } else {
106
+ return defaultDate || new Date();
107
+ }
108
+ }
@@ -0,0 +1,37 @@
1
+ import Intl from 'intl';
2
+ import {getLanguage} from './localization';
3
+ // eslint-disable-next-line import/no-unassigned-import
4
+ import 'intl/locale-data/jsonp/en';
5
+
6
+ export function formatPercent(percent: number | string): string {
7
+ const formatter = new Intl.NumberFormat(getLanguage(), {
8
+ style: 'percent',
9
+ minimumFractionDigits: 0,
10
+ maximumFractionDigits: 2,
11
+ });
12
+
13
+ return formatter.format(Number(percent) / 100);
14
+ }
15
+
16
+ export function formatCurrency(
17
+ price: number | string,
18
+ currency?: string,
19
+ ): string {
20
+ const formatter = new Intl.NumberFormat(getLanguage(), {
21
+ style: 'currency',
22
+ minimumFractionDigits: 0,
23
+ maximumFractionDigits: 2,
24
+ currency: currency || 'USD',
25
+ });
26
+
27
+ return formatter.format(Number(price));
28
+ }
29
+
30
+ export function formatDecimal(value: number | string): string {
31
+ const formatter = new Intl.NumberFormat(getLanguage(), {
32
+ style: 'decimal',
33
+ minimumFractionDigits: 0,
34
+ });
35
+
36
+ return formatter.format(Number(value));
37
+ }
@@ -0,0 +1,51 @@
1
+ import LocalizedStrings from 'react-native-localization';
2
+ import {commonLocalization} from './translations/commonLocalization';
3
+ import {errorsLocalization} from './translations/errorsLocalization';
4
+ import {emptyLocalization} from './translations/emptyLocalization';
5
+ import {pagesLocalization} from './translations/pagesLocalization';
6
+ import {onboardingLocalization} from './translations/onboardingLocalization';
7
+ import {setDateLocale} from './dateFormatter';
8
+ import {loginLocalization} from './translations/loginLocalization';
9
+ import {homeLocalization} from './translations/homeLocalization';
10
+ import {profileLocalization} from './translations/profileLocalization';
11
+
12
+ export enum Languages {
13
+ en = 'en',
14
+ }
15
+
16
+ export const localization = {
17
+ common: new LocalizedStrings(commonLocalization),
18
+ errors: new LocalizedStrings(errorsLocalization),
19
+ empty: new LocalizedStrings(emptyLocalization),
20
+ pages: new LocalizedStrings(pagesLocalization),
21
+ onboarding: new LocalizedStrings(onboardingLocalization),
22
+ login: new LocalizedStrings(loginLocalization),
23
+ home: new LocalizedStrings(homeLocalization),
24
+ profile: new LocalizedStrings(profileLocalization),
25
+ };
26
+
27
+ export function getLanguage(): string {
28
+ return localization.common.getLanguage();
29
+ }
30
+
31
+ export function getInterfaceLanguage(): string {
32
+ return localization.common.getInterfaceLanguage();
33
+ }
34
+
35
+ export function setLanguage(language?: Languages): void {
36
+ let localizationLanguage: Languages | string | undefined = language;
37
+
38
+ if (localizationLanguage == null) {
39
+ localizationLanguage = getLanguage();
40
+ }
41
+
42
+ const strings: any = Object.keys(localization);
43
+
44
+ for (const key of strings) {
45
+ if ((localization as any)[key].setLanguage) {
46
+ (localization as any)[key].setLanguage(localizationLanguage);
47
+ }
48
+ }
49
+
50
+ setDateLocale(localizationLanguage);
51
+ }
@@ -0,0 +1,29 @@
1
+ export const commonLocalization = {
2
+ en: {
3
+ search: 'Search',
4
+ selectPhoto: 'Select a photo',
5
+ takePhoto: 'Take a photo',
6
+ chooseFromLibrary: 'Choose from library',
7
+ cancel: 'Cancel',
8
+ reject: 'Reject',
9
+ delete: 'Delete',
10
+ back: 'Back',
11
+ next: 'Next',
12
+ done: 'Done',
13
+ ok: 'Ok',
14
+ continue: 'Continue',
15
+ save: 'Save',
16
+ loading: 'Loading',
17
+ photo: 'Photo',
18
+ yes: 'Yes',
19
+ no: 'No',
20
+ areYouSure: 'Are you sure?',
21
+ warning: 'Warning',
22
+ success: 'Success',
23
+ select: 'Select',
24
+ dataSuccessfullyUpdated: 'Data has been successfully updated',
25
+ settings: 'Settings',
26
+ required: ' (required)',
27
+ optional: ' (optional)',
28
+ },
29
+ };
@@ -0,0 +1,6 @@
1
+ export const emptyLocalization = {
2
+ en: {
3
+ noData: 'No data',
4
+ checkThisPageLater: 'Check this page later',
5
+ },
6
+ };
@@ -0,0 +1,22 @@
1
+ export const errorsLocalization = {
2
+ en: {
3
+ listErrorTitle: 'Failed to load data.',
4
+ tryAgain: 'Try again.',
5
+ thisFieldIsRequired: 'This field is required.',
6
+ unknownErrorHasOccurred: 'An unknown error has occurred.',
7
+ failedToOpenUrl: 'Failed to open URL. Check that the app is installed.',
8
+ pleaseCheckYourInternetConnection: 'Please check your Internet connection.',
9
+ invalidEmail: 'Invalid Email',
10
+ invalidPhoneNumber: 'Invalid phone number',
11
+ invalidFullName: 'Invalid full name',
12
+ invalidFromDate: (fromField: string, toField: string) =>
13
+ `${fromField} can't be later than ${toField.toLowerCase()}`,
14
+ invalidToDate: (fromField: string, toField: string) =>
15
+ `${toField} can't be earlier than ${fromField.toLowerCase()}`,
16
+ datesCantBeEqual: (fromField: string, toField: string) =>
17
+ `${fromField} and ${toField.toLowerCase()} can't be equal`,
18
+ mobileDataIsTurnedOff: 'Mobile Data is Turned Off',
19
+ turnOnMobileData: 'Turn on mobile data or use Wi-Fi to access data.',
20
+ error: 'Error',
21
+ },
22
+ };
@@ -0,0 +1,5 @@
1
+ export const homeLocalization = {
2
+ en: {
3
+ welcome: 'Hi ',
4
+ },
5
+ };
@@ -0,0 +1,14 @@
1
+ export const loginLocalization = {
2
+ en: {
3
+ Login: 'Login',
4
+ welcome: 'Welcome',
5
+ Email: 'Email',
6
+ EnterEmail: 'Enter email',
7
+ Password: 'Password',
8
+ EnterPassword: 'Enter password',
9
+ forgetPassword: 'Forget Password',
10
+ continue: 'continue',
11
+ notMember: 'not a member yet?',
12
+ findNursery: 'Find nursery',
13
+ },
14
+ };