@fadyshawky/react-native-magic 2.0.4 → 2.0.5

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 (110) hide show
  1. package/package.json +1 -1
  2. package/template/src/common/ImageResources.g.ts +33 -1
  3. package/template/src/common/components/Background.tsx +3 -1
  4. package/template/src/common/components/Container.tsx +1 -1
  5. package/template/src/common/components/OTPInput.tsx +3 -2
  6. package/template/src/common/components/PrimaryButton.tsx +23 -23
  7. package/template/src/common/components/PrimaryTextInput.tsx +189 -199
  8. package/template/src/common/components/RadioIcon.tsx +4 -4
  9. package/template/src/common/components/SafeText.tsx +41 -0
  10. package/template/src/common/components/SearchBar.tsx +19 -17
  11. package/template/src/common/components/TryAgain.tsx +3 -3
  12. package/template/src/common/localization/localization.ts +10 -0
  13. package/template/src/common/localization/translations/commonLocalization.ts +97 -0
  14. package/template/src/common/localization/translations/homeLocalization.ts +24 -0
  15. package/template/src/common/localization/translations/loginLocalization.ts +28 -2
  16. package/template/src/common/localization/translations/mainNavigationLocalization.ts +30 -0
  17. package/template/src/common/localization/translations/navigationLocalization.ts +48 -0
  18. package/template/src/common/localization/translations/otpLocalization.ts +28 -0
  19. package/template/src/common/localization/translations/passwordLocalization.ts +54 -0
  20. package/template/src/common/localization/translations/posLocalization.ts +196 -0
  21. package/template/src/common/utils/FeesCaalculation.tsx +37 -0
  22. package/template/src/common/utils/index.tsx +11 -0
  23. package/template/src/common/utils/printData.tsx +161 -0
  24. package/template/src/common/validations/errorValidations.ts +3 -2
  25. package/template/src/components/PrinterExample.js +226 -0
  26. package/template/src/core/api/serverHeaders.ts +62 -1
  27. package/template/src/core/store/Categories/categoryActions.ts +33 -0
  28. package/template/src/core/store/Categories/categorySlice.ts +75 -0
  29. package/template/src/core/store/Categories/categoryState.ts +41 -0
  30. package/template/src/core/store/Providers/providersActions.ts +102 -0
  31. package/template/src/core/store/Providers/providersSlice.ts +136 -0
  32. package/template/src/core/store/Providers/providersState.ts +37 -0
  33. package/template/src/core/store/Services/servicesActions.ts +191 -0
  34. package/template/src/core/store/Services/servicesSlice.ts +205 -0
  35. package/template/src/core/store/Services/servicesState.ts +466 -0
  36. package/template/src/core/store/app/appSlice.ts +13 -5
  37. package/template/src/core/store/app/appState.ts +10 -2
  38. package/template/src/core/store/rootReducer.ts +6 -1
  39. package/template/src/core/store/store.tsx +55 -2
  40. package/template/src/core/store/user/userActions.ts +164 -26
  41. package/template/src/core/store/user/userSlice.ts +193 -21
  42. package/template/src/core/store/user/userState.ts +148 -25
  43. package/template/src/core/theme/colors.ts +70 -94
  44. package/template/src/core/theme/commonConsts.ts +1 -1
  45. package/template/src/core/theme/commonSizes.ts +94 -119
  46. package/template/src/core/theme/commonStyles.ts +22 -22
  47. package/template/src/core/theme/fonts.ts +14 -13
  48. package/template/src/core/theme/themes.ts +75 -386
  49. package/template/src/core/theme/types.ts +15 -201
  50. package/template/src/core/utils/stringUtils.ts +114 -0
  51. package/template/src/modules/SunmiCard.js +212 -0
  52. package/template/src/modules/SunmiPrepaid.ts +122 -0
  53. package/template/src/navigation/AuthStack.tsx +8 -0
  54. package/template/src/navigation/HeaderComponents.tsx +76 -1
  55. package/template/src/navigation/MainNavigation.tsx +3 -1
  56. package/template/src/navigation/MainStack.tsx +130 -56
  57. package/template/src/navigation/TabBar.tsx +111 -59
  58. package/template/src/navigation/types.ts +24 -0
  59. package/template/src/screens/Categories/Categories.tsx +141 -0
  60. package/template/src/screens/Categories/hooks/useCategoriesData.ts +33 -0
  61. package/template/src/screens/Categories/types.ts +7 -0
  62. package/template/src/screens/Favorites/Favorites.tsx +130 -0
  63. package/template/src/screens/ForceChangePassword/ForceChangePasswordScreen.tsx +155 -0
  64. package/template/src/screens/History/History.tsx +430 -0
  65. package/template/src/screens/History/hooks/useHistoryData.ts +49 -0
  66. package/template/src/screens/History/types.ts +7 -0
  67. package/template/src/screens/InquiredBill/InquiredBill.tsx +443 -0
  68. package/template/src/screens/InquiredBill/hooks/useInquiredData.ts +91 -0
  69. package/template/src/screens/Login/Login.tsx +85 -85
  70. package/template/src/screens/OTP/OTPScreen.tsx +170 -0
  71. package/template/src/screens/PaymentConfirmation/PaymentConfirmation.tsx +326 -0
  72. package/template/src/screens/Providers/Providers.tsx +166 -0
  73. package/template/src/screens/Providers/hooks/useProvidersData.ts +33 -0
  74. package/template/src/screens/Providers/types.ts +7 -0
  75. package/template/src/screens/ReceiptScreen/ReceiptScreen.tsx +181 -0
  76. package/template/src/screens/ReceiptScreen/hooks/useReceiptData.ts +46 -0
  77. package/template/src/screens/ReceiptScreen/utils/utils.tsx +156 -0
  78. package/template/src/screens/Services/Services.tsx +144 -0
  79. package/template/src/screens/Services/hooks/useServicesData.ts +41 -0
  80. package/template/src/screens/SingleService/Components/FawryInputs.tsx +446 -0
  81. package/template/src/screens/SingleService/SingleService.tsx +229 -0
  82. package/template/src/screens/SingleService/hooks/useServiceData.ts +164 -0
  83. package/template/src/screens/home/Components/PayByCode.tsx +129 -0
  84. package/template/src/screens/home/HomeScreen.tsx +268 -77
  85. package/template/src/screens/home/hooks/useHomeData.ts +32 -38
  86. package/template/src/screens/index.tsx +24 -0
  87. package/template/src/screens/profile/Profile.tsx +290 -2
  88. package/template/src/services/SunmiPrinterInternal.js +268 -0
  89. package/template/src/types/sunmiPrepaid.d.ts +20 -0
  90. package/template/src/utils/SunmiPrinter.ts +442 -0
  91. package/template/src/utils/feesCalculator.ts +92 -0
  92. package/template/src/common/components/Stepper.tsx +0 -153
  93. package/template/src/common/components/Svg.tsx +0 -25
  94. package/template/src/common/hooks/useDebounce.ts +0 -17
  95. package/template/src/common/hooks/usePrevious.ts +0 -11
  96. package/template/src/common/localization/intlFormatter.ts +0 -37
  97. package/template/src/common/urls/emailUrl.ts +0 -20
  98. package/template/src/common/urls/mapUrl.ts +0 -22
  99. package/template/src/common/utils/listHandlers.ts +0 -30
  100. package/template/src/common/utils/serializeQueryParams.ts +0 -10
  101. package/template/src/common/validations/hooks/useDatesError.ts +0 -40
  102. package/template/src/common/validations/profileValidations.ts +0 -30
  103. package/template/src/core/theme/shadows.ts +0 -135
  104. package/template/src/navigation/TopTabBar.tsx +0 -77
  105. package/template/src/screens/Settings/Settings.tsx +0 -5
  106. package/template/src/screens/home/components/CarouselSection.tsx +0 -79
  107. package/template/src/screens/home/components/FeaturedCarousel.tsx +0 -128
  108. package/template/src/screens/main/Main.tsx +0 -5
  109. package/template/src/screens/registration/RegistrationScreen.tsx +0 -198
  110. package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +0 -129
@@ -0,0 +1,161 @@
1
+ import moment from 'moment';
2
+ import {Service} from '../../core/store/Services/servicesState';
3
+ import {ServiceProvider} from '../../core/store/Providers/providersState';
4
+
5
+ const localization = {
6
+ serialNumber: 'الرقم التسلسلي',
7
+ serviceName: 'اسم الخدمة',
8
+ providerName: 'اسم المقدم',
9
+ customerNumber: 'رقم العميل',
10
+ value: 'القيمة',
11
+ serviceCost: 'تكلفة الخدمة',
12
+ operationNumber: 'رقم العملية',
13
+ referenceNumber: 'الرقم المرجعي',
14
+ time: 'الوقت',
15
+ total: 'الإجمالي',
16
+ expiryDate: 'تاريخ الانتهاء',
17
+ dueDate: 'الموعد المستحق',
18
+ issueDate: 'تاريخ الإصدار',
19
+ quantity: 'الكمية',
20
+ vatValue: 'القسيمة',
21
+ };
22
+
23
+ export const makePrintData = ({
24
+ service,
25
+ BillingAcct,
26
+ provider,
27
+ billInfo,
28
+ amuont,
29
+ fees,
30
+ quantity,
31
+ vatValue,
32
+ }: {
33
+ service: Service;
34
+ BillingAcct: string;
35
+ provider: ServiceProvider;
36
+ billInfo: any;
37
+ amuont: string;
38
+ fees: number;
39
+ quantity: string;
40
+ vatValue: string;
41
+ }) => {
42
+ let newData = [];
43
+ newData.push({
44
+ value: 'logo',
45
+ type: 'image',
46
+ });
47
+
48
+ newData.push({
49
+ value: service?.Name,
50
+ type: 'header2',
51
+ });
52
+ if (billInfo?.BillLabel) {
53
+ newData.push({
54
+ value: billInfo?.BillLabel,
55
+ type: 'header2',
56
+ });
57
+ }
58
+ newData.push({
59
+ value: provider?.BillerName,
60
+ type: 'header2',
61
+ });
62
+
63
+ if (BillingAcct) {
64
+ newData.push({
65
+ value: BillingAcct,
66
+ type: 'body',
67
+ label: localization['customerNumber'],
68
+ });
69
+ }
70
+ // response?.PmtInfo?.ExtraBillingAcctKeys?.ExtraBillingAcctKey?.forEach(
71
+ // (item: any) => {
72
+ // newData.push({
73
+ // value: item.Value,
74
+ // type: 'extra_description'
75
+ // });
76
+ // }
77
+ // );
78
+
79
+ if (!!vatValue) {
80
+ newData.push({
81
+ value: `${vatValue}`,
82
+ label: localization['vatValue'],
83
+ type: 'body',
84
+ });
85
+ }
86
+ if (quantity) {
87
+ newData.push({
88
+ value: `${quantity}`,
89
+ label: localization['quantity'],
90
+ type: 'body',
91
+ });
92
+ }
93
+ newData.push({
94
+ value: `${amuont} EGP`,
95
+ label: localization['value'],
96
+ type: 'body',
97
+ });
98
+
99
+ if (fees?.toString() !== '0') {
100
+ newData.push({
101
+ value: `${fees?.toString()} EGP`,
102
+ label: localization['serviceCost'],
103
+ type: 'body',
104
+ });
105
+ }
106
+ newData.push({
107
+ value: moment().format('YYYY-MM-DD HH:mm'),
108
+ label: localization['time'],
109
+ type: 'body',
110
+ });
111
+
112
+ newData.push({
113
+ value: quantity
114
+ ? `${(Number(quantity) * (Number(amuont) + Number(fees)))?.toFixed(
115
+ 2,
116
+ )} EGP`
117
+ : `${(Number(amuont) + Number(fees))?.toString()} EGP`,
118
+ label: localization['total'],
119
+ type: 'body_title',
120
+ });
121
+
122
+ if (billInfo?.DueDt) {
123
+ newData.push({
124
+ value: billInfo?.DueDt,
125
+ type: 'body',
126
+ label: localization['dueDate'],
127
+ });
128
+ }
129
+
130
+ if (billInfo?.IssueDt) {
131
+ newData.push({
132
+ value: billInfo?.IssueDt,
133
+ type: 'body',
134
+ label: localization['issueDate'],
135
+ });
136
+ }
137
+
138
+ if (billInfo?.ExtraBillInfo) {
139
+ newData.push({
140
+ value: billInfo?.ExtraBillInfo,
141
+ type: 'description',
142
+ label: '',
143
+ });
144
+ }
145
+
146
+ if (!!service?.meta?.note) {
147
+ newData.push({
148
+ value: service?.meta?.note,
149
+ type: 'description',
150
+ });
151
+ }
152
+
153
+ if (!!service?.meta?.explain) {
154
+ newData.push({
155
+ value: service?.meta?.explain,
156
+ type: 'description',
157
+ });
158
+ }
159
+
160
+ return newData;
161
+ };
@@ -1,8 +1,9 @@
1
+ import {localization} from '../localization/localization';
1
2
  import {unwrapResult} from '@reduxjs/toolkit';
2
3
  import {Alert} from 'react-native';
4
+ import {IErrorResult, ErrorRepresentationType} from '../../../types';
3
5
  import Snackbar from 'react-native-snackbar';
4
- import {ErrorRepresentationType, IErrorResult} from '../../../types';
5
- import {localization} from '../localization/localization';
6
+ import {NewColors} from '../../core/theme/colors';
6
7
  export function handlePromiseResult(
7
8
  promiseAction: Promise<any>,
8
9
  successMessage?: string,
@@ -0,0 +1,226 @@
1
+ import React, {useState, useEffect} from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ Alert,
9
+ } from 'react-native';
10
+ import SunmiPrinter from '../utils/SunmiPrinter';
11
+
12
+ const PrinterExample = () => {
13
+ const [isPrinterAvailable, setIsPrinterAvailable] = useState(false);
14
+ const [isLoading, setIsLoading] = useState(false);
15
+
16
+ useEffect(() => {
17
+ checkPrinterAvailability();
18
+ }, []);
19
+
20
+ const checkPrinterAvailability = async () => {
21
+ try {
22
+ const available = await SunmiPrinter.isPrinterAvailable();
23
+ setIsPrinterAvailable(available);
24
+ } catch (error) {
25
+ console.error('Error checking printer availability:', error);
26
+ setIsPrinterAvailable(false);
27
+ }
28
+ };
29
+
30
+ const handlePrintText = async () => {
31
+ if (!isPrinterAvailable) {
32
+ Alert.alert('Printer Error', 'Printer is not available');
33
+ return;
34
+ }
35
+
36
+ setIsLoading(true);
37
+ try {
38
+ await SunmiPrinter.printText(
39
+ 'Hello, Sunmi Printer!\n',
40
+ SunmiPrinter.FONT_SIZE_LARGE,
41
+ );
42
+ await SunmiPrinter.feedPaper(3);
43
+ Alert.alert('Success', 'Text printed successfully');
44
+ } catch (error) {
45
+ console.error('Error printing text:', error);
46
+ Alert.alert('Print Error', error.message || 'Failed to print text');
47
+ } finally {
48
+ setIsLoading(false);
49
+ }
50
+ };
51
+
52
+ const handlePrintCenteredText = async () => {
53
+ if (!isPrinterAvailable) {
54
+ Alert.alert('Printer Error', 'Printer is not available');
55
+ return;
56
+ }
57
+
58
+ setIsLoading(true);
59
+ try {
60
+ await SunmiPrinter.printTextWithAlignment(
61
+ 'Centered Text Example\n',
62
+ SunmiPrinter.FONT_SIZE_LARGE,
63
+ SunmiPrinter.ALIGN_CENTER,
64
+ );
65
+ await SunmiPrinter.feedPaper(3);
66
+ Alert.alert('Success', 'Centered text printed successfully');
67
+ } catch (error) {
68
+ console.error('Error printing centered text:', error);
69
+ Alert.alert(
70
+ 'Print Error',
71
+ error.message || 'Failed to print centered text',
72
+ );
73
+ } finally {
74
+ setIsLoading(false);
75
+ }
76
+ };
77
+
78
+ const handlePrintDivider = async () => {
79
+ if (!isPrinterAvailable) {
80
+ Alert.alert('Printer Error', 'Printer is not available');
81
+ return;
82
+ }
83
+
84
+ setIsLoading(true);
85
+ try {
86
+ await SunmiPrinter.printDivider();
87
+ await SunmiPrinter.feedPaper(3);
88
+ Alert.alert('Success', 'Divider printed successfully');
89
+ } catch (error) {
90
+ console.error('Error printing divider:', error);
91
+ Alert.alert('Print Error', error.message || 'Failed to print divider');
92
+ } finally {
93
+ setIsLoading(false);
94
+ }
95
+ };
96
+
97
+ const handlePrintSampleReceipt = async () => {
98
+ if (!isPrinterAvailable) {
99
+ Alert.alert('Printer Error', 'Printer is not available');
100
+ return;
101
+ }
102
+
103
+ setIsLoading(true);
104
+ try {
105
+ await SunmiPrinter.printSampleReceipt();
106
+ Alert.alert('Success', 'Sample receipt printed successfully');
107
+ } catch (error) {
108
+ console.error('Error printing sample receipt:', error);
109
+ Alert.alert(
110
+ 'Print Error',
111
+ error.message || 'Failed to print sample receipt',
112
+ );
113
+ } finally {
114
+ setIsLoading(false);
115
+ }
116
+ };
117
+
118
+ return (
119
+ <ScrollView style={styles.container}>
120
+ <Text style={styles.title}>Sunmi Printer Example</Text>
121
+
122
+ <View style={styles.statusContainer}>
123
+ <Text style={styles.statusLabel}>Printer Status:</Text>
124
+ <Text
125
+ style={[
126
+ styles.statusValue,
127
+ {color: isPrinterAvailable ? 'green' : 'red'},
128
+ ]}>
129
+ {isPrinterAvailable ? 'Available' : 'Not Available'}
130
+ </Text>
131
+ </View>
132
+
133
+ <TouchableOpacity
134
+ style={[styles.button, isLoading && styles.buttonDisabled]}
135
+ onPress={handlePrintText}
136
+ disabled={isLoading}>
137
+ <Text style={styles.buttonText}>Print Text</Text>
138
+ </TouchableOpacity>
139
+
140
+ <TouchableOpacity
141
+ style={[styles.button, isLoading && styles.buttonDisabled]}
142
+ onPress={handlePrintCenteredText}
143
+ disabled={isLoading}>
144
+ <Text style={styles.buttonText}>Print Centered Text</Text>
145
+ </TouchableOpacity>
146
+
147
+ <TouchableOpacity
148
+ style={[styles.button, isLoading && styles.buttonDisabled]}
149
+ onPress={handlePrintDivider}
150
+ disabled={isLoading}>
151
+ <Text style={styles.buttonText}>Print Divider</Text>
152
+ </TouchableOpacity>
153
+
154
+ <TouchableOpacity
155
+ style={[styles.button, isLoading && styles.buttonDisabled]}
156
+ onPress={handlePrintSampleReceipt}
157
+ disabled={isLoading}>
158
+ <Text style={styles.buttonText}>Print Sample Receipt</Text>
159
+ </TouchableOpacity>
160
+
161
+ <TouchableOpacity
162
+ style={[
163
+ styles.button,
164
+ styles.refreshButton,
165
+ isLoading && styles.buttonDisabled,
166
+ ]}
167
+ onPress={checkPrinterAvailability}
168
+ disabled={isLoading}>
169
+ <Text style={styles.buttonText}>Refresh Printer Status</Text>
170
+ </TouchableOpacity>
171
+ </ScrollView>
172
+ );
173
+ };
174
+
175
+ const styles = StyleSheet.create({
176
+ container: {
177
+ flex: 1,
178
+ padding: 16,
179
+ backgroundColor: '#f5f5f5',
180
+ },
181
+ title: {
182
+ fontSize: 24,
183
+ fontWeight: 'bold',
184
+ marginBottom: 24,
185
+ textAlign: 'center',
186
+ },
187
+ statusContainer: {
188
+ flexDirection: 'row',
189
+ alignItems: 'center',
190
+ marginBottom: 24,
191
+ padding: 16,
192
+ backgroundColor: '#fff',
193
+ borderRadius: 8,
194
+ elevation: 2,
195
+ },
196
+ statusLabel: {
197
+ fontSize: 16,
198
+ fontWeight: 'bold',
199
+ marginRight: 8,
200
+ },
201
+ statusValue: {
202
+ fontSize: 16,
203
+ fontWeight: 'bold',
204
+ },
205
+ button: {
206
+ backgroundColor: '#2196F3',
207
+ padding: 16,
208
+ borderRadius: 8,
209
+ marginBottom: 16,
210
+ alignItems: 'center',
211
+ elevation: 2,
212
+ },
213
+ buttonDisabled: {
214
+ backgroundColor: '#cccccc',
215
+ },
216
+ buttonText: {
217
+ color: '#fff',
218
+ fontSize: 16,
219
+ fontWeight: 'bold',
220
+ },
221
+ refreshButton: {
222
+ backgroundColor: '#4CAF50',
223
+ },
224
+ });
225
+
226
+ export default PrinterExample;
@@ -1,6 +1,10 @@
1
1
  import axios, {AxiosDefaults} from 'axios';
2
+ import DeviceInfo from 'react-native-device-info';
3
+ import {fetch} from 'react-native-ssl-pinning';
2
4
 
3
5
  export const defaultHeaders: HeadersInit_ = {
6
+ 'app-version': DeviceInfo.getVersion(),
7
+ 'serial-number': DeviceInfo.getSerialNumberSync(),
4
8
  Connection: 'keep-alive',
5
9
  'Content-Type': 'application/json',
6
10
  };
@@ -10,7 +14,7 @@ declare type MethodData = {
10
14
  config?: any;
11
15
  };
12
16
 
13
- const baseURL = '';
17
+ const baseURL = 'https://sofapi.neo-dg.app/api/v1/mobile/';
14
18
 
15
19
  const instance = axios.create({
16
20
  baseURL: baseURL,
@@ -19,6 +23,63 @@ const instance = axios.create({
19
23
  },
20
24
  });
21
25
 
26
+ export async function configureSecureRequest() {
27
+ // Only apply SSL pinning in production
28
+ if (baseURL.includes('neopayplus')) {
29
+ try {
30
+ instance.defaults.adapter = (config: any) => {
31
+ const url = `${config.baseURL}${config.url}`;
32
+ const fetchOptions: any = {
33
+ method: config.method?.toUpperCase() || 'GET',
34
+ timeoutInterval: 0,
35
+ headers: {
36
+ ...config.headers,
37
+ Accept: 'application/json',
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ // Don't double stringify the data
41
+ body: config.data ? config.data : undefined,
42
+ sslPinning: {
43
+ certs: ['STAR.neopayplus.com'],
44
+ allowInvalidCertificates: false,
45
+ validatesDomainName: true,
46
+ },
47
+ };
48
+
49
+ if (fetchOptions.method === 'GET' || fetchOptions.method === 'HEAD') {
50
+ delete fetchOptions.body;
51
+ }
52
+
53
+ return fetch(url, fetchOptions)
54
+ .then(async response => {
55
+ const data = await response.json();
56
+ return {
57
+ data,
58
+ status: response.status,
59
+ statusText: response.statusText,
60
+ headers: response.headers,
61
+ config,
62
+ request: {},
63
+ };
64
+ })
65
+ .catch(error => {
66
+ console.error('error: ', error);
67
+ if (error === 'timeout') {
68
+ throw new Error(error);
69
+ } else if (JSON.parse(error?.bodyString)?.error) {
70
+ throw new Error(JSON.parse(error?.bodyString)?.error);
71
+ } else if (JSON.parse(error?.bodyString)?.message) {
72
+ throw new Error(JSON.parse(error?.bodyString)?.message);
73
+ }
74
+ });
75
+ };
76
+ } catch (error) {
77
+ console.info('error: ', error);
78
+ throw error;
79
+ }
80
+ }
81
+ }
82
+
22
83
  // Export HTTP methods using the configured instance
23
84
  export const post = ({url, data, config}: MethodData) =>
24
85
  instance.post(url, data, config);
@@ -0,0 +1,33 @@
1
+ import {createAsyncThunk} from '@reduxjs/toolkit';
2
+ import {handleFetchJsonResponse} from '../../api/responseHandlers';
3
+ import {post} from '../../api/serverHeaders';
4
+ import {RootState} from '../rootReducer';
5
+ import {extractServerError} from '../../api/errorHandler';
6
+
7
+ export const getCategories = createAsyncThunk(
8
+ 'categories/list',
9
+ async (
10
+ {data: {limit}}: {data: {limit?: number}},
11
+ {rejectWithValue, getState, dispatch}: any,
12
+ ) => {
13
+ try {
14
+ const token = (getState() as RootState).user.accessToken;
15
+
16
+ const response = await post({
17
+ url: 'serviceMangement/Category/get_categories',
18
+ data: {limit},
19
+ config: {
20
+ headers: {
21
+ Authorization: `${token}`,
22
+ },
23
+ },
24
+ });
25
+
26
+ return handleFetchJsonResponse(response);
27
+ } catch (e: any) {
28
+ // Extract the server error from the Axios error
29
+ const serverError = extractServerError(e);
30
+ return rejectWithValue(serverError);
31
+ }
32
+ },
33
+ );
@@ -0,0 +1,75 @@
1
+ import {createSlice} from '@reduxjs/toolkit';
2
+ import {newState} from '../../../common/utils/newState';
3
+ import {handleErrorResponse} from '../../api/responseHandlers';
4
+ import {getCategories} from './categoryActions';
5
+ import {
6
+ CategoriesInitialState,
7
+ CategoriesState,
8
+ Category,
9
+ CategoryPayload,
10
+ } from './categoryState';
11
+ import {LoadState} from '../../../../types';
12
+
13
+ function getCategoriesHandler(
14
+ state: CategoriesState,
15
+ payload: {payload: CategoryPayload},
16
+ ) {
17
+ return newState(state, {
18
+ categories: payload.payload.categories,
19
+ settings: payload.payload.settings,
20
+ loadState: LoadState.allIsLoaded,
21
+ });
22
+ }
23
+ function getCategoriesLoadingHandler(state: CategoriesState) {
24
+ return newState(state, {
25
+ loadState: LoadState.loadingMore,
26
+ });
27
+ }
28
+
29
+ function getCategoriesErrorHandler(
30
+ state: CategoriesState,
31
+ payload: {payload: {message: string}},
32
+ ) {
33
+ handleErrorResponse(payload.payload.message || 'Categories fetch failed');
34
+ return newState(state, {
35
+ loadState: LoadState.error,
36
+ });
37
+ }
38
+
39
+ function categoriesLogoutHandler(state: CategoriesState) {
40
+ return newState(state, CategoriesInitialState);
41
+ }
42
+
43
+ function selectedCategoryHandler(
44
+ state: CategoriesState,
45
+ payload: {payload: Category},
46
+ ) {
47
+ return newState(state, {
48
+ selectedCategory: payload.payload,
49
+ });
50
+ }
51
+
52
+ function clearSelectedCategoryHandler(state: CategoriesState) {
53
+ return newState(state, {
54
+ selectedCategory: CategoriesInitialState.selectedCategory,
55
+ });
56
+ }
57
+
58
+ export const {reducer: CategoriesReducer, actions} = createSlice({
59
+ name: 'categories',
60
+ initialState: CategoriesInitialState,
61
+ reducers: {
62
+ setCategoriesLogout: categoriesLogoutHandler,
63
+ setSelectedCategory: selectedCategoryHandler,
64
+ clearSelectedCategory: clearSelectedCategoryHandler,
65
+ },
66
+ extraReducers: builder => {
67
+ builder
68
+ .addCase(getCategories.fulfilled, getCategoriesHandler)
69
+ .addCase(getCategories.rejected, getCategoriesErrorHandler)
70
+ .addCase(getCategories.pending, getCategoriesLoadingHandler);
71
+ },
72
+ });
73
+
74
+ export const {setCategoriesLogout, setSelectedCategory, clearSelectedCategory} =
75
+ actions;
@@ -0,0 +1,41 @@
1
+ import {LoadState} from '../../../../types';
2
+
3
+ export type PosSettings = {
4
+ id: number;
5
+ name: string;
6
+ function_name: string;
7
+ is_active: boolean;
8
+ createdAt: string;
9
+ updatedAt: string;
10
+ };
11
+
12
+ export interface CategoriesState {
13
+ categories: Category[];
14
+ loadState: LoadState;
15
+ settings: PosSettings[];
16
+ selectedCategory: Category | null;
17
+ }
18
+
19
+ export interface Category {
20
+ createdAt?: string | null;
21
+ externalId?: null;
22
+ id: number;
23
+ img_url: string;
24
+ name: string;
25
+ name_en?: string | null;
26
+ parent_id: number;
27
+ priority: number;
28
+ updatedAt: string;
29
+ }
30
+
31
+ export interface CategoryPayload {
32
+ categories: Category[];
33
+ settings: PosSettings[];
34
+ }
35
+
36
+ export const CategoriesInitialState: CategoriesState = {
37
+ categories: [],
38
+ loadState: LoadState.idle,
39
+ settings: [],
40
+ selectedCategory: null,
41
+ };