initial commit
This commit is contained in:
@@ -0,0 +1,550 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import { Link, Navigate, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import FaqAccordion from "@/components/frontend/FaqAccordion";
|
||||
import ReviewCard from "@/components/frontend/ReviewCard";
|
||||
|
||||
import StarIcon from "@/components/frontend/icons/StarIcon";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import DateTimePicker from "@/components/frontend/DateTimePicker";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useBookingContext } from "./bookingContext";
|
||||
import CustomSelect from "@/components/frontend/CustomSelect";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import FavoriteButton from "@/components/frontend/FavoriteButton";
|
||||
import Counter from "@/components/frontend/Counter";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import { usePropertyAddons, usePropertySpace, usePropertySpaceImages, usePublicUserData, usePropertySpaceAmenities, usePropertySpaceFaqs, usePropertySpaceReviews } from "@/hooks/api";
|
||||
import PropertyImageSlider from "@/components/frontend/PropertyImageSlider";
|
||||
import PropertySpaceMapImage from "@/components/frontend/PropertySpaceMapImage";
|
||||
import AllReviewsModal from "@/components/frontend/AllReviewsModal";
|
||||
import { AuthContext } from "@/authContext";
|
||||
import CircleCheckIcon from "@/components/frontend/icons/CircleCheckIcon";
|
||||
|
||||
let sdk = new MkdSDK();
|
||||
|
||||
const PropertyPage = () => {
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const { state: authState, dispatch: authDispatch } = useContext(AuthContext);
|
||||
const { state: spaceData } = useLocation();
|
||||
const { bookingData, dispatch } = useBookingContext();
|
||||
const [galleryOpen, setGalleryOpen] = useState(false);
|
||||
const [reviewsPopup, setReviewsPopup] = useState(false);
|
||||
const [fetching, setFetching] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const bookingDetails = bookingData?.from === "" ? bookingData : JSON.parse(localStorage.getItem("booking_details"));
|
||||
const [reviewDirection, setReviewDirection] = useState("DESC");
|
||||
const [bookedSlots, setBookedSlots] = useState([]);
|
||||
const [scheduleTemplate, setScheduleTemplate] = useState({});
|
||||
const [render, forceRender] = useState(false);
|
||||
const { handleSubmit, register, setValue } = useForm({
|
||||
defaultValues: bookingDetails,
|
||||
});
|
||||
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
|
||||
const { propertySpace, notFound } = usePropertySpace(id, render);
|
||||
const hostData = usePublicUserData(propertySpace.host_id);
|
||||
const spaceImages = usePropertySpaceImages(propertySpace.id, true, setFetching);
|
||||
const spaceAddons = usePropertyAddons(propertySpace.property_id);
|
||||
const spaceAmenities = usePropertySpaceAmenities(propertySpace.id);
|
||||
const faqs = usePropertySpaceFaqs(propertySpace.id);
|
||||
const reviews = usePropertySpaceReviews(propertySpace.id);
|
||||
const [showMap, setShowMap] = useState(false);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
if (!fetching && spaceImages.length === 0) {
|
||||
navigate("*")
|
||||
}
|
||||
|
||||
async function fetchBookedSlots(id) {
|
||||
try {
|
||||
const result = await callCustomAPI("customer/schedule", "post", { property_spaces_id: id }, "", null, "v3");
|
||||
if (Array.isArray(result.list)) {
|
||||
setBookedSlots(result.list);
|
||||
}
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchScheduleTemplate(id) {
|
||||
try {
|
||||
const result = await callCustomAPI(
|
||||
"property_spaces_schedule_template",
|
||||
"post",
|
||||
{
|
||||
page: 1,
|
||||
limit: 1,
|
||||
where: [`property_spaces_id = ${id}`],
|
||||
},
|
||||
"PAGINATE",
|
||||
);
|
||||
if (Array.isArray(result.list) && result.list.length > 0) {
|
||||
setScheduleTemplate({ custom_slots: result.list[0].custom_slots });
|
||||
|
||||
}
|
||||
if (result.list[0]?.schedule_template_id) {
|
||||
const templateResult = await callCustomAPI(
|
||||
"schedule_template",
|
||||
"post",
|
||||
{
|
||||
page: 1,
|
||||
limit: 1,
|
||||
where: [`id = ${result.list[0].schedule_template_id}`],
|
||||
},
|
||||
"PAGINATE",
|
||||
);
|
||||
if (Array.isArray(templateResult.list) && (templateResult.list[0] ?? {})) {
|
||||
setScheduleTemplate((prev) => {
|
||||
let updated = { ...prev, ...templateResult.list[0] };
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function switchToCustomer() {
|
||||
authDispatch({ type: "SWITCH_TO_CUSTOMER" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a customer`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
if (!authState.isAuthenticated) {
|
||||
navigate(`/login?redirect_uri=${pathname}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (authState.user == propertySpace.host_id) {
|
||||
globalDispatch({ type: "SHOW_ERROR", payload: { heading: "error", message: "Owners can't book their own spaces" } })
|
||||
return
|
||||
}
|
||||
|
||||
if (globalState.user.verificationStatus != 1) {
|
||||
globalDispatch({ type: "OPEN_NOT_VERIFIED_MODAL" });
|
||||
return;
|
||||
}
|
||||
dispatch({ type: "SET_BOOKING_DETAILS", payload: { ...data, ...propertySpace } });
|
||||
navigate("booking-preview");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isNaN(id)) return;
|
||||
fetchBookedSlots(id);
|
||||
fetchScheduleTemplate(id);
|
||||
|
||||
// if (spaceImages.length === 0) {
|
||||
// navigate("*")
|
||||
// }
|
||||
}, []);
|
||||
|
||||
const sortByPostDate = (a, b) => {
|
||||
if (reviewDirection == "DESC") {
|
||||
return new Date(b.post_date) - new Date(a.post_date);
|
||||
}
|
||||
return new Date(a.post_date) - new Date(b.post_date);
|
||||
};
|
||||
|
||||
if (notFound || isNaN(id)) return <Navigate to="/not-found" />;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="container mx-auto min-h-screen pt-[140px] text-sm normal-case md:text-base 2xl:px-16"
|
||||
onClick={() => {
|
||||
setShowCalendar(false);
|
||||
}}
|
||||
>
|
||||
<div className="mb-[18px] flex flex-col items-start justify-between px-[17px] md:flex-row md:items-center md:px-0">
|
||||
<div className="flex flex-col items-start gap-4 normal-case md:flex-row md:items-center">
|
||||
<h2 className="text-3xl font-semibold">{propertySpace.name ?? spaceData?.name}</h2>
|
||||
<button
|
||||
className="whitespace-nowrap text-sm underline"
|
||||
target="_blank"
|
||||
onClick={() => setShowMap(true)}
|
||||
>
|
||||
(view on map)
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-[19px] flex w-full justify-center gap-4 md:mt-0 md:w-[unset]">
|
||||
<p className="flex flex-grow items-center justify-center gap-2 rounded-sm border bg-[#F3F9F7] px-[14px] py-[10px]">
|
||||
<StarIcon />
|
||||
<strong className="font-semibold">
|
||||
{(Number(propertySpace.average_space_rating ?? spaceData?.average_space_rating) || 0).toFixed(1)}
|
||||
<span className="font-normal">({propertySpace.space_rating_count ?? spaceData?.space_rating_count})</span>
|
||||
</strong>
|
||||
</p>
|
||||
<div className="flex flex-grow items-center justify-center gap-2 rounded-sm border bg-[#F3F9F7] px-[14px] py-[10px]">
|
||||
<FavoriteButton
|
||||
space_id={propertySpace.id}
|
||||
user_property_spaces_id={propertySpace.user_property_spaces_id}
|
||||
reRender={forceRender}
|
||||
withLoader={true}
|
||||
className="-mb-1"
|
||||
buttonClassName=""
|
||||
stroke="#344054"
|
||||
favColor={"black"}
|
||||
/>
|
||||
<span>Save</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="snap-scroll relative mb-[66px] flex h-[381px] gap-[32px] px-[14px] md:px-0">
|
||||
{spaceImages[0]?.photo_url &&
|
||||
<img
|
||||
src={spaceImages[0]?.photo_url}
|
||||
className="h-full rounded-lg object-cover xl:min-w-[616px]"
|
||||
/>
|
||||
}
|
||||
{spaceImages[1]?.photo_url &&
|
||||
<img
|
||||
src={spaceImages[1]?.photo_url}
|
||||
className="h-full w-[292px] rounded-lg object-cover"
|
||||
/>
|
||||
}
|
||||
<div className={`${((!spaceImages[3]?.photo_url)) ? "flex min-w-[550px] flex-col" : "block"} "gap-4 overflow-hidden md:gap-[32px]"`}>
|
||||
{spaceImages[2]?.photo_url &&
|
||||
<img
|
||||
src={spaceImages[2]?.photo_url}
|
||||
className={`${spaceImages[3]?.photo_url && "h-1/2"} "rounded-lg object-cover md:w-full"`}
|
||||
/>
|
||||
}
|
||||
{spaceImages[3]?.photo_url &&
|
||||
<img
|
||||
src={spaceImages[3]?.photo_url}
|
||||
className="h-1/2 rounded-lg object-cover md:w-full"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
{spaceImages[4]?.photo_url &&
|
||||
<img
|
||||
src={spaceImages[4]?.photo_url}
|
||||
className="h-full w-[292px] rounded-lg object-cover"
|
||||
/>
|
||||
}
|
||||
<button
|
||||
className="sticky right-6 mb-[8px] min-w-[170px] self-end border bg-[#00000080] px-3 py-1 text-center text-sm text-white"
|
||||
onClick={() => setGalleryOpen(true)}
|
||||
>
|
||||
View all photos ({spaceImages.length})
|
||||
</button>
|
||||
</div>
|
||||
<section className="relative flex flex-col items-start xl:flex-row xl:gap-12 lg:w-[90%] w-full mx-auto">
|
||||
<div className="w-full md:px-0 xl:w-3/5 px-10">
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">Description</h3>
|
||||
<p className="">{propertySpace.description ?? spaceData?.description}</p>
|
||||
<hr className="my-[32px] md:my-[47px]" />
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">Amenities</h3>
|
||||
<ul className="addons-grid list-disk-important">
|
||||
{spaceAmenities.map((am, idx) => (
|
||||
<li
|
||||
className="flex w-fit items-center gap-2 mb-4 sm:mb-0"
|
||||
key={idx}
|
||||
>
|
||||
<CircleCheckIcon />
|
||||
{am.amenity_name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<hr className="my-[32px] md:my-[47px]" />
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">Add ons</h3>
|
||||
<ul className="addons-grid list-disk-important">
|
||||
{spaceAddons.map((addon) => (
|
||||
<li
|
||||
className="flex w-fit sm:w-full items-center gap-2 mb-4 sm:mb-0"
|
||||
key={addon.id}
|
||||
>
|
||||
<span className="w-fit">
|
||||
{" "}
|
||||
<div className="flex gap-4">
|
||||
<CircleCheckIcon /> {addon.add_on_name}
|
||||
</div>{" "}
|
||||
</span>{" "}
|
||||
<strong className="font-semibold">${addon.cost}/h</strong>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<hr className="my-[32px] md:my-[47px]" />
|
||||
<div className="mb-[28px] flex flex-wrap items-center justify-between">
|
||||
<h3 className="mb-2 text-xl font-semibold md:mb-0 md:text-2xl">About the host</h3>
|
||||
{(authState.role == "customer" && propertySpace?.id) && (
|
||||
<Link
|
||||
to={`/account/messages?other_user_id=${propertySpace.host_id}&space=${propertySpace.id}`}
|
||||
className="my-text-gradient hidden w-[178px] whitespace-nowrap rounded-md border border-[#33D4B7] px-6 py-2 text-center font-semibold md:inline"
|
||||
id="contact-host"
|
||||
>
|
||||
Contact the host
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 md:justify-start md:gap-[24px]">
|
||||
<div className="w-max-content">
|
||||
<img
|
||||
src={hostData.photo ?? "/default.png"}
|
||||
className="h-[72px] w-[72px] rounded-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3 w-[90%]">
|
||||
<p className="hidden text-xl font-bold md:block">{propertySpace.first_name}</p>
|
||||
<p className="hidden md:block">{propertySpace.about ?? spaceData?.about}</p>
|
||||
</div>
|
||||
{(authState.role == "customer" && propertySpace?.id) && (
|
||||
<Link
|
||||
to={`/account/messages?other_user_id=${propertySpace.host_id}&space=${propertySpace.id}`}
|
||||
className="my-text-gradient inline whitespace-nowrap rounded-md border border-[#33D4B7] px-4 py-1 text-center text-sm font-semibold md:hidden"
|
||||
id="contact-host"
|
||||
>
|
||||
Contact the host
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-4 block md:hidden">{propertySpace.about ?? spaceData?.about}</p>
|
||||
|
||||
<hr className="my-[32px] md:my-[47px]" />
|
||||
<div className="mb-[18px] flex items-center justify-between">
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">Reviews</h3>
|
||||
<CustomSelect
|
||||
options={[
|
||||
{ label: "By Date: Newest First", value: "DESC" },
|
||||
{ label: "By Date: Oldest First", value: "ASC" },
|
||||
]}
|
||||
onChange={setReviewDirection}
|
||||
accessor="label"
|
||||
valueAccessor="value"
|
||||
className="min-w-[200px]"
|
||||
listOptionClassName={"pl-4"}
|
||||
/>
|
||||
</div>
|
||||
<section>
|
||||
{reviews.length == 0 && <p>No reviews yet</p>}
|
||||
{reviews
|
||||
.sort(sortByPostDate)
|
||||
.slice(0, 10)
|
||||
.map((rw) => (
|
||||
<ReviewCard
|
||||
key={rw.id}
|
||||
data={rw}
|
||||
/>
|
||||
))}
|
||||
<div className="text-center">
|
||||
{reviews.length > 10 ? (
|
||||
<button
|
||||
className="font-semibold underline"
|
||||
onClick={() => setReviewsPopup(true)}
|
||||
>
|
||||
View more ({reviews.length})
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
<hr className="my-[32px] md:my-[47px]" />
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">FAQs</h3>
|
||||
{faqs.map((faq) => (
|
||||
<FaqAccordion
|
||||
key={faq.id}
|
||||
data={faq}
|
||||
/>
|
||||
))}
|
||||
<hr className="my-[32px] md:my-[47px]" />
|
||||
<h3 className="mb-4 text-2xl font-semibold">Property rules</h3>
|
||||
<p className="mb-32">{propertySpace.rule ?? spaceData?.rule}</p>
|
||||
</div>
|
||||
<div className="sticky bottom-0 hidden w-full flex-grow bg-white xl:top-16 xl:bottom-[unset] xl:block xl:w-[unset] ml-24">
|
||||
<div className="sticky-price-summary mx-auto max-w-2xl p-6 md:border-2">
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">Price and availability</h3>
|
||||
<div className="mb-[13px] flex justify-between">
|
||||
<span className="text-lg">Max capacity</span>
|
||||
<span>
|
||||
{" "}
|
||||
<strong className="font-semibold">{propertySpace.max_capacity ?? spaceData?.max_capacity}</strong> people
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-[13px] flex justify-between">
|
||||
<span className="text-lg">Pricing from</span>
|
||||
<span>
|
||||
from: <strong className="font-semibold">${propertySpace.rate ?? spaceData?.rate}</strong>/h
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form
|
||||
className="flex flex-col"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="mb-[13px] flex items-center justify-between">
|
||||
<span className="text-lg">Number of guests</span>
|
||||
<Counter
|
||||
register={register}
|
||||
name="num_guests"
|
||||
setValue={setValue}
|
||||
initialValue={bookingDetails.num_guests || 1}
|
||||
maxCount={propertySpace.max_capacity ?? spaceData?.max_capacity}
|
||||
minCount={1}
|
||||
/>
|
||||
</div>
|
||||
<hr className="mb-[24px] hidden md:block" />
|
||||
<div className="z-50 mb-3">
|
||||
<DateTimePicker
|
||||
register={register}
|
||||
setValue={setValue}
|
||||
fieldNames={["selectedDate", "from", "to"]}
|
||||
showCalendar={showCalendar}
|
||||
setShowCalendar={setShowCalendar}
|
||||
fromDefault={bookingDetails.from}
|
||||
toDefault={bookingDetails.to}
|
||||
bookedSlots={bookedSlots.map((slot) => ({ fromTime: new Date(slot.start_time), toTime: new Date(slot.end_time) }))}
|
||||
scheduleTemplate={scheduleTemplate}
|
||||
defaultDate={bookingDetails.selectedDate || undefined}
|
||||
/>
|
||||
</div>
|
||||
{authState.role != "customer" && authState.isAuthenticated ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={switchToCustomer}
|
||||
className="login-btn-gradient gap-2 rounded-sm py-3 px-2 text-center tracking-wide text-white outline-none focus:outline-none"
|
||||
>
|
||||
Join as customer to book
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
id="proceed-to-preview"
|
||||
className="login-btn-gradient gap-2 rounded-sm py-3 px-2 text-center tracking-wide text-white outline-none focus:outline-none"
|
||||
disabled={(() => {
|
||||
const el = document.getElementById("booking-time");
|
||||
return !(el && !el.innerText.includes("Select"));
|
||||
})()}
|
||||
>
|
||||
{window.innerWidth > 500 ? "Continue" : "Continue to checkout"}
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto -mt-16 block w-full max-w-xl p-6 xl:hidden">
|
||||
<h3 className="mb-[8px] text-2xl font-semibold">Price and availability</h3>
|
||||
<div className="mb-[13px] flex justify-between">
|
||||
<span className="text-lg">Max capacity</span>
|
||||
<span>
|
||||
{" "}
|
||||
<strong className="font-semibold">{propertySpace.max_capacity ?? spaceData?.max_capacity}</strong> people
|
||||
</span>
|
||||
</div>
|
||||
<div className="mb-[13px] flex justify-between">
|
||||
<span className="text-lg">Pricing from</span>
|
||||
<span>
|
||||
from: <strong className="font-semibold">${propertySpace.rate ?? spaceData?.rate}</strong>/h
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form
|
||||
className="flex flex-col"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="mb-[13px] flex items-center justify-between">
|
||||
<span className="text-lg">Number of guests</span>
|
||||
<Counter
|
||||
register={register}
|
||||
name="num_guests"
|
||||
setValue={setValue}
|
||||
initialValue={bookingDetails.num_guests || 1}
|
||||
maxCount={propertySpace.max_capacity ?? spaceData?.max_capacity}
|
||||
minCount={1}
|
||||
/>
|
||||
</div>
|
||||
<hr className="mb-[24px] hidden md:block" />
|
||||
<div className="z-50 mb-3">
|
||||
<DateTimePicker
|
||||
register={register}
|
||||
setValue={setValue}
|
||||
fieldNames={["selectedDate", "from", "to"]}
|
||||
showCalendar={showCalendar}
|
||||
setShowCalendar={setShowCalendar}
|
||||
fromDefault={bookingDetails.from}
|
||||
toDefault={bookingDetails.to}
|
||||
bookedSlots={bookedSlots.map((slot) => ({ fromTime: new Date(slot.start_time), toTime: new Date(slot.end_time) }))}
|
||||
scheduleTemplate={scheduleTemplate}
|
||||
/>
|
||||
</div>
|
||||
{authState.role != "customer" && authState.isAuthenticated ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={switchToCustomer}
|
||||
className="login-btn-gradient gap-2 rounded-sm py-3 px-2 text-center tracking-wide text-white outline-none focus:outline-none"
|
||||
>
|
||||
Join as customer to book
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="submit"
|
||||
id="proceed-to-preview"
|
||||
className="login-btn-gradient gap-2 rounded-sm py-3 px-2 text-center tracking-wide text-white outline-none focus:outline-none"
|
||||
disabled={(() => {
|
||||
const els = document.querySelectorAll("#booking-time");
|
||||
return Array.from(els).every((el) => el.innerText.includes("Select"));
|
||||
})()}
|
||||
>
|
||||
{window.innerWidth > 500 && "Continue"}
|
||||
{window.innerWidth < 500 && "Continue to checkout"}
|
||||
</button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
<PropertyImageSlider
|
||||
spaceImages={spaceImages}
|
||||
modalOpen={galleryOpen}
|
||||
closeModal={() => setGalleryOpen(false)}
|
||||
/>
|
||||
<AllReviewsModal
|
||||
modalOpen={reviewsPopup}
|
||||
closeModal={() => setReviewsPopup(false)}
|
||||
reviews={reviews}
|
||||
onDirectionChange={setReviewDirection}
|
||||
/>
|
||||
<Tooltip
|
||||
anchorId="proceed-to-preview"
|
||||
place="bottom"
|
||||
content={"Proceed to book"}
|
||||
noArrow
|
||||
/>
|
||||
<Tooltip
|
||||
anchorId="contact-host"
|
||||
place="bottom"
|
||||
content={"Chat with Host"}
|
||||
noArrow
|
||||
/>
|
||||
<PropertySpaceMapImage
|
||||
modalImage={`https://maps.googleapis.com/maps/api/staticmap?center=${propertySpace.address_line_1 ?? ""},${propertySpace.address_line_2 ?? ""},${propertySpace.city ?? ""},${propertySpace.country ?? ""
|
||||
}&zoom=15&size=600x400&maptype=roadmap&markers=color:red|${propertySpace.address_line_1 ?? ""},${propertySpace.address_line_2 ?? ""}
|
||||
&key=${import.meta.env.VITE_GOOGLE_API_KEY}`}
|
||||
modalOpen={showMap}
|
||||
closeModal={() => setShowMap(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PropertyPage;
|
||||
Reference in New Issue
Block a user