@fadyshawky/react-native-magic 2.0.5 → 2.0.7

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 (77) hide show
  1. package/package.json +1 -1
  2. package/template/App.tsx +21 -16
  3. package/template/ios/reactnativemagic/AppDelegate.mm +5 -0
  4. package/template/src/common/ImageResources.g.ts +1 -33
  5. package/template/src/common/components/Background.tsx +7 -7
  6. package/template/src/common/components/Container.tsx +7 -10
  7. package/template/src/common/localization/LocalizationProvider.tsx +14 -17
  8. package/template/src/common/localization/RTLInitializer.tsx +90 -0
  9. package/template/src/common/localization/intlFormatter.ts +37 -0
  10. package/template/src/common/localization/localization.ts +1 -3
  11. package/template/src/common/localization/translations/commonLocalization.ts +11 -81
  12. package/template/src/common/localization/translations/emptyLocalization.ts +6 -2
  13. package/template/src/common/localization/translations/errorsLocalization.ts +33 -13
  14. package/template/src/common/localization/translations/homeLocalization.ts +4 -24
  15. package/template/src/common/localization/translations/loginLocalization.ts +26 -29
  16. package/template/src/common/localization/translations/mainNavigationLocalization.ts +2 -2
  17. package/template/src/common/localization/translations/onboardingLocalization.ts +40 -9
  18. package/template/src/common/localization/translations/otpLocalization.ts +8 -8
  19. package/template/src/common/localization/translations/pagesLocalization.ts +13 -1
  20. package/template/src/common/localization/translations/passwordLocalization.ts +3 -3
  21. package/template/src/common/localization/translations/profileLocalization.ts +4 -4
  22. package/template/src/core/store/app/appSlice.ts +2 -2
  23. package/template/src/core/store/app/appState.ts +1 -1
  24. package/template/src/core/theme/colors.ts +106 -70
  25. package/template/src/core/theme/commonConsts.ts +1 -1
  26. package/template/src/core/theme/commonSizes.ts +119 -94
  27. package/template/src/core/theme/commonStyles.ts +22 -22
  28. package/template/src/core/theme/fonts.ts +13 -14
  29. package/template/src/core/theme/shadows.ts +135 -0
  30. package/template/src/core/theme/themes.ts +386 -75
  31. package/template/src/core/theme/types.ts +201 -15
  32. package/template/src/navigation/HeaderComponents.tsx +6 -30
  33. package/template/src/navigation/MainNavigation.tsx +2 -3
  34. package/template/src/navigation/MainStack.tsx +6 -97
  35. package/template/src/screens/Login/Login.tsx +5 -7
  36. package/template/src/screens/OTP/OTPScreen.tsx +12 -13
  37. package/template/src/screens/home/HomeScreen.tsx +2 -295
  38. package/template/src/screens/profile/Profile.tsx +2 -290
  39. package/template/src/common/localization/translations/posLocalization.ts +0 -196
  40. package/template/src/components/PrinterExample.js +0 -226
  41. package/template/src/core/store/Categories/categoryActions.ts +0 -33
  42. package/template/src/core/store/Categories/categorySlice.ts +0 -75
  43. package/template/src/core/store/Categories/categoryState.ts +0 -41
  44. package/template/src/core/store/Providers/providersActions.ts +0 -102
  45. package/template/src/core/store/Providers/providersSlice.ts +0 -136
  46. package/template/src/core/store/Providers/providersState.ts +0 -37
  47. package/template/src/core/store/Services/servicesActions.ts +0 -191
  48. package/template/src/core/store/Services/servicesSlice.ts +0 -205
  49. package/template/src/core/store/Services/servicesState.ts +0 -466
  50. package/template/src/modules/SunmiCard.js +0 -212
  51. package/template/src/modules/SunmiPrepaid.ts +0 -122
  52. package/template/src/screens/Categories/Categories.tsx +0 -141
  53. package/template/src/screens/Categories/hooks/useCategoriesData.ts +0 -33
  54. package/template/src/screens/Categories/types.ts +0 -7
  55. package/template/src/screens/Favorites/Favorites.tsx +0 -130
  56. package/template/src/screens/ForceChangePassword/ForceChangePasswordScreen.tsx +0 -155
  57. package/template/src/screens/History/History.tsx +0 -430
  58. package/template/src/screens/History/hooks/useHistoryData.ts +0 -49
  59. package/template/src/screens/History/types.ts +0 -7
  60. package/template/src/screens/InquiredBill/InquiredBill.tsx +0 -443
  61. package/template/src/screens/InquiredBill/hooks/useInquiredData.ts +0 -91
  62. package/template/src/screens/PaymentConfirmation/PaymentConfirmation.tsx +0 -326
  63. package/template/src/screens/Providers/Providers.tsx +0 -166
  64. package/template/src/screens/Providers/hooks/useProvidersData.ts +0 -33
  65. package/template/src/screens/Providers/types.ts +0 -7
  66. package/template/src/screens/ReceiptScreen/ReceiptScreen.tsx +0 -181
  67. package/template/src/screens/ReceiptScreen/hooks/useReceiptData.ts +0 -46
  68. package/template/src/screens/ReceiptScreen/utils/utils.tsx +0 -156
  69. package/template/src/screens/Services/Services.tsx +0 -144
  70. package/template/src/screens/Services/hooks/useServicesData.ts +0 -41
  71. package/template/src/screens/SingleService/Components/FawryInputs.tsx +0 -446
  72. package/template/src/screens/SingleService/SingleService.tsx +0 -229
  73. package/template/src/screens/SingleService/hooks/useServiceData.ts +0 -164
  74. package/template/src/services/SunmiPrinterInternal.js +0 -268
  75. package/template/src/types/sunmiPrepaid.d.ts +0 -20
  76. package/template/src/utils/SunmiPrinter.ts +0 -442
  77. package/template/src/utils/feesCalculator.ts +0 -92
@@ -1,326 +0,0 @@
1
- import {
2
- RouteProp,
3
- useIsFocused,
4
- useNavigation,
5
- useRoute,
6
- } from '@react-navigation/native';
7
- import {NativeStackNavigationProp} from '@react-navigation/native-stack';
8
- import React, {useEffect, useState} from 'react';
9
- import {
10
- Alert,
11
- Image,
12
- ImageResizeMode,
13
- ImageStyle,
14
- ScrollView,
15
- StyleSheet,
16
- TextStyle,
17
- ViewStyle,
18
- } from 'react-native';
19
- import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
20
- import {ButtonType} from '../../../types';
21
- import {Container} from '../../common/components/Container';
22
- import {PrimaryButton} from '../../common/components/PrimaryButton';
23
- import {RTLAwareText} from '../../common/components/RTLAwareText';
24
- import {ImageResources} from '../../common/ImageResources.g';
25
- import {useTranslation} from '../../common/localization/LocalizationProvider';
26
- import {useAppDispatch, useAppSelector} from '../../core/store/reduxHelpers';
27
- import {RootState} from '../../core/store/rootReducer';
28
- import {
29
- payService,
30
- reverseService,
31
- } from '../../core/store/Services/servicesActions';
32
- import {CommonSizes} from '../../core/theme/commonSizes';
33
- import {scaleHeight} from '../../core/theme/scaling';
34
- import {useTheme} from '../../core/theme/ThemeProvider';
35
- import {Theme} from '../../core/theme/types';
36
- import {HeaderBack, HeaderButton} from '../../navigation/HeaderComponents';
37
- import {RootStackParamList} from '../../navigation/types';
38
- import {omit} from 'lodash';
39
- import {calculateFees, makePrintData} from '../../common/utils';
40
- import {normalizeAndTrimWhitespace} from '../../common/helpers/stringsHelpers';
41
- import {decryptTripleDES} from '../../core/utils/stringUtils';
42
- import {RTLAwareView} from '../../common/components/RTLAwareView';
43
- import {SkypeIndicator} from 'react-native-indicators';
44
- import {createThemedStyles} from '../../core/theme/commonStyles';
45
- import {getLabel} from '../ReceiptScreen/utils/utils';
46
- import {getTextStyle} from '../ReceiptScreen/utils/utils';
47
- import {getSeparator} from '../ReceiptScreen/utils/utils';
48
- import SunmiPrepaid from '../../modules/SunmiPrepaid';
49
-
50
- export function PaymentConfirmation(): JSX.Element {
51
- const {theme} = useTheme();
52
- const navigation =
53
- useNavigation<NativeStackNavigationProp<RootStackParamList>>();
54
- const scroll = React.useRef<KeyboardAwareScrollView>(null);
55
- const t = useTranslation();
56
-
57
- const {service} = useAppSelector((state: RootState) => state.services);
58
-
59
- const {inquiredBill} = useAppSelector((state: RootState) => state.services);
60
-
61
- const customProps =
62
- inquiredBill?.Response?.PresSvcRs?.MsgRqHdr?.CustomProperties
63
- ?.CustomProperty;
64
-
65
- const {selectedProvider} = useAppSelector(
66
- (state: RootState) => state.providers,
67
- );
68
- const dispatch = useAppDispatch();
69
-
70
- const {paymentData} =
71
- useRoute<RouteProp<RootStackParamList, 'PaymentConfirmation'>>().params;
72
- const billInfo = inquiredBill?.BillRec?.[0]?.BillInfo;
73
- let billRec: any = inquiredBill?.BillRec?.[0];
74
- billRec = omit(billRec, ['BillInfo']);
75
-
76
- const [isLoading, setIsLoading] = useState(false);
77
-
78
- const boldTextStyle: TextStyle = {
79
- ...theme.text.body1,
80
- fontWeight: 'bold',
81
- };
82
-
83
- const isFocused = useIsFocused();
84
-
85
- const [confirmationData, setConfirmationData] = useState<any[]>([]);
86
-
87
- useEffect(() => {
88
- if (!isFocused) {
89
- return;
90
- }
91
- setConfirmationData(
92
- makePrintData({
93
- service,
94
- BillingAcct:
95
- service?.PmtType === 'POST'
96
- ? billRec?.BillingAcct
97
- : paymentData?.fawryInputParameters?.BillingAcct,
98
- provider: selectedProvider,
99
- billInfo,
100
- amuont: paymentData?.amountToBePaid,
101
- fees: paymentData?.fees,
102
- ...(paymentData?.quantity && {quantity: paymentData?.quantity}),
103
- ...(paymentData?.vatValue && {vatValue: paymentData?.vatValue}),
104
- }),
105
- );
106
-
107
- return () => {
108
- setConfirmationData([]);
109
- };
110
- }, [isFocused]);
111
-
112
- const handleReverse = async (response: any) => {
113
- try {
114
- const result = await dispatch(
115
- reverseService({
116
- data: {
117
- id: response?.payload?.data?.results?.find(
118
- (k: any) => k.label === 'رقم العملية',
119
- )?.value,
120
- },
121
- }),
122
- );
123
- if (result.type.includes('fulfilled')) {
124
- return Alert.alert('تم إعادة الرصيد', 'تم إعادة الرصيد بنجاح', [
125
- {
126
- text: 'تم',
127
- onPress: () => {
128
- navigation.goBack();
129
- setTimeout(() => {
130
- navigation.goBack();
131
- }, 500);
132
- },
133
- },
134
- ]);
135
- }
136
- } catch (error) {
137
- handleReverse(response);
138
- }
139
- };
140
-
141
- const handleChargeCard = async (response: any) => {
142
- try {
143
- if (response?.payload?.data?.chargeValue) {
144
- const writeRes = await SunmiPrepaid.writeCardCharge(
145
- response?.payload?.data?.chargeValue,
146
- response?.payload?.data?.cardMetadata || null,
147
- );
148
- if (writeRes) {
149
- if (response.type.includes('fulfilled')) {
150
- navigation.navigate('ReceiptScreen', {
151
- type: 'print',
152
- });
153
- }
154
- }
155
- } else {
156
- handleReverse(response);
157
- }
158
- } catch (error) {
159
- handleReverse(response);
160
- }
161
- };
162
-
163
- const handleSubmit = async () => {
164
- try {
165
- setIsLoading(true);
166
- let data = {};
167
- if (service?.PmtType === 'PREP' || service?.PmtType === 'VOCH') {
168
- data = {
169
- ...(service?.PmtType === 'VOCH' && {quantity: paymentData?.quantity}),
170
- BillTypeCode: service?.BillTypeCode as number,
171
- PmtType: service?.PmtType as string,
172
- BillingAcct:
173
- (paymentData?.fawryInputParameters?.BillingAcct as string) ??
174
- undefined,
175
- CurAmt: paymentData?.amountToBePaid,
176
- ExtraBillingAcctKeys:
177
- paymentData?.fawryInputParameters?.ExtraBillingAcctKey.filter(
178
- k => !!k.Value || k.EnumValues,
179
- ).length > 0
180
- ? {
181
- ExtraBillingAcctKey:
182
- paymentData?.fawryInputParameters?.ExtraBillingAcctKey?.filter(
183
- k => !!k.Value || k.EnumValues,
184
- ).map(item => ({
185
- ...(item.Value && {Key: item.Key}),
186
- ...(item.Value && {Value: item.Value}),
187
- ...(item.EnumValues && {
188
- EnumValues: item.EnumValues,
189
- }),
190
- })),
191
- }
192
- : undefined,
193
- };
194
- } else {
195
- data = {
196
- BillTypeCode: billRec?.BillTypeCode as number,
197
- PmtType: service?.PmtType as string,
198
- BillingAcct: billRec?.BillingAcct as string,
199
- BillRefNumber: billRec?.BillRefNumber as string,
200
- CurAmt: paymentData?.amountToBePaid.toString(),
201
- ExtraBillingAcctKeys: billRec?.ExtraBillingAcctKeys as string,
202
- billInfo: billInfo,
203
- };
204
- }
205
-
206
- const response = await dispatch(payService({data}));
207
-
208
- if (
209
- service?.AcctInputMethod === 'GASSC' ||
210
- service?.AcctInputMethod === 'SC' ||
211
- service?.AcctInputMethod === 'WSC'
212
- ) {
213
- handleChargeCard(response);
214
- } else {
215
- if (response.type.includes('fulfilled')) {
216
- navigation.navigate('ReceiptScreen', {
217
- type: 'print',
218
- });
219
- }
220
- }
221
- } catch (error) {
222
- console.error('error: ', error);
223
- } finally {
224
- setIsLoading(false);
225
- }
226
- };
227
-
228
- return (
229
- <Container
230
- ref={scroll}
231
- testID={'ReceiptScreenID'}
232
- contentContainerStyle={styles.contentContainer}
233
- style={styles.container}
234
- backgroundImage={ImageResources.background_2}
235
- withoutPadding
236
- extendedBackground
237
- withoutScroll
238
- bounces={true}
239
- backgroundColor={theme.colors.background}>
240
- {isLoading ? (
241
- <SkypeIndicator size={80} color={theme.colors.mutedLavender} />
242
- ) : (
243
- <>
244
- <HeaderButton onPress={() => navigation.goBack()} />
245
- <ScrollView
246
- style={styles.scrollView}
247
- contentContainerStyle={[
248
- styles.scrollViewContent,
249
- {backgroundColor: theme.colors.surface},
250
- createThemedStyles(theme).dropShadow,
251
- ]}>
252
- {Array.isArray(confirmationData) &&
253
- confirmationData?.map((h, i) => (
254
- <RTLAwareView
255
- key={`${h.label}-${i}`}
256
- style={styles.receiptItem}>
257
- {getSeparator(h.type, theme)}
258
- <RTLAwareView
259
- style={getTextStyle(h.type, theme)?.container as ViewStyle}>
260
- <RTLAwareText
261
- style={getTextStyle(h.type, theme)?.value as TextStyle}>
262
- {h.label === 'VouchPIN'
263
- ? decryptTripleDES(
264
- h.value,
265
- '8pe/hqinrZQV6pHNkQ0WwbD0ZHkaAbbj',
266
- )
267
- : normalizeAndTrimWhitespace(h.value)}
268
- </RTLAwareText>
269
- {h.label &&
270
- h.label !== 'VouchPIN' &&
271
- getLabel(h.type, h.label, theme)}
272
- </RTLAwareView>
273
- </RTLAwareView>
274
- ))}
275
- </ScrollView>
276
- <PrimaryButton
277
- label={t('confirm', 'common')}
278
- type={ButtonType.solid}
279
- onPress={handleSubmit}
280
- />
281
- <PrimaryButton
282
- label={t('back', 'common')}
283
- onPress={() => {
284
- navigation.goBack();
285
- }}
286
- style={styles.backToHomeButton}
287
- type={ButtonType.outlineNegative}
288
- />
289
- </>
290
- )}
291
- </Container>
292
- );
293
- }
294
-
295
- const styles = StyleSheet.create({
296
- container: {
297
- flexGrow: 1,
298
- paddingBottom: scaleHeight(20),
299
- borderTopRightRadius: CommonSizes.spacing.large,
300
- borderTopLeftRadius: CommonSizes.spacing.large,
301
- paddingHorizontal: CommonSizes.spacing.large,
302
- gap: CommonSizes.spacing.xl,
303
- justifyContent: 'flex-start',
304
- },
305
- contentContainer: {
306
- flexGrow: 1,
307
- },
308
- scrollView: {
309
- width: '100%',
310
- borderRadius: CommonSizes.borderRadius.medium,
311
- flexGrow: 1,
312
- },
313
- scrollViewContent: {
314
- paddingHorizontal: CommonSizes.spacing.huge,
315
- gap: CommonSizes.spacing.large,
316
- paddingVertical: CommonSizes.spacing.huge,
317
- flexGrow: 1,
318
- },
319
- receiptItem: {
320
- width: '100%',
321
- gap: CommonSizes.spacing.medium,
322
- },
323
- backToHomeButton: {
324
- alignSelf: 'flex-end',
325
- },
326
- });
@@ -1,166 +0,0 @@
1
- import {
2
- RouteProp,
3
- useIsFocused,
4
- useNavigation,
5
- useRoute,
6
- } from '@react-navigation/native';
7
- import {NativeStackNavigationProp} from '@react-navigation/native-stack';
8
- import React, {useEffect, useRef} from 'react';
9
- import {StyleSheet, View} from 'react-native';
10
- import {SkypeIndicator} from 'react-native-indicators';
11
- import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
12
- import {LoadState} from '../../../types';
13
- import {Card} from '../../common/components/Cards';
14
- import {Container} from '../../common/components/Container';
15
- import {FlatListWrapper} from '../../common/components/FlatListWrapper';
16
- import {ImageResources} from '../../common/ImageResources.g';
17
- import {
18
- resetProviders,
19
- setSelectedProvider,
20
- } from '../../core/store/Providers/providersSlice';
21
- import {ServiceProvider} from '../../core/store/Providers/providersState';
22
- import {useAppDispatch} from '../../core/store/reduxHelpers';
23
- import {CommonSizes} from '../../core/theme/commonSizes';
24
- import {scaleHeight, scaleWidth} from '../../core/theme/scaling';
25
- import {useTheme} from '../../core/theme/ThemeProvider';
26
- import {HeaderBack} from '../../navigation/HeaderComponents';
27
- import {RootStackParamList} from '../../navigation/types';
28
- import {useProvidersData} from './hooks/useProvidersData';
29
-
30
- export function Providers(): JSX.Element {
31
- const dispatch = useAppDispatch();
32
- const {categoryID} =
33
- useRoute<RouteProp<RootStackParamList, 'Providers'>>().params;
34
- const navigation =
35
- useNavigation<NativeStackNavigationProp<RootStackParamList>>();
36
- const {fetchData, providers, isLoading} = useProvidersData(categoryID);
37
- const {theme} = useTheme();
38
- const scroll = useRef<KeyboardAwareScrollView>(null);
39
-
40
- function navigateToServices(provider: ServiceProvider) {
41
- dispatch(setSelectedProvider(provider));
42
- navigation.navigate('Services', {
43
- providerID:
44
- provider?.provider === 'Fawry'
45
- ? (provider?.BillerId?.toString() as string)
46
- : (provider?.id?.toString() as string),
47
- });
48
- }
49
- const isFocused = useIsFocused();
50
-
51
- useEffect(() => {
52
- if (isFocused) {
53
- fetchData();
54
- }
55
- }, [isFocused]);
56
-
57
- return (
58
- <Container
59
- ref={scroll}
60
- testID={'ProvidersScreenID'}
61
- contentContainerStyle={styles.contentContainer}
62
- style={styles.container}
63
- backgroundImage={ImageResources[`${theme.mode}_background_1`]}
64
- withoutPadding
65
- withoutScroll
66
- backgroundColor={theme.colors.background}>
67
- {isLoading ? (
68
- <SkypeIndicator size={80} color={theme.colors.mutedLavender} />
69
- ) : (
70
- <>
71
- <HeaderBack
72
- onPress={() => {
73
- dispatch(resetProviders());
74
- navigation.goBack();
75
- }}
76
- />
77
-
78
- <FlatListWrapper
79
- contentContainerStyle={{
80
- alignItems: 'flex-start',
81
- paddingBottom: scaleHeight(50),
82
- paddingLeft: CommonSizes.spacing.medium,
83
- }}
84
- style={{
85
- flex: 1,
86
- alignSelf: 'flex-start',
87
- }}
88
- ItemSeparatorComponent={() => (
89
- <View style={{height: CommonSizes.spacing.large}} />
90
- )}
91
- keyExtractor={(item, index) => `${item.id}-${index}`}
92
- loadState={LoadState.needLoad}
93
- data={providers}
94
- numColumns={3}
95
- renderItem={({item, index}) => {
96
- return (
97
- <Card
98
- cardStyle={{
99
- width: scaleWidth(187),
100
- height: scaleHeight(260),
101
- }}
102
- key={item.id}
103
- icon={{uri: item.img_url}}
104
- title={item.name || item?.BillerName}
105
- onPress={() => {
106
- navigateToServices(item);
107
- }}
108
- marginRight={
109
- (index % 3) + 1 !== 3
110
- ? CommonSizes.spacing.medium
111
- : undefined
112
- }
113
- />
114
- );
115
- }}
116
- />
117
- </>
118
- )}
119
- </Container>
120
- );
121
- }
122
-
123
- const styles = StyleSheet.create({
124
- container: {
125
- flexGrow: 1,
126
- borderTopRightRadius: CommonSizes.spacing.large,
127
- borderTopLeftRadius: CommonSizes.spacing.large,
128
- gap: CommonSizes.spacing.large,
129
- justifyContent: 'flex-start',
130
- alignItems: 'center',
131
- },
132
- contentContainer: {
133
- flexGrow: 1,
134
- },
135
- searchBar: {
136
- width: '90%',
137
- alignSelf: 'center',
138
- marginTop: CommonSizes.spacing.medium,
139
- },
140
- formContainer: {
141
- alignItems: 'center',
142
- paddingHorizontal: CommonSizes.spacing.large,
143
- gap: CommonSizes.spacing.large,
144
- },
145
- balanceSection: {
146
- alignItems: 'center',
147
- justifyContent: 'space-evenly',
148
- gap: CommonSizes.spacing.large,
149
- },
150
- seeMoreSection: {
151
- alignItems: 'center',
152
- gap: CommonSizes.spacing.large,
153
- alignSelf: 'center',
154
- },
155
- sectionImage: {
156
- width: scaleWidth(120),
157
- height: scaleHeight(70),
158
- },
159
- amountText: {
160
- textAlign: 'left',
161
- },
162
- balanceCard: {
163
- width: '100%',
164
- borderRadius: CommonSizes.spacing.huge,
165
- },
166
- });
@@ -1,33 +0,0 @@
1
- import {useState} from 'react';
2
- import {getProviders} from '../../../core/store/Providers/providersActions';
3
- import {useAppDispatch, useAppSelector} from '../../../core/store/reduxHelpers';
4
- import {RootState} from '../../../core/store/rootReducer';
5
- export function useProvidersData(categoryID: string) {
6
- const [isLoading, setIsLoading] = useState(false);
7
- const dispatch = useAppDispatch();
8
- const {providers} = useAppSelector((state: RootState) => state.providers);
9
-
10
- const fetchData = async () => {
11
- setIsLoading(true);
12
- try {
13
- await dispatch(
14
- getProviders({data: {limit: 5000, category_id: Number(categoryID)}}),
15
- );
16
- } catch (error) {
17
- console.error('Error fetching home data:', error);
18
- } finally {
19
- setIsLoading(false);
20
- }
21
- };
22
-
23
- const refreshData = () => {
24
- fetchData();
25
- };
26
-
27
- return {
28
- isLoading,
29
- refreshData,
30
- fetchData,
31
- providers,
32
- };
33
- }
@@ -1,7 +0,0 @@
1
- export interface CarouselItem {
2
- id: string;
3
- title: string;
4
- subtitle?: string;
5
- imageUrl: string;
6
- onPress?: (item: CarouselItem) => void;
7
- }