@fadyshawky/react-native-magic 2.2.1 → 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.
- package/LICENSE +21 -0
- package/README.md +90 -56
- package/index.js +4 -0
- package/package.json +8 -2
- package/scripts/askPackageName.js +10 -5
- package/template/.env.development +8 -6
- package/template/.env.example +15 -5
- package/template/.env.production +8 -6
- package/template/.env.staging +8 -6
- package/template/.eslintrc.js +14 -0
- package/template/.husky/pre-commit +1 -0
- package/template/App.tsx +47 -16
- package/template/__tests__/App.test.tsx +28 -10
- package/template/babel.config.js +20 -1
- package/template/docs/ARCHITECTURE.md +40 -10
- package/template/docs/BEST_PRACTICES.md +10 -1
- package/template/docs/CUSTOMIZATION.md +118 -5
- package/template/docs/design-system.html +1164 -0
- package/template/docs/wireframes.html +411 -0
- package/template/index.js +10 -0
- package/template/jest.config.js +16 -1
- package/template/jest.setup.js +61 -0
- package/template/package-lock.json +12178 -8293
- package/template/package.json +53 -20
- package/template/react-native.config.js +3 -0
- package/template/resources/fonts/.gitkeep +0 -0
- package/template/scripts/ci-sync-env.cjs +71 -0
- package/template/src/assets/brand/logo-mark.svg +8 -0
- package/template/src/assets/brand/logo-mono.svg +9 -0
- package/template/src/assets/brand/logo-primary.svg +15 -0
- package/template/src/assets/brand/wordmark-dark.svg +18 -0
- package/template/src/common/components/AppBottomSheet.tsx +87 -0
- package/template/src/common/components/AppSwitch.tsx +75 -0
- package/template/src/common/components/AppTextInput.tsx +161 -0
- package/template/src/common/components/Avatar.tsx +75 -0
- package/template/src/common/components/Badge.tsx +66 -0
- package/template/src/common/components/CardScroller.tsx +58 -0
- package/template/src/common/components/Cards.tsx +13 -7
- package/template/src/common/components/Carousel.tsx +196 -0
- package/template/src/common/components/Checkbox.tsx +85 -0
- package/template/src/common/components/Chip.tsx +55 -0
- package/template/src/common/components/Dropdown.tsx +202 -0
- package/template/src/common/components/ErrorBoundary.tsx +82 -0
- package/template/src/common/components/FlatListWrapper.tsx +4 -5
- package/template/src/common/components/ListItem.tsx +90 -0
- package/template/src/common/components/LoadingComponent.tsx +8 -2
- package/template/src/common/components/Logo.tsx +77 -0
- package/template/src/common/components/ModalDialog.tsx +141 -0
- package/template/src/common/components/NetworkBanner.tsx +47 -0
- package/template/src/common/components/OTPInput.tsx +0 -1
- package/template/src/common/components/PrimaryButton.tsx +0 -14
- package/template/src/common/components/PrimaryTextInput.tsx +66 -130
- package/template/src/common/components/RadioGroup.tsx +95 -0
- package/template/src/common/components/SafeText.tsx +4 -3
- package/template/src/common/components/SearchBar.tsx +7 -5
- package/template/src/common/components/SegmentedControl.tsx +77 -0
- package/template/src/common/components/Skeleton.tsx +47 -0
- package/template/src/common/components/TryAgain.tsx +4 -2
- package/template/src/common/helpers/arrayHelpers.ts +2 -2
- package/template/src/common/helpers/defaultKeyIdExtractor.ts +1 -1
- package/template/src/common/helpers/regexHelpers.ts +1 -2
- package/template/src/common/helpers/stringsHelpers.ts +0 -1
- package/template/src/common/hooks/useBackHandler.ts +5 -2
- package/template/src/common/hooks/useEventRegister.ts +1 -1
- package/template/src/common/hooks/useFlatListActions.ts +1 -1
- package/template/src/common/hooks/useWhyDidYouUpdate.ts +1 -1
- package/template/src/common/localization/LocalizationProvider.tsx +1 -1
- package/template/src/common/localization/RTLInitializer.tsx +1 -1
- package/template/src/common/localization/dateFormatter.ts +0 -1
- package/template/src/common/localization/intlFormatter.ts +0 -1
- package/template/src/common/localization/localization.ts +2 -2
- package/template/src/common/localization/translations/homeLocalization.ts +14 -0
- package/template/src/common/localization/translations/loginLocalization.ts +8 -0
- package/template/src/common/localization/translations/mainNavigationLocalization.ts +2 -0
- package/template/src/common/localization/translations/profileLocalization.ts +16 -0
- package/template/src/common/utils/index.tsx +0 -6
- package/template/src/common/validations/commonValidations.ts +2 -2
- package/template/src/core/api/errorHandler.ts +1 -1
- package/template/src/core/api/responseHandlers.ts +1 -3
- package/template/src/core/api/serverHeaders.ts +61 -12
- package/template/src/core/notifications/notificationAuth.ts +6 -0
- package/template/src/core/notifications/notificationService.ts +125 -0
- package/template/src/core/notifications/routeFromNotificationData.ts +32 -0
- package/template/src/core/store/categories/categoriesActions.ts +25 -0
- package/template/src/core/store/categories/categoriesSlice.ts +51 -0
- package/template/src/core/store/categories/categoriesState.ts +19 -0
- package/template/src/core/store/rootReducer.ts +2 -0
- package/template/src/core/store/store.tsx +6 -1
- package/template/src/core/store/user/userActions.ts +75 -14
- package/template/src/core/store/user/userSlice.ts +49 -26
- package/template/src/core/store/user/userState.ts +6 -4
- package/template/src/core/theme/ThemeProvider.tsx +5 -3
- package/template/src/core/theme/brand.ts +50 -0
- package/template/src/core/theme/colors.ts +113 -99
- package/template/src/core/theme/commonConsts.ts +2 -2
- package/template/src/core/theme/commonStyles.ts +1 -1
- package/template/src/core/theme/themes.ts +2 -0
- package/template/src/core/theme/types.ts +4 -2
- package/template/src/core/utils/stringUtils.ts +1 -1
- package/template/src/design-system/index.ts +2 -0
- package/template/src/design-system/tokens/brand.ts +6 -0
- package/template/src/design-system/tokens/index.ts +3 -0
- package/template/src/design-system/tokens/palette.ts +4 -0
- package/template/src/design-system/tokens/typography-spacing.ts +2 -0
- package/template/src/navigation/AuthStack.tsx +1 -4
- package/template/src/navigation/HeaderComponents.tsx +6 -3
- package/template/src/navigation/MainStack.tsx +18 -6
- package/template/src/navigation/RootNavigation.tsx +4 -7
- package/template/src/navigation/TabBar.tsx +7 -6
- package/template/src/navigation/types.ts +10 -31
- package/template/src/screens/Login/Login.tsx +47 -47
- package/template/src/screens/OTP/OTPScreen.tsx +6 -9
- package/template/src/screens/components/ComponentsScreen.tsx +301 -0
- package/template/src/screens/home/HomeScreen.tsx +143 -1
- package/template/src/screens/home/hooks/useHomeData.ts +19 -5
- package/template/src/screens/index.tsx +1 -0
- package/template/src/screens/profile/Profile.tsx +139 -2
- package/template/src/screens/splash/Splash.tsx +44 -11
- package/template/src/sheetManager/sheets.tsx +1 -1
- package/template/tsconfig.json +14 -2
- package/template/types/globals.d.ts +43 -0
- package/template/types/index.ts +2 -6
- package/template/types/modules.d.ts +9 -0
- package/template/types/react-native-config.d.ts +0 -2
- package/.vscode/settings.json +0 -8
- package/CHANGELOG.md +0 -119
- package/CODE_OF_CONDUCT.md +0 -83
- package/CONTRIBUTING.md +0 -60
- package/local.properties +0 -1
- package/template/src/common/components/ImageCropPickerButton.tsx +0 -107
- package/template/src/common/components/PhotoTakingButton.tsx +0 -94
- package/template/src/common/helpers/imageHelpers.ts +0 -5
- package/template/src/common/helpers/inAppReviewHelper.ts +0 -30
- package/template/src/common/helpers/orientationHelpers.ts +0 -25
- package/template/src/common/helpers/shareHelpers.ts +0 -47
- package/template/src/common/utils/FeesCaalculation.tsx +0 -37
- package/template/src/common/utils/printData.tsx +0 -161
- 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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
+
});
|