@thetechfossil/upfiles 0.1.1 → 0.4.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.
package/README.md CHANGED
@@ -4,6 +4,7 @@ Lightweight JavaScript client and React component to upload files via a presigne
4
4
 
5
5
  - Client: get presigned URLs and PUT to S3
6
6
  - React `<Uploader />`: drag/drop, progress, single/multiple, accepts/types, size limits
7
+ - React `<ProjectFilesWidget />`: list all files for a project (Plugin API), pick one, and optionally auto-save to your app
7
8
 
8
9
  ## Installation
9
10
 
@@ -23,16 +24,16 @@ yarn add @thetechfossil/upfiles
23
24
 
24
25
  ## Server prerequisites
25
26
 
26
- Your server must expose a presign endpoint compatible with:
27
+ Your Upfiles app must expose the Plugin API presign endpoint:
27
28
 
28
29
  - Method: `POST`
29
- - Path: `/api/upload/presigned-url` (default; configurable)
30
- - Body: `{ fileName, fileType, fileSize? }`
31
- - Response: `{ presignedUrl, fileKey, publicUrl }`
30
+ - Path: `/api/plugin/upload/presigned-url` (default; configurable)
31
+ - Body: `{ fileName, fileType, fileSize, folderPath?, projectId? }` (`fileSize` is required)
32
+ - Response: `{ presignedUrl, fileKey, publicUrl, apiKeyId?, projectId? }`
32
33
 
33
- This repository already provides a Next.js route at `src/app/api/upload/presigned-url/route.ts`.
34
+ This repository already provides a Next.js route at `src/app/api/plugin/upload/presigned-url/route.ts`.
34
35
 
35
- If your server requires auth, you may need cookies (`withCredentials`) or an Authorization header.
36
+ For plugins/packages calling across origins, authenticate using an API key header as documented below. CORS must allow your plugin origin.
36
37
 
37
38
  ## Basic usage (React)
38
39
 
@@ -53,6 +54,13 @@ export default function Page() {
53
54
  }}
54
55
  dropzoneClassName="border border-dashed rounded-md p-6 cursor-pointer"
55
56
  buttonClassName="px-3 py-2 rounded bg-blue-600 text-white"
57
+ // Optional: pass clientOptions for cross-origin + API key auth
58
+ // clientOptions={{
59
+ // baseUrl: 'https://YOUR_APP_HOST',
60
+ // // or presignUrl: 'https://YOUR_APP_HOST/api/plugin/upload/presigned-url',
61
+ // apiKey: process.env.NEXT_PUBLIC_UPFILES_API_KEY!,
62
+ // apiKeyHeader: 'authorization', // or 'x-api-key' | 'x-up-api-key'
63
+ // }}
56
64
  />
57
65
  );
58
66
  }
@@ -65,17 +73,21 @@ import { UpfilesClient } from '@thetechfossil/upfiles';
65
73
 
66
74
  // Same-origin by default; only set these when needed:
67
75
  const client = new UpfilesClient({
68
- // presignUrl: 'https://api.example.com/api/upload/presigned-url', // full override
69
- // baseUrl: 'https://api.example.com', // used with presignPath
70
- // presignPath: '/api/upload/presigned-url', // default
71
- // withCredentials: true,
76
+ // presignUrl: 'https://YOUR_APP_HOST/api/plugin/upload/presigned-url', // full override
77
+ // baseUrl: 'https://YOUR_APP_HOST', // used with presignPath
78
+ // presignPath: '/api/plugin/upload/presigned-url', // default
79
+ // withCredentials: true, // for session routes (not typical for Plugin API)
80
+ // Provide API key for Plugin API auth:
81
+ // apiKey: 'upk_...'
82
+ // apiKeyHeader: 'authorization' // or 'x-api-key' | 'x-up-api-key'
72
83
  });
73
84
 
74
85
  async function upload(file) {
75
86
  const { presignedUrl, publicUrl } = await client.getPresignedUrl({
76
87
  fileName: file.name,
77
88
  fileType: file.type,
78
- fileSize: file.size,
89
+ fileSize: file.size, // required
90
+ folderPath: 'my-plugin/uploads/',
79
91
  });
80
92
  const res = await client.uploadToS3(presignedUrl, file);
81
93
  if (!res.ok) throw new Error('Upload failed');
@@ -88,12 +100,17 @@ async function upload(file) {
88
100
  - `new UpfilesClient(options)`
89
101
  - `presignUrl` (optional): full absolute URL to presign endpoint. Highest priority.
90
102
  - `baseUrl` (optional): API base origin. Used with `presignPath`.
91
- - `presignPath` (optional): relative path, default `/api/upload/presigned-url`.
92
- - `headers` (optional): headers for your server, e.g. Authorization.
103
+ - `presignPath` (optional): relative path, default `/api/plugin/upload/presigned-url`.
104
+ - `headers` (optional): extra headers to send.
105
+ - `apiKey` (optional): API key value (e.g., `upk_...`).
106
+ - `apiKeyHeader` (optional): one of `'authorization' | 'x-api-key' | 'x-up-api-key'` (default `'authorization'`). If `'authorization'`, `Bearer` prefix is auto-added for `upk_...`.
93
107
  - `withCredentials` (optional): `true` to send cookies.
94
- - Defaults: same-origin requests to `/api/upload/presigned-url`.
95
- - `client.getPresignedUrl({ fileName, fileType, fileSize? })`
108
+ - Defaults: same-origin requests to `/api/plugin/upload/presigned-url`.
109
+ - `client.getPresignedUrl({ fileName, fileType, fileSize, projectId?, folderPath? })`
96
110
  - `client.uploadToS3(presignedUrl, file)`
111
+ - `client.upload(file, { projectId?, folderPath?, fetchThumbnails? })` → returns `{ publicUrl, fileKey, fileName, fileType, fileSize, projectId?, apiKeyId?, thumbnails? }`
112
+ - `client.getThumbnails(fileKey)` → returns `Thumbnail[]`
113
+ - `client.getProjectFiles({ folderPath? })` → returns `FileListItem[]`
97
114
 
98
115
  - `<Uploader />` props
99
116
  - `clientOptions` (optional): same as `UpfilesClient` options (only needed for cross-origin or auth customization)
@@ -101,13 +118,81 @@ async function upload(file) {
101
118
  - `accept` (array of MIME patterns)
102
119
  - `maxFileSize` (bytes, default 100MB)
103
120
  - `maxFiles` (default 10)
121
+ - `fetchThumbnails` (boolean): if true, fetch thumbnails after upload via Plugin API
104
122
  - `onComplete(files)` and `onError(error)`
105
123
  - `className`, `buttonClassName`, `dropzoneClassName`
106
124
  - `children`: custom dropzone inner content
107
125
 
126
+ ### Thumbnails usage
127
+
128
+ Your Upfiles app exposes `GET /api/plugin/thumbnails?fileKey=...` for plugins. Enable `fetchThumbnails` to auto-fetch after upload, or call `client.getThumbnails(fileKey)` yourself.
129
+
130
+ Example with `client.upload`:
131
+
132
+ ```ts
133
+ const result = await client.upload(file, { folderPath: 'my-plugin/', fetchThumbnails: true });
134
+ console.log(result.thumbnails);
135
+ ```
136
+
137
+ ### List project files (Plugin API)
138
+
139
+ Your Upfiles app exposes `GET /api/plugin/files?folderPath=...` for plugins (API key auth + CORS). The client can call it via `getProjectFiles`:
140
+
141
+ ```ts
142
+ import { UpfilesClient } from '@thetechfossil/upfiles';
143
+
144
+ const client = new UpfilesClient({
145
+ baseUrl: 'https://YOUR_APP_HOST',
146
+ apiKey: 'upk_...',
147
+ thumbnailsPath: '/api/plugin/thumbnails', // used to derive /api/plugin/files
148
+ });
149
+
150
+ const files = await client.getProjectFiles({ folderPath: 'optional/subfolder' });
151
+ // [{ key, originalName, size, contentType, uploadedAt, url }, ...]
152
+ ```
153
+
154
+ ### `<ProjectFilesWidget />`
155
+
156
+ Simple UI to fetch and select a file for the project tied to the API key. Emits the selection, and can optionally POST it to your backend.
157
+
158
+ ```tsx
159
+ import { ProjectFilesWidget } from '@thetechfossil/upfiles';
160
+
161
+ export default function PickFile() {
162
+ return (
163
+ <ProjectFilesWidget
164
+ clientOptions={{
165
+ baseUrl: 'https://YOUR_APP_HOST',
166
+ apiKey: process.env.NEXT_PUBLIC_UPFILES_API_KEY!,
167
+ apiKeyHeader: 'authorization',
168
+ thumbnailsPath: '/api/plugin/thumbnails',
169
+ }}
170
+ // Optional: filter within a folder
171
+ // folderPath="my-plugin/"
172
+
173
+ onSelect={(f) => {
174
+ // { name, key, url, size, contentType }
175
+ console.log('Selected:', f.name, f.key);
176
+ }}
177
+
178
+ // Optional: built-in save
179
+ // saveUrl="/api/files/save" // Your app route to persist selection
180
+ // onSave={async (f) => { await fetch('/api/files/save', { method: 'POST', body: JSON.stringify(f) }); }}
181
+ // onSaved={(result) => console.log('Saved!', result)}
182
+ />
183
+ );
184
+ }
185
+ ```
186
+
108
187
  ## Notes
109
188
 
110
189
  - Dev with Vite: configure a proxy so `/api/*` maps to your backend (e.g., `http://localhost:4000`), then `<Uploader />` works with no config.
111
- - Ensure CORS where applicable (S3 bucket CORS; server CORS only when using cross-origin calls).
190
+ - Ensure CORS where applicable (S3 bucket CORS; and set `PLUGIN_ALLOWED_ORIGINS` on your Upfiles app for cross-origin Plugin API calls).
112
191
  - For cross-origin with cookies (NextAuth), set `withCredentials: true` and configure your server to allow credentials.
113
- - Consider adding API token auth if distributing the SDK publicly.
192
+ Plugin API typically uses API keys instead of cookies.
193
+
194
+ ### Plugin endpoints provided by this repo
195
+
196
+ - `POST /api/plugin/upload/presigned-url` – generate presigned S3 URL for upload
197
+ - `GET /api/plugin/thumbnails?fileKey=...` – list thumbnails for a file
198
+ - `GET /api/plugin/files?folderPath=...` – list project files (API key scoped), optional folder prefix
package/dist/index.d.mts CHANGED
@@ -4,6 +4,34 @@ type PresignResponse = {
4
4
  presignedUrl: string;
5
5
  fileKey: string;
6
6
  publicUrl: string;
7
+ apiKeyId?: string;
8
+ projectId?: string;
9
+ };
10
+ type UploadedFileResult = {
11
+ publicUrl: string;
12
+ fileKey: string;
13
+ fileName: string;
14
+ fileType: string;
15
+ fileSize: number;
16
+ projectId?: string;
17
+ apiKeyId?: string;
18
+ thumbnails?: Thumbnail[];
19
+ };
20
+ type Thumbnail = {
21
+ id: string;
22
+ key: string;
23
+ url: string;
24
+ size: number;
25
+ sizeType?: string;
26
+ createdAt?: string;
27
+ };
28
+ type FileListItem = {
29
+ key: string;
30
+ originalName: string;
31
+ size: number;
32
+ contentType: string;
33
+ uploadedAt: string;
34
+ url: string;
7
35
  };
8
36
  type UpfilesClientOptions = {
9
37
  baseUrl?: string;
@@ -11,6 +39,9 @@ type UpfilesClientOptions = {
11
39
  presignUrl?: string;
12
40
  headers?: Record<string, string>;
13
41
  withCredentials?: boolean;
42
+ apiKey?: string;
43
+ apiKeyHeader?: 'authorization' | 'x-api-key' | 'x-up-api-key';
44
+ thumbnailsPath?: string;
14
45
  };
15
46
  declare class UpfilesClient {
16
47
  private baseUrl?;
@@ -18,15 +49,27 @@ declare class UpfilesClient {
18
49
  private presignUrl?;
19
50
  private headers?;
20
51
  private withCredentials?;
52
+ private apiKey?;
53
+ private apiKeyHeader;
54
+ private thumbnailsPath;
21
55
  constructor(opts: UpfilesClientOptions);
22
56
  getPresignedUrl(params: {
23
57
  fileName: string;
24
58
  fileType: string;
25
- fileSize?: number;
59
+ fileSize: number;
26
60
  projectId?: string;
27
61
  folderPath?: string;
28
62
  }): Promise<PresignResponse>;
29
63
  uploadToS3(presignedUrl: string, file: File): Promise<Response>;
64
+ upload(file: File, extras?: {
65
+ projectId?: string;
66
+ folderPath?: string;
67
+ fetchThumbnails?: boolean;
68
+ }): Promise<UploadedFileResult>;
69
+ getThumbnails(fileKey: string): Promise<Thumbnail[]>;
70
+ getProjectFiles(params?: {
71
+ folderPath?: string;
72
+ }): Promise<FileListItem[]>;
30
73
  }
31
74
 
32
75
  type UploaderFile = {
@@ -40,6 +83,8 @@ type UploaderFile = {
40
83
  error?: string;
41
84
  publicUrl?: string;
42
85
  fileKey?: string;
86
+ projectId?: string;
87
+ apiKeyId?: string;
43
88
  };
44
89
  type UploaderProps = {
45
90
  clientOptions?: UpfilesClientOptions;
@@ -55,7 +100,33 @@ type UploaderProps = {
55
100
  buttonClassName?: string;
56
101
  dropzoneClassName?: string;
57
102
  children?: React.ReactNode;
103
+ fetchThumbnails?: boolean;
58
104
  };
59
105
  declare const Uploader: React.FC<UploaderProps>;
60
106
 
61
- export { type PresignResponse, UpfilesClient, type UpfilesClientOptions, Uploader, type UploaderFile, type UploaderProps };
107
+ type ProjectFilesWidgetProps = {
108
+ clientOptions: UpfilesClientOptions;
109
+ folderPath?: string;
110
+ className?: string;
111
+ listClassName?: string;
112
+ itemClassName?: string;
113
+ onSelect: (file: {
114
+ name: string;
115
+ key: string;
116
+ url: string;
117
+ size: number;
118
+ contentType: string;
119
+ }) => void;
120
+ saveUrl?: string;
121
+ onSave?: (file: {
122
+ name: string;
123
+ key: string;
124
+ url: string;
125
+ size: number;
126
+ contentType: string;
127
+ }) => Promise<void> | void;
128
+ onSaved?: (result?: any) => void;
129
+ };
130
+ declare const ProjectFilesWidget: React.FC<ProjectFilesWidgetProps>;
131
+
132
+ export { type FileListItem, type PresignResponse, ProjectFilesWidget, type ProjectFilesWidgetProps, type Thumbnail, UpfilesClient, type UpfilesClientOptions, type UploadedFileResult, Uploader, type UploaderFile, type UploaderProps };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,34 @@ type PresignResponse = {
4
4
  presignedUrl: string;
5
5
  fileKey: string;
6
6
  publicUrl: string;
7
+ apiKeyId?: string;
8
+ projectId?: string;
9
+ };
10
+ type UploadedFileResult = {
11
+ publicUrl: string;
12
+ fileKey: string;
13
+ fileName: string;
14
+ fileType: string;
15
+ fileSize: number;
16
+ projectId?: string;
17
+ apiKeyId?: string;
18
+ thumbnails?: Thumbnail[];
19
+ };
20
+ type Thumbnail = {
21
+ id: string;
22
+ key: string;
23
+ url: string;
24
+ size: number;
25
+ sizeType?: string;
26
+ createdAt?: string;
27
+ };
28
+ type FileListItem = {
29
+ key: string;
30
+ originalName: string;
31
+ size: number;
32
+ contentType: string;
33
+ uploadedAt: string;
34
+ url: string;
7
35
  };
8
36
  type UpfilesClientOptions = {
9
37
  baseUrl?: string;
@@ -11,6 +39,9 @@ type UpfilesClientOptions = {
11
39
  presignUrl?: string;
12
40
  headers?: Record<string, string>;
13
41
  withCredentials?: boolean;
42
+ apiKey?: string;
43
+ apiKeyHeader?: 'authorization' | 'x-api-key' | 'x-up-api-key';
44
+ thumbnailsPath?: string;
14
45
  };
15
46
  declare class UpfilesClient {
16
47
  private baseUrl?;
@@ -18,15 +49,27 @@ declare class UpfilesClient {
18
49
  private presignUrl?;
19
50
  private headers?;
20
51
  private withCredentials?;
52
+ private apiKey?;
53
+ private apiKeyHeader;
54
+ private thumbnailsPath;
21
55
  constructor(opts: UpfilesClientOptions);
22
56
  getPresignedUrl(params: {
23
57
  fileName: string;
24
58
  fileType: string;
25
- fileSize?: number;
59
+ fileSize: number;
26
60
  projectId?: string;
27
61
  folderPath?: string;
28
62
  }): Promise<PresignResponse>;
29
63
  uploadToS3(presignedUrl: string, file: File): Promise<Response>;
64
+ upload(file: File, extras?: {
65
+ projectId?: string;
66
+ folderPath?: string;
67
+ fetchThumbnails?: boolean;
68
+ }): Promise<UploadedFileResult>;
69
+ getThumbnails(fileKey: string): Promise<Thumbnail[]>;
70
+ getProjectFiles(params?: {
71
+ folderPath?: string;
72
+ }): Promise<FileListItem[]>;
30
73
  }
31
74
 
32
75
  type UploaderFile = {
@@ -40,6 +83,8 @@ type UploaderFile = {
40
83
  error?: string;
41
84
  publicUrl?: string;
42
85
  fileKey?: string;
86
+ projectId?: string;
87
+ apiKeyId?: string;
43
88
  };
44
89
  type UploaderProps = {
45
90
  clientOptions?: UpfilesClientOptions;
@@ -55,7 +100,33 @@ type UploaderProps = {
55
100
  buttonClassName?: string;
56
101
  dropzoneClassName?: string;
57
102
  children?: React.ReactNode;
103
+ fetchThumbnails?: boolean;
58
104
  };
59
105
  declare const Uploader: React.FC<UploaderProps>;
60
106
 
61
- export { type PresignResponse, UpfilesClient, type UpfilesClientOptions, Uploader, type UploaderFile, type UploaderProps };
107
+ type ProjectFilesWidgetProps = {
108
+ clientOptions: UpfilesClientOptions;
109
+ folderPath?: string;
110
+ className?: string;
111
+ listClassName?: string;
112
+ itemClassName?: string;
113
+ onSelect: (file: {
114
+ name: string;
115
+ key: string;
116
+ url: string;
117
+ size: number;
118
+ contentType: string;
119
+ }) => void;
120
+ saveUrl?: string;
121
+ onSave?: (file: {
122
+ name: string;
123
+ key: string;
124
+ url: string;
125
+ size: number;
126
+ contentType: string;
127
+ }) => Promise<void> | void;
128
+ onSaved?: (result?: any) => void;
129
+ };
130
+ declare const ProjectFilesWidget: React.FC<ProjectFilesWidgetProps>;
131
+
132
+ export { type FileListItem, type PresignResponse, ProjectFilesWidget, type ProjectFilesWidgetProps, type Thumbnail, UpfilesClient, type UpfilesClientOptions, type UploadedFileResult, Uploader, type UploaderFile, type UploaderProps };
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ProjectFilesWidget: () => ProjectFilesWidget,
23
24
  UpfilesClient: () => UpfilesClient,
24
25
  Uploader: () => Uploader
25
26
  });
@@ -29,19 +30,35 @@ module.exports = __toCommonJS(index_exports);
29
30
  var UpfilesClient = class {
30
31
  constructor(opts) {
31
32
  this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
32
- this.presignPath = opts.presignPath ?? "/api/upload/presigned-url";
33
+ this.presignPath = opts.presignPath ?? "/api/plugin/upload/presigned-url";
33
34
  this.presignUrl = opts.presignUrl;
34
35
  this.headers = opts.headers;
35
36
  this.withCredentials = opts.withCredentials;
37
+ this.apiKey = opts.apiKey;
38
+ this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
39
+ this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
36
40
  }
37
41
  async getPresignedUrl(params) {
42
+ if (!params.fileName) throw new Error("fileName is required");
43
+ if (!params.fileType) throw new Error("fileType is required");
44
+ if (!params.fileSize || params.fileSize <= 0) throw new Error("fileSize must be > 0");
38
45
  const target = this.presignUrl ? this.presignUrl : this.baseUrl ? `${this.baseUrl}${this.presignPath}` : this.presignPath;
46
+ const headers = {
47
+ "content-type": "application/json",
48
+ ...this.headers || {}
49
+ };
50
+ if (this.apiKey) {
51
+ if (this.apiKeyHeader === "authorization") {
52
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
53
+ } else if (this.apiKeyHeader === "x-api-key") {
54
+ headers["x-api-key"] = this.apiKey;
55
+ } else {
56
+ headers["x-up-api-key"] = this.apiKey;
57
+ }
58
+ }
39
59
  const res = await fetch(target, {
40
60
  method: "POST",
41
- headers: {
42
- "Content-Type": "application/json",
43
- ...this.headers || {}
44
- },
61
+ headers,
45
62
  credentials: this.withCredentials ? "include" : "same-origin",
46
63
  body: JSON.stringify(params)
47
64
  });
@@ -65,6 +82,92 @@ var UpfilesClient = class {
65
82
  }
66
83
  });
67
84
  }
85
+ // Convenience: perform full flow and return URL/path metadata for DB
86
+ async upload(file, extras) {
87
+ const presign = await this.getPresignedUrl({
88
+ fileName: file.name,
89
+ fileType: file.type || "application/octet-stream",
90
+ fileSize: file.size,
91
+ projectId: extras?.projectId,
92
+ folderPath: extras?.folderPath
93
+ });
94
+ const res = await this.uploadToS3(presign.presignedUrl, file);
95
+ if (!res.ok) {
96
+ throw new Error(`Upload failed with status ${res.status}`);
97
+ }
98
+ let thumbnails;
99
+ if (extras?.fetchThumbnails) {
100
+ try {
101
+ const t = await this.getThumbnails(presign.fileKey);
102
+ thumbnails = t;
103
+ } catch {
104
+ }
105
+ }
106
+ return {
107
+ publicUrl: presign.publicUrl,
108
+ fileKey: presign.fileKey,
109
+ fileName: file.name,
110
+ fileType: file.type,
111
+ fileSize: file.size,
112
+ projectId: presign.projectId,
113
+ apiKeyId: presign.apiKeyId,
114
+ thumbnails
115
+ };
116
+ }
117
+ async getThumbnails(fileKey) {
118
+ const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
119
+ const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
120
+ const headers = { ...this.headers || {} };
121
+ if (this.apiKey) {
122
+ if (this.apiKeyHeader === "authorization") {
123
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
124
+ } else if (this.apiKeyHeader === "x-api-key") {
125
+ headers["x-api-key"] = this.apiKey;
126
+ } else {
127
+ headers["x-up-api-key"] = this.apiKey;
128
+ }
129
+ }
130
+ const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
131
+ if (!res.ok) {
132
+ let msg = "Failed to fetch thumbnails";
133
+ try {
134
+ const data2 = await res.json();
135
+ msg = data2.error || msg;
136
+ } catch {
137
+ }
138
+ throw new Error(msg);
139
+ }
140
+ const data = await res.json();
141
+ return data?.thumbnails ?? [];
142
+ }
143
+ async getProjectFiles(params) {
144
+ const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
145
+ const filesPath = `${basePath}/files`;
146
+ const target = this.baseUrl ? `${this.baseUrl}${filesPath}` : filesPath;
147
+ const url = params?.folderPath ? `${target}?folderPath=${encodeURIComponent(params.folderPath)}` : target;
148
+ const headers = { ...this.headers || {} };
149
+ if (this.apiKey) {
150
+ if (this.apiKeyHeader === "authorization") {
151
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
152
+ } else if (this.apiKeyHeader === "x-api-key") {
153
+ headers["x-api-key"] = this.apiKey;
154
+ } else {
155
+ headers["x-up-api-key"] = this.apiKey;
156
+ }
157
+ }
158
+ const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
159
+ if (!res.ok) {
160
+ let msg = "Failed to fetch files";
161
+ try {
162
+ const data2 = await res.json();
163
+ msg = data2.error || msg;
164
+ } catch {
165
+ }
166
+ throw new Error(msg);
167
+ }
168
+ const data = await res.json();
169
+ return data?.files ?? [];
170
+ }
68
171
  };
69
172
 
70
173
  // src/Uploader.tsx
@@ -83,7 +186,8 @@ var Uploader = ({
83
186
  dropzoneClassName,
84
187
  children,
85
188
  projectId,
86
- folderPath
189
+ folderPath,
190
+ fetchThumbnails
87
191
  }) => {
88
192
  const inputRef = (0, import_react.useRef)(null);
89
193
  const client = (0, import_react.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
@@ -153,14 +257,31 @@ var Uploader = ({
153
257
  xhr.setRequestHeader("Content-Type", item.type || "application/octet-stream");
154
258
  xhr.send(item.file);
155
259
  });
260
+ let thumbs;
261
+ try {
262
+ if (fetchThumbnails) {
263
+ thumbs = await client.getThumbnails(presign.fileKey);
264
+ }
265
+ } catch {
266
+ }
156
267
  update({ status: "success", progress: 100, fileKey: presign.fileKey, publicUrl: presign.publicUrl });
157
- return { ...item, status: "success", progress: 100, fileKey: presign.fileKey, publicUrl: presign.publicUrl };
268
+ return {
269
+ ...item,
270
+ status: "success",
271
+ progress: 100,
272
+ fileKey: presign.fileKey,
273
+ publicUrl: presign.publicUrl,
274
+ projectId: presign.projectId,
275
+ apiKeyId: presign.apiKeyId,
276
+ // @ts-ignore - allow downstream to access thumbs if present
277
+ thumbnails: thumbs
278
+ };
158
279
  } catch (err) {
159
280
  const message = err instanceof Error ? err.message : "Upload failed";
160
281
  update({ status: "error", error: message });
161
282
  throw err;
162
283
  }
163
- }, [client]);
284
+ }, [client, projectId, folderPath, fetchThumbnails]);
164
285
  const uploadAll = (0, import_react.useCallback)(async () => {
165
286
  const targets = files.filter((f) => f.status === "pending");
166
287
  if (!targets.length) return;
@@ -261,8 +382,152 @@ var Uploader = ({
261
382
  ] })
262
383
  ] });
263
384
  };
385
+
386
+ // src/ProjectFilesWidget.tsx
387
+ var import_react2 = require("react");
388
+ var import_jsx_runtime2 = require("react/jsx-runtime");
389
+ var ProjectFilesWidget = ({
390
+ clientOptions,
391
+ folderPath,
392
+ className,
393
+ listClassName,
394
+ itemClassName,
395
+ onSelect,
396
+ saveUrl,
397
+ onSave,
398
+ onSaved
399
+ }) => {
400
+ const client = (0, import_react2.useMemo)(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
401
+ const [files, setFiles] = (0, import_react2.useState)([]);
402
+ const [loading, setLoading] = (0, import_react2.useState)(false);
403
+ const [error, setError] = (0, import_react2.useState)(null);
404
+ const [query, setQuery] = (0, import_react2.useState)("");
405
+ const [savingKey, setSavingKey] = (0, import_react2.useState)(null);
406
+ (0, import_react2.useEffect)(() => {
407
+ let cancelled = false;
408
+ const run = async () => {
409
+ setLoading(true);
410
+ setError(null);
411
+ try {
412
+ const data = await client.getProjectFiles({ folderPath });
413
+ if (!cancelled) setFiles(data);
414
+ } catch (e) {
415
+ if (!cancelled) setError(e?.message || "Failed to load files");
416
+ } finally {
417
+ if (!cancelled) setLoading(false);
418
+ }
419
+ };
420
+ run();
421
+ return () => {
422
+ cancelled = true;
423
+ };
424
+ }, [client, folderPath]);
425
+ const visible = files.filter(
426
+ (f) => !query ? true : (f.originalName || "").toLowerCase().includes(query.toLowerCase())
427
+ );
428
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, children: [
429
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
430
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
431
+ "input",
432
+ {
433
+ type: "text",
434
+ placeholder: "Search by filename...",
435
+ value: query,
436
+ onChange: (e) => setQuery(e.target.value),
437
+ style: { flex: 1, padding: 8, border: "1px solid #e5e7eb", borderRadius: 6 }
438
+ }
439
+ ),
440
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
441
+ "button",
442
+ {
443
+ onClick: () => {
444
+ setQuery("");
445
+ (async () => {
446
+ setLoading(true);
447
+ setError(null);
448
+ try {
449
+ const data = await client.getProjectFiles({ folderPath });
450
+ setFiles(data);
451
+ } catch (e) {
452
+ setError(e?.message || "Failed to load files");
453
+ } finally {
454
+ setLoading(false);
455
+ }
456
+ })();
457
+ },
458
+ style: { padding: "8px 12px", border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" },
459
+ children: "Refresh"
460
+ }
461
+ )
462
+ ] }),
463
+ loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
464
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
465
+ !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
466
+ visible.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
467
+ "li",
468
+ {
469
+ className: itemClassName,
470
+ style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
471
+ children: [
472
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0 }, children: [
473
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f.originalName }),
474
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: 12, color: "#666" }, children: [
475
+ (f.size / (1024 * 1024)).toFixed(2),
476
+ " MB \u2022 ",
477
+ f.contentType
478
+ ] })
479
+ ] }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: f.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
483
+ "button",
484
+ {
485
+ onClick: async () => {
486
+ const selected = { name: f.originalName, key: f.key, url: f.url, size: f.size, contentType: f.contentType };
487
+ onSelect(selected);
488
+ if (typeof onSave === "function" || !!saveUrl) {
489
+ try {
490
+ setSavingKey(f.key);
491
+ if (typeof onSave === "function") {
492
+ await onSave(selected);
493
+ onSaved?.();
494
+ }
495
+ if (saveUrl) {
496
+ const res = await fetch(saveUrl, {
497
+ method: "POST",
498
+ headers: { "content-type": "application/json" },
499
+ body: JSON.stringify(selected),
500
+ credentials: "same-origin"
501
+ });
502
+ const data = await res.json().catch(() => void 0);
503
+ if (!res.ok) throw new Error(data?.error || "Failed to save");
504
+ onSaved?.(data);
505
+ }
506
+ } catch (e) {
507
+ console.error("Save selection failed", e);
508
+ alert(e?.message || "Failed to save");
509
+ } finally {
510
+ setSavingKey(null);
511
+ }
512
+ }
513
+ },
514
+ disabled: savingKey === f.key,
515
+ style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey === f.key ? 0.7 : 1 },
516
+ children: savingKey === f.key ? "Saving\u2026" : "Use"
517
+ }
518
+ )
519
+ ] })
520
+ ]
521
+ },
522
+ f.key
523
+ )),
524
+ visible.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
525
+ ] })
526
+ ] });
527
+ };
264
528
  // Annotate the CommonJS export names for ESM import in node:
265
529
  0 && (module.exports = {
530
+ ProjectFilesWidget,
266
531
  UpfilesClient,
267
532
  Uploader
268
533
  });
package/dist/index.mjs CHANGED
@@ -2,19 +2,35 @@
2
2
  var UpfilesClient = class {
3
3
  constructor(opts) {
4
4
  this.baseUrl = opts.baseUrl?.replace(/\/$/, "");
5
- this.presignPath = opts.presignPath ?? "/api/upload/presigned-url";
5
+ this.presignPath = opts.presignPath ?? "/api/plugin/upload/presigned-url";
6
6
  this.presignUrl = opts.presignUrl;
7
7
  this.headers = opts.headers;
8
8
  this.withCredentials = opts.withCredentials;
9
+ this.apiKey = opts.apiKey;
10
+ this.apiKeyHeader = opts.apiKeyHeader ?? "authorization";
11
+ this.thumbnailsPath = opts.thumbnailsPath ?? "/api/plugin/thumbnails";
9
12
  }
10
13
  async getPresignedUrl(params) {
14
+ if (!params.fileName) throw new Error("fileName is required");
15
+ if (!params.fileType) throw new Error("fileType is required");
16
+ if (!params.fileSize || params.fileSize <= 0) throw new Error("fileSize must be > 0");
11
17
  const target = this.presignUrl ? this.presignUrl : this.baseUrl ? `${this.baseUrl}${this.presignPath}` : this.presignPath;
18
+ const headers = {
19
+ "content-type": "application/json",
20
+ ...this.headers || {}
21
+ };
22
+ if (this.apiKey) {
23
+ if (this.apiKeyHeader === "authorization") {
24
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
25
+ } else if (this.apiKeyHeader === "x-api-key") {
26
+ headers["x-api-key"] = this.apiKey;
27
+ } else {
28
+ headers["x-up-api-key"] = this.apiKey;
29
+ }
30
+ }
12
31
  const res = await fetch(target, {
13
32
  method: "POST",
14
- headers: {
15
- "Content-Type": "application/json",
16
- ...this.headers || {}
17
- },
33
+ headers,
18
34
  credentials: this.withCredentials ? "include" : "same-origin",
19
35
  body: JSON.stringify(params)
20
36
  });
@@ -38,6 +54,92 @@ var UpfilesClient = class {
38
54
  }
39
55
  });
40
56
  }
57
+ // Convenience: perform full flow and return URL/path metadata for DB
58
+ async upload(file, extras) {
59
+ const presign = await this.getPresignedUrl({
60
+ fileName: file.name,
61
+ fileType: file.type || "application/octet-stream",
62
+ fileSize: file.size,
63
+ projectId: extras?.projectId,
64
+ folderPath: extras?.folderPath
65
+ });
66
+ const res = await this.uploadToS3(presign.presignedUrl, file);
67
+ if (!res.ok) {
68
+ throw new Error(`Upload failed with status ${res.status}`);
69
+ }
70
+ let thumbnails;
71
+ if (extras?.fetchThumbnails) {
72
+ try {
73
+ const t = await this.getThumbnails(presign.fileKey);
74
+ thumbnails = t;
75
+ } catch {
76
+ }
77
+ }
78
+ return {
79
+ publicUrl: presign.publicUrl,
80
+ fileKey: presign.fileKey,
81
+ fileName: file.name,
82
+ fileType: file.type,
83
+ fileSize: file.size,
84
+ projectId: presign.projectId,
85
+ apiKeyId: presign.apiKeyId,
86
+ thumbnails
87
+ };
88
+ }
89
+ async getThumbnails(fileKey) {
90
+ const target = this.baseUrl ? `${this.baseUrl}${this.thumbnailsPath}` : this.thumbnailsPath;
91
+ const url = `${target}?fileKey=${encodeURIComponent(fileKey)}`;
92
+ const headers = { ...this.headers || {} };
93
+ if (this.apiKey) {
94
+ if (this.apiKeyHeader === "authorization") {
95
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
96
+ } else if (this.apiKeyHeader === "x-api-key") {
97
+ headers["x-api-key"] = this.apiKey;
98
+ } else {
99
+ headers["x-up-api-key"] = this.apiKey;
100
+ }
101
+ }
102
+ const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
103
+ if (!res.ok) {
104
+ let msg = "Failed to fetch thumbnails";
105
+ try {
106
+ const data2 = await res.json();
107
+ msg = data2.error || msg;
108
+ } catch {
109
+ }
110
+ throw new Error(msg);
111
+ }
112
+ const data = await res.json();
113
+ return data?.thumbnails ?? [];
114
+ }
115
+ async getProjectFiles(params) {
116
+ const basePath = this.thumbnailsPath.replace(/\/thumbnails$/, "");
117
+ const filesPath = `${basePath}/files`;
118
+ const target = this.baseUrl ? `${this.baseUrl}${filesPath}` : filesPath;
119
+ const url = params?.folderPath ? `${target}?folderPath=${encodeURIComponent(params.folderPath)}` : target;
120
+ const headers = { ...this.headers || {} };
121
+ if (this.apiKey) {
122
+ if (this.apiKeyHeader === "authorization") {
123
+ headers["authorization"] = this.apiKey.startsWith("upk_") ? `Bearer ${this.apiKey}` : this.apiKey;
124
+ } else if (this.apiKeyHeader === "x-api-key") {
125
+ headers["x-api-key"] = this.apiKey;
126
+ } else {
127
+ headers["x-up-api-key"] = this.apiKey;
128
+ }
129
+ }
130
+ const res = await fetch(url, { method: "GET", headers, credentials: this.withCredentials ? "include" : "same-origin" });
131
+ if (!res.ok) {
132
+ let msg = "Failed to fetch files";
133
+ try {
134
+ const data2 = await res.json();
135
+ msg = data2.error || msg;
136
+ } catch {
137
+ }
138
+ throw new Error(msg);
139
+ }
140
+ const data = await res.json();
141
+ return data?.files ?? [];
142
+ }
41
143
  };
42
144
 
43
145
  // src/Uploader.tsx
@@ -56,7 +158,8 @@ var Uploader = ({
56
158
  dropzoneClassName,
57
159
  children,
58
160
  projectId,
59
- folderPath
161
+ folderPath,
162
+ fetchThumbnails
60
163
  }) => {
61
164
  const inputRef = useRef(null);
62
165
  const client = useMemo(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
@@ -126,14 +229,31 @@ var Uploader = ({
126
229
  xhr.setRequestHeader("Content-Type", item.type || "application/octet-stream");
127
230
  xhr.send(item.file);
128
231
  });
232
+ let thumbs;
233
+ try {
234
+ if (fetchThumbnails) {
235
+ thumbs = await client.getThumbnails(presign.fileKey);
236
+ }
237
+ } catch {
238
+ }
129
239
  update({ status: "success", progress: 100, fileKey: presign.fileKey, publicUrl: presign.publicUrl });
130
- return { ...item, status: "success", progress: 100, fileKey: presign.fileKey, publicUrl: presign.publicUrl };
240
+ return {
241
+ ...item,
242
+ status: "success",
243
+ progress: 100,
244
+ fileKey: presign.fileKey,
245
+ publicUrl: presign.publicUrl,
246
+ projectId: presign.projectId,
247
+ apiKeyId: presign.apiKeyId,
248
+ // @ts-ignore - allow downstream to access thumbs if present
249
+ thumbnails: thumbs
250
+ };
131
251
  } catch (err) {
132
252
  const message = err instanceof Error ? err.message : "Upload failed";
133
253
  update({ status: "error", error: message });
134
254
  throw err;
135
255
  }
136
- }, [client]);
256
+ }, [client, projectId, folderPath, fetchThumbnails]);
137
257
  const uploadAll = useCallback(async () => {
138
258
  const targets = files.filter((f) => f.status === "pending");
139
259
  if (!targets.length) return;
@@ -234,7 +354,151 @@ var Uploader = ({
234
354
  ] })
235
355
  ] });
236
356
  };
357
+
358
+ // src/ProjectFilesWidget.tsx
359
+ import { useEffect, useMemo as useMemo2, useState as useState2 } from "react";
360
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
361
+ var ProjectFilesWidget = ({
362
+ clientOptions,
363
+ folderPath,
364
+ className,
365
+ listClassName,
366
+ itemClassName,
367
+ onSelect,
368
+ saveUrl,
369
+ onSave,
370
+ onSaved
371
+ }) => {
372
+ const client = useMemo2(() => new UpfilesClient(clientOptions ?? {}), [clientOptions]);
373
+ const [files, setFiles] = useState2([]);
374
+ const [loading, setLoading] = useState2(false);
375
+ const [error, setError] = useState2(null);
376
+ const [query, setQuery] = useState2("");
377
+ const [savingKey, setSavingKey] = useState2(null);
378
+ useEffect(() => {
379
+ let cancelled = false;
380
+ const run = async () => {
381
+ setLoading(true);
382
+ setError(null);
383
+ try {
384
+ const data = await client.getProjectFiles({ folderPath });
385
+ if (!cancelled) setFiles(data);
386
+ } catch (e) {
387
+ if (!cancelled) setError(e?.message || "Failed to load files");
388
+ } finally {
389
+ if (!cancelled) setLoading(false);
390
+ }
391
+ };
392
+ run();
393
+ return () => {
394
+ cancelled = true;
395
+ };
396
+ }, [client, folderPath]);
397
+ const visible = files.filter(
398
+ (f) => !query ? true : (f.originalName || "").toLowerCase().includes(query.toLowerCase())
399
+ );
400
+ return /* @__PURE__ */ jsxs2("div", { className, children: [
401
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center", marginBottom: 8 }, children: [
402
+ /* @__PURE__ */ jsx2(
403
+ "input",
404
+ {
405
+ type: "text",
406
+ placeholder: "Search by filename...",
407
+ value: query,
408
+ onChange: (e) => setQuery(e.target.value),
409
+ style: { flex: 1, padding: 8, border: "1px solid #e5e7eb", borderRadius: 6 }
410
+ }
411
+ ),
412
+ /* @__PURE__ */ jsx2(
413
+ "button",
414
+ {
415
+ onClick: () => {
416
+ setQuery("");
417
+ (async () => {
418
+ setLoading(true);
419
+ setError(null);
420
+ try {
421
+ const data = await client.getProjectFiles({ folderPath });
422
+ setFiles(data);
423
+ } catch (e) {
424
+ setError(e?.message || "Failed to load files");
425
+ } finally {
426
+ setLoading(false);
427
+ }
428
+ })();
429
+ },
430
+ style: { padding: "8px 12px", border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" },
431
+ children: "Refresh"
432
+ }
433
+ )
434
+ ] }),
435
+ loading && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#666" }, children: "Loading files\u2026" }),
436
+ error && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12, color: "#b91c1c" }, children: error }),
437
+ !loading && !error && /* @__PURE__ */ jsxs2("ul", { className: listClassName, style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 8 }, children: [
438
+ visible.map((f) => /* @__PURE__ */ jsxs2(
439
+ "li",
440
+ {
441
+ className: itemClassName,
442
+ style: { border: "1px solid #e5e7eb", borderRadius: 8, padding: 10, display: "flex", justifyContent: "space-between", alignItems: "center" },
443
+ children: [
444
+ /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
445
+ /* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis" }, children: f.originalName }),
446
+ /* @__PURE__ */ jsxs2("div", { style: { fontSize: 12, color: "#666" }, children: [
447
+ (f.size / (1024 * 1024)).toFixed(2),
448
+ " MB \u2022 ",
449
+ f.contentType
450
+ ] })
451
+ ] }),
452
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
453
+ /* @__PURE__ */ jsx2("a", { href: f.url, target: "_blank", rel: "noreferrer", style: { fontSize: 12, color: "#2563eb" }, children: "View" }),
454
+ /* @__PURE__ */ jsx2(
455
+ "button",
456
+ {
457
+ onClick: async () => {
458
+ const selected = { name: f.originalName, key: f.key, url: f.url, size: f.size, contentType: f.contentType };
459
+ onSelect(selected);
460
+ if (typeof onSave === "function" || !!saveUrl) {
461
+ try {
462
+ setSavingKey(f.key);
463
+ if (typeof onSave === "function") {
464
+ await onSave(selected);
465
+ onSaved?.();
466
+ }
467
+ if (saveUrl) {
468
+ const res = await fetch(saveUrl, {
469
+ method: "POST",
470
+ headers: { "content-type": "application/json" },
471
+ body: JSON.stringify(selected),
472
+ credentials: "same-origin"
473
+ });
474
+ const data = await res.json().catch(() => void 0);
475
+ if (!res.ok) throw new Error(data?.error || "Failed to save");
476
+ onSaved?.(data);
477
+ }
478
+ } catch (e) {
479
+ console.error("Save selection failed", e);
480
+ alert(e?.message || "Failed to save");
481
+ } finally {
482
+ setSavingKey(null);
483
+ }
484
+ }
485
+ },
486
+ disabled: savingKey === f.key,
487
+ style: { padding: "6px 10px", background: "#2563eb", color: "#fff", border: "1px solid #1d4ed8", borderRadius: 6, opacity: savingKey === f.key ? 0.7 : 1 },
488
+ children: savingKey === f.key ? "Saving\u2026" : "Use"
489
+ }
490
+ )
491
+ ] })
492
+ ]
493
+ },
494
+ f.key
495
+ )),
496
+ visible.length === 0 && /* @__PURE__ */ jsx2("li", { style: { fontSize: 12, color: "#666" }, children: "No files found" })
497
+ ] })
498
+ ] });
499
+ };
237
500
  export {
501
+ ProjectFilesWidget,
238
502
  UpfilesClient,
239
503
  Uploader
240
504
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thetechfossil/upfiles",
3
- "version": "0.1.1",
4
- "description": "Lightweight client and React components to upload files to UpFiles API (presigned S3)",
3
+ "version": "0.4.0",
4
+ "description": "Lightweight client and React components for Upfiles Plugin API (presigned S3)",
5
5
  "license": "MIT",
6
6
  "author": "UpFiles",
7
7
  "repository": {
@@ -14,8 +14,7 @@
14
14
  "exports": {
15
15
  ".": {
16
16
  "import": "./dist/index.mjs",
17
- "require": "./dist/index.cjs",
18
- "types": "./dist/index.d.ts"
17
+ "require": "./dist/index.cjs"
19
18
  }
20
19
  },
21
20
  "files": [
@@ -29,7 +28,7 @@
29
28
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
30
29
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
31
30
  "typecheck": "tsc -p tsconfig.json --noEmit",
32
- "prepublishOnly": "npm run build"
31
+ "prepublishOnly": "bun run build"
33
32
  },
34
33
  "peerDependencies": {
35
34
  "react": ">=18"