create-react-native-starter-kit 0.0.0-stage → 1.0.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.
- package/LICENSE +21 -0
- package/README.md +378 -2
- package/bin/create-react-native-starter-kit.js +95 -0
- package/lib/copy-src.js +127 -0
- package/lib/creator.js +211 -0
- package/lib/deps.js +58 -0
- package/lib/patch-configs.js +318 -0
- package/lib/patch-native.js +588 -0
- package/package.json +47 -4
- package/scripts/sync-template.js +52 -0
- package/template/.env.example +3 -0
- package/template/patches/react-native-element-dropdown+2.12.4.patch +67 -0
- package/template/src/app/App.tsx +25 -0
- package/template/src/app/navigation/root.tsx +24 -0
- package/template/src/app/navigation/stacks/app.stack.tsx +67 -0
- package/template/src/app/navigation/stacks/app.tab.tsx +72 -0
- package/template/src/app/navigation/stacks/auth.stack.tsx +56 -0
- package/template/src/app/providers.tsx +43 -0
- package/template/src/assets/fonts/InterTight-Black.ttf +0 -0
- package/template/src/assets/fonts/InterTight-BlackItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Bold.ttf +0 -0
- package/template/src/assets/fonts/InterTight-BoldItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraBold.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraBoldItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraLight.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ExtraLightItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Italic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Light.ttf +0 -0
- package/template/src/assets/fonts/InterTight-LightItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Medium.ttf +0 -0
- package/template/src/assets/fonts/InterTight-MediumItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Regular.ttf +0 -0
- package/template/src/assets/fonts/InterTight-SemiBold.ttf +0 -0
- package/template/src/assets/fonts/InterTight-SemiBoldItalic.ttf +0 -0
- package/template/src/assets/fonts/InterTight-Thin.ttf +0 -0
- package/template/src/assets/fonts/InterTight-ThinItalic.ttf +0 -0
- package/template/src/assets/icons/back.svg +3 -0
- package/template/src/assets/icons/calendar.svg +3 -0
- package/template/src/assets/icons/claim_upload.svg +3 -0
- package/template/src/assets/icons/clock.svg +1 -0
- package/template/src/assets/icons/downArrow.svg +3 -0
- package/template/src/assets/icons/edit.svg +3 -0
- package/template/src/assets/icons/eye.svg +4 -0
- package/template/src/assets/icons/eyeClose.svg +7 -0
- package/template/src/assets/icons/folder.svg +5 -0
- package/template/src/assets/icons/home.svg +4 -0
- package/template/src/assets/icons/index.ts +35 -0
- package/template/src/assets/icons/log_out.svg +4 -0
- package/template/src/assets/icons/setting.svg +4 -0
- package/template/src/assets/icons/tabs/home/filled_dark.svg +3 -0
- package/template/src/assets/icons/tabs/home/filled_light.svg +3 -0
- package/template/src/assets/icons/tabs/home/outline.svg +4 -0
- package/template/src/assets/icons/tabs/index.ts +31 -0
- package/template/src/assets/icons/tabs/profile/filled_dark.svg +6 -0
- package/template/src/assets/icons/tabs/profile/filled_light.svg +6 -0
- package/template/src/assets/icons/tabs/profile/outline.svg +3 -0
- package/template/src/assets/icons/tooltip_dark.svg +1 -0
- package/template/src/assets/icons/tooltip_light.svg +1 -0
- package/template/src/assets/icons/upArrow.svg +3 -0
- package/template/src/assets/images/back.png +0 -0
- package/template/src/assets/images/index.ts +7 -0
- package/template/src/assets/images/placeholder/profile.png +0 -0
- package/template/src/components/shared/AppImage.tsx +190 -0
- package/template/src/components/shared/AvatarPicker.tsx +175 -0
- package/template/src/components/shared/CustomButton.tsx +273 -0
- package/template/src/components/shared/DocumentPicker.tsx +86 -0
- package/template/src/components/shared/GlobalLoader.tsx +47 -0
- package/template/src/components/shared/Icon.tsx +28 -0
- package/template/src/components/shared/PressableIcon.tsx +104 -0
- package/template/src/components/shared/bottom-sheet/BaseBottomSheet.tsx +30 -0
- package/template/src/components/shared/bottom-sheet/BaseBottomSheetModal.tsx +50 -0
- package/template/src/components/shared/bottom-sheet/sheets/ImagePickerSheet.tsx +310 -0
- package/template/src/components/shared/bottom-sheet/types.ts +4 -0
- package/template/src/components/shared/form-input/FormInput.tsx +140 -0
- package/template/src/components/shared/form-input/FormInputRenderer.tsx +82 -0
- package/template/src/components/shared/form-input/FullNameInput.tsx +243 -0
- package/template/src/components/shared/form-input/formFieldStyles.ts +43 -0
- package/template/src/components/shared/form-input/type.ts +188 -0
- package/template/src/components/shared/form-input/variants/CountryPhoneVariant.tsx +289 -0
- package/template/src/components/shared/form-input/variants/DateTimeVariant.tsx +280 -0
- package/template/src/components/shared/form-input/variants/DropdownVariant.tsx +221 -0
- package/template/src/components/shared/form-input/variants/TextInputVariant.tsx +300 -0
- package/template/src/components/shared/form-input/variants/ToggleVariant.tsx +194 -0
- package/template/src/components/shared/modal/BaseModal.tsx +83 -0
- package/template/src/components/shared/modal/common/AlertModal.tsx +173 -0
- package/template/src/components/shared/navigation/AnimatedTabIcon.tsx +37 -0
- package/template/src/components/shared/navigation/HeaderActionButton.tsx +70 -0
- package/template/src/components/shared/navigation/HeaderBackButton.tsx +38 -0
- package/template/src/components/shared/navigation/StackHeader.tsx +105 -0
- package/template/src/components/shared/navigation/tabIconRender.tsx +14 -0
- package/template/src/components/shared/offline/OfflineQueryBoundary.tsx +25 -0
- package/template/src/components/shared/offline/OfflineState.tsx +229 -0
- package/template/src/components/shared/offline/index.ts +6 -0
- package/template/src/components/shared/offline/types.ts +15 -0
- package/template/src/components/shared/offline/wifi-icon-animation/BrokenWifiAnimation.tsx +116 -0
- package/template/src/components/shared/offline/wifi-icon-animation/WifiPiece.tsx +80 -0
- package/template/src/components/shared/offline/wifi-icon-animation/geometry.ts +163 -0
- package/template/src/components/shared/offline/wifi-icon-animation/useWifiIconAnimationActive.ts +37 -0
- package/template/src/components/shared/skelton/BaseSkelton.tsx +71 -0
- package/template/src/components/shared/skelton/ShimmerHolder.ts +6 -0
- package/template/src/components/shared/skelton/holders/HomeShimmer.tsx +25 -0
- package/template/src/components/shared/skelton/holders/index.ts +1 -0
- package/template/src/components/shared/skelton/layouts/avatar.layout.ts +18 -0
- package/template/src/components/shared/skelton/layouts/home.layout.ts +16 -0
- package/template/src/components/shared/skelton/layouts/index.ts +3 -0
- package/template/src/components/shared/skelton/layouts/text.layout.ts +18 -0
- package/template/src/components/shared/text/CustomText.tsx +85 -0
- package/template/src/components/shared/text/ShimmerText.tsx +159 -0
- package/template/src/components/shared/text/types.ts +40 -0
- package/template/src/constants/device.ts +12 -0
- package/template/src/constants/endpoint.ts +11 -0
- package/template/src/constants/keyExtractor.ts +6 -0
- package/template/src/constants/limits.ts +9 -0
- package/template/src/constants/queryKey.ts +3 -0
- package/template/src/constants/regex.ts +7 -0
- package/template/src/context/NetworkContext.tsx +102 -0
- package/template/src/context/ThemeContext.tsx +56 -0
- package/template/src/hooks/app/index.ts +2 -0
- package/template/src/hooks/app/useGetProfile.ts +38 -0
- package/template/src/hooks/app/useUpdateProfile.ts +21 -0
- package/template/src/hooks/useAppDispatch.ts +6 -0
- package/template/src/hooks/useAppSelector.ts +4 -0
- package/template/src/hooks/useDebounce.ts +15 -0
- package/template/src/hooks/useDocumentPicker.ts +102 -0
- package/template/src/hooks/useImagePicker.ts +83 -0
- package/template/src/i18n/i18next.d.ts +9 -0
- package/template/src/i18n/index.ts +16 -0
- package/template/src/i18n/locales/en.json +274 -0
- package/template/src/i18n/locales/ru.json +165 -0
- package/template/src/i18n/resources.ts +11 -0
- package/template/src/redux/selectors/auth.selectors.ts +4 -0
- package/template/src/redux/slice/auth.slice.ts +23 -0
- package/template/src/redux/slice/loader.slice.ts +25 -0
- package/template/src/redux/slice/settings.slice.ts +26 -0
- package/template/src/redux/slice/user.slice.ts +25 -0
- package/template/src/screens/app/main-tab/index.tsx +73 -0
- package/template/src/screens/app/main-tab/style.ts +0 -0
- package/template/src/screens/app/profile-setup/index.tsx +162 -0
- package/template/src/screens/app/profile-setup/styles.ts +3 -0
- package/template/src/screens/auth/forgot-password/index.tsx +59 -0
- package/template/src/screens/auth/forgot-password/styles.ts +0 -0
- package/template/src/screens/auth/index.ts +4 -0
- package/template/src/screens/auth/login/index.tsx +138 -0
- package/template/src/screens/auth/login/styles.ts +3 -0
- package/template/src/screens/auth/signup/index.tsx +111 -0
- package/template/src/screens/auth/signup/styles.ts +3 -0
- package/template/src/screens/auth/verify-email/index.tsx +15 -0
- package/template/src/screens/auth/verify-email/styles.ts +3 -0
- package/template/src/screens/tab/home/index.tsx +49 -0
- package/template/src/screens/tab/home/styles.ts +3 -0
- package/template/src/screens/tab/index.ts +2 -0
- package/template/src/screens/tab/profile/index.tsx +50 -0
- package/template/src/screens/tab/profile/styles.ts +3 -0
- package/template/src/services/api/auth/auth.api.ts +22 -0
- package/template/src/services/api/auth/auth.endpoints.ts +7 -0
- package/template/src/services/api/auth/auth.types.ts +9 -0
- package/template/src/services/api/auth/index.ts +3 -0
- package/template/src/services/api/axios/axios.instance.ts +15 -0
- package/template/src/services/api/axios/axios.interceptors.ts +43 -0
- package/template/src/services/api/axios/index.ts +2 -0
- package/template/src/services/api/mock/mockAdapter.ts +97 -0
- package/template/src/services/api/user/index.ts +2 -0
- package/template/src/services/api/user/user.api.ts +67 -0
- package/template/src/services/api/user/user.endpoints.ts +0 -0
- package/template/src/services/api/user/user.types.ts +0 -0
- package/template/src/services/errors/backendErrorCodes.ts +7 -0
- package/template/src/services/errors/errorHandler.ts +94 -0
- package/template/src/services/errors/firebaseErrorMap.ts +61 -0
- package/template/src/services/errors/httpStatusMap.ts +47 -0
- package/template/src/services/errors/index.ts +4 -0
- package/template/src/services/network/index.ts +1 -0
- package/template/src/services/network/onlineManager.ts +22 -0
- package/template/src/services/query/index.ts +1 -0
- package/template/src/services/query/queryClient.ts +23 -0
- package/template/src/services/toast/ToastCard.tsx +137 -0
- package/template/src/services/toast/ToastConfig.tsx +32 -0
- package/template/src/services/toast/index.ts +3 -0
- package/template/src/services/toast/toast.ts +68 -0
- package/template/src/storage/mmkvAdapter.ts +3 -0
- package/template/src/storage/mmkvInstance.ts +16 -0
- package/template/src/store/index.ts +15 -0
- package/template/src/store/mmkv.ts +6 -0
- package/template/src/store/persist.ts +10 -0
- package/template/src/store/rootReducer.ts +26 -0
- package/template/src/theme/dark.ts +40 -0
- package/template/src/theme/fonts.ts +20 -0
- package/template/src/theme/index.ts +6 -0
- package/template/src/theme/layout.ts +23 -0
- package/template/src/theme/light.ts +41 -0
- package/template/src/theme/palette.ts +32 -0
- package/template/src/theme/spacing.ts +7 -0
- package/template/src/theme/typography.ts +80 -0
- package/template/src/types/api.types.ts +34 -0
- package/template/src/types/env.d.ts +6 -0
- package/template/src/types/index.ts +2 -0
- package/template/src/types/navigation.types.ts +94 -0
- package/template/src/utils/clearAppSession.ts +10 -0
- package/template/src/utils/date.ts +19 -0
- package/template/src/utils/documentPickerHandler.ts +96 -0
- package/template/src/utils/getFileNameFromUri.ts +18 -0
- package/template/src/utils/getInitials.ts +13 -0
- package/template/src/utils/hindiText.ts +33 -0
- package/template/src/utils/imageCompress.ts +97 -0
- package/template/src/utils/imagePickerHandler.ts +200 -0
- package/template/src/utils/maskPhone.ts +17 -0
- package/template/src/utils/normalize.ts +12 -0
- package/template/src/validation/messages.ts +11 -0
- package/template/src/validation/rules.ts +44 -0
- package/template/src/validation/schema/auth.schema.ts +53 -0
- package/template/src/validation/schema/user.schema.ts +46 -0
- package/template/src/validation/tValError.ts +13 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { spacing } from './spacing';
|
|
3
|
+
|
|
4
|
+
export const layout = StyleSheet.create({
|
|
5
|
+
flexContainer: {
|
|
6
|
+
flex: 1,
|
|
7
|
+
padding: spacing.md,
|
|
8
|
+
},
|
|
9
|
+
rowGapCenter: {
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
gap: 8,
|
|
13
|
+
justifyContent: 'center',
|
|
14
|
+
marginTop: spacing.md,
|
|
15
|
+
},
|
|
16
|
+
flexEnd: { flex: 1, justifyContent: 'flex-end' },
|
|
17
|
+
flexStart: { flex: 1, justifyContent: 'flex-start' },
|
|
18
|
+
flex: { flex: 1 },
|
|
19
|
+
separator: { height: 1, marginVertical: spacing.md },
|
|
20
|
+
marginTopMd: { marginTop: spacing.md },
|
|
21
|
+
marginBottomMd: { marginBottom: spacing.md },
|
|
22
|
+
marginBottomLg: { marginBottom: spacing.lg },
|
|
23
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { IconName } from '@/assets/icons';
|
|
2
|
+
import { palette } from './palette';
|
|
3
|
+
|
|
4
|
+
export const light = {
|
|
5
|
+
color: {
|
|
6
|
+
primary: palette.blue[500],
|
|
7
|
+
primary_soft: palette.blue[400],
|
|
8
|
+
primary_wash: palette.blue[50],
|
|
9
|
+
primary_accent: palette.blue[400],
|
|
10
|
+
primary_deep: palette.blue[600],
|
|
11
|
+
secondary: palette.blue[500],
|
|
12
|
+
secondary_muted: palette.blue[50],
|
|
13
|
+
background_primary: palette.white[900],
|
|
14
|
+
background_secondary: palette.white[200],
|
|
15
|
+
background_muted: palette.white[100],
|
|
16
|
+
background_soft: palette.white[100],
|
|
17
|
+
text_primary: palette.black[500],
|
|
18
|
+
text_secondary: palette.gray[100],
|
|
19
|
+
text_heading: palette.black[600],
|
|
20
|
+
text_body: palette.gray[300],
|
|
21
|
+
text_subtle: palette.gray[100],
|
|
22
|
+
text_input: palette.black[500],
|
|
23
|
+
text_placeholder: palette.gray[400],
|
|
24
|
+
border: palette.gray[200],
|
|
25
|
+
placeholder: palette.gray[400],
|
|
26
|
+
text_default: palette.white[900],
|
|
27
|
+
danger: palette.red[500],
|
|
28
|
+
danger_soft: palette.red[500],
|
|
29
|
+
danger_strong: palette.red[600],
|
|
30
|
+
success: palette.green[500],
|
|
31
|
+
info: palette.blue[400],
|
|
32
|
+
disabled: palette.white[100],
|
|
33
|
+
shadow: palette.black[500],
|
|
34
|
+
shadow_strong: palette.black[900],
|
|
35
|
+
},
|
|
36
|
+
icon: {
|
|
37
|
+
tooltip: 'tooltip_light' as IconName,
|
|
38
|
+
} satisfies Record<string, IconName>,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ColorType = typeof light.color;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const palette = {
|
|
2
|
+
blue: {
|
|
3
|
+
50: '#E8F3FF',
|
|
4
|
+
400: '#0597FF',
|
|
5
|
+
500: '#082B60',
|
|
6
|
+
600: '#061E45',
|
|
7
|
+
},
|
|
8
|
+
white: {
|
|
9
|
+
100: '#F5F6FB',
|
|
10
|
+
200: '#FAFAFA',
|
|
11
|
+
900: '#FFFFFF',
|
|
12
|
+
},
|
|
13
|
+
black: {
|
|
14
|
+
200: '#121212',
|
|
15
|
+
500: '#2D2D2D',
|
|
16
|
+
600: '#010407',
|
|
17
|
+
900: '#000000',
|
|
18
|
+
},
|
|
19
|
+
gray: {
|
|
20
|
+
100: '#89909D',
|
|
21
|
+
200: '#E4E6F4',
|
|
22
|
+
300: '#43434C',
|
|
23
|
+
400: '#A6ADB9',
|
|
24
|
+
},
|
|
25
|
+
red: {
|
|
26
|
+
500: '#F64649',
|
|
27
|
+
600: '#D83A52',
|
|
28
|
+
},
|
|
29
|
+
green: {
|
|
30
|
+
500: '#22C55E',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { normalize } from '@/utils/normalize';
|
|
2
|
+
import { FontFamily } from './fonts';
|
|
3
|
+
|
|
4
|
+
export type FontWeight = '200' | '300' | '400' | '500' | '600' | '700' | '800';
|
|
5
|
+
|
|
6
|
+
export const FONT_MAP: Record<FontWeight, string> = {
|
|
7
|
+
'200': FontFamily.InterTightExtraLight,
|
|
8
|
+
'300': FontFamily.InterTightLight,
|
|
9
|
+
'400': FontFamily.InterTightRegular,
|
|
10
|
+
'500': FontFamily.InterTightMedium,
|
|
11
|
+
'600': FontFamily.InterTightSemiBold,
|
|
12
|
+
'700': FontFamily.InterTightBold,
|
|
13
|
+
'800': FontFamily.InterTightExtraBold,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type TextVariant =
|
|
17
|
+
| 'caption'
|
|
18
|
+
| 'micro'
|
|
19
|
+
| 'footnote'
|
|
20
|
+
| 'subtitle'
|
|
21
|
+
| 'label'
|
|
22
|
+
| 'callout'
|
|
23
|
+
| 'body'
|
|
24
|
+
| 'titleMd'
|
|
25
|
+
| 'titleSm'
|
|
26
|
+
| 'title'
|
|
27
|
+
| 'header'
|
|
28
|
+
| 'display'
|
|
29
|
+
| 'hero'
|
|
30
|
+
| 'button';
|
|
31
|
+
|
|
32
|
+
export const TYPOGRAPHY = {
|
|
33
|
+
caption: {
|
|
34
|
+
fontSize: normalize(10),
|
|
35
|
+
},
|
|
36
|
+
micro: {
|
|
37
|
+
fontSize: normalize(11),
|
|
38
|
+
},
|
|
39
|
+
footnote: {
|
|
40
|
+
fontSize: normalize(12),
|
|
41
|
+
},
|
|
42
|
+
subtitle: {
|
|
43
|
+
fontSize: normalize(13),
|
|
44
|
+
},
|
|
45
|
+
label: {
|
|
46
|
+
fontSize: normalize(14),
|
|
47
|
+
marginBottom: 8,
|
|
48
|
+
},
|
|
49
|
+
callout: {
|
|
50
|
+
fontSize: normalize(15),
|
|
51
|
+
},
|
|
52
|
+
body: {
|
|
53
|
+
fontSize: normalize(16),
|
|
54
|
+
marginBottom: 8,
|
|
55
|
+
},
|
|
56
|
+
titleMd: {
|
|
57
|
+
fontSize: normalize(17),
|
|
58
|
+
},
|
|
59
|
+
titleSm: {
|
|
60
|
+
fontSize: normalize(18),
|
|
61
|
+
},
|
|
62
|
+
title: {
|
|
63
|
+
fontSize: normalize(20),
|
|
64
|
+
marginBottom: 8,
|
|
65
|
+
},
|
|
66
|
+
header: {
|
|
67
|
+
fontSize: normalize(24),
|
|
68
|
+
marginBottom: 24,
|
|
69
|
+
},
|
|
70
|
+
display: {
|
|
71
|
+
fontSize: normalize(26),
|
|
72
|
+
},
|
|
73
|
+
hero: {
|
|
74
|
+
fontSize: normalize(30),
|
|
75
|
+
},
|
|
76
|
+
button: {
|
|
77
|
+
fontSize: normalize(16),
|
|
78
|
+
textAlign: 'center',
|
|
79
|
+
},
|
|
80
|
+
} as const;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ApiResponse<T> {
|
|
2
|
+
result: boolean;
|
|
3
|
+
requestId: string;
|
|
4
|
+
message: string;
|
|
5
|
+
messageLBL: string;
|
|
6
|
+
payload: T;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MetaLink {
|
|
10
|
+
url: string | null;
|
|
11
|
+
label: string;
|
|
12
|
+
active: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PaginationMeta {
|
|
16
|
+
current_page: number;
|
|
17
|
+
from: number;
|
|
18
|
+
last_page: number;
|
|
19
|
+
links: MetaLink[];
|
|
20
|
+
path: string;
|
|
21
|
+
per_page: number;
|
|
22
|
+
to: number;
|
|
23
|
+
total: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PaginatedPayload<T> {
|
|
27
|
+
data: T[];
|
|
28
|
+
meta: PaginationMeta;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ApiSuccess<T> = {
|
|
32
|
+
data: T;
|
|
33
|
+
message?: string;
|
|
34
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNavigationContainerRef,
|
|
3
|
+
NavigatorScreenParams,
|
|
4
|
+
type CompositeScreenProps,
|
|
5
|
+
} from '@react-navigation/native';
|
|
6
|
+
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
7
|
+
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Auth Stack - Unauthenticated screens
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export type AuthStackParamList = {
|
|
14
|
+
Login: undefined;
|
|
15
|
+
Signup: undefined;
|
|
16
|
+
ForgotPassword: undefined;
|
|
17
|
+
VerifyEmail: undefined;
|
|
18
|
+
ResetPassword: undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Main Tab Navigator - Bottom tabs for authenticated users
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
export type BottomTabParamList = {
|
|
26
|
+
Home: undefined;
|
|
27
|
+
Profile: undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// App Stack - Authenticated screens (including tabs)
|
|
32
|
+
// ============================================================================
|
|
33
|
+
|
|
34
|
+
export type AppStackParamList = {
|
|
35
|
+
MainTabs: undefined;
|
|
36
|
+
ProfileSetup: undefined;
|
|
37
|
+
BottomTabs: NavigatorScreenParams<BottomTabParamList>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Screen Props Types - For use in screen components
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* --------------------
|
|
46
|
+
* Auth Screens
|
|
47
|
+
* --------------------
|
|
48
|
+
* Used ONLY inside AuthStack screens
|
|
49
|
+
*/
|
|
50
|
+
export type AuthStackScreenProps<RouteName extends keyof AuthStackParamList> =
|
|
51
|
+
NativeStackScreenProps<AuthStackParamList, RouteName>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* --------------------
|
|
55
|
+
* App Stack Screens
|
|
56
|
+
* --------------------
|
|
57
|
+
* Used for non-tab authenticated screens
|
|
58
|
+
* (Settings, Help, About, etc.)
|
|
59
|
+
*/
|
|
60
|
+
export type AppStackScreenProps<RouteName extends keyof AppStackParamList> =
|
|
61
|
+
NativeStackScreenProps<AppStackParamList, RouteName>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* --------------------
|
|
65
|
+
* Tab Screens (Nested inside AppStack)
|
|
66
|
+
* --------------------
|
|
67
|
+
* These screens can navigate:
|
|
68
|
+
* - within tabs
|
|
69
|
+
* - to AppStack screens
|
|
70
|
+
*/
|
|
71
|
+
export type TabScreenProps<RouteName extends keyof BottomTabParamList> =
|
|
72
|
+
CompositeScreenProps<
|
|
73
|
+
BottomTabScreenProps<BottomTabParamList, RouteName>,
|
|
74
|
+
NativeStackScreenProps<AppStackParamList>
|
|
75
|
+
>;
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Global React Navigation typing
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// This tells React Navigation what the ROOT navigation param list is.
|
|
81
|
+
// It enables type-safe `useNavigation()` without generics
|
|
82
|
+
// and prevents navigating to invalid routes globally.
|
|
83
|
+
|
|
84
|
+
declare global {
|
|
85
|
+
namespace ReactNavigation {
|
|
86
|
+
interface RootParamList extends AppStackParamList {}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Navigation Ref - For navigating outside of components
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
export const navigationRef = createNavigationContainerRef<AppStackParamList>();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { logoutAction } from '@/redux/slice/auth.slice';
|
|
2
|
+
import { queryClient } from '@/services/query/queryClient';
|
|
3
|
+
import { persistor, store } from '@/store';
|
|
4
|
+
|
|
5
|
+
/** Clears Redux, React Query cache, and persisted storage. */
|
|
6
|
+
export const clearAppSession = async () => {
|
|
7
|
+
store.dispatch(logoutAction());
|
|
8
|
+
queryClient.clear();
|
|
9
|
+
await persistor.purge();
|
|
10
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a stored ISO date string into a Date for form fields.
|
|
3
|
+
*/
|
|
4
|
+
export const toDate = (iso: string | null | undefined): Date | undefined => {
|
|
5
|
+
if (!iso) return undefined;
|
|
6
|
+
const parsed = new Date(iso);
|
|
7
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert a form Date (or string) into an ISO string for storage / API.
|
|
12
|
+
*/
|
|
13
|
+
export const toIso = (
|
|
14
|
+
value: Date | string | null | undefined,
|
|
15
|
+
): string | null => {
|
|
16
|
+
if (!value) return null;
|
|
17
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
18
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
19
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
errorCodes,
|
|
3
|
+
isErrorWithCode,
|
|
4
|
+
keepLocalCopy,
|
|
5
|
+
pick,
|
|
6
|
+
types,
|
|
7
|
+
type DocumentPickerOptionsBase,
|
|
8
|
+
} from '@react-native-documents/picker';
|
|
9
|
+
import { ErrorHandler } from '@/services/errors';
|
|
10
|
+
|
|
11
|
+
export type PickedDocument = {
|
|
12
|
+
uri: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: string | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type PickDocumentResult = {
|
|
18
|
+
documents: PickedDocument[];
|
|
19
|
+
cancelled: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type PickDocumentOptions = {
|
|
23
|
+
multiple?: boolean;
|
|
24
|
+
/** MIME / UTI groups to allow. Defaults to images + common docs. */
|
|
25
|
+
type?: DocumentPickerOptionsBase['type'];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const DEFAULT_TYPES: NonNullable<DocumentPickerOptionsBase['type']> = [
|
|
29
|
+
types.images,
|
|
30
|
+
types.pdf,
|
|
31
|
+
types.doc,
|
|
32
|
+
types.docx,
|
|
33
|
+
types.plainText,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pick one or more files from the device (images, PDFs, docs, etc.).
|
|
38
|
+
*/
|
|
39
|
+
export const pickDocument = async (
|
|
40
|
+
options?: PickDocumentOptions,
|
|
41
|
+
): Promise<PickDocumentResult> => {
|
|
42
|
+
const multiple = options?.multiple ?? false;
|
|
43
|
+
const type = options?.type ?? DEFAULT_TYPES;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const results = await pick({
|
|
47
|
+
type,
|
|
48
|
+
allowMultiSelection: multiple,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!results.length) {
|
|
52
|
+
return { documents: [], cancelled: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const documents: PickedDocument[] = [];
|
|
56
|
+
|
|
57
|
+
for (const file of results) {
|
|
58
|
+
if (!file.uri) continue;
|
|
59
|
+
|
|
60
|
+
const fileName = file.name ?? 'document';
|
|
61
|
+
const [copyResult] = await keepLocalCopy({
|
|
62
|
+
files: [
|
|
63
|
+
{
|
|
64
|
+
uri: file.uri,
|
|
65
|
+
fileName,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
destination: 'cachesDirectory',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const localUri =
|
|
72
|
+
copyResult?.status === 'success' ? copyResult.localUri : file.uri;
|
|
73
|
+
|
|
74
|
+
documents.push({
|
|
75
|
+
uri: localUri,
|
|
76
|
+
name: fileName,
|
|
77
|
+
type: file.type ?? null,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
documents,
|
|
83
|
+
cancelled: documents.length === 0,
|
|
84
|
+
};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (
|
|
87
|
+
isErrorWithCode(error) &&
|
|
88
|
+
error.code === errorCodes.OPERATION_CANCELED
|
|
89
|
+
) {
|
|
90
|
+
return { documents: [], cancelled: true };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ErrorHandler(error);
|
|
94
|
+
return { documents: [], cancelled: false };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a display file name from an optional known name or a URI path segment.
|
|
3
|
+
*/
|
|
4
|
+
export const getFileNameFromUri = (
|
|
5
|
+
uri: string,
|
|
6
|
+
fallbackName?: string | null,
|
|
7
|
+
): string => {
|
|
8
|
+
if (fallbackName?.trim()) {
|
|
9
|
+
return fallbackName.trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const segment = uri.split('/').filter(Boolean).pop();
|
|
13
|
+
if (!segment) {
|
|
14
|
+
return 'file';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return decodeURIComponent(segment);
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns up to two initials from a display name.
|
|
3
|
+
*/
|
|
4
|
+
export const getInitials = (name?: string | null): string => {
|
|
5
|
+
if (!name?.trim()) return '';
|
|
6
|
+
|
|
7
|
+
const parts = name.trim().split(/\s+/);
|
|
8
|
+
if (parts.length === 1) {
|
|
9
|
+
return parts[0].slice(0, 2).toUpperCase();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return `${parts[0][0] ?? ''}${parts[1][0] ?? ''}`.toUpperCase();
|
|
13
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Platform,
|
|
3
|
+
StyleSheet,
|
|
4
|
+
type TextStyle,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
} from 'react-native';
|
|
7
|
+
import { normalize } from '@/utils/normalize';
|
|
8
|
+
|
|
9
|
+
/** Devanagari needs more vertical room than Latin for matras. */
|
|
10
|
+
const HINDI_LINE_HEIGHT_RATIO = 1.45;
|
|
11
|
+
|
|
12
|
+
export const isHindiLanguage = (lng?: string | null) =>
|
|
13
|
+
(lng ?? '').toLowerCase().startsWith('hi');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hindi-only text metrics. Apply after the base styles so other languages stay unchanged.
|
|
17
|
+
* Inflates tight lineHeights and restores Android font padding for matras.
|
|
18
|
+
*/
|
|
19
|
+
export const getHindiTextStyle = (
|
|
20
|
+
style?: StyleProp<TextStyle>,
|
|
21
|
+
): TextStyle | undefined => {
|
|
22
|
+
const flat = StyleSheet.flatten(style) ?? {};
|
|
23
|
+
const fontSize =
|
|
24
|
+
typeof flat.fontSize === 'number' ? flat.fontSize : normalize(14);
|
|
25
|
+
const minLineHeight = Math.ceil(fontSize * HINDI_LINE_HEIGHT_RATIO);
|
|
26
|
+
const currentLineHeight =
|
|
27
|
+
typeof flat.lineHeight === 'number' ? flat.lineHeight : fontSize;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
lineHeight: Math.max(currentLineHeight, minLineHeight),
|
|
31
|
+
...(Platform.OS === 'android' ? { includeFontPadding: true } : null),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import ImageResizer, { Response } from '@bam.tech/react-native-image-resizer';
|
|
2
|
+
import { isAndroid, isIOS } from '@/constants/device';
|
|
3
|
+
|
|
4
|
+
export const getCompressedImage = async ({
|
|
5
|
+
path,
|
|
6
|
+
maxWidth = 256,
|
|
7
|
+
maxHeight = 256,
|
|
8
|
+
quality = 80,
|
|
9
|
+
maxSize = 256,
|
|
10
|
+
}: {
|
|
11
|
+
path: string;
|
|
12
|
+
maxWidth?: number;
|
|
13
|
+
maxHeight?: number;
|
|
14
|
+
quality?: number;
|
|
15
|
+
maxSize?: number;
|
|
16
|
+
}): Promise<Response | null> => {
|
|
17
|
+
const getCalculatedSize = (width: number, height: number) => {
|
|
18
|
+
let ratio = maxSize / width;
|
|
19
|
+
let newWidth = maxSize;
|
|
20
|
+
let newHeight = height * ratio;
|
|
21
|
+
|
|
22
|
+
if (newHeight > maxSize) {
|
|
23
|
+
ratio = maxSize / height;
|
|
24
|
+
newHeight = maxSize;
|
|
25
|
+
newWidth = width * ratio;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { width: newWidth, height: newHeight };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let newSize = { width: maxWidth, height: maxHeight };
|
|
32
|
+
|
|
33
|
+
if (maxWidth > maxSize || maxHeight > maxSize) {
|
|
34
|
+
newSize = getCalculatedSize(maxWidth, maxHeight);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await ImageResizer.createResizedImage(
|
|
39
|
+
path,
|
|
40
|
+
newSize.width,
|
|
41
|
+
newSize.height,
|
|
42
|
+
'JPEG',
|
|
43
|
+
quality,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (result?.path) {
|
|
47
|
+
return {
|
|
48
|
+
...result,
|
|
49
|
+
path: isAndroid ? 'file://' + result.path : result.path,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.warn('Image compression error:', err);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type CompressedUploadFile = {
|
|
61
|
+
uri: string;
|
|
62
|
+
name: string;
|
|
63
|
+
type: 'image/jpeg';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Compress a local image and shape it for multipart FormData upload. */
|
|
67
|
+
export const getCompressedImageForUpload = async (
|
|
68
|
+
path: string,
|
|
69
|
+
options?: { maxSize?: number; quality?: number },
|
|
70
|
+
): Promise<CompressedUploadFile | null> => {
|
|
71
|
+
const maxSize = options?.maxSize ?? 256;
|
|
72
|
+
const quality = options?.quality ?? 80;
|
|
73
|
+
|
|
74
|
+
const compressed = await getCompressedImage({
|
|
75
|
+
path,
|
|
76
|
+
maxWidth: maxSize,
|
|
77
|
+
maxHeight: maxSize,
|
|
78
|
+
maxSize,
|
|
79
|
+
quality,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!compressed?.path) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const uri = compressed.path;
|
|
87
|
+
const rawName = uri.split('/').pop() || 'profile.jpg';
|
|
88
|
+
const name = rawName.includes('.')
|
|
89
|
+
? rawName.replace(/\.\w+$/, '.jpg')
|
|
90
|
+
: `${rawName}.jpg`;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
uri: isIOS ? uri.replace('file://', '') : uri,
|
|
94
|
+
name,
|
|
95
|
+
type: 'image/jpeg',
|
|
96
|
+
};
|
|
97
|
+
};
|