@fadyshawky/react-native-magic 2.2.0 → 2.3.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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -55
  3. package/index.js +4 -0
  4. package/package.json +9 -3
  5. package/scripts/askPackageName.js +10 -5
  6. package/template/.env.development +8 -6
  7. package/template/.env.example +15 -5
  8. package/template/.env.production +8 -6
  9. package/template/.env.staging +8 -6
  10. package/template/.eslintrc.js +14 -0
  11. package/template/.husky/pre-commit +1 -0
  12. package/template/App.tsx +47 -16
  13. package/template/__tests__/App.test.tsx +28 -10
  14. package/template/babel.config.js +20 -1
  15. package/template/docs/ARCHITECTURE.md +40 -10
  16. package/template/docs/BEST_PRACTICES.md +10 -1
  17. package/template/docs/CUSTOMIZATION.md +118 -5
  18. package/template/docs/design-system.html +1164 -0
  19. package/template/docs/wireframes.html +411 -0
  20. package/template/index.js +10 -0
  21. package/template/jest.config.js +16 -1
  22. package/template/jest.setup.js +61 -0
  23. package/template/package-lock.json +12178 -8293
  24. package/template/package.json +53 -19
  25. package/template/react-native.config.js +3 -0
  26. package/template/resources/fonts/.gitkeep +0 -0
  27. package/template/scripts/ci-sync-env.cjs +71 -0
  28. package/template/src/assets/brand/logo-mark.svg +8 -0
  29. package/template/src/assets/brand/logo-mono.svg +9 -0
  30. package/template/src/assets/brand/logo-primary.svg +15 -0
  31. package/template/src/assets/brand/wordmark-dark.svg +18 -0
  32. package/template/src/common/components/AppBottomSheet.tsx +87 -0
  33. package/template/src/common/components/AppSwitch.tsx +75 -0
  34. package/template/src/common/components/AppTextInput.tsx +161 -0
  35. package/template/src/common/components/Avatar.tsx +75 -0
  36. package/template/src/common/components/Badge.tsx +66 -0
  37. package/template/src/common/components/CardScroller.tsx +58 -0
  38. package/template/src/common/components/Cards.tsx +13 -7
  39. package/template/src/common/components/Carousel.tsx +196 -0
  40. package/template/src/common/components/Checkbox.tsx +85 -0
  41. package/template/src/common/components/Chip.tsx +55 -0
  42. package/template/src/common/components/Dropdown.tsx +202 -0
  43. package/template/src/common/components/ErrorBoundary.tsx +82 -0
  44. package/template/src/common/components/FlatListWrapper.tsx +8 -8
  45. package/template/src/common/components/ListItem.tsx +90 -0
  46. package/template/src/common/components/LoadingComponent.tsx +8 -2
  47. package/template/src/common/components/Logo.tsx +77 -0
  48. package/template/src/common/components/ModalDialog.tsx +141 -0
  49. package/template/src/common/components/NetworkBanner.tsx +47 -0
  50. package/template/src/common/components/OTPInput.tsx +0 -1
  51. package/template/src/common/components/PrimaryButton.tsx +0 -14
  52. package/template/src/common/components/PrimaryTextInput.tsx +66 -130
  53. package/template/src/common/components/RadioGroup.tsx +95 -0
  54. package/template/src/common/components/SafeText.tsx +4 -3
  55. package/template/src/common/components/SearchBar.tsx +7 -5
  56. package/template/src/common/components/SegmentedControl.tsx +77 -0
  57. package/template/src/common/components/Skeleton.tsx +47 -0
  58. package/template/src/common/components/TryAgain.tsx +4 -2
  59. package/template/src/common/helpers/arrayHelpers.ts +2 -2
  60. package/template/src/common/helpers/defaultKeyIdExtractor.ts +1 -1
  61. package/template/src/common/helpers/regexHelpers.ts +1 -2
  62. package/template/src/common/helpers/stringsHelpers.ts +0 -1
  63. package/template/src/common/hooks/useBackHandler.ts +5 -2
  64. package/template/src/common/hooks/useEventRegister.ts +1 -1
  65. package/template/src/common/hooks/useFlatListActions.ts +1 -1
  66. package/template/src/common/hooks/useWhyDidYouUpdate.ts +1 -1
  67. package/template/src/common/localization/LocalizationProvider.tsx +1 -1
  68. package/template/src/common/localization/RTLInitializer.tsx +1 -1
  69. package/template/src/common/localization/dateFormatter.ts +0 -1
  70. package/template/src/common/localization/intlFormatter.ts +0 -1
  71. package/template/src/common/localization/localization.ts +2 -2
  72. package/template/src/common/localization/translations/homeLocalization.ts +14 -0
  73. package/template/src/common/localization/translations/loginLocalization.ts +8 -0
  74. package/template/src/common/localization/translations/mainNavigationLocalization.ts +2 -0
  75. package/template/src/common/localization/translations/profileLocalization.ts +16 -0
  76. package/template/src/common/utils/index.tsx +0 -6
  77. package/template/src/common/validations/commonValidations.ts +2 -2
  78. package/template/src/core/api/errorHandler.ts +1 -1
  79. package/template/src/core/api/responseHandlers.ts +1 -3
  80. package/template/src/core/api/serverHeaders.ts +61 -12
  81. package/template/src/core/notifications/notificationAuth.ts +6 -0
  82. package/template/src/core/notifications/notificationService.ts +125 -0
  83. package/template/src/core/notifications/routeFromNotificationData.ts +32 -0
  84. package/template/src/core/store/categories/categoriesActions.ts +25 -0
  85. package/template/src/core/store/categories/categoriesSlice.ts +51 -0
  86. package/template/src/core/store/categories/categoriesState.ts +19 -0
  87. package/template/src/core/store/rootReducer.ts +2 -0
  88. package/template/src/core/store/store.tsx +6 -1
  89. package/template/src/core/store/user/userActions.ts +75 -14
  90. package/template/src/core/store/user/userSlice.ts +49 -26
  91. package/template/src/core/store/user/userState.ts +6 -4
  92. package/template/src/core/theme/ThemeProvider.tsx +5 -3
  93. package/template/src/core/theme/brand.ts +50 -0
  94. package/template/src/core/theme/colors.ts +113 -99
  95. package/template/src/core/theme/commonConsts.ts +2 -2
  96. package/template/src/core/theme/commonStyles.ts +1 -1
  97. package/template/src/core/theme/themes.ts +2 -0
  98. package/template/src/core/theme/types.ts +4 -2
  99. package/template/src/core/utils/stringUtils.ts +1 -1
  100. package/template/src/design-system/index.ts +2 -0
  101. package/template/src/design-system/tokens/brand.ts +6 -0
  102. package/template/src/design-system/tokens/index.ts +3 -0
  103. package/template/src/design-system/tokens/palette.ts +4 -0
  104. package/template/src/design-system/tokens/typography-spacing.ts +2 -0
  105. package/template/src/navigation/AuthStack.tsx +1 -4
  106. package/template/src/navigation/HeaderComponents.tsx +6 -3
  107. package/template/src/navigation/MainStack.tsx +18 -6
  108. package/template/src/navigation/RootNavigation.tsx +4 -7
  109. package/template/src/navigation/TabBar.tsx +7 -6
  110. package/template/src/navigation/types.ts +10 -31
  111. package/template/src/screens/Login/Login.tsx +47 -47
  112. package/template/src/screens/OTP/OTPScreen.tsx +6 -9
  113. package/template/src/screens/components/ComponentsScreen.tsx +301 -0
  114. package/template/src/screens/home/HomeScreen.tsx +143 -1
  115. package/template/src/screens/home/hooks/useHomeData.ts +19 -5
  116. package/template/src/screens/index.tsx +1 -0
  117. package/template/src/screens/profile/Profile.tsx +139 -2
  118. package/template/src/screens/splash/Splash.tsx +44 -11
  119. package/template/src/sheetManager/sheets.tsx +1 -1
  120. package/template/tsconfig.json +14 -2
  121. package/template/types/globals.d.ts +43 -0
  122. package/template/types/index.ts +2 -6
  123. package/template/types/modules.d.ts +9 -0
  124. package/template/types/react-native-config.d.ts +0 -2
  125. package/.vscode/settings.json +0 -8
  126. package/CHANGELOG.md +0 -119
  127. package/CODE_OF_CONDUCT.md +0 -83
  128. package/CONTRIBUTING.md +0 -60
  129. package/local.properties +0 -1
  130. package/template/src/common/components/ImageCropPickerButton.tsx +0 -107
  131. package/template/src/common/components/PhotoTakingButton.tsx +0 -94
  132. package/template/src/common/helpers/imageHelpers.ts +0 -5
  133. package/template/src/common/helpers/inAppReviewHelper.ts +0 -30
  134. package/template/src/common/helpers/orientationHelpers.ts +0 -25
  135. package/template/src/common/helpers/shareHelpers.ts +0 -47
  136. package/template/src/common/utils/FeesCaalculation.tsx +0 -37
  137. package/template/src/common/utils/printData.tsx +0 -161
  138. package/template/src/common/validations/examples/TextInputWithValidation.tsx +0 -229
@@ -0,0 +1,202 @@
1
+ import React, {useMemo, useState} from 'react';
2
+ import {
3
+ Modal,
4
+ Pressable,
5
+ ScrollView,
6
+ StyleSheet,
7
+ ViewStyle,
8
+ } from 'react-native';
9
+ import Svg, {Path} from 'react-native-svg';
10
+ import {useTheme} from '../../core/theme/ThemeProvider';
11
+ import {CommonSizes} from '../../core/theme/commonSizes';
12
+ import {RTLAwareText} from './RTLAwareText';
13
+ import {RTLAwareView} from './RTLAwareView';
14
+
15
+ interface DropdownOption {
16
+ label: string;
17
+ value: string;
18
+ }
19
+
20
+ interface DropdownProps {
21
+ label?: string;
22
+ value?: string;
23
+ placeholder?: string;
24
+ options: DropdownOption[];
25
+ onSelect: (value: string) => void;
26
+ error?: string | null;
27
+ }
28
+
29
+ function ChevronDown({color}: {color: string}): JSX.Element {
30
+ return (
31
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
32
+ <Path
33
+ d="M6 9L12 15L18 9"
34
+ stroke={color}
35
+ strokeWidth={2}
36
+ strokeLinecap="round"
37
+ strokeLinejoin="round"
38
+ />
39
+ </Svg>
40
+ );
41
+ }
42
+
43
+ function CheckIcon({color}: {color: string}): JSX.Element {
44
+ return (
45
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
46
+ <Path
47
+ d="M5 12.5L10 17.5L19 7"
48
+ stroke={color}
49
+ strokeWidth={2.2}
50
+ strokeLinecap="round"
51
+ strokeLinejoin="round"
52
+ />
53
+ </Svg>
54
+ );
55
+ }
56
+
57
+ export function Dropdown(props: DropdownProps): JSX.Element {
58
+ const {label, value, placeholder, options, onSelect, error} = props;
59
+ const {theme} = useTheme();
60
+ const [open, setOpen] = useState(false);
61
+
62
+ const selected = useMemo(
63
+ () => options.find(option => option.value === value),
64
+ [options, value],
65
+ );
66
+
67
+ const valueColorStyle = {
68
+ color: selected
69
+ ? theme.colors.grayScale_700
70
+ : theme.colors.grayScale_200,
71
+ };
72
+
73
+ const fieldStyle: ViewStyle = {
74
+ flexDirection: 'row',
75
+ alignItems: 'center',
76
+ justifyContent: 'space-between',
77
+ backgroundColor: theme.colors.grayScale_0,
78
+ borderColor: error ? theme.colors.error_400 : theme.colors.grayScale_50,
79
+ borderWidth: CommonSizes.borderWidth.medium,
80
+ borderRadius: CommonSizes.borderRadius.large,
81
+ paddingHorizontal: CommonSizes.spacing.xLarge,
82
+ paddingVertical: CommonSizes.spacing.xLarge,
83
+ };
84
+
85
+ return (
86
+ <RTLAwareView style={styles.container}>
87
+ {label ? (
88
+ <RTLAwareText
89
+ style={[theme.text.bodyMediumBold, {color: theme.colors.grayScale_700}]}>
90
+ {label}
91
+ </RTLAwareText>
92
+ ) : null}
93
+
94
+ <Pressable
95
+ onPress={() => setOpen(true)}
96
+ accessibilityRole="button"
97
+ accessibilityLabel={label ?? placeholder ?? 'Dropdown'}>
98
+ <RTLAwareView style={fieldStyle}>
99
+ <RTLAwareText
100
+ style={[
101
+ theme.text.bodyLargeRegular,
102
+ styles.flex1,
103
+ valueColorStyle,
104
+ ]}>
105
+ {selected ? selected.label : placeholder ?? ''}
106
+ </RTLAwareText>
107
+ <ChevronDown color={theme.colors.grayScale_200} />
108
+ </RTLAwareView>
109
+ </Pressable>
110
+
111
+ {error ? (
112
+ <RTLAwareText
113
+ style={[theme.text.bodySmallRegular, {color: theme.colors.error_400}]}>
114
+ {error}
115
+ </RTLAwareText>
116
+ ) : null}
117
+
118
+ <Modal transparent visible={open} animationType="fade">
119
+ <Pressable
120
+ style={styles.backdrop}
121
+ onPress={() => setOpen(false)}
122
+ accessibilityRole="button"
123
+ accessibilityLabel="Close dropdown">
124
+ <Pressable
125
+ style={[
126
+ styles.card,
127
+ {backgroundColor: theme.colors.grayScale_0},
128
+ ]}
129
+ onPress={() => {}}>
130
+ <ScrollView bounces={false}>
131
+ {options.map(option => {
132
+ const isSelected = option.value === value;
133
+ const optionColorStyle = {
134
+ color: isSelected
135
+ ? theme.colors.PlatinateBlue_400
136
+ : theme.colors.grayScale_700,
137
+ };
138
+ return (
139
+ <Pressable
140
+ key={option.value}
141
+ onPress={() => {
142
+ onSelect(option.value);
143
+ setOpen(false);
144
+ }}
145
+ accessibilityRole="button"
146
+ style={styles.optionRow}>
147
+ <RTLAwareView style={styles.optionInner}>
148
+ <RTLAwareText
149
+ style={[
150
+ theme.text.bodyLargeRegular,
151
+ styles.flex1,
152
+ optionColorStyle,
153
+ ]}>
154
+ {option.label}
155
+ </RTLAwareText>
156
+ {isSelected ? (
157
+ <CheckIcon color={theme.colors.PlatinateBlue_400} />
158
+ ) : null}
159
+ </RTLAwareView>
160
+ </Pressable>
161
+ );
162
+ })}
163
+ </ScrollView>
164
+ </Pressable>
165
+ </Pressable>
166
+ </Modal>
167
+ </RTLAwareView>
168
+ );
169
+ }
170
+
171
+ const styles = StyleSheet.create({
172
+ container: {
173
+ width: '100%',
174
+ flexDirection: 'column',
175
+ gap: CommonSizes.spacing.medium,
176
+ } as ViewStyle,
177
+ backdrop: {
178
+ flex: 1,
179
+ backgroundColor: 'rgba(6, 8, 15, 0.55)',
180
+ alignItems: 'center',
181
+ justifyContent: 'center',
182
+ padding: CommonSizes.spacing.xxLarge,
183
+ },
184
+ card: {
185
+ width: '100%',
186
+ maxHeight: '60%',
187
+ borderRadius: CommonSizes.borderRadius.large,
188
+ overflow: 'hidden',
189
+ paddingVertical: CommonSizes.spacing.small,
190
+ },
191
+ optionRow: {
192
+ paddingHorizontal: CommonSizes.spacing.xLarge,
193
+ paddingVertical: CommonSizes.spacing.xLarge,
194
+ },
195
+ optionInner: {
196
+ flexDirection: 'row',
197
+ alignItems: 'center',
198
+ },
199
+ flex1: {
200
+ flex: 1,
201
+ },
202
+ });
@@ -0,0 +1,82 @@
1
+ import React from 'react';
2
+ import {StyleSheet, Text, TouchableOpacity, View} from 'react-native';
3
+ import {NaturalColors, PrimaryColors} from '../../core/theme/colors';
4
+ import {Fonts} from '../../core/theme/fonts';
5
+ import {CommonSizes} from '../../core/theme/commonSizes';
6
+
7
+ interface Props {
8
+ children: React.ReactNode;
9
+ fallback?: (reset: () => void, error: Error) => React.ReactNode;
10
+ onError?: (error: Error, info: React.ErrorInfo) => void;
11
+ }
12
+
13
+ interface State {
14
+ error: Error | null;
15
+ }
16
+
17
+ export class ErrorBoundary extends React.Component<Props, State> {
18
+ state: State = {error: null};
19
+
20
+ static getDerivedStateFromError(error: Error): State {
21
+ return {error};
22
+ }
23
+
24
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
25
+ if (__DEV__) {
26
+ console.error('[ErrorBoundary]', error, info.componentStack);
27
+ }
28
+ this.props.onError?.(error, info);
29
+ }
30
+
31
+ reset = () => this.setState({error: null});
32
+
33
+ render() {
34
+ if (!this.state.error) return this.props.children;
35
+ if (this.props.fallback) {
36
+ return this.props.fallback(this.reset, this.state.error);
37
+ }
38
+ return (
39
+ <View style={styles.root}>
40
+ <Text style={styles.title}>Something went wrong</Text>
41
+ <Text style={styles.message} numberOfLines={6}>
42
+ {this.state.error.message}
43
+ </Text>
44
+ <TouchableOpacity style={styles.button} onPress={this.reset}>
45
+ <Text style={styles.buttonText}>Try again</Text>
46
+ </TouchableOpacity>
47
+ </View>
48
+ );
49
+ }
50
+ }
51
+
52
+ const styles = StyleSheet.create({
53
+ root: {
54
+ flex: 1,
55
+ alignItems: 'center',
56
+ justifyContent: 'center',
57
+ padding: 24,
58
+ backgroundColor: NaturalColors.background_2,
59
+ },
60
+ title: {
61
+ fontFamily: Fonts.bold,
62
+ fontSize: CommonSizes.font.bodyXLarge,
63
+ marginBottom: 12,
64
+ },
65
+ message: {
66
+ fontFamily: Fonts.regular,
67
+ fontSize: CommonSizes.font.bodyMedium,
68
+ textAlign: 'center',
69
+ marginBottom: 24,
70
+ },
71
+ button: {
72
+ backgroundColor: PrimaryColors.PlatinateBlue_400,
73
+ paddingVertical: 12,
74
+ paddingHorizontal: 24,
75
+ borderRadius: 8,
76
+ },
77
+ buttonText: {
78
+ color: '#fff',
79
+ fontFamily: Fonts.bold,
80
+ fontSize: CommonSizes.font.bodyMedium,
81
+ },
82
+ });
@@ -1,5 +1,5 @@
1
- import React, {FC, useMemo} from 'react';
2
- import {FlatList, FlatListProps} from 'react-native';
1
+ import React, {useMemo} from 'react';
2
+ import {FlashList, FlashListProps} from '@shopify/flash-list';
3
3
  import {LoadState} from '../../../types';
4
4
  import {TryAgain} from './TryAgain';
5
5
  import {Separator} from './Separator';
@@ -9,12 +9,13 @@ import {localization} from '../localization/localization';
9
9
  import {defaultKeyIdExtractor} from '../helpers/defaultKeyIdExtractor';
10
10
  import {CommonStyles} from '../../core/theme/commonStyles';
11
11
 
12
- interface IProps extends FlatListProps<any> {
12
+ interface IProps extends FlashListProps<any> {
13
13
  loadState: LoadState;
14
14
  tryAgain?: () => void;
15
15
  error?: string | null;
16
16
  }
17
17
 
18
+ // Shared FlashList defaults. Tune estimatedItemSize per-screen for long/heavy lists.
18
19
  const FlatListWrapperProps = {
19
20
  keyExtractor: defaultKeyIdExtractor,
20
21
  ListEmptyComponent: (
@@ -34,7 +35,7 @@ export function FlatListWrapper({
34
35
  ...props
35
36
  }: IProps) {
36
37
  const ListEmptyComponent = useMemo(() => {
37
- if (loadState == LoadState.error) {
38
+ if (loadState === LoadState.error) {
38
39
  return (
39
40
  <TryAgain
40
41
  onPress={tryAgain}
@@ -47,20 +48,19 @@ export function FlatListWrapper({
47
48
  }, [loadState, props.ListEmptyComponent, error, tryAgain]);
48
49
 
49
50
  const refreshing = useMemo(() => {
50
- return loadState == LoadState.pullToRefresh;
51
+ return loadState === LoadState.pullToRefresh;
51
52
  }, [loadState]);
52
53
 
53
- if (loadState == LoadState.firstLoad) {
54
+ if (loadState === LoadState.firstLoad) {
54
55
  return <LoadingComponent />;
55
56
  } else {
56
57
  return (
57
- <FlatList
58
+ <FlashList
58
59
  contentContainerStyle={CommonStyles.listContentContainer}
59
60
  {...FlatListWrapperProps}
60
61
  {...props}
61
62
  refreshing={refreshing}
62
63
  ListEmptyComponent={ListEmptyComponent}
63
- removeClippedSubviews={true}
64
64
  />
65
65
  );
66
66
  }
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import {Pressable, StyleSheet, View} from 'react-native';
3
+ import Svg, {Path} from 'react-native-svg';
4
+ import {CommonSizes} from '../../core/theme/commonSizes';
5
+ import {useTheme} from '../../core/theme/ThemeProvider';
6
+ import {RTLAwareText} from './RTLAwareText';
7
+ import {RTLAwareView} from './RTLAwareView';
8
+
9
+ interface ListItemProps {
10
+ title: string;
11
+ subtitle?: string;
12
+ left?: React.ReactNode;
13
+ right?: React.ReactNode;
14
+ onPress?: () => void;
15
+ showChevron?: boolean;
16
+ }
17
+
18
+ /**
19
+ * A standard list row: an optional `left` slot, a title + optional muted
20
+ * subtitle, then a `right` slot or a trailing chevron. Becomes tappable when
21
+ * `onPress` is supplied.
22
+ */
23
+ export function ListItem({
24
+ title,
25
+ subtitle,
26
+ left,
27
+ right,
28
+ onPress,
29
+ showChevron,
30
+ }: ListItemProps): JSX.Element {
31
+ const {theme} = useTheme();
32
+
33
+ const trailing =
34
+ right != null ? (
35
+ right
36
+ ) : showChevron ? (
37
+ <Chevron color={theme.colors.grayScale_200} />
38
+ ) : null;
39
+
40
+ const content = (
41
+ <RTLAwareView style={styles.row}>
42
+ {left != null ? <View>{left}</View> : null}
43
+ <View style={styles.textColumn}>
44
+ <RTLAwareText style={theme.text.bodyLargeBold}>{title}</RTLAwareText>
45
+ {subtitle != null ? (
46
+ <RTLAwareText
47
+ style={[
48
+ theme.text.bodySmallRegular,
49
+ {color: theme.colors.grayScale_200},
50
+ ]}>
51
+ {subtitle}
52
+ </RTLAwareText>
53
+ ) : null}
54
+ </View>
55
+ {trailing != null ? <View>{trailing}</View> : null}
56
+ </RTLAwareView>
57
+ );
58
+
59
+ if (onPress) {
60
+ return <Pressable onPress={onPress}>{content}</Pressable>;
61
+ }
62
+
63
+ return content;
64
+ }
65
+
66
+ function Chevron({color}: {color: string}): JSX.Element {
67
+ return (
68
+ <Svg width={20} height={20} viewBox="0 0 24 24" fill="none">
69
+ <Path
70
+ d="M9 6l6 6-6 6"
71
+ stroke={color}
72
+ strokeWidth={2}
73
+ strokeLinecap="round"
74
+ strokeLinejoin="round"
75
+ />
76
+ </Svg>
77
+ );
78
+ }
79
+
80
+ const styles = StyleSheet.create({
81
+ row: {
82
+ flexDirection: 'row',
83
+ alignItems: 'center',
84
+ gap: CommonSizes.spacing.large,
85
+ paddingVertical: CommonSizes.spacing.large,
86
+ },
87
+ textColumn: {
88
+ flex: 1,
89
+ },
90
+ });
@@ -1,11 +1,17 @@
1
1
  import React from 'react';
2
- import {Image, View} from 'react-native';
2
+ import {Image, StyleSheet, View} from 'react-native';
3
3
  import {CommonStyles} from '../../core/theme/commonStyles';
4
4
 
5
5
  export const LoadingComponent = () => {
6
6
  return (
7
7
  <View style={CommonStyles.flexCenter}>
8
- <Image resizeMode="cover" style={{flex: 1}} source={0} />
8
+ <Image resizeMode="cover" style={styles.image} source={0} />
9
9
  </View>
10
10
  );
11
11
  };
12
+
13
+ const styles = StyleSheet.create({
14
+ image: {
15
+ flex: 1,
16
+ },
17
+ });
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import Svg, {Defs, LinearGradient, Path, Rect, Stop} from 'react-native-svg';
3
+
4
+ /**
5
+ * Fady Shawky "FS" brand mark — a bold filled F beside a clean S, in a rounded
6
+ * chip. Premium "Midnight" black + blue treatment. Pure geometry (filled rects
7
+ * for the F, one stroked path for the S), so it renders identically in
8
+ * react-native-svg and plain SVG and stays legible at small sizes.
9
+ *
10
+ * variants:
11
+ * - 'gradient' (default): blue-black chip, blue mark — the primary logo
12
+ * - 'mono': dark chip, white mark — for dark UI
13
+ * - 'light': near-white chip, ink mark — for light UI
14
+ * - 'mark': no chip, blue mark — inline / nav usage
15
+ */
16
+ export type LogoVariant = 'gradient' | 'mono' | 'light' | 'mark';
17
+
18
+ interface LogoProps {
19
+ size?: number;
20
+ variant?: LogoVariant;
21
+ }
22
+
23
+ const S_PATH =
24
+ 'M101 42 C101 32 91 28 83 28 C73 28 67 35 67 44 C67 52 75 56 84 59 C93 62 101 66 101 76 C101 86 91 91 83 91 C73 91 67 85 66 77';
25
+
26
+ function chipFill(variant: LogoVariant): string | undefined {
27
+ switch (variant) {
28
+ case 'gradient':
29
+ return 'url(#fsChip)';
30
+ case 'mono':
31
+ return '#0D1124';
32
+ case 'light':
33
+ return '#F4F6FE';
34
+ case 'mark':
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ function markColor(variant: LogoVariant): string {
40
+ switch (variant) {
41
+ case 'gradient':
42
+ return '#6BA0FF';
43
+ case 'mono':
44
+ return '#FFFFFF';
45
+ case 'light':
46
+ return '#0A1230';
47
+ case 'mark':
48
+ return '#2F6BFF';
49
+ }
50
+ }
51
+
52
+ export function Logo({size = 96, variant = 'gradient'}: LogoProps) {
53
+ const fill = chipFill(variant);
54
+ const c = markColor(variant);
55
+ return (
56
+ <Svg width={size} height={size} viewBox="0 0 120 120" fill="none">
57
+ <Defs>
58
+ <LinearGradient id="fsChip" x1="0" y1="0" x2="1" y2="1">
59
+ <Stop offset="0" stopColor="#0A1230" />
60
+ <Stop offset="1" stopColor="#1B45B8" />
61
+ </LinearGradient>
62
+ </Defs>
63
+ {fill ? <Rect x="0" y="0" width="120" height="120" rx="30" fill={fill} /> : null}
64
+ <Rect x="25" y="24" width="13" height="72" rx="3" fill={c} />
65
+ <Rect x="25" y="24" width="33" height="13" rx="3" fill={c} />
66
+ <Rect x="25" y="52" width="26" height="12" rx="3" fill={c} />
67
+ <Path
68
+ d={S_PATH}
69
+ stroke={c}
70
+ strokeWidth={13}
71
+ strokeLinecap="round"
72
+ strokeLinejoin="round"
73
+ fill="none"
74
+ />
75
+ </Svg>
76
+ );
77
+ }
@@ -0,0 +1,141 @@
1
+ import React from 'react';
2
+ import {Modal, Pressable, StyleSheet, View, ViewStyle} from 'react-native';
3
+ import {ButtonType} from '../../../types';
4
+ import {useTheme} from '../../core/theme/ThemeProvider';
5
+ import {CommonSizes} from '../../core/theme/commonSizes';
6
+ import {PrimaryButton} from './PrimaryButton';
7
+ import {RTLAwareText} from './RTLAwareText';
8
+
9
+ type DialogActionVariant = 'primary' | 'ghost' | 'destructive';
10
+
11
+ interface DialogAction {
12
+ label: string;
13
+ onPress: () => void;
14
+ variant?: DialogActionVariant;
15
+ }
16
+
17
+ interface ModalDialogProps {
18
+ visible: boolean;
19
+ onClose: () => void;
20
+ title?: string;
21
+ message?: string;
22
+ actions?: DialogAction[];
23
+ }
24
+
25
+ function buttonTypeForVariant(variant?: DialogActionVariant): ButtonType {
26
+ switch (variant) {
27
+ case 'destructive':
28
+ return ButtonType.outlineNegative;
29
+ case 'ghost':
30
+ return ButtonType.borderless;
31
+ case 'primary':
32
+ default:
33
+ return ButtonType.solid;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * A centered alert/confirm dialog built on react-native's Modal.
39
+ * Fades in over a dimmed midnight backdrop; tapping the backdrop closes it.
40
+ * Falls back to a single "OK" button when no actions are supplied.
41
+ */
42
+ export function ModalDialog({
43
+ visible,
44
+ onClose,
45
+ title,
46
+ message,
47
+ actions,
48
+ }: ModalDialogProps): JSX.Element {
49
+ const {theme} = useTheme();
50
+
51
+ const cardStyle: ViewStyle = {
52
+ backgroundColor: theme.colors.grayScale_0,
53
+ borderRadius: CommonSizes.borderRadius.large,
54
+ padding: CommonSizes.spacing.xLarge,
55
+ maxWidth: 340,
56
+ width: '100%',
57
+ alignSelf: 'center',
58
+ };
59
+
60
+ return (
61
+ <Modal
62
+ transparent
63
+ visible={visible}
64
+ animationType="fade"
65
+ onRequestClose={onClose}>
66
+ <View style={styles.container}>
67
+ <Pressable
68
+ style={styles.backdrop}
69
+ onPress={onClose}
70
+ accessibilityRole="button"
71
+ accessibilityLabel="Close"
72
+ />
73
+ <Pressable style={cardStyle} onPress={event => event.stopPropagation()}>
74
+ {title ? (
75
+ <RTLAwareText style={[theme.text.header4, styles.title]}>
76
+ {title}
77
+ </RTLAwareText>
78
+ ) : null}
79
+ {message ? (
80
+ <RTLAwareText
81
+ style={[
82
+ theme.text.bodyMediumRegular,
83
+ styles.message,
84
+ {color: theme.colors.grayScale_200},
85
+ ]}>
86
+ {message}
87
+ </RTLAwareText>
88
+ ) : null}
89
+ <View style={styles.actions}>
90
+ {actions && actions.length > 0 ? (
91
+ actions.map((action, index) => (
92
+ <View
93
+ key={`${action.label}-${index}`}
94
+ style={
95
+ index > 0 ? styles.actionSpacing : undefined
96
+ }>
97
+ <PrimaryButton
98
+ label={action.label}
99
+ type={buttonTypeForVariant(action.variant)}
100
+ onPress={action.onPress}
101
+ />
102
+ </View>
103
+ ))
104
+ ) : (
105
+ <PrimaryButton
106
+ label="OK"
107
+ type={ButtonType.solid}
108
+ onPress={onClose}
109
+ />
110
+ )}
111
+ </View>
112
+ </Pressable>
113
+ </View>
114
+ </Modal>
115
+ );
116
+ }
117
+
118
+ const styles = StyleSheet.create({
119
+ container: {
120
+ flex: 1,
121
+ justifyContent: 'center',
122
+ alignItems: 'center',
123
+ paddingHorizontal: CommonSizes.spacing.xLarge,
124
+ },
125
+ backdrop: {
126
+ ...StyleSheet.absoluteFill,
127
+ backgroundColor: 'rgba(6,8,15,0.6)',
128
+ },
129
+ title: {
130
+ marginBottom: CommonSizes.spacing.medium,
131
+ },
132
+ message: {
133
+ marginBottom: CommonSizes.spacing.large,
134
+ },
135
+ actions: {
136
+ marginTop: CommonSizes.spacing.medium,
137
+ },
138
+ actionSpacing: {
139
+ marginTop: CommonSizes.spacing.medium,
140
+ },
141
+ });