finished
This commit is contained in:
@@ -13,6 +13,7 @@ import Category from "./pages/Category";
|
|||||||
import CreateLising from "./pages/CreateListing";
|
import CreateLising from "./pages/CreateListing";
|
||||||
import Listing from "./pages/Listing";
|
import Listing from "./pages/Listing";
|
||||||
import Contact from "./pages/Contact";
|
import Contact from "./pages/Contact";
|
||||||
|
import EditListing from "./pages/EditListing";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -30,6 +31,7 @@ function App() {
|
|||||||
<Route path="/sign-up" element={<Signup />} />
|
<Route path="/sign-up" element={<Signup />} />
|
||||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||||
<Route path="/create-listing" element={<CreateLising />} />
|
<Route path="/create-listing" element={<CreateLising />} />
|
||||||
|
<Route path="/edit-listing/:listingId" element={<EditListing />} />
|
||||||
<Route
|
<Route
|
||||||
path="/category/:categoryName/:listingId"
|
path="/category/:categoryName/:listingId"
|
||||||
element={<Listing />}
|
element={<Listing />}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { ReactComponent as DeleteIcon } from "../assets/svg/deleteIcon.svg";
|
import { ReactComponent as DeleteIcon } from "../assets/svg/deleteIcon.svg";
|
||||||
|
import { ReactComponent as EditIcon } from "../assets/svg/editIcon.svg";
|
||||||
import bedIcon from "../assets/svg/bedIcon.svg";
|
import bedIcon from "../assets/svg/bedIcon.svg";
|
||||||
import bathtubIcon from "../assets/svg/bathtubIcon.svg";
|
import bathtubIcon from "../assets/svg/bathtubIcon.svg";
|
||||||
|
|
||||||
function ListingItem({ listing, id, onDelete }) {
|
function ListingItem({ listing, id, onDelete, onEdit }) {
|
||||||
return (
|
return (
|
||||||
<li className="categoryListing">
|
<li className="categoryListing">
|
||||||
<Link
|
<Link
|
||||||
@@ -52,6 +53,7 @@ function ListingItem({ listing, id, onDelete }) {
|
|||||||
onClick={() => onDelete(listing.id, listing.name)}
|
onClick={() => onDelete(listing.id, listing.name)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{onEdit && <EditIcon className="editIcon" onClick={() => onEdit(id)} />}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function Slider() {
|
|||||||
fetchListings();
|
fetchListings();
|
||||||
}, []);
|
}, []);
|
||||||
if (loading) return <Spinner />;
|
if (loading) return <Spinner />;
|
||||||
console.log(listings[0].data.imgUrls);
|
if (listings.length === 0) return <></>
|
||||||
return (
|
return (
|
||||||
listings && (
|
listings && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ const firebaseConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Initialize Firebase
|
// Initialize Firebase
|
||||||
const app = initializeApp(firebaseConfig);
|
initializeApp(firebaseConfig);
|
||||||
export const db = getFirestore()
|
export const db = getFirestore()
|
||||||
|
|||||||
+44
-1
@@ -17,6 +17,7 @@ import ListingItem from "../components/ListingItem";
|
|||||||
function Category() {
|
function Category() {
|
||||||
const [listings, setListings] = useState(null);
|
const [listings, setListings] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [lastFetchedlisting, setLastFetchedlisting] = useState(null);
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
||||||
@@ -30,7 +31,11 @@ function Category() {
|
|||||||
orderBy("timestamp", "desc"),
|
orderBy("timestamp", "desc"),
|
||||||
limit(10)
|
limit(10)
|
||||||
);
|
);
|
||||||
|
|
||||||
const querySnap = await getDocs(q);
|
const querySnap = await getDocs(q);
|
||||||
|
const lastVisible = querySnap.docs[querySnap.docs.length - 1];
|
||||||
|
setLastFetchedlisting(lastVisible);
|
||||||
|
|
||||||
let listings = [];
|
let listings = [];
|
||||||
querySnap.forEach((doc) => {
|
querySnap.forEach((doc) => {
|
||||||
return listings.push({
|
return listings.push({
|
||||||
@@ -42,13 +47,44 @@ function Category() {
|
|||||||
setListings(listings);
|
setListings(listings);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
toast.error("could not fetch listings");
|
toast.error("could not fetch listings");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchListings();
|
fetchListings();
|
||||||
}, [params.categoryName]);
|
}, [params.categoryName]);
|
||||||
|
const onFetchMoreListings = async () => {
|
||||||
|
try {
|
||||||
|
const listingsRef = collection(db, "listings");
|
||||||
|
|
||||||
|
const q = query(
|
||||||
|
listingsRef,
|
||||||
|
where("type", "==", params.categoryName),
|
||||||
|
orderBy("timestamp", "desc"),
|
||||||
|
startAfter(lastFetchedlisting),
|
||||||
|
limit(10)
|
||||||
|
);
|
||||||
|
|
||||||
|
const querySnap = await getDocs(q);
|
||||||
|
const lastVisible = querySnap.docs[querySnap.docs.length - 1];
|
||||||
|
setLastFetchedlisting(lastVisible);
|
||||||
|
|
||||||
|
let listings = [];
|
||||||
|
querySnap.forEach((doc) => {
|
||||||
|
return listings.push({
|
||||||
|
id: doc.id,
|
||||||
|
data: doc.data(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setListings((prevState) => [...prevState, ...listings]);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("could not fetch listings");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="category">
|
<div className="category">
|
||||||
@@ -74,6 +110,13 @@ function Category() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{lastFetchedlisting && (
|
||||||
|
<p className="loadMore" onClick={onFetchMoreListings}>
|
||||||
|
Load More
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p>No listings for {params.categoryName}</p>
|
<p>No listings for {params.categoryName}</p>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Spinner from "../components/Spinner";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
function CreateLising() {
|
function CreateLising() {
|
||||||
const [geolocationEnabled, setGeolocationEnabled] = useState(false);
|
const [geolocationEnabled, _] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
type: "rent",
|
type: "rent",
|
||||||
@@ -119,6 +119,8 @@ function CreateLising() {
|
|||||||
case "running":
|
case "running":
|
||||||
console.log("Upload is running");
|
console.log("Upload is running");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
@@ -146,7 +148,7 @@ function CreateLising() {
|
|||||||
geolocation,
|
geolocation,
|
||||||
timestamp: serverTimestamp(),
|
timestamp: serverTimestamp(),
|
||||||
};
|
};
|
||||||
formDataCopy.location=address
|
formDataCopy.location = address;
|
||||||
delete formDataCopy.images;
|
delete formDataCopy.images;
|
||||||
delete formDataCopy.address;
|
delete formDataCopy.address;
|
||||||
location && (formDataCopy.location = location);
|
location && (formDataCopy.location = location);
|
||||||
|
|||||||
@@ -0,0 +1,447 @@
|
|||||||
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
import { getAuth, onAuthStateChanged } from "firebase/auth";
|
||||||
|
import {
|
||||||
|
getStorage,
|
||||||
|
ref,
|
||||||
|
uploadBytesResumable,
|
||||||
|
getDownloadURL,
|
||||||
|
} from "firebase/storage";
|
||||||
|
import { db } from "../firebase.config";
|
||||||
|
import { doc, updateDoc, getDoc, serverTimestamp } from "firebase/firestore";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import Spinner from "../components/Spinner";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
function EditListing() {
|
||||||
|
const [geolocationEnabled, setGeolocationEnabled] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [listing, setListing] = useState(false);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
type: "rent",
|
||||||
|
name: "",
|
||||||
|
bedrooms: 1,
|
||||||
|
bathrooms: 1,
|
||||||
|
parking: false,
|
||||||
|
furnished: false,
|
||||||
|
address: "",
|
||||||
|
offer: false,
|
||||||
|
regularPrice: 0,
|
||||||
|
discountedPrice: 0,
|
||||||
|
images: {},
|
||||||
|
latittude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
bedrooms,
|
||||||
|
bathrooms,
|
||||||
|
parking,
|
||||||
|
furnished,
|
||||||
|
address,
|
||||||
|
offer,
|
||||||
|
regularPrice,
|
||||||
|
discountedPrice,
|
||||||
|
images,
|
||||||
|
latittude,
|
||||||
|
longitude,
|
||||||
|
} = formData;
|
||||||
|
|
||||||
|
const auth = getAuth();
|
||||||
|
const params = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const isMounted = useRef(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (listing && listing.userRef !== auth.currentUser.uid) {
|
||||||
|
toast.error("You cannot edit that listing");
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMounted) {
|
||||||
|
onAuthStateChanged(auth, (user) => {
|
||||||
|
if (user) {
|
||||||
|
setFormData({ ...formData, userRef: user.uid });
|
||||||
|
} else {
|
||||||
|
navigate("/sign-in");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
isMounted.current = false;
|
||||||
|
};
|
||||||
|
}, [isMounted]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
const fetchListing = async () => {
|
||||||
|
const docRef = doc(db, "listings", params.listingId);
|
||||||
|
const docSnap = await getDoc(docRef);
|
||||||
|
if (docSnap.exists()) {
|
||||||
|
setListing(docSnap.data());
|
||||||
|
setFormData({ ...docSnap.data(), address: docSnap.data().location });
|
||||||
|
setListing(false);
|
||||||
|
} else {
|
||||||
|
navigate("/");
|
||||||
|
toast.error("Listing does not exist");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchListing();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
if (discountedPrice >= regularPrice) {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error("Discounted price needs to be less than regular price");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (images.length > 6) {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error("Max 6 images");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let geolocation = {};
|
||||||
|
let location;
|
||||||
|
|
||||||
|
if (geolocationEnabled) {
|
||||||
|
//Don't want to register for a geolocation
|
||||||
|
/* const response = await fetch(
|
||||||
|
`http://api.positionstack.com/v1/forward?access_key=${APIKEY}&query=${address}`
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
setFormData((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
latitude: data.data[0].latitude,
|
||||||
|
longitude: data.data[0].longitude,
|
||||||
|
}));
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
geolocation.lat = latittude;
|
||||||
|
geolocation.lng = longitude;
|
||||||
|
location = address;
|
||||||
|
}
|
||||||
|
const storeImage = async (image) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const storage = getStorage();
|
||||||
|
const fileName = `${auth.currentUser.uid}-${image.name}-${uuidv4()}`;
|
||||||
|
const storageRef = ref(storage, "images/" + fileName);
|
||||||
|
const uploadTask = uploadBytesResumable(storageRef, image);
|
||||||
|
uploadTask.on(
|
||||||
|
"state_changed",
|
||||||
|
(snapshot) => {
|
||||||
|
const progress =
|
||||||
|
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
|
||||||
|
console.log("Upload is " + progress + "% done");
|
||||||
|
switch (snapshot.state) {
|
||||||
|
case "paused":
|
||||||
|
console.log("Upload is paused");
|
||||||
|
break;
|
||||||
|
case "running":
|
||||||
|
console.log("Upload is running");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
|
||||||
|
resolve(downloadURL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const imgUrls = await Promise.all(
|
||||||
|
[...images].map((image) => storeImage(image))
|
||||||
|
).catch(() => {
|
||||||
|
setLoading(false);
|
||||||
|
toast.error("Image is not uploaded");
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formDataCopy = {
|
||||||
|
...formData,
|
||||||
|
imgUrls,
|
||||||
|
geolocation,
|
||||||
|
timestamp: serverTimestamp(),
|
||||||
|
};
|
||||||
|
formDataCopy.location = address;
|
||||||
|
delete formDataCopy.images;
|
||||||
|
delete formDataCopy.address;
|
||||||
|
location && (formDataCopy.location = location);
|
||||||
|
!formDataCopy.offer && delete formDataCopy.discountedPrice;
|
||||||
|
|
||||||
|
const docRef = doc(db, "listings", params.listingId);
|
||||||
|
await updateDoc(docRef, formDataCopy);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
toast.success("Listing saved");
|
||||||
|
navigate(`/category/${formDataCopy.type}/${docRef.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMutate = (e) => {
|
||||||
|
let boolean = null;
|
||||||
|
if (e.target.value === "false") {
|
||||||
|
boolean = false;
|
||||||
|
}
|
||||||
|
if (e.target.value === "true") {
|
||||||
|
boolean = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.target.files) {
|
||||||
|
setFormData((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
images: e.target.files,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (!e.target.files) {
|
||||||
|
setFormData((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
[e.target.id]: boolean ?? e.target.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="profile">
|
||||||
|
<header>
|
||||||
|
<p className="pageHeader">Edit a listing</p>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<label className="formLabel">Sell / Rent</label>
|
||||||
|
<div className="formButtons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={type === "sale" ? "formButtonActive" : "formButton"}
|
||||||
|
id="type"
|
||||||
|
value="sale"
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
Sell
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={type === "rent" ? "formButtonActive" : "formButton"}
|
||||||
|
id="type"
|
||||||
|
value="rent"
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
Rent
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<label className="formLabel">Name</label>
|
||||||
|
<input
|
||||||
|
className="formInputName"
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
value={name}
|
||||||
|
onChange={onMutate}
|
||||||
|
maxLength="32"
|
||||||
|
minLength="10"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div className="formRooms fles">
|
||||||
|
<div>
|
||||||
|
<label className="formLabel">Bedrooms</label>
|
||||||
|
<input
|
||||||
|
className="formInputSmall"
|
||||||
|
type="number"
|
||||||
|
id="bedrooms"
|
||||||
|
value={bedrooms}
|
||||||
|
onChange={onMutate}
|
||||||
|
min="1"
|
||||||
|
max="50"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="formLabel">Bathrooms</label>
|
||||||
|
<input
|
||||||
|
className="formInputSmall"
|
||||||
|
type="number"
|
||||||
|
id="bathrooms"
|
||||||
|
value={bathrooms}
|
||||||
|
onChange={onMutate}
|
||||||
|
min="1"
|
||||||
|
max="50"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="formLabel">Parking spot</label>
|
||||||
|
<div className="formButtons">
|
||||||
|
<button
|
||||||
|
className={parking ? "formButtonActive" : "formButton"}
|
||||||
|
type="button"
|
||||||
|
id="parking"
|
||||||
|
value={true}
|
||||||
|
onClick={onMutate}
|
||||||
|
min="1"
|
||||||
|
max="50"
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
!parking && parking !== null ? "formButtonActive" : "formButton"
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
id="parking"
|
||||||
|
value={false}
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="formLabel">Furnished</label>
|
||||||
|
<div className="formButtons">
|
||||||
|
<button
|
||||||
|
className={furnished ? "formButtonActive" : "formButton"}
|
||||||
|
type="button"
|
||||||
|
id="furnished"
|
||||||
|
value={true}
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
!furnished && furnished !== null
|
||||||
|
? "formButtonActive"
|
||||||
|
: "formButton"
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
id="furnished"
|
||||||
|
value={false}
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<label className="formLabel">Address</label>
|
||||||
|
<textarea
|
||||||
|
className="formInputAddress"
|
||||||
|
type="text"
|
||||||
|
id="address"
|
||||||
|
value={address}
|
||||||
|
onChange={onMutate}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{!geolocationEnabled && (
|
||||||
|
<div className="formLatLng fles">
|
||||||
|
<div>
|
||||||
|
<label className="formLabel">Latitude</label>
|
||||||
|
<input
|
||||||
|
className="formInputSmall"
|
||||||
|
type="number"
|
||||||
|
id="latittude"
|
||||||
|
value={latittude}
|
||||||
|
onChange={onMutate}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="formLabel">Longitude</label>
|
||||||
|
<input
|
||||||
|
className="formInputSmall"
|
||||||
|
type="number"
|
||||||
|
id="longitude"
|
||||||
|
value={longitude}
|
||||||
|
onChange={onMutate}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<label className="formLabel">Offer</label>
|
||||||
|
<div className="formButtons">
|
||||||
|
<button
|
||||||
|
className={offer ? "formButtonActive" : "formButton"}
|
||||||
|
type="button"
|
||||||
|
id="offer"
|
||||||
|
value={true}
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
!offer && offer !== null ? "formButtonActive" : "formButton"
|
||||||
|
}
|
||||||
|
type="button"
|
||||||
|
id="offer"
|
||||||
|
value={false}
|
||||||
|
onClick={onMutate}
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<label className="formLabel"> Regular Price</label>
|
||||||
|
<div className="formPriceDiv">
|
||||||
|
<input
|
||||||
|
className="formInputSmall"
|
||||||
|
type="number"
|
||||||
|
id="regularPrice"
|
||||||
|
value={regularPrice}
|
||||||
|
onChange={onMutate}
|
||||||
|
min="50"
|
||||||
|
max="750000000"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{formData.type === "rent" && (
|
||||||
|
<p className="formPriceText">$ / Month</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{offer && (
|
||||||
|
<>
|
||||||
|
<label className="formLabel">Discounted Price</label>
|
||||||
|
<input
|
||||||
|
className="formInputName"
|
||||||
|
type="number"
|
||||||
|
id="discountedPrice"
|
||||||
|
value={discountedPrice}
|
||||||
|
onChange={onMutate}
|
||||||
|
min="50"
|
||||||
|
max="750000000"
|
||||||
|
required={offer}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<label className="formLabel">Images</label>
|
||||||
|
<p className="imagesInfo">
|
||||||
|
The first image will be the cover (max 6).
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
className="formInputFile"
|
||||||
|
type="file"
|
||||||
|
id="images"
|
||||||
|
onChange={onMutate}
|
||||||
|
max="6"
|
||||||
|
accept=".jpg,.png,.jpeg"
|
||||||
|
multiple
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button className="primaryButton createListingButton">
|
||||||
|
Edit Listing
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default EditListing;
|
||||||
+45
-3
@@ -1,5 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import {
|
import {
|
||||||
collection,
|
collection,
|
||||||
getDocs,
|
getDocs,
|
||||||
@@ -17,8 +16,8 @@ import ListingItem from "../components/ListingItem";
|
|||||||
function Offers() {
|
function Offers() {
|
||||||
const [listings, setListings] = useState(null);
|
const [listings, setListings] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [lastFetchedlisting, setLastFetchedlisting] = useState(null);
|
||||||
|
|
||||||
const params = useParams();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchListings = async () => {
|
const fetchListings = async () => {
|
||||||
@@ -28,9 +27,13 @@ function Offers() {
|
|||||||
listingsRef,
|
listingsRef,
|
||||||
where("offer", "==", true),
|
where("offer", "==", true),
|
||||||
orderBy("timestamp", "desc"),
|
orderBy("timestamp", "desc"),
|
||||||
limit(10)
|
limit(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
const querySnap = await getDocs(q);
|
const querySnap = await getDocs(q);
|
||||||
|
const lastVisible = querySnap.docs[querySnap.docs.length - 1];
|
||||||
|
setLastFetchedlisting(lastVisible);
|
||||||
|
|
||||||
let listings = [];
|
let listings = [];
|
||||||
querySnap.forEach((doc) => {
|
querySnap.forEach((doc) => {
|
||||||
return listings.push({
|
return listings.push({
|
||||||
@@ -50,6 +53,38 @@ function Offers() {
|
|||||||
fetchListings();
|
fetchListings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onFetchMoreListings = async () => {
|
||||||
|
try {
|
||||||
|
const listingsRef = collection(db, "listings");
|
||||||
|
|
||||||
|
const q = query(
|
||||||
|
listingsRef,
|
||||||
|
where("offer", "==", true),
|
||||||
|
orderBy("timestamp", "desc"),
|
||||||
|
startAfter(lastFetchedlisting),
|
||||||
|
limit(10)
|
||||||
|
);
|
||||||
|
|
||||||
|
const querySnap = await getDocs(q);
|
||||||
|
const lastVisible = querySnap.docs[querySnap.docs.length - 1];
|
||||||
|
setLastFetchedlisting(lastVisible);
|
||||||
|
|
||||||
|
let listings = [];
|
||||||
|
querySnap.forEach((doc) => {
|
||||||
|
return listings.push({
|
||||||
|
id: doc.id,
|
||||||
|
data: doc.data(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setListings((prevState) => [...prevState, ...listings]);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("could not fetch listings");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="category">
|
<div className="category">
|
||||||
<header>
|
<header>
|
||||||
@@ -70,6 +105,13 @@ function Offers() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{lastFetchedlisting && (
|
||||||
|
<p className="loadMore" onClick={onFetchMoreListings}>
|
||||||
|
Load More
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p>There are no current Offers</p>
|
<p>There are no current Offers</p>
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ function Profile() {
|
|||||||
toast.success("Successfully deleted listing ");
|
toast.success("Successfully deleted listing ");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEdit = (listingId)=> navigate(`/edit-listing/${listingId}`)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="profile">
|
<div className="profile">
|
||||||
<header className="profileHeader">
|
<header className="profileHeader">
|
||||||
@@ -143,6 +146,7 @@ function Profile() {
|
|||||||
listing={listing.data}
|
listing={listing.data}
|
||||||
id={listing.id}
|
id={listing.id}
|
||||||
onDelete={() => onDelete(listing.id)}
|
onDelete={() => onDelete(listing.id)}
|
||||||
|
onEdit={() => onEdit(listing.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user