@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.
@@ -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
@@ -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
@@ -1,125 +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 IMAGE_MIME_TYPES = [
7
- 'image/png',
8
- 'image/jpeg',
9
- 'image/webp',
10
- 'image/svg+xml',
11
- 'image/gif',
12
- 'image/avif',
13
- 'image/x-icon',
14
- 'image/vnd.microsoft.icon',
15
- 'image/heic',
16
- 'image/heif',
17
- ]
18
-
19
- const ImagesGallery = ({
20
- update_data,
21
- closePopup,
22
- apiService,
23
- onError,
24
- // Styling props
25
- className,
26
- gridClassName,
27
- imageItemClassName,
28
- paginationContainerClassName,
29
- }) => {
30
- const [images, setImages] = useState([])
31
- const [total_count, setTotalCount] = useState(0)
32
- const [currentPage, setCurrentPage] = useState(1)
33
- const [isLoading, setISLoading] = useState(false)
34
-
35
- // Fetch images
36
- useEffect(() => {
37
- loadMedia()
38
- }, [])
39
-
40
- const loadMedia = async (page_number = 1) => {
41
- try {
42
- const response = await apiService.fetchMedia({
43
- mimeTypes: IMAGE_MIME_TYPES,
44
- page: page_number,
45
- setISLoading,
46
- isLoading,
47
- })
48
-
49
- // Handle different response formats
50
- // Response might be: { media: [...], count: ... } or { data: { media: [...], count: ... } } or directly [...]
51
- const mediaData = response?.media || response?.data?.media || response?.data || response || []
52
- const count = response?.count || response?.data?.count || response?.total || 0
53
-
54
- setImages(Array.isArray(mediaData) ? mediaData : [])
55
- setTotalCount(count)
56
- setCurrentPage(page_number)
57
- } catch (error) {
58
- console.error('Error loading media:', error)
59
- if (onError) {
60
- onError(error)
61
- }
62
- }
63
- }
64
-
65
- const handleImageClick = (image) => {
66
- if (update_data) {
67
- update_data(image.url || image)
68
- }
69
- if (closePopup) {
70
- closePopup()
71
- }
72
- }
73
-
74
- // Default classes with customization support
75
- const defaultContainerClass = 'h-full flex flex-col justify-between'
76
- const defaultGridClass = 'grid grid-cols-[repeat(auto-fill,minmax(calc(160px),1fr))] gap-5'
77
- const defaultImageItemClass =
78
- 'cursor-pointer w-full h-40 object-contain p-3 overflow-hidden bg-white rounded-md shadow-md hover:shadow-lg transition-shadow'
79
- const defaultPaginationContainerClass = 'mb-4 bg-gray-100/90 p-4 sticky bottom-0'
80
-
81
- return (
82
- <div className={className || defaultContainerClass}>
83
- <div className={gridClassName || defaultGridClass}>
84
- <FileUpload
85
- loadMedia={loadMedia}
86
- accept="image/*"
87
- title="Add Image"
88
- apiService={apiService}
89
- onUploadError={onError}
90
- />
91
-
92
- {isLoading ? (
93
- <ImagesSkeleton />
94
- ) : (
95
- images?.map((image, index) => {
96
- const imageUrl = typeof image === 'string' ? image : image.url
97
- const imageKey = image.id || image.url || index
98
-
99
- return (
100
- <img
101
- key={imageKey}
102
- onClick={() => handleImageClick(image)}
103
- className={imageItemClassName || defaultImageItemClass}
104
- src={imageUrl}
105
- alt={image.name || 'Media item'}
106
- />
107
- )
108
- })
109
- )}
110
- </div>
111
-
112
- <div className={paginationContainerClassName || defaultPaginationContainerClass}>
113
- {total_count > 0 && (
114
- <Pagination
115
- nextPage={(value) => loadMedia(value)}
116
- pageCount={Math.ceil(total_count / 30)}
117
- current_page={currentPage}
118
- />
119
- )}
120
- </div>
121
- </div>
122
- )
123
- }
124
-
125
- export default ImagesGallery
@@ -1,101 +0,0 @@
1
- import React from 'react'
2
- import { createPortal } from 'react-dom'
3
- import MediaTab from './MediaTab'
4
- import { CloseIcon } from './Icons'
5
-
6
- const MediaPopup = ({
7
- isOpen,
8
- onClose,
9
- onSelect,
10
- currentData,
11
- options = ['image'],
12
- apiService,
13
- onError,
14
- title = 'Media Gallery',
15
- // Styling props
16
- className,
17
- overlayClassName,
18
- containerClassName,
19
- headerClassName,
20
- titleClassName,
21
- closeButtonClassName,
22
- // Pass-through styling props for child components
23
- tabListClassName,
24
- tabButtonClassName,
25
- tabButtonActiveClassName,
26
- tabButtonInactiveClassName,
27
- tabPanelClassName,
28
- gridClassName,
29
- imageItemClassName,
30
- videoItemClassName,
31
- paginationContainerClassName,
32
- }) => {
33
- if (!isOpen) return null
34
-
35
- const handleUpdate = (data) => {
36
- if (onSelect) {
37
- onSelect(data)
38
- }
39
- }
40
-
41
- const handleClose = () => {
42
- if (onClose) {
43
- onClose()
44
- }
45
- }
46
-
47
- // Default classes with customization support
48
- const defaultOverlayClass = 'fixed inset-0 w-screen h-screen bg-black opacity-10'
49
- const defaultContainerClass =
50
- 'origin-top-right z-[9999] absolute inset-0 max-w-3xl mx-auto my-auto min-h-[200px] h-[600px] bg-white rounded shadow-inner cursor-auto p-6'
51
- const defaultHeaderClass = 'flex items-center justify-between gap-4'
52
- const defaultTitleClass = 'text-[#000] font-bold text-2xl'
53
- const defaultCloseButtonClass =
54
- 'bg-transparent rounded-full hover:bg-gray-100 p-1 transition-colors'
55
-
56
- return createPortal(
57
- <>
58
- <div
59
- className={overlayClassName || defaultOverlayClass}
60
- style={{ zIndex: 9998 }}
61
- onClick={handleClose}
62
- />
63
- <div className={containerClassName || defaultContainerClass} style={{ zIndex: 9999 }}>
64
- {/* Header */}
65
- <div className={headerClassName || defaultHeaderClass}>
66
- <p className={titleClassName || defaultTitleClass}>{title}</p>
67
- <button
68
- className={closeButtonClassName || defaultCloseButtonClass}
69
- type="button"
70
- onClick={handleClose}
71
- aria-label="Close"
72
- >
73
- <CloseIcon />
74
- </button>
75
- </div>
76
- <MediaTab
77
- imagePopup={isOpen}
78
- update_data={handleUpdate}
79
- current_data={currentData}
80
- closePopup={handleClose}
81
- options={options}
82
- apiService={apiService}
83
- onError={onError}
84
- // Pass styling props through
85
- tabListClassName={tabListClassName}
86
- tabButtonClassName={tabButtonClassName}
87
- tabButtonActiveClassName={tabButtonActiveClassName}
88
- tabButtonInactiveClassName={tabButtonInactiveClassName}
89
- tabPanelClassName={tabPanelClassName}
90
- gridClassName={gridClassName}
91
- imageItemClassName={imageItemClassName}
92
- videoItemClassName={videoItemClassName}
93
- paginationContainerClassName={paginationContainerClassName}
94
- />
95
- </div>
96
- </>,
97
- document.body
98
- )
99
- }
100
-
101
- export default MediaPopup
@@ -1,152 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { Tab } from '@headlessui/react'
3
- import ImagesGallery from './MediaGallery'
4
- import VideosGallery from './VideosGallery'
5
-
6
- function classNames(...classes) {
7
- return classes.filter(Boolean).join(' ')
8
- }
9
-
10
- const MediaTab = ({
11
- imagePopup,
12
- current_data,
13
- update_data,
14
- closePopup,
15
- options,
16
- apiService,
17
- onError,
18
- // Styling props
19
- className,
20
- tabListClassName,
21
- tabButtonClassName,
22
- tabButtonActiveClassName,
23
- tabButtonInactiveClassName,
24
- tabPanelClassName,
25
- // Pass-through styling props for gallery components
26
- gridClassName,
27
- imageItemClassName,
28
- videoItemClassName,
29
- paginationContainerClassName,
30
- }) => {
31
- const [selectedIndex, setSelectedIndex] = useState(0)
32
-
33
- // Default classes with customization support
34
- const defaultTabGroupClass = 'h-[520px] overflow-y-auto scrollbar-none'
35
- const defaultTabListClass = 'sticky top-0 z-30 bg-white w-full flex border-b-2 border-gray-200'
36
- const defaultTabButtonBaseClass =
37
- 'rounded-lg py-2.5 px-8 text-base font-medium leading-5 flex flex-col md:flex-row items-center justify-center gap-3 ring-0 focus:outline-none'
38
- const defaultTabButtonActiveClass =
39
- 'relative text-primary after:content-[""] after:h-1 after:w-full after:block after:absolute after:-bottom-0.5 after:bg-primary after:rounded-card'
40
- const defaultTabButtonInactiveClass = 'text-[#4F4F4F]'
41
- const defaultTabPanelClass = 'h-full py-4 relative'
42
-
43
- return (
44
- <Tab.Group
45
- as="div"
46
- key={selectedIndex}
47
- className={className || defaultTabGroupClass}
48
- onChange={setSelectedIndex}
49
- selectedIndex={selectedIndex}
50
- >
51
- <Tab.List className={tabListClassName || defaultTabListClass}>
52
- {options.includes('image') && (
53
- <Tab
54
- as="p"
55
- id="image-tab-button"
56
- className={({ selected }) =>
57
- classNames(
58
- tabButtonClassName || defaultTabButtonBaseClass,
59
- selected
60
- ? tabButtonActiveClassName || defaultTabButtonActiveClass
61
- : tabButtonInactiveClassName || defaultTabButtonInactiveClass
62
- )
63
- }
64
- >
65
- <span>Images</span>
66
- </Tab>
67
- )}
68
- {options.includes('video') && (
69
- <Tab
70
- as="p"
71
- id="video-tab-button"
72
- className={({ selected }) =>
73
- classNames(
74
- tabButtonClassName || defaultTabButtonBaseClass,
75
- selected
76
- ? tabButtonActiveClassName || defaultTabButtonActiveClass
77
- : tabButtonInactiveClassName || defaultTabButtonInactiveClass
78
- )
79
- }
80
- >
81
- <span>Videos</span>
82
- </Tab>
83
- )}
84
- {options.includes('file') && (
85
- <Tab
86
- as="p"
87
- id="file-tab-button"
88
- className={({ selected }) =>
89
- classNames(
90
- tabButtonClassName || defaultTabButtonBaseClass,
91
- selected
92
- ? tabButtonActiveClassName || defaultTabButtonActiveClass
93
- : tabButtonInactiveClassName || defaultTabButtonInactiveClass
94
- )
95
- }
96
- >
97
- <span>Files</span>
98
- </Tab>
99
- )}
100
- </Tab.List>
101
- <Tab.Panels className="h-full" selectedIndex={selectedIndex}>
102
- {options.includes('image') && (
103
- <Tab.Panel className={tabPanelClassName || defaultTabPanelClass}>
104
- <ImagesGallery
105
- imagePopup={imagePopup}
106
- update_data={update_data}
107
- current_data={current_data}
108
- closePopup={closePopup}
109
- apiService={apiService}
110
- onError={onError}
111
- gridClassName={gridClassName}
112
- imageItemClassName={imageItemClassName}
113
- paginationContainerClassName={paginationContainerClassName}
114
- />
115
- </Tab.Panel>
116
- )}
117
- {options.includes('video') && (
118
- <Tab.Panel className={tabPanelClassName || defaultTabPanelClass}>
119
- <VideosGallery
120
- imagePopup={imagePopup}
121
- update_data={update_data}
122
- current_data={current_data}
123
- closePopup={closePopup}
124
- apiService={apiService}
125
- onError={onError}
126
- gridClassName={gridClassName}
127
- videoItemClassName={videoItemClassName}
128
- paginationContainerClassName={paginationContainerClassName}
129
- />
130
- </Tab.Panel>
131
- )}
132
- {options.includes('file') && (
133
- <Tab.Panel className={tabPanelClassName || defaultTabPanelClass}>
134
- <ImagesGallery
135
- imagePopup={imagePopup}
136
- update_data={update_data}
137
- current_data={current_data}
138
- closePopup={closePopup}
139
- apiService={apiService}
140
- onError={onError}
141
- gridClassName={gridClassName}
142
- imageItemClassName={imageItemClassName}
143
- paginationContainerClassName={paginationContainerClassName}
144
- />
145
- </Tab.Panel>
146
- )}
147
- </Tab.Panels>
148
- </Tab.Group>
149
- )
150
- }
151
-
152
- export default MediaTab