@tradly/asset 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/FileUpload.js +155 -0
- package/dist/components/Icons.js +32 -0
- package/dist/components/ImagesSkeleton.js +14 -0
- package/dist/components/MediaGallery.js +144 -0
- package/dist/components/MediaPopup.js +97 -0
- package/dist/components/MediaTab.js +132 -0
- package/dist/components/Pagination.js +134 -0
- package/dist/components/VideosGallery.js +144 -0
- package/dist/services/apiService.js +373 -0
- package/package.json +17 -5
- 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/{src → dist}/index.js +0 -0
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
const Pagination = ({
|
|
4
|
-
className,
|
|
5
|
-
nextPage,
|
|
6
|
-
current_page,
|
|
7
|
-
pageCount,
|
|
8
|
-
// Styling props
|
|
9
|
-
navClassName,
|
|
10
|
-
previousButtonClassName,
|
|
11
|
-
nextButtonClassName,
|
|
12
|
-
pageButtonClassName,
|
|
13
|
-
pageButtonActiveClassName,
|
|
14
|
-
ellipsisClassName,
|
|
15
|
-
}) => {
|
|
16
|
-
const totalPages = Math.ceil(pageCount) || 1
|
|
17
|
-
const currentPage = current_page || 1
|
|
18
|
-
|
|
19
|
-
// Generate page numbers to display
|
|
20
|
-
const getPageNumbers = () => {
|
|
21
|
-
const pages = []
|
|
22
|
-
const maxVisible = 5
|
|
23
|
-
let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2))
|
|
24
|
-
let endPage = Math.min(totalPages, startPage + maxVisible - 1)
|
|
25
|
-
|
|
26
|
-
if (endPage - startPage < maxVisible - 1) {
|
|
27
|
-
startPage = Math.max(1, endPage - maxVisible + 1)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Add first page and ellipsis
|
|
31
|
-
if (startPage > 1) {
|
|
32
|
-
pages.push(1)
|
|
33
|
-
if (startPage > 2) {
|
|
34
|
-
pages.push('...')
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Add visible pages
|
|
39
|
-
for (let i = startPage; i <= endPage; i++) {
|
|
40
|
-
pages.push(i)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Add ellipsis and last page
|
|
44
|
-
if (endPage < totalPages) {
|
|
45
|
-
if (endPage < totalPages - 1) {
|
|
46
|
-
pages.push('...')
|
|
47
|
-
}
|
|
48
|
-
pages.push(totalPages)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return pages
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const handlePageClick = (page) => {
|
|
55
|
-
if (page !== currentPage && page >= 1 && page <= totalPages) {
|
|
56
|
-
nextPage(page)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const handlePrevious = () => {
|
|
61
|
-
if (currentPage > 1) {
|
|
62
|
-
nextPage(currentPage - 1)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const handleNext = () => {
|
|
67
|
-
if (currentPage < totalPages) {
|
|
68
|
-
nextPage(currentPage + 1)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (totalPages <= 1) return null
|
|
73
|
-
|
|
74
|
-
const pageNumbers = getPageNumbers()
|
|
75
|
-
|
|
76
|
-
// Default classes with customization support
|
|
77
|
-
const defaultContainerClass = 'flex justify-center'
|
|
78
|
-
const defaultNavClass =
|
|
79
|
-
'relative z-0 inline-flex flex-wrap justify-center rounded-md shadow-sm -space-x-px'
|
|
80
|
-
const defaultPreviousButtonClass =
|
|
81
|
-
'relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50'
|
|
82
|
-
const defaultNextButtonClass =
|
|
83
|
-
'relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50'
|
|
84
|
-
const defaultPageButtonClass =
|
|
85
|
-
'relative inline-flex items-center px-4 py-2 border bg-white border-gray-300 text-gray-500 hover:bg-gray-50 text-sm font-medium'
|
|
86
|
-
const defaultPageButtonActiveClass =
|
|
87
|
-
'z-10 bg-primary border-primary text-white relative inline-flex items-center px-4 py-2 border text-md font-semibold'
|
|
88
|
-
const defaultEllipsisClass =
|
|
89
|
-
'relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700'
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<div className={className || defaultContainerClass}>
|
|
93
|
-
<nav className={navClassName || defaultNavClass}>
|
|
94
|
-
{/* Previous Button */}
|
|
95
|
-
<button
|
|
96
|
-
onClick={handlePrevious}
|
|
97
|
-
disabled={currentPage === 1}
|
|
98
|
-
className={`${previousButtonClassName || defaultPreviousButtonClass} ${
|
|
99
|
-
currentPage === 1 ? 'opacity-50 cursor-not-allowed' : ''
|
|
100
|
-
}`}
|
|
101
|
-
aria-label="Previous page"
|
|
102
|
-
>
|
|
103
|
-
<svg
|
|
104
|
-
className="h-5 w-5"
|
|
105
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
106
|
-
viewBox="0 0 20 20"
|
|
107
|
-
fill="currentColor"
|
|
108
|
-
aria-hidden="true"
|
|
109
|
-
>
|
|
110
|
-
<path
|
|
111
|
-
fillRule="evenodd"
|
|
112
|
-
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
|
|
113
|
-
clipRule="evenodd"
|
|
114
|
-
/>
|
|
115
|
-
</svg>
|
|
116
|
-
</button>
|
|
117
|
-
|
|
118
|
-
{/* Page Numbers */}
|
|
119
|
-
{pageNumbers.map((page, index) => {
|
|
120
|
-
if (page === '...') {
|
|
121
|
-
return (
|
|
122
|
-
<span key={`ellipsis-${index}`} className={ellipsisClassName || defaultEllipsisClass}>
|
|
123
|
-
...
|
|
124
|
-
</span>
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const isActive = page === currentPage
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<button
|
|
132
|
-
key={page}
|
|
133
|
-
onClick={() => handlePageClick(page)}
|
|
134
|
-
className={
|
|
135
|
-
isActive
|
|
136
|
-
? pageButtonActiveClassName || defaultPageButtonActiveClass
|
|
137
|
-
: pageButtonClassName || defaultPageButtonClass
|
|
138
|
-
}
|
|
139
|
-
aria-label={`Page ${page}`}
|
|
140
|
-
aria-current={isActive ? 'page' : undefined}
|
|
141
|
-
>
|
|
142
|
-
{page}
|
|
143
|
-
</button>
|
|
144
|
-
)
|
|
145
|
-
})}
|
|
146
|
-
|
|
147
|
-
{/* Next Button */}
|
|
148
|
-
<button
|
|
149
|
-
onClick={handleNext}
|
|
150
|
-
disabled={currentPage === totalPages}
|
|
151
|
-
className={`${nextButtonClassName || defaultNextButtonClass} ${
|
|
152
|
-
currentPage === totalPages ? 'opacity-50 cursor-not-allowed' : ''
|
|
153
|
-
}`}
|
|
154
|
-
aria-label="Next page"
|
|
155
|
-
>
|
|
156
|
-
<svg
|
|
157
|
-
className="h-5 w-5"
|
|
158
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
159
|
-
viewBox="0 0 20 20"
|
|
160
|
-
fill="currentColor"
|
|
161
|
-
aria-hidden="true"
|
|
162
|
-
>
|
|
163
|
-
<path
|
|
164
|
-
fillRule="evenodd"
|
|
165
|
-
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
166
|
-
clipRule="evenodd"
|
|
167
|
-
/>
|
|
168
|
-
</svg>
|
|
169
|
-
</button>
|
|
170
|
-
</nav>
|
|
171
|
-
</div>
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export default Pagination
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
2
|
-
import FileUpload from './FileUpload'
|
|
3
|
-
import ImagesSkeleton from './ImagesSkeleton'
|
|
4
|
-
import Pagination from './Pagination'
|
|
5
|
-
|
|
6
|
-
const VIDEO_MIME_TYPES = [
|
|
7
|
-
'video/mp4',
|
|
8
|
-
'video/quicktime',
|
|
9
|
-
'video/x-ms-wmv',
|
|
10
|
-
'video/h265',
|
|
11
|
-
'video/hevc',
|
|
12
|
-
'video/webm',
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
const VideosGallery = ({
|
|
16
|
-
update_data,
|
|
17
|
-
closePopup,
|
|
18
|
-
apiService,
|
|
19
|
-
onError,
|
|
20
|
-
// Styling props
|
|
21
|
-
className,
|
|
22
|
-
gridClassName,
|
|
23
|
-
videoItemClassName,
|
|
24
|
-
paginationContainerClassName,
|
|
25
|
-
}) => {
|
|
26
|
-
const [videos, setVideos] = useState([])
|
|
27
|
-
const [total_count, setTotalCount] = useState(0)
|
|
28
|
-
const [currentPage, setCurrentPage] = useState(1)
|
|
29
|
-
const [isLoading, setISLoading] = useState(false)
|
|
30
|
-
|
|
31
|
-
// Fetch videos
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
loadMedia()
|
|
34
|
-
}, [])
|
|
35
|
-
|
|
36
|
-
const loadMedia = async (page_number = 1) => {
|
|
37
|
-
try {
|
|
38
|
-
const response = await apiService.fetchMedia({
|
|
39
|
-
mimeTypes: VIDEO_MIME_TYPES,
|
|
40
|
-
page: page_number,
|
|
41
|
-
setISLoading,
|
|
42
|
-
isLoading,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Handle different response formats
|
|
46
|
-
// Response might be: { media: [...], count: ... } or { data: { media: [...], count: ... } } or directly [...]
|
|
47
|
-
const mediaData = response?.media || response?.data?.media || response?.data || response || []
|
|
48
|
-
const count = response?.count || response?.data?.count || response?.total || 0
|
|
49
|
-
|
|
50
|
-
setVideos(Array.isArray(mediaData) ? mediaData : [])
|
|
51
|
-
setTotalCount(count)
|
|
52
|
-
setCurrentPage(page_number)
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('Error loading videos:', error)
|
|
55
|
-
if (onError) {
|
|
56
|
-
onError(error)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const handleVideoClick = (video) => {
|
|
62
|
-
if (update_data) {
|
|
63
|
-
update_data(video.url || video)
|
|
64
|
-
}
|
|
65
|
-
if (closePopup) {
|
|
66
|
-
closePopup()
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Default classes with customization support
|
|
71
|
-
const defaultContainerClass = 'h-full flex flex-col justify-between'
|
|
72
|
-
const defaultGridClass = 'grid grid-cols-[repeat(auto-fill,minmax(calc(180px),1fr))] gap-5'
|
|
73
|
-
const defaultVideoItemClass =
|
|
74
|
-
'cursor-pointer w-full h-40 object-contain overflow-hidden bg-white rounded-md shadow-md hover:shadow-lg transition-shadow'
|
|
75
|
-
const defaultPaginationContainerClass = 'mb-4 bg-gray-100/90 p-4 sticky bottom-0'
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div className={className || defaultContainerClass}>
|
|
79
|
-
<div className={gridClassName || defaultGridClass}>
|
|
80
|
-
<FileUpload
|
|
81
|
-
loadMedia={loadMedia}
|
|
82
|
-
accept="video/*"
|
|
83
|
-
title="Add Video"
|
|
84
|
-
apiService={apiService}
|
|
85
|
-
onUploadError={onError}
|
|
86
|
-
/>
|
|
87
|
-
|
|
88
|
-
{isLoading ? (
|
|
89
|
-
<ImagesSkeleton />
|
|
90
|
-
) : (
|
|
91
|
-
videos?.map((video, index) => {
|
|
92
|
-
const videoUrl = typeof video === 'string' ? video : video.url
|
|
93
|
-
const videoKey = video.id || video.url || index
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<video
|
|
97
|
-
key={videoKey}
|
|
98
|
-
onClick={() => handleVideoClick(video)}
|
|
99
|
-
className={videoItemClassName || defaultVideoItemClass}
|
|
100
|
-
controls
|
|
101
|
-
src={videoUrl}
|
|
102
|
-
/>
|
|
103
|
-
)
|
|
104
|
-
})
|
|
105
|
-
)}
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
<div className={paginationContainerClassName || defaultPaginationContainerClass}>
|
|
109
|
-
{total_count > 0 && (
|
|
110
|
-
<Pagination
|
|
111
|
-
nextPage={(value) => loadMedia(value)}
|
|
112
|
-
pageCount={Math.ceil(total_count / 30)}
|
|
113
|
-
current_page={currentPage}
|
|
114
|
-
/>
|
|
115
|
-
)}
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export default VideosGallery
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import axios from 'axios'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get the API base URL based on environment
|
|
5
|
-
* @returns {string} API base URL
|
|
6
|
-
*/
|
|
7
|
-
const getApiBaseUrl = (environment) => {
|
|
8
|
-
// Check if environment includes 'dev' (case insensitive)
|
|
9
|
-
const isDev = environment && String(environment).toLowerCase().includes('dev')
|
|
10
|
-
return isDev ? 'https://api.dev.tradly.app' : 'https://api.tradly.app'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* API Service for Media Gallery
|
|
15
|
-
* Handles all API calls with Tradly authentication
|
|
16
|
-
* Includes full upload logic using direct API call to S3 signed URL endpoint
|
|
17
|
-
*/
|
|
18
|
-
class MediaApiService {
|
|
19
|
-
constructor(config = {}) {
|
|
20
|
-
this.authKey = config.authKey || ''
|
|
21
|
-
// Auto-detect API base URL from environment, or use provided one
|
|
22
|
-
const environment =
|
|
23
|
-
config.environment || (typeof process !== 'undefined' && process.env?.ENVIRONMENT) || ''
|
|
24
|
-
this.apiBaseUrl = config.apiBaseUrl || getApiBaseUrl(environment)
|
|
25
|
-
this.bearerToken = config.bearerToken || '' // Bearer token for Authorization header
|
|
26
|
-
this.onError = config.onError || null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Set authentication key
|
|
31
|
-
*/
|
|
32
|
-
setAuthKey(authKey) {
|
|
33
|
-
this.authKey = authKey
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Set API base URL (used for all API calls)
|
|
38
|
-
*/
|
|
39
|
-
setApiBaseUrl(apiBaseUrl) {
|
|
40
|
-
this.apiBaseUrl = apiBaseUrl
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Set Bearer token for Authorization header
|
|
45
|
-
*/
|
|
46
|
-
setBearerToken(bearerToken) {
|
|
47
|
-
this.bearerToken = bearerToken
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Make API call with authentication to Tradly API
|
|
52
|
-
*/
|
|
53
|
-
async apiCall({ method, path, data, params, setISLoading, isLoading }) {
|
|
54
|
-
if (setISLoading) setISLoading(true)
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const headers = {
|
|
58
|
-
'Content-Type': 'application/json',
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Add auth key to headers if provided (X-Auth-Key header)
|
|
62
|
-
if (this.authKey) {
|
|
63
|
-
headers['X-Auth-Key'] = this.authKey
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Add Bearer token if provided
|
|
67
|
-
if (this.bearerToken) {
|
|
68
|
-
headers['Authorization'] = `Bearer ${this.bearerToken}`
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const response = await axios({
|
|
72
|
-
method,
|
|
73
|
-
url: `${this.apiBaseUrl}${path}`,
|
|
74
|
-
headers,
|
|
75
|
-
data,
|
|
76
|
-
params,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
if (setISLoading) setISLoading(false)
|
|
80
|
-
return response.data
|
|
81
|
-
} catch (error) {
|
|
82
|
-
if (setISLoading) setISLoading(false)
|
|
83
|
-
|
|
84
|
-
if (this.onError) {
|
|
85
|
-
this.onError(error)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
throw error
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Fetch media items from Tradly API
|
|
94
|
-
* GET /v1/media?page=1&parent=0&mime_type=...
|
|
95
|
-
*/
|
|
96
|
-
async fetchMedia({ mimeTypes, page = 1, parent = 0, setISLoading, isLoading }) {
|
|
97
|
-
const params = {
|
|
98
|
-
page,
|
|
99
|
-
parent,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Add mime_type to params if provided
|
|
103
|
-
if (mimeTypes) {
|
|
104
|
-
const mimeTypeParam = Array.isArray(mimeTypes) ? mimeTypes.join(',') : mimeTypes
|
|
105
|
-
params.mime_type = mimeTypeParam
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return this.apiCall({
|
|
109
|
-
method: 'GET',
|
|
110
|
-
path: '/v1/media',
|
|
111
|
-
params,
|
|
112
|
-
setISLoading,
|
|
113
|
-
isLoading,
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get S3 signed upload URLs from API
|
|
119
|
-
*/
|
|
120
|
-
async getS3SignedUrls(files, authKey) {
|
|
121
|
-
const auth_key = authKey || this.authKey
|
|
122
|
-
|
|
123
|
-
if (!auth_key) {
|
|
124
|
-
throw new Error('Authentication key is required for upload')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (!this.apiBaseUrl) {
|
|
128
|
-
throw new Error(
|
|
129
|
-
'API base URL is required. The package automatically detects it from ENVIRONMENT, or you can set apiBaseUrl in config: new MediaApiService({ apiBaseUrl: "https://api.tradly.app" })'
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Prepare file data
|
|
134
|
-
const fileData = files.map((file) => ({
|
|
135
|
-
name: file.name.replace(/\s/g, '-'),
|
|
136
|
-
type: file.type,
|
|
137
|
-
}))
|
|
138
|
-
|
|
139
|
-
// Prepare headers
|
|
140
|
-
const headers = {
|
|
141
|
-
'Content-Type': 'application/json',
|
|
142
|
-
'X-Auth-Key': auth_key, // Capital X for Tradly API
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Add Bearer token if provided
|
|
146
|
-
if (this.bearerToken) {
|
|
147
|
-
headers['Authorization'] = `Bearer ${this.bearerToken}`
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const response = await axios({
|
|
152
|
-
method: 'POST',
|
|
153
|
-
url: `${this.apiBaseUrl}/v1/utils/S3signedUploadURL`,
|
|
154
|
-
headers,
|
|
155
|
-
data: { files: fileData },
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
if (response.data.error) {
|
|
159
|
-
throw new Error(response.data.error)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return response.data.result || response.data.data?.result || response.data
|
|
163
|
-
} catch (error) {
|
|
164
|
-
console.error('Error getting S3 signed URLs:', error)
|
|
165
|
-
if (this.onError) {
|
|
166
|
-
this.onError(error)
|
|
167
|
-
}
|
|
168
|
-
throw error
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Upload media files using direct API call
|
|
174
|
-
* This includes the full upload logic: get S3 signed URLs -> upload to S3 -> save to media API
|
|
175
|
-
*/
|
|
176
|
-
async uploadMedia(files, authKey) {
|
|
177
|
-
const auth_key = authKey || this.authKey
|
|
178
|
-
|
|
179
|
-
if (!auth_key) {
|
|
180
|
-
throw new Error('Authentication key is required for upload')
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let all_files_uri = []
|
|
184
|
-
let upload_files = []
|
|
185
|
-
let upload_full_files = []
|
|
186
|
-
|
|
187
|
-
// Process all files
|
|
188
|
-
for (let i = 0; i < files.length; i++) {
|
|
189
|
-
const element = files[i]
|
|
190
|
-
|
|
191
|
-
// Check if file already has a path (from previous upload)
|
|
192
|
-
if (element.full_file === null && element.path) {
|
|
193
|
-
all_files_uri.push(element.path)
|
|
194
|
-
} else {
|
|
195
|
-
// Prepare file data for upload
|
|
196
|
-
let file_data = {
|
|
197
|
-
name: element.name.replace(/\s/g, '-'),
|
|
198
|
-
type: element.type,
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
upload_files.push(file_data)
|
|
202
|
-
upload_full_files.push(element)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Upload files when we've processed all files
|
|
206
|
-
if (files.length === i + 1 && upload_files.length > 0) {
|
|
207
|
-
try {
|
|
208
|
-
// Step 1: Get signed URLs from API
|
|
209
|
-
const responseFiles = await this.getS3SignedUrls(upload_full_files, auth_key)
|
|
210
|
-
|
|
211
|
-
// Step 2: Upload each file to S3
|
|
212
|
-
for (let index = 0; index < responseFiles.length; index++) {
|
|
213
|
-
const path = responseFiles[index].signedUrl
|
|
214
|
-
const fileURI = responseFiles[index].fileUri
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const res = await fetch(path, {
|
|
218
|
-
method: 'PUT',
|
|
219
|
-
headers: {
|
|
220
|
-
ContentType: upload_files[index].type,
|
|
221
|
-
},
|
|
222
|
-
body: upload_full_files[index],
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
if (res.ok) {
|
|
226
|
-
all_files_uri.push(fileURI)
|
|
227
|
-
} else {
|
|
228
|
-
console.error(`Failed to upload file ${index + 1}`)
|
|
229
|
-
}
|
|
230
|
-
} catch (error) {
|
|
231
|
-
console.error(`Error uploading file ${index + 1}:`, error)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Step 3: Save media metadata to Tradly API
|
|
236
|
-
if (all_files_uri.length > 0) {
|
|
237
|
-
const mediaData = all_files_uri.map((url, index) => {
|
|
238
|
-
const originalFile = upload_full_files[index]
|
|
239
|
-
return {
|
|
240
|
-
type: 1,
|
|
241
|
-
parent: 0,
|
|
242
|
-
url: url,
|
|
243
|
-
name: originalFile.name.replace(/\s/g, '-'),
|
|
244
|
-
mime_type: originalFile.type,
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
// Save to media API - POST /v1/media with { media: [...] }
|
|
249
|
-
await this.apiCall({
|
|
250
|
-
method: 'POST',
|
|
251
|
-
path: '/v1/media',
|
|
252
|
-
data: { media: mediaData },
|
|
253
|
-
})
|
|
254
|
-
}
|
|
255
|
-
} catch (error) {
|
|
256
|
-
console.error('Upload error:', error)
|
|
257
|
-
if (this.onError) {
|
|
258
|
-
this.onError(error)
|
|
259
|
-
}
|
|
260
|
-
throw error
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Return all uploaded file URIs
|
|
266
|
-
return all_files_uri
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export default MediaApiService
|
/package/{src → dist}/index.js
RENAMED
|
File without changes
|