@susonwaiba/react-media-uploader 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,102 +1,128 @@
1
- # NextJS Integration
2
-
3
- ## Prisma schema
4
-
5
- ```text
6
- enum MediaStatusEnum {
7
- INIT
8
- TEMP
9
- ACTIVE
10
- INACTIVE
11
- CANCELED
12
- DELETED
13
- }
14
-
15
- enum MediaTypeEnum {
16
- IMAGE
17
- PDF
18
- DOCS
19
- OTHER
20
- }
21
-
22
- model Media {
23
- id String @id @default(cuid())
24
- status MediaStatusEnum @default(INIT)
25
- type MediaTypeEnum
26
- title String
27
- description String?
28
- name String
29
- dir String?
30
- path String
31
- provider String
32
- container String?
33
- mimeType String?
34
- size Float?
35
- height Float?
36
- width Float?
37
- duration Float?
38
- tags String[] @default([])
39
- checksum String?
40
- createdAt DateTime @default(now())
41
- updatedAt DateTime @default(now()) @updatedAt
42
- deletedAt DateTime?
43
- }
44
- ```
45
-
46
- ## API endpoints
47
-
48
- - POST: `/api/media/generate-upload-url`
49
- Response:
50
- ```json
51
- {
52
- "item": {},
53
- "sasUrl": "<sas_upload_url>"
54
- }
55
- ```
56
-
57
- - POST: `/api/media/mark-media-as-active`
58
- Payload:
59
- ```json
60
- {
61
- "mediaIds": []
62
- }
63
- ```
64
- Response:
65
- ```json
66
- {
67
- "items": []
68
- }
69
- ```
70
-
71
- - POST: `/api/media/mark-media-as-canceled`
72
- Payload:
73
- ```json
74
- {
75
- "mediaIds": []
76
- }
77
- ```
78
- Response:
79
- ```json
80
- {
81
- "items": []
82
- }
83
- ```
84
-
85
- - POST: `/api/media/mark-media-as-temp`
86
- Payload:
87
- ```json
88
- {
89
- "mediaIds": []
90
- }
91
- ```
92
- Response:
93
- ```json
94
- {
95
- "items": []
96
- }
97
- ```
98
-
99
- ## Storage cleanup
100
-
101
- Run cron and filter with status for unused media cleanup.
102
-
1
+ # NextJS Integration
2
+
3
+ ## Prisma schema
4
+
5
+ ```text
6
+ enum MediaStatusEnum {
7
+ INIT
8
+ TEMP
9
+ ACTIVE
10
+ INACTIVE
11
+ CANCELED
12
+ DELETED
13
+ }
14
+
15
+ enum MediaTypeEnum {
16
+ IMAGE
17
+ PDF
18
+ DOCS
19
+ OTHER
20
+ }
21
+
22
+ model Media {
23
+ id String @id @default(cuid())
24
+ status MediaStatusEnum @default(INIT)
25
+ type MediaTypeEnum
26
+ title String
27
+ description String?
28
+ name String
29
+ dir String?
30
+ path String
31
+ provider String
32
+ container String?
33
+ mimeType String?
34
+ size Float?
35
+ height Float?
36
+ width Float?
37
+ duration Float?
38
+ tags String[] @default([])
39
+ checksum String?
40
+ createdAt DateTime @default(now())
41
+ updatedAt DateTime @default(now()) @updatedAt
42
+ deletedAt DateTime?
43
+ }
44
+ ```
45
+
46
+ ## API endpoints
47
+
48
+ - POST: `/api/media/generate-upload-url`
49
+
50
+ Payload:
51
+
52
+ ```json
53
+ {
54
+ "type": "IMAGE",
55
+ "name": "screenshot.jpeg",
56
+ "mimeType": "image/jpeg",
57
+ "size": 40868,
58
+ "checksum": "b745eb078c31eaa2ef5d87f772bbf37d78528d3c"
59
+ }
60
+ ```
61
+
62
+ Response:
63
+
64
+ ```json
65
+ {
66
+ "item": {},
67
+ "uploadUrl": "<upload_url_with_token>"
68
+ }
69
+ ```
70
+
71
+ - POST: `/api/media/mark-media-as-active`
72
+
73
+ Payload:
74
+
75
+ ```json
76
+ {
77
+ "mediaIds": []
78
+ }
79
+ ```
80
+
81
+ Response:
82
+
83
+ ```json
84
+ {
85
+ "items": []
86
+ }
87
+ ```
88
+
89
+ - POST: `/api/media/mark-media-as-canceled`
90
+
91
+ Payload:
92
+
93
+ ```json
94
+ {
95
+ "mediaIds": []
96
+ }
97
+ ```
98
+
99
+ Response:
100
+
101
+ ```json
102
+ {
103
+ "items": []
104
+ }
105
+ ```
106
+
107
+ - POST: `/api/media/mark-media-as-temp`
108
+
109
+ Payload:
110
+
111
+ ```json
112
+ {
113
+ "mediaIds": []
114
+ }
115
+ ```
116
+
117
+ Response:
118
+
119
+ ```json
120
+ {
121
+ "items": []
122
+ }
123
+ ```
124
+
125
+ ## Storage cleanup
126
+
127
+ Run cron and filter with status for unused media cleanup.
128
+
package/README.md CHANGED
@@ -1,62 +1,106 @@
1
- # React Media Uploader
2
-
3
- `Status: Under development`
4
-
5
- ## Quick start
6
-
7
- ```bash
8
- bun install
9
-
10
- bun run build
11
- ```
12
-
13
- ## `useMediaUploader()` hook
14
-
15
- #### Upload on select
16
-
17
- ```typescript
18
- export function Uploader() {
19
- const uploader = useMediaUploader();
20
- return (
21
- <input
22
- name="image"
23
- type="file"
24
- multiple
25
- onChange={uploader.onFileInputChange}
26
- />
27
- );
28
- }
29
- ```
30
-
31
- #### Manual upload
32
-
33
- ```typescript
34
- export function Uploader() {
35
- const uploader = useMediaUploader({
36
- enableManualUpload: true,
37
- });
38
-
39
- const onSubmit = async (e: React.FormEvent) => {
40
- e.preventDefault();
41
- const mediaValues = await uploader.uploadManually();
42
- console.log("mediaValues ->", mediaValues);
43
- // submit data to API
44
- };
45
-
46
- return (
47
- <form onSubmit={onSubmit}>
48
- <div className="mb-4">
49
- <input
50
- name="image"
51
- type="file"
52
- multiple
53
- onChange={uploader.onFileInputChange}
54
- />
55
- </div>
56
- <div>
57
- <button type="submit">Upload</button>
58
- </div>
59
- </form>
60
- );
61
- }
62
- ```
1
+ # React Media Uploader
2
+
3
+ `Status: Under development`
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ bun install
9
+
10
+ bun run build
11
+ ```
12
+
13
+ ## Hook
14
+
15
+ #### Upload on select
16
+
17
+ ```typescript
18
+ "use client";
19
+
20
+ import { useMediaUploader } from "@susonwaiba/react-media-uploader";
21
+
22
+ export function Uploader() {
23
+ const uploader = useMediaUploader();
24
+ return (
25
+ <input
26
+ name="image"
27
+ type="file"
28
+ onChange={uploader.onFileInputChange}
29
+ />
30
+ );
31
+ }
32
+ ```
33
+
34
+ #### Manual upload
35
+
36
+ ```typescript
37
+ "use client";
38
+
39
+ import { useMediaUploader, MediaStatusEnum } from "@susonwaiba/react-media-uploader";
40
+
41
+ export function ManualUploader() {
42
+ const uploader = useMediaUploader({
43
+ enableManualUpload: true,
44
+ // Update media status to ACTIVE status on success
45
+ mediaUploadSuccessStatus: MediaStatusEnum.ACTIVE,
46
+ });
47
+
48
+ const onSubmit = async (e: React.FormEvent) => {
49
+ e.preventDefault();
50
+ const mediaValues = await uploader.uploadManually();
51
+ console.log("mediaValues ->", mediaValues);
52
+ // submit data to API
53
+ };
54
+
55
+ return (
56
+ <form onSubmit={onSubmit}>
57
+ <div className="mb-4">
58
+ <input
59
+ name="image"
60
+ type="file"
61
+ multiple
62
+ onChange={uploader.onFileInputChange}
63
+ />
64
+ </div>
65
+ <div>
66
+ <button type="submit">Upload</button>
67
+ </div>
68
+ </form>
69
+ );
70
+ }
71
+ ```
72
+
73
+ #### UseMediaUploaderProps
74
+
75
+ ```typescript
76
+ export interface UseMediaUploaderProps<T extends object> {
77
+ defaultValues?: T;
78
+ mediaUploadSuccessStatus?: MediaStatusEnum;
79
+ enableManualUpload?: boolean;
80
+ serverConfig?: {
81
+ additionalHeaders?: Record<string, string>;
82
+ generateUploadUrl?: string;
83
+ markMediaAsTemp?: string;
84
+ markMediaAsActive?: string;
85
+ markMediaAsCanceled?: string;
86
+ };
87
+ onUploadSuccess?: (currentValues: any) => Promise<void>;
88
+ onUploadFailure?: (uploadRes: any) => Promise<void>;
89
+ }
90
+ ```
91
+
92
+ #### UseMediaUploaderResponse
93
+
94
+ ```typescript
95
+ export interface UseMediaUploaderResponse<T extends object> {
96
+ values: T;
97
+ setValues: (val: T) => void;
98
+ enableManualUpload?: boolean;
99
+ uploadManually: () => Promise<T>;
100
+ mediaItems: Record<string, MediaItem>;
101
+ setMediaItems: (items: Record<string, MediaItem>) => void;
102
+ uploadInfos: Record<string, UploadMediaInfo>;
103
+ onFileInputChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
104
+ onFileChange: (file: File, name: string, multiple?: boolean) => Promise<void>;
105
+ }
106
+ ```
package/bun.lock CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "lockfileVersion": 1,
3
+ "configVersion": 0,
3
4
  "workspaces": {
4
5
  "": {
5
6
  "name": "uploader",
@@ -8,6 +8,15 @@ export interface UseMediaUploaderProps<T extends object> {
8
8
  defaultValues?: T;
9
9
  mediaUploadSuccessStatus?: MediaStatusEnum;
10
10
  enableManualUpload?: boolean;
11
+ serverConfig?: {
12
+ additionalHeaders?: Record<string, string>;
13
+ generateUploadUrl?: string;
14
+ markMediaAsTemp?: string;
15
+ markMediaAsActive?: string;
16
+ markMediaAsCanceled?: string;
17
+ };
18
+ onUploadSuccess?: (currentValues: any) => Promise<void>;
19
+ onUploadFailure?: (uploadRes: any) => Promise<void>;
11
20
  }
12
21
  export interface UseMediaUploaderResponse<T extends object> {
13
22
  values: T;
@@ -15,8 +24,9 @@ export interface UseMediaUploaderResponse<T extends object> {
15
24
  enableManualUpload?: boolean;
16
25
  uploadManually: () => Promise<T>;
17
26
  mediaItems: Record<string, MediaItem>;
27
+ setMediaItems: (items: Record<string, MediaItem>) => void;
18
28
  uploadInfos: Record<string, UploadMediaInfo>;
19
29
  onFileInputChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
20
30
  onFileChange: (file: File, name: string, multiple?: boolean) => Promise<void>;
21
31
  }
22
- export declare function useMediaUploader<T extends object>({ defaultValues, mediaUploadSuccessStatus, enableManualUpload, }?: UseMediaUploaderProps<T>): UseMediaUploaderResponse<T>;
32
+ export declare function useMediaUploader<T extends object>({ defaultValues, mediaUploadSuccessStatus, enableManualUpload, serverConfig, onUploadSuccess, onUploadFailure, }?: UseMediaUploaderProps<T>): UseMediaUploaderResponse<T>;
@@ -1,12 +1,11 @@
1
1
  import { markMediaAsCanceled, generateFileHash, generateUploadUrl, uploadToStorage, markMediaAsTemp, markMediaAsActive, generateMediaType, } from "../lib/media-helper";
2
2
  import { MediaStatusEnum } from "../types/media";
3
3
  import { useState } from "react";
4
- export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = MediaStatusEnum.TEMP, enableManualUpload = false, } = {}) {
4
+ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = MediaStatusEnum.TEMP, enableManualUpload = false, serverConfig, onUploadSuccess, onUploadFailure, } = {}) {
5
5
  const [values, setValues] = useState(defaultValues ?? {});
6
6
  const [mediaItems, setMediaItems] = useState({});
7
7
  const [uploadInfos, setUploadInfos] = useState({});
8
8
  const onFileInputChange = async (e) => {
9
- console.log("onFileInputChange -> e ->", e);
10
9
  const target = e.target;
11
10
  const name = target.name;
12
11
  const multiple = target.multiple || false;
@@ -42,7 +41,11 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
42
41
  }
43
42
  };
44
43
  const uploadMediaFile = async (item) => {
45
- const sasUrlRes = await generateUploadUrl({ media: item?.media });
44
+ const sasUrlRes = await generateUploadUrl({
45
+ url: serverConfig?.generateUploadUrl,
46
+ additionalHeaders: serverConfig?.additionalHeaders,
47
+ media: item?.media,
48
+ });
46
49
  if (sasUrlRes?.data?.item && sasUrlRes?.data?.sasUrl) {
47
50
  item["media"] = sasUrlRes?.data?.item;
48
51
  setMediaItems((previous) => {
@@ -52,7 +55,7 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
52
55
  });
53
56
  const abortController = new AbortController();
54
57
  const uploadRes = await uploadToStorage({
55
- sasUrl: sasUrlRes?.data?.sasUrl,
58
+ uploadUrl: sasUrlRes?.data?.uploadUrl,
56
59
  file: item.file,
57
60
  onUploadProgress: (progressEvent) => {
58
61
  const currentUploadInfo = {
@@ -68,6 +71,8 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
68
71
  return newState;
69
72
  });
70
73
  await markMediaAsCanceled({
74
+ url: serverConfig?.markMediaAsCanceled,
75
+ additionalHeaders: serverConfig?.additionalHeaders,
71
76
  mediaIds: [sasUrlRes?.data?.item?.id],
72
77
  });
73
78
  },
@@ -80,11 +85,11 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
80
85
  },
81
86
  abortController,
82
87
  });
83
- if (uploadRes?.status === 201) {
88
+ if (uploadRes?.status === 201 || uploadRes?.status === 200) {
84
89
  return await onMediaUploadSuccess(item);
85
90
  }
86
- else {
87
- console.log("Media upload failed");
91
+ else if (onUploadFailure) {
92
+ onUploadFailure(uploadRes);
88
93
  }
89
94
  }
90
95
  return undefined;
@@ -94,11 +99,15 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
94
99
  let markRes;
95
100
  if (mediaUploadSuccessStatus === MediaStatusEnum.TEMP) {
96
101
  markRes = await markMediaAsTemp({
102
+ url: serverConfig?.markMediaAsTemp,
103
+ additionalHeaders: serverConfig?.additionalHeaders,
97
104
  mediaIds: [item.media.id],
98
105
  });
99
106
  }
100
107
  else if (mediaUploadSuccessStatus === MediaStatusEnum.ACTIVE) {
101
108
  markRes = await markMediaAsActive({
109
+ url: serverConfig?.markMediaAsActive,
110
+ additionalHeaders: serverConfig?.additionalHeaders,
102
111
  mediaIds: [item.media.id],
103
112
  });
104
113
  }
@@ -132,7 +141,9 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
132
141
  }
133
142
  return newState;
134
143
  });
135
- console.log("Media uploaded successfully");
144
+ if (onUploadSuccess) {
145
+ onUploadSuccess(currentValues);
146
+ }
136
147
  return currentValues;
137
148
  }
138
149
  }
@@ -142,7 +153,6 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
142
153
  const uploadInfoIds = Object.keys(uploadInfos);
143
154
  const mediaItemsToBeUploaded = Object.values(mediaItems).filter((item) => !uploadInfoIds?.includes(item.localId));
144
155
  const uploadResponses = await Promise.all(mediaItemsToBeUploaded?.map(async (item) => await uploadMediaFile(item)));
145
- console.log("uploadResponses ->", uploadResponses);
146
156
  const result = { ...values };
147
157
  for (const uploadResponse of uploadResponses.filter((item) => item !== undefined)) {
148
158
  for (const key in uploadResponse) {
@@ -169,6 +179,7 @@ export function useMediaUploader({ defaultValues, mediaUploadSuccessStatus = Med
169
179
  enableManualUpload,
170
180
  uploadManually,
171
181
  mediaItems,
182
+ setMediaItems,
172
183
  uploadInfos,
173
184
  onFileInputChange,
174
185
  onFileChange,
@@ -7,25 +7,33 @@ export declare const defaultHeaders: {
7
7
  "Content-Type": string;
8
8
  };
9
9
  export interface GenerateUploadUrlProps {
10
+ url?: string;
11
+ additionalHeaders?: Record<string, string>;
10
12
  media: Partial<Media>;
11
13
  }
12
- export declare function generateUploadUrl({ media }: GenerateUploadUrlProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
14
+ export declare function generateUploadUrl({ url, additionalHeaders, media, }: GenerateUploadUrlProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
13
15
  export interface UploadToStorageProps {
14
- sasUrl: string;
16
+ uploadUrl: string;
15
17
  file: File;
16
18
  onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
17
19
  abortController?: AbortController;
18
20
  }
19
- export declare function uploadToStorage({ sasUrl, file, onUploadProgress, abortController, }: UploadToStorageProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
21
+ export declare function uploadToStorage({ uploadUrl, file, onUploadProgress, abortController, }: UploadToStorageProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
20
22
  export interface MarkMediaAsTempProps {
23
+ url?: string;
24
+ additionalHeaders?: Record<string, string>;
21
25
  mediaIds: (string | number)[];
22
26
  }
23
- export declare function markMediaAsTemp({ mediaIds }: MarkMediaAsTempProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
27
+ export declare function markMediaAsTemp({ url, additionalHeaders, mediaIds, }: MarkMediaAsTempProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
24
28
  export interface MarkMediaAsActiveProps {
29
+ url?: string;
30
+ additionalHeaders?: Record<string, string>;
25
31
  mediaIds: (string | number)[];
26
32
  }
27
- export declare function markMediaAsActive({ mediaIds }: MarkMediaAsActiveProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
33
+ export declare function markMediaAsActive({ url, additionalHeaders, mediaIds, }: MarkMediaAsActiveProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
28
34
  export interface MarkMediaAsCanceledProps {
35
+ url?: string;
36
+ additionalHeaders?: Record<string, string>;
29
37
  mediaIds: (string | number)[];
30
38
  }
31
- export declare function markMediaAsCanceled({ mediaIds, }: MarkMediaAsActiveProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
39
+ export declare function markMediaAsCanceled({ url, additionalHeaders, mediaIds, }: MarkMediaAsActiveProps): Promise<import("axios").AxiosResponse<any, any, {}>>;
@@ -28,14 +28,17 @@ export async function generateFileHash(file, algorithm = "sha-1") {
28
28
  export const defaultHeaders = {
29
29
  "Content-Type": "application/json",
30
30
  };
31
- export async function generateUploadUrl({ media }) {
32
- const res = await axios.post("/api/media/generate-upload-url", media, {
33
- headers: defaultHeaders,
31
+ export async function generateUploadUrl({ url, additionalHeaders, media, }) {
32
+ const res = await axios.post(url ?? "/api/media/generate-upload-url", media, {
33
+ headers: {
34
+ ...defaultHeaders,
35
+ ...additionalHeaders,
36
+ },
34
37
  });
35
38
  return res;
36
39
  }
37
- export async function uploadToStorage({ sasUrl, file, onUploadProgress, abortController, }) {
38
- const res = await axios.put(sasUrl, file, {
40
+ export async function uploadToStorage({ uploadUrl, file, onUploadProgress, abortController, }) {
41
+ const res = await axios.put(uploadUrl, file, {
39
42
  headers: {
40
43
  "x-ms-blob-type": "BlockBlob",
41
44
  "Content-Type": file.type,
@@ -45,21 +48,30 @@ export async function uploadToStorage({ sasUrl, file, onUploadProgress, abortCon
45
48
  });
46
49
  return res;
47
50
  }
48
- export async function markMediaAsTemp({ mediaIds }) {
49
- const res = await axios.post(`/api/media/mark-media-as-temp`, { mediaIds }, {
50
- headers: defaultHeaders,
51
+ export async function markMediaAsTemp({ url, additionalHeaders, mediaIds, }) {
52
+ const res = await axios.post(url ?? `/api/media/mark-media-as-temp`, { mediaIds }, {
53
+ headers: {
54
+ ...defaultHeaders,
55
+ ...additionalHeaders,
56
+ },
51
57
  });
52
58
  return res;
53
59
  }
54
- export async function markMediaAsActive({ mediaIds }) {
55
- const res = await axios.post(`/api/media/mark-media-as-active`, { mediaIds }, {
56
- headers: defaultHeaders,
60
+ export async function markMediaAsActive({ url, additionalHeaders, mediaIds, }) {
61
+ const res = await axios.post(url ?? `/api/media/mark-media-as-active`, { mediaIds }, {
62
+ headers: {
63
+ ...defaultHeaders,
64
+ ...additionalHeaders,
65
+ },
57
66
  });
58
67
  return res;
59
68
  }
60
- export async function markMediaAsCanceled({ mediaIds, }) {
61
- const res = await axios.post(`/api/media/mark-media-as-canceled`, [mediaIds], {
62
- headers: defaultHeaders,
69
+ export async function markMediaAsCanceled({ url, additionalHeaders, mediaIds, }) {
70
+ const res = await axios.post(url ?? `/api/media/mark-media-as-canceled`, [mediaIds], {
71
+ headers: {
72
+ ...defaultHeaders,
73
+ ...additionalHeaders,
74
+ },
63
75
  });
64
76
  return res;
65
77
  }