@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,301 @@
1
+ import React, {useState} from 'react';
2
+ import {StyleSheet, TextStyle, View, ViewStyle} from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
4
+ import {ButtonType} from '../../../types';
5
+ import {AppBottomSheet} from '../../common/components/AppBottomSheet';
6
+ import {AppSwitch} from '../../common/components/AppSwitch';
7
+ import {AppTextInput} from '../../common/components/AppTextInput';
8
+ import {Avatar} from '../../common/components/Avatar';
9
+ import {Badge} from '../../common/components/Badge';
10
+ import {CardScroller} from '../../common/components/CardScroller';
11
+ import {Carousel} from '../../common/components/Carousel';
12
+ import {Checkbox} from '../../common/components/Checkbox';
13
+ import {Chip} from '../../common/components/Chip';
14
+ import {Container} from '../../common/components/Container';
15
+ import {Dropdown} from '../../common/components/Dropdown';
16
+ import {ListItem} from '../../common/components/ListItem';
17
+ import {ModalDialog} from '../../common/components/ModalDialog';
18
+ import {PrimaryButton} from '../../common/components/PrimaryButton';
19
+ import {RadioGroup} from '../../common/components/RadioGroup';
20
+ import {RTLAwareText} from '../../common/components/RTLAwareText';
21
+ import {RTLAwareView} from '../../common/components/RTLAwareView';
22
+ import {SegmentedControl} from '../../common/components/SegmentedControl';
23
+ import {Skeleton} from '../../common/components/Skeleton';
24
+ import {BrandGradients, GradientDirection} from '../../core/theme/brand';
25
+ import {CommonSizes} from '../../core/theme/commonSizes';
26
+ import {useTheme} from '../../core/theme/ThemeProvider';
27
+
28
+ const SLIDES = [
29
+ {title: 'Build fast', sub: 'Scaffolded for you'},
30
+ {title: 'Stay consistent', sub: 'One design system'},
31
+ {title: 'Ship anywhere', sub: 'iOS · Android'},
32
+ ];
33
+ const CARDS = [
34
+ {title: 'Getting started', sub: 'Set up your env'},
35
+ {title: 'Theming', sub: 'Tokens & gradients'},
36
+ {title: 'Navigation', sub: 'Token-gated stacks'},
37
+ ];
38
+
39
+ const Section = ({
40
+ title,
41
+ children,
42
+ cardStyle,
43
+ titleStyle,
44
+ }: {
45
+ title: string;
46
+ children: React.ReactNode;
47
+ cardStyle: ViewStyle | ViewStyle[];
48
+ titleStyle: TextStyle | TextStyle[];
49
+ }) => (
50
+ <View style={styles.section}>
51
+ <RTLAwareText style={titleStyle}>{title}</RTLAwareText>
52
+ <RTLAwareView style={cardStyle}>{children}</RTLAwareView>
53
+ </View>
54
+ );
55
+
56
+ export function ComponentsScreen(): JSX.Element {
57
+ const {theme} = useTheme();
58
+ const [name, setName] = useState('');
59
+ const [pass, setPass] = useState('');
60
+ const [notes, setNotes] = useState('');
61
+ const [country, setCountry] = useState<string | undefined>();
62
+ const [agree, setAgree] = useState(false);
63
+ const [notify, setNotify] = useState(true);
64
+ const [plan, setPlan] = useState('a');
65
+ const [seg, setSeg] = useState(0);
66
+ const [chip, setChip] = useState('all');
67
+ const [sheet, setSheet] = useState(false);
68
+ const [dialog, setDialog] = useState(false);
69
+
70
+ const cardStyle = [
71
+ styles.card,
72
+ {
73
+ backgroundColor: theme.colors.grayScale_0,
74
+ borderColor: theme.colors.grayScale_50,
75
+ },
76
+ ];
77
+ const titleStyle = [theme.text.bodyXLargeBold, styles.h];
78
+
79
+ return (
80
+ <Container
81
+ testID={'ComponentsScreenID'}
82
+ backgroundImage={0}
83
+ backgroundColor={theme.colors.background_2}
84
+ contentContainerStyle={styles.content}>
85
+ <RTLAwareText style={theme.text.header3}>Components</RTLAwareText>
86
+
87
+ <Section title="Text inputs" cardStyle={cardStyle} titleStyle={titleStyle}>
88
+ <AppTextInput label="Name" value={name} onChangeText={setName} placeholder="Your name" />
89
+ <AppTextInput
90
+ label="Password"
91
+ value={pass}
92
+ onChangeText={setPass}
93
+ placeholder="••••••••"
94
+ secureTextEntry
95
+ />
96
+ <AppTextInput
97
+ label="Notes"
98
+ value={notes}
99
+ onChangeText={setNotes}
100
+ placeholder="Write something…"
101
+ multiline
102
+ />
103
+ <Dropdown
104
+ label="Country"
105
+ value={country}
106
+ placeholder="Select a country"
107
+ options={[
108
+ {label: 'Egypt', value: 'eg'},
109
+ {label: 'United Arab Emirates', value: 'ae'},
110
+ {label: 'Saudi Arabia', value: 'sa'},
111
+ ]}
112
+ onSelect={setCountry}
113
+ />
114
+ </Section>
115
+
116
+ <Section title="Toggles" cardStyle={cardStyle} titleStyle={titleStyle}>
117
+ <Checkbox checked={agree} onChange={setAgree} label="I agree to the terms" />
118
+ <RTLAwareView style={styles.row}>
119
+ <RTLAwareText style={theme.text.bodyLargeRegular}>Notifications</RTLAwareText>
120
+ <AppSwitch value={notify} onValueChange={setNotify} />
121
+ </RTLAwareView>
122
+ <RadioGroup
123
+ value={plan}
124
+ options={[
125
+ {label: 'Starter', value: 'a'},
126
+ {label: 'Pro', value: 'b'},
127
+ {label: 'Team', value: 'c'},
128
+ ]}
129
+ onChange={setPlan}
130
+ />
131
+ </Section>
132
+
133
+ <Section
134
+ title="Selection & display"
135
+ cardStyle={cardStyle}
136
+ titleStyle={titleStyle}>
137
+ <SegmentedControl segments={['All', 'Active', 'Done']} index={seg} onChange={setSeg} />
138
+ <RTLAwareView style={styles.chips}>
139
+ {['all', 'design', 'code', 'launch'].map(c => (
140
+ <Chip key={c} label={c} selected={chip === c} onPress={() => setChip(c)} />
141
+ ))}
142
+ </RTLAwareView>
143
+ <RTLAwareView style={styles.row}>
144
+ <Badge label="New" variant="primary" />
145
+ <Badge label="Live" variant="success" />
146
+ <Badge label="Error" variant="error" />
147
+ <Badge count={128} variant="neutral" />
148
+ </RTLAwareView>
149
+ <RTLAwareView style={styles.row}>
150
+ <Avatar name="Fady Shawky" />
151
+ <Avatar name="Alex Doe" size={36} />
152
+ <Avatar size={36} />
153
+ </RTLAwareView>
154
+ </Section>
155
+
156
+ <Section title="Carousel" cardStyle={cardStyle} titleStyle={titleStyle}>
157
+ <Carousel
158
+ data={SLIDES}
159
+ height={130}
160
+ renderItem={({item}) => (
161
+ <LinearGradient
162
+ colors={BrandGradients.primary}
163
+ start={GradientDirection.start}
164
+ end={GradientDirection.end}
165
+ style={styles.slide}>
166
+ <RTLAwareText style={[theme.text.header4, styles.onGradientText]}>
167
+ {item.title}
168
+ </RTLAwareText>
169
+ <RTLAwareText
170
+ style={[theme.text.bodyMediumRegular, styles.onGradientText]}>
171
+ {item.sub}
172
+ </RTLAwareText>
173
+ </LinearGradient>
174
+ )}
175
+ />
176
+ </Section>
177
+
178
+ <Section title="Card scroller" cardStyle={cardStyle} titleStyle={titleStyle}>
179
+ <CardScroller
180
+ data={CARDS}
181
+ cardWidth={220}
182
+ renderItem={({item}) => (
183
+ <View
184
+ style={[
185
+ styles.scard,
186
+ {
187
+ backgroundColor: theme.colors.background_2,
188
+ borderColor: theme.colors.grayScale_50,
189
+ },
190
+ ]}>
191
+ <RTLAwareText style={theme.text.bodyLargeBold}>{item.title}</RTLAwareText>
192
+ <RTLAwareText
193
+ style={{...theme.text.bodySmallRegular, color: theme.colors.grayScale_200}}>
194
+ {item.sub}
195
+ </RTLAwareText>
196
+ </View>
197
+ )}
198
+ />
199
+ </Section>
200
+
201
+ <Section title="List rows" cardStyle={cardStyle} titleStyle={titleStyle}>
202
+ <ListItem
203
+ title="Profile"
204
+ subtitle="Edit your details"
205
+ left={<Avatar name="Fady Shawky" size={36} />}
206
+ showChevron
207
+ onPress={() => {}}
208
+ />
209
+ <ListItem title="Notifications" right={<AppSwitch value={notify} onValueChange={setNotify} />} />
210
+ <ListItem title="Language" subtitle="English" showChevron onPress={() => {}} />
211
+ </Section>
212
+
213
+ <Section
214
+ title="Overlays & loading"
215
+ cardStyle={cardStyle}
216
+ titleStyle={titleStyle}>
217
+ <PrimaryButton
218
+ label="Open bottom sheet"
219
+ type={ButtonType.solid}
220
+ onPressIn={() => setSheet(true)}
221
+ />
222
+ <PrimaryButton
223
+ label="Open dialog"
224
+ type={ButtonType.outline}
225
+ onPressIn={() => setDialog(true)}
226
+ />
227
+ <View style={styles.skeletons}>
228
+ <Skeleton height={16} />
229
+ <Skeleton height={16} width="70%" />
230
+ <Skeleton height={80} radius={CommonSizes.borderRadius.large} />
231
+ </View>
232
+ </Section>
233
+
234
+ <AppBottomSheet visible={sheet} onClose={() => setSheet(false)} title="Choose a plan">
235
+ <RadioGroup
236
+ value={plan}
237
+ options={[
238
+ {label: 'Starter', value: 'a'},
239
+ {label: 'Pro', value: 'b'},
240
+ {label: 'Team', value: 'c'},
241
+ ]}
242
+ onChange={setPlan}
243
+ />
244
+ <PrimaryButton label="Done" type={ButtonType.solid} onPressIn={() => setSheet(false)} />
245
+ </AppBottomSheet>
246
+
247
+ <ModalDialog
248
+ visible={dialog}
249
+ onClose={() => setDialog(false)}
250
+ title="Delete item?"
251
+ message="This action cannot be undone."
252
+ actions={[
253
+ {label: 'Cancel', onPress: () => setDialog(false), variant: 'ghost'},
254
+ {label: 'Delete', onPress: () => setDialog(false), variant: 'destructive'},
255
+ ]}
256
+ />
257
+ </Container>
258
+ );
259
+ }
260
+
261
+ const styles = StyleSheet.create({
262
+ content: {
263
+ paddingHorizontal: CommonSizes.spacing.large,
264
+ paddingTop: CommonSizes.spacing.large,
265
+ paddingBottom: CommonSizes.spacing.xxxLarge,
266
+ gap: CommonSizes.spacing.xLarge,
267
+ },
268
+ section: {gap: CommonSizes.spacing.medium},
269
+ h: {marginBottom: CommonSizes.spacing.small},
270
+ onGradientText: {color: '#EAF0FF'},
271
+ card: {
272
+ borderRadius: CommonSizes.borderRadius.large,
273
+ borderWidth: CommonSizes.borderWidth.small,
274
+ padding: CommonSizes.spacing.large,
275
+ gap: CommonSizes.spacing.large,
276
+ },
277
+ row: {
278
+ flexDirection: 'row',
279
+ alignItems: 'center',
280
+ justifyContent: 'space-between',
281
+ gap: CommonSizes.spacing.medium,
282
+ flexWrap: 'wrap',
283
+ },
284
+ chips: {flexDirection: 'row', flexWrap: 'wrap', gap: CommonSizes.spacing.small},
285
+ slide: {
286
+ flex: 1,
287
+ height: 130,
288
+ borderRadius: CommonSizes.borderRadius.large,
289
+ alignItems: 'center',
290
+ justifyContent: 'center',
291
+ gap: CommonSizes.spacing.small,
292
+ },
293
+ scard: {
294
+ width: 220,
295
+ borderRadius: CommonSizes.borderRadius.large,
296
+ borderWidth: CommonSizes.borderWidth.small,
297
+ padding: CommonSizes.spacing.large,
298
+ gap: CommonSizes.spacing.small,
299
+ },
300
+ skeletons: {gap: CommonSizes.spacing.medium},
301
+ });
@@ -1,5 +1,147 @@
1
1
  import React from 'react';
2
+ import {StyleSheet, View} from 'react-native';
3
+ import LinearGradient from 'react-native-linear-gradient';
4
+ import {SafeAreaView} from 'react-native-safe-area-context';
5
+ import {Avatar} from '../../common/components/Avatar';
6
+ import {Chip} from '../../common/components/Chip';
7
+ import {FlatListWrapper} from '../../common/components/FlatListWrapper';
8
+ import {ListItem} from '../../common/components/ListItem';
9
+ import {Logo} from '../../common/components/Logo';
10
+ import {RTLAwareText} from '../../common/components/RTLAwareText';
11
+ import {RTLAwareView} from '../../common/components/RTLAwareView';
12
+ import {useTranslation} from '../../common/localization/LocalizationProvider';
13
+ import {Category} from '../../core/store/categories/categoriesState';
14
+ import {BrandGradients, GradientDirection, Glow} from '../../core/theme/brand';
15
+ import {CommonSizes} from '../../core/theme/commonSizes';
16
+ import {useTheme} from '../../core/theme/ThemeProvider';
17
+ import {useHomeData} from './hooks/useHomeData';
2
18
 
3
19
  export function HomeScreen(): JSX.Element {
4
- return <></>;
20
+ const {theme} = useTheme();
21
+ const t = useTranslation();
22
+ const {user, categories, loadState, error, reload} = useHomeData();
23
+
24
+ const renderItem = ({item}: {item: Category}) => (
25
+ <ListItem
26
+ title={item.name}
27
+ subtitle={item.icon}
28
+ showChevron
29
+ onPress={() => {}}
30
+ left={
31
+ <LinearGradient
32
+ colors={BrandGradients.primary}
33
+ start={GradientDirection.start}
34
+ end={GradientDirection.end}
35
+ style={styles.itemIcon}>
36
+ <RTLAwareText
37
+ style={[theme.text.bodyMediumExtraBold, styles.onGradientText]}>
38
+ {(item.name?.charAt(0) || '•').toUpperCase()}
39
+ </RTLAwareText>
40
+ </LinearGradient>
41
+ }
42
+ />
43
+ );
44
+
45
+ const header = (
46
+ <View>
47
+ <RTLAwareView style={styles.topbar}>
48
+ <RTLAwareView style={styles.greetWrap}>
49
+ <Logo size={36} variant="gradient" />
50
+ <View style={styles.flex}>
51
+ <RTLAwareText
52
+ style={{
53
+ ...theme.text.bodySmallRegular,
54
+ color: theme.colors.grayScale_200,
55
+ }}>
56
+ {t('greeting', 'home')}
57
+ </RTLAwareText>
58
+ <RTLAwareText style={theme.text.bodyXLargeBold} numberOfLines={1}>
59
+ {user?.full_name || t('there', 'home')}
60
+ </RTLAwareText>
61
+ </View>
62
+ </RTLAwareView>
63
+ <Avatar name={user?.full_name || 'U'} size={40} />
64
+ </RTLAwareView>
65
+
66
+ <LinearGradient
67
+ colors={BrandGradients.primary}
68
+ start={GradientDirection.start}
69
+ end={GradientDirection.end}
70
+ style={[styles.hero, Glow.primary]}>
71
+ <RTLAwareText
72
+ style={[theme.text.bodySmallExtraBold, styles.onGradientText]}>
73
+ {t('heroEyebrow', 'home')}
74
+ </RTLAwareText>
75
+ <RTLAwareText style={[theme.text.header3, styles.onGradientText]}>
76
+ {t('heroTitle', 'home')}
77
+ </RTLAwareText>
78
+ <RTLAwareText
79
+ style={[theme.text.bodyMediumRegular, styles.onGradientText]}>
80
+ {t('heroSubtitle', 'home')}
81
+ </RTLAwareText>
82
+ <RTLAwareView style={styles.heroCta}>
83
+ <Chip label={t('explore', 'home')} onPress={() => {}} />
84
+ </RTLAwareView>
85
+ </LinearGradient>
86
+
87
+ <RTLAwareText style={[theme.text.bodyXLargeBold, styles.sectionTitle]}>
88
+ {t('items', 'home')}
89
+ </RTLAwareText>
90
+ </View>
91
+ );
92
+
93
+ return (
94
+ <SafeAreaView
95
+ style={[styles.screen, {backgroundColor: theme.colors.background_2}]}
96
+ edges={['top']}>
97
+ <FlatListWrapper
98
+ data={categories}
99
+ loadState={loadState}
100
+ error={error}
101
+ tryAgain={reload}
102
+ onRefresh={reload}
103
+ renderItem={renderItem}
104
+ ListHeaderComponent={header}
105
+ contentContainerStyle={styles.listContent}
106
+ />
107
+ </SafeAreaView>
108
+ );
5
109
  }
110
+
111
+ const styles = StyleSheet.create({
112
+ screen: {flex: 1},
113
+ flex: {flex: 1},
114
+ onGradientText: {color: '#EAF0FF'},
115
+ listContent: {
116
+ paddingHorizontal: CommonSizes.spacing.large,
117
+ paddingBottom: CommonSizes.spacing.xxxLarge,
118
+ },
119
+ topbar: {
120
+ flexDirection: 'row',
121
+ alignItems: 'center',
122
+ justifyContent: 'space-between',
123
+ paddingTop: CommonSizes.spacing.large,
124
+ paddingBottom: CommonSizes.spacing.xLarge,
125
+ },
126
+ greetWrap: {
127
+ flexDirection: 'row',
128
+ alignItems: 'center',
129
+ gap: CommonSizes.spacing.medium,
130
+ flex: 1,
131
+ },
132
+ hero: {
133
+ borderRadius: CommonSizes.borderRadius.xLarge,
134
+ padding: CommonSizes.spacing.xLarge,
135
+ gap: CommonSizes.spacing.small,
136
+ marginBottom: CommonSizes.spacing.xLarge,
137
+ },
138
+ heroCta: {alignSelf: 'flex-start', marginTop: CommonSizes.spacing.small},
139
+ sectionTitle: {marginBottom: CommonSizes.spacing.medium},
140
+ itemIcon: {
141
+ width: 40,
142
+ height: 40,
143
+ borderRadius: CommonSizes.borderRadius.medium,
144
+ alignItems: 'center',
145
+ justifyContent: 'center',
146
+ },
147
+ });
@@ -1,18 +1,32 @@
1
- import {useState} from 'react';
1
+ import {useCallback, useEffect} from 'react';
2
+ import {fetchCategories} from '../../../core/store/categories/categoriesActions';
2
3
  import {useAppDispatch, useAppSelector} from '../../../core/store/reduxHelpers';
3
4
  import {RootState} from '../../../core/store/rootReducer';
5
+
6
+ /**
7
+ * Loads the example `categories` feature and exposes the user.
8
+ * Fetches on mount; `reload` is wired to the list's pull-to-refresh / try-again.
9
+ */
4
10
  export function useHomeData() {
5
- const [isLoading, setIsLoading] = useState(false);
6
11
  const dispatch = useAppDispatch();
7
- const {categories, loadState} = useAppSelector(
12
+ const {categories, loadState, error} = useAppSelector(
8
13
  (state: RootState) => state.categories,
9
14
  );
10
15
  const {user} = useAppSelector((state: RootState) => state.user);
11
16
 
12
-
17
+ const loadCategories = useCallback(() => {
18
+ dispatch(fetchCategories());
19
+ }, [dispatch]);
20
+
21
+ useEffect(() => {
22
+ loadCategories();
23
+ }, [loadCategories]);
13
24
 
14
25
  return {
15
- isLoading,
16
26
  user,
27
+ categories,
28
+ loadState,
29
+ error,
30
+ reload: loadCategories,
17
31
  };
18
32
  }
@@ -4,6 +4,7 @@ export {OTPScreen as OTP} from './OTP/OTPScreen';
4
4
 
5
5
  // Main Navigation Screens
6
6
  export {HomeScreen as Home} from './home/HomeScreen';
7
+ export {ComponentsScreen as Components} from './components/ComponentsScreen';
7
8
  export {Profile} from './profile/Profile';
8
9
  export {Splash} from './splash/Splash';
9
10
 
@@ -1,5 +1,142 @@
1
- import React from 'react';
1
+ import React, {useState} from 'react';
2
+ import {Alert, StyleSheet, View} from 'react-native';
3
+ import {ButtonType} from '../../../types';
4
+ import {AppSwitch} from '../../common/components/AppSwitch';
5
+ import {Avatar} from '../../common/components/Avatar';
6
+ import {Container} from '../../common/components/Container';
7
+ import {ListItem} from '../../common/components/ListItem';
8
+ import {PrimaryButton} from '../../common/components/PrimaryButton';
9
+ import {RTLAwareText} from '../../common/components/RTLAwareText';
10
+ import {RTLAwareView} from '../../common/components/RTLAwareView';
11
+ import {Languages} from '../../common/localization/localization';
12
+ import {
13
+ useLocalization,
14
+ useTranslation,
15
+ } from '../../common/localization/LocalizationProvider';
16
+ import {useAppDispatch, useAppSelector} from '../../core/store/reduxHelpers';
17
+ import {setLogout} from '../../core/store/user/userSlice';
18
+ import {CommonSizes} from '../../core/theme/commonSizes';
19
+ import {useTheme} from '../../core/theme/ThemeProvider';
2
20
 
3
21
  export function Profile(): JSX.Element {
4
- return <></>;
22
+ const dispatch = useAppDispatch();
23
+ const {user} = useAppSelector(state => state.user);
24
+ const {theme, toggleTheme} = useTheme();
25
+ const t = useTranslation();
26
+ const {currentLanguage, changeLanguage} = useLocalization();
27
+ const [notify, setNotify] = useState(true);
28
+
29
+ const cardStyle = [
30
+ styles.card,
31
+ {
32
+ backgroundColor: theme.colors.grayScale_0,
33
+ borderColor: theme.colors.grayScale_50,
34
+ },
35
+ ];
36
+ const valueText = {
37
+ ...theme.text.bodyMediumBold,
38
+ color: theme.colors.PlatinateBlue_400,
39
+ };
40
+ const dividerStyle = [
41
+ styles.divider,
42
+ {backgroundColor: theme.colors.grayScale_50},
43
+ ];
44
+
45
+ const onToggleLanguage = () =>
46
+ changeLanguage(
47
+ currentLanguage === Languages.ar ? Languages.en : Languages.ar,
48
+ );
49
+
50
+ const onLogout = () =>
51
+ Alert.alert(t('logout', 'profile'), t('logoutConfirm', 'profile'), [
52
+ {text: t('cancel', 'common'), style: 'cancel'},
53
+ {
54
+ text: t('logout', 'profile'),
55
+ style: 'destructive',
56
+ onPress: () => dispatch(setLogout()),
57
+ },
58
+ ]);
59
+
60
+ return (
61
+ <Container
62
+ testID={'ProfileScreenID'}
63
+ backgroundImage={0}
64
+ contentContainerStyle={styles.content}
65
+ backgroundColor={theme.colors.background_2}>
66
+ <RTLAwareView style={styles.headerRow}>
67
+ <Avatar name={user?.full_name || 'U'} size={64} />
68
+ <View style={styles.flex}>
69
+ <RTLAwareText style={theme.text.header4} numberOfLines={1}>
70
+ {user?.full_name || t('account', 'profile')}
71
+ </RTLAwareText>
72
+ <RTLAwareText
73
+ style={{
74
+ ...theme.text.bodyMediumRegular,
75
+ color: theme.colors.grayScale_200,
76
+ }}>
77
+ {user?.mobile_number || '—'}
78
+ </RTLAwareText>
79
+ </View>
80
+ </RTLAwareView>
81
+
82
+ <RTLAwareView style={cardStyle}>
83
+ <ListItem
84
+ title={t('editProfile', 'profile')}
85
+ showChevron
86
+ onPress={() => {}}
87
+ />
88
+ <View style={dividerStyle} />
89
+ <ListItem
90
+ title={t('language', 'profile')}
91
+ onPress={onToggleLanguage}
92
+ right={
93
+ <RTLAwareText style={valueText}>
94
+ {currentLanguage === Languages.ar ? 'العربية' : 'English'}
95
+ </RTLAwareText>
96
+ }
97
+ />
98
+ <View style={dividerStyle} />
99
+ <ListItem
100
+ title={t('appearance', 'profile')}
101
+ onPress={toggleTheme}
102
+ right={
103
+ <RTLAwareText style={valueText}>
104
+ {theme.mode === 'dark' ? t('dark', 'profile') : t('light', 'profile')}
105
+ </RTLAwareText>
106
+ }
107
+ />
108
+ <View style={dividerStyle} />
109
+ <ListItem
110
+ title={t('notifications', 'profile')}
111
+ right={<AppSwitch value={notify} onValueChange={setNotify} />}
112
+ />
113
+ </RTLAwareView>
114
+
115
+ <PrimaryButton
116
+ label={t('logout', 'profile')}
117
+ onPressIn={onLogout}
118
+ type={ButtonType.outlineNegative}
119
+ />
120
+ </Container>
121
+ );
5
122
  }
123
+
124
+ const styles = StyleSheet.create({
125
+ content: {
126
+ paddingHorizontal: CommonSizes.spacing.large,
127
+ paddingTop: CommonSizes.spacing.large,
128
+ gap: CommonSizes.spacing.xLarge,
129
+ },
130
+ flex: {flex: 1},
131
+ headerRow: {
132
+ flexDirection: 'row',
133
+ alignItems: 'center',
134
+ gap: CommonSizes.spacing.large,
135
+ },
136
+ card: {
137
+ borderRadius: CommonSizes.borderRadius.large,
138
+ borderWidth: CommonSizes.borderWidth.small,
139
+ paddingHorizontal: CommonSizes.spacing.large,
140
+ },
141
+ divider: {height: CommonSizes.borderWidth.small, width: '100%'},
142
+ });