@ibdop/platform-kit 1.0.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.
Files changed (51) hide show
  1. package/README.md +63 -0
  2. package/dist/components/ErrorBoundary.d.ts +68 -0
  3. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  4. package/dist/components/Notification.d.ts +46 -0
  5. package/dist/components/Notification.d.ts.map +1 -0
  6. package/dist/components/VersionInfo.d.ts +18 -0
  7. package/dist/components/VersionInfo.d.ts.map +1 -0
  8. package/dist/components/index.d.ts +8 -0
  9. package/dist/components/index.d.ts.map +1 -0
  10. package/dist/hooks/index.d.ts +9 -0
  11. package/dist/hooks/index.d.ts.map +1 -0
  12. package/dist/hooks/useApi.d.ts +65 -0
  13. package/dist/hooks/useApi.d.ts.map +1 -0
  14. package/dist/hooks/useInfoData.d.ts +35 -0
  15. package/dist/hooks/useInfoData.d.ts.map +1 -0
  16. package/dist/hooks/usePermissions.d.ts +23 -0
  17. package/dist/hooks/usePermissions.d.ts.map +1 -0
  18. package/dist/hooks/useShellAuth.d.ts +23 -0
  19. package/dist/hooks/useShellAuth.d.ts.map +1 -0
  20. package/dist/hooks/useV1Config.d.ts +27 -0
  21. package/dist/hooks/useV1Config.d.ts.map +1 -0
  22. package/dist/index.cjs.js +18 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.es.js +1500 -0
  26. package/dist/index.umd.js +18 -0
  27. package/dist/services/api.d.ts +59 -0
  28. package/dist/services/api.d.ts.map +1 -0
  29. package/dist/services/index.d.ts +8 -0
  30. package/dist/services/index.d.ts.map +1 -0
  31. package/dist/services/logger.d.ts +41 -0
  32. package/dist/services/logger.d.ts.map +1 -0
  33. package/dist/types/index.d.ts +178 -0
  34. package/dist/types/index.d.ts.map +1 -0
  35. package/package.json +90 -0
  36. package/src/components/ErrorBoundary.tsx +292 -0
  37. package/src/components/Notification.tsx +297 -0
  38. package/src/components/VersionInfo.tsx +271 -0
  39. package/src/components/index.ts +14 -0
  40. package/src/global.d.ts +40 -0
  41. package/src/hooks/index.ts +13 -0
  42. package/src/hooks/useApi.ts +314 -0
  43. package/src/hooks/useInfoData.ts +124 -0
  44. package/src/hooks/usePermissions.ts +88 -0
  45. package/src/hooks/useShellAuth.ts +145 -0
  46. package/src/hooks/useV1Config.ts +112 -0
  47. package/src/index.ts +17 -0
  48. package/src/services/api.ts +290 -0
  49. package/src/services/index.ts +9 -0
  50. package/src/services/logger.ts +71 -0
  51. package/src/types/index.ts +215 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * VersionInfo - Компонент для отображения информации о версии MF
3
+ *
4
+ * Показывает popover с детальной информацией о версии, Git, CI/CD
5
+ */
6
+
7
+ import React, { useState } from 'react'
8
+ import { useInfoData } from '../hooks/useInfoData'
9
+ import type { InfoData } from '../types'
10
+
11
+ // Icons (mock - in real implementation use @coreui/icons)
12
+ const icons = {
13
+ info: 'ℹ️',
14
+ code: '💻',
15
+ link: '🔗',
16
+ user: '👤',
17
+ clock: '🕐',
18
+ apps: '📦',
19
+ storage: '💾',
20
+ tags: '🏷️',
21
+ spreadsheet: '📊',
22
+ }
23
+
24
+ // MF Name for logging
25
+ const MF_NAME = 'platform-kit'
26
+
27
+ // Development mode flag
28
+ const isDev = import.meta.env?.DEV === true || import.meta.env?.MODE === 'development'
29
+
30
+ /**
31
+ * Logger for VersionInfo
32
+ */
33
+ const logger = {
34
+ log: (...args: unknown[]) => {
35
+ if (isDev) console.log(`[${MF_NAME}]`, ...args)
36
+ },
37
+ warn: (...args: unknown[]) => {
38
+ if (isDev) console.warn(`[${MF_NAME}]`, ...args)
39
+ },
40
+ error: (...args: unknown[]) => {
41
+ console.error(`[${MF_NAME}]`, ...args)
42
+ },
43
+ }
44
+
45
+ /**
46
+ * Props для VersionInfo
47
+ */
48
+ interface VersionInfoProps {
49
+ mfeName: string
50
+ }
51
+
52
+ /**
53
+ * VersionInfo - компонент для отображения информации о версии
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * <VersionInfo mfeName="@ib-dop/mf-home" />
58
+ * ```
59
+ */
60
+ export default function VersionInfo({ mfeName }: VersionInfoProps): React.ReactElement {
61
+ const { data: infoData, isLoading, error } = useInfoData({ mfeName })
62
+ const [visible, setVisible] = useState(false)
63
+
64
+ logger.log('VersionInfo rendered:', { mfeName, isLoading, error })
65
+
66
+ if (isLoading) {
67
+ return (
68
+ <span className="text-muted small me-2">
69
+ <span className="me-1">⏳</span>
70
+ Загрузка...
71
+ </span>
72
+ )
73
+ }
74
+
75
+ if (error || !infoData) {
76
+ logger.warn('Failed to load version info:', error)
77
+ return (
78
+ <span className="text-muted small me-2" title={`Ошибка: ${error || 'нет данных'}`}>
79
+ <span className="me-1">ℹ️</span>
80
+ N/A
81
+ </span>
82
+ )
83
+ }
84
+
85
+ const info = infoData as Record<string, string>
86
+ const displayVersion = info.BUILD_VERSION || info.IMAGE_VERSION || info.APP_NAME || 'N/A'
87
+
88
+ // Collect available fields
89
+ const availableFields: { key: string; value: string; label: string; icon: string }[] = []
90
+
91
+ if (info.APP_NAME) {
92
+ availableFields.push({ key: 'APP_NAME', value: info.APP_NAME, label: 'Приложение', icon: icons.apps })
93
+ }
94
+ if (info.BUILD_VERSION) {
95
+ availableFields.push({ key: 'BUILD_VERSION', value: info.BUILD_VERSION, label: 'Версия', icon: icons.tags })
96
+ }
97
+ if (info.IMAGE_VERSION) {
98
+ availableFields.push({ key: 'IMAGE_VERSION', value: info.IMAGE_VERSION, label: 'Образ', icon: icons.storage })
99
+ }
100
+ if (info.GIT_COMMIT) {
101
+ availableFields.push({ key: 'GIT_COMMIT', value: info.GIT_COMMIT, label: 'Commit', icon: icons.spreadsheet })
102
+ }
103
+ if (info.GIT_BRANCH) {
104
+ availableFields.push({ key: 'GIT_BRANCH', value: info.GIT_BRANCH, label: 'Ветка', icon: icons.spreadsheet })
105
+ }
106
+ if (info.CI_COMMIT_AUTHOR) {
107
+ availableFields.push({ key: 'CI_COMMIT_AUTHOR', value: info.CI_COMMIT_AUTHOR, label: 'Автор', icon: icons.user })
108
+ }
109
+ if (info.CI_COMMIT_TIMESTAMP) {
110
+ availableFields.push({ key: 'CI_COMMIT_TIMESTAMP', value: info.CI_COMMIT_TIMESTAMP, label: 'Дата', icon: icons.clock })
111
+ }
112
+ if (info.CI_JOB_URL) {
113
+ availableFields.push({ key: 'CI_JOB_URL', value: info.CI_JOB_URL, label: 'CI Job', icon: icons.link })
114
+ }
115
+ if (info.CI_PIPELINE_URL) {
116
+ availableFields.push({ key: 'CI_PIPELINE_URL', value: info.CI_PIPELINE_URL, label: 'Pipeline', icon: icons.link })
117
+ }
118
+ if (info.CI_COMMIT_MESSAGE) {
119
+ const msg = info.CI_COMMIT_MESSAGE.length > 60
120
+ ? info.CI_COMMIT_MESSAGE.substring(0, 57) + '...'
121
+ : info.CI_COMMIT_MESSAGE
122
+ availableFields.push({ key: 'CI_COMMIT_MESSAGE', value: info.CI_COMMIT_MESSAGE, label: 'Сообщение', icon: icons.code })
123
+ }
124
+
125
+ return (
126
+ <div style={{ display: 'inline-block', position: 'relative' }}>
127
+ {/* Button */}
128
+ <button
129
+ onClick={() => setVisible(!visible)}
130
+ style={{
131
+ background: 'transparent',
132
+ border: 'none',
133
+ cursor: 'pointer',
134
+ padding: '4px 8px',
135
+ fontSize: '14px',
136
+ display: 'inline-flex',
137
+ alignItems: 'center',
138
+ }}
139
+ title="Информация о версии"
140
+ >
141
+ <span className="me-1">{icons.info}</span>
142
+ <span className="text-dark">{displayVersion}</span>
143
+ </button>
144
+
145
+ {/* Popover */}
146
+ {visible && (
147
+ <div
148
+ style={{
149
+ position: 'absolute',
150
+ top: '100%',
151
+ right: 0,
152
+ zIndex: 1000,
153
+ minWidth: '300px',
154
+ background: 'white',
155
+ border: '1px solid #dee2e6',
156
+ borderRadius: '8px',
157
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
158
+ padding: '16px',
159
+ marginTop: '4px',
160
+ }}
161
+ >
162
+ {/* Header */}
163
+ <div style={{
164
+ marginBottom: '12px',
165
+ paddingBottom: '8px',
166
+ borderBottom: '1px solid #dee2e6',
167
+ display: 'flex',
168
+ alignItems: 'center',
169
+ }}>
170
+ <span className="me-2">{icons.apps}</span>
171
+ <strong>{info.APP_NAME || mfeName}</strong>
172
+ </div>
173
+
174
+ {/* Version */}
175
+ <div style={{ marginBottom: '12px' }}>
176
+ <span className="text-muted">Версия: </span>
177
+ <strong>{displayVersion}</strong>
178
+ </div>
179
+
180
+ {/* Fields */}
181
+ {availableFields.length > 0 && (
182
+ <div style={{ fontSize: '13px' }}>
183
+ {availableFields.map(({ key, value, label, icon }) => {
184
+ const displayValue = value.length > 40 && (key.includes('URL') || key.includes('MESSAGE'))
185
+ ? `${value.substring(0, 37)}...`
186
+ : value
187
+
188
+ return (
189
+ <div key={key} style={{ marginBottom: '8px', display: 'flex', alignItems: 'flex-start' }}>
190
+ <span className="me-2" style={{ flexShrink: 0 }}>{icon}</span>
191
+ <div style={{ flex: 1, minWidth: 0 }}>
192
+ <div className="small text-muted">{label}</div>
193
+ <div
194
+ className="font-monospace small text-truncate"
195
+ style={{ maxWidth: '100%' }}
196
+ title={value}
197
+ >
198
+ {key.includes('URL') ? (
199
+ <a
200
+ href={value}
201
+ target="_blank"
202
+ rel="noopener noreferrer"
203
+ style={{ color: '#007bff' }}
204
+ >
205
+ {displayValue}
206
+ </a>
207
+ ) : (
208
+ displayValue
209
+ )}
210
+ </div>
211
+ </div>
212
+ </div>
213
+ )
214
+ })}
215
+ </div>
216
+ )}
217
+
218
+ {availableFields.length === 0 && (
219
+ <div className="text-center text-muted py-2 small">
220
+ Нет информации о версии
221
+ </div>
222
+ )}
223
+
224
+ {/* Footer */}
225
+ <div style={{
226
+ marginTop: '12px',
227
+ paddingTop: '8px',
228
+ borderTop: '1px solid #dee2e6',
229
+ textAlign: 'center',
230
+ fontSize: '12px',
231
+ color: '#6c757d',
232
+ }}>
233
+ IngoBank DevOps Platform
234
+ </div>
235
+
236
+ {/* Close button */}
237
+ <button
238
+ onClick={() => setVisible(false)}
239
+ style={{
240
+ position: 'absolute',
241
+ top: '8px',
242
+ right: '8px',
243
+ background: 'transparent',
244
+ border: 'none',
245
+ cursor: 'pointer',
246
+ fontSize: '16px',
247
+ color: '#6c757d',
248
+ }}
249
+ >
250
+ ×
251
+ </button>
252
+ </div>
253
+ )}
254
+
255
+ {/* Click outside to close */}
256
+ {visible && (
257
+ <div
258
+ onClick={() => setVisible(false)}
259
+ style={{
260
+ position: 'fixed',
261
+ top: 0,
262
+ left: 0,
263
+ right: 0,
264
+ bottom: 0,
265
+ zIndex: 999,
266
+ }}
267
+ />
268
+ )}
269
+ </div>
270
+ )
271
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Components barrel export
3
+ */
4
+
5
+ export { default as ErrorBoundary } from './ErrorBoundary'
6
+
7
+ export { default as VersionInfo } from './VersionInfo'
8
+
9
+ export { NotificationProvider, useNotification, dispatchNotification } from './Notification'
10
+
11
+ export type {
12
+ NotificationPayload,
13
+ NotificationType,
14
+ } from '../types'
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Global type declarations for platform-kit
3
+ */
4
+
5
+ interface ImportMetaEnv {
6
+ readonly DEV: boolean
7
+ readonly MODE: string
8
+ readonly BASE_URL: string
9
+ readonly PROD: boolean
10
+ }
11
+
12
+ interface ImportMeta {
13
+ readonly env: ImportMetaEnv
14
+ }
15
+
16
+ // Window extensions for shell integration
17
+ interface Window {
18
+ __SHELL_AUTH_INSTANCE__?: {
19
+ isAuthenticated: boolean
20
+ user?: {
21
+ profile?: {
22
+ access_token?: string
23
+ }
24
+ }
25
+ signinRedirect?: () => Promise<void>
26
+ removeUser?: () => Promise<void>
27
+ [key: string]: unknown
28
+ }
29
+ __SHELL_AUTH__?: {
30
+ authInstance?: {
31
+ isAuthenticated: boolean
32
+ user?: {
33
+ profile?: {
34
+ access_token?: string
35
+ }
36
+ }
37
+ }
38
+ }
39
+ __MF_NAME__?: string
40
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Hooks barrel export
3
+ */
4
+
5
+ export { useShellAuth, getAuth } from './useShellAuth'
6
+
7
+ export { useInfoData } from './useInfoData'
8
+
9
+ export { useV1Config } from './useV1Config'
10
+
11
+ export { useApi } from './useApi'
12
+
13
+ export { usePermissions } from './usePermissions'
@@ -0,0 +1,314 @@
1
+ /**
2
+ * useApi - Унифицированный хук для API запросов
3
+ *
4
+ * Предоставляет удобный интерфейс для работы с API с автоматической
5
+ * обработкой ошибок и уведомлениями.
6
+ */
7
+
8
+ import { useState, useCallback } from 'react'
9
+ import type { ApiError, ApiResponse, NotificationPayload } from '../types'
10
+
11
+ // Development mode flag
12
+ const isDev = import.meta.env?.DEV === true || import.meta.env?.MODE === 'development'
13
+
14
+ /**
15
+ * Logger for useApi
16
+ */
17
+ const logger = {
18
+ log: (...args: unknown[]) => {
19
+ if (isDev) console.log('[useApi]', ...args)
20
+ },
21
+ warn: (...args: unknown[]) => {
22
+ if (isDev) console.warn('[useApi]', ...args)
23
+ },
24
+ error: (...args: unknown[]) => {
25
+ console.error('[useApi]', ...args)
26
+ },
27
+ }
28
+
29
+
30
+
31
+ /**
32
+ * Dispatch notification to shell
33
+ */
34
+ export function dispatchNotification(notification: Omit<NotificationPayload, 'timestamp' | 'mfeName'>): void {
35
+ if (typeof window === 'undefined') return
36
+
37
+ const mfeName = (window as unknown as { __MF_NAME__?: string }).__MF_NAME__ || 'unknown'
38
+
39
+ const payload: NotificationPayload = {
40
+ ...notification,
41
+ mfeName,
42
+ timestamp: Date.now(),
43
+ }
44
+
45
+ window.dispatchEvent(new CustomEvent('mfe-notification', {
46
+ detail: payload,
47
+ bubbles: true,
48
+ }))
49
+
50
+ logger.log('Notification dispatched:', payload)
51
+ }
52
+
53
+ /**
54
+ * Dispatch error notification to shell
55
+ */
56
+ export function dispatchErrorNotification(error: ApiError, context?: string): void {
57
+ const messages: Record<ApiError['type'], { title: string; message: string }> = {
58
+ network: {
59
+ title: 'Нет подключения',
60
+ message: 'Сервер недоступен. Проверьте подключение к интернету.',
61
+ },
62
+ unauthorized: {
63
+ title: 'Требуется вход',
64
+ message: 'Ваша сессия истекла. Войдите в систему снова.',
65
+ },
66
+ forbidden: {
67
+ title: 'Доступ запрещён',
68
+ message: 'У вас нет прав для выполнения этого действия.',
69
+ },
70
+ not_found: {
71
+ title: 'Не найдено',
72
+ message: 'Запрошенный ресурс не найден.',
73
+ },
74
+ server: {
75
+ title: 'Ошибка сервера',
76
+ message: 'Произошла ошибка на сервере. Попробуйте позже.',
77
+ },
78
+ client: {
79
+ title: 'Ошибка',
80
+ message: error.message || 'Произошла ошибка при выполнении запроса.',
81
+ },
82
+ unknown: {
83
+ title: 'Неизвестная ошибка',
84
+ message: error.message || 'Произошла неизвестная ошибка.',
85
+ },
86
+ }
87
+
88
+ const msg = messages[error.type] || messages.unknown
89
+
90
+ dispatchNotification({
91
+ type: error.type === 'network' ? 'warning' : 'error',
92
+ title: msg.title,
93
+ message: context ? `${msg.message} (${context})` : msg.message,
94
+ })
95
+ }
96
+
97
+ /**
98
+ * Interface для конфигурации useApi
99
+ */
100
+ export interface UseApiConfig<T = never> {
101
+ notifyOnError?: boolean
102
+ notifyOnSuccess?: boolean
103
+ successMessage?: string
104
+ errorContext?: string
105
+ onSuccess?: (data: T) => void
106
+ onError?: (error: ApiError) => void
107
+ }
108
+
109
+ /**
110
+ * Interface для результата useApi
111
+ */
112
+ export interface UseApiResult<T> {
113
+ data: T | null
114
+ error: ApiError | null
115
+ isLoading: boolean
116
+ isError: boolean
117
+ isSuccess: boolean
118
+ execute: () => Promise<T | null>
119
+ reset: () => void
120
+ }
121
+
122
+ /**
123
+ * Централизованный хук для API запросов
124
+ *
125
+ * @param request - функция, возвращающая Promise<ApiResponse<T>>
126
+ * @param config - конфигурация
127
+ * @returns UseApiResult<T>
128
+ *
129
+ * @example
130
+ * ```tsx
131
+ * const { data, isLoading, isError, execute } = useApi<User[]>(
132
+ * () => fetch('/api/users').then(r => r.json()),
133
+ * { notifyOnError: true, errorContext: 'загрузка пользователей' }
134
+ * )
135
+ * ```
136
+ */
137
+ export function useApi<T>(
138
+ request: () => Promise<ApiResponse<T>>,
139
+ config: UseApiConfig<T> = {}
140
+ ): UseApiResult<T> {
141
+ const {
142
+ notifyOnError = true,
143
+ notifyOnSuccess = false,
144
+ successMessage,
145
+ errorContext,
146
+ onSuccess,
147
+ onError,
148
+ } = config
149
+
150
+ const [data, setData] = useState<T | null>(null)
151
+ const [error, setError] = useState<ApiError | null>(null)
152
+ const [isLoading, setIsLoading] = useState(false)
153
+
154
+ const isError = error !== null
155
+ const isSuccess = data !== null && !isLoading && !isError
156
+
157
+ const execute = useCallback(async () => {
158
+ setIsLoading(true)
159
+ setError(null)
160
+
161
+ try {
162
+ const response = await request()
163
+
164
+ if (response.ok) {
165
+ setData(response.data)
166
+
167
+ if (notifyOnSuccess && successMessage) {
168
+ dispatchNotification({
169
+ type: 'success',
170
+ title: 'Успешно',
171
+ message: successMessage,
172
+ })
173
+ }
174
+
175
+ onSuccess?.(response.data)
176
+ return response.data
177
+ } else {
178
+ // Handle API error
179
+ const apiError: ApiError = {
180
+ message: (response.data as unknown as string) || 'Request failed',
181
+ status: response.status,
182
+ type: 'client',
183
+ timestamp: Date.now(),
184
+ }
185
+
186
+ setError(apiError)
187
+
188
+ if (notifyOnError) {
189
+ dispatchErrorNotification(apiError, errorContext)
190
+ }
191
+
192
+ onError?.(apiError)
193
+ return null
194
+ }
195
+ } catch (err) {
196
+ // Handle network/server errors
197
+ const apiError = err as ApiError
198
+ setError(apiError)
199
+
200
+ if (notifyOnError) {
201
+ dispatchErrorNotification(apiError, errorContext)
202
+ }
203
+
204
+ onError?.(apiError)
205
+ return null
206
+ } finally {
207
+ setIsLoading(false)
208
+ }
209
+ }, [request, notifyOnError, notifyOnSuccess, successMessage, errorContext, onSuccess, onError])
210
+
211
+ const reset = useCallback(() => {
212
+ setData(null)
213
+ setError(null)
214
+ setIsLoading(false)
215
+ }, [])
216
+
217
+ return {
218
+ data,
219
+ error,
220
+ isLoading,
221
+ isError,
222
+ isSuccess,
223
+ execute,
224
+ reset,
225
+ }
226
+ }
227
+
228
+ // Convenience hooks for common HTTP methods
229
+
230
+ /**
231
+ * GET запрос
232
+ */
233
+ export function useGet<T>(
234
+ url: string,
235
+ params?: Record<string, unknown>,
236
+ config: UseApiConfig<T> = {}
237
+ ): UseApiResult<T> {
238
+ const request = () =>
239
+ fetch(url, params ? {
240
+ method: 'GET',
241
+ headers: { 'Content-Type': 'application/json' }
242
+ } : {})
243
+ .then(async (response) => {
244
+ const data = await response.json()
245
+ return { data, status: response.status, ok: response.ok } as ApiResponse<T>
246
+ })
247
+
248
+ return useApi(request, config)
249
+ }
250
+
251
+ /**
252
+ * POST запрос
253
+ */
254
+ export function usePost<T>(
255
+ url: string,
256
+ data?: unknown,
257
+ config: UseApiConfig<T> = {}
258
+ ): UseApiResult<T> {
259
+ const request = () =>
260
+ fetch(url, {
261
+ method: 'POST',
262
+ headers: { 'Content-Type': 'application/json' },
263
+ body: data ? JSON.stringify(data) : undefined,
264
+ })
265
+ .then(async (response) => {
266
+ const responseData = await response.json()
267
+ return { data: responseData, status: response.status, ok: response.ok } as ApiResponse<T>
268
+ })
269
+
270
+ return useApi(request, config)
271
+ }
272
+
273
+ /**
274
+ * PUT запрос
275
+ */
276
+ export function usePut<T>(
277
+ url: string,
278
+ data?: unknown,
279
+ config: UseApiConfig<T> = {}
280
+ ): UseApiResult<T> {
281
+ const request = () =>
282
+ fetch(url, {
283
+ method: 'PUT',
284
+ headers: { 'Content-Type': 'application/json' },
285
+ body: data ? JSON.stringify(data) : undefined,
286
+ })
287
+ .then(async (response) => {
288
+ const responseData = await response.json()
289
+ return { data: responseData, status: response.status, ok: response.ok } as ApiResponse<T>
290
+ })
291
+
292
+ return useApi(request, config)
293
+ }
294
+
295
+ /**
296
+ * DELETE запрос
297
+ */
298
+ export function useDel<T>(
299
+ url: string,
300
+ config: UseApiConfig<T> = {}
301
+ ): UseApiResult<T> {
302
+ const request = () =>
303
+ fetch(url, {
304
+ method: 'DELETE',
305
+ headers: { 'Content-Type': 'application/json' },
306
+ })
307
+ .then(async (response) => {
308
+ const responseData = await response.json()
309
+ return { data: responseData, status: response.status, ok: response.ok } as ApiResponse<T>
310
+ })
311
+
312
+ return useApi(request, config)
313
+ }
314
+