@kyro-cms/admin 0.3.1 → 0.3.4
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/dist/EditorClient-XEUOVAAC.js +466 -0
- package/dist/EditorClient-XEUOVAAC.js.map +1 -0
- package/dist/EditorClient-YLCGVDXY.cjs +468 -0
- package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
- package/dist/chunk-7KPIUCGT.js +384 -0
- package/dist/chunk-7KPIUCGT.js.map +1 -0
- package/dist/chunk-GOACG6R7.cjs +473 -0
- package/dist/chunk-GOACG6R7.cjs.map +1 -0
- package/dist/index.cjs +14861 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1661 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +563 -0
- package/dist/index.js +14784 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -19
- package/src/components/ActionBar.tsx +7 -43
- package/src/components/Admin.tsx +138 -277
- package/src/components/ApiKeysManager.tsx +428 -419
- package/src/components/AuditLogsPage.tsx +35 -39
- package/src/components/AuthBridge.tsx +51 -0
- package/src/components/AutoForm.tsx +495 -1230
- package/src/components/BrandingHub.tsx +18 -19
- package/src/components/BulkActionsBar.tsx +1 -1
- package/src/components/CreateView.tsx +22 -36
- package/src/components/Dashboard.tsx +60 -84
- package/src/components/DetailView.tsx +113 -91
- package/src/components/DeveloperCenter.tsx +200 -198
- package/src/components/FieldRenderer.tsx +206 -0
- package/src/components/GraphQLPlayground.tsx +340 -480
- package/src/components/ListView.tsx +828 -254
- package/src/components/LoginPage.tsx +3 -4
- package/src/components/MarketplaceManager.tsx +254 -0
- package/src/components/MediaGallery.tsx +856 -1192
- package/src/components/PluginsManager.tsx +277 -0
- package/src/components/RestPlayground.tsx +398 -560
- package/src/components/SessionsManager.tsx +211 -0
- package/src/components/Sidebar.astro +179 -151
- package/src/components/ThemeProvider.tsx +7 -161
- package/src/components/UserManagement.tsx +162 -146
- package/src/components/UserMenu.tsx +110 -0
- package/src/components/WebhookManager.tsx +305 -367
- package/src/components/blocks/AccordionBlock.tsx +4 -4
- package/src/components/blocks/ArrayBlock.tsx +3 -3
- package/src/components/blocks/BlockEditModal.tsx +8 -8
- package/src/components/blocks/BlockWrapper.tsx +61 -0
- package/src/components/blocks/ButtonBlock.tsx +4 -4
- package/src/components/blocks/ChildBlocksTree.tsx +23 -25
- package/src/components/blocks/CodeBlock.tsx +15 -15
- package/src/components/blocks/ColumnsBlock.tsx +6 -44
- package/src/components/blocks/DividerBlock.tsx +3 -3
- package/src/components/blocks/FileBlock.tsx +4 -4
- package/src/components/blocks/HeadingBlock.tsx +6 -38
- package/src/components/blocks/HeroBlock.tsx +4 -4
- package/src/components/blocks/ImageBlock.tsx +4 -4
- package/src/components/blocks/LinkBlock.tsx +4 -4
- package/src/components/blocks/ListBlock.tsx +3 -3
- package/src/components/blocks/ParagraphBlock.tsx +12 -42
- package/src/components/blocks/RelationshipBlock.tsx +4 -4
- package/src/components/blocks/RichTextBlock.tsx +4 -4
- package/src/components/blocks/VStackBlock.tsx +5 -37
- package/src/components/blocks/VideoBlock.tsx +4 -4
- package/src/components/blocks/types.ts +11 -0
- package/src/components/fields/AccordionField.tsx +1 -1
- package/src/components/fields/ArrayField.tsx +2 -2
- package/src/components/fields/ArrayLayout.tsx +93 -0
- package/src/components/fields/BlocksField.tsx +122 -111
- package/src/components/fields/ButtonField.tsx +1 -1
- package/src/components/fields/CheckboxField.tsx +14 -15
- package/src/components/fields/ChildrenField.tsx +2 -2
- package/src/components/fields/CodeField.tsx +3 -3
- package/src/components/fields/ColumnsField.tsx +2 -2
- package/src/components/fields/DateField.tsx +13 -26
- package/src/components/fields/EditorClient.tsx +26 -28
- package/src/components/fields/FieldLayout.tsx +52 -0
- package/src/components/fields/GroupLayout.tsx +35 -0
- package/src/components/fields/JSONField.tsx +7 -7
- package/src/components/fields/LinkField.tsx +1 -1
- package/src/components/fields/MarkdownField.tsx +1 -1
- package/src/components/fields/NumberField.tsx +13 -26
- package/src/components/fields/PortableTextField.tsx +4 -4
- package/src/components/fields/PortableTextRenderer.tsx +1 -1
- package/src/components/fields/RelationshipBlockField.tsx +31 -23
- package/src/components/fields/RelationshipField.tsx +14 -14
- package/src/components/fields/SelectField.tsx +17 -26
- package/src/components/fields/TabsLayout.tsx +69 -0
- package/src/components/fields/TextField.tsx +85 -38
- package/src/components/fields/UploadField.tsx +71 -41
- package/src/components/fields/VideoField.tsx +1 -1
- package/src/components/fields/extensions/blockComponents.tsx +2 -2
- package/src/components/fields/extensions/blocksStore.ts +207 -193
- package/src/components/fields/types.ts +22 -0
- package/src/components/layout/Layout.tsx +1 -1
- package/src/components/ui/ActionMenu.tsx +63 -0
- package/src/components/ui/Badge.tsx +59 -5
- package/src/components/ui/BlockDrawer.tsx +4 -5
- package/src/components/ui/CommandPalette.tsx +58 -36
- package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
- package/src/components/ui/Dropdown.tsx +18 -16
- package/src/components/ui/EmptyState.tsx +25 -0
- package/src/components/ui/GlobalModal.tsx +49 -0
- package/src/components/ui/IconButton.tsx +44 -0
- package/src/components/ui/Modal.tsx +19 -20
- package/src/components/ui/PageHeader.tsx +158 -0
- package/src/components/ui/Pagination.tsx +61 -0
- package/src/components/ui/PromptModal.tsx +1 -1
- package/src/components/ui/SearchInput.tsx +57 -0
- package/src/components/ui/SeoPreview.tsx +31 -0
- package/src/components/ui/SessionModal.tsx +0 -0
- package/src/components/ui/SlidePanel.tsx +2 -0
- package/src/components/ui/Toast.tsx +65 -122
- package/src/components/ui/Toaster.tsx +18 -0
- package/src/components/ui/icons.tsx +112 -0
- package/src/components/users/UserDetail.tsx +290 -0
- package/src/components/users/UserForm.tsx +242 -0
- package/src/components/users/UsersList.tsx +338 -0
- package/src/env.d.ts +13 -13
- package/src/fields/index.ts +2 -1
- package/src/global.d.ts +7 -0
- package/src/hooks/data.ts +2 -9
- package/src/hooks/useAsyncData.ts +36 -0
- package/src/hooks/useAutoFormState.ts +527 -0
- package/src/hooks/useSelection.ts +49 -0
- package/src/hooks/useSession.ts +0 -0
- package/src/index.ts +11 -1
- package/src/integration.ts +86 -11
- package/src/kyro-cms.d.ts +209 -0
- package/src/layouts/AdminLayout.astro +128 -11
- package/src/layouts/AuthLayout.astro +21 -5
- package/src/lib/api.ts +175 -55
- package/src/lib/autoform-store.ts +435 -0
- package/src/lib/config.ts +82 -34
- package/src/lib/createRegistry.ts +29 -0
- package/src/lib/default-kyro-config.ts +4 -0
- package/src/lib/globals.ts +50 -0
- package/src/lib/media-utils.ts +18 -0
- package/src/lib/object-utils.ts +77 -0
- package/src/lib/paths.ts +61 -0
- package/src/lib/stores/index.ts +370 -0
- package/src/lib/types.ts +43 -0
- package/src/lib/useResourceManager.ts +105 -0
- package/src/pages/403.astro +67 -0
- package/src/pages/[collection]/[id].astro +14 -180
- package/src/pages/[collection]/index.astro +11 -6
- package/src/pages/api-explorer.astro +173 -0
- package/src/pages/audit/index.astro +2 -0
- package/src/pages/auth/login.astro +122 -0
- package/src/pages/auth/register.astro +167 -0
- package/src/pages/graphql-explorer.astro +59 -0
- package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
- package/src/pages/index.astro +577 -0
- package/src/pages/index_ALT.astro +3 -0
- package/src/pages/keys.astro +11 -0
- package/src/pages/marketplace.astro +11 -0
- package/src/pages/media.astro +3 -0
- package/src/pages/plugins.astro +8 -0
- package/src/pages/preview/[collection]/[id].astro +188 -123
- package/src/pages/rest-playground.astro +62 -0
- package/src/pages/roles/index.astro +183 -76
- package/src/pages/sessions.astro +8 -0
- package/src/pages/settings/[slug].astro +92 -114
- package/src/pages/settings/index.astro +5 -3
- package/src/pages/users/[id].astro +25 -154
- package/src/pages/users/index.astro +19 -130
- package/src/pages/users/new.astro +9 -86
- package/src/pages/webhooks.astro +11 -0
- package/src/routes.ts +80 -0
- package/src/styles/main.css +119 -79
- package/src/theme/tokens.ts +1 -0
- package/src/vite-env.d.ts +14 -0
- package/src/collections/auth/index.ts +0 -155
- package/src/collections/portfolio/index.ts +0 -343
- package/src/components/ApiExplorer.tsx +0 -325
- package/src/components/EnhancedListView.tsx +0 -889
- package/src/components/GraphQLExplorer.tsx +0 -675
- package/src/components/Icons.tsx +0 -23
- package/src/components/StatusBadge.tsx +0 -76
- package/src/lib/MediaService.ts +0 -541
- package/src/lib/auth/sqlite-adapter.ts +0 -319
- package/src/lib/dataStore.ts +0 -226
- package/src/lib/db/adapter.ts +0 -54
- package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
- package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
- package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
- package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
- package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
- package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
- package/src/lib/db/index.ts +0 -449
- package/src/lib/db/mongodb-adapter.ts +0 -207
- package/src/lib/db/mongodb-auth-adapter.ts +0 -305
- package/src/lib/db/schema/mysql-auth.ts +0 -113
- package/src/lib/db/schema/mysql-content.ts +0 -20
- package/src/lib/db/schema/postgres-auth.ts +0 -116
- package/src/lib/db/schema/postgres-content.ts +0 -35
- package/src/lib/db/schema/postgres-media.ts +0 -52
- package/src/lib/db/schema/postgres-settings.ts +0 -11
- package/src/lib/db/schema/sqlite-auth.ts +0 -112
- package/src/lib/db/schema/sqlite-content.ts +0 -20
- package/src/lib/db/version-adapter.ts +0 -248
- package/src/lib/graphql/index.ts +0 -1
- package/src/lib/graphql/schema.ts +0 -443
- package/src/lib/rate-limit.ts +0 -267
- package/src/lib/storage.ts +0 -374
- package/src/lib/store.ts +0 -85
- package/src/middleware.ts +0 -177
- package/src/pages/admin/api-explorer.astro +0 -98
- package/src/pages/admin/graphql-explorer.astro +0 -40
- package/src/pages/admin/index.astro +0 -286
- package/src/pages/admin/keys.astro +0 -8
- package/src/pages/admin/rest-playground.astro +0 -44
- package/src/pages/admin/webhooks.astro +0 -8
- package/src/pages/api/[collection]/[id]/publish.ts +0 -52
- package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
- package/src/pages/api/[collection]/[id]/versions.ts +0 -66
- package/src/pages/api/[collection]/[id].ts +0 -213
- package/src/pages/api/[collection]/index.ts +0 -209
- package/src/pages/api/auth/[id].ts +0 -121
- package/src/pages/api/auth/audit-logs.ts +0 -57
- package/src/pages/api/auth/login.ts +0 -211
- package/src/pages/api/auth/logout.ts +0 -66
- package/src/pages/api/auth/me.ts +0 -36
- package/src/pages/api/auth/refresh.ts +0 -119
- package/src/pages/api/auth/register.ts +0 -188
- package/src/pages/api/auth/users.ts +0 -97
- package/src/pages/api/collections.ts +0 -59
- package/src/pages/api/globals/[slug].ts +0 -42
- package/src/pages/api/graphql.ts +0 -90
- package/src/pages/api/health.ts +0 -426
- package/src/pages/api/keys/[id].ts +0 -26
- package/src/pages/api/keys/index.ts +0 -75
- package/src/pages/api/media/[id].ts +0 -309
- package/src/pages/api/media/folders.ts +0 -609
- package/src/pages/api/media/index.ts +0 -146
- package/src/pages/api/media/resize.ts +0 -267
- package/src/pages/api/search.ts +0 -82
- package/src/pages/api/slug-availability.ts +0 -70
- package/src/pages/api/storage-config.ts +0 -20
- package/src/pages/api/storage-status.ts +0 -206
- package/src/pages/api/upload.ts +0 -334
- package/src/pages/api/webhooks/index.ts +0 -71
- package/src/pages/login.astro +0 -82
- package/src/pages/register.astro +0 -102
package/src/lib/api.ts
CHANGED
|
@@ -1,75 +1,136 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
11
|
-
): Promise<
|
|
12
|
-
const
|
|
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
|
-
|
|
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 =
|
|
81
|
+
export async function apiPost<T = unknown>(
|
|
23
82
|
url: string,
|
|
24
|
-
body?:
|
|
25
|
-
options?: RequestInit,
|
|
83
|
+
body?: unknown,
|
|
84
|
+
options?: RequestInit & { autoToast?: boolean },
|
|
26
85
|
): Promise<T> {
|
|
27
|
-
const
|
|
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
|
-
|
|
35
|
-
...options,
|
|
90
|
+
...fetchOptions,
|
|
36
91
|
});
|
|
37
92
|
if (!response.ok) {
|
|
38
|
-
|
|
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 =
|
|
104
|
+
export async function apiPatch<T = unknown>(
|
|
44
105
|
url: string,
|
|
45
|
-
body?:
|
|
46
|
-
options?: RequestInit,
|
|
106
|
+
body?: unknown,
|
|
107
|
+
options?: RequestInit & { autoToast?: boolean },
|
|
47
108
|
): Promise<T> {
|
|
48
|
-
const
|
|
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
|
-
|
|
56
|
-
...options,
|
|
113
|
+
...fetchOptions,
|
|
57
114
|
});
|
|
58
115
|
if (!response.ok) {
|
|
59
|
-
|
|
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 =
|
|
127
|
+
export async function apiPatchNoThrow<T = unknown>(
|
|
65
128
|
url: string,
|
|
66
|
-
body?:
|
|
129
|
+
body?: unknown,
|
|
67
130
|
): Promise<{ ok: boolean; data?: T; error?: string }> {
|
|
68
|
-
const response = await
|
|
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 =
|
|
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
|
|
146
|
+
const { autoToast = true, ...fetchOptions } = options || {};
|
|
147
|
+
const response = await fetchWithAuth(url, {
|
|
86
148
|
method: "DELETE",
|
|
87
|
-
|
|
88
|
-
...options,
|
|
149
|
+
...fetchOptions,
|
|
89
150
|
});
|
|
90
151
|
if (!response.ok) {
|
|
91
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
196
|
+
params?: Record<string, unknown>,
|
|
128
197
|
): string {
|
|
129
|
-
let url =
|
|
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,
|
|
209
|
+
params?: Record<string, unknown>,
|
|
141
210
|
): string {
|
|
142
|
-
let url =
|
|
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 =
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
+
}
|