@kyro-cms/admin 0.3.2 → 0.3.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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
package/src/lib/api.ts CHANGED
@@ -1,75 +1,136 @@
1
- export interface ApiResponse<T = any> {
1
+ import { apiPath, adminPath, resolveApi, resolveAdmin, resolveMedia } from "./paths";
2
+ import { toast } from "./stores";
3
+
4
+ export interface ApiResponse<T = unknown> {
2
5
  docs?: T[];
3
6
  doc?: T;
4
7
  totalDocs?: number;
5
8
  error?: string;
6
9
  }
7
10
 
8
- export async function apiGet<T = any>(
11
+ export { resolveApi as resolveApi, resolveAdmin as resolveAdminUrl, resolveApi as resolveUrl, resolveMedia as resolveMedia };
12
+
13
+ const API_BASE = apiPath;
14
+ const ADMIN_BASE = adminPath;
15
+
16
+ const TOKEN_REFRESH_URL = "/api/auth/refresh";
17
+
18
+ async function refreshToken(): Promise<boolean> {
19
+ try {
20
+ const response = await fetch(resolveApi(TOKEN_REFRESH_URL), {
21
+ method: "POST",
22
+ credentials: "include",
23
+ });
24
+ return response.ok;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ export async function fetchWithAuth(
9
31
  url: string,
10
- options?: RequestInit,
11
- ): Promise<T> {
12
- const response = await fetch(url, {
32
+ options: RequestInit = {},
33
+ ): Promise<Response> {
34
+ const headers: Record<string, string> = {};
35
+ if (!(options.body instanceof FormData)) {
36
+ headers["Content-Type"] = "application/json";
37
+ }
38
+ Object.assign(headers, options.headers as Record<string, string> | undefined);
39
+
40
+ const response = await fetch(resolveApi(url), {
13
41
  ...options,
14
42
  credentials: "include",
43
+ headers,
15
44
  });
45
+
46
+ if (response.status === 401) {
47
+ const refreshed = await refreshToken();
48
+ if (refreshed) {
49
+ const retryHeaders: Record<string, string> = {};
50
+ if (!(options.body instanceof FormData)) {
51
+ retryHeaders["Content-Type"] = "application/json";
52
+ }
53
+ Object.assign(retryHeaders, options.headers as Record<string, string> | undefined);
54
+ const retryResponse = await fetch(resolveApi(url), {
55
+ ...options,
56
+ credentials: "include",
57
+ headers: retryHeaders,
58
+ });
59
+ return retryResponse;
60
+ }
61
+ window.location.href = "/admin/login";
62
+ }
63
+
64
+ return response;
65
+ }
66
+
67
+ export async function apiGet<T = unknown>(
68
+ url: string,
69
+ options?: RequestInit & { autoToast?: boolean },
70
+ ): Promise<T> {
71
+ const { autoToast = true, ...fetchOptions } = options || {};
72
+ const response = await fetchWithAuth(url, fetchOptions);
16
73
  if (!response.ok) {
17
- throw new Error(`API Error: ${response.status}`);
74
+ const errorMsg = `GET Failed: ${response.status}`;
75
+ if (autoToast) toast.error(errorMsg);
76
+ throw new Error(errorMsg);
18
77
  }
19
78
  return response.json();
20
79
  }
21
80
 
22
- export async function apiPost<T = any>(
81
+ export async function apiPost<T = unknown>(
23
82
  url: string,
24
- body?: any,
25
- options?: RequestInit,
83
+ body?: unknown,
84
+ options?: RequestInit & { autoToast?: boolean },
26
85
  ): Promise<T> {
27
- const response = await fetch(url, {
86
+ const { autoToast = true, ...fetchOptions } = options || {};
87
+ const response = await fetchWithAuth(url, {
28
88
  method: "POST",
29
- headers: {
30
- "Content-Type": "application/json",
31
- ...options?.headers,
32
- },
33
89
  body: body ? JSON.stringify(body) : undefined,
34
- credentials: "include",
35
- ...options,
90
+ ...fetchOptions,
36
91
  });
37
92
  if (!response.ok) {
38
- throw new Error(`API Error: ${response.status}`);
93
+ let errorMessage = `POST Failed: ${response.status}`;
94
+ try {
95
+ const errorData = await response.json();
96
+ if (errorData.error) errorMessage = errorData.error;
97
+ } catch {}
98
+ if (autoToast) toast.error(errorMessage);
99
+ throw new Error(errorMessage);
39
100
  }
40
101
  return response.json();
41
102
  }
42
103
 
43
- export async function apiPatch<T = any>(
104
+ export async function apiPatch<T = unknown>(
44
105
  url: string,
45
- body?: any,
46
- options?: RequestInit,
106
+ body?: unknown,
107
+ options?: RequestInit & { autoToast?: boolean },
47
108
  ): Promise<T> {
48
- const response = await fetch(url, {
109
+ const { autoToast = true, ...fetchOptions } = options || {};
110
+ const response = await fetchWithAuth(url, {
49
111
  method: "PATCH",
50
- headers: {
51
- "Content-Type": "application/json",
52
- ...options?.headers,
53
- },
54
112
  body: body ? JSON.stringify(body) : undefined,
55
- credentials: "include",
56
- ...options,
113
+ ...fetchOptions,
57
114
  });
58
115
  if (!response.ok) {
59
- throw new Error(`API Error: ${response.status}`);
116
+ let errorMessage = `Update Failed: ${response.status}`;
117
+ try {
118
+ const errorData = await response.json();
119
+ if (errorData.error) errorMessage = errorData.error;
120
+ } catch {}
121
+ if (autoToast) toast.error(errorMessage);
122
+ throw new Error(errorMessage);
60
123
  }
61
124
  return response.json();
62
125
  }
63
126
 
64
- export async function apiPatchNoThrow<T = any>(
127
+ export async function apiPatchNoThrow<T = unknown>(
65
128
  url: string,
66
- body?: any,
129
+ body?: unknown,
67
130
  ): Promise<{ ok: boolean; data?: T; error?: string }> {
68
- const response = await fetch(url, {
131
+ const response = await fetchWithAuth(url, {
69
132
  method: "PATCH",
70
- headers: { "Content-Type": "application/json" },
71
133
  body: body ? JSON.stringify(body) : undefined,
72
- credentials: "include",
73
134
  });
74
135
  if (!response.ok) {
75
136
  return { ok: false, error: `Error: ${response.status}` };
@@ -78,22 +139,30 @@ export async function apiPatchNoThrow<T = any>(
78
139
  return { ok: true, data };
79
140
  }
80
141
 
81
- export async function apiDelete<T = any>(
142
+ export async function apiDelete<T = unknown>(
82
143
  url: string,
83
- options?: RequestInit,
144
+ options?: RequestInit & { autoToast?: boolean },
84
145
  ): Promise<T> {
85
- const response = await fetch(url, {
146
+ const { autoToast = true, ...fetchOptions } = options || {};
147
+ const response = await fetchWithAuth(url, {
86
148
  method: "DELETE",
87
- credentials: "include",
88
- ...options,
149
+ ...fetchOptions,
89
150
  });
90
151
  if (!response.ok) {
91
- throw new Error(`API Error: ${response.status}`);
152
+ let errorMessage = `Delete Failed: ${response.status}`;
153
+ try {
154
+ const errorData = await response.json();
155
+ if (errorData.error) errorMessage = errorData.error;
156
+ } catch {}
157
+ if (autoToast) toast.error(errorMessage);
158
+ throw new Error(errorMessage);
92
159
  }
93
- return response.json();
160
+ const text = await response.text();
161
+ if (!text) return {} as T;
162
+ return JSON.parse(text);
94
163
  }
95
164
 
96
- export function buildQueryString(params: Record<string, any>): string {
165
+ export function buildQueryString(params: Record<string, unknown>): string {
97
166
  const urlParams = new URLSearchParams();
98
167
  for (const [key, value] of Object.entries(params)) {
99
168
  if (value !== undefined && value !== null && value !== "") {
@@ -124,9 +193,9 @@ export function buildSearchQuery(
124
193
 
125
194
  export function buildCollectionUrl(
126
195
  collection: string,
127
- params?: Record<string, any>,
196
+ params?: Record<string, unknown>,
128
197
  ): string {
129
- let url = `/api/${collection}`;
198
+ let url = `${API_BASE}/${collection}`;
130
199
  if (params) {
131
200
  const query = buildQueryString(params);
132
201
  if (query) url += `?${query}`;
@@ -137,9 +206,9 @@ export function buildCollectionUrl(
137
206
  export function buildDocumentUrl(
138
207
  collection: string,
139
208
  id: string,
140
- params?: Record<string, any>,
209
+ params?: Record<string, unknown>,
141
210
  ): string {
142
- let url = `/api/${collection}/${id}`;
211
+ let url = `${API_BASE}/${collection}/${id}`;
143
212
  if (params) {
144
213
  const query = buildQueryString(params);
145
214
  if (query) url += `?${query}`;
@@ -147,17 +216,68 @@ export function buildDocumentUrl(
147
216
  return url;
148
217
  }
149
218
 
150
- export async function apiUpload<T = any>(
219
+ export async function apiUpload<T = unknown>(
151
220
  url: string,
152
221
  body: FormData,
222
+ onProgress?: (percent: number) => void,
153
223
  ): Promise<T> {
154
- const response = await fetch(url, {
155
- method: "POST",
156
- body,
157
- credentials: "include",
158
- });
159
- if (!response.ok) {
160
- throw new Error(`Upload Error: ${response.status}`);
224
+ if (!onProgress) {
225
+ // Fast path: no progress tracking needed
226
+ const response = await fetchWithAuth(url, {
227
+ method: "POST",
228
+ body,
229
+ });
230
+ if (!response.ok) {
231
+ throw new Error(`Upload Error: ${response.status}`);
232
+ }
233
+ return response.json();
161
234
  }
162
- return response.json();
163
- }
235
+
236
+ // Use XHR for upload progress events
237
+ return new Promise((resolve, reject) => {
238
+ const xhr = new XMLHttpRequest();
239
+ xhr.open("POST", resolveApi(url));
240
+ xhr.withCredentials = true;
241
+
242
+ xhr.upload.addEventListener("progress", (e) => {
243
+ if (e.lengthComputable) {
244
+ onProgress(Math.round((e.loaded / e.total) * 100));
245
+ }
246
+ });
247
+
248
+ xhr.addEventListener("load", () => {
249
+ if (xhr.status >= 200 && xhr.status < 300) {
250
+ try {
251
+ resolve(JSON.parse(xhr.responseText));
252
+ } catch {
253
+ reject(new Error("Invalid JSON response"));
254
+ }
255
+ } else if (xhr.status === 401) {
256
+ // Attempt token refresh then retry once
257
+ refreshToken().then((refreshed) => {
258
+ if (!refreshed) { reject(new Error(`Upload Error: ${xhr.status}`)); return; }
259
+ const xhr2 = new XMLHttpRequest();
260
+ xhr2.open("POST", resolveApi(url));
261
+ xhr2.withCredentials = true;
262
+ xhr2.upload.addEventListener("progress", (e) => {
263
+ if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
264
+ });
265
+ xhr2.addEventListener("load", () => {
266
+ if (xhr2.status >= 200 && xhr2.status < 300) {
267
+ try { resolve(JSON.parse(xhr2.responseText)); } catch { reject(new Error("Invalid JSON")); }
268
+ } else {
269
+ reject(new Error(`Upload Error: ${xhr2.status}`));
270
+ }
271
+ });
272
+ xhr2.addEventListener("error", () => reject(new Error("Network error")));
273
+ xhr2.send(body);
274
+ });
275
+ } else {
276
+ reject(new Error(`Upload Error: ${xhr.status}`));
277
+ }
278
+ });
279
+
280
+ xhr.addEventListener("error", () => reject(new Error("Network error during upload")));
281
+ xhr.send(body);
282
+ });
283
+ }