completed create listing functionality
This commit is contained in:
parent
ef315715e3
commit
5ec090f6ea
1 changed files with 82 additions and 26 deletions
|
@ -1,14 +1,32 @@
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import {getDownloadURL, getStorage, ref, uploadBytesResumable} from 'firebase/storage'
|
import {getDownloadURL, getStorage, ref, uploadBytesResumable} from 'firebase/storage'
|
||||||
import {app} from '../firebase'
|
import {app} from '../firebase'
|
||||||
|
import {useSelector} from 'react-redux'
|
||||||
|
import {useNavigate} from 'react-router-dom'
|
||||||
|
|
||||||
|
|
||||||
const CreatListing = () => {
|
const CreatListing = () => {
|
||||||
|
const {currentUser} = useSelector((state) => state.user)
|
||||||
|
const navigate =useNavigate();
|
||||||
const [files, setFiles] = useState([]);
|
const [files, setFiles] = useState([]);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
imageUrls: [],
|
imageUrls: [],
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
address: '',
|
||||||
|
type: 'rent',
|
||||||
|
bed: 1,
|
||||||
|
bath: 1,
|
||||||
|
currentPrice: 100,
|
||||||
|
discountPrice: 0,
|
||||||
|
offer: false,
|
||||||
|
parking: false,
|
||||||
|
furnished: false,
|
||||||
});
|
});
|
||||||
const [imageUploadError, setImageUploadError] = useState(false);
|
const [imageUploadError, setImageUploadError] = useState(false);
|
||||||
const [upload, setUpload] = useState(false);
|
const [upload, setUpload] = useState(false);
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const handleImageSubmit = (e) => {
|
const handleImageSubmit = (e) => {
|
||||||
if(files.length > 0 && files.length + formData.imageUrls.length < 11){
|
if(files.length > 0 && files.length + formData.imageUrls.length < 11){
|
||||||
setUpload(true);
|
setUpload(true);
|
||||||
|
@ -58,72 +76,109 @@ const CreatListing = () => {
|
||||||
setFormData({...formData, imageUrls: formData.imageUrls.filter((_, i) => i !== index),
|
setFormData({...formData, imageUrls: formData.imageUrls.filter((_, i) => i !== index),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const handleChange = (e) => {
|
||||||
|
if (e.target.id === 'sale' || e.target.id === 'rent') {
|
||||||
|
setFormData({...formData, type: e.target.id, });
|
||||||
|
}
|
||||||
|
if(e.target.id === 'parking' || e.target.id === 'furnished' || e.target.id === 'offer'){
|
||||||
|
setFormData({...formData, [e.target.id]: e.target.checked, });
|
||||||
|
}
|
||||||
|
if(e.target.type === 'number' || e.target.type === 'text' || e.target.type === 'textarea'){
|
||||||
|
setFormData({...formData, [e.target.id]: e.target.value, });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
if(formData.imageUrls.length < 1)
|
||||||
|
return setError('You need to upload at least one image');
|
||||||
|
if (+formData.currentPrice < +formData.discountPrice)
|
||||||
|
return setError('Discount must be lower than your current price');
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(false);
|
||||||
|
const res = await fetch('/server/listing/create', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({...formData, userRef: currentUser._id,}),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
setLoading(false);
|
||||||
|
if(data.success === false){
|
||||||
|
setError(data.message)
|
||||||
|
}
|
||||||
|
navigate(`/listing/${data._id}`)
|
||||||
|
} catch (error) {
|
||||||
|
setError(error.message);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-3 max-w-4xl mx-auto">
|
<div className="p-3 max-w-4xl mx-auto">
|
||||||
<h1 className="text-2xl uppercase text-center text-blue-900 font-serif my-10 tracking-wide">Create a listing</h1>
|
<h1 className="text-2xl uppercase text-center text-blue-900 font-serif my-10 tracking-wide">Create a listing</h1>
|
||||||
<form className="flex flex-col sm:flex-row gap-4">
|
<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-4">
|
||||||
<div className="flex flex-col flex-1 gap-4">
|
<div className="flex flex-col flex-1 gap-4">
|
||||||
<input type="text" placeholder="Name" id="name" className='border p-2 rounded-md text-sm ' maxLength='62' minLength='10' required/>
|
<input type="text" placeholder="Title" onChange={handleChange} value={formData.name} id="name" className='border p-2 rounded-md text-sm ' maxLength='62' minLength='10' required/>
|
||||||
<textarea type="text" placeholder="Description" id="description" className='border p-2 rounded-md text-sm resize-none' required/>
|
<textarea type="text" placeholder="Description" onChange={handleChange} value={formData.description} id="description" className='border p-2 rounded-md text-sm resize-none' required/>
|
||||||
<input type="text" placeholder="Address" id="address" className='border p-2 rounded-md text-sm ' required/>
|
<input type="text" placeholder="Address" onChange={handleChange} value={formData.address} id="address" className='border p-2 rounded-md text-sm ' required/>
|
||||||
<div className="flex gap-4 flex-wrap text-sm">
|
<div className="flex gap-4 flex-wrap text-sm">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="checkbox" id="sale" className="w-4"/>
|
<input type="checkbox" onChange={handleChange} checked={formData.type === "sale"} id="sale" className="w-4"/>
|
||||||
<span>Sell</span>
|
<span>Sell</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="checkbox" id="rent" className="w-4"/>
|
<input type="checkbox" onChange={handleChange} checked={formData.type === "rent"} id="rent" className="w-4"/>
|
||||||
<span>Rent</span>
|
<span>Rent</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="checkbox" id="garage" className="w-4"/>
|
<input type="checkbox" onChange={handleChange} checked={formData.parking} id="parking" className="w-4"/>
|
||||||
<span>Garage</span>
|
<span>Garage</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="checkbox" id="furnished" className="w-4"/>
|
<input type="checkbox" onChange={handleChange} checked={formData.furnished} id="furnished" className="w-4"/>
|
||||||
<span>Furnished</span>
|
<span>Furnished</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="checkbox" id="offer" className="w-4"/>
|
<input type="checkbox" onChange={handleChange} checked={formData.offer} id="offer" className="w-4"/>
|
||||||
<span>Offer</span>
|
<span>Offer</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className=" flex flex-wrap gap-6 text-sm">
|
<div className=" flex flex-wrap gap-6 text-sm">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<input type='number' id="bed" min='1' required className="p-2 border border-gray-500 rounded-lg w-20"/>
|
<input type='number' onChange={handleChange} value={formData.bed} id="bed" min='1' required className="p-2 border border-gray-500 rounded-lg w-20"/>
|
||||||
<p>Beds</p>
|
<p>Beds</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<input type='number' id="bath" min='1' required className="p-2 border border-gray-500 rounded-lg w-20"/>
|
<input type='number' onChange={handleChange} value={formData.bath} id="bath" min='1' required className="p-2 border border-gray-500 rounded-lg w-20"/>
|
||||||
<p>Baths</p>
|
<p>Baths</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<input type='number' id="regularPrice" min='1' required className="p-2 border border-gray-500 rounded-lg [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"/>
|
<input type='number' onChange={handleChange} value={formData.currentPrice} id="currentPrice" min='100' max='10000000' required className="p-2 border border-gray-500 rounded-lg [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"/>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<p>Regular price</p>
|
<p>Current price</p>
|
||||||
<span className="text-xs">($ per mounth)</span>
|
<span className="text-xs">($ per mounth)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{formData.offer && (
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<input type='number' id="discountPrice" min='1' required className="p-2 border border-gray-500 rounded-lg [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"/>
|
<input type='number' onChange={handleChange} value={formData.discountPrice} id="discountPrice" min='0' max='10000000' required className="p-2 border border-gray-500 rounded-lg [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"/>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<p>Discounted price</p>
|
<p>Discounted price</p>
|
||||||
<span className="text-xs">($ per mounth)</span>
|
<span className="text-xs">($ per mounth)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col flex-1 gap-4">
|
<div className="flex flex-col flex-1 gap-4">
|
||||||
<p className="font-semibold">Images: <span className="font-normal text-gray-500 ml-2">You can upload maximum 10 images</span></p>
|
<p className="font-semibold">Images: <span className="font-normal text-gray-500 ml-2">You can upload maximum 10 images</span></p>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<input type="file" multiple accept="image/*" onChange={(e) => setFiles(e.target.files)} id="images" className="p-2 border border-gray-300 rounded w-full"/>
|
<input type="file" multiple accept="image/*" onChange={(e) => setFiles(e.target.files)} id="images" className="p-2 border border-gray-300 rounded w-full"/>
|
||||||
<button type='button' onClick={handleImageSubmit} className="p-2 text-grenn-900 text-xs tracking-wider border border-blue-800 rounded uppercase hover:bg-blue-100 hover:border-blue-300 disabled:opacity-80">{upload ? 'Uploading...' : 'Upload'}</button>
|
<button disabled={loading} type='button' onClick={handleImageSubmit} className="p-2 text-grenn-900 text-xs tracking-wider border border-blue-800 rounded uppercase hover:bg-blue-100 hover:border-blue-300 disabled:opacity-80">{upload ? 'Uploading...' : 'Upload'}</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-red-700 text-sm self-center">{imageUploadError && imageUploadError}</p>
|
<p className="text-red-700 text-sm self-center">{imageUploadError && imageUploadError}</p>
|
||||||
{
|
{
|
||||||
|
@ -134,7 +189,8 @@ const CreatListing = () => {
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
<button className="p-2 bg-blue-950 text-white text-sm font-semibold rounded-md uppercase hover:opacity-95 disabled:bg-opacity-80">Create Listing</button>
|
<button disabled={loading || upload} className="p-2 bg-blue-950 text-white text-sm font-semibold rounded-md uppercase hover:opacity-95 disabled:bg-opacity-80">{loading ? 'Creating...' : 'Create listing'}</button>
|
||||||
|
{ error && <p className="text-red-700 text-sm">{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue