@visns-studio/visns-components 5.7.0 → 5.7.2
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/package.json +1 -4
- package/src/components/cms/DataGrid.jsx +2 -2
- package/src/components/crm/Autocomplete.jsx +161 -127
- package/src/components/crm/DataGrid.jsx +2 -2
- package/src/components/crm/Download.jsx +15 -4
- package/src/components/crm/Fetch.jsx +69 -16
- package/src/components/crm/Notification.jsx +237 -106
- package/src/components/crm/QuickAction.jsx +318 -91
- package/src/components/crm/generic/GenericDetail.jsx +43 -127
- package/src/components/crm/generic/NotificationList.jsx +76 -7
- package/src/components/crm/generic/styles/NotificationList.module.scss +90 -2
- package/src/components/crm/styles/DataGrid.module.scss +5 -0
- package/src/components/crm/styles/Notification.module.scss +313 -96
- package/src/components/crm/styles/QuickAction.module.scss +168 -46
- package/src/components/styles/global.css +5 -0
- package/src/utils/fetchUtil.js +242 -0
package/package.json
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
"dependencies": {
|
|
3
3
|
"@emotion/is-prop-valid": "^1.3.1",
|
|
4
4
|
"@fontsource/barlow": "^5.2.5",
|
|
5
|
-
"@inovua/reactdatagrid-community": "^5.10.2",
|
|
6
|
-
"@inovua/reactdatagrid-enterprise": "^5.10.2",
|
|
7
5
|
"@nivo/bar": "^0.88.0",
|
|
8
6
|
"@nivo/core": "^0.88.0",
|
|
9
7
|
"@nivo/line": "^0.88.0",
|
|
@@ -16,7 +14,6 @@
|
|
|
16
14
|
"akar-icons": "^1.9.31",
|
|
17
15
|
"array-move": "^4.0.0",
|
|
18
16
|
"awesome-debounce-promise": "^2.1.0",
|
|
19
|
-
"axios": "^1.9.0",
|
|
20
17
|
"browser-image-compression": "^2.0.2",
|
|
21
18
|
"dayjs": "^1.11.13",
|
|
22
19
|
"fabric": "^6.6.4",
|
|
@@ -83,7 +80,7 @@
|
|
|
83
80
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
84
81
|
},
|
|
85
82
|
"name": "@visns-studio/visns-components",
|
|
86
|
-
"version": "5.7.
|
|
83
|
+
"version": "5.7.2",
|
|
87
84
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
88
85
|
"main": "src/index.js",
|
|
89
86
|
"files": [
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { useNavigate } from 'react-router-dom';
|
|
3
|
-
import axios from 'axios';
|
|
4
3
|
import debounce from 'lodash.debounce';
|
|
5
4
|
import moment from 'moment';
|
|
6
5
|
import numeral from 'numeral';
|
|
@@ -12,6 +11,7 @@ import DateFilter from '@visns-studio/visns-datagrid-enterprise/DateFilter';
|
|
|
12
11
|
import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
|
|
13
12
|
import { toast } from 'react-toastify';
|
|
14
13
|
import Toggle from 'react-toggle';
|
|
14
|
+
import fetchUtil from '../../utils/fetchUtil';
|
|
15
15
|
import { confirmDialog } from '../utils/ConfirmDialog';
|
|
16
16
|
import {
|
|
17
17
|
Backspace,
|
|
@@ -81,7 +81,7 @@ const loadData = async (
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
const response = await
|
|
84
|
+
const response = await fetchUtil.post(url, params);
|
|
85
85
|
const { data, total } = response.data;
|
|
86
86
|
return { data, count: total };
|
|
87
87
|
} catch (error) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo, memo } from 'react';
|
|
3
2
|
import AwesomeDebouncePromise from 'awesome-debounce-promise';
|
|
4
3
|
import { ToggleOffFill, ToggleOnFill } from 'akar-icons';
|
|
5
4
|
import styles from './styles/Autocomplete.module.scss';
|
|
5
|
+
import fetchUtil from '../../utils/fetchUtil';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Create the debounced function outside the component to prevent recreation
|
|
8
|
+
const createDebouncedSearch = (fn) =>
|
|
9
|
+
AwesomeDebouncePromise(fn, 500, {
|
|
10
|
+
key: (id) => id,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const VisnsAutocomplete = memo((props) => {
|
|
8
14
|
const {
|
|
9
15
|
api,
|
|
10
16
|
country,
|
|
@@ -15,160 +21,188 @@ const VisnsAutocomplete = (props) => {
|
|
|
15
21
|
setFormData,
|
|
16
22
|
style,
|
|
17
23
|
} = props;
|
|
24
|
+
|
|
18
25
|
const [search, setSearch] = useState('');
|
|
19
26
|
const [results, setResults] = useState([]);
|
|
20
27
|
const [isLoading, setIsLoading] = useState(false);
|
|
21
28
|
const [overwrite, setOverwrite] = useState(false);
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
setIsLoading(false);
|
|
28
|
-
} else {
|
|
29
|
-
let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json?types=address,poi&access_token=${api}`;
|
|
30
|
+
// Memoize URL building logic to avoid recalculations
|
|
31
|
+
const buildSearchUrl = useCallback(
|
|
32
|
+
(value) => {
|
|
33
|
+
if (!value || !api) return null;
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
url += `&country=${country}`;
|
|
33
|
-
}
|
|
35
|
+
let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json?types=address,poi&access_token=${api}`;
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
37
|
+
if (country) {
|
|
38
|
+
url += `&country=${country}`;
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
41
|
+
if (mapbox) {
|
|
42
|
+
if (mapbox.proximity) {
|
|
43
|
+
url += `&proximity=${mapbox.proximity}`;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
setResults(response.data.features);
|
|
49
|
-
setIsLoading(false);
|
|
50
|
-
})
|
|
51
|
-
.catch(function (error) {
|
|
52
|
-
console.log(error);
|
|
53
|
-
});
|
|
46
|
+
if (mapbox.bbox) {
|
|
47
|
+
url += `&bbox=${mapbox.bbox}`;
|
|
48
|
+
}
|
|
54
49
|
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
return url;
|
|
52
|
+
},
|
|
53
|
+
[api, country, mapbox]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const performSearch = useCallback(
|
|
57
|
+
(id, value) => {
|
|
58
|
+
if (id <= 0 || !value) {
|
|
59
|
+
setResults([]);
|
|
60
|
+
setIsLoading(false);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const url = buildSearchUrl(value);
|
|
65
|
+
if (!url) return;
|
|
66
|
+
|
|
67
|
+
fetchUtil
|
|
68
|
+
.get(url)
|
|
69
|
+
.then((response) => {
|
|
70
|
+
setResults(response.data.features || []);
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
console.log(error);
|
|
75
|
+
setIsLoading(false);
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
[buildSearchUrl]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Create the debounced search function using our memoized performSearch
|
|
82
|
+
const performSearchDebounced = useMemo(
|
|
83
|
+
() => createDebouncedSearch(performSearch),
|
|
84
|
+
[performSearch]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const handleSearchChange = useCallback(
|
|
88
|
+
async (e) => {
|
|
89
|
+
if (!e) return;
|
|
90
|
+
|
|
91
|
+
const newValue = e.target.value;
|
|
92
|
+
setSearch(newValue);
|
|
61
93
|
setFormData((prevState) => ({
|
|
62
94
|
...prevState,
|
|
63
|
-
[field]:
|
|
95
|
+
[field]: newValue,
|
|
64
96
|
}));
|
|
65
97
|
|
|
66
|
-
if (overwrite
|
|
98
|
+
if (!overwrite) {
|
|
67
99
|
setIsLoading(true);
|
|
100
|
+
await performSearchDebounced(1, newValue);
|
|
68
101
|
}
|
|
102
|
+
},
|
|
103
|
+
[field, overwrite, performSearchDebounced, setFormData]
|
|
104
|
+
);
|
|
69
105
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const performSearchDebounced = AwesomeDebouncePromise(performSearch, 500, {
|
|
77
|
-
key: (id, value) => id,
|
|
78
|
-
});
|
|
106
|
+
const handleItemClicked = useCallback(
|
|
107
|
+
(place) => {
|
|
108
|
+
const address = place.address || '';
|
|
109
|
+
const text = place.text || '';
|
|
110
|
+
const search = address && text ? `${address} ${text}` : text;
|
|
79
111
|
|
|
80
|
-
|
|
81
|
-
const address =
|
|
82
|
-
place.address !== undefined && place.address !== null
|
|
83
|
-
? place.address
|
|
84
|
-
: '';
|
|
85
|
-
const text =
|
|
86
|
-
place.text !== undefined && place.text !== null ? place.text : '';
|
|
87
|
-
|
|
88
|
-
const search = address && text ? `${address} ${text}` : text;
|
|
89
|
-
setSearch(search);
|
|
90
|
-
setResults([]);
|
|
91
|
-
onSelect(place, field);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const handleOverwrite = () => {
|
|
95
|
-
if (overwrite === false) {
|
|
96
|
-
setOverwrite(true);
|
|
112
|
+
setSearch(search);
|
|
97
113
|
setResults([]);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
onSelect(place, field);
|
|
115
|
+
},
|
|
116
|
+
[field, onSelect]
|
|
117
|
+
);
|
|
102
118
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
119
|
+
const handleOverwrite = useCallback(() => {
|
|
120
|
+
setOverwrite((prevState) => {
|
|
121
|
+
if (!prevState) {
|
|
122
|
+
setResults([]);
|
|
107
123
|
}
|
|
124
|
+
return !prevState;
|
|
125
|
+
});
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
// Effect to handle overwrite toggle
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
if (!overwrite && search) {
|
|
131
|
+
setIsLoading(true);
|
|
132
|
+
performSearch(1, search);
|
|
108
133
|
}
|
|
109
|
-
}, [overwrite]);
|
|
134
|
+
}, [overwrite, search, performSearch]);
|
|
110
135
|
|
|
136
|
+
// Effect to sync with inputValue prop
|
|
111
137
|
useEffect(() => {
|
|
112
|
-
|
|
138
|
+
if (inputValue !== undefined) {
|
|
139
|
+
setSearch(inputValue);
|
|
140
|
+
}
|
|
113
141
|
}, [inputValue]);
|
|
114
142
|
|
|
143
|
+
// Memoize the input style to prevent recalculation
|
|
144
|
+
const inputStyle = useMemo(
|
|
145
|
+
() => ({
|
|
146
|
+
height: style?.multi_select_height || '52px',
|
|
147
|
+
}),
|
|
148
|
+
[style]
|
|
149
|
+
);
|
|
150
|
+
|
|
115
151
|
return (
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
<div className={styles.AutocompletePlace}>
|
|
153
|
+
<input
|
|
154
|
+
className={styles['AutocompletePlace-input']}
|
|
155
|
+
style={inputStyle}
|
|
156
|
+
type="text"
|
|
157
|
+
value={search || ''}
|
|
158
|
+
onChange={handleSearchChange}
|
|
159
|
+
placeholder="Type an address"
|
|
160
|
+
autoComplete="off"
|
|
161
|
+
/>
|
|
162
|
+
|
|
163
|
+
{overwrite ? (
|
|
164
|
+
<ToggleOnFill
|
|
165
|
+
data-tooltip-id="system-tooltip"
|
|
166
|
+
data-tooltip-content="Enable Autocomplete"
|
|
167
|
+
strokeWidth={2}
|
|
168
|
+
size={24}
|
|
169
|
+
onClick={handleOverwrite}
|
|
170
|
+
className={styles.toggleActive}
|
|
131
171
|
/>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{isLoading && (
|
|
163
|
-
<li className={styles['AutocompletePlace-items']}>
|
|
164
|
-
Loading...
|
|
165
|
-
</li>
|
|
166
|
-
)}
|
|
167
|
-
</ul>
|
|
168
|
-
) : null}
|
|
169
|
-
</div>
|
|
170
|
-
</>
|
|
172
|
+
) : (
|
|
173
|
+
<ToggleOffFill
|
|
174
|
+
data-tooltip-id="system-tooltip"
|
|
175
|
+
data-tooltip-content="Disable Autocomplete"
|
|
176
|
+
strokeWidth={2}
|
|
177
|
+
size={24}
|
|
178
|
+
onClick={handleOverwrite}
|
|
179
|
+
/>
|
|
180
|
+
)}
|
|
181
|
+
|
|
182
|
+
{results.length > 0 && (
|
|
183
|
+
<ul className="AutocompletePlace-results">
|
|
184
|
+
{results.map((place) => (
|
|
185
|
+
<li
|
|
186
|
+
key={place.id}
|
|
187
|
+
className={styles['AutocompletePlace-items']}
|
|
188
|
+
onClick={() => handleItemClicked(place)}
|
|
189
|
+
>
|
|
190
|
+
{place.place_name}
|
|
191
|
+
</li>
|
|
192
|
+
))}
|
|
193
|
+
|
|
194
|
+
{isLoading && (
|
|
195
|
+
<li className={styles['AutocompletePlace-items']}>
|
|
196
|
+
Loading...
|
|
197
|
+
</li>
|
|
198
|
+
)}
|
|
199
|
+
</ul>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
171
202
|
);
|
|
172
|
-
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Add display name for debugging
|
|
206
|
+
VisnsAutocomplete.displayName = 'VisnsAutocomplete';
|
|
173
207
|
|
|
174
208
|
export default VisnsAutocomplete;
|
|
@@ -10,7 +10,6 @@ import React, {
|
|
|
10
10
|
useState,
|
|
11
11
|
} from 'react';
|
|
12
12
|
import { useNavigate } from 'react-router-dom';
|
|
13
|
-
import axios from 'axios';
|
|
14
13
|
import debounce from 'lodash.debounce';
|
|
15
14
|
import moment from 'moment';
|
|
16
15
|
import numeral from 'numeral';
|
|
@@ -26,6 +25,7 @@ import { toast } from 'react-toastify';
|
|
|
26
25
|
import imageCompression from 'browser-image-compression';
|
|
27
26
|
import Vapor from 'laravel-vapor';
|
|
28
27
|
import Lightbox from 'yet-another-react-lightbox';
|
|
28
|
+
import fetchUtil from '../../utils/fetchUtil';
|
|
29
29
|
import { confirmDialog } from '../utils/ConfirmDialog';
|
|
30
30
|
import {
|
|
31
31
|
Alarm,
|
|
@@ -118,7 +118,7 @@ const loadData = async (
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
try {
|
|
121
|
-
const response = await
|
|
121
|
+
const response = await fetchUtil.post(url, params);
|
|
122
122
|
const { data, total } = response.data;
|
|
123
123
|
return { data, count: total };
|
|
124
124
|
} catch (error) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
3
2
|
import { trackPromise } from 'react-promise-tracker';
|
|
4
3
|
import { toast } from 'react-toastify';
|
|
5
4
|
import parse from 'html-react-parser';
|
|
5
|
+
import fetchUtil from '../../utils/fetchUtil';
|
|
6
6
|
|
|
7
7
|
const CustomDownload = (
|
|
8
8
|
url,
|
|
@@ -11,7 +11,18 @@ const CustomDownload = (
|
|
|
11
11
|
successCallback = null,
|
|
12
12
|
errorCallback = null
|
|
13
13
|
) => {
|
|
14
|
-
let headers = {
|
|
14
|
+
let headers = {
|
|
15
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Add CSRF token if available
|
|
19
|
+
const csrfToken = document
|
|
20
|
+
.querySelector('meta[name="csrf-token"]')
|
|
21
|
+
?.getAttribute('content');
|
|
22
|
+
if (csrfToken) {
|
|
23
|
+
headers['X-CSRF-TOKEN'] = csrfToken;
|
|
24
|
+
headers['X-XSRF-TOKEN'] = csrfToken;
|
|
25
|
+
}
|
|
15
26
|
let options = {};
|
|
16
27
|
|
|
17
28
|
if (method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH') {
|
|
@@ -39,7 +50,7 @@ const CustomDownload = (
|
|
|
39
50
|
return trackPromise(
|
|
40
51
|
new Promise((resolve, reject) => {
|
|
41
52
|
// First, make a request without responseType: 'blob' to check for errors
|
|
42
|
-
|
|
53
|
+
fetchUtil({
|
|
43
54
|
method: method,
|
|
44
55
|
data: formData,
|
|
45
56
|
headers: headers,
|
|
@@ -64,7 +75,7 @@ const CustomDownload = (
|
|
|
64
75
|
resolve(response);
|
|
65
76
|
} else {
|
|
66
77
|
// If no error, make the actual blob request
|
|
67
|
-
|
|
78
|
+
fetchUtil({
|
|
68
79
|
method: method,
|
|
69
80
|
data: formData,
|
|
70
81
|
headers: headers,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import axios from 'axios';
|
|
3
2
|
import { trackPromise } from 'react-promise-tracker';
|
|
4
3
|
import { toast } from 'react-toastify';
|
|
5
4
|
import parse from 'html-react-parser';
|
|
@@ -14,7 +13,17 @@ const CustomFetch = (
|
|
|
14
13
|
const baseUrl = '';
|
|
15
14
|
const headers = {
|
|
16
15
|
'Content-Type': 'application/json',
|
|
16
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
17
17
|
};
|
|
18
|
+
|
|
19
|
+
// Add CSRF token if available
|
|
20
|
+
const csrfToken = document
|
|
21
|
+
.querySelector('meta[name="csrf-token"]')
|
|
22
|
+
?.getAttribute('content');
|
|
23
|
+
if (csrfToken) {
|
|
24
|
+
headers['X-CSRF-TOKEN'] = csrfToken;
|
|
25
|
+
headers['X-XSRF-TOKEN'] = csrfToken;
|
|
26
|
+
}
|
|
18
27
|
const MAX_PAYLOAD_SIZE_MB =
|
|
19
28
|
parseFloat(import.meta.env.VITE_MAX_PAYLOAD_SIZE_MB) || 4.5;
|
|
20
29
|
const MAX_PAYLOAD_SIZE = MAX_PAYLOAD_SIZE_MB * 1024 * 1024; // Convert MB to bytes
|
|
@@ -48,36 +57,80 @@ const CustomFetch = (
|
|
|
48
57
|
};
|
|
49
58
|
}
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
// Configure fetch options
|
|
61
|
+
const fetchOptions = {
|
|
52
62
|
method: method,
|
|
53
|
-
data: formData,
|
|
54
63
|
headers: headers,
|
|
55
|
-
url: baseUrl + url,
|
|
56
64
|
};
|
|
57
65
|
|
|
66
|
+
// For GET and HEAD requests, convert formData to query parameters
|
|
67
|
+
if (method.toUpperCase() === 'GET' || method.toUpperCase() === 'HEAD') {
|
|
68
|
+
if (formData && Object.keys(formData).length > 0) {
|
|
69
|
+
// Convert formData to query string
|
|
70
|
+
const queryParams = new URLSearchParams();
|
|
71
|
+
Object.entries(formData).forEach(([key, value]) => {
|
|
72
|
+
if (value !== undefined && value !== null) {
|
|
73
|
+
queryParams.append(key, value);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const queryString = queryParams.toString();
|
|
77
|
+
if (queryString) {
|
|
78
|
+
url += (url.includes('?') ? '&' : '?') + queryString;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// For other methods, add body
|
|
83
|
+
fetchOptions.body = formData ? JSON.stringify(formData) : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
return trackPromise(
|
|
59
87
|
new Promise((resolve, reject) => {
|
|
60
|
-
|
|
61
|
-
.then((response) => {
|
|
62
|
-
|
|
63
|
-
|
|
88
|
+
fetch(baseUrl + url, fetchOptions)
|
|
89
|
+
.then(async (response) => {
|
|
90
|
+
// Parse the response data
|
|
91
|
+
const contentType = response.headers.get('content-type');
|
|
92
|
+
let data;
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
contentType &&
|
|
96
|
+
contentType.includes('application/json')
|
|
97
|
+
) {
|
|
98
|
+
data = await response.json();
|
|
64
99
|
} else {
|
|
65
|
-
|
|
100
|
+
data = await response.text();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Create a response object similar to axios for compatibility
|
|
104
|
+
const responseObj = {
|
|
105
|
+
data: data,
|
|
106
|
+
status: response.status,
|
|
107
|
+
statusText: response.statusText,
|
|
108
|
+
headers: response.headers,
|
|
109
|
+
ok: response.ok,
|
|
110
|
+
};
|
|
66
111
|
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
// Handle error responses (4xx, 5xx)
|
|
114
|
+
const error = new Error(response.statusText);
|
|
115
|
+
error.response = responseObj;
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Handle success
|
|
120
|
+
if (successCallback) {
|
|
121
|
+
successCallback(data);
|
|
122
|
+
} else {
|
|
123
|
+
const error = data.error;
|
|
67
124
|
if (error && error !== '') {
|
|
68
125
|
toast.error(<div>{parse(error)}</div>);
|
|
69
126
|
}
|
|
70
127
|
}
|
|
71
|
-
resolve(
|
|
128
|
+
resolve(responseObj);
|
|
72
129
|
})
|
|
73
130
|
.catch((error) => {
|
|
74
131
|
let errorMessage = '';
|
|
75
132
|
|
|
76
|
-
if (
|
|
77
|
-
error.response &&
|
|
78
|
-
error.response.data &&
|
|
79
|
-
error.response.data.message
|
|
80
|
-
) {
|
|
133
|
+
if (error.response && error.response.data) {
|
|
81
134
|
const { data } = error.response;
|
|
82
135
|
|
|
83
136
|
if (data.errors) {
|
|
@@ -88,7 +141,7 @@ const CustomFetch = (
|
|
|
88
141
|
errorMessage += `${errorMsg}<br />`;
|
|
89
142
|
});
|
|
90
143
|
});
|
|
91
|
-
} else {
|
|
144
|
+
} else if (data.message) {
|
|
92
145
|
if (data.message === 'CSRF token mismatch.') {
|
|
93
146
|
window.location.replace('/login');
|
|
94
147
|
} else {
|