ultra-image-uploader 0.0.1
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 +201 -0
- package/dist/components/ImageUploader.d.ts +12 -0
- package/dist/components/ImageUploader.js +27 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/utils/imageUpload.d.ts +10 -0
- package/dist/utils/imageUpload.js +31 -0
- package/package.json +27 -0
- package/src/components/ImageUploader.tsx +115 -0
- package/src/index.ts +4 -0
- package/src/utils/imageUpload.ts +56 -0
- package/tsconfig.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Ultra Image Uploader
|
|
2
|
+
|
|
3
|
+
A modern React component for handling image uploads with drag-and-drop functionality, image preview, and ImgBB integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Easy Integration** - Simple to use with any React/Next.js project
|
|
8
|
+
- 🖼️ **Drag & Drop** - Intuitive drag and drop interface
|
|
9
|
+
- 📸 **Image Preview** - Instant preview of uploaded images
|
|
10
|
+
- ✨ **Multiple Upload** - Support for multiple image uploads
|
|
11
|
+
- 🔍 **File Validation** - Built-in file type and size validation
|
|
12
|
+
- 🎨 **Customizable** - Highly customizable styling and components
|
|
13
|
+
- 🗑️ **Delete Function** - Easy image removal functionality
|
|
14
|
+
- 🔄 **ImgBB Integration** - Built-in support for ImgBB image hosting
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install ultra-image-uploaderer
|
|
20
|
+
# or
|
|
21
|
+
yarn add ultra-image-uploaderer
|
|
22
|
+
# or
|
|
23
|
+
pnpm add ultra-image-uploaderer
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Basic Usage
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { ImageUploader, imageUrl } from "ultra-image-uploaderer";
|
|
32
|
+
import { useState } from "react";
|
|
33
|
+
|
|
34
|
+
function App() {
|
|
35
|
+
const [images, setImages] = useState<File[]>([]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ImageUploader
|
|
39
|
+
images={images}
|
|
40
|
+
setImages={setImages}
|
|
41
|
+
mode="add"
|
|
42
|
+
multiple={true}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### With ImgBB Integration
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { ImageUploader, imageUrl } from "ultra-image-uploaderer";
|
|
52
|
+
import { useState } from "react";
|
|
53
|
+
|
|
54
|
+
function ImageUploadForm() {
|
|
55
|
+
const [images, setImages] = useState<File[]>([]);
|
|
56
|
+
|
|
57
|
+
const handleSubmit = async () => {
|
|
58
|
+
try {
|
|
59
|
+
const uploadedUrls = await imageUrl(images, "YOUR_IMGBB_API_KEY");
|
|
60
|
+
console.log("Uploaded image URLs:", uploadedUrls);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Upload failed:", error);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<form onSubmit={handleSubmit}>
|
|
68
|
+
<ImageUploader
|
|
69
|
+
images={images}
|
|
70
|
+
setImages={setImages}
|
|
71
|
+
mode="add"
|
|
72
|
+
multiple={true}
|
|
73
|
+
/>
|
|
74
|
+
<button type="submit">Upload Images</button>
|
|
75
|
+
</form>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Component Props
|
|
81
|
+
|
|
82
|
+
| Prop | Type | Default | Description |
|
|
83
|
+
| ----------------------- | ------------------------------------- | ----------------------------- | -------------------------------- |
|
|
84
|
+
| `images` | `File[]` | Required | Array of selected image files |
|
|
85
|
+
| `setImages` | `(images: File[]) => void` | Required | Function to update images array |
|
|
86
|
+
| `mode` | `"add" \| "update"` | Required | Mode of operation |
|
|
87
|
+
| `defaultImages` | `string[]` | `[]` | Array of existing image URLs |
|
|
88
|
+
| `multiple` | `boolean` | `false` | Allow multiple file selection |
|
|
89
|
+
| `maxFileSize` | `number` | `5242880` | Maximum file size in bytes (5MB) |
|
|
90
|
+
| `allowedFileTypes` | `string[]` | `["image/jpeg", "image/png"]` | Allowed MIME types |
|
|
91
|
+
| `containerClassName` | `string` | `""` | Custom container class |
|
|
92
|
+
| `uploadBoxClassName` | `string` | `""` | Custom upload box class |
|
|
93
|
+
| `imageClassName` | `string` | `""` | Custom image preview class |
|
|
94
|
+
| `uploadBoxStyle` | `React.CSSProperties` | `{}` | Custom upload box styles |
|
|
95
|
+
| `imageStyle` | `React.CSSProperties` | `{}` | Custom image preview styles |
|
|
96
|
+
| `uploadIcon` | `React.ReactNode` | `<UploadCloudIcon />` | Custom upload icon |
|
|
97
|
+
| `deleteIcon` | `React.ReactNode` | `<TrashIcon />` | Custom delete icon |
|
|
98
|
+
| `uploadText` | `string` | `"Choose files to upload"` | Upload box text |
|
|
99
|
+
| `dragAndDropText` | `string` | `"Drag and drop files here"` | Drag and drop text |
|
|
100
|
+
| `fileTypeText` | `string` | `"PNG, JPG, or JPEG files"` | File type info text |
|
|
101
|
+
| `onUpload` | `(files: File[]) => void` | - | Upload callback |
|
|
102
|
+
| `onRemove` | `(file: File, index: number) => void` | - | Remove callback |
|
|
103
|
+
| `onFileValidationError` | `(error: string) => void` | - | Validation error callback |
|
|
104
|
+
|
|
105
|
+
## Usage Examples
|
|
106
|
+
|
|
107
|
+
### Add Mode (New Upload)
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { ImageUploader, imageUrl } from "ultra-image-uploaderer";
|
|
111
|
+
|
|
112
|
+
function AddImage() {
|
|
113
|
+
const [images, setImages] = useState<File[]>([]);
|
|
114
|
+
|
|
115
|
+
const handleSubmit = async () => {
|
|
116
|
+
const imgUrls = await imageUrl(images, "YOUR_IMGBB_API_KEY");
|
|
117
|
+
// Handle the uploaded image URLs
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<form onSubmit={handleSubmit}>
|
|
122
|
+
<ImageUploader
|
|
123
|
+
images={images}
|
|
124
|
+
setImages={setImages}
|
|
125
|
+
mode="add"
|
|
126
|
+
multiple={true}
|
|
127
|
+
uploadBoxClassName="border-3 border-dashed p-5"
|
|
128
|
+
imageClassName="w-20 h-20"
|
|
129
|
+
/>
|
|
130
|
+
<button type="submit">Upload</button>
|
|
131
|
+
</form>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Update Mode (Edit Existing Images)
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
function UpdateImage() {
|
|
140
|
+
const [images, setImages] = useState<File[]>([]);
|
|
141
|
+
const existingImages = ["https://example.com/image1.jpg"];
|
|
142
|
+
|
|
143
|
+
const handleSubmit = async () => {
|
|
144
|
+
const newImgUrls = await imageUrl(images, "YOUR_IMGBB_API_KEY");
|
|
145
|
+
// Combine existing and new images
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<form onSubmit={handleSubmit}>
|
|
150
|
+
<ImageUploader
|
|
151
|
+
images={images}
|
|
152
|
+
setImages={setImages}
|
|
153
|
+
mode="update"
|
|
154
|
+
multiple={true}
|
|
155
|
+
defaultImages={existingImages}
|
|
156
|
+
uploadBoxClassName="border-3 border-dashed p-5"
|
|
157
|
+
imageClassName="w-20 h-20"
|
|
158
|
+
/>
|
|
159
|
+
<button type="submit">Update</button>
|
|
160
|
+
</form>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Styling
|
|
166
|
+
|
|
167
|
+
The component uses Tailwind CSS classes by default but can be customized using className props:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<ImageUploader
|
|
171
|
+
images={images}
|
|
172
|
+
setImages={setImages}
|
|
173
|
+
mode="add"
|
|
174
|
+
containerClassName="max-w-2xl mx-auto"
|
|
175
|
+
uploadBoxClassName="border-2 border-dashed border-blue-500 rounded-lg"
|
|
176
|
+
imageClassName="rounded-lg shadow-md"
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## ImgBB Integration
|
|
181
|
+
|
|
182
|
+
The package includes a utility function `imageUrl` for uploading images to ImgBB:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const uploadImages = async (files: File[]) => {
|
|
186
|
+
try {
|
|
187
|
+
const urls = await imageUrl(files, "YOUR_IMGBB_API_KEY");
|
|
188
|
+
console.log("Uploaded URLs:", urls);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error("Upload failed:", error);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
198
|
+
|
|
199
|
+
## Contributing
|
|
200
|
+
|
|
201
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ImageUploaderProps {
|
|
2
|
+
images: File[];
|
|
3
|
+
setImages: (images: File[]) => void;
|
|
4
|
+
mode: "add" | "update";
|
|
5
|
+
defaultImages?: string[];
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
inputStyles?: string;
|
|
8
|
+
containerStyles?: string;
|
|
9
|
+
uploadText?: string;
|
|
10
|
+
typeText?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function ImageUploader({ images, setImages, mode, defaultImages, multiple, inputStyles, containerStyles, uploadText, typeText, }: ImageUploaderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { TrashIcon, UploadCloudIcon } from "lucide-react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
export function ImageUploader({ images, setImages, mode, defaultImages = [], multiple = false, inputStyles = "", containerStyles = "", uploadText = "Browse files or drag & drop", typeText = "PNG, JPG, JPEG, WEBP", }) {
|
|
6
|
+
const [removedDefaultImages, setRemovedDefaultImages] = useState([]);
|
|
7
|
+
console.log(removedDefaultImages);
|
|
8
|
+
const handleImageChange = (e) => {
|
|
9
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
10
|
+
const files = Array.from(e.target.files);
|
|
11
|
+
if (!multiple && files.length > 1) {
|
|
12
|
+
alert("Only one image can be uploaded at a time.");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
setImages(multiple ? [...images, ...files] : [files[0]]);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const removeImage = (index) => {
|
|
19
|
+
setImages(images.filter((_, i) => i !== index));
|
|
20
|
+
};
|
|
21
|
+
// Function to remove a default image
|
|
22
|
+
const removeDefaultImage = (index) => {
|
|
23
|
+
setRemovedDefaultImages((prev) => [...prev, index]);
|
|
24
|
+
};
|
|
25
|
+
return (_jsx("div", { className: `space-y-4 ${containerStyles}`, children: _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("div", { className: `${inputStyles} h-40 w-full bg-gray-100 border border-gray-200 cursor-pointer flex justify-center items-center text-white text-center relative rounded-xs duration-300`, children: [_jsxs("div", { className: "flex flex-col items-center text-black", children: [_jsx(UploadCloudIcon, { className: "text-3xl text-gray-900 mb-1" }), _jsx("span", { className: "text-sm text-gray-800 font-semibold", children: uploadText }), _jsx("span", { className: "text-gray-600 text-xs", children: typeText })] }), _jsx("input", { type: "file", accept: "image/*", multiple: multiple, onChange: handleImageChange, className: "absolute inset-0 opacity-0 cursor-pointer rounded-lg" })] }), mode === "update" &&
|
|
26
|
+
defaultImages.map((url, index) => !removedDefaultImages.includes(index) && (_jsxs("div", { className: "relative w-fit", children: [_jsx("img", { src: url, alt: `Existing Image ${index + 1}`, width: 100, height: 100, className: "h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg transition-shadow duration-200" }), _jsx(TrashIcon, { onClick: () => removeDefaultImage(index), className: "absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200" })] }, `default-${index}`))), images.map((image, index) => (_jsxs("div", { className: "relative w-fit", children: [_jsx("img", { src: URL.createObjectURL(image), alt: `Preview ${index + 1}`, width: 100, height: 100, className: "h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg duration-200" }), _jsx(TrashIcon, { onClick: () => removeImage(index), className: "absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200" })] }, index)))] }) }));
|
|
27
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const uploadImageToImageBB = async (imageFile, apiKey) => {
|
|
2
|
+
const formData = new FormData();
|
|
3
|
+
formData.append("image", imageFile);
|
|
4
|
+
try {
|
|
5
|
+
const response = await fetch(`https://api.imgbb.com/1/upload?key=${apiKey}`, {
|
|
6
|
+
method: "POST",
|
|
7
|
+
body: formData,
|
|
8
|
+
});
|
|
9
|
+
const data = await response.json();
|
|
10
|
+
if (data.success) {
|
|
11
|
+
return data.data.url;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
throw new Error("Image upload failed");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error("Error uploading image:", error);
|
|
19
|
+
alert("❌ Failed to upload image. Please try again.");
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const uploadImagesToImageBB = async (images, apiKey) => {
|
|
24
|
+
const imageURLs = (await Promise.all(images.map((image) => uploadImageToImageBB(image, apiKey)))).filter((url) => url !== null);
|
|
25
|
+
if (imageURLs.length === 0) {
|
|
26
|
+
throw new Error("Failed to upload images");
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
urls: imageURLs,
|
|
30
|
+
};
|
|
31
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ultra-image-uploader",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React component for image uploads with drag-and-drop and ImgBB integration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepare": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@types/node": "^22.13.14",
|
|
13
|
+
"lucide-react": "^0.485.0",
|
|
14
|
+
"react": "^19.1.0",
|
|
15
|
+
"react-dom": "^19.1.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^19.1.1"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"react",
|
|
22
|
+
"form",
|
|
23
|
+
"typescript"
|
|
24
|
+
],
|
|
25
|
+
"author": "Digontha Das",
|
|
26
|
+
"license": "MIT"
|
|
27
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { TrashIcon, UploadCloudIcon } from "lucide-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
export interface ImageUploaderProps {
|
|
6
|
+
images: File[];
|
|
7
|
+
setImages: (images: File[]) => void;
|
|
8
|
+
mode: "add" | "update";
|
|
9
|
+
defaultImages?: string[];
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
inputStyles?: string;
|
|
12
|
+
containerStyles?: string;
|
|
13
|
+
uploadText?: string;
|
|
14
|
+
typeText?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ImageUploader({
|
|
18
|
+
images,
|
|
19
|
+
setImages,
|
|
20
|
+
mode,
|
|
21
|
+
defaultImages = [],
|
|
22
|
+
multiple = false,
|
|
23
|
+
inputStyles = "",
|
|
24
|
+
containerStyles = "",
|
|
25
|
+
uploadText = "Browse files or drag & drop",
|
|
26
|
+
typeText = "PNG, JPG, JPEG, WEBP",
|
|
27
|
+
}: ImageUploaderProps) {
|
|
28
|
+
const [removedDefaultImages, setRemovedDefaultImages] = useState<number[]>(
|
|
29
|
+
[]
|
|
30
|
+
);
|
|
31
|
+
console.log(removedDefaultImages);
|
|
32
|
+
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
33
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
34
|
+
const files = Array.from(e.target.files);
|
|
35
|
+
if (!multiple && files.length > 1) {
|
|
36
|
+
alert("Only one image can be uploaded at a time.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setImages(multiple ? [...images, ...files] : [files[0]]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const removeImage = (index: number) => {
|
|
44
|
+
setImages(images.filter((_, i) => i !== index));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Function to remove a default image
|
|
48
|
+
const removeDefaultImage = (index: number) => {
|
|
49
|
+
setRemovedDefaultImages((prev) => [...prev, index]);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className={`space-y-4 ${containerStyles}`}>
|
|
54
|
+
<div className="flex flex-wrap gap-4">
|
|
55
|
+
{/* Upload Box */}
|
|
56
|
+
<div
|
|
57
|
+
className={`${inputStyles} h-40 w-full bg-gray-100 border border-gray-200 cursor-pointer flex justify-center items-center text-white text-center relative rounded-xs duration-300`}
|
|
58
|
+
>
|
|
59
|
+
<div className="flex flex-col items-center text-black">
|
|
60
|
+
<UploadCloudIcon className="text-3xl text-gray-900 mb-1" />
|
|
61
|
+
<span className="text-sm text-gray-800 font-semibold">
|
|
62
|
+
{uploadText}
|
|
63
|
+
</span>
|
|
64
|
+
<span className="text-gray-600 text-xs">{typeText}</span>
|
|
65
|
+
</div>
|
|
66
|
+
<input
|
|
67
|
+
type="file"
|
|
68
|
+
accept="image/*"
|
|
69
|
+
multiple={multiple}
|
|
70
|
+
onChange={handleImageChange}
|
|
71
|
+
className="absolute inset-0 opacity-0 cursor-pointer rounded-lg"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{/* Default Images (Update Mode) */}
|
|
76
|
+
{mode === "update" &&
|
|
77
|
+
defaultImages.map(
|
|
78
|
+
(url, index) =>
|
|
79
|
+
!removedDefaultImages.includes(index) && (
|
|
80
|
+
<div key={`default-${index}`} className="relative w-fit">
|
|
81
|
+
<img
|
|
82
|
+
src={url}
|
|
83
|
+
alt={`Existing Image ${index + 1}`}
|
|
84
|
+
width={100}
|
|
85
|
+
height={100}
|
|
86
|
+
className="h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg transition-shadow duration-200"
|
|
87
|
+
/>
|
|
88
|
+
<TrashIcon
|
|
89
|
+
onClick={() => removeDefaultImage(index)}
|
|
90
|
+
className="absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{/* Uploaded Images */}
|
|
97
|
+
{images.map((image, index) => (
|
|
98
|
+
<div key={index} className="relative w-fit">
|
|
99
|
+
<img
|
|
100
|
+
src={URL.createObjectURL(image)}
|
|
101
|
+
alt={`Preview ${index + 1}`}
|
|
102
|
+
width={100}
|
|
103
|
+
height={100}
|
|
104
|
+
className="h-32 w-32 object-cover rounded-sm border border-gray-200 cursor-pointer hover:shadow-lg duration-200"
|
|
105
|
+
/>
|
|
106
|
+
<TrashIcon
|
|
107
|
+
onClick={() => removeImage(index)}
|
|
108
|
+
className="absolute -top-2 -right-2 rounded-full bg-red-600 text-2xl text-white cursor-pointer p-1 hover:bg-red-700 transition-colors duration-200"
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ImageBBResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data: {
|
|
4
|
+
url: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
export interface ImageBBUrlResult {
|
|
9
|
+
urls: string[];
|
|
10
|
+
}
|
|
11
|
+
const uploadImageToImageBB = async (
|
|
12
|
+
imageFile: File,
|
|
13
|
+
apiKey: string
|
|
14
|
+
): Promise<string | null> => {
|
|
15
|
+
const formData = new FormData();
|
|
16
|
+
formData.append("image", imageFile);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch(
|
|
20
|
+
`https://api.imgbb.com/1/upload?key=${apiKey}`,
|
|
21
|
+
{
|
|
22
|
+
method: "POST",
|
|
23
|
+
body: formData,
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const data: ImageBBResponse = await response.json();
|
|
28
|
+
|
|
29
|
+
if (data.success) {
|
|
30
|
+
return data.data.url;
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error("Image upload failed");
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error("Error uploading image:", error);
|
|
36
|
+
alert("❌ Failed to upload image. Please try again.");
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const uploadImagesToImageBB = async (
|
|
42
|
+
images: File[],
|
|
43
|
+
apiKey: string
|
|
44
|
+
): Promise<ImageBBUrlResult> => {
|
|
45
|
+
const imageURLs = (
|
|
46
|
+
await Promise.all(images.map((image) => uploadImageToImageBB(image, apiKey)))
|
|
47
|
+
).filter((url): url is string => url !== null);
|
|
48
|
+
|
|
49
|
+
if (imageURLs.length === 0) {
|
|
50
|
+
throw new Error("Failed to upload images");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
urls: imageURLs,
|
|
55
|
+
};
|
|
56
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "./dist",
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Node",
|
|
8
|
+
"target": "ESNext",
|
|
9
|
+
"lib": ["DOM", "ESNext", "DOM.Iterable"],
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationDir": "./dist",
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|