@fadyshawky/react-native-magic 2.0.9 → 2.1.1

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 (39) hide show
  1. package/.vscode/settings.json +2 -1
  2. package/README.md +55 -214
  3. package/package.json +9 -90
  4. package/scripts/askPackageName.js +165 -0
  5. package/template/.env.example +12 -0
  6. package/template/App.tsx +20 -16
  7. package/template/CHANGELOG.md +25 -0
  8. package/template/android/app/build.gradle +3 -3
  9. package/template/android/app/src/main/java/com/reactnativemagic/MainActivity.kt +8 -2
  10. package/template/android/app/src/main/java/com/reactnativemagic/MainApplication.kt +12 -29
  11. package/template/android/build.gradle +5 -5
  12. package/template/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  13. package/template/docs/ARCHITECTURE.md +27 -0
  14. package/template/docs/BEST_PRACTICES.md +33 -0
  15. package/template/docs/CUSTOMIZATION.md +53 -0
  16. package/template/index.js +1 -0
  17. package/template/package.json +44 -89
  18. package/template/src/common/components/AppStatusBar.tsx +24 -0
  19. package/template/src/common/components/Cards.tsx +1 -1
  20. package/template/src/common/components/EmptyView.tsx +1 -1
  21. package/template/src/common/components/OTPInput.tsx +1 -1
  22. package/template/src/common/components/PrimaryButton.tsx +5 -5
  23. package/template/src/common/components/PrimaryTextInput.tsx +4 -4
  24. package/template/src/common/components/RadioButton.tsx +1 -1
  25. package/template/src/common/components/RadioIcon.tsx +4 -4
  26. package/template/src/common/components/SnackbarProvider.tsx +11 -0
  27. package/template/src/common/components/TryAgain.tsx +3 -3
  28. package/template/src/common/validations/errorValidations.ts +1 -1
  29. package/template/src/core/api/serverHeaders.ts +38 -9
  30. package/template/src/core/config/index.ts +13 -0
  31. package/template/src/core/store/store.tsx +3 -53
  32. package/template/src/core/theme/colors.ts +3 -0
  33. package/template/src/core/theme/commonSizes.ts +3 -0
  34. package/template/src/core/theme/fonts.ts +3 -0
  35. package/template/src/core/utils/stringUtils.ts +2 -45
  36. package/template/src/navigation/TabBar.tsx +5 -5
  37. package/template/src/screens/index.tsx +0 -15
  38. package/template/tsconfig.json +6 -1
  39. package/template.config.js +4 -2
@@ -1,6 +1,6 @@
1
1
  import React, {FC, memo, useMemo} from 'react';
2
2
  import {StyleSheet, View, ViewStyle} from 'react-native';
3
- import {NewColors} from '../../core/theme/colors';
3
+ import {PrimaryColors} from '../../core/theme/colors';
4
4
 
5
5
  interface IProps {
6
6
  isSelected: boolean;
@@ -41,14 +41,14 @@ const commonInnerCircle: ViewStyle = {
41
41
  const styles = StyleSheet.create({
42
42
  outerCircle: {
43
43
  ...commonOuterCircle,
44
- borderColor: NewColors.blueNormalActive,
44
+ borderColor: PrimaryColors.PlatinateBlue_400,
45
45
  } as ViewStyle,
46
46
  outerCircleSelected: {
47
47
  ...commonOuterCircle,
48
- borderColor: NewColors.blueNormalActive,
48
+ borderColor: PrimaryColors.PlatinateBlue_400,
49
49
  } as ViewStyle,
50
50
  innerCircle: {
51
51
  ...commonInnerCircle,
52
- backgroundColor: NewColors.blueNormalActive,
52
+ backgroundColor: PrimaryColors.PlatinateBlue_400,
53
53
  } as ViewStyle,
54
54
  });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Wrapper for snackbar/toast. The template uses react-native-snackbar directly.
5
+ * Replace with a custom implementation (e.g. context + showSnackbar) if needed.
6
+ */
7
+ export const SnackbarProvider = ({
8
+ children = null,
9
+ }: {
10
+ children?: React.ReactNode;
11
+ }): React.JSX.Element => <>{children}</>;
@@ -6,7 +6,7 @@ import {
6
6
  TouchableOpacity,
7
7
  View,
8
8
  } from 'react-native';
9
- import {Colors, NewColors} from '../../core/theme/colors';
9
+ import {PrimaryColors} from '../../core/theme/colors';
10
10
  import {CommonSizes} from '../../core/theme/commonSizes';
11
11
  import {CommonStyles} from '../../core/theme/commonStyles';
12
12
  import {localization} from '../localization/localization';
@@ -38,11 +38,11 @@ const styles = StyleSheet.create({
38
38
  title: {
39
39
  ...CommonStyles.normalText,
40
40
  textAlign: 'center',
41
- marginBottom: CommonSizes.spacing.extraSmall,
41
+ marginBottom: CommonSizes.spacing.xSmall,
42
42
  } as TextStyle,
43
43
  description: {
44
44
  ...CommonStyles.normalText,
45
- color: NewColors.blueNormalActive,
45
+ color: PrimaryColors.PlatinateBlue_400,
46
46
  textAlign: 'center',
47
47
  textDecorationLine: 'underline',
48
48
  } as TextStyle,
@@ -3,7 +3,7 @@ import {unwrapResult} from '@reduxjs/toolkit';
3
3
  import {Alert} from 'react-native';
4
4
  import {IErrorResult, ErrorRepresentationType} from '../../../types';
5
5
  import Snackbar from 'react-native-snackbar';
6
- import {NewColors} from '../../core/theme/colors';
6
+
7
7
  export function handlePromiseResult(
8
8
  promiseAction: Promise<any>,
9
9
  successMessage?: string,
@@ -1,28 +1,57 @@
1
- import axios, {AxiosDefaults} from 'axios';
2
- import DeviceInfo from 'react-native-device-info';
1
+ import axios, {AxiosDefaults, AxiosError, AxiosResponse} from 'axios';
2
+ import {store} from '../store/store';
3
+ import {setLogout} from '../store/user/userSlice';
4
+ import {API_BASE_URL} from '../config';
3
5
 
4
- export const defaultHeaders: HeadersInit_ = {
5
- 'app-version': DeviceInfo.getVersion(),
6
- 'serial-number': DeviceInfo.getSerialNumberSync(),
6
+ export const defaultHeaders: HeadersInit = {
7
7
  Connection: 'keep-alive',
8
8
  'Content-Type': 'application/json',
9
9
  };
10
+
10
11
  declare type MethodData = {
11
12
  url: AxiosDefaults['httpsAgent'];
12
13
  data?: AxiosDefaults['data'];
13
14
  config?: any;
14
15
  };
15
16
 
16
- const baseURL = '';
17
-
18
17
  const instance = axios.create({
19
- baseURL: baseURL,
18
+ baseURL: API_BASE_URL,
20
19
  headers: {
21
20
  ...defaultHeaders,
22
21
  },
23
22
  });
24
23
 
25
- // Export HTTP methods using the configured instance
24
+ instance.interceptors.request.use(
25
+ config => {
26
+ const state = store.getState();
27
+ const accessToken = state.user.accessToken;
28
+ const locale = state.app?.language ?? 'en';
29
+ if (accessToken) {
30
+ config.headers.Authorization = `Bearer ${accessToken}`;
31
+ }
32
+ config.headers.locale = locale;
33
+ return config;
34
+ },
35
+ error => Promise.reject(error),
36
+ );
37
+
38
+ instance.interceptors.response.use(
39
+ (response: AxiosResponse) => response,
40
+ async (error: AxiosError) => {
41
+ const originalRequest = error.config;
42
+ if (
43
+ originalRequest?.url?.includes('/login') ||
44
+ originalRequest?.url?.includes('/refresh-token')
45
+ ) {
46
+ return Promise.reject(error);
47
+ }
48
+ if (error.response?.status === 401 || error.response?.status === 402) {
49
+ store.dispatch(setLogout());
50
+ }
51
+ return Promise.reject(error);
52
+ },
53
+ );
54
+
26
55
  export const post = ({url, data, config}: MethodData) =>
27
56
  instance.post(url, data, config);
28
57
  export const get = ({url, config}: MethodData) => instance.get(url, config);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * App config – single place for API base URL, env, and feature toggles.
3
+ * Change your app's API and feature toggles here and in .env.
4
+ */
5
+ import Config from 'react-native-config';
6
+
7
+ const config = Config as Record<string, string | undefined>;
8
+ export const API_BASE_URL =
9
+ config.API_BASE_URL || config.API_URL || 'https://api.example.com';
10
+ export const ENV = config.ENV || config.ENVIRONMENT || 'development';
11
+
12
+ /** Optional: enable RTL layout (e.g. for Arabic). */
13
+ export const enableRTL = true;
@@ -1,49 +1,11 @@
1
1
  import React from 'react';
2
2
  import {Action, configureStore, ThunkAction} from '@reduxjs/toolkit';
3
- import {
4
- createTransform,
5
- PersistConfig,
6
- persistReducer,
7
- persistStore,
8
- } from 'redux-persist';
3
+ import {PersistConfig, persistReducer, persistStore} from 'redux-persist';
9
4
  import {rootReducer, RootState} from './rootReducer';
10
5
  import {Provider} from 'react-redux';
11
6
  import {PersistGate} from 'redux-persist/integration/react';
12
7
  import AsyncStorage from '@react-native-async-storage/async-storage';
13
8
  import {createWhitelistFilter} from 'redux-persist-transform-filter';
14
- import {getUniqueId} from 'react-native-device-info';
15
- import CryptoJS from 'crypto-js';
16
-
17
- let deviceId: string;
18
- getUniqueId()
19
- .then(res => {
20
- deviceId = res;
21
- })
22
- .catch(error => {});
23
-
24
- const encrypt = createTransform(
25
- (inboundState, key) => {
26
- if (!inboundState) {
27
- return inboundState;
28
- }
29
-
30
- const cryptedText = CryptoJS.AES.encrypt(
31
- JSON.stringify(inboundState),
32
- deviceId,
33
- );
34
-
35
- return cryptedText?.toString();
36
- },
37
- (outboundState, key) => {
38
- if (!outboundState) {
39
- return outboundState;
40
- }
41
- const bytes = CryptoJS.AES.decrypt(outboundState, deviceId);
42
- const decrypted = bytes.toString(CryptoJS.enc.Utf8);
43
-
44
- return JSON.parse(decrypted);
45
- },
46
- );
47
9
 
48
10
  const persistConfig: PersistConfig<RootState> = {
49
11
  key: 'root',
@@ -51,11 +13,8 @@ const persistConfig: PersistConfig<RootState> = {
51
13
  version: 1,
52
14
  timeout: 1000,
53
15
  transforms: [
54
- createWhitelistFilter('app'),
55
- createWhitelistFilter('user'),
56
- createWhitelistFilter('services'),
57
- createWhitelistFilter('providers'),
58
- createWhitelistFilter('categories'),
16
+ createWhitelistFilter('user', ['accessToken', 'user']),
17
+ createWhitelistFilter('app', ['language', 'isRTL']),
59
18
  ],
60
19
  };
61
20
 
@@ -66,16 +25,7 @@ export const store = configureStore({
66
25
  middleware: getDefaultMiddleware =>
67
26
  getDefaultMiddleware({
68
27
  serializableCheck: false,
69
- immutableCheck: {
70
- warnAfter: 300,
71
- ignoredPaths: [
72
- 'services.inquiredBill',
73
- 'providers.providers',
74
- 'categories.categories',
75
- ],
76
- },
77
28
  }),
78
- devTools: process.env.NODE_ENV !== 'production',
79
29
  });
80
30
 
81
31
  export const persistor = persistStore(store);
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Customize for your brand – this is the only place to change app colors.
3
+ */
1
4
  export enum PrimaryColors {
2
5
  PlatinateBlue_0 = '#E9EDFF',
3
6
  PlatinateBlue_25 = '#D3DAFF',
@@ -1,5 +1,8 @@
1
1
  import {ISize} from './types';
2
2
 
3
+ /**
4
+ * Customize for your brand – this is the only place to change app sizes/spacing.
5
+ */
3
6
  export const CommonSizes = {
4
7
  font: {
5
8
  // Body Text Sizes
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Customize for your brand – this is the only place to change app fonts.
3
+ */
1
4
  export const Fonts = {
2
5
  // Almarai Font Family
3
6
  light: 'Almarai-Light',
@@ -1,9 +1,7 @@
1
1
  /**
2
- * Safely converts any value to a string
3
- * This is especially useful for error messages that might be objects
2
+ * Safely converts any value to a string.
3
+ * Useful for error messages that might be objects.
4
4
  */
5
- import CryptoJS from 'crypto-js';
6
-
7
5
  export const ensureString = (value: any): string => {
8
6
  if (value === null || value === undefined) {
9
7
  return '';
@@ -31,47 +29,6 @@ export const ensureString = (value: any): string => {
31
29
  return String(value);
32
30
  };
33
31
 
34
- export function decryptTripleDES(
35
- encryptedBase64: string,
36
- secretKeyBase64: string,
37
- ) {
38
- try {
39
- // Decode the key from base64
40
- const decodedKey = CryptoJS.enc.Base64.parse(secretKeyBase64);
41
-
42
- // Decode the encrypted value from base64
43
- const encryptedWordArray = CryptoJS.enc.Base64.parse(encryptedBase64);
44
-
45
- // Decrypt using Triple DES
46
- const decrypted = CryptoJS.TripleDES.decrypt(
47
- {ciphertext: encryptedWordArray},
48
- decodedKey,
49
- {
50
- mode: CryptoJS.mode.ECB,
51
- padding: CryptoJS.pad.Pkcs7,
52
- },
53
- );
54
-
55
- let decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
56
-
57
- if (!decryptedText) {
58
- throw new Error('Decryption resulted in empty string');
59
- }
60
-
61
- decryptedText = decryptedText.replace(/(.{4})/g, '$1-');
62
- if (decryptedText.endsWith('-')) {
63
- decryptedText = decryptedText.slice(0, -1);
64
- }
65
-
66
- return decryptedText;
67
- } catch (error) {
68
- throw new Error(`Decryption failed`);
69
- }
70
- }
71
-
72
- // Example usage:
73
- // const decrypted = decryptBase64('encrypted_base64_string', 'your_secret_key');
74
-
75
32
  /**
76
33
  * Converts a camelCase string to PascalCase with specific formatting
77
34
  * Example: billerId -> BillerId
@@ -16,7 +16,7 @@ import {
16
16
  useRTL,
17
17
  useTranslation,
18
18
  } from '../common/localization/LocalizationProvider';
19
- import {BlackColors, NewColors} from '../core/theme/colors';
19
+ import {PrimaryColors} from '../core/theme/colors';
20
20
  import {CommonSizes} from '../core/theme/commonSizes';
21
21
  import {CommonStyles} from '../core/theme/commonStyles';
22
22
  import {scaleHeight, scaleWidth} from '../core/theme/scaling';
@@ -125,11 +125,11 @@ const styles = StyleSheet.create({
125
125
  flexDirection: 'row',
126
126
  alignItems: 'center',
127
127
  height: scaleHeight(130),
128
- borderTopLeftRadius: CommonSizes.borderRadius.huge,
129
- borderTopRightRadius: CommonSizes.borderRadius.huge,
128
+ borderTopLeftRadius: CommonSizes.borderRadius.xxLarge,
129
+ borderTopRightRadius: CommonSizes.borderRadius.xxLarge,
130
130
  justifyContent: 'space-evenly',
131
131
  ...CommonStyles.dropShadow,
132
- backgroundColor: BlackColors.indigoBlue,
132
+ backgroundColor: PrimaryColors.PlatinateBlue_600,
133
133
  position: 'absolute',
134
134
  bottom: 0,
135
135
  left: 0,
@@ -141,6 +141,6 @@ const styles = StyleSheet.create({
141
141
  },
142
142
  label: {},
143
143
  labelFocused: {
144
- color: NewColors.blueNormalActive,
144
+ color: PrimaryColors.PlatinateBlue_400,
145
145
  },
146
146
  });
@@ -1,24 +1,9 @@
1
1
  // Authentication Screens
2
2
  export {Login} from './Login/Login';
3
3
  export {OTPScreen as OTP} from './OTP/OTPScreen';
4
- export {ForceChangePasswordScreen as ForceChangePassword} from './ForceChangePassword/ForceChangePasswordScreen';
5
4
 
6
5
  // Main Navigation Screens
7
6
  export {HomeScreen as Home} from './home/HomeScreen';
8
7
  export {Profile} from './profile/Profile';
9
8
  export {Splash} from './splash/Splash';
10
9
 
11
- // Service Related Screens
12
- export {Categories} from './Categories/Categories';
13
- export {Services} from './Services/Services';
14
- export {SingleService} from './SingleService/SingleService';
15
- export {Providers} from './Providers/Providers';
16
- export {InquiredBill} from './InquiredBill/InquiredBill';
17
- export {PaymentConfirmation} from './PaymentConfirmation/PaymentConfirmation';
18
-
19
- // History and Receipt Screens
20
- export {History} from './History/History';
21
- export {Favorites} from './Favorites/Favorites';
22
-
23
- // Device and Card Related Screens
24
- export {ReceiptScreen} from './ReceiptScreen/ReceiptScreen';
@@ -1,3 +1,8 @@
1
1
  {
2
- "extends": "@react-native/typescript-config/tsconfig.json"
2
+ "extends": "@react-native/typescript-config",
3
+ "compilerOptions": {
4
+ "types": ["jest"]
5
+ },
6
+ "include": ["**/*.ts", "**/*.tsx"],
7
+ "exclude": ["**/node_modules", "**/Pods"]
3
8
  }
@@ -1,4 +1,6 @@
1
1
  module.exports = {
2
- placeholderName: "reactnativemagic",
3
- templateDir: "./template",
2
+ placeholderName: 'reactnativemagic',
3
+ titlePlaceholder: 'React Native Magic App',
4
+ templateDir: './template',
5
+ postInitScript: 'scripts/askPackageName.js',
4
6
  };