@tradly/asset 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FileUpload.js +163 -0
- package/dist/components/Icons.js +39 -0
- package/dist/components/ImagesSkeleton.js +21 -0
- package/dist/components/MediaGallery.js +153 -0
- package/dist/components/MediaPopup.js +104 -0
- package/dist/components/MediaTab.js +141 -0
- package/dist/components/Pagination.js +141 -0
- package/dist/components/VideosGallery.js +153 -0
- package/dist/esm/components/FileUpload.js +155 -0
- package/dist/esm/components/Icons.js +32 -0
- package/dist/esm/components/ImagesSkeleton.js +14 -0
- package/dist/esm/components/MediaGallery.js +144 -0
- package/dist/esm/components/MediaPopup.js +97 -0
- package/dist/esm/components/MediaTab.js +132 -0
- package/dist/esm/components/Pagination.js +134 -0
- package/dist/esm/components/VideosGallery.js +144 -0
- package/{index.js → dist/esm/index.js} +6 -6
- package/dist/esm/services/apiService.js +373 -0
- package/dist/index.js +60 -0
- package/dist/services/apiService.js +379 -0
- package/package.json +17 -6
- package/EXAMPLE_USAGE.jsx +0 -128
- package/MIGRATION_GUIDE.md +0 -249
- package/STYLING_GUIDE.md +0 -219
- package/src/components/FileUpload.jsx +0 -114
- package/src/components/Icons.jsx +0 -23
- package/src/components/ImagesSkeleton.jsx +0 -18
- package/src/components/MediaGallery.jsx +0 -125
- package/src/components/MediaPopup.jsx +0 -101
- package/src/components/MediaTab.jsx +0 -152
- package/src/components/Pagination.jsx +0 -175
- package/src/components/VideosGallery.jsx +0 -121
- package/src/services/apiService.js +0 -270
package/MIGRATION_GUIDE.md
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
# Migration Guide: From Existing Media Components to @tradly/asset Package
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This guide helps you migrate from the existing media components in
|
|
6
|
-
`src/Shared/fields/MediaSections/` to the new reusable `@tradly/asset` package.
|
|
7
|
-
|
|
8
|
-
## Benefits of Using the Package
|
|
9
|
-
|
|
10
|
-
1. **Reusability**: Use the same media gallery across multiple projects
|
|
11
|
-
2. **Maintainability**: Single source of truth for media gallery functionality
|
|
12
|
-
3. **Flexibility**: Easy to configure with different auth keys and API endpoints
|
|
13
|
-
4. **Separation of Concerns**: Media gallery logic is isolated from your main
|
|
14
|
-
app
|
|
15
|
-
|
|
16
|
-
## Package Structure
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
packages/asset/
|
|
20
|
-
├── src/
|
|
21
|
-
│ ├── components/
|
|
22
|
-
│ │ ├── MediaPopup.jsx # Main popup component
|
|
23
|
-
│ │ ├── MediaTab.jsx # Tab navigation
|
|
24
|
-
│ │ ├── MediaGallery.jsx # Images gallery
|
|
25
|
-
│ │ ├── VideosGallery.jsx # Videos gallery
|
|
26
|
-
│ │ ├── FileUpload.jsx # File upload component
|
|
27
|
-
│ │ ├── Pagination.jsx # Pagination component
|
|
28
|
-
│ │ ├── ImagesSkeleton.jsx # Loading skeleton
|
|
29
|
-
│ │ └── Icons.jsx # Icon components
|
|
30
|
-
│ ├── services/
|
|
31
|
-
│ │ └── apiService.js # API service layer
|
|
32
|
-
│ └── index.js # Main export
|
|
33
|
-
├── package.json
|
|
34
|
-
├── README.md
|
|
35
|
-
└── EXAMPLE_USAGE.jsx
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Migration Steps
|
|
39
|
-
|
|
40
|
-
### Step 1: Install/Setup the Package
|
|
41
|
-
|
|
42
|
-
Since this is a local package, you can either:
|
|
43
|
-
|
|
44
|
-
**Option A: Use as local package (for now)**
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
// In your main package.json, add:
|
|
48
|
-
{
|
|
49
|
-
"dependencies": {
|
|
50
|
-
"@tradly/asset": "file:./packages/asset"
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
**Option B: Publish to npm (later)**
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
cd packages/asset
|
|
59
|
-
npm publish
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Step 2: Update Your Component
|
|
63
|
-
|
|
64
|
-
**Before (Old way):**
|
|
65
|
-
|
|
66
|
-
```jsx
|
|
67
|
-
import MediaGallery from "@/Shared/fields/MediaSections/MediaPopup";
|
|
68
|
-
|
|
69
|
-
function MyComponent() {
|
|
70
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<MediaGallery
|
|
74
|
-
isOpenPopup={isOpen}
|
|
75
|
-
setIsOpenPopup={setIsOpen}
|
|
76
|
-
current_data={selectedMedia}
|
|
77
|
-
update_data={setSelectedMedia}
|
|
78
|
-
options={["image"]}
|
|
79
|
-
/>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
**After (New way):**
|
|
85
|
-
|
|
86
|
-
```jsx
|
|
87
|
-
import { MediaPopup, MediaApiService } from "@tradly/asset";
|
|
88
|
-
import { getAuthKey } from "constant/functions";
|
|
89
|
-
|
|
90
|
-
function MyComponent() {
|
|
91
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
92
|
-
const [selectedMedia, setSelectedMedia] = useState(null);
|
|
93
|
-
|
|
94
|
-
// Initialize API service with Tradly SDK
|
|
95
|
-
const apiService = new MediaApiService({
|
|
96
|
-
authKey: getAuthKey(),
|
|
97
|
-
baseUrl: "/api",
|
|
98
|
-
tradly: tradly, // Pass Tradly SDK instance (required for uploads)
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<MediaPopup
|
|
103
|
-
isOpen={isOpen}
|
|
104
|
-
onClose={() => setIsOpen(false)}
|
|
105
|
-
onSelect={setSelectedMedia}
|
|
106
|
-
currentData={selectedMedia}
|
|
107
|
-
options={["image"]}
|
|
108
|
-
apiService={apiService}
|
|
109
|
-
/>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Step 3: Configure API Service
|
|
115
|
-
|
|
116
|
-
The package handles all upload logic internally using direct API calls. The API
|
|
117
|
-
base URL is automatically detected from your environment:
|
|
118
|
-
|
|
119
|
-
```jsx
|
|
120
|
-
const apiService = new MediaApiService({
|
|
121
|
-
authKey: getAuthKey(), // X-Auth-Key header
|
|
122
|
-
bearerToken: "your-bearer-token", // Required: Bearer token
|
|
123
|
-
// environment is auto-detected from process.env.ENVIRONMENT
|
|
124
|
-
// apiBaseUrl is auto-set based on environment:
|
|
125
|
-
// - Dev: https://api.dev.tradly.app
|
|
126
|
-
// - Prod: https://api.tradly.app
|
|
127
|
-
});
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
The package will automatically:
|
|
131
|
-
|
|
132
|
-
1. Detect environment from `process.env.ENVIRONMENT` (or you can pass
|
|
133
|
-
`environment: 'dev'` or `'production'`)
|
|
134
|
-
2. Set the correct API base URL (`https://api.dev.tradly.app` for dev,
|
|
135
|
-
`https://api.tradly.app` for prod)
|
|
136
|
-
3. Get S3 signed URLs via API call to `POST /v1/utils/S3signedUploadURL`
|
|
137
|
-
4. Upload files to S3 using the signed URLs
|
|
138
|
-
5. Save media metadata via `POST /v1/media` with `{ media: [...] }` format
|
|
139
|
-
6. Fetch media list via `GET /v1/media?page=1&parent=0&mime_type=...`
|
|
140
|
-
|
|
141
|
-
## Key Differences
|
|
142
|
-
|
|
143
|
-
| Old Component | New Package |
|
|
144
|
-
| --------------------------------------- | -------------------------------- |
|
|
145
|
-
| `isOpenPopup` prop | `isOpen` prop |
|
|
146
|
-
| `setIsOpenPopup` prop | `onClose` callback |
|
|
147
|
-
| `update_data` prop | `onSelect` callback |
|
|
148
|
-
| `current_data` prop | `currentData` prop |
|
|
149
|
-
| Uses `proxy_api_call` directly | Uses `MediaApiService` |
|
|
150
|
-
| Auth key from `getAuthKey()` internally | Auth key passed via `apiService` |
|
|
151
|
-
| Hardcoded API paths | Configurable via `apiService` |
|
|
152
|
-
|
|
153
|
-
## Complete Example
|
|
154
|
-
|
|
155
|
-
Here's a complete example showing how to replace the old component:
|
|
156
|
-
|
|
157
|
-
```jsx
|
|
158
|
-
import React, { useState } from "react";
|
|
159
|
-
import { MediaPopup, MediaApiService } from "@tradly/asset";
|
|
160
|
-
import { getAuthKey } from "constant/functions";
|
|
161
|
-
|
|
162
|
-
function MyMediaSelector() {
|
|
163
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
164
|
-
const [selectedImage, setSelectedImage] = useState(null);
|
|
165
|
-
|
|
166
|
-
// Create API service instance
|
|
167
|
-
// API base URL is automatically detected from ENVIRONMENT
|
|
168
|
-
// All API calls go directly to Tradly API (no proxy needed)
|
|
169
|
-
const apiService = React.useMemo(() => {
|
|
170
|
-
return new MediaApiService({
|
|
171
|
-
authKey: getAuthKey(),
|
|
172
|
-
bearerToken: "your-bearer-token", // Required: Bearer token
|
|
173
|
-
// apiBaseUrl is auto-detected from process.env.ENVIRONMENT
|
|
174
|
-
onError: (error) => {
|
|
175
|
-
console.error("Media API Error:", error);
|
|
176
|
-
// Show error toast, etc.
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
}, []);
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<div>
|
|
183
|
-
<button onClick={() => setIsOpen(true)}>
|
|
184
|
-
Select Media
|
|
185
|
-
</button>
|
|
186
|
-
|
|
187
|
-
{selectedImage && (
|
|
188
|
-
<img
|
|
189
|
-
src={selectedImage}
|
|
190
|
-
alt="Selected"
|
|
191
|
-
/>
|
|
192
|
-
)}
|
|
193
|
-
|
|
194
|
-
<MediaPopup
|
|
195
|
-
isOpen={isOpen}
|
|
196
|
-
onClose={() => setIsOpen(false)}
|
|
197
|
-
onSelect={(url) => {
|
|
198
|
-
setSelectedImage(url);
|
|
199
|
-
setIsOpen(false);
|
|
200
|
-
}}
|
|
201
|
-
currentData={selectedImage}
|
|
202
|
-
options={["image", "video"]}
|
|
203
|
-
apiService={apiService}
|
|
204
|
-
onError={(error) => {
|
|
205
|
-
console.error("Gallery error:", error);
|
|
206
|
-
}}
|
|
207
|
-
/>
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export default MyMediaSelector;
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
## Testing
|
|
216
|
-
|
|
217
|
-
After migration, test the following:
|
|
218
|
-
|
|
219
|
-
1. ✅ Opening/closing the media gallery
|
|
220
|
-
2. ✅ Uploading new images/videos
|
|
221
|
-
3. ✅ Selecting existing media
|
|
222
|
-
4. ✅ Pagination works correctly
|
|
223
|
-
5. ✅ Error handling works
|
|
224
|
-
6. ✅ Authentication is working
|
|
225
|
-
|
|
226
|
-
## Rollback Plan
|
|
227
|
-
|
|
228
|
-
If you need to rollback:
|
|
229
|
-
|
|
230
|
-
1. Keep the old components in `src/Shared/fields/MediaSections/`
|
|
231
|
-
2. Import from the old location instead of the package
|
|
232
|
-
3. The old components will continue to work as before
|
|
233
|
-
|
|
234
|
-
## Next Steps
|
|
235
|
-
|
|
236
|
-
1. Test the package in a development environment
|
|
237
|
-
2. Gradually migrate components one by one
|
|
238
|
-
3. Once stable, consider removing old components
|
|
239
|
-
4. Optionally publish to npm for use in other projects
|
|
240
|
-
|
|
241
|
-
## Support
|
|
242
|
-
|
|
243
|
-
If you encounter issues during migration:
|
|
244
|
-
|
|
245
|
-
1. Check the `EXAMPLE_USAGE.jsx` file
|
|
246
|
-
2. Review the `README.md` for API documentation
|
|
247
|
-
3. Ensure all peer dependencies are installed
|
|
248
|
-
4. Verify your auth key and API base URL are correct
|
|
249
|
-
|
package/STYLING_GUIDE.md
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
# Styling Guide - Customizing Media Gallery with Tailwind CSS
|
|
2
|
-
|
|
3
|
-
The `@tradly/asset` package supports full customization of all components using
|
|
4
|
-
Tailwind CSS class names. You can customize styles at every level of the
|
|
5
|
-
component hierarchy.
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
All components accept `className` props that allow you to override default
|
|
10
|
-
styles. The package uses sensible Tailwind defaults, but you can customize
|
|
11
|
-
everything to match your design system.
|
|
12
|
-
|
|
13
|
-
## MediaPopup Styling Props
|
|
14
|
-
|
|
15
|
-
The main popup component supports these styling props:
|
|
16
|
-
|
|
17
|
-
```jsx
|
|
18
|
-
<MediaPopup
|
|
19
|
-
// ... other props
|
|
20
|
-
overlayClassName="..." // Overlay/backdrop styles
|
|
21
|
-
containerClassName="..." // Main popup container
|
|
22
|
-
headerClassName="..." // Header container
|
|
23
|
-
titleClassName="..." // Title text
|
|
24
|
-
closeButtonClassName="..." // Close button
|
|
25
|
-
/>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Example
|
|
29
|
-
|
|
30
|
-
```jsx
|
|
31
|
-
<MediaPopup
|
|
32
|
-
isOpen={isOpen}
|
|
33
|
-
onClose={() => setIsOpen(false)}
|
|
34
|
-
onSelect={handleSelect}
|
|
35
|
-
apiService={apiService}
|
|
36
|
-
overlayClassName="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm"
|
|
37
|
-
containerClassName="max-w-4xl bg-gray-900 text-white rounded-xl shadow-2xl"
|
|
38
|
-
titleClassName="text-3xl font-extrabold text-blue-400"
|
|
39
|
-
closeButtonClassName="text-white hover:text-red-400 hover:bg-red-900/20"
|
|
40
|
-
/>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## MediaTab Styling Props
|
|
44
|
-
|
|
45
|
-
Customize the tab navigation:
|
|
46
|
-
|
|
47
|
-
```jsx
|
|
48
|
-
<MediaTab
|
|
49
|
-
// ... other props
|
|
50
|
-
className="..." // Tab group container
|
|
51
|
-
tabListClassName="..." // Tab list container
|
|
52
|
-
tabButtonClassName="..." // Base tab button styles
|
|
53
|
-
tabButtonActiveClassName="..." // Active tab button styles
|
|
54
|
-
tabButtonInactiveClassName="..." // Inactive tab button styles
|
|
55
|
-
tabPanelClassName="..." // Tab panel container
|
|
56
|
-
/>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Example
|
|
60
|
-
|
|
61
|
-
```jsx
|
|
62
|
-
<MediaTab
|
|
63
|
-
options={["image", "video"]}
|
|
64
|
-
apiService={apiService}
|
|
65
|
-
tabListClassName="bg-gray-800 border-b-2 border-blue-500"
|
|
66
|
-
tabButtonClassName="text-gray-300 hover:text-white px-6 py-3 transition-all"
|
|
67
|
-
tabButtonActiveClassName="text-blue-400 border-b-4 border-blue-400 font-bold"
|
|
68
|
-
tabButtonInactiveClassName="text-gray-400"
|
|
69
|
-
/>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## MediaGallery / VideosGallery Styling Props
|
|
73
|
-
|
|
74
|
-
Customize the media grid and items:
|
|
75
|
-
|
|
76
|
-
```jsx
|
|
77
|
-
<ImagesGallery
|
|
78
|
-
// ... other props
|
|
79
|
-
className="..." // Container
|
|
80
|
-
gridClassName="..." // Grid layout
|
|
81
|
-
imageItemClassName="..." // Individual image item
|
|
82
|
-
paginationContainerClassName="..." // Pagination container
|
|
83
|
-
/>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Example
|
|
87
|
-
|
|
88
|
-
```jsx
|
|
89
|
-
<ImagesGallery
|
|
90
|
-
apiService={apiService}
|
|
91
|
-
gridClassName="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4"
|
|
92
|
-
imageItemClassName="rounded-lg border-2 border-gray-300 hover:border-blue-500 transition-all cursor-pointer transform hover:scale-105"
|
|
93
|
-
paginationContainerClassName="bg-gray-100 p-6 rounded-lg mt-4"
|
|
94
|
-
/>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## FileUpload Styling Props
|
|
98
|
-
|
|
99
|
-
Customize the upload button:
|
|
100
|
-
|
|
101
|
-
```jsx
|
|
102
|
-
<FileUpload
|
|
103
|
-
// ... other props
|
|
104
|
-
className="..." // Container
|
|
105
|
-
buttonClassName="..." // Upload button
|
|
106
|
-
iconContainerClassName="..." // Icon container
|
|
107
|
-
titleClassName="..." // Title text
|
|
108
|
-
loadingClassName="..." // Loading state
|
|
109
|
-
/>
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Example
|
|
113
|
-
|
|
114
|
-
```jsx
|
|
115
|
-
<FileUpload
|
|
116
|
-
accept="image/*"
|
|
117
|
-
title="Upload Image"
|
|
118
|
-
apiService={apiService}
|
|
119
|
-
buttonClassName="border-2 border-dashed border-blue-500 bg-blue-50 hover:bg-blue-100 rounded-xl p-8"
|
|
120
|
-
iconContainerClassName="bg-blue-500 p-4 rounded-full"
|
|
121
|
-
titleClassName="text-blue-600 font-semibold mt-4"
|
|
122
|
-
loadingClassName="bg-blue-100 border-2 border-blue-300 rounded-xl"
|
|
123
|
-
/>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## Pagination Styling Props
|
|
127
|
-
|
|
128
|
-
Customize pagination controls:
|
|
129
|
-
|
|
130
|
-
```jsx
|
|
131
|
-
<Pagination
|
|
132
|
-
// ... other props
|
|
133
|
-
className="..." // Container
|
|
134
|
-
navClassName="..." // Nav element
|
|
135
|
-
previousButtonClassName="..." // Previous button
|
|
136
|
-
nextButtonClassName="..." // Next button
|
|
137
|
-
pageButtonClassName="..." // Page number buttons
|
|
138
|
-
pageButtonActiveClassName="..." // Active page button
|
|
139
|
-
ellipsisClassName="..." // Ellipsis (...)
|
|
140
|
-
/>
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Example
|
|
144
|
-
|
|
145
|
-
```jsx
|
|
146
|
-
<Pagination
|
|
147
|
-
nextPage={handlePageChange}
|
|
148
|
-
current_page={currentPage}
|
|
149
|
-
pageCount={totalPages}
|
|
150
|
-
navClassName="flex items-center space-x-2"
|
|
151
|
-
previousButtonClassName="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg"
|
|
152
|
-
nextButtonClassName="px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg"
|
|
153
|
-
pageButtonClassName="px-4 py-2 bg-white border border-gray-300 hover:bg-gray-100 rounded"
|
|
154
|
-
pageButtonActiveClassName="px-4 py-2 bg-blue-500 text-white font-bold rounded"
|
|
155
|
-
/>
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Complete Customization Example
|
|
159
|
-
|
|
160
|
-
Here's a fully customized example:
|
|
161
|
-
|
|
162
|
-
```jsx
|
|
163
|
-
import { MediaPopup, MediaApiService } from "@tradly/asset";
|
|
164
|
-
|
|
165
|
-
function CustomStyledGallery() {
|
|
166
|
-
const apiService = new MediaApiService({
|
|
167
|
-
authKey: "your-key",
|
|
168
|
-
bearerToken: "your-token",
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
return (
|
|
172
|
-
<MediaPopup
|
|
173
|
-
isOpen={isOpen}
|
|
174
|
-
onClose={() => setIsOpen(false)}
|
|
175
|
-
onSelect={handleSelect}
|
|
176
|
-
apiService={apiService}
|
|
177
|
-
// Popup styles
|
|
178
|
-
overlayClassName="fixed inset-0 bg-black/60 backdrop-blur-sm"
|
|
179
|
-
containerClassName="max-w-5xl bg-gradient-to-br from-gray-900 to-gray-800 rounded-2xl shadow-2xl border border-gray-700"
|
|
180
|
-
headerClassName="flex items-center justify-between p-6 border-b border-gray-700"
|
|
181
|
-
titleClassName="text-2xl font-bold text-white"
|
|
182
|
-
closeButtonClassName="text-gray-400 hover:text-white hover:bg-gray-700 rounded-full p-2 transition-all"
|
|
183
|
-
// Tab styles (passed to MediaTab internally)
|
|
184
|
-
tabListClassName="bg-gray-800/50 border-b border-gray-700"
|
|
185
|
-
tabButtonClassName="text-gray-400 hover:text-white px-6 py-4 transition-colors"
|
|
186
|
-
tabButtonActiveClassName="text-blue-400 border-b-2 border-blue-400 font-semibold"
|
|
187
|
-
tabButtonInactiveClassName="text-gray-500"
|
|
188
|
-
// Gallery styles (passed to MediaGallery internally)
|
|
189
|
-
gridClassName="grid grid-cols-3 md:grid-cols-5 gap-4 p-6"
|
|
190
|
-
imageItemClassName="rounded-xl border-2 border-gray-700 hover:border-blue-500 cursor-pointer transition-all hover:scale-105 shadow-lg"
|
|
191
|
-
paginationContainerClassName="bg-gray-800/50 p-4 border-t border-gray-700"
|
|
192
|
-
/>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Styling Tips
|
|
198
|
-
|
|
199
|
-
1. **Use Tailwind's responsive prefixes**: `md:`, `lg:`, etc.
|
|
200
|
-
2. **Combine with your design system**: Use your existing color palette and
|
|
201
|
-
spacing
|
|
202
|
-
3. **Maintain accessibility**: Keep contrast ratios and focus states
|
|
203
|
-
4. **Test hover states**: Ensure interactive elements have clear hover feedback
|
|
204
|
-
5. **Use transitions**: Add `transition-all` or `transition-colors` for smooth
|
|
205
|
-
animations
|
|
206
|
-
|
|
207
|
-
## Default Classes Reference
|
|
208
|
-
|
|
209
|
-
If you want to see the default classes, check the component source files. All
|
|
210
|
-
default classes use Tailwind utility classes and can be completely overridden.
|
|
211
|
-
|
|
212
|
-
## Notes
|
|
213
|
-
|
|
214
|
-
- All className props are optional
|
|
215
|
-
- If not provided, sensible defaults are used
|
|
216
|
-
- You can override individual parts without affecting others
|
|
217
|
-
- Classes are merged/applied using the `||` operator, so your custom classes
|
|
218
|
-
completely replace defaults
|
|
219
|
-
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { CameraIcon } from './Icons'
|
|
3
|
-
|
|
4
|
-
const FileUpload = ({
|
|
5
|
-
loadMedia,
|
|
6
|
-
accept,
|
|
7
|
-
title,
|
|
8
|
-
apiService,
|
|
9
|
-
onUploadStart,
|
|
10
|
-
onUploadComplete,
|
|
11
|
-
onUploadError,
|
|
12
|
-
// Styling props
|
|
13
|
-
className,
|
|
14
|
-
buttonClassName,
|
|
15
|
-
iconContainerClassName,
|
|
16
|
-
titleClassName,
|
|
17
|
-
loadingClassName,
|
|
18
|
-
}) => {
|
|
19
|
-
const [files, setFiles] = useState([])
|
|
20
|
-
const [isLoading, setISLoading] = useState(false)
|
|
21
|
-
const [length, setLength] = useState(0)
|
|
22
|
-
|
|
23
|
-
// Upload files
|
|
24
|
-
const uploadFiles = async (fileList) => {
|
|
25
|
-
if (onUploadStart) {
|
|
26
|
-
onUploadStart(fileList)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
setISLoading(true)
|
|
30
|
-
setLength(fileList.length)
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
// Use the API service to upload files
|
|
34
|
-
// The apiService.uploadMedia handles:
|
|
35
|
-
// 1. Generate S3 signed URLs via tradly.app.generateS3ImageURL
|
|
36
|
-
// 2. Upload files to S3
|
|
37
|
-
// 3. Save media metadata to API
|
|
38
|
-
const uploadedUrls = await apiService.uploadMedia(fileList, apiService.authKey)
|
|
39
|
-
|
|
40
|
-
if (onUploadComplete) {
|
|
41
|
-
onUploadComplete(uploadedUrls)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
setLength(0)
|
|
45
|
-
if (loadMedia) {
|
|
46
|
-
loadMedia()
|
|
47
|
-
}
|
|
48
|
-
} catch (error) {
|
|
49
|
-
console.error('Upload error:', error)
|
|
50
|
-
if (onUploadError) {
|
|
51
|
-
onUploadError(error)
|
|
52
|
-
}
|
|
53
|
-
} finally {
|
|
54
|
-
setISLoading(false)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Default classes with customization support
|
|
59
|
-
const defaultContainerClass = 'min-w-40 h-40'
|
|
60
|
-
const defaultButtonClass =
|
|
61
|
-
'w-full h-full flex flex-col justify-center items-center text-sm border border-primary border-dashed rounded-lg hover:bg-gray-50 transition-colors'
|
|
62
|
-
const defaultIconContainerClass = 'p-[10px] bg-primary rounded-full'
|
|
63
|
-
const defaultTitleClass = 'mt-2'
|
|
64
|
-
const defaultLoadingClass =
|
|
65
|
-
'flex items-center justify-center h-40 bg-gray-200 rounded-lg shadow-md animate-pulse'
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<>
|
|
69
|
-
<div className={className || defaultContainerClass}>
|
|
70
|
-
<input
|
|
71
|
-
required
|
|
72
|
-
id={`media_select_${files?.length}`}
|
|
73
|
-
type="file"
|
|
74
|
-
className="hidden"
|
|
75
|
-
accept={accept}
|
|
76
|
-
placeholder=""
|
|
77
|
-
onChange={async (e) => {
|
|
78
|
-
e.stopPropagation()
|
|
79
|
-
const all_files = Array.from(e.target.files)
|
|
80
|
-
if (all_files?.length > 0) {
|
|
81
|
-
await uploadFiles(all_files)
|
|
82
|
-
}
|
|
83
|
-
// Reset input
|
|
84
|
-
e.target.value = ''
|
|
85
|
-
}}
|
|
86
|
-
multiple={true}
|
|
87
|
-
/>
|
|
88
|
-
|
|
89
|
-
<button
|
|
90
|
-
type="button"
|
|
91
|
-
className={buttonClassName || defaultButtonClass}
|
|
92
|
-
onClick={() => document.getElementById(`media_select_${files?.length}`).click()}
|
|
93
|
-
disabled={isLoading}
|
|
94
|
-
>
|
|
95
|
-
<span className={iconContainerClassName || defaultIconContainerClass}>
|
|
96
|
-
<CameraIcon />
|
|
97
|
-
</span>
|
|
98
|
-
<span className={titleClassName || defaultTitleClass}>{title}</span>
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
{isLoading &&
|
|
103
|
-
Array.from({ length: length }).map((_, index) => {
|
|
104
|
-
return (
|
|
105
|
-
<div key={index} className={loadingClassName || defaultLoadingClass}>
|
|
106
|
-
File Uploading... {index + 1}/{length}
|
|
107
|
-
</div>
|
|
108
|
-
)
|
|
109
|
-
})}
|
|
110
|
-
</>
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export default FileUpload
|
package/src/components/Icons.jsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
export const CloseIcon = ({ className = 'w-8 h-8' }) => (
|
|
4
|
-
<svg
|
|
5
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
-
fill="none"
|
|
7
|
-
viewBox="0 0 24 24"
|
|
8
|
-
strokeWidth="1.5"
|
|
9
|
-
stroke="currentColor"
|
|
10
|
-
className={className}
|
|
11
|
-
>
|
|
12
|
-
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
13
|
-
</svg>
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
export const CameraIcon = () => (
|
|
17
|
-
<svg width="22" height="18" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
18
|
-
<path
|
|
19
|
-
d="M7.75033 0.166672L5.76783 2.33334H2.33366C1.14199 2.33334 0.166992 3.30834 0.166992 4.50001V17.5C0.166992 18.6917 1.14199 19.6667 2.33366 19.6667H19.667C20.8587 19.6667 21.8337 18.6917 21.8337 17.5V4.50001C21.8337 3.30834 20.8587 2.33334 19.667 2.33334H16.2328L14.2503 0.166672H7.75033ZM11.0003 16.4167C8.01033 16.4167 5.58366 13.99 5.58366 11C5.58366 8.01001 8.01033 5.58334 11.0003 5.58334C13.9903 5.58334 16.417 8.01001 16.417 11C16.417 13.99 13.9903 16.4167 11.0003 16.4167Z"
|
|
20
|
-
fill="white"
|
|
21
|
-
/>
|
|
22
|
-
</svg>
|
|
23
|
-
)
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
const ImagesSkeleton = ({ per_page = 30 }) => {
|
|
4
|
-
const gridItems = []
|
|
5
|
-
|
|
6
|
-
for (let i = 0; i < per_page; i++) {
|
|
7
|
-
gridItems.push(
|
|
8
|
-
<div
|
|
9
|
-
key={i}
|
|
10
|
-
className="w-full min-h-[160px] bg-[#3B3269] bg-opacity-[20%] rounded-card animate-pulse"
|
|
11
|
-
></div>
|
|
12
|
-
)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return gridItems
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export default ImagesSkeleton
|