create-react-native-starter-kit 0.0.0-stage → 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 (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +378 -2
  3. package/bin/create-react-native-starter-kit.js +95 -0
  4. package/lib/copy-src.js +127 -0
  5. package/lib/creator.js +211 -0
  6. package/lib/deps.js +58 -0
  7. package/lib/patch-configs.js +318 -0
  8. package/lib/patch-native.js +387 -0
  9. package/package.json +47 -4
  10. package/scripts/sync-template.js +52 -0
  11. package/template/.env.example +3 -0
  12. package/template/patches/react-native-element-dropdown+2.12.4.patch +67 -0
  13. package/template/src/app/App.tsx +25 -0
  14. package/template/src/app/navigation/root.tsx +24 -0
  15. package/template/src/app/navigation/stacks/app.stack.tsx +67 -0
  16. package/template/src/app/navigation/stacks/app.tab.tsx +72 -0
  17. package/template/src/app/navigation/stacks/auth.stack.tsx +56 -0
  18. package/template/src/app/providers.tsx +43 -0
  19. package/template/src/assets/fonts/InterTight-Black.ttf +0 -0
  20. package/template/src/assets/fonts/InterTight-BlackItalic.ttf +0 -0
  21. package/template/src/assets/fonts/InterTight-Bold.ttf +0 -0
  22. package/template/src/assets/fonts/InterTight-BoldItalic.ttf +0 -0
  23. package/template/src/assets/fonts/InterTight-ExtraBold.ttf +0 -0
  24. package/template/src/assets/fonts/InterTight-ExtraBoldItalic.ttf +0 -0
  25. package/template/src/assets/fonts/InterTight-ExtraLight.ttf +0 -0
  26. package/template/src/assets/fonts/InterTight-ExtraLightItalic.ttf +0 -0
  27. package/template/src/assets/fonts/InterTight-Italic.ttf +0 -0
  28. package/template/src/assets/fonts/InterTight-Light.ttf +0 -0
  29. package/template/src/assets/fonts/InterTight-LightItalic.ttf +0 -0
  30. package/template/src/assets/fonts/InterTight-Medium.ttf +0 -0
  31. package/template/src/assets/fonts/InterTight-MediumItalic.ttf +0 -0
  32. package/template/src/assets/fonts/InterTight-Regular.ttf +0 -0
  33. package/template/src/assets/fonts/InterTight-SemiBold.ttf +0 -0
  34. package/template/src/assets/fonts/InterTight-SemiBoldItalic.ttf +0 -0
  35. package/template/src/assets/fonts/InterTight-Thin.ttf +0 -0
  36. package/template/src/assets/fonts/InterTight-ThinItalic.ttf +0 -0
  37. package/template/src/assets/icons/back.svg +3 -0
  38. package/template/src/assets/icons/calendar.svg +3 -0
  39. package/template/src/assets/icons/claim_upload.svg +3 -0
  40. package/template/src/assets/icons/clock.svg +1 -0
  41. package/template/src/assets/icons/downArrow.svg +3 -0
  42. package/template/src/assets/icons/edit.svg +3 -0
  43. package/template/src/assets/icons/eye.svg +4 -0
  44. package/template/src/assets/icons/eyeClose.svg +7 -0
  45. package/template/src/assets/icons/folder.svg +5 -0
  46. package/template/src/assets/icons/home.svg +4 -0
  47. package/template/src/assets/icons/index.ts +33 -0
  48. package/template/src/assets/icons/setting.svg +4 -0
  49. package/template/src/assets/icons/tabs/home/filled_dark.svg +3 -0
  50. package/template/src/assets/icons/tabs/home/filled_light.svg +3 -0
  51. package/template/src/assets/icons/tabs/home/outline.svg +4 -0
  52. package/template/src/assets/icons/tabs/index.ts +31 -0
  53. package/template/src/assets/icons/tabs/profile/filled_dark.svg +6 -0
  54. package/template/src/assets/icons/tabs/profile/filled_light.svg +6 -0
  55. package/template/src/assets/icons/tabs/profile/outline.svg +3 -0
  56. package/template/src/assets/icons/tooltip_dark.svg +1 -0
  57. package/template/src/assets/icons/tooltip_light.svg +1 -0
  58. package/template/src/assets/icons/upArrow.svg +3 -0
  59. package/template/src/assets/images/back.png +0 -0
  60. package/template/src/assets/images/index.ts +7 -0
  61. package/template/src/assets/images/placeholder/profile.png +0 -0
  62. package/template/src/components/shared/AppImage.tsx +190 -0
  63. package/template/src/components/shared/AvatarPicker.tsx +175 -0
  64. package/template/src/components/shared/CustomButton.tsx +273 -0
  65. package/template/src/components/shared/DocumentPicker.tsx +86 -0
  66. package/template/src/components/shared/GlobalLoader.tsx +47 -0
  67. package/template/src/components/shared/Icon.tsx +28 -0
  68. package/template/src/components/shared/PressableIcon.tsx +104 -0
  69. package/template/src/components/shared/bottom-sheet/BaseBottomSheet.tsx +30 -0
  70. package/template/src/components/shared/bottom-sheet/BaseBottomSheetModal.tsx +50 -0
  71. package/template/src/components/shared/bottom-sheet/sheets/ImagePickerSheet.tsx +310 -0
  72. package/template/src/components/shared/bottom-sheet/types.ts +4 -0
  73. package/template/src/components/shared/form-input/FormInput.tsx +139 -0
  74. package/template/src/components/shared/form-input/FormInputRenderer.tsx +82 -0
  75. package/template/src/components/shared/form-input/FullNameInput.tsx +243 -0
  76. package/template/src/components/shared/form-input/formFieldStyles.ts +43 -0
  77. package/template/src/components/shared/form-input/type.ts +188 -0
  78. package/template/src/components/shared/form-input/variants/CountryPhoneVariant.tsx +289 -0
  79. package/template/src/components/shared/form-input/variants/DateTimeVariant.tsx +280 -0
  80. package/template/src/components/shared/form-input/variants/DropdownVariant.tsx +221 -0
  81. package/template/src/components/shared/form-input/variants/TextInputVariant.tsx +300 -0
  82. package/template/src/components/shared/form-input/variants/ToggleVariant.tsx +194 -0
  83. package/template/src/components/shared/modal/BaseModal.tsx +83 -0
  84. package/template/src/components/shared/modal/common/AlertModal.tsx +173 -0
  85. package/template/src/components/shared/navigation/AnimatedTabIcon.tsx +37 -0
  86. package/template/src/components/shared/navigation/HeaderActionButton.tsx +70 -0
  87. package/template/src/components/shared/navigation/HeaderBackButton.tsx +38 -0
  88. package/template/src/components/shared/navigation/StackHeader.tsx +105 -0
  89. package/template/src/components/shared/navigation/tabIconRender.tsx +14 -0
  90. package/template/src/components/shared/offline/OfflineQueryBoundary.tsx +25 -0
  91. package/template/src/components/shared/offline/OfflineState.tsx +229 -0
  92. package/template/src/components/shared/offline/index.ts +6 -0
  93. package/template/src/components/shared/offline/types.ts +15 -0
  94. package/template/src/components/shared/offline/wifi-icon-animation/BrokenWifiAnimation.tsx +116 -0
  95. package/template/src/components/shared/offline/wifi-icon-animation/WifiPiece.tsx +80 -0
  96. package/template/src/components/shared/offline/wifi-icon-animation/geometry.ts +163 -0
  97. package/template/src/components/shared/offline/wifi-icon-animation/useWifiIconAnimationActive.ts +37 -0
  98. package/template/src/components/shared/skelton/BaseSkelton.tsx +71 -0
  99. package/template/src/components/shared/skelton/ShimmerHolder.ts +6 -0
  100. package/template/src/components/shared/skelton/holders/HomeShimmer.tsx +25 -0
  101. package/template/src/components/shared/skelton/holders/index.ts +1 -0
  102. package/template/src/components/shared/skelton/layouts/avatar.layout.ts +18 -0
  103. package/template/src/components/shared/skelton/layouts/home.layout.ts +26 -0
  104. package/template/src/components/shared/skelton/layouts/index.ts +3 -0
  105. package/template/src/components/shared/skelton/layouts/text.layout.ts +18 -0
  106. package/template/src/components/shared/text/CustomText.tsx +85 -0
  107. package/template/src/components/shared/text/ShimmerText.tsx +159 -0
  108. package/template/src/components/shared/text/types.ts +40 -0
  109. package/template/src/constants/device.ts +12 -0
  110. package/template/src/constants/endpoint.ts +11 -0
  111. package/template/src/constants/keyExtractor.ts +6 -0
  112. package/template/src/constants/limits.ts +9 -0
  113. package/template/src/constants/queryKey.ts +3 -0
  114. package/template/src/constants/regex.ts +7 -0
  115. package/template/src/context/NetworkContext.tsx +102 -0
  116. package/template/src/context/ThemeContext.tsx +56 -0
  117. package/template/src/hooks/app/index.ts +2 -0
  118. package/template/src/hooks/app/useGetProfile.ts +38 -0
  119. package/template/src/hooks/app/useUpdateProfile.ts +21 -0
  120. package/template/src/hooks/useAppDispatch.ts +6 -0
  121. package/template/src/hooks/useAppSelector.ts +4 -0
  122. package/template/src/hooks/useDebounce.ts +15 -0
  123. package/template/src/hooks/useDocumentPicker.ts +102 -0
  124. package/template/src/hooks/useImagePicker.ts +83 -0
  125. package/template/src/i18n/i18next.d.ts +9 -0
  126. package/template/src/i18n/index.ts +16 -0
  127. package/template/src/i18n/locales/en.json +274 -0
  128. package/template/src/i18n/locales/ru.json +165 -0
  129. package/template/src/i18n/resources.ts +11 -0
  130. package/template/src/redux/selectors/auth.selectors.ts +4 -0
  131. package/template/src/redux/slice/auth.slice.ts +23 -0
  132. package/template/src/redux/slice/loader.slice.ts +25 -0
  133. package/template/src/redux/slice/settings.slice.ts +26 -0
  134. package/template/src/redux/slice/user.slice.ts +25 -0
  135. package/template/src/screens/app/main-tab/index.tsx +73 -0
  136. package/template/src/screens/app/main-tab/style.ts +0 -0
  137. package/template/src/screens/app/profile-setup/index.tsx +161 -0
  138. package/template/src/screens/app/profile-setup/styles.ts +3 -0
  139. package/template/src/screens/auth/forgot-password/index.tsx +59 -0
  140. package/template/src/screens/auth/forgot-password/styles.ts +0 -0
  141. package/template/src/screens/auth/index.ts +4 -0
  142. package/template/src/screens/auth/login/index.tsx +137 -0
  143. package/template/src/screens/auth/login/styles.ts +3 -0
  144. package/template/src/screens/auth/signup/index.tsx +110 -0
  145. package/template/src/screens/auth/signup/styles.ts +3 -0
  146. package/template/src/screens/auth/verify-email/index.tsx +15 -0
  147. package/template/src/screens/auth/verify-email/styles.ts +3 -0
  148. package/template/src/screens/tab/home/index.tsx +49 -0
  149. package/template/src/screens/tab/home/styles.ts +3 -0
  150. package/template/src/screens/tab/index.ts +2 -0
  151. package/template/src/screens/tab/profile/index.tsx +50 -0
  152. package/template/src/screens/tab/profile/styles.ts +3 -0
  153. package/template/src/services/api/auth/auth.api.ts +22 -0
  154. package/template/src/services/api/auth/auth.endpoints.ts +7 -0
  155. package/template/src/services/api/auth/auth.types.ts +9 -0
  156. package/template/src/services/api/auth/index.ts +3 -0
  157. package/template/src/services/api/axios/axios.instance.ts +15 -0
  158. package/template/src/services/api/axios/axios.interceptors.ts +43 -0
  159. package/template/src/services/api/axios/index.ts +2 -0
  160. package/template/src/services/api/mock/mockAdapter.ts +97 -0
  161. package/template/src/services/api/user/index.ts +2 -0
  162. package/template/src/services/api/user/user.api.ts +67 -0
  163. package/template/src/services/api/user/user.endpoints.ts +0 -0
  164. package/template/src/services/api/user/user.types.ts +0 -0
  165. package/template/src/services/errors/backendErrorCodes.ts +7 -0
  166. package/template/src/services/errors/errorHandler.ts +94 -0
  167. package/template/src/services/errors/firebaseErrorMap.ts +61 -0
  168. package/template/src/services/errors/httpStatusMap.ts +47 -0
  169. package/template/src/services/errors/index.ts +4 -0
  170. package/template/src/services/network/index.ts +1 -0
  171. package/template/src/services/network/onlineManager.ts +22 -0
  172. package/template/src/services/query/index.ts +1 -0
  173. package/template/src/services/query/queryClient.ts +23 -0
  174. package/template/src/services/toast/ToastCard.tsx +137 -0
  175. package/template/src/services/toast/ToastConfig.tsx +32 -0
  176. package/template/src/services/toast/index.ts +3 -0
  177. package/template/src/services/toast/toast.ts +68 -0
  178. package/template/src/storage/mmkvAdapter.ts +3 -0
  179. package/template/src/storage/mmkvInstance.ts +16 -0
  180. package/template/src/store/index.ts +15 -0
  181. package/template/src/store/mmkv.ts +6 -0
  182. package/template/src/store/persist.ts +10 -0
  183. package/template/src/store/rootReducer.ts +26 -0
  184. package/template/src/theme/dark.ts +40 -0
  185. package/template/src/theme/fonts.ts +20 -0
  186. package/template/src/theme/index.ts +6 -0
  187. package/template/src/theme/layout.ts +22 -0
  188. package/template/src/theme/light.ts +41 -0
  189. package/template/src/theme/palette.ts +32 -0
  190. package/template/src/theme/spacing.ts +7 -0
  191. package/template/src/theme/typography.ts +80 -0
  192. package/template/src/types/api.types.ts +34 -0
  193. package/template/src/types/env.d.ts +6 -0
  194. package/template/src/types/index.ts +2 -0
  195. package/template/src/types/navigation.types.ts +94 -0
  196. package/template/src/utils/clearAppSession.ts +10 -0
  197. package/template/src/utils/date.ts +19 -0
  198. package/template/src/utils/documentPickerHandler.ts +96 -0
  199. package/template/src/utils/getFileNameFromUri.ts +18 -0
  200. package/template/src/utils/getInitials.ts +13 -0
  201. package/template/src/utils/hindiText.ts +33 -0
  202. package/template/src/utils/imageCompress.ts +97 -0
  203. package/template/src/utils/imagePickerHandler.ts +200 -0
  204. package/template/src/utils/maskPhone.ts +17 -0
  205. package/template/src/utils/normalize.ts +12 -0
  206. package/template/src/validation/messages.ts +11 -0
  207. package/template/src/validation/rules.ts +44 -0
  208. package/template/src/validation/schema/auth.schema.ts +53 -0
  209. package/template/src/validation/schema/user.schema.ts +46 -0
  210. package/template/src/validation/tValError.ts +13 -0
@@ -0,0 +1,7 @@
1
+ export const BACKEND_ERROR_MAP: Record<string, string> = {
2
+ USER_NOT_FOUND: 'apiErrors.userNotFound',
3
+ INVALID_TOKEN: 'apiErrors.invalidToken',
4
+ EMAIL_ALREADY_EXISTS: 'apiErrors.emailExists',
5
+ PASSWORD_TOO_WEAK: 'apiErrors.weakPassword',
6
+ ACCOUNT_DISABLED: 'apiErrors.accountDisabled',
7
+ };
@@ -0,0 +1,94 @@
1
+ import axios from 'axios';
2
+ import i18n from '@/i18n';
3
+ import { FIREBASE_ERROR_MAP } from './firebaseErrorMap';
4
+ import { BACKEND_ERROR_MAP } from './backendErrorCodes';
5
+ import { HTTP_STATUS_MAP } from './httpStatusMap';
6
+ import { ErrorToast } from '../toast';
7
+
8
+ export type BackendErrorResponse = {
9
+ code?: string;
10
+ error_code?: string;
11
+ message?: string;
12
+ error?: string;
13
+ msg?: string;
14
+ errors?: Record<string, string[]>;
15
+ };
16
+
17
+ function extractBackendMessage(data: any): string | null {
18
+ if (!data) return null;
19
+ if (typeof data === 'string') return data;
20
+
21
+ return (
22
+ data.message ||
23
+ data.error ||
24
+ data.msg ||
25
+ (data.errors && Object.values<any>(data.errors)[0]?.[0]) ||
26
+ null
27
+ );
28
+ }
29
+
30
+ function extractBackendCode(data: any): string | null {
31
+ return data?.code || data?.error_code || null; //"code": "EMAIL_ALREADY_EXISTS" OR "error_code": "EMAIL_ALREADY_EXISTS"
32
+ }
33
+
34
+ export function getErrorMessage(error: any): string {
35
+ /* 1️⃣ Firebase Auth */
36
+ if (
37
+ typeof error?.code === 'string' &&
38
+ error.code.startsWith('auth/') &&
39
+ FIREBASE_ERROR_MAP[error.code]
40
+ ) {
41
+ return i18n.t(FIREBASE_ERROR_MAP[error.code] as any);
42
+ }
43
+
44
+ /* 2️⃣ Axios backend / network */
45
+ if (axios.isAxiosError(error)) {
46
+ // network error
47
+ if (error.message === 'Network Error') {
48
+ return i18n.t('errors.network');
49
+ }
50
+
51
+ const status = error.response?.status;
52
+ const data = error.response?.data;
53
+
54
+ // backend business code
55
+ const backendCode = extractBackendCode(data);
56
+ if (backendCode && BACKEND_ERROR_MAP[backendCode]) {
57
+ return i18n.t(BACKEND_ERROR_MAP[backendCode] as any);
58
+ }
59
+
60
+ // raw backend message
61
+ const backendMsg = extractBackendMessage(data);
62
+ if (backendMsg) return backendMsg;
63
+
64
+ // http status → i18n
65
+ if (status && HTTP_STATUS_MAP[status]) {
66
+ return i18n.t(HTTP_STATUS_MAP[status] as any);
67
+ }
68
+ }
69
+
70
+ /* 3️⃣ Fetch style */
71
+ if (error?.status && HTTP_STATUS_MAP[error.status]) {
72
+ return i18n.t(HTTP_STATUS_MAP[error.status] as any);
73
+ }
74
+
75
+ /* 4️⃣ JS runtime */
76
+ if (error instanceof Error && error.message) {
77
+ return error.message;
78
+ }
79
+
80
+ /* 5️⃣ Final fallback */
81
+ return i18n.t('errors.generic');
82
+ }
83
+
84
+ export function ErrorHandler(error: unknown) {
85
+ if (__DEV__) {
86
+ console.log('[ErrorHandler]', error);
87
+ }
88
+
89
+ const message = getErrorMessage(error);
90
+
91
+ ErrorToast({
92
+ message,
93
+ });
94
+ }
@@ -0,0 +1,61 @@
1
+ export const FIREBASE_ERROR_MAP: Record<string, string> = {
2
+ 'auth/too-many-requests': 'firebase.tooManyRequests',
3
+ 'auth/claims-too-large': 'firebase.claimsTooLarge',
4
+ 'auth/email-already-exists': 'firebase.emailExists',
5
+ 'auth/user-not-found': 'firebase.userNotFound',
6
+ 'auth/wrong-password': 'firebase.wrongPassword',
7
+ 'auth/email-already-in-use': 'firebase.emailAlreadyInUse',
8
+ 'auth/insufficient-permission': 'firebase.insufficientPermission',
9
+ 'auth/internal-error': 'firebase.internalError',
10
+ 'auth/invalid-argument': 'firebase.invalidArgument',
11
+ 'auth/invalid-claims': 'firebase.invalidClaims',
12
+ 'auth/invalid-continue-uri': 'firebase.invalidContinueUri',
13
+ 'auth/invalid-creation-time': 'firebase.invalidCreationTime',
14
+ 'auth/invalid-credential': 'firebase.invalidCredential',
15
+ 'auth/invalid-disabled-field': 'firebase.invalidDisabledField',
16
+ 'auth/invalid-display-name': 'firebase.invalidDisplayName',
17
+ 'auth/invalid-dynamic-link-domain': 'firebase.invalidDynamicLinkDomain',
18
+ 'auth/invalid-email': 'firebase.invalidEmail',
19
+ 'auth/invalid-email-verified': 'firebase.invalidEmailVerified',
20
+ 'auth/invalid-hash-algorithm': 'firebase.invalidHashAlgorithm',
21
+ 'auth/invalid-hash-block-size': 'firebase.invalidHashBlockSize',
22
+ 'auth/invalid-hash-derived-key-length':
23
+ 'firebase.invalidHashDerivedKeyLength',
24
+ 'auth/invalid-hash-key': 'firebase.invalidHashKey',
25
+ 'auth/invalid-hash-memory-cost': 'firebase.invalidHashMemoryCost',
26
+ 'auth/invalid-hash-parallelization': 'firebase.invalidHashParallelization',
27
+ 'auth/invalid-hash-rounds': 'firebase.invalidHashRounds',
28
+ 'auth/invalid-hash-salt-separator': 'firebase.invalidHashSaltSeparator',
29
+ 'auth/invalid-id-token': 'firebase.invalidIdToken',
30
+ 'auth/invalid-last-sign-in-time': 'firebase.invalidLastSignInTime',
31
+ 'auth/invalid-page-token': 'firebase.invalidPageToken',
32
+ 'auth/invalid-password': 'firebase.invalidPassword',
33
+ 'auth/invalid-password-has': 'firebase.invalidPasswordHash',
34
+ 'auth/invalid-password-salt': 'firebase.invalidPasswordSalt',
35
+ 'auth/invalid-phone-number': 'firebase.invalidPhoneNumber',
36
+ 'auth/invalid-photo-url': 'firebase.invalidPhotoUrl',
37
+ 'auth/invalid-provider-data': 'firebase.invalidProviderData',
38
+ 'auth/invalid-provider-id': 'firebase.invalidProviderId',
39
+ 'auth/invalid-oauth-responsetype': 'firebase.invalidOauthResponseType',
40
+ 'auth/invalid-session-cookie-duration':
41
+ 'firebase.invalidSessionCookieDuration',
42
+ 'auth/invalid-uid': 'firebase.invalidUid',
43
+ 'auth/invalid-user-import': 'firebase.invalidUserImport',
44
+ 'auth/maximum-user-count-exceeded': 'firebase.maximumUserCountExceeded',
45
+ 'auth/missing-android-pkg-name': 'firebase.missingAndroidPkgName',
46
+ 'auth/missing-continue-uri': 'firebase.missingContinueUri',
47
+ 'auth/missing-hash-algorithm': 'firebase.missingHashAlgorithm',
48
+ 'auth/missing-ios-bundle-id': 'firebase.missingIosBundleId',
49
+ 'auth/missing-uid': 'firebase.missingUid',
50
+ 'auth/missing-oauth-client-secret': 'firebase.missingOauthClientSecret',
51
+ 'auth/operation-not-allowed': 'firebase.operationNotAllowed',
52
+ 'auth/phone-number-already-exists': 'firebase.phoneNumberExists',
53
+ 'auth/project-not-found': 'firebase.projectNotFound',
54
+ 'auth/reserved-claims': 'firebase.reservedClaims',
55
+ 'auth/session-cookie-expired': 'firebase.sessionCookieExpired',
56
+ 'auth/session-cookie-revoked': 'firebase.sessionCookieRevoked',
57
+ 'auth/uid-already-exists': 'firebase.uidExists',
58
+ 'auth/unauthorized-continue-uri': 'firebase.unauthorizedContinueUri',
59
+ 'auth/network-request-failed': 'firebase.networkFailed',
60
+ 'auth/developer-error': 'firebase.developerError',
61
+ };
@@ -0,0 +1,47 @@
1
+ export const HTTP_STATUS_MAP: Record<number, string> = {
2
+ 100: 'http.100',
3
+ 101: 'http.101',
4
+ 200: 'http.200',
5
+ 201: 'http.201',
6
+ 202: 'http.202',
7
+ 203: 'http.203',
8
+ 204: 'http.204',
9
+ 205: 'http.205',
10
+ 206: 'http.206',
11
+ 300: 'http.300',
12
+ 301: 'http.301',
13
+ 302: 'http.302',
14
+ 303: 'http.303',
15
+ 304: 'http.304',
16
+ 305: 'http.305',
17
+ 307: 'http.307',
18
+ 400: 'http.400',
19
+ 401: 'http.401',
20
+ 402: 'http.402',
21
+ 403: 'http.403',
22
+ 404: 'http.404',
23
+ 405: 'http.405',
24
+ 406: 'http.406',
25
+ 407: 'http.407',
26
+ 408: 'http.408',
27
+ 409: 'http.409',
28
+ 410: 'http.410',
29
+ 411: 'http.411',
30
+ 412: 'http.412',
31
+ 413: 'http.413',
32
+ 414: 'http.414',
33
+ 415: 'http.415',
34
+ 416: 'http.416',
35
+ 417: 'http.417',
36
+ 422: 'http.422',
37
+ 426: 'http.426',
38
+ 428: 'http.428',
39
+ 429: 'http.429',
40
+ 431: 'http.431',
41
+ 500: 'http.500',
42
+ 501: 'http.501',
43
+ 502: 'http.502',
44
+ 503: 'http.503',
45
+ 504: 'http.504',
46
+ 505: 'http.505',
47
+ };
@@ -0,0 +1,4 @@
1
+ export * from './errorHandler';
2
+ export * from './backendErrorCodes';
3
+ export * from './firebaseErrorMap';
4
+ export * from './httpStatusMap';
@@ -0,0 +1 @@
1
+ export { isDeviceOnline, setupOnlineManager } from './onlineManager';
@@ -0,0 +1,22 @@
1
+ import NetInfo from '@react-native-community/netinfo';
2
+ import { onlineManager } from '@tanstack/react-query';
3
+
4
+ /**
5
+ * Standard TanStack Query + NetInfo wiring.
6
+ * When offline, queries with networkMode: 'online' pause
7
+ * and do not hit the network. Cached data remains available.
8
+ *
9
+ * `setEventListener` returns void; the listener callback itself returns
10
+ * the NetInfo unsubscribe that Query owns internally.
11
+ */
12
+ export const setupOnlineManager = (): void => {
13
+ onlineManager.setEventListener(setOnline => {
14
+ return NetInfo.addEventListener(state => {
15
+ const online =
16
+ state.isConnected === true && state.isInternetReachable !== false;
17
+ setOnline(online);
18
+ });
19
+ });
20
+ };
21
+
22
+ export const isDeviceOnline = (): boolean => onlineManager.isOnline();
@@ -0,0 +1 @@
1
+ export { queryClient } from './queryClient';
@@ -0,0 +1,23 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+
3
+ export const queryClient = new QueryClient({
4
+ defaultOptions: {
5
+ queries: {
6
+ /** Pause fetches while offline; serve cache when available. */
7
+ networkMode: 'online',
8
+ staleTime: 5 * 60 * 1000,
9
+ gcTime: 10 * 60 * 1000,
10
+ refetchOnMount: true,
11
+ refetchOnReconnect: true,
12
+ refetchOnWindowFocus: true,
13
+ retry: 2,
14
+ },
15
+ mutations: {
16
+ /**
17
+ * 'online' would pause mutations offline and leave isPending stuck true.
18
+ * The axios offline guard rejects immediately instead, so the UI recovers.
19
+ */
20
+ networkMode: 'always',
21
+ },
22
+ },
23
+ });
@@ -0,0 +1,137 @@
1
+ import React from 'react';
2
+ import { IconName } from '@/assets/icons';
3
+ import Icon from '@/components/shared/Icon';
4
+ import { useAppTheme } from '@/context/ThemeContext';
5
+ import { FontFamily } from '@/theme';
6
+ import { BaseToast, BaseToastProps } from 'react-native-toast-message';
7
+ import { StyleSheet } from 'react-native';
8
+
9
+ type ToastCardType = 'success' | 'error' | 'info';
10
+
11
+ const TEXT_PROPS = { allowFontScaling: false } as const;
12
+
13
+ interface ToastCardProps extends BaseToastProps {
14
+ type: ToastCardType;
15
+ leftIcon?: IconName;
16
+ rightIcon?: IconName;
17
+ }
18
+
19
+ const ToastCard = ({ type, leftIcon, rightIcon, ...rest }: ToastCardProps) => {
20
+ const { color } = useAppTheme();
21
+
22
+ const accentByType: Record<ToastCardType, string> = {
23
+ success: color.success,
24
+ error: color.danger_soft,
25
+ info: color.info,
26
+ };
27
+
28
+ const renderLeadingIcon = leftIcon
29
+ ? () => <Icon name={leftIcon} size={24} style={styles.leadingIcon} />
30
+ : undefined;
31
+
32
+ const renderTrailingIcon = rightIcon
33
+ ? () => <Icon name={rightIcon} size={24} style={styles.trailingIcon} />
34
+ : undefined;
35
+
36
+ return (
37
+ <BaseToast
38
+ {...rest}
39
+ renderLeadingIcon={renderLeadingIcon}
40
+ renderTrailingIcon={renderTrailingIcon}
41
+ style={[
42
+ styles.base,
43
+ leftIcon ? styles.hasLeftIcon : styles.noLeftIcon,
44
+ rightIcon ? styles.hasRightIcon : styles.noRightIcon,
45
+ {
46
+ borderLeftColor: accentByType[type],
47
+ backgroundColor: color.background_secondary,
48
+ },
49
+ ]}
50
+ contentContainerStyle={[
51
+ styles.contentContainer,
52
+ leftIcon ? styles.contentHasLeftIcon : styles.contentNoLeftIcon,
53
+ rightIcon ? styles.contentHasRightIcon : styles.contentNoRightIcon,
54
+ {
55
+ backgroundColor: color.background_secondary,
56
+ },
57
+ ]}
58
+ text1NumberOfLines={2}
59
+ text2NumberOfLines={3}
60
+ text1Props={TEXT_PROPS}
61
+ text2Props={TEXT_PROPS}
62
+ text1Style={[
63
+ styles.text1,
64
+ {
65
+ color: color.text_primary,
66
+ },
67
+ ]}
68
+ text2Style={[
69
+ styles.text2,
70
+ {
71
+ color: color.text_primary,
72
+ },
73
+ ]}
74
+ />
75
+ );
76
+ };
77
+
78
+ export default ToastCard;
79
+
80
+ const styles = StyleSheet.create({
81
+ base: {
82
+ overflow: 'hidden',
83
+ },
84
+
85
+ contentContainer: {
86
+ paddingVertical: 8,
87
+ },
88
+
89
+ leadingIcon: {
90
+ alignSelf: 'center',
91
+ },
92
+
93
+ trailingIcon: {
94
+ alignSelf: 'center',
95
+ },
96
+
97
+ text1: {
98
+ fontFamily: FontFamily.InterTightSemiBold,
99
+ },
100
+
101
+ text2: {
102
+ fontFamily: FontFamily.InterTightRegular,
103
+ fontSize: 12,
104
+ },
105
+
106
+ hasLeftIcon: {
107
+ paddingLeft: 10,
108
+ },
109
+
110
+ noLeftIcon: {
111
+ paddingLeft: 0,
112
+ },
113
+
114
+ hasRightIcon: {
115
+ paddingRight: 10,
116
+ },
117
+
118
+ noRightIcon: {
119
+ paddingRight: 2,
120
+ },
121
+
122
+ contentHasLeftIcon: {
123
+ paddingLeft: 10,
124
+ },
125
+
126
+ contentNoLeftIcon: {
127
+ paddingLeft: 15,
128
+ },
129
+
130
+ contentHasRightIcon: {
131
+ paddingRight: 10,
132
+ },
133
+
134
+ contentNoRightIcon: {
135
+ paddingRight: 15,
136
+ },
137
+ });
@@ -0,0 +1,32 @@
1
+ import { ToastConfig } from 'react-native-toast-message';
2
+ import ToastCard from './ToastCard';
3
+
4
+ export const toastConfig: ToastConfig = {
5
+ success: props => <ToastCard {...props} type="success" />,
6
+ error: props => <ToastCard {...props} type="error" />,
7
+ info: props => <ToastCard {...props} type="info" />,
8
+ successWithIcon: props => (
9
+ <ToastCard
10
+ {...props}
11
+ type="success"
12
+ leftIcon={props.props?.leftIcon}
13
+ rightIcon={props.props?.rightIcon}
14
+ />
15
+ ),
16
+ errorWithIcon: props => (
17
+ <ToastCard
18
+ {...props}
19
+ type="error"
20
+ leftIcon={props.props?.leftIcon}
21
+ rightIcon={props.props?.rightIcon}
22
+ />
23
+ ),
24
+ infoWithIcon: props => (
25
+ <ToastCard
26
+ {...props}
27
+ type="info"
28
+ leftIcon={props.props?.leftIcon}
29
+ rightIcon={props.props?.rightIcon}
30
+ />
31
+ ),
32
+ };
@@ -0,0 +1,3 @@
1
+ export * from './toast';
2
+ export * from './ToastCard';
3
+ export * from './ToastConfig';
@@ -0,0 +1,68 @@
1
+ import Toast, { ToastShowParams } from 'react-native-toast-message';
2
+ import { IconName } from '@/assets/icons';
3
+ import i18n from '@/i18n';
4
+
5
+ type AtLeastOne<T, Keys extends keyof T = keyof T> = Partial<T> &
6
+ { [K in Keys]-?: Required<Pick<T, K>> }[Keys];
7
+
8
+ type IconProps = {
9
+ leftIcon?: IconName;
10
+ rightIcon?: IconName;
11
+ };
12
+
13
+ type BaseToastArgs = {
14
+ title?: string;
15
+ message?: string;
16
+ };
17
+
18
+ type ToastWithIconArgs = BaseToastArgs & {
19
+ icons: AtLeastOne<IconProps>;
20
+ };
21
+
22
+ const DEFAULT_OPTIONS: Omit<ToastShowParams, 'props' | 'type'> = {
23
+ position: 'top' as const,
24
+ visibilityTime: 4000,
25
+ autoHide: true,
26
+ topOffset: 50,
27
+ avoidKeyboard: true,
28
+ };
29
+
30
+ const showToast = (
31
+ type: ToastShowParams['type'],
32
+ defaultTitle: string,
33
+ args: BaseToastArgs,
34
+ icons?: ToastWithIconArgs['icons'],
35
+ ) => {
36
+ const resolvedTitle = args.title ?? defaultTitle;
37
+
38
+ if (!resolvedTitle && !args.message) {
39
+ __DEV__ && console.warn('[Toast] title or message should be provided');
40
+ return;
41
+ }
42
+
43
+ Toast.show({
44
+ ...DEFAULT_OPTIONS,
45
+ type,
46
+ text1: resolvedTitle,
47
+ text2: args.message,
48
+ props: icons,
49
+ });
50
+ };
51
+
52
+ export const SuccessToast = (args: BaseToastArgs) =>
53
+ showToast('success', i18n.t('common.success'), args);
54
+
55
+ export const ErrorToast = (args: BaseToastArgs) =>
56
+ showToast('error', i18n.t('common.error'), args);
57
+
58
+ export const InfoToast = (args: BaseToastArgs) =>
59
+ showToast('info', i18n.t('common.info'), args);
60
+
61
+ export const SuccessToastWithIcon = (args: ToastWithIconArgs) =>
62
+ showToast('successWithIcon', i18n.t('common.success'), args, args.icons);
63
+
64
+ export const ErrorToastWithIcon = (args: ToastWithIconArgs) =>
65
+ showToast('errorWithIcon', i18n.t('common.error'), args, args.icons);
66
+
67
+ export const InfoToastWithIcon = (args: ToastWithIconArgs) =>
68
+ showToast('infoWithIcon', i18n.t('common.info'), args, args.icons);
@@ -0,0 +1,3 @@
1
+ import { createMMKV } from 'react-native-mmkv';
2
+
3
+ export const createMMKVInstance = (id: string) => createMMKV({ id });
@@ -0,0 +1,16 @@
1
+ import { MMKV } from 'react-native-mmkv';
2
+
3
+ export const createMMKVAdapter = (storage: MMKV) => ({
4
+ setItem: (key: string, value: string) => {
5
+ storage.set(key, value);
6
+ return Promise.resolve();
7
+ },
8
+ getItem: (key: string) => {
9
+ const value = storage.getString(key);
10
+ return Promise.resolve(value ?? null);
11
+ },
12
+ removeItem: (key: string) => {
13
+ storage.remove(key);
14
+ return Promise.resolve();
15
+ },
16
+ });
@@ -0,0 +1,15 @@
1
+ import { configureStore } from '@reduxjs/toolkit';
2
+ import { persistedReducer } from './persist';
3
+ import { persistStore } from 'redux-persist';
4
+
5
+ export const store = configureStore({
6
+ reducer: persistedReducer,
7
+ middleware: getDefaultMiddleware =>
8
+ getDefaultMiddleware({
9
+ serializableCheck: false,
10
+ }),
11
+ });
12
+
13
+ export const persistor = persistStore(store);
14
+
15
+ export type AppDispatch = typeof store.dispatch;
@@ -0,0 +1,6 @@
1
+ import { createMMKVInstance } from '@/storage/mmkvAdapter';
2
+ import { createMMKVAdapter } from '@/storage/mmkvInstance';
3
+
4
+ const reduxMMKV = createMMKVInstance('reduxStorage');
5
+
6
+ export const reduxStorage = createMMKVAdapter(reduxMMKV);
@@ -0,0 +1,10 @@
1
+ import { PersistConfig, persistReducer } from 'redux-persist';
2
+ import { reduxStorage } from './mmkv';
3
+ import { rootReducer, RootStoreState } from './rootReducer';
4
+
5
+ const persistConfig: PersistConfig<RootStoreState> = {
6
+ key: 'root',
7
+ storage: reduxStorage,
8
+ };
9
+
10
+ export const persistedReducer = persistReducer(persistConfig, rootReducer);
@@ -0,0 +1,26 @@
1
+ import { combineReducers } from '@reduxjs/toolkit';
2
+ import authReducer from '@/redux/slice/auth.slice';
3
+ import userReducer from '@/redux/slice/user.slice';
4
+ import loaderReducer from '@/redux/slice/loader.slice';
5
+ import settingsReducer from '@/redux/slice/settings.slice';
6
+ import i18n from '@/i18n';
7
+
8
+ const appReducer = combineReducers({
9
+ auth: authReducer,
10
+ user: userReducer,
11
+ loader: loaderReducer,
12
+ settings: settingsReducer,
13
+ });
14
+
15
+ export const rootReducer = (
16
+ state: ReturnType<typeof appReducer> | undefined,
17
+ action: any,
18
+ ) => {
19
+ if (action.type === 'auth/logout') {
20
+ state = undefined;
21
+ void i18n.changeLanguage('en');
22
+ }
23
+ return appReducer(state, action);
24
+ };
25
+
26
+ export type RootStoreState = ReturnType<typeof rootReducer>;
@@ -0,0 +1,40 @@
1
+ import { palette } from './palette';
2
+ import { light } from './light';
3
+ import { IconName } from '@/assets/icons';
4
+
5
+ export const dark = {
6
+ color: {
7
+ primary: palette.blue[400],
8
+ primary_soft: palette.blue[500],
9
+ primary_wash: palette.black[200],
10
+ primary_accent: palette.blue[400],
11
+ primary_deep: palette.blue[600],
12
+ secondary: palette.blue[400],
13
+ secondary_muted: palette.blue[600],
14
+ background_primary: palette.black[900],
15
+ background_secondary: palette.black[200],
16
+ background_muted: palette.black[200],
17
+ background_soft: palette.black[200],
18
+ text_primary: palette.white[900],
19
+ text_secondary: palette.gray[400],
20
+ text_heading: palette.white[900],
21
+ text_body: palette.gray[400],
22
+ text_subtle: palette.gray[400],
23
+ text_input: palette.white[900],
24
+ text_placeholder: palette.gray[400],
25
+ border: palette.gray[300],
26
+ placeholder: palette.gray[400],
27
+ text_default: palette.white[900],
28
+ danger: palette.red[500],
29
+ danger_soft: palette.red[500],
30
+ danger_strong: palette.red[600],
31
+ success: palette.green[500],
32
+ info: palette.blue[400],
33
+ disabled: palette.gray[300],
34
+ shadow: palette.black[500],
35
+ shadow_strong: palette.black[900],
36
+ },
37
+ icon: {
38
+ tooltip: 'tooltip_dark',
39
+ } satisfies Record<keyof typeof light.icon, IconName>,
40
+ } satisfies typeof light;
@@ -0,0 +1,20 @@
1
+ export const FontFamily = {
2
+ InterTightBlack: 'InterTight-Black',
3
+ InterTightBlackItalic: 'InterTight-BlackItalic',
4
+ InterTightBold: 'InterTight-Bold',
5
+ InterTightBoldItalic: 'InterTight-BoldItalic',
6
+ InterTightExtraBold: 'InterTight-ExtraBold',
7
+ InterTightExtraBoldItalic: 'InterTight-ExtraBoldItalic',
8
+ InterTightExtraLight: 'InterTight-ExtraLight',
9
+ InterTightExtraLightItalic: 'InterTight-ExtraLightItalic',
10
+ InterTightItalic: 'InterTight-Italic',
11
+ InterTightLight: 'InterTight-Light',
12
+ InterTightLightItalic: 'InterTight-LightItalic',
13
+ InterTightMedium: 'InterTight-Medium',
14
+ InterTightMediumItalic: 'InterTight-MediumItalic',
15
+ InterTightRegular: 'InterTight-Regular',
16
+ InterTightSemiBold: 'InterTight-SemiBold',
17
+ InterTightSemiBoldItalic: 'InterTight-SemiBoldItalic',
18
+ InterTightThin: 'InterTight-Thin',
19
+ InterTightThinItalic: 'InterTight-ThinItalic',
20
+ } as const;
@@ -0,0 +1,6 @@
1
+ export * from './palette';
2
+ export * from './light';
3
+ export * from './dark';
4
+ export * from './spacing';
5
+ export * from './fonts';
6
+ export * from './typography';