11import { Button } from "@/components/ui/Button" ;
22import { useState } from "react" ;
33import { Button as RAC_Button , FileTrigger } from "react-aria-components" ;
4- import { useUploadEventBanner } from "../hooks/useUploadEventBanner " ;
4+ import { useEventBannerActions } from "../hooks/useEventBannerActions " ;
55import { toast } from "react-toastify" ;
6+ import imageCompression , { type Options } from "browser-image-compression" ;
67
78interface Props {
89 bannerUrl : string | null ;
@@ -12,41 +13,106 @@ interface Props {
1213const EventBannerUploader = ( { bannerUrl, eventId } : Props ) => {
1314 // State to hold the selected file
1415 const [ file , setFile ] = useState < File | null | undefined > ( undefined ) ; // File and null can be uploaded, undefined means no change
15- const { mutateAsync } = useUploadEventBanner ( eventId ) ;
16+ const [ currentBanner , setCurrentBanner ] = useState < string | null > ( bannerUrl ) ;
17+ const [ uploadProgress , setUploadProgress ] = useState ( 0 ) ; // 0-100
18+ const [ isUploading , setIsUploading ] = useState ( false ) ;
1619
17- const handleFileSelection = ( e : FileList | null ) => {
18- let files = e ? Array . from ( e ) : [ ] ;
19- setFile ( files [ 0 ] || null ) ;
20+ const { upload, remove } = useEventBannerActions ( eventId ) ;
21+
22+ const handleFileSelection = async ( e : FileList | null ) => {
23+ const files = e ? Array . from ( e ) : [ ] ;
24+
25+ if ( ! files [ 0 ] ) {
26+ setFile ( null ) ;
27+ return ;
28+ }
29+
30+ const selectedFile = files [ 0 ] ;
31+
32+ const options = {
33+ maxSizeMB : 0.5 ,
34+ maxWidthOrHeight : 1920 ,
35+ useWebWorker : true ,
36+ } as Options ;
37+
38+ // Compress the image file
39+ const compressedFileBlob = await imageCompression ( selectedFile , options ) ;
40+
41+ // Create a new File object from the compressed Blob to preserve the file name and type
42+ // This is needed so the server can correctly identify the file type
43+ const compressedFile = new File ( [ compressedFileBlob ] , selectedFile . name , {
44+ type : compressedFileBlob . type ,
45+ } ) ;
46+
47+ setFile ( compressedFile ) ;
2048 } ;
2149
2250 const handleUpload = async ( ) => {
2351 if ( file ) {
24- console . log ( "Uploading file:" , file ) ;
25- await mutateAsync ( file , {
52+ setIsUploading ( true ) ;
53+ setUploadProgress ( 0 ) ;
54+
55+ // Fake progress bar until upload completes
56+ let progress = 0 ;
57+ const interval = setInterval ( ( ) => {
58+ progress += Math . random ( ) * 3 + 1 ; // Random increment between 1-4%
59+ if ( progress >= 80 ) progress = 80 ; // Cap at 80% until upload finishes
60+ setUploadProgress ( progress ) ;
61+ } , 10 ) ; // Every 10ms to fake a smooth progress
62+
63+ await upload . mutateAsync ( file , {
2664 onSuccess : ( data ) => {
27- bannerUrl = data . banner_url ;
28- setFile ( undefined ) ; // Reset file state after successful upload
65+ clearInterval ( interval ) ;
66+ setUploadProgress ( 100 ) ;
67+ setTimeout ( ( ) => {
68+ setIsUploading ( false ) ;
69+ setUploadProgress ( 0 ) ;
70+ setCurrentBanner ( data . banner_url ) ;
71+ setFile ( undefined ) ;
72+ } , 500 ) ;
2973 toast . success ( "Banner uploaded successfully!" , {
3074 position : "bottom-right" ,
3175 } ) ;
3276 } ,
3377 onError : ( ) => {
78+ clearInterval ( interval ) ;
79+ setIsUploading ( false ) ;
80+ setUploadProgress ( 0 ) ;
3481 toast . error ( "Failed to upload banner. Please try again." , {
3582 position : "bottom-right" ,
3683 } ) ;
3784 } ,
3885 } ) ;
3986 } else {
40- toast . info ( "No file selected for upload." , {
41- position : "bottom-right" ,
87+ await remove . mutateAsync ( undefined , {
88+ onSuccess : ( ) => {
89+ setCurrentBanner ( null ) ;
90+ toast . success ( "Banner removed successfully!" , {
91+ position : "bottom-right" ,
92+ } ) ;
93+ } ,
94+ onError : ( ) => {
95+ toast . error ( "Failed to remove banner. Please try again." , {
96+ position : "bottom-right" ,
97+ } ) ;
98+ } ,
4299 } ) ;
43100 }
44101 } ;
45102
46103 return (
47104 < div className = "max-w-lg flex flex-col gap-2" >
48105 < p > Banner</ p >
49- { bannerUrl || file ? (
106+ { file === null || ( file === undefined && ! currentBanner ) ? (
107+ < FileTrigger
108+ acceptedFileTypes = { [ "image/*" ] }
109+ onSelect = { handleFileSelection }
110+ >
111+ < RAC_Button className = "w-full h-58 flex items-center justify-center bg-neutral-100 dark:bg-neutral-800 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-sm cursor-pointer select-none hover:dark:bg-neutral-700 hover:bg-neutral-200 transition-colors" >
112+ < span className = "text-neutral-500" > No Banner Uploaded</ span >
113+ </ RAC_Button >
114+ </ FileTrigger >
115+ ) : (
50116 < >
51117 < FileTrigger
52118 acceptedFileTypes = { [ "image/png" , "image/jpeg" , "image/jpg" ] }
@@ -55,9 +121,9 @@ const EventBannerUploader = ({ bannerUrl, eventId }: Props) => {
55121 >
56122 < RAC_Button className = "relative w-full h-58 group p-0 overflow-hidden rounded-sm cursor-pointer" >
57123 < img
58- src = { bannerUrl || ( file && URL . createObjectURL ( file ) ) || "" }
124+ src = { file ? URL . createObjectURL ( file ) : currentBanner || "" }
59125 alt = "Event Banner"
60- className = "object-cover w-full h-full rounded-sm border border-neutral-300 dark:border-neutral-700 "
126+ className = "w-full h-58 object-cover rounded-sm "
61127 />
62128 { /* Overlay */ }
63129 < div className = "absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity flex flex-row justify-center items-center" >
@@ -74,18 +140,8 @@ const EventBannerUploader = ({ bannerUrl, eventId }: Props) => {
74140 Remove Banner
75141 </ Button >
76142 </ >
77- ) : (
78- < FileTrigger
79- acceptedFileTypes = { [ "image/*" ] }
80- onSelect = { handleFileSelection }
81- >
82- < RAC_Button
83- className = "w-full h-58 flex items-center justify-center bg-neutral-100 dark:bg-neutral-800 border border-dashed border-neutral-300 dark:border-neutral-700 rounded-sm cursor-pointer select-none hover:dark:bg-neutral-700 hover:bg-neutral-200 transition-colors"
84- >
85- < span className = "text-neutral-500" > No Banner Uploaded</ span >
86- </ RAC_Button >
87- </ FileTrigger >
88143 ) }
144+
89145 { file !== undefined && (
90146 < Button
91147 variant = "primary"
@@ -96,10 +152,21 @@ const EventBannerUploader = ({ bannerUrl, eventId }: Props) => {
96152 Save Banner Changes
97153 </ Button >
98154 ) }
155+
99156 < p className = "text-sm text-neutral-500" >
100157 Recommended dimensions: 1200x300px. Max file size: 5MB. Supported
101158 formats: JPG, JPEG, PNG.
102159 </ p >
160+
161+ { /* Progress bar for uploads */ }
162+ { isUploading && (
163+ < div className = "absolute bottom-0 left-0 w-full h-1 bg-neutral-200 dark:bg-neutral-600 rounded-t overflow-hidden" >
164+ < div
165+ className = "h-1 bg-cyan-700 transition-[width] duration-300 ease-out"
166+ style = { { width : `${ uploadProgress } %` } }
167+ />
168+ </ div >
169+ ) }
103170 </ div >
104171 ) ;
105172} ;
0 commit comments