1- import React , { useState } from 'react' ;
2- import { collection , addDoc } from " firebase/firestore" ;
3- import { db } from '../firebase' ;
1+ import React , { useState , useCallback } from 'react' ;
2+ import { collection , addDoc } from ' firebase/firestore' ;
3+ import { db , storage } from '../firebase' ;
44import { useNavigate } from 'react-router-dom' ;
55import { useForm } from 'react-hook-form' ;
66import { yupResolver } from '@hookform/resolvers/yup' ;
77import { WithContext as ReactTags } from 'react-tag-input' ;
8+ import { useDropzone } from 'react-dropzone' ;
89import * as yup from 'yup' ;
910import '../tags.css' ;
11+ import { ref , uploadBytesResumable , getDownloadURL } from 'firebase/storage' ;
1012
1113const KeyCodes = {
1214 comma : 188 ,
@@ -22,55 +24,104 @@ function AddProject() {
2224 const [ projectLink , setProjectLink ] = useState ( '' ) ;
2325 const [ demoLink , setDemoLink ] = useState ( '' ) ;
2426 const [ category , setCategory ] = useState ( '' ) ;
25- const userData = collection ( db , 'projects' ) ;
2627 const [ tags , setTags ] = useState ( [ ] ) ;
28+ const [ selectedImages , setSelectedImages ] = useState ( [ ] ) ;
29+ const [ droppedImages , setDroppedImages ] = useState ( [ ] ) ;
30+ const [ errorMessage , setErrorMessage ] = useState ( '' ) ;
31+ const [ uploadProgress , setUploadProgress ] = useState ( 0 ) ; // State for progress indicator
2732 const navigate = useNavigate ( ) ;
2833
29- const saveData = async ( ) => {
30- const tagsArr = tags . map ( tag => tag . text ) ;
34+ const saveData = async ( data ) => {
35+ const imageFiles = selectedImages . concat ( droppedImages ) ;
36+ const tagsArr = tags . map ( ( tag ) => tag . text ) ;
3137 const projectData = {
3238 title : title ,
3339 description : description ,
3440 userGithubLink : githubLink ,
3541 projectGithubLink : projectLink ,
3642 demoLink : demoLink ,
3743 tags : tagsArr ,
38- category : category
44+ category : category ,
3945 } ;
40- await addDoc ( userData , projectData ) ;
41- navigate ( '/viewprojects' ) ;
46+
47+ // Upload the image files to Firebase Storage
48+ const storagePromises = imageFiles . map ( ( imageFile ) => {
49+ const storageRef = ref ( storage , 'images/' + imageFile . name ) ;
50+
51+ // Create a unique upload task for each file
52+ const uploadTask = uploadBytesResumable ( storageRef , imageFile ) ;
53+
54+ // Track the upload progress
55+ uploadTask . on ( 'state_changed' , ( snapshot ) => {
56+ const progress = Math . round (
57+ ( snapshot . bytesTransferred / snapshot . totalBytes ) * 100
58+ ) ;
59+ setUploadProgress ( progress ) ;
60+ } ) ;
61+
62+ // Return a promise that resolves when the upload is complete
63+ return new Promise ( ( resolve , reject ) => {
64+ uploadTask
65+ . then ( ( snapshot ) => {
66+ // Get the download URL after the upload is complete
67+ getDownloadURL ( snapshot . ref )
68+ . then ( ( downloadUrl ) => resolve ( downloadUrl ) )
69+ . catch ( reject ) ;
70+ } )
71+ . catch ( reject ) ;
72+ } ) ;
73+ } ) ;
74+
75+ try {
76+ // Wait for all image uploads to complete
77+ const downloadUrls = await Promise . all ( storagePromises ) ;
78+
79+ // Add the image URLs to the project data
80+ projectData . imageUrls = downloadUrls ;
81+
82+ // Save the project data to Firestore
83+ const docRef = await addDoc ( collection ( db , 'projects' ) , projectData ) ;
84+ navigate ( '/viewprojects' ) ;
85+ } catch ( error ) {
86+ setErrorMessage ( error . message ) ;
87+ }
4288 } ;
4389
4490
4591 const schema = yup . object ( {
46- title : yup . string ( ) . required ( " Project Title is required" ) ,
47- description : yup . string ( ) . required ( " Project Description is required" ) ,
48- userGithubLink : yup . string ( ) . required ( " User Github Link is required" ) ,
49- projectGithubLink : yup . string ( ) . required ( " Project Github Link is required" ) ,
50- demoLink : yup . string ( ) . required ( " Demo Link is required" ) ,
92+ title : yup . string ( ) . required ( ' Project Title is required' ) ,
93+ description : yup . string ( ) . required ( ' Project Description is required' ) ,
94+ userGithubLink : yup . string ( ) . required ( ' User Github Link is required' ) ,
95+ projectGithubLink : yup . string ( ) . required ( ' Project Github Link is required' ) ,
96+ demoLink : yup . string ( ) . required ( ' Demo Link is required' ) ,
5197 } ) . required ( ) ;
5298
5399 const { register, handleSubmit, formState : { errors } } = useForm ( {
54100 resolver : yupResolver ( schema )
55101 } ) ;
56102
57103 const onSubmit = ( data ) => {
58- saveData ( ) ;
104+ saveData ( data ) ;
105+ } ;
106+
107+ const handleImageChange = ( e ) => {
108+ const files = Array . from ( e . target . files ) ;
109+ setSelectedImages ( files ) ;
59110 } ;
60111
61112 const suggestions = [
62- { id : "1" , text : " Javascript" } ,
63- { id : "2" , text : " Python" } ,
64- { id : "3" , text : " Java" } ,
65- { id : "4" , text : " HTML" } ,
66- { id : "5" , text : " PHP" } ,
67- { id : "6" , text : " TypeScript" } ,
68- { id : "7" , text : " React" } ,
69- { id : "8" , text : " Vue" } ,
70- { id : "9" , text : " Angular" } ,
71- { id : "10" , text : " Bootstrap" } ,
72- { id : "11" , text : " Tailwind" } ,
73- { id : "12" , text : " CSS" } ,
113+ { id : '1' , text : ' Javascript' } ,
114+ { id : '2' , text : ' Python' } ,
115+ { id : '3' , text : ' Java' } ,
116+ { id : '4' , text : ' HTML' } ,
117+ { id : '5' , text : ' PHP' } ,
118+ { id : '6' , text : ' TypeScript' } ,
119+ { id : '7' , text : ' React' } ,
120+ { id : '8' , text : ' Vue' } ,
121+ { id : '9' , text : ' Angular' } ,
122+ { id : '10' , text : ' Bootstrap' } ,
123+ { id : '11' , text : ' Tailwind' } ,
124+ { id : '12' , text : ' CSS' } ,
74125 ] ;
75126
76127 const handleAddition = tag => {
@@ -81,9 +132,21 @@ function AddProject() {
81132 setTags ( tags . filter ( ( tag , index ) => index !== i ) ) ;
82133 } ;
83134
135+ const { getRootProps, getInputProps, isDragActive } = useDropzone ( {
136+ accept : 'image/*' ,
137+ onDrop : ( acceptedFiles ) => {
138+ const invalidFiles = acceptedFiles . filter ( file => ! file . type . startsWith ( 'image/' ) ) ;
139+ if ( invalidFiles . length > 0 ) {
140+ setErrorMessage ( 'Invalid file types. Please drop only image files.' ) ;
141+ } else {
142+ setDroppedImages ( ( prevImages ) => [ ...prevImages , ...acceptedFiles ] ) ;
143+ }
144+ } ,
145+ } ) ;
146+
84147 return (
85148 < section className = '' >
86- < div className = 'flex md:pl-10 flex-col items-center ml-1 md:ml-60 h-[100%] pb-4 mb-8 pt-10 Context' >
149+ < div className = 'flex md:pl-10 flex-col items-center ml-1 md:ml-60 h-[100%] pb-8 mb-8 pt-10 Context' >
87150 < form onSubmit = { handleSubmit ( onSubmit ) } >
88151 < h2 className = "font-medium leading-tight text-md mt-10 mx-1 md:mt-[50px] mb-4 text-gray-700" > Add your project details by filling the form below</ h2 >
89152 < div className = 'flex flex-col md:flex-row justify-start items-start w-[100%]' >
@@ -92,6 +155,25 @@ function AddProject() {
92155 < p className = 'text-red-500' > { errors . title ?. message } </ p >
93156 < textarea placeholder = 'Project Description' { ...register ( "description" ) } className = 'placeholder:text-slate-500 block bg-white min-h-[170px] w-[90vw] md:w-[36vw] lg:w-[32vw] border border-slate-300 rounded-md my-4 py-2 pl-4 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1' onChange = { ( e ) => setDescription ( e . target . value ) } > </ textarea >
94157 < p className = 'text-red-500' > { errors . description ?. message } </ p >
158+ < section className = 'flex flex-col w-full justify-start bg-gray-200 border border-gray-400 p-3' >
159+ < div >
160+ < div { ...getRootProps ( ) } >
161+ < input { ...getInputProps ( ) } />
162+ { isDragActive ? (
163+ < p > Drop the files here ...</ p >
164+ ) : (
165+ < p > Drag and drop some files here, or click to select files</ p >
166+ ) }
167+ </ div >
168+ { errorMessage && < p className = "text-red-500" > { errorMessage } </ p > }
169+ < div className = "w-full grid grid-cols-2 gap-4 md:grid-cols-3 md:gap-2" >
170+ { droppedImages . map ( ( image , index ) => (
171+ < img key = { index } src = { URL . createObjectURL ( image ) } alt = "Selected" className = "w-32 h-32 my-4" />
172+ ) ) }
173+ </ div >
174+ </ div >
175+ </ section >
176+
95177 </ div >
96178
97179 < div className = 'md:ml-5 w-11/12 h-64 md:w-2/3 lg:w-1/2 my-5 mx-5 md:my-0' >
@@ -108,14 +190,17 @@ function AddProject() {
108190 className = 'block bg-white w-[90vw] md:w-[36vw] lg:w-[32vw] border border-slate-300 rounded-md my-4 py-2 pl-4 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1'
109191 >
110192 < option value = "" > Select Category</ option >
111- < option value = "Development" > Development</ option >
112- < option value = "Mobile App Development" > Mobile App Development</ option >
193+ < option value = "Web Development" > Web Development</ option >
194+ < option value = "Mobile Development" > Mobile Development</ option >
113195 < option value = "Data Science" > Data Science</ option >
196+ < option value = "Machine Learning" > Machine Learning</ option >
114197 < option value = "Artificial Intelligence" > Artificial Intelligence</ option >
115- < option value = "Game Development" > Game Development</ option >
198+ < option value = "Blockchain" > Blockchain</ option >
199+ < option value = "Cybersecurity" > Cybersecurity</ option >
116200 < option value = "UI/UX Design" > UI/UX Design</ option >
117- < option value = "E-commerce" > E-commerce </ option >
201+ < option value = "Other" > Other </ option >
118202 </ select >
203+ < p className = 'text-red-500' > { errors . category ?. message } </ p >
119204 < div className = 'placeholder:text-slate-500 block bg-white w-[90vw] md:w-[36vw] lg:w-[32vw] border border-slate-300 rounded-md my-4 py-2 pl-4 pr-3 shadow-sm focus:outline-none focus:border-sky-500 focus:ring-sky-500 focus:ring-1' >
120205 < ReactTags
121206 tags = { tags }
@@ -136,15 +221,31 @@ function AddProject() {
136221 } }
137222 />
138223 </ div >
224+ < div className = 'my-4' >
225+ < button type = 'submit' className = 'bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded' >
226+ Submit
227+ </ button >
228+ < button onClick = { ( ) => navigate ( '/viewprojects' ) } className = 'bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded ml-4' >
229+ Cancel
230+ </ button >
231+ </ div >
139232 </ div >
140233 </ div >
141- < button className = "px-6 py-2 mt-8 mx-1 font-medium text-white bg-blue-800 rounded-md transition-all duration-300 ease-in-out hover:-translate-y-1 hover:scale-110 hover:bg-blue-900 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50" type = 'submit' >
142- Save Project
143- </ button >
234+ { uploadProgress > 0 && (
235+ < div className = "relative pt-1" >
236+ < div className = "overflow-hidden h-2 mb-4 text-xs flex rounded bg-gray-200" >
237+ < div
238+ style = { { width : `${ uploadProgress } %` } }
239+ className = "shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-500"
240+ > </ div >
241+ </ div >
242+ < div className = "text-center" > { uploadProgress } %</ div >
243+ </ div >
244+ ) }
144245 </ form >
145246 </ div >
146247 </ section >
147- )
248+ ) ;
148249}
149250
150251export default AddProject ;
0 commit comments