@visns-studio/visns-components 5.6.14 → 5.7.1

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.
@@ -1,36 +1,99 @@
1
1
  .quickActions {
2
2
  width: 100%;
3
- background: rgba(var(--bg-rgb), 0.95);
4
- padding: var(--padding);
5
- border-top: solid 4px rgba(var(--bg-rgb), 1.05);
6
- border-bottom: solid 1px rgba(var(--primary-rgb), 0.25);
3
+ background: rgba(var(--bg-rgb), 0.98);
4
+ padding: 0.75rem var(--padding);
5
+ border-top: solid 3px rgba(var(--primary-rgb), 0.1);
6
+ border-bottom: solid 1px rgba(var(--primary-rgb), 0.15);
7
7
  color: var(--paragraph-color);
8
8
  display: flex;
9
- flex-wrap: nowrap;
9
+ flex-wrap: wrap;
10
10
  align-items: center;
11
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
11
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
12
+ overflow: visible; /* Ensure dropdowns are visible */
13
+ position: relative; /* Create a stacking context */
14
+ z-index: 100; /* Ensure proper stacking */
15
+ gap: 0.5rem; /* Add consistent spacing between elements */
16
+
17
+ .actionIcon {
18
+ width: 32px;
19
+ height: 32px;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ background-color: rgba(var(--primary-rgb), 0.08);
24
+ border-radius: 50%;
25
+ margin-right: 0.75rem;
26
+ position: relative;
27
+ color: rgba(var(--primary-rgb), 0.8);
28
+ transition: all 0.2s ease;
12
29
 
13
- > span {
14
- width: max-content;
15
- display: block;
16
- font-weight: var(--title-weight);
17
- padding: 0 1rem 0 0;
30
+ &:hover {
31
+ background-color: rgba(var(--primary-rgb), 0.12);
32
+ transform: scale(1.05);
33
+ }
34
+
35
+ &::after {
36
+ content: '';
37
+ position: absolute;
38
+ right: -0.5rem;
39
+ top: 50%;
40
+ transform: translateY(-50%);
41
+ height: 60%;
42
+ width: 1px;
43
+ background-color: rgba(var(--primary-rgb), 0.15);
44
+ }
18
45
  }
19
46
 
20
47
  > button {
21
48
  appearance: none;
22
- border: 1px solid rgba(var(--primary-rgb), 0.25);
23
- border-radius: var(--br);
24
- background: transparent;
49
+ border: 1px solid rgba(var(--primary-rgb), 0.2);
50
+ border-radius: var(--br, 4px);
51
+ background: rgba(var(--bg-rgb), 1);
25
52
  width: max-content;
26
53
  color: var(--paragraph-color);
27
- padding: 0.35rem 0.65rem;
28
- margin: 0 0.25rem;
29
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
54
+ padding: 0.4rem 0.8rem;
55
+ margin: 0 0.2rem;
56
+ font-size: 0.85rem;
57
+ font-weight: 500;
58
+ transition: all 0.2s ease;
59
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ min-height: 32px;
64
+ position: relative;
65
+ overflow: hidden;
66
+
67
+ &::before {
68
+ content: '';
69
+ position: absolute;
70
+ top: 0;
71
+ left: 0;
72
+ width: 100%;
73
+ height: 100%;
74
+ background: linear-gradient(
75
+ to bottom,
76
+ rgba(var(--primary-rgb), 0.05),
77
+ transparent
78
+ );
79
+ opacity: 0;
80
+ transition: opacity 0.2s ease;
81
+ }
30
82
 
31
83
  &:hover {
32
- background: var(--tertiary-color);
33
- transform: translateY(-2px);
84
+ background: rgba(var(--bg-rgb), 1);
85
+ transform: translateY(-1px);
86
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.08);
87
+ border-color: rgba(var(--primary-rgb), 0.3);
88
+
89
+ &::before {
90
+ opacity: 1;
91
+ }
92
+ }
93
+
94
+ &:active {
95
+ transform: translateY(0);
96
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
34
97
  }
35
98
  }
36
99
 
@@ -38,24 +101,34 @@
38
101
  display: flex;
39
102
  align-items: center;
40
103
  background: transparent;
41
- margin: 0 0.25rem;
104
+ margin-left: auto; /* Push search to the right */
42
105
  flex-grow: 1;
43
106
  position: relative;
107
+ max-width: 300px; /* Limit width for better appearance */
108
+ overflow: visible; /* Allow dropdown to be visible */
44
109
 
45
110
  input[type='text'] {
46
111
  flex-grow: 1;
47
- border: none;
112
+ border: 1px solid rgba(var(--primary-rgb), 0.2);
48
113
  outline: none;
49
- padding: 4px 0.65rem;
50
- padding-right: 30px;
51
- border-radius: var(--br);
114
+ padding: 0.5rem 0.75rem;
115
+ padding-right: 36px;
116
+ border-radius: var(--br, 4px);
52
117
  height: 32px;
53
- font-size: 14px;
118
+ font-size: 0.85rem;
54
119
  width: 100%;
55
- box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
120
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
121
+ transition: all 0.2s ease;
122
+ background-color: rgba(var(--bg-rgb), 1);
123
+
124
+ &::placeholder {
125
+ color: rgba(var(--paragraph-rgb), 0.5);
126
+ font-size: 0.85rem;
127
+ }
56
128
 
57
129
  &:focus {
58
- box-shadow: 0 0 8px rgba(var(--primary-rgb), 0.5);
130
+ box-shadow: 0 0 0 2px rgba(var(--primary-rgb), 0.15);
131
+ border-color: rgba(var(--primary-rgb), 0.4);
59
132
  }
60
133
  }
61
134
 
@@ -65,35 +138,73 @@
65
138
  display: flex;
66
139
  align-items: center;
67
140
  justify-content: center;
141
+ cursor: pointer;
142
+ width: 24px;
143
+ height: 24px;
144
+ border-radius: 50%;
145
+ transition: background-color 0.2s ease;
146
+
147
+ &:hover {
148
+ background-color: rgba(var(--primary-rgb), 0.05);
149
+ }
68
150
  }
69
151
 
70
152
  .searchIcon {
71
- width: 20px;
72
- height: 20px;
153
+ width: 18px;
154
+ height: 18px;
155
+ color: rgba(var(--paragraph-rgb), 0.6);
156
+ transition: color 0.2s ease;
157
+
158
+ &:hover {
159
+ color: rgba(var(--primary-rgb), 0.8);
160
+ }
73
161
  }
74
162
  }
75
163
 
76
- .searchResults {
164
+ /* Search results styling */
165
+ :global(.AutocompletePlace-results) {
77
166
  position: absolute;
78
167
  top: 100%;
79
168
  width: 100%;
80
- background: var(--third-color);
81
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
82
- z-index: 100;
169
+ background: var(--bg-color, white);
170
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
171
+ z-index: 9999; /* Ensure it appears on top */
83
172
  max-height: 400px;
84
173
  overflow-y: auto;
85
- border-radius: var(--br);
86
- margin-top: 5px;
174
+ overflow-x: hidden; /* Prevent horizontal scrolling */
175
+ border-radius: calc(var(--br, 4px) - 1px);
176
+ margin-top: 6px;
177
+ border: 1px solid rgba(var(--primary-rgb), 0.1);
178
+ scrollbar-width: thin;
179
+ scrollbar-color: rgba(var(--primary-rgb), 0.2) transparent;
180
+
181
+ &::-webkit-scrollbar {
182
+ width: 6px;
183
+ }
184
+
185
+ &::-webkit-scrollbar-track {
186
+ background: transparent;
187
+ }
188
+
189
+ &::-webkit-scrollbar-thumb {
190
+ background-color: rgba(var(--primary-rgb), 0.2);
191
+ border-radius: 3px;
192
+ }
87
193
  }
88
194
 
89
195
  .resultItem {
90
- padding: 0.5rem 1rem;
196
+ padding: 0.5rem 0.75rem;
91
197
  cursor: pointer;
92
- transition: background-color 0.3s ease, transform 0.2s ease;
198
+ transition: all 0.15s ease;
199
+ border-bottom: 1px solid rgba(var(--primary-rgb), 0.05);
200
+ white-space: nowrap;
201
+ overflow: hidden;
202
+ text-overflow: ellipsis;
203
+ width: 100%;
93
204
 
94
205
  &:hover {
95
- background-color: #f2f2f2;
96
- transform: translateX(5px);
206
+ background-color: rgba(var(--primary-rgb), 0.05);
207
+ padding-left: 1rem;
97
208
  }
98
209
 
99
210
  &:last-child {
@@ -102,25 +213,36 @@
102
213
 
103
214
  h3 {
104
215
  margin: 0;
105
- font-weight: var(--title-weight);
106
- color: var(--header-color);
107
- font-size: 14px;
216
+ font-weight: 600;
217
+ color: var(--header-color, #333);
218
+ font-size: 0.8rem;
219
+ padding-bottom: 6px;
220
+ border-bottom: 1px solid rgba(var(--primary-rgb), 0.08);
221
+ text-transform: uppercase;
222
+ letter-spacing: 0.03em;
108
223
  }
109
224
 
110
225
  p {
111
226
  margin: 0.25rem 0 0;
112
227
  color: var(--paragraph-color);
113
- font-size: 13px;
228
+ font-size: 0.85rem;
229
+ white-space: nowrap;
230
+ overflow: hidden;
231
+ text-overflow: ellipsis;
232
+ line-height: 1.4;
114
233
  }
115
234
 
116
235
  &.selected {
117
- background-color: rgba(var(--primary-rgb), 0.1);
236
+ background-color: rgba(var(--primary-rgb), 0.08);
237
+ font-weight: 500;
238
+ padding-left: 1rem;
239
+ border-left: 2px solid rgba(var(--primary-rgb), 0.5);
118
240
  }
119
241
  }
120
242
 
121
243
  .resultItemDivider {
122
244
  height: 1px;
123
- background-color: var(--border-color);
124
- margin: 0.25rem 0;
245
+ background-color: rgba(var(--primary-rgb), 0.08);
246
+ margin: 0.25rem 0.75rem;
125
247
  }
126
248
  }
@@ -223,6 +223,11 @@ input[type]:not([type='search']):not([type='url']):not([type='hidden']):not(
223
223
  backdrop-filter: blur(3px);
224
224
  }
225
225
 
226
+ .modalWide {
227
+ width: 80vw !important;
228
+ max-width: 1200px !important;
229
+ }
230
+
226
231
  .AutocompletePlace-results {
227
232
  z-index: 999;
228
233
  width: 500px;
@@ -0,0 +1,155 @@
1
+ import Swal from 'sweetalert2';
2
+ import '../crm/generic/styles/SweetAlertCustom.css';
3
+
4
+ /**
5
+ * A utility function that replaces react-confirm-alert with SweetAlert2
6
+ * while maintaining a similar look and feel.
7
+ *
8
+ * @param {Object} options - Configuration options
9
+ * @param {string} options.title - The title of the confirmation dialog
10
+ * @param {string} options.message - The message to display in the dialog
11
+ * @param {Array} options.buttons - Array of button objects with label and onClick properties
12
+ * @param {Object} options.customUI - Custom UI function (ignored, for compatibility only)
13
+ * @param {boolean} options.closeOnClickOutside - Whether to close on outside click (default: true)
14
+ * @param {boolean} options.closeOnEscape - Whether to close on escape key (default: true)
15
+ * @param {string} options.keyCodeForClose - Key code for closing (ignored, for compatibility only)
16
+ * @param {boolean} options.willUnmount - Unmount callback (ignored, for compatibility only)
17
+ * @param {boolean} options.afterClose - After close callback (ignored, for compatibility only)
18
+ * @param {boolean} options.onClickOutside - On click outside callback (ignored, for compatibility only)
19
+ * @param {boolean} options.onKeypressEscape - On keypress escape callback (ignored, for compatibility only)
20
+ * @param {Object} options.overlayClassName - Overlay class name (ignored, for compatibility only)
21
+ * @param {Object} options.overlayStyle - Overlay style (ignored, for compatibility only)
22
+ * @param {Object} options.contentClassName - Content class name (ignored, for compatibility only)
23
+ * @param {Object} options.contentStyle - Content style (ignored, for compatibility only)
24
+ * @param {Object} options.bodyClassName - Body class name (ignored, for compatibility only)
25
+ * @param {Object} options.titleClassName - Title class name (ignored, for compatibility only)
26
+ * @param {Object} options.titleStyle - Title style (ignored, for compatibility only)
27
+ * @param {Object} options.messageClassName - Message class name (ignored, for compatibility only)
28
+ * @param {Object} options.messageStyle - Message style (ignored, for compatibility only)
29
+ * @param {Object} options.buttonClassName - Button class name (ignored, for compatibility only)
30
+ * @param {Object} options.buttonStyle - Button style (ignored, for compatibility only)
31
+ * @param {Object} options.childrenClassName - Children class name (ignored, for compatibility only)
32
+ */
33
+ export const confirmDialog = (options) => {
34
+ const {
35
+ title = '',
36
+ message = '',
37
+ buttons = [],
38
+ closeOnClickOutside = true,
39
+ closeOnEscape = true,
40
+ data = null, // Optional data object from the selected row
41
+ } = options;
42
+
43
+ // Find Yes and No buttons if they exist
44
+ const yesButton = buttons.find((btn) => btn.label === 'Yes');
45
+ const noButton = buttons.find((btn) => btn.label === 'No');
46
+
47
+ // Default button text
48
+ const confirmButtonText = yesButton ? yesButton.label : 'Yes';
49
+ const cancelButtonText = noButton ? noButton.label : 'No';
50
+
51
+ // Enhanced message with item details if available
52
+ let enhancedMessage = message;
53
+ let itemDetails = '';
54
+
55
+ // Extract meaningful information from the data object if available
56
+ if (data) {
57
+ // Common identifiers that might be present in data objects
58
+ const identifiers = [
59
+ 'name',
60
+ 'label',
61
+ 'title',
62
+ 'email',
63
+ 'username',
64
+ 'id',
65
+ 'code',
66
+ 'reference',
67
+ ];
68
+
69
+ // Find the first available identifier
70
+ for (const id of identifiers) {
71
+ if (
72
+ data[id] &&
73
+ typeof data[id] === 'string' &&
74
+ data[id].trim() !== ''
75
+ ) {
76
+ itemDetails = data[id];
77
+ break;
78
+ }
79
+ }
80
+
81
+ // If we found an identifier, enhance the message
82
+ if (itemDetails && !message.includes(itemDetails)) {
83
+ // If the message already contains a question mark, add the details before it
84
+ if (message.includes('?')) {
85
+ enhancedMessage = message.replace('?', `"${itemDetails}"?`);
86
+ } else {
87
+ // Otherwise, append the details
88
+ enhancedMessage = `${message} "${itemDetails}"`;
89
+ }
90
+ }
91
+ }
92
+
93
+ // Configure SweetAlert2 options
94
+ const swalOptions = {
95
+ title: title,
96
+ html: enhancedMessage,
97
+ icon: 'question',
98
+ showCancelButton: true,
99
+ confirmButtonText: confirmButtonText,
100
+ cancelButtonText: cancelButtonText,
101
+ confirmButtonColor: 'var(--primary-color, #4f46e5)',
102
+ cancelButtonColor: '#6b7280',
103
+ allowOutsideClick: closeOnClickOutside,
104
+ allowEscapeKey: closeOnEscape,
105
+ reverseButtons: true,
106
+ focusCancel: false,
107
+ buttonsStyling: true,
108
+ showClass: {
109
+ popup: 'swal2-show',
110
+ backdrop: 'swal2-backdrop-show',
111
+ icon: 'swal2-icon-show',
112
+ },
113
+ customClass: {
114
+ container: 'swal2-container',
115
+ popup: 'swal2-popup',
116
+ header: 'swal2-header',
117
+ title: 'swal2-title',
118
+ closeButton: 'swal2-close-button',
119
+ icon: 'swal2-icon',
120
+ image: 'swal2-image',
121
+ htmlContainer: 'swal2-html-container',
122
+ input: 'swal2-input',
123
+ inputLabel: 'swal2-input-label',
124
+ validationMessage: 'swal2-validation-message',
125
+ actions: 'swal2-actions',
126
+ confirmButton: 'swal2-confirm',
127
+ denyButton: 'swal2-deny',
128
+ cancelButton: 'swal2-cancel',
129
+ loader: 'swal2-loader',
130
+ footer: 'swal2-footer',
131
+ },
132
+ };
133
+
134
+ // Show SweetAlert2 dialog
135
+ Swal.fire(swalOptions).then((result) => {
136
+ if (
137
+ result.isConfirmed &&
138
+ yesButton &&
139
+ typeof yesButton.onClick === 'function'
140
+ ) {
141
+ yesButton.onClick();
142
+ } else if (
143
+ result.dismiss === Swal.DismissReason.cancel &&
144
+ noButton &&
145
+ typeof noButton.onClick === 'function'
146
+ ) {
147
+ noButton.onClick();
148
+ }
149
+ });
150
+ };
151
+
152
+ // Export as default for compatibility with react-confirm-alert
153
+ export default {
154
+ confirmAlert: confirmDialog,
155
+ };
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Fetch utility to replace axios with similar API
3
+ * Designed to work with Laravel backend
4
+ */
5
+
6
+ /**
7
+ * Creates an axios-like response object from a fetch response
8
+ * @param {Response} response - The fetch Response object
9
+ * @param {any} data - The parsed response data
10
+ * @returns {Object} - An axios-like response object
11
+ */
12
+ const createAxiosLikeResponse = async (response, data) => {
13
+ return {
14
+ data,
15
+ status: response.status,
16
+ statusText: response.statusText,
17
+ headers: response.headers,
18
+ config: {
19
+ url: response.url,
20
+ method: response.type,
21
+ },
22
+ request: null, // Not available in fetch
23
+ };
24
+ };
25
+
26
+ /**
27
+ * Main fetch function that mimics axios API
28
+ * @param {Object|string} config - Axios-like config object or URL string
29
+ * @returns {Promise} - Promise that resolves to an axios-like response
30
+ */
31
+ const fetchUtil = async (config) => {
32
+ // Handle string input (URL)
33
+ if (typeof config === 'string') {
34
+ config = { url: config };
35
+ }
36
+
37
+ const {
38
+ url,
39
+ method = 'GET',
40
+ headers = {},
41
+ data,
42
+ params,
43
+ responseType,
44
+ withCredentials = false,
45
+ } = config;
46
+
47
+ // Build request URL with query parameters
48
+ let requestUrl = url;
49
+
50
+ // For GET and HEAD requests, convert data to query parameters if it exists
51
+ if (
52
+ (method.toUpperCase() === 'GET' || method.toUpperCase() === 'HEAD') &&
53
+ data &&
54
+ typeof data === 'object' &&
55
+ !(data instanceof FormData)
56
+ ) {
57
+ // Create params object if it doesn't exist
58
+ params = params || {};
59
+ // Merge data into params
60
+ Object.assign(params, data);
61
+ // Remove data since we've moved it to params
62
+ data = undefined;
63
+ }
64
+
65
+ // Add params to URL
66
+ if (params) {
67
+ const queryParams = new URLSearchParams();
68
+ Object.entries(params).forEach(([key, value]) => {
69
+ if (value !== undefined && value !== null) {
70
+ queryParams.append(key, value);
71
+ }
72
+ });
73
+ const queryString = queryParams.toString();
74
+ if (queryString) {
75
+ requestUrl += (url.includes('?') ? '&' : '?') + queryString;
76
+ }
77
+ }
78
+
79
+ // Set up headers
80
+ const requestHeaders = new Headers({
81
+ ...headers,
82
+ });
83
+
84
+ // If not explicitly set and sending data, default to JSON
85
+ if (
86
+ data &&
87
+ !requestHeaders.has('Content-Type') &&
88
+ !(data instanceof FormData)
89
+ ) {
90
+ requestHeaders.set('Content-Type', 'application/json');
91
+ }
92
+
93
+ // Add CSRF token for Laravel if available
94
+ const csrfToken = document
95
+ .querySelector('meta[name="csrf-token"]')
96
+ ?.getAttribute('content');
97
+ if (csrfToken) {
98
+ // Laravel accepts the CSRF token in different headers
99
+ if (!requestHeaders.has('X-CSRF-TOKEN')) {
100
+ requestHeaders.set('X-CSRF-TOKEN', csrfToken);
101
+ }
102
+ if (!requestHeaders.has('X-XSRF-TOKEN')) {
103
+ requestHeaders.set('X-XSRF-TOKEN', csrfToken);
104
+ }
105
+ }
106
+
107
+ // Add X-Requested-With header for Laravel to recognize AJAX requests
108
+ if (!requestHeaders.has('X-Requested-With')) {
109
+ requestHeaders.set('X-Requested-With', 'XMLHttpRequest');
110
+ }
111
+
112
+ // Handle credentials
113
+ const credentials = withCredentials ? 'include' : 'same-origin';
114
+
115
+ // Prepare request options
116
+ const requestOptions = {
117
+ method: method.toUpperCase(),
118
+ headers: requestHeaders,
119
+ credentials,
120
+ };
121
+
122
+ // Add body for non-GET requests
123
+ if (method.toUpperCase() !== 'GET' && method.toUpperCase() !== 'HEAD') {
124
+ // Handle Laravel's _method for PUT/PATCH/DELETE
125
+ if (['PUT', 'PATCH', 'DELETE'].includes(method.toUpperCase())) {
126
+ if (data instanceof FormData) {
127
+ data.append('_method', method.toUpperCase());
128
+ requestOptions.method = 'POST';
129
+ } else if (data && typeof data === 'object') {
130
+ requestOptions.body = JSON.stringify({
131
+ ...data,
132
+ _method: method.toUpperCase(),
133
+ });
134
+ requestOptions.method = 'POST';
135
+ } else {
136
+ // If no data, create a body with just _method
137
+ requestOptions.body = JSON.stringify({
138
+ _method: method.toUpperCase(),
139
+ });
140
+ requestOptions.method = 'POST';
141
+ }
142
+ } else {
143
+ // For regular POST
144
+ if (data instanceof FormData) {
145
+ requestOptions.body = data;
146
+ } else if (data !== undefined) {
147
+ requestOptions.body = JSON.stringify(data);
148
+ }
149
+ }
150
+ }
151
+
152
+ try {
153
+ const response = await fetch(requestUrl, requestOptions);
154
+
155
+ // Parse response based on content type or specified responseType
156
+ let responseData;
157
+
158
+ if (responseType === 'blob') {
159
+ responseData = await response.blob();
160
+ } else if (responseType === 'arraybuffer') {
161
+ responseData = await response.arrayBuffer();
162
+ } else if (responseType === 'text') {
163
+ responseData = await response.text();
164
+ } else {
165
+ // Default: try to parse as JSON, fallback to text
166
+ const contentType = response.headers.get('content-type');
167
+ if (contentType && contentType.includes('application/json')) {
168
+ responseData = await response.json();
169
+ } else {
170
+ responseData = await response.text();
171
+ }
172
+ }
173
+
174
+ const axiosLikeResponse = await createAxiosLikeResponse(
175
+ response,
176
+ responseData
177
+ );
178
+
179
+ // Handle error responses (4xx, 5xx)
180
+ if (!response.ok) {
181
+ const error = new Error(response.statusText || 'Request failed');
182
+ error.response = axiosLikeResponse;
183
+ throw error;
184
+ }
185
+
186
+ return axiosLikeResponse;
187
+ } catch (error) {
188
+ // If the error already has a response property, it's from our code above
189
+ if (!error.response) {
190
+ // Network errors or other fetch errors
191
+ error.response = {
192
+ data: { message: error.message },
193
+ status: 0,
194
+ statusText: error.message,
195
+ };
196
+ }
197
+ throw error;
198
+ }
199
+ };
200
+
201
+ // Add convenience methods to match axios API
202
+ ['get', 'delete', 'head', 'options'].forEach((method) => {
203
+ fetchUtil[method] = (url, config = {}) => {
204
+ // If second parameter is data (object but not a config object with recognized properties)
205
+ if (
206
+ config &&
207
+ typeof config === 'object' &&
208
+ !config.headers &&
209
+ !config.params &&
210
+ !config.responseType &&
211
+ !config.withCredentials
212
+ ) {
213
+ // It's likely data, not config
214
+ return fetchUtil({
215
+ method,
216
+ url,
217
+ params: config, // Use as query parameters
218
+ });
219
+ }
220
+
221
+ return fetchUtil({
222
+ ...config,
223
+ method,
224
+ url,
225
+ });
226
+ };
227
+ });
228
+
229
+ ['post', 'put', 'patch'].forEach((method) => {
230
+ fetchUtil[method] = (url, data, config = {}) => {
231
+ return fetchUtil({
232
+ ...config,
233
+ method,
234
+ url,
235
+ data,
236
+ });
237
+ };
238
+ });
239
+
240
+ export default fetchUtil;