initial commit
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
const AddOnCounter = ({ data, register, singleName }) => {
|
||||
const [counter, setCounter] = useState(1);
|
||||
console.log(singleName)
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex justify-between mb-[12px]">
|
||||
<form className="checkbox-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
className=""
|
||||
id={"cb" + data.id}
|
||||
value={data.cost}
|
||||
{...register(singleName ?? data.add_on_name)}
|
||||
/>
|
||||
<label className="text-black" htmlFor={"cb" + data.id}>{data.add_on_name}</label>
|
||||
</form>
|
||||
|
||||
<div className="flex gap-[32px] items-center">
|
||||
{data.showCounter && (
|
||||
<div className="border border-[#475467] rounded-xl p-2 flex gap-[10px] items-center text-white">
|
||||
<button
|
||||
className={"border rounded-full px-2 text-white" + (counter > 0 ? " border-[#475467]" : "")}
|
||||
onClick={() =>
|
||||
setCounter((prev) => {
|
||||
if (prev == 0) return prev;
|
||||
return prev - 1;
|
||||
})
|
||||
}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span>{counter}</span>
|
||||
<button
|
||||
className={"border rounded-full px-2" + " border-[#475467]"}
|
||||
onClick={() => setCounter((prev) => prev + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<p className="font-semibold text-[#344054]"> ${data.cost * counter}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddOnCounter;
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function AddonCounterV2({ data, register, name }) {
|
||||
const [counter, setCounter] = useState(1);
|
||||
return (
|
||||
<div className="flex justify-between mb-[12px]">
|
||||
<form className="checkbox-container mb-[12px]">
|
||||
<input
|
||||
type="checkbox"
|
||||
value={data.add_on_name}
|
||||
id={"cb" + data.id}
|
||||
{...register(name)}
|
||||
/>
|
||||
<label htmlFor={"cb" + data.id}>{data.add_on_name}</label>
|
||||
</form>
|
||||
<div className="flex gap-[32px] items-center">
|
||||
{data.showCounter && (
|
||||
<div className="border border-[#475467] rounded-xl p-2 flex gap-[10px] items-center">
|
||||
<button
|
||||
className={"border rounded-full px-2" + (counter > 0 ? " border-[#475467]" : "")}
|
||||
onClick={() =>
|
||||
setCounter((prev) => {
|
||||
if (prev == 0) return prev;
|
||||
return prev - 1;
|
||||
})
|
||||
}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span>{counter}</span>
|
||||
<button
|
||||
className={"border rounded-full px-2" + " border-[#475467]"}
|
||||
onClick={() => setCounter((prev) => prev + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<p className="font-semibold text-[#344054]"> ${data.cost * counter}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import CustomSelect from "./CustomSelect";
|
||||
import ReviewCard from "./ReviewCard";
|
||||
|
||||
export default function AllReviewsModal({ modalOpen, closeModal, reviews, onDirectionChange }) {
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-50"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-red-800" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-7xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<div className="flex md:flex-row flex-col gap-4 justify-between items-center">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-2xl font-bold leading-6 text-gray-900"
|
||||
>
|
||||
All Reviews ({reviews.length})
|
||||
</Dialog.Title>
|
||||
<div className="flex gap-8 items-start">
|
||||
<CustomSelect
|
||||
options={[
|
||||
{ label: "By Date: Newest First", value: "DESC" },
|
||||
{ label: "By Date: Oldest First", value: "ASC" },
|
||||
]}
|
||||
onChange={onDirectionChange}
|
||||
accessor="label"
|
||||
valueAccessor="value"
|
||||
className="min-w-[200px]"
|
||||
/>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="my-8" />
|
||||
<section className="overflow-y-auto h-[70vh] review-scroll pr-[13px]">
|
||||
{reviews.map((rw) => (
|
||||
<ReviewCard
|
||||
key={rw.id}
|
||||
data={rw}
|
||||
/>
|
||||
))}
|
||||
</section>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import React, { useState } from "react";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { parseJsonSafely } from "@/utils/utils";
|
||||
import PencilIcon from "./icons/PencilIcon";
|
||||
import TrashIcon from "./icons/TrashIcon";
|
||||
import { formatAMPM, daysMapping } from "@/utils/date-time-utils";
|
||||
import { useContext } from "react";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import ThreeDotsMenu from "./ThreeDotsMenu";
|
||||
import EditTemplateModal from "@/pages/Host/Spaces/Add/EditTemplateModal";
|
||||
|
||||
const AvailabilityTemplate = ({ data, forceRender, selectedTemplate, setSelectedTemplate }) => {
|
||||
const [editPopup, setEditPopup] = useState(false);
|
||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
|
||||
const parsedSlots = parseJsonSafely(data.slots, []);
|
||||
|
||||
async function deleteTemplate(id) {
|
||||
globalDispatch({ type: "START_LOADING" });
|
||||
try {
|
||||
await callCustomAPI("host/schedule-slot/template", "delete", { id }, "");
|
||||
if (forceRender) forceRender(new Date());
|
||||
} catch (err) {
|
||||
globalDispatch({ type: "STOP_LOADING" });
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-[44px] flex items-center justify-between rounded-lg border border-[#EAECF0] bg-[#F9FAFB] p-[12px]">
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between lg:justify-start">
|
||||
<div className="lg:min-w-[370px]">
|
||||
<h3 className="text-xl font-semibold">{data.template_name}</h3>
|
||||
<p className="capitalize">
|
||||
(
|
||||
{daysMapping
|
||||
.filter((day) => data[day] == 1)
|
||||
.map((day, i, arr) => {
|
||||
return day + (i == arr.length - 1 ? "" : ", ");
|
||||
})}
|
||||
)
|
||||
</p>
|
||||
<div className="block md:hidden">
|
||||
<br />
|
||||
<ThreeDotsMenu
|
||||
items={[
|
||||
{
|
||||
label: "Edit",
|
||||
icon: <PencilIcon />,
|
||||
onClick: () => setEditPopup(true),
|
||||
},
|
||||
{
|
||||
label: "Delete",
|
||||
icon: <TrashIcon />,
|
||||
onClick: () => deleteTemplate(data.id),
|
||||
},
|
||||
]}
|
||||
menuClassName="right-[unset] left-0 origin-top-left"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-end gap-[32px] lg:flex-nowrap lg:justify-start">
|
||||
{Array.isArray(parsedSlots) &&
|
||||
parsedSlots.slice(0, 2).map((slot, idx) => (
|
||||
<div
|
||||
className="whitespace-nowrap"
|
||||
key={idx}
|
||||
>
|
||||
<p className="text-sm">Slot {idx + 1}:</p>
|
||||
<p className="font-semibold">
|
||||
{formatAMPM(slot.start)} - {formatAMPM(slot.end)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden lg:flex">
|
||||
<button
|
||||
onClick={() => setEditPopup(true)}
|
||||
className={`inline-flex w-full items-center gap-2 px-4 py-2 text-center text-sm`}
|
||||
>
|
||||
<PencilIcon />
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteTemplate(data.id)}
|
||||
className={`inline-flex w-full items-center gap-2 px-4 py-2 text-center text-sm`}
|
||||
>
|
||||
<TrashIcon />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<EditTemplateModal
|
||||
data={data}
|
||||
selectedTemplate={selectedTemplate}
|
||||
setSelectedTemplate={setSelectedTemplate}
|
||||
forceRender={forceRender}
|
||||
onSuccess={() => {
|
||||
if (forceRender) forceRender();
|
||||
}}
|
||||
modalOpen={editPopup}
|
||||
closeModal={() => setEditPopup(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AvailabilityTemplate;
|
||||
@@ -0,0 +1,79 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import { isNotInViewport } from "@/utils/utils";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import Icon from "../Icons";
|
||||
import HeartIcon from "./icons/HeartIcon";
|
||||
import LogoutIcon from "./icons/LogoutIcon";
|
||||
import SearchIcon from "./icons/SearchIcon";
|
||||
|
||||
export default function BottomNav({ scrollDir, showAccount }) {
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { dispatch } = useContext(AuthContext);
|
||||
|
||||
const [showStaticBar, setShowStaticBar] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
setShowStaticBar(isNotInViewport("search-bar"));
|
||||
};
|
||||
window.addEventListener("scroll", onScroll);
|
||||
setShowStaticBar(isNotInViewport("search-bar"));
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [pathname]);
|
||||
|
||||
function logout() {
|
||||
dispatch({ type: "LOGOUT" });
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
const whiteList = ["/search", "/"];
|
||||
|
||||
if (!whiteList.some((path) => pathname == path)) return null;
|
||||
|
||||
return (
|
||||
<div className={`${scrollDir == "UP" && showStaticBar ? "block" : "hidden"} md:hidden bg-white py-1 fixed bottom-0 left-0 right-0 z-[200] bottom-nav border-t border-b slideUp`}>
|
||||
<div className="flex justify-center text-sm">
|
||||
<NavLink
|
||||
to="/explore"
|
||||
className="px-4 py-2 flex flex-col items-center justify-between"
|
||||
>
|
||||
<SearchIcon stroke={"black"} />
|
||||
Explore
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/favorites"
|
||||
className="px-4 py-2 flex flex-col items-center justify-between"
|
||||
>
|
||||
<HeartIcon stroke={"black"} />
|
||||
Favorites
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={showAccount ? "/account/profile" : "/login"}
|
||||
className="px-4 py-2 flex flex-col items-center justify-between"
|
||||
>
|
||||
<Icon
|
||||
type="user"
|
||||
fill=""
|
||||
variant="circle"
|
||||
className={"my-stroke-" + "white"}
|
||||
/>
|
||||
{showAccount ? "Account" : "Login"}
|
||||
</NavLink>
|
||||
<button
|
||||
className={`${showAccount ? "flex" : "hidden"} px-4 py-2 flex-col items-center justify-between`}
|
||||
onClick={logout}
|
||||
>
|
||||
<LogoutIcon />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
const Counter = ({ register, setValue, name, initialValue, maxCount, minCount }) => {
|
||||
const [counter, setCounter] = useState(Number(initialValue) || 0);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(name, counter);
|
||||
}, [counter]);
|
||||
|
||||
useEffect(() => {
|
||||
setCounter(initialValue || 0);
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<div className="p-2 flex gap-[10px] items-center font-semibold">
|
||||
<button
|
||||
type="button"
|
||||
className={"border-2 rounded-md px-3 text-2xl" + (counter > (minCount || 0) ? " border-black" : " border-[#D0D5DD]")}
|
||||
onClick={() =>
|
||||
setCounter((prev) => {
|
||||
if (prev == (minCount || 0)) return prev;
|
||||
return prev - 1;
|
||||
})
|
||||
}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span>{counter}</span>
|
||||
<button
|
||||
type="button"
|
||||
className={"border-2 text-2xl rounded-md px-3" + (counter >= maxCount ? " border-[#D0D5DD]" : " border-black")}
|
||||
onClick={() =>
|
||||
setCounter((prev) => {
|
||||
if (prev + 1 > Number(maxCount)) return prev;
|
||||
return prev + 1;
|
||||
})
|
||||
}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<input
|
||||
type="hidden"
|
||||
{...register(name)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Counter;
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useState } from "react";
|
||||
import usePlacesService from "react-google-autocomplete/lib/usePlacesAutocompleteService";
|
||||
import LocationIcon from "./icons/LocationIcon";
|
||||
|
||||
function truncateString(str, limit) {
|
||||
if (str.length > limit) return str.slice(0, limit) + "...";
|
||||
return str;
|
||||
}
|
||||
|
||||
export default function CustomLocationAutoComplete({ location, setLocation, className, truncateNum, onChange, onClear, hideIcon, detailMode, ...restProps }) {
|
||||
const [predictionsOpen, setPredictionsOpen] = useState(false);
|
||||
// const [predictions, setPredictions] = useState([]);
|
||||
|
||||
const { placesService, placePredictions, getPlacePredictions, isPlacePredictionsLoading } = usePlacesService({
|
||||
apiKey: import.meta.env.VITE_GOOGLE_API_KEY,
|
||||
});
|
||||
|
||||
// useEffect(() => {
|
||||
// fetch place details for the first element in placePredictions array
|
||||
// if (placePredictions.length)
|
||||
// placesService?.getDetails(
|
||||
// {
|
||||
// placeId: placePredictions[0].place_id,
|
||||
// },
|
||||
// (placeDetails) => setPredictions(placeDetails),
|
||||
// );
|
||||
// }, [placePredictions]);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
as={"div"}
|
||||
className={`relative ${className ?? ""}`}
|
||||
>
|
||||
{!hideIcon && <LocationIcon />}
|
||||
|
||||
<input
|
||||
{...restProps}
|
||||
className="border-0 focus:outline-none w-full truncate text-black"
|
||||
onChange={(evt) => {
|
||||
getPlacePredictions({ input: evt.target.value });
|
||||
setLocation(evt.target.value);
|
||||
if (onChange) {
|
||||
onChange(evt.target.value);
|
||||
}
|
||||
}}
|
||||
onFocus={() => setPredictionsOpen(true)}
|
||||
onBlur={() => setPredictionsOpen(false)}
|
||||
value={location}
|
||||
/>
|
||||
{location && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setLocation("");
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Transition
|
||||
show={predictionsOpen}
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={`${placePredictions.length > 0 ? "py-2 shadow-lg ring-1" : ""
|
||||
} z-50 absolute left-0 right-0 top-full mt-2 w-full origin-top divide-y divide-gray-100 rounded-xl bg-white ring-black ring-opacity-5 focus:outline-none`}
|
||||
>
|
||||
{!detailMode &&
|
||||
placePredictions.map((place, idx) => (
|
||||
<div
|
||||
className="px-1 py-1"
|
||||
key={idx}
|
||||
>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`${active ? "bg-gray-100 text-black" : "text-gray-800"} pill flex w-full items-center rounded-md px-2 py-2 text-sm truncate`}
|
||||
onClick={() => {
|
||||
setLocation(place.description);
|
||||
if (onChange) {
|
||||
onChange(place.description);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{truncateString(place.description, truncateNum ?? 30)}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
))}
|
||||
{detailMode &&
|
||||
placePredictions.map((place, idx) => (
|
||||
<div
|
||||
className="px-1 py-1"
|
||||
key={idx}
|
||||
>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`${active ? "bg-gray-100 text-black" : "text-gray-800"} pill flex w-full items-center rounded-md px-3 pr-5 py-3 text-sm truncate`}
|
||||
onClick={() => {
|
||||
setLocation(place.structured_formatting.main_text);
|
||||
if (onChange) {
|
||||
onChange(place.structured_formatting.main_text);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="font-semibold">
|
||||
{place.structured_formatting.main_text.slice(
|
||||
place.structured_formatting.main_text_matched_substrings[0].offset,
|
||||
place.structured_formatting.main_text_matched_substrings[0].length,
|
||||
)}
|
||||
</span>
|
||||
{place.structured_formatting.main_text.slice(
|
||||
place.structured_formatting.main_text_matched_substrings[0].offset + place.structured_formatting.main_text_matched_substrings[0].length,
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
))}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import NextIcon from "./icons/NextIcon";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function CustomSelect({
|
||||
options,
|
||||
accessor,
|
||||
name,
|
||||
register,
|
||||
setValue,
|
||||
formMode,
|
||||
valueAccessor,
|
||||
defaultValue,
|
||||
className,
|
||||
optionsClassName,
|
||||
defaultOptionClassName,
|
||||
onChange,
|
||||
initialEditValue,
|
||||
buttonClassName,
|
||||
listOptionClassName,
|
||||
noSelectedHighlight,
|
||||
hideIcon,
|
||||
}) {
|
||||
const [selected, setSelected] = useState(defaultValue ?? options[0]);
|
||||
|
||||
useEffect(() => {
|
||||
if (formMode) {
|
||||
if (selected == defaultValue) {
|
||||
setValue(name, "");
|
||||
} else {
|
||||
setValue(name, valueAccessor ? selected[valueAccessor] : selected);
|
||||
}
|
||||
}
|
||||
}, [selected]);
|
||||
|
||||
useEffect(() => {
|
||||
if (formMode && defaultValue) {
|
||||
setValue(name, "");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialEditValue) {
|
||||
setSelected(initialEditValue);
|
||||
}
|
||||
}, [JSON.stringify(initialEditValue)]);
|
||||
|
||||
return (
|
||||
<div className={`border p-2 rounded-md focus:outline-none active:outline-none ${className}`}>
|
||||
<Listbox
|
||||
value={selected}
|
||||
onChange={(v) => {
|
||||
setSelected(v);
|
||||
if (onChange) {
|
||||
onChange(valueAccessor ? v[valueAccessor] : v);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{formMode && (
|
||||
<input
|
||||
{...register(name)}
|
||||
type="hidden"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="relative mt-1">
|
||||
<Listbox.Button
|
||||
className={`flex items-center justify-between w-full ${(accessor && JSON.stringify(selected) == JSON.stringify(defaultValue)) || accessor == defaultValue ? defaultOptionClassName : ""}`}
|
||||
>
|
||||
<span className={`block truncate ${buttonClassName ?? ""}`}>{accessor ? selected[accessor] : selected}</span>
|
||||
<span className={`${hideIcon ? "hidden" : "inline"} pointer-events-none flex items-center pr-2`}>
|
||||
<NextIcon />
|
||||
</span>{" "}
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Listbox.Options
|
||||
className={`absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm ${optionsClassName} z-50 tiny-scroll`}
|
||||
>
|
||||
{defaultValue && (
|
||||
<Listbox.Option
|
||||
className={({ active }) => `relative cursor-default select-none py-2 pr-4 ${active ? "bg-amber-100 text-amber-900" : "text-gray-900"} ${listOptionClassName ?? "pl-10"}`}
|
||||
value={defaultValue}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span className={`block truncate ${selected ? "font-medium" : "font-normal"}`}>{accessor ? defaultValue[accessor] : defaultValue}</span>
|
||||
{selected && !noSelectedHighlight ? <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">✓</span> : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
)}
|
||||
{options.map((option, idx) => (
|
||||
<Listbox.Option
|
||||
key={idx}
|
||||
className={({ active }) => `relative cursor-default select-none py-2 ${listOptionClassName ?? "pl-10"} pr-4 ${active ? "bg-amber-100 text-amber-900" : "text-gray-900"}`}
|
||||
value={option}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span className={`block truncate ${selected ? "font-medium" : "font-normal"}`}>{accessor ? option[accessor] : option}</span>
|
||||
{selected && !noSelectedHighlight ? <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600">✓</span> : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { formatDate, isSameDay } from "@/utils/date-time-utils";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { Calendar } from "react-calendar";
|
||||
import CalendarIcon from "./icons/CalendarIcon";
|
||||
import NextIcon from "./icons/NextIcon";
|
||||
import PrevIcon from "./icons/PrevIcon";
|
||||
import { useController } from "react-hook-form";
|
||||
|
||||
const DatePicker = ({ initialDate, searchDate, control, setSearchDate, className, placeHolder, min, max, onChange, onReset, labelClassName, xClassName, panelClassName, hideIcon }) => {
|
||||
let isInitial = isSameDay(searchDate, initialDate);
|
||||
const { field, fieldState } = useController({ control, name });
|
||||
const [date, setDate] = useState(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNaN(new Date(field.value))) setDate(new Date(field.value));
|
||||
}, [field.value]);
|
||||
return (
|
||||
<div className={`w-full ${className ?? ""}`}>
|
||||
<Popover className="lg:relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button className={`${open ? "" : "text-opacity-90"} flex justify-between w-full`}>
|
||||
<div className={`flex gap-2 ${labelClassName ?? ""}`}>
|
||||
{/* {!hideIcon ? <CalendarIcon /> : null} */}
|
||||
{isInitial ? (
|
||||
<CalendarIcon />
|
||||
) : (
|
||||
<button
|
||||
className={`self-end ${xClassName ?? ""}`}
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSearchDate(initialDate);
|
||||
if (onReset) {
|
||||
onReset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
<span className={`${isInitial ? "text-gray-400" : ""}`}>{!isInitial ? formatDate(searchDate) : placeHolder ?? "Select date"}</span>
|
||||
</div>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
className="relativ"
|
||||
>
|
||||
<Popover.Panel className={`absolute left-1/2 z-10 mt-3 -translate-x-1/2 transform px-4 sm:px-0 ${panelClassName ?? ""}`}>
|
||||
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<Calendar
|
||||
onChange={(v) => {
|
||||
setSearchDate(v);
|
||||
|
||||
if (onChange) {
|
||||
onChange(v);
|
||||
}
|
||||
}}
|
||||
value={date}
|
||||
className={`calendar date-picker`}
|
||||
defaultValue={initialDate}
|
||||
nextLabel={<NextIcon />}
|
||||
prevLabel={<PrevIcon />}
|
||||
next2Label={
|
||||
<div
|
||||
className="w-full h-full cursor-default"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></div>
|
||||
}
|
||||
prev2Label={
|
||||
<div
|
||||
className="w-full h-full cursor-default"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></div>
|
||||
}
|
||||
minDate={min}
|
||||
maxDate={max}
|
||||
maxDetail="month"
|
||||
/>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatePicker;
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import moment from "moment";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Calendar } from "react-calendar";
|
||||
import { useController } from "react-hook-form";
|
||||
import CalendarIcon from "./icons/CalendarIcon";
|
||||
import NextIcon from "./icons/NextIcon";
|
||||
import PrevIcon from "./icons/PrevIcon";
|
||||
|
||||
export default function DatePickerV2({ control, name, min, type, max, setValue, classNameCustomized }) {
|
||||
const { field, fieldState } = useController({ control, name });
|
||||
const [date, setDate] = useState(new Date());
|
||||
const [showCalender, setShowCalender] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNaN(new Date(field.value))) setDate(new Date(field.value));
|
||||
}, [field.value]);
|
||||
|
||||
return (
|
||||
<div className={`${classNameCustomized ? classNameCustomized : type ? "mb-0" : "mb-8 w-full relative"}`}>
|
||||
<div className="relative !min-h-[40px] gap-3 hover:cursor-pointer">
|
||||
<input
|
||||
type="date"
|
||||
max="9999-12-31"
|
||||
className={`${classNameCustomized ? "h-10" : "h-12"} text-left !min-h-[40px] w-full resize-non rounded-md border bg-transparent p-2 px-4 focus:outline-none active:outline-none`}
|
||||
autoComplete="off"
|
||||
{...field}
|
||||
disabled={false}
|
||||
placeholder="ceremony"
|
||||
style={{ textAlignLast: 'left' }}
|
||||
/>
|
||||
<div className="flex items-center absolute -right-2 top-[7px]">
|
||||
<span>D.O.B</span>
|
||||
<button
|
||||
className={" h-[38px] bg-white px-3"}
|
||||
type="button"
|
||||
onClick={() => setShowCalender(!showCalender)}
|
||||
>
|
||||
<CalendarIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{showCalender &&
|
||||
<div className="absolute z-50">
|
||||
<Calendar
|
||||
onChange={(v) => {
|
||||
setValue(moment(v).format("yyyy-MM-DD"));
|
||||
setShowCalender(false);
|
||||
}}
|
||||
value={date}
|
||||
className={`calendar date-picker`}
|
||||
nextLabel={<NextIcon />}
|
||||
prevLabel={<PrevIcon />}
|
||||
next2Label={
|
||||
<div
|
||||
className="h-full w-full cursor-default"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></div>
|
||||
}
|
||||
prev2Label={
|
||||
<div
|
||||
className="h-full w-full cursor-default"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></div>
|
||||
}
|
||||
minDate={min}
|
||||
maxDate={max}
|
||||
maxDetail="month"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { fullMonthsMapping, hourlySlots, monthsMapping, daysMapping } from "@/utils/date-time-utils";
|
||||
import { formatScheduleDate, parseJsonSafely } from "@/utils/utils";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { Calendar } from "react-calendar";
|
||||
import CalendarIcon from "./icons/CalendarIcon";
|
||||
import NextIcon from "./icons/NextIcon";
|
||||
import PrevIcon from "./icons/PrevIcon";
|
||||
|
||||
const DateTimePicker = ({ defaultDate, register, fieldNames, setValue, showCalendar, setShowCalendar, toDefault, fromDefault, bookedSlots, scheduleTemplate, defaultMessage }) => {
|
||||
const [selectedDate, setSelectedDate] = useState(defaultDate ?? new Date());
|
||||
const [from, setFrom] = useState(fromDefault ?? "");
|
||||
const [to, setTo] = useState(toDefault ?? "");
|
||||
|
||||
const onApply = () => {
|
||||
setValue("from", from);
|
||||
setValue("to", to);
|
||||
setValue("selectedDate", selectedDate);
|
||||
setShowCalendar(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={showCalendar ? "popup-mobile z-50" : ""}
|
||||
onClick={() => setShowCalendar((prev) => !prev)}
|
||||
>
|
||||
{fieldNames.map((field, idx) => (
|
||||
<input
|
||||
key={idx}
|
||||
type="hidden"
|
||||
{...register(field)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`${showCalendar ? "" : "border-2"} md:border-2 p-2 w-full md:relative flex pr-16 gap-2 items-center`}
|
||||
onClick={(e) => {
|
||||
setShowCalendar((prev) => !prev);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className={`md:inline ${showCalendar ? "hidden" : ""}`}>
|
||||
<CalendarIcon />
|
||||
</div>
|
||||
<span
|
||||
id="booking-time"
|
||||
className={showCalendar ? "hidden" : "inline whitespace-nowrap md:text-base text-sm"}
|
||||
>
|
||||
{from && to
|
||||
? monthsMapping[selectedDate.getMonth()] + " " + selectedDate.getDate() + "/" + selectedDate.getFullYear() + " - " + from + " to " + to
|
||||
: defaultMessage ?? "Select date and time"}
|
||||
</span>
|
||||
{
|
||||
<div
|
||||
className={`${showCalendar ? "block" : "hidden"
|
||||
} absolute md:w-[unset] w-[80vw] bottom-[15px] top-[0%] md:-top-[22.5rem] 2xl:-top-[20rem] md:-left-10 lg:left-[-150px] left-0 md:right-[unset] right-0 text-center mx-auto shadow-lg bg-white border-2 text-sm md:max-h-[unset] min-h-[55vh] overflow-y-auto`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex justify-between items-center border-b p-[16px]">
|
||||
<h3 className="text-xl font-semibold">Select date and time</h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCalendar(false)}
|
||||
className="p-1 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex md:flex-row flex-col">
|
||||
<div className="">
|
||||
<Calendar
|
||||
onChange={(newDate) => {
|
||||
setSelectedDate(newDate);
|
||||
setFrom("");
|
||||
setTo("");
|
||||
}}
|
||||
value={selectedDate}
|
||||
className={`custom-calendar`}
|
||||
nextLabel={<NextIcon />}
|
||||
prevLabel={<PrevIcon />}
|
||||
next2Label={<></>}
|
||||
prev2Label={<></>}
|
||||
tileDisabled={({ date }) => {
|
||||
let customSlots = [];
|
||||
try {
|
||||
if (scheduleTemplate?.custom_slots && (Object.keys(scheduleTemplate?.custom_slots))?.length > 0) {
|
||||
customSlots = JSON.parse(scheduleTemplate?.custom_slots || "[]");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Invalid JSON in custom_slots", e);
|
||||
}
|
||||
if (customSlots.length > 0 && customSlots[(formatScheduleDate(date)).toString()]?.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (scheduleTemplate?.id && scheduleTemplate[daysMapping[date.getDay()]] != 1) {
|
||||
return true;
|
||||
}
|
||||
}}
|
||||
minDate={new Date()}
|
||||
maxDetail="month"
|
||||
/>
|
||||
<p className="text-left p-[16px] text-[#667085]">Pacific Time - US & Canada</p>
|
||||
<div className="md:flex hidden px-[16px] py-1 cursor-default text-left">
|
||||
<p className="min-w-[150px]">From - {from}</p>
|
||||
<p>Until - {to}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<p className="font-semibold mb-4 text-center">
|
||||
<span className="capitalize">{daysMapping[selectedDate.getDay()]}</span> , {fullMonthsMapping[selectedDate.getMonth()]} {selectedDate.getDate()}
|
||||
</p>
|
||||
<div className="flex flex-col gap-[12px] custom-calendar-scroll review-scroll overflow-y-auto overflow-x-hidden md:max-h-[270px] max-h-[150px] md:px-6 px-3 text-[#667085]">
|
||||
{hourlySlots.map((tm, idx) => {
|
||||
var formattedDate = moment(selectedDate).format("MM/DD/YY");
|
||||
var fromTime = new Date(formattedDate + " " + from);
|
||||
var toTime = new Date(formattedDate + " " + to);
|
||||
var slotTime = new Date(formattedDate + " " + tm);
|
||||
var slotTimeOnly = new Date("01/01/2001" + " " + tm);
|
||||
var json = scheduleTemplate.custom_slots ?? "[]";
|
||||
var custom_slots_obj = parseJsonSafely(json, {});
|
||||
var custom_slots = custom_slots_obj[formattedDate] ?? [];
|
||||
custom_slots = custom_slots.map((slot) => ({ fromTime: new Date(slot.start), toTime: new Date(slot.end) }));
|
||||
var template_slots = Array.isArray(scheduleTemplate.slots) ? scheduleTemplate.slots.map((slot) => ({ fromTime: new Date(slot.start), toTime: new Date(slot.end) })) : [];
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={idx}
|
||||
className={`${from == tm || to == tm ? "border-black border-2" : "border disabled:bg-[#F2F4F7] disabled:line-through border-[#EAECF0]"
|
||||
} md:w-[152px] w-full text-center py-[8px] ${from && to && fromTime <= slotTime && toTime >= slotTime ? "font-semibold between-slots" : ""}`}
|
||||
onClick={(e) => {
|
||||
if (from == tm) {
|
||||
setFrom("");
|
||||
setTo("");
|
||||
return;
|
||||
}
|
||||
if (to == tm) {
|
||||
setTo("");
|
||||
return;
|
||||
}
|
||||
if (from == "") {
|
||||
setFrom(e.target.innerText);
|
||||
} else {
|
||||
setTo(e.target.innerText);
|
||||
}
|
||||
}}
|
||||
disabled={(() => {
|
||||
// disabled slots that are not available in template only if a custom slot was not defined for the selectedDay
|
||||
// if custom slots were defined for selectedDay then disable slots that are not included
|
||||
|
||||
// disable if time is < current time
|
||||
if (slotTime < new Date()) return true;
|
||||
|
||||
if (custom_slots.length > 0) {
|
||||
var shouldDisable = false;
|
||||
for (let i = 0; i < custom_slots.length; i++) {
|
||||
const slot = custom_slots[i];
|
||||
if (slot.fromTime <= slotTime && slot.toTime >= slotTime) {
|
||||
shouldDisable = false;
|
||||
break;
|
||||
} else {
|
||||
shouldDisable = true;
|
||||
}
|
||||
}
|
||||
if (shouldDisable) return true;
|
||||
}
|
||||
else {
|
||||
var shouldDisable = false;
|
||||
for (let i = 0; i < template_slots.length; i++) {
|
||||
const slot = template_slots[i];
|
||||
if (slot.fromTime <= slotTimeOnly && slot.toTime >= slotTimeOnly) {
|
||||
shouldDisable = false;
|
||||
break;
|
||||
} else {
|
||||
shouldDisable = true;
|
||||
}
|
||||
}
|
||||
if (shouldDisable) return true;
|
||||
}
|
||||
})()}
|
||||
>
|
||||
{tm}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-8 px-6">
|
||||
<button
|
||||
type="button"
|
||||
className="login-btn-gradient w-[152px] text-center py-[8px] rounded-sm text-white"
|
||||
disabled={from == "" || to == ""}
|
||||
onClick={onApply}
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
<div className="md:hidden flex px-1 py-1 mt-2 cursor-default text-left">
|
||||
<p className="min-w-[150px]">From - {from}</p>
|
||||
<p>Until - {to}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateTimePicker;
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DRAFT_STATUS } from "@/utils/constants";
|
||||
|
||||
const DraftProgress = ({ data, scheduleTemplate }) => {
|
||||
return (
|
||||
<div className="flex relative mx-auto md:max-w-lg max-w-[300px] items-center justify-between mb-40 normal-case">
|
||||
<div className="absolute left-0 absolute-middle bg-gray-300 right-0 flex">
|
||||
<div className={`${data.draft_status >= DRAFT_STATUS.IMAGES ? "login-btn-gradient" : ""} h-full flex-grow`}></div>
|
||||
<div className={`${data.draft_status > DRAFT_STATUS.SCHEDULING ? "login-btn-gradient" : ""} h-full flex-grow`}></div>
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<Link
|
||||
to={`/account/my-spaces/${data.id}/edit-${data.draft_status >= DRAFT_STATUS.PROPERTY_SPACE ? "property-space?mode=edit" : "property-space?mode=create"}`}
|
||||
className={`draft-stage ${data.draft_status >= DRAFT_STATUS.PROPERTY_SPACE ? "complete" : ""}`}
|
||||
state={data}
|
||||
>
|
||||
1
|
||||
</Link>
|
||||
<p className="absolute -left-6">About location</p>
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<Link
|
||||
to={`/account/my-spaces/${data.id}/edit-${data.draft_status >= DRAFT_STATUS.IMAGES ? "images?mode=edit" : "images?mode=create"}`}
|
||||
className={`draft-stage ${data.draft_status >= DRAFT_STATUS.IMAGES ? "complete" : ""}`}
|
||||
state={data}
|
||||
>
|
||||
2
|
||||
</Link>
|
||||
<p className="absolute md:w-[unset] -left-4 !w-[60px]">Images, Addons, FAQs etc</p>
|
||||
</div>
|
||||
<div className="relative z-10">
|
||||
<Link
|
||||
to={`/account/my-spaces/${data.id}/edit-${scheduleTemplate.id ? "scheduling?mode=edit" : "scheduling?mode=create"}`}
|
||||
className={`draft-stage ${data.draft_status > DRAFT_STATUS.SCHEDULING ? "complete" : ""}`}
|
||||
state={scheduleTemplate}
|
||||
>
|
||||
3
|
||||
</Link>
|
||||
<p className="absolute -left-8 md:!w-[unset] !w-[50px]">Templates & Scheduling</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DraftProgress;
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const FaqAccordion = ({ data }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="mb-[16px]">
|
||||
<div className="mb-[12px]">
|
||||
<button
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
className="flex items-center"
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="8"
|
||||
viewBox="0 0 14 8"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`${open ? "rotate-180" : "rotate-90"} duration-200`}
|
||||
>
|
||||
<path
|
||||
d="M13 7L7 1L1 7"
|
||||
stroke="#475467"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-semibold ml-4 text-[16px]">{data.question}</span>
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={`ml-8 duration-500 overflow-hidden ${open ? `pointer-events-auto max-h-[300px]` : "max-h-0 pointer-events-none"}`}
|
||||
dangerouslySetInnerHTML={{ __html: data.answer }}
|
||||
></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FaqAccordion;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
|
||||
const FaqTile = ({ data }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={`mb-8 overflow-hidden`}>
|
||||
<div
|
||||
className={`mb-5 bg-[#F0F5F3] p-2 px-5 cursor-pointer rounded-xl overflow-hidden`}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<h1>{data.question}</h1>
|
||||
<button className="text-4xl">{!open ? <span>+</span> : <span>−</span>} </button>
|
||||
</div>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
show={open}
|
||||
enter="transition-all ease duration-500 overflow-hidden"
|
||||
enterFrom="max-h-0"
|
||||
enterTo="max-h-[400px]"
|
||||
leave="transition-all ease duration-500 overflow-hidden"
|
||||
leaveFrom="max-h-[400px]"
|
||||
leaveTo="max-h-0"
|
||||
>
|
||||
<p
|
||||
className={`sun-editor-editable pl-4 z-50`}
|
||||
dangerouslySetInnerHTML={{ __html: data.answer }}
|
||||
></p>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FaqTile;
|
||||
@@ -0,0 +1,207 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import HeartIcon from "@/components/frontend/icons/HeartIcon";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import React, { useContext, useState } from "react";
|
||||
import useDelayUnmount from "@/hooks/useDelayUnmount";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
function FavoriteButton({ space_id, user_property_spaces_id, reRender, withLoader, className, buttonClassName, stroke, favColor }) {
|
||||
const [unfavorite, setUnfavorite] = useState(false);
|
||||
const showUnfavorite = useDelayUnmount(unfavorite, 100);
|
||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const { state: authState } = useContext(AuthContext);
|
||||
const sdk = new MkdSDK();
|
||||
async function favorite() {
|
||||
if (withLoader) {
|
||||
globalDispatch({ type: "START_LOADING" });
|
||||
}
|
||||
sdk.setTable("user_property_spaces");
|
||||
try {
|
||||
await sdk.callRestAPI({ property_spaces_id: space_id, user_id: authState.user }, "POST");
|
||||
if (reRender) {
|
||||
reRender(new Date());
|
||||
}
|
||||
globalDispatch({ type: "STOP_LOADING" });
|
||||
} catch (err) {
|
||||
globalDispatch({ type: "STOP_LOADING" });
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function unFavorite() {
|
||||
if (withLoader) {
|
||||
globalDispatch({ type: "START_LOADING" });
|
||||
}
|
||||
sdk.setTable("user_property_spaces");
|
||||
try {
|
||||
await sdk.callRestAPI({ id: user_property_spaces_id }, "DELETE");
|
||||
if (reRender) {
|
||||
reRender(new Date());
|
||||
}
|
||||
globalDispatch({ type: "STOP_LOADING" });
|
||||
setUnfavorite(false);
|
||||
} catch (err) {
|
||||
globalDispatch({ type: "STOP_LOADING" });
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation Failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleFavorite() {
|
||||
if (user_property_spaces_id) {
|
||||
setUnfavorite(true);
|
||||
} else {
|
||||
favorite();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className ?? "flex flex-grow justify-end pt-2"}>
|
||||
<button
|
||||
className={buttonClassName ?? "pointer-auto flex h-[32px] w-[32px] items-center justify-center rounded-full bg-[#13131366] text-end"}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleFavorite();
|
||||
}}
|
||||
id="favorite-button"
|
||||
>
|
||||
<HeartIcon
|
||||
isFav={user_property_spaces_id !== null && user_property_spaces_id !== 0}
|
||||
stroke={stroke}
|
||||
favColor={favColor}
|
||||
/>
|
||||
</button>
|
||||
{showUnfavorite && (
|
||||
<div
|
||||
className="popup-container flex items-center justify-center normal-case"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setUnfavorite(false);
|
||||
{
|
||||
showUnfavorite && (
|
||||
<div
|
||||
className="popup-container flex items-center justify-center normal-case"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setUnfavorite(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`${unfavorite ? "pop-in" : "pop-out"} w-[400px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="mb-[18px] flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">Are you sure?</h3>
|
||||
<button
|
||||
className="rounded-full border p-1 px-3 text-2xl font-normal duration-300 hover:bg-gray-200"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setUnfavorite(false);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p>Are you sure you want to remove this space from your favorites?</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 flex-grow rounded border-2 border-[#98A2B3] py-2 tracking-wide outline-none focus:outline-none"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setUnfavorite(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="login-btn-gradient mt-4 flex-grow rounded py-2 tracking-wide text-white outline-none focus:outline-none"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
unFavorite();
|
||||
}}
|
||||
>
|
||||
Yes, remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`${unfavorite ? "pop-in" : "pop-out"} w-[400px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="mb-[18px] flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">Are you sure?</h3>
|
||||
<button
|
||||
className="rounded-full border p-1 px-3 text-2xl font-normal duration-300 hover:bg-gray-200"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setUnfavorite(false);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p>Are you sure you want to remove this space from your favorites?</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 flex-grow rounded border-2 border-[#98A2B3] py-2 tracking-wide outline-none focus:outline-none"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setUnfavorite(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="login-btn-gradient mt-4 flex-grow rounded py-2 tracking-wide text-white outline-none focus:outline-none"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
unFavorite();
|
||||
}}
|
||||
>
|
||||
Yes, remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* <Tooltip
|
||||
anchorId="favorite-button"
|
||||
place="right"
|
||||
content={user_property_spaces_id ? "Remove from favorites" : "Add to favorites"}
|
||||
noArrow
|
||||
/> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FavoriteButton;
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import StarIcon from "./icons/StarIcon";
|
||||
|
||||
const FilterCheckBoxes = ({ name, options, searchField, query, optionFieldName, filterPopup }) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
const uncheckAll = () => {
|
||||
searchParams.set(searchField, "");
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
const updateSearchQuery = (e) => {
|
||||
e.preventDefault();
|
||||
var prev = searchParams.get(searchField);
|
||||
prev = prev?.split(",") || [];
|
||||
if (!prev.includes(e.target.name)) {
|
||||
prev.push(e.target.name);
|
||||
} else {
|
||||
prev.splice(prev.indexOf(e.target.name), 1);
|
||||
}
|
||||
searchParams.set(searchField, prev.join(","));
|
||||
setSearchParams(searchParams);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (filterPopup) {
|
||||
setOpen(true);
|
||||
}
|
||||
}, [filterPopup]);
|
||||
|
||||
return (
|
||||
<div className="mb-[34px]">
|
||||
<div className="flex justify-between mb-[12px]">
|
||||
<h4 className="font-semibold text-[16px] lg:block flex justify-between w-full">
|
||||
<span className="lg:border-r lg:pr-2 lg:mr-2">{name}</span>
|
||||
<button
|
||||
className="lg:text-xs text-sm font-normal lowercase"
|
||||
onClick={uncheckAll}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</h4>
|
||||
<button
|
||||
className={`${open ? "" : "rotate-180"} duration-200 lg:inline hidden`}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
height="8"
|
||||
viewBox="0 0 14 8"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13 7L7 1L1 7"
|
||||
stroke="#475467"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className={`text-gray-500 text-[16px] duration-500 overflow-hidden ${open ? `pointer-events-auto max-h-[300px]` : "max-h-0 pointer-events-none"}`}>
|
||||
{options.map((op) => (
|
||||
<div
|
||||
className="checkbox-container mb-[12px]"
|
||||
key={op.id}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={op[optionFieldName] ?? op.name}
|
||||
onClick={updateSearchQuery}
|
||||
name={op[optionFieldName] ?? op.name}
|
||||
checked={Array.isArray(query[searchField]) ? query[searchField]?.includes(op[optionFieldName] ?? op.name) : false}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<label htmlFor={op[optionFieldName] ?? op.name}>{op[optionFieldName] ?? op.name} {name == "Reviews" ? Array(Number(op.name)).fill("").map(() => <span className="ml-1 mb-1"><StarIcon /></span>) : null}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterCheckBoxes;
|
||||
@@ -0,0 +1,122 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import React from "react";
|
||||
import { useMemo } from "react";
|
||||
import { useContext } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import LogoIcon from "./icons/LogoIcon";
|
||||
|
||||
const Footer = () => {
|
||||
const { state: authState, dispatch: authDispatch } = useContext(AuthContext);
|
||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const blackList = useMemo(() => ["/admin", "/login", "/account/messages", "/signup"], []);
|
||||
|
||||
function switchToHost() {
|
||||
authDispatch({ type: "SWITCH_TO_HOST" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a host`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function switchToHostOrCustomer() {
|
||||
if (authState.role == "host") {
|
||||
switchToCustomer();
|
||||
} else {
|
||||
switchToHost();
|
||||
}
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
if (blackList.some((path) => pathname.startsWith(path))) return null;
|
||||
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div className="header-light pb-10">
|
||||
<div className="container mx-auto px-4 py-[24px] text-white 2xl:px-16">
|
||||
<div className="mb-[17px] hidden justify-between md:flex">
|
||||
<Link to="/">
|
||||
<LogoIcon />
|
||||
</Link>
|
||||
<div className="flex gap-[24px]">
|
||||
{(authState.role == "host" || authState.role == "customer") && (
|
||||
<>
|
||||
{authState.originalRole != "customer" ? (
|
||||
<button
|
||||
className="duration-200 hover:underline"
|
||||
onClick={switchToHostOrCustomer}
|
||||
>
|
||||
Join as {authState.role == "host" ? "customer" : "host"}
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
className="duration-200 hover:underline"
|
||||
to="/become-a-host"
|
||||
>
|
||||
Host Your Space
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-72 gap-[24px] pl-2">
|
||||
<Link
|
||||
className="duration-200 hover:underline"
|
||||
to="/faq"
|
||||
>
|
||||
FAQs
|
||||
</Link>
|
||||
<Link
|
||||
className="duration-200 hover:underline"
|
||||
to="/contact-us"
|
||||
>
|
||||
Contact us
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[17px] flex justify-between text-xs md:text-sm">
|
||||
<div className="flex gap-[24px]">
|
||||
<span>ergo © All rights reserved</span>
|
||||
</div>
|
||||
<div className="flex gap-[24px]">
|
||||
<Link
|
||||
className="duration-200 hover:underline"
|
||||
to="/help/terms_and_conditions"
|
||||
>
|
||||
Terms and conditions
|
||||
</Link>
|
||||
<Link
|
||||
className="duration-200 hover:underline"
|
||||
to="/help/privacy-policy"
|
||||
>
|
||||
Privacy and policy
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
import { IMAGE_STATUS } from "@/utils/constants";
|
||||
import React, { useEffect } from "react";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
import StarIcon from "./icons/StarIcon";
|
||||
import { useState } from "react";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
|
||||
const HostCard = ({ data }) => {
|
||||
let sdk = new MkdSDK();
|
||||
|
||||
const [user, setUser] = useState()
|
||||
|
||||
const fetchUser = async () => {
|
||||
sdk.setTable("user")
|
||||
const users = await sdk.getAllUsers()
|
||||
let host_user = users?.find(user => user.id == data.id)
|
||||
setUser(host_user)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("token")) {
|
||||
fetchUser()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex items-start w-[400px] md:text-base text-sm remove-select">
|
||||
<img
|
||||
src={data.is_photo_approved == IMAGE_STATUS.APPROVED ? data.photo ?? "/default.png" : "/default.png"}
|
||||
alt={data.first_name}
|
||||
className="rounded-full cursor-pointer md:w-[80px] md:h-[80px] w-[60px] h-[60px] object-cover"
|
||||
/>
|
||||
<div className="px-[12px]">
|
||||
<h4 className="md:text-2xl text-lg font-semibold">
|
||||
{data.first_name || <Skeleton />} {data.last_name}
|
||||
</h4>
|
||||
<div className="flex items-center">
|
||||
<p className="text-gray-500 mb-[6px]">{data?.city && data?.city}</p>
|
||||
<p className="text-gray-500 mb-[6px]">{data?.country && ", " + data?.country}</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-end lowercase">
|
||||
<p className="flex gap-2 items-center">
|
||||
<StarIcon />
|
||||
<span>
|
||||
{(Number(data.avg_host_rating) || 0).toFixed(1)}
|
||||
{(typeof data?.rating_count === "number" && data?.rating_count > 0) &&
|
||||
<span>
|
||||
{" "}({data.rating_count})
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HostCard;
|
||||
@@ -0,0 +1,72 @@
|
||||
import React, { useRef } from "react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
|
||||
import { Mousewheel } from "swiper";
|
||||
import HostCard from "./HostCard";
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export default function HostCardSlider({ hosts }) {
|
||||
const scrollTable = useRef(null);
|
||||
const navigate = useNavigate()
|
||||
|
||||
const moveTable = (ref) => {
|
||||
ref.scrollLeft += 160;
|
||||
};
|
||||
const moveTableBack = (ref) => {
|
||||
ref.scrollLeft += -160;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{hosts.length == 0 && (
|
||||
<p className="text-center flex items-center justify-center normal-case min-h-[200px] max-w-fit">
|
||||
<b>No Hosts found</b>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
<div
|
||||
ref={scrollTable}
|
||||
className="flex justify-between w-full overflow-auto sidebar-holdee">
|
||||
{hosts.length > 0 && hosts.map((host, idx) => (
|
||||
<div
|
||||
className=""
|
||||
key={idx}
|
||||
>
|
||||
<HostCard data={host} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* !["/"].includes(location.pathname) && */}
|
||||
{(hosts.length > 3 && window.innerWidth > 800) &&
|
||||
<div className="flex items-center pb-2 gap-3 justify-center mx-auto w-full pt-6">
|
||||
<div className="cursor-pointer"
|
||||
onClick={() =>
|
||||
moveTableBack(scrollTable.current)
|
||||
}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(`/admin/${backTo}`)}
|
||||
className="mr-2 mb-2 inline-flex items-center py-2.5 pr-5 text-center text-sm font-semibold"
|
||||
>
|
||||
<ArrowLeftIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="cursor-pointer" onClick={() => moveTable(scrollTable.current)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => navigate(`/admin/${backTo}`)}
|
||||
className="mr-2 mb-2 inline-flex items-center py-2.5 pr-5 text-center text-sm font-semibold"
|
||||
>
|
||||
<ArrowRightIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
export default function LoadingButton({ loading, loadingEl, children, ...restProps }) {
|
||||
return (
|
||||
<button
|
||||
{...restProps}
|
||||
style={{ pointerEvents: loading ? "none" : undefined, cursor: loading ? "not-allowed" : undefined }}
|
||||
>
|
||||
{loading ? (
|
||||
loadingEl ?? (
|
||||
<div className="flex justify-center">
|
||||
<svg
|
||||
style={{ margin: "auto", background: "none", display: "block", shapeRendering: "auto" }}
|
||||
width="36px"
|
||||
height="36px"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#d0d5dd"
|
||||
strokeWidth="10"
|
||||
strokeDasharray="42.76482137044271 42.76482137044271"
|
||||
d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z"
|
||||
strokeLinecap="round"
|
||||
style={{ transform: "scale(1)", transformOrigin: "50px 50px" }}
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
repeatCount="indefinite"
|
||||
dur="1.6666666666666667s"
|
||||
keyTimes="0;1"
|
||||
values="0;256.58892822265625"
|
||||
></animate>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<span>{children}</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import LoadingButton from "./LoadingButton";
|
||||
|
||||
export default function LogoutModal({ modalOpen, closeModal }) {
|
||||
const { dispatch: authDispatch } = useContext(AuthContext);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function logout() {
|
||||
setLoading(true);
|
||||
const sdk = new MkdSDK();
|
||||
try {
|
||||
await sdk.logout();
|
||||
authDispatch({ type: "LOGOUT" });
|
||||
navigate("/");
|
||||
closeModal();
|
||||
} catch (err) {
|
||||
// still logout if the token is already expired
|
||||
if (err.message == "TOKEN_EXPIRED") {
|
||||
authDispatch({ type: "LOGOUT" });
|
||||
navigate("/");
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
Are you sure
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">Are you sure you want to sign out?</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="button"
|
||||
className={`inline-flex justify-center rounded-md ${loading ? "py-1 px-6" : "py-2 px-4"} login-btn-gradient text-sm font-medium text-white`}
|
||||
onClick={logout}
|
||||
>
|
||||
Proceed
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { useState } from "react";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Mousewheel } from "swiper";
|
||||
import { useContext } from "react";
|
||||
import { AuthContext } from "@/authContext";
|
||||
|
||||
export default function NavBarSlider() {
|
||||
const [swiper, setSwiper] = useState(null);
|
||||
const { state } = useContext(AuthContext);
|
||||
const role = state.role;
|
||||
|
||||
const customerNavItems = [
|
||||
{ name: "My Bookings", route: "/account/my-bookings" },
|
||||
{ name: "Messages", route: "/account/messages" },
|
||||
{ name: "Reviews", route: "/account/reviews" },
|
||||
{ name: "Profile", route: "/account/profile" },
|
||||
{ name: "Payment", route: "/account/payments" },
|
||||
{ name: "Billing", route: "/account/billing" },
|
||||
];
|
||||
const hostNavItems = [
|
||||
{ name: "My Bookings", route: "/account/my-bookings" },
|
||||
{ name: "Messages", route: "/account/messages" },
|
||||
{ name: "Reviews", route: "/account/reviews" },
|
||||
{ name: "My Spaces", route: "/account/my-spaces" },
|
||||
{ name: "My Addons", route: "/account/my-addons" },
|
||||
{ name: "My Amenities", route: "/account/my-amenities" },
|
||||
{ name: "Profile", route: "/account/profile" },
|
||||
{ name: "Payment", route: "/account/payments" },
|
||||
{ name: "Billing", route: "/account/billing" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="border-b">
|
||||
<Swiper
|
||||
slidesPerView={"auto"}
|
||||
centeredSlides={true}
|
||||
spaceBetween={0}
|
||||
mousewheel={true}
|
||||
className="navbar-slider"
|
||||
initialSlide={1}
|
||||
centeredSlidesBounds={true}
|
||||
modules={[Mousewheel]}
|
||||
onSwiper={setSwiper}
|
||||
breakpoints={{
|
||||
640: { enabled: false },
|
||||
}}
|
||||
>
|
||||
{(role == "host" ? hostNavItems : customerNavItems).map((items, i) => (
|
||||
<SwiperSlide
|
||||
className="!w-[120px] slider-menu text-center pb-3"
|
||||
key={items.route}
|
||||
>
|
||||
<NavLink
|
||||
className={`${items.name === "Reviews" && "thirteenth-step"} ${items.name === "Payment" && "twelfth-step"} ${items.name === "My Bookings" && "seventeen-step"}`}
|
||||
to={items.route}
|
||||
onClick={() => swiper.slideTo(i)}
|
||||
>
|
||||
{items.name}
|
||||
</NavLink>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
<div className="mover"></div>
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import { AuthContext, tokenExpireError } from "@/authContext";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { ID_VERIFICATION_STATUSES, IMAGE_STATUS } from "@/utils/constants";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { useRef } from "react";
|
||||
import { useContext } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import Icon from "../Icons";
|
||||
import LogoutModal from "./LogoutModal";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { ChatBubbleBottomCenterIcon } from "@heroicons/react/24/outline";
|
||||
import { useTour } from "@reactour/tour";
|
||||
|
||||
const sdk = new MkdSDK();
|
||||
|
||||
export default function NavMenu({ variant }) {
|
||||
const { state: globalState, dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const { state: authState, dispatch: authDispatch } = useContext(AuthContext);
|
||||
const [unreadCount, setUnreadCount] = useState(globalState.unreadMessages);
|
||||
const [height, setHeight] = useState(window.innerHeight);
|
||||
const navigate = useNavigate();
|
||||
const menuRef = useRef(null);
|
||||
|
||||
function switchToHost() {
|
||||
authDispatch({ type: "SWITCH_TO_HOST" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a host`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
function switchToAdmin() {
|
||||
authDispatch({ type: "SWITCH_TO_ADMIN" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as an admin`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
navigate("/admin/dashboard");
|
||||
}
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
navigate("/");
|
||||
}
|
||||
async function fetchUnreadMessagesCount() {
|
||||
try {
|
||||
const result = await sdk.getMyRoom();
|
||||
if (Array.isArray(result.messages)) {
|
||||
globalDispatch({
|
||||
type: "SET_UNREAD_MESSAGES_COUNT",
|
||||
payload: result.messages.filter((msg) => {
|
||||
const messageSenderId = JSON.parse(msg.chat).user_id;
|
||||
return Number(messageSenderId) != Number(authState.user);
|
||||
}).length,
|
||||
});
|
||||
}
|
||||
|
||||
setUnreadCount(result.messages.filter((msg) => {
|
||||
const messageSenderId = JSON.parse(msg.chat).user_id;
|
||||
return Number(messageSenderId) != Number(authState.user);
|
||||
}).length)
|
||||
} catch (err) {
|
||||
tokenExpireError(authDispatch, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchUnreadMessagesCount();
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
setHeight(window.innerHeight);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
// Cleanup event listener on component unmount
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [logoutModal, setLogoutModal] = useState(false);
|
||||
|
||||
function getVerifiedColor(status) {
|
||||
switch (status) {
|
||||
case ID_VERIFICATION_STATUSES.PENDING:
|
||||
return "";
|
||||
case ID_VERIFICATION_STATUSES.VERIFIED:
|
||||
return "text-green-600";
|
||||
case ID_VERIFICATION_STATUSES.REJECTED:
|
||||
return "text-red-600";
|
||||
default:
|
||||
return "text-red-600";
|
||||
}
|
||||
}
|
||||
|
||||
const { setIsOpen } = useTour()
|
||||
|
||||
const verificationStatuses = ["Pending Verification", "Verified", "Not Verified"];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="z-10 text-black">
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative inline-block text-left"
|
||||
ref={menuRef}
|
||||
>
|
||||
<div>
|
||||
<Menu.Button
|
||||
className="eighth-step pointer-events-auto relative h-[36px] w-[36px] overflow-hidden rounded-full"
|
||||
id="menu-btn"
|
||||
>
|
||||
<Icon
|
||||
type="user"
|
||||
fill=""
|
||||
variant="circle"
|
||||
className={"my-stroke-" + variant}
|
||||
/>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
show={globalState.menuIconOpen || undefined}
|
||||
className="overflow-y-auto"
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className={`absolute hidden-scrollbar ${(height < 720 && height > 560) && "max-h-[500px]"} ${(height < 560) && "max-h-[400px]"} overflow-y-auto right-0 mt-2 w-80 max-w-screen-sm origin-top-right rounded-3xl border bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}>
|
||||
<div className="flex flex-col items-center border-b p-4">
|
||||
<img
|
||||
src={globalState.user.is_photo_approved == IMAGE_STATUS.APPROVED ? globalState.user.photo ?? "/default.png" : "/default.png"}
|
||||
className="mb-3 h-[36px] w-[36px] rounded-full object-cover"
|
||||
/>
|
||||
<h3 className="mb-1 font-semibold">
|
||||
{globalState.user.first_name} {globalState.user.last_name}
|
||||
</h3>
|
||||
<p className="font-thin">You are signed in as {authState.role}</p>
|
||||
<span className={getVerifiedColor(globalState.user.verificationStatus)}>{verificationStatuses[globalState.user.verificationStatus] ?? "Not verified"}</span>
|
||||
</div>
|
||||
<Menu.Item>
|
||||
<>
|
||||
<div className={`block border-b px-4 py-2 md:hidden`}>
|
||||
<button
|
||||
onClick={() => navigate("/account/profile")}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Account & profile
|
||||
</button>
|
||||
</div>
|
||||
<div className="px-4 py-2 md:hidden block">
|
||||
<Link
|
||||
to={"/account/messages"}
|
||||
className={`relative -mx-3 flex w-full justify-between items-center rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Messages{" "}
|
||||
{globalState.unreadMessages > 0 && (
|
||||
<strong className={`login-btn-gradient flex h-[23px] w-[23px] items-center justify-center rounded-full border p-2 text-xs text-white`}>{globalState.unreadMessages}</strong>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
</Menu.Item>
|
||||
<div className={`hidden border-b px-4 py-2 md:block`}>
|
||||
<Menu.Item>
|
||||
<>
|
||||
<Link
|
||||
to={"/account/my-bookings"}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
My bookings
|
||||
</Link>
|
||||
<Link
|
||||
to={"/account/messages"}
|
||||
className={`relative -mx-3 flex w-full justify-between items-center rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Messages{" "}
|
||||
{globalState.unreadMessages > 0 && (
|
||||
<strong className={`login-btn-gradient flex h-[23px] w-[23px] items-center justify-center rounded-full border p-2 text-xs text-white`}>{globalState.unreadMessages}</strong>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to={"/account/reviews"}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Reviews
|
||||
</Link>
|
||||
{authState.role == "host" && (
|
||||
<Link
|
||||
to={"/account/my-spaces"}
|
||||
data-tour='first-step-2'
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
My Spaces
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
to={"/account/profile"}
|
||||
data-tour="first-step"
|
||||
// data-step={2}
|
||||
className="first-step -mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200"
|
||||
>
|
||||
Profile
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
to={"/account/payments"}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Payment
|
||||
</Link>
|
||||
<Link
|
||||
to={"/account/billing"}
|
||||
className={`ninth-step -mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Billing
|
||||
</Link>
|
||||
</>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
<div className={`border-t px-4 pt-2 pb-2`}>
|
||||
<Menu.Item>
|
||||
<button
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
onClick={() => { globalDispatch({ type: "START_TOUR" }); setIsOpen(true) }}
|
||||
>
|
||||
Help me get started
|
||||
</button>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link
|
||||
to="/faq"
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
FAQs
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
<Link
|
||||
to="/favorites"
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Favorites
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{authState.role == "customer" ? (
|
||||
<>
|
||||
{authState.originalRole != "customer" ? (
|
||||
<button
|
||||
onClick={switchToHost}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Sign in as host
|
||||
</button>
|
||||
) : (
|
||||
<Link
|
||||
to={"/become-a-host"}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Become a host
|
||||
</Link>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
onClick={switchToCustomer}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Sign in as customer
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
{["superadmin", "admin"].includes(authState.originalRole) && (
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={switchToAdmin}
|
||||
className={`-mx-3 flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
>
|
||||
Sign in as admin
|
||||
</button>
|
||||
</Menu.Item>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-1">
|
||||
<Menu.Item>
|
||||
<button
|
||||
className={`flex w-full justify-start rounded-pill p-2 px-3 duration-200 hover:bg-gray-200`}
|
||||
onClick={() => setLogoutModal(true)}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
<LogoutModal
|
||||
modalOpen={logoutModal}
|
||||
closeModal={() => setLogoutModal(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import Swiper from "swiper";
|
||||
import { SwiperSlide, Swiper as SwiperComponent } from "swiper/react";
|
||||
import { Navigation, Pagination, A11y } from "swiper";
|
||||
|
||||
export default function PropertyEditImageSlider({ modalOpen, closeModal, spaceImages }) {
|
||||
const [currentImageSlide, setCurrentImageSlide] = useState(0);
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-50"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="div"
|
||||
className="bg-white p-5 rounded-lg md:w-4/5 w-5/6 transform overflow-hidden shadow-xl transition-all"
|
||||
>
|
||||
<div className="flex justify-between md:mb-[24px] mb-4">
|
||||
<div></div>
|
||||
<p className="self-center normal-case">
|
||||
Images {currentImageSlide + 1} of {spaceImages.length}
|
||||
</p>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="">
|
||||
<SwiperComponent
|
||||
// install Swiper modules
|
||||
modules={[Navigation, Pagination, A11y]}
|
||||
spaceBetween={50}
|
||||
slidesPerView={1}
|
||||
loop={true}
|
||||
navigation
|
||||
pagination={{
|
||||
clickable: true,
|
||||
renderBullet: (i, className) => `<img src="${spaceImages[i].photo_url || "/default-property.jpg"}" draggable="false" class="pagination-image ${className}" />`,
|
||||
}}
|
||||
className="property-swiper-slid"
|
||||
>
|
||||
{spaceImages.map((img, i) => (
|
||||
<SwiperSlide
|
||||
key={i}
|
||||
className="md:pb-[120px]"
|
||||
>
|
||||
{({ isActive }) => {
|
||||
if (isActive) setCurrentImageSlide(i);
|
||||
return (
|
||||
<img
|
||||
src={img.photo_url || "/default-property.jpg"}
|
||||
draggable={"false"}
|
||||
className="w-full property-swiper-image md:h-[600px] h-[300px"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</SwiperComponent>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import Swiper from "swiper";
|
||||
import { SwiperSlide, Swiper as SwiperComponent } from "swiper/react";
|
||||
import { Navigation, Pagination, A11y } from "swiper";
|
||||
|
||||
export default function PropertyImageSlider({ modalOpen, closeModal, spaceImages }) {
|
||||
const [currentImageSlide, setCurrentImageSlide] = useState(0);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-50"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="div"
|
||||
className="bg-white p-5 rounded-lg md:w-4/5 w-5/6 transform overflow-hidden shadow-xl transition-all"
|
||||
>
|
||||
<div className="flex justify-between md:mb-[24px] mb-4">
|
||||
<div></div>
|
||||
{spaceImages.length > 0 ?
|
||||
<p className="self-center normal-case">
|
||||
Images {currentImageSlide + 1} of {spaceImages.length}
|
||||
</p>
|
||||
:
|
||||
<p className="self-center normal-case">
|
||||
No approved images to preview
|
||||
</p>
|
||||
}
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="">
|
||||
<SwiperComponent
|
||||
// install Swiper modules
|
||||
modules={[Navigation, Pagination, A11y]}
|
||||
spaceBetween={50}
|
||||
slidesPerView={1}
|
||||
loop={true}
|
||||
navigation
|
||||
pagination={{
|
||||
clickable: true,
|
||||
renderBullet: (i, className) => `<img src="${spaceImages[i].photo_url || "/default-property.jpg"}" draggable="false" class="pagination-imag ${className}" />`,
|
||||
}}
|
||||
className="property-swiper-slid"
|
||||
>
|
||||
{spaceImages.map((img, i) => (
|
||||
<SwiperSlide
|
||||
key={i}
|
||||
className="md:pb-[120px]"
|
||||
>
|
||||
{({ isActive }) => {
|
||||
if (isActive) setCurrentImageSlide(i);
|
||||
return (
|
||||
<img
|
||||
src={img?.photo_url || "/default-property.jpg"}
|
||||
draggable={"false"}
|
||||
className="w-full property-swiper-image md:h-[600px] h-[300px]"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</SwiperComponent>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import Swiper from "swiper";
|
||||
import { SwiperSlide, Swiper as SwiperComponent } from "swiper/react";
|
||||
import { Navigation, Pagination, A11y } from "swiper";
|
||||
|
||||
export default function PropertyImageSliderAdd({ modalOpen, closeModal, spaceImages }) {
|
||||
const [currentImageSlide, setCurrentImageSlide] = useState(0);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-50"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="div"
|
||||
className="bg-white p-5 rounded-lg md:w-4/5 w-5/6 transform overflow-hidden shadow-xl transition-all"
|
||||
>
|
||||
{/* <div className="flex justify-between md:mb-[24px] mb-4">
|
||||
<div></div>
|
||||
{spaceImages.length > 0 ?
|
||||
<p className="self-center normal-case">
|
||||
Images {currentImageSlide + 1} of {spaceImages.length}
|
||||
</p>
|
||||
:
|
||||
<p className="self-center normal-case">
|
||||
No approved images to preview
|
||||
</p>
|
||||
}
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div> */}
|
||||
<div className="flex justify-between md:mb-[24px] mb-4">
|
||||
<div></div>
|
||||
<p className="self-center normal-case">
|
||||
Images {currentImageSlide + 1} of {spaceImages?.filter((v) => (v != null && v != "")).length}
|
||||
</p>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="">
|
||||
<SwiperComponent
|
||||
// install Swiper modules
|
||||
modules={[Navigation, Pagination, A11y]}
|
||||
spaceBetween={50}
|
||||
slidesPerView={1}
|
||||
loop={true}
|
||||
navigation
|
||||
pagination={{
|
||||
clickable: true,
|
||||
renderBullet: (i, className) => `<img src="${spaceImages[i] || "/default-property.jpg"}" draggable="false" class="pagination-imag ${className}" />`,
|
||||
}}
|
||||
className="property-swiper-slid"
|
||||
>
|
||||
{spaceImages?.filter((v) => (v != null && v != "")).map((img, i) => (
|
||||
<SwiperSlide
|
||||
key={i}
|
||||
className="md:pb-[120px]"
|
||||
>
|
||||
{({ isActive }) => {
|
||||
if (isActive) setCurrentImageSlide(i);
|
||||
return (
|
||||
<img
|
||||
src={img || "/default-property.jpg"}
|
||||
draggable={"false"}
|
||||
className="w-full property-swiper-image md:h-[600px] h-[300px"
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</SwiperComponent>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React, { useState } from "react";
|
||||
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
|
||||
import { Link } from "react-router-dom";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import PersonIcon from "./icons/PersonIcon";
|
||||
import StarIcon from "./icons/StarIcon";
|
||||
import FavoriteButton from "./FavoriteButton";
|
||||
|
||||
let sdk = new MkdSDK();
|
||||
|
||||
const PropertySpaceCard = ({ data, forceRender, isFav }) => {
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
return (
|
||||
<SkeletonTheme enableAnimation={false}>
|
||||
<Link
|
||||
to={`/property/${data.id}`}
|
||||
state={data}
|
||||
className={`overflow-hidden relative flex flex-col ${data.id ? "" : "pointer-events-none"}`}
|
||||
>
|
||||
<img
|
||||
src={data.url}
|
||||
className="w-full rounded-lg h-[var(--property-card-img-height)] object-cover mb-2"
|
||||
alt={data.name}
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
/>
|
||||
{imageLoaded ? (
|
||||
<div className="absolute z-1 w-full h-[var(--property-card-img-height)] top-0 left-0 mb-[8px] flex flex-col px-[8px] pb-[13px]">
|
||||
<FavoriteButton
|
||||
space_id={data.id}
|
||||
user_property_spaces_id={data.user_property_spaces_id}
|
||||
reRender={forceRender}
|
||||
withLoader={true}
|
||||
/>
|
||||
<span className="px-2 py-1 text-white bg-black font-bold rounded-lg text-xs self-start">{data.category || <Skeleton />}</span>
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton className="!absolute z-1 w-full h-[var(--property-card-img-height)] top-0 left-0 !rounded-lg" />
|
||||
)}
|
||||
{/* Need to move this up because of br caused by skeleton */}
|
||||
<div className={`px-[12px] ${imageLoaded ? "" : "-mt-5"} flex-grow flex flex-col`}>
|
||||
<h4 className="text-lg font-semibold">{data.name || <Skeleton />}</h4>
|
||||
<p className="text-gray-500 mb-[6px] truncate flex-grow">{data.city ? data.city + ", " + data.country : <Skeleton />}</p>
|
||||
<div className="flex justify-between items-end lowercase">
|
||||
<p>
|
||||
{data.rate ? "from:" : <Skeleton width={100} />}{" "}
|
||||
{data.rate ? (
|
||||
<>
|
||||
<span className="font-bold">${data.rate}</span> / <span className="">hour</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{data.max_capacity ? <PersonIcon /> : <span></span>}
|
||||
|
||||
<span>{data.max_capacity || <Skeleton />}</span>
|
||||
</div>
|
||||
<p className="flex gap-2 items-center">
|
||||
{data.max_capacity ? <StarIcon /> : <span></span>}
|
||||
{data.rate ? <span>{(Number(data.average_space_rating) || 0).toFixed(1)}</span> : <span></span>}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
|
||||
export default PropertySpaceCard;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export default function PropertySpaceMapImage({ modalOpen, modalImage, closeModal }) {
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="img"
|
||||
className="w-full max-w-4xl transform overflow-hidden align-middle shadow-xl transition-all max-h-[500px]"
|
||||
src={modalImage}
|
||||
></Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
|
||||
import { StarIcon } from "@heroicons/react/24/solid";
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import FavoriteButton from "./FavoriteButton";
|
||||
import PersonIcon from "./icons/PersonIcon";
|
||||
|
||||
import PropertySpaceMapImage from "./PropertySpaceMapImage";
|
||||
|
||||
const PropertySpaceTile = ({ data, forceRender }) => {
|
||||
const [showMap, setShowMap] = useState(false);
|
||||
var amenities = (data.amenities ?? "").split(",");
|
||||
amenities = Array.from(new Set(amenities));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link
|
||||
to={`/property/${data.id}`}
|
||||
state={data}
|
||||
className="mb-[20px] border lg:flex-row flex-col flex lg:gap-[32px] lg:h-[220px] lg:w-[unset] w-full max-w-full my-shadow"
|
||||
>
|
||||
<div
|
||||
className="rounded-sm bg-cover bg-center bg-no-repeat flex flex-col px-[12px] pr-1 pb-[10px] lg:h-full h-[210px] lg:w-[262px] w-full space-tile"
|
||||
style={{ backgroundImage: `url('${data.url}')` }}
|
||||
>
|
||||
<FavoriteButton
|
||||
space_id={data.id}
|
||||
user_property_spaces_id={data.user_property_spaces_id}
|
||||
reRender={forceRender}
|
||||
withLoader={true}
|
||||
|
||||
/>
|
||||
<span className="px-2 py-1 text-white bg-black font-bold rounded-lg text-xs self-start">{data.category || "N/A"}</span>
|
||||
</div>
|
||||
<div className="py-6 md:flex flex-gro justify-between w-full items-end lg:pl-0 pl-4 pr-4 lg:pr-8">
|
||||
<div className="w-[200px]">
|
||||
<h2 className="text-[18px] font-semibold mb-[6px] w-full whitespace-normal md:whitespace-wrap">{data.name}</h2>
|
||||
<p className="text-[#475467] tracking-wider md:truncate mb-1">{data.city}</p>
|
||||
<p className="text-[#475467] tracking-wider md:truncate">{data.country} </p>
|
||||
<div className="lg:mt-[21px] mt-[6px] flex items-end">
|
||||
<p className="mr-[31px]">
|
||||
from: <span className="font-bold">${data.rate}</span>/<span className="">hour</span>
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<PersonIcon />
|
||||
<span>{data.max_capacity}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col w-full md:items-end mt-3 lg:mt-0">
|
||||
<div className="">
|
||||
<p className="flex text-xl gap-2 items-center lg:mb-[9px]">
|
||||
<StarIcon className="w-5" />
|
||||
<strong className="font-semibold">
|
||||
{(Number(data.average_space_rating) || 0).toFixed(1)}
|
||||
{Number(data.space_rating_count) > 0 &&
|
||||
<span className="font-normal">({data.space_rating_count})</span>
|
||||
}
|
||||
</strong>
|
||||
</p>
|
||||
<button
|
||||
className="text-sm underline whitespace-nowrap mt-1 lg:mt-0"
|
||||
target="_blank"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setShowMap(true);
|
||||
}}
|
||||
>
|
||||
(view on map)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 lg:mt-[50px] lg:flex flex-wra max-w-[200px gap-[12px] whitespace-wrap">
|
||||
{amenities.slice(0, 3).map((am, idx) => (
|
||||
<span
|
||||
className="text-[14px] bg-[#F2F4F7] h-fit rounded-[3px] pt-[2px] px-[8px] mr-1 lg:mr-0 pb-[3px] text-[#667085]"
|
||||
key={idx}
|
||||
>
|
||||
{am}
|
||||
</span>
|
||||
))}
|
||||
{amenities.length > 3 ? <span className="text-[14px] bg-[#F2F4F7] rounded-[3px] pt-[2px] px-[8px] pb-[3px] text-[#667085]">+{amenities.length - 3} more</span> : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<PropertySpaceMapImage
|
||||
modalImage={`https://maps.googleapis.com/maps/api/staticmap?center=${data.address_line_1 || ""}, ${data.address_line_2 || ""}, ${data.city || ""}, ${data.country || ""
|
||||
}&zoom=15&size=600x400&maptype=roadmap&markers=color:red|${data.address_line_1 || ""}, ${data.address_line_2 || ""}
|
||||
&key=${import.meta.env.VITE_GOOGLE_API_KEY}`}
|
||||
modalOpen={showMap}
|
||||
closeModal={() => setShowMap(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PropertySpaceTile;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { IMAGE_STATUS } from "@/utils/constants";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import StarIcon from "./icons/StarIcon";
|
||||
|
||||
const ReviewCard = ({ data }) => {
|
||||
const role = localStorage.getItem("role") ?? "customer";
|
||||
return (
|
||||
<div className="flex md:gap-[24px] gap-[16px] text-[#667085] mb-[32px]">
|
||||
<img
|
||||
src={data.customer_photo_approved == IMAGE_STATUS.APPROVED ? data.photo ?? "/default.png" : "/default.png"}
|
||||
className="md:w-[40px] md:h-[40px] w-[30px] h-[30px] object-cover rounded-full"
|
||||
/>
|
||||
<div className="flex-grow">
|
||||
<div className="flex justify-start items-center mb-[8px]">
|
||||
<p>Posted by - </p>
|
||||
<p className="flex gap-2 items-center">
|
||||
<span className="text-black font-semibold">{data?.customer_last_name}{" "}{data?.customer_first_name}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-[8px]">
|
||||
<p>{moment(data.post_date).format("MM/DD/YY")}</p>
|
||||
<p className="flex gap-2 items-center">
|
||||
<StarIcon />
|
||||
<span className="text-black font-semibold">{(Number(data.space_rating) || 0).toFixed(1)}</span>
|
||||
</p>
|
||||
</div>
|
||||
<p className="mb-[16px] md:text-base text-sm">{data.comment}</p>
|
||||
<div className="flex gap-[8px] flex-wrap">
|
||||
{data.hashtags != null &&
|
||||
data.hashtags.split(",").map((tag, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="text-[14px] bg-[#F2F4F7] rounded-[3px] pt-[2px] px-[8px] pb-[3px] whitespace-nowrap"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReviewCard;
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
|
||||
const SearchAutoComplete = ({ selected, setSelected, className, optionsClassName }) => {
|
||||
const [categories, setCategories] = useState([]);
|
||||
const [query, setQuery] = useState("");
|
||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
|
||||
const filteredCategories =
|
||||
query === ""
|
||||
? categories
|
||||
: categories
|
||||
.filter((cat) => cat.category.toLowerCase().replace(/\s+/g, "").includes(query.toLowerCase().replace(/\s+/g, "")))
|
||||
.sort((a, b) => {
|
||||
if (a.category.toLowerCase().indexOf(query.toLowerCase()) > b.category.toLowerCase().indexOf(query.toLowerCase())) {
|
||||
return 1;
|
||||
} else if (a.category.toLowerCase().indexOf(query.toLowerCase()) < b.category.toLowerCase().indexOf(query.toLowerCase())) {
|
||||
return -1;
|
||||
} else {
|
||||
if (a.category > b.category) return 1;
|
||||
else return -1;
|
||||
}
|
||||
});
|
||||
|
||||
async function fetchCategories() {
|
||||
const where = [1];
|
||||
try {
|
||||
const result = await callCustomAPI("spaces", "get", { page: 1, limit: 1000 }, "");
|
||||
if (Array.isArray(result.list)) {
|
||||
setCategories(result.list);
|
||||
}
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchCategories();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`${className ?? ""}`}>
|
||||
<Combobox
|
||||
value={selected}
|
||||
onChange={setSelected}
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="relative w-full cursor-default overflow-hidden text-left focus:outline-none sm:text-sm">
|
||||
<Combobox.Input
|
||||
className="w-full py-1 border-none px-3 text-sm leading-5 text-gray-900 focus:outline-none bg-transparent"
|
||||
displayValue={(cat) => cat.category}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder="Search by category"
|
||||
value={query}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
afterLeave={() => setQuery("")}
|
||||
>
|
||||
<Combobox.Options
|
||||
className={`absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm tiny-scroll ${
|
||||
optionsClassName ?? ""
|
||||
}`}
|
||||
>
|
||||
{filteredCategories.length === 0 && query !== "" ? (
|
||||
<div className="relative cursor-default select-none py-2 px-4 text-gray-700">Other</div>
|
||||
) : (
|
||||
filteredCategories
|
||||
.filter((cat) => cat.category != "")
|
||||
.map((cat) => (
|
||||
<Combobox.Option
|
||||
key={cat.id}
|
||||
className={({ active }) => `relative cursor-default select-none py-3 px-4 ${active ? "bg-teal-600 text-white" : "text-gray-900"}`}
|
||||
value={cat}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<>
|
||||
<span className={`block truncate ${selected ? "font-medium" : "font-normal"}`}>{cat.category}</span>
|
||||
</>
|
||||
)}
|
||||
</Combobox.Option>
|
||||
))
|
||||
)}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Combobox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchAutoComplete;
|
||||
@@ -0,0 +1,118 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useMatch, useNavigate } from "react-router";
|
||||
import { createSearchParams, useSearchParams } from "react-router-dom";
|
||||
import SearchIcon from "./icons/SearchIcon";
|
||||
import ReactTestUtils from "react-dom/test-utils";
|
||||
import { isNotInViewport, sleep } from "@/utils/utils";
|
||||
import { useForm } from "react-hook-form";
|
||||
import CustomLocationAutoCompleteV2 from "../CustomLocationAutoCompleteV2";
|
||||
import CustomComboBox from "../CustomComboBox";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
|
||||
import CustomStaticLocationAutoCompleteV2 from "../CustomStaticLocationAutoCompleteV2";
|
||||
|
||||
const StaticSearchBar = ({ className }) => {
|
||||
const navigate = useNavigate();
|
||||
const inSearchPage = useMatch("/search");
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [showStaticBar, setShowStaticBar] = useState(isNotInViewport("search-bar"));
|
||||
const { pathname } = useLocation();
|
||||
const { state: globalState, dispatch } = useContext(GlobalContext);
|
||||
|
||||
|
||||
|
||||
const categories = globalState.spaceCategories;
|
||||
|
||||
const { handleSubmit, control, setValue } = useForm({ defaultValues: { category: "", location: globalState.location } });
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
setShowStaticBar(isNotInViewport("search-bar"));
|
||||
};
|
||||
window.addEventListener("scroll", onScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setShowStaticBar(false);
|
||||
}, [pathname]);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const searchBar = document.getElementById("search-bar");
|
||||
|
||||
if (inSearchPage && searchBar) {
|
||||
// submit search form
|
||||
if (data.category) searchParams.set("category", selected.category);
|
||||
if (globalState.location) searchParams.set("location", location);
|
||||
setSearchParams(searchParams);
|
||||
await sleep(500);
|
||||
ReactTestUtils.Simulate.submit(searchBar);
|
||||
return;
|
||||
}
|
||||
navigate({
|
||||
pathname: "/search",
|
||||
search: createSearchParams({
|
||||
location: globalState.location ?? "",
|
||||
category: data.category ?? "",
|
||||
}).toString(),
|
||||
});
|
||||
};
|
||||
|
||||
if (!showStaticBar || !["/search", "/"].includes(pathname)) return null;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<form
|
||||
className="my-shadow2 flex w-full max-w-xl items-center rounded-lg rounded-r-pill border bg-white pl-1 md:rounded-r-lg md:pl-4 xl:ml-16 xl:max-w-3xl"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
autoComplete="off"
|
||||
id="top-header-search-bar"
|
||||
>
|
||||
<CustomComboBox
|
||||
control={control}
|
||||
name="category"
|
||||
labelField="category"
|
||||
valueField="category"
|
||||
setValue={(val) => setValue("category", val)}
|
||||
items={categories}
|
||||
containerClassName="relative hidden h-[40px] items-center md:flex md:w-[500px]"
|
||||
className="w-full truncate border-0 text-black focus:outline-none"
|
||||
placeholder="Search by category"
|
||||
/>
|
||||
|
||||
<CustomStaticLocationAutoCompleteV2
|
||||
containerClassName={"flex h-[40px] w-full items-center gap-2 rounded-t-md bg-white px-2 pr-1 md:h-[unset] lg:max-w-[331px] lg:rounded-none lg:py-0"}
|
||||
placeholder="Search by city or zip code"
|
||||
className="border-0 focus:outline-none"
|
||||
// control={control}
|
||||
// name="location"
|
||||
type="static"
|
||||
setValue={(val) => dispatch({
|
||||
type: "SETLOCATION",
|
||||
payload: {
|
||||
location:val
|
||||
},
|
||||
})}
|
||||
suggestionType={["(regions)"]}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="login-btn-gradient hidden w-1/2 items-center justify-center gap-2 rounded-md rounded-tl-none rounded-bl-none py-3 tracking-wide text-white outline-none focus:outline-none md:flex md:w-[unset] md:px-4 lg:w-[unset]"
|
||||
>
|
||||
<SearchIcon className="md:w-[50px]" />
|
||||
<span className="hidden md:inline">Search</span>
|
||||
</button>
|
||||
<button className="login-btn-gradient flex h-10 w-11 items-center justify-center rounded-circle md:hidden">
|
||||
{" "}
|
||||
<MagnifyingGlassIcon className="h-5 w-5 font-semibold text-white" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default StaticSearchBar;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import React from "react";
|
||||
import { Fragment } from "react";
|
||||
import Icon from "../Icons";
|
||||
|
||||
const ThreeDotsMenu = ({ items, direction, disabled, menuClassName, hidden }) => {
|
||||
const filteredItems = items.filter((item) => !item.notShow);
|
||||
return (
|
||||
<Menu
|
||||
as="div"
|
||||
className={`relative max-w-[150px] ${hidden ? "hidden" : ""}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="">
|
||||
<Menu.Button
|
||||
disabled={disabled}
|
||||
className={"inline-flex justify-center px-1 py-3 text-sm font-medium text-gray-700 " + (direction == "vert" ? "rotate-90" : "")}
|
||||
>
|
||||
<Icon type="dots" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className={`absolute right-0 z-10 mt-0 w-40 origin-top-right rounded-md bg-white ${filteredItems.length ? "shadow-lg ring-1" : ""} ring-black ring-opacity-5 focus:outline-none ${
|
||||
menuClassName ?? ""
|
||||
}`}
|
||||
>
|
||||
<div className={filteredItems.length > 0 ? "py-1" : ""}>
|
||||
{filteredItems.map((item, idx) => (
|
||||
<Menu.Item key={idx}>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={item.onClick}
|
||||
className={`${active ? "bg-gray-100 text-gray-900" : "text-gray-700"} w-full text-center inline-flex gap-2 items-center px-4 py-2 text-sm whitespace-nowrap`}
|
||||
>
|
||||
{item?.icon}
|
||||
{item.label}
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThreeDotsMenu;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
const AddIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10 6V10M10 10V14M10 10H6M10 10H14M5.8 19H14.2C15.8802 19 16.7202 19 17.362 18.673C17.9265 18.3854 18.3854 17.9265 18.673 17.362C19 16.7202 19 15.8802 19 14.2V5.8C19 4.11984 19 3.27976 18.673 2.63803C18.3854 2.07354 17.9265 1.6146 17.362 1.32698C16.7202 1 15.8802 1 14.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V14.2C1 15.8802 1 16.7202 1.32698 17.362C1.6146 17.9265 2.07354 18.3854 2.63803 18.673C3.27976 19 4.11984 19 5.8 19Z"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const CalendarIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="20"
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.25 8.16658H0.75M12.6667 0.833252V4.49992M5.33333 0.833252V4.49992M5.15 19.1666H12.85C14.3901 19.1666 15.1602 19.1666 15.7485 18.8669C16.2659 18.6032 16.6866 18.1825 16.9503 17.6651C17.25 17.0768 17.25 16.3067 17.25 14.7666V7.06659C17.25 5.52644 17.25 4.75637 16.9503 4.16811C16.6866 3.65067 16.2659 3.22997 15.7485 2.96632C15.1602 2.66659 14.3901 2.66659 12.85 2.66659H5.15C3.60986 2.66659 2.83978 2.66659 2.25153 2.96632C1.73408 3.22997 1.31338 3.65067 1.04973 4.16811C0.75 4.75637 0.75 5.52644 0.75 7.06658V14.7666C0.75 16.3067 0.75 17.0768 1.04973 17.6651C1.31338 18.1825 1.73408 18.6032 2.25153 18.8669C2.83978 19.1666 3.60986 19.1666 5.15 19.1666Z"
|
||||
stroke="#98A2B3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CalendarIcon;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
const CircleCheckIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.24984 10.2439L8.74984 12.7439L13.7498 7.74386M18.3332 10.2439C18.3332 14.8462 14.6022 18.5772 9.99984 18.5772C5.39746 18.5772 1.6665 14.8462 1.6665 10.2439C1.6665 5.64148 5.39746 1.91052 9.99984 1.91052C14.6022 1.91052 18.3332 5.64148 18.3332 10.2439Z"
|
||||
stroke="#101828"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default CircleCheckIcon;
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
const CopyIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.875 1.5021C7.36871 1.50896 7.06477 1.53827 6.81901 1.66349C6.53677 1.8073 6.3073 2.03677 6.16349 2.31901C6.03827 2.56477 6.00896 2.86871 6.0021 3.375M14.625 1.5021C15.1313 1.50896 15.4352 1.53827 15.681 1.66349C15.9632 1.8073 16.1927 2.03677 16.3365 2.31901C16.4617 2.56477 16.491 2.86871 16.4979 3.37499M16.4979 10.125C16.491 10.6313 16.4617 10.9352 16.3365 11.181C16.1927 11.4632 15.9632 11.6927 15.681 11.8365C15.4352 11.9617 15.1313 11.991 14.625 11.9979M16.5 5.99999V7.49999M10.5 1.5H12M3.9 16.5H9.6C10.4401 16.5 10.8601 16.5 11.181 16.3365C11.4632 16.1927 11.6927 15.9632 11.8365 15.681C12 15.3601 12 14.9401 12 14.1V8.4C12 7.55992 12 7.13988 11.8365 6.81901C11.6927 6.53677 11.4632 6.3073 11.181 6.16349C10.8601 6 10.4401 6 9.6 6H3.9C3.05992 6 2.63988 6 2.31901 6.16349C2.03677 6.3073 1.8073 6.53677 1.66349 6.81901C1.5 7.13988 1.5 7.55992 1.5 8.4V14.1C1.5 14.9401 1.5 15.3601 1.66349 15.681C1.8073 15.9632 2.03677 16.1927 2.31901 16.3365C2.63988 16.5 3.05992 16.5 3.9 16.5Z"
|
||||
stroke="#98A2B3"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default CopyIcon;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
|
||||
const CustomizedIcon = () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="16"
|
||||
viewBox="0 0 14 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.66667 1.33301V3.99967M4.33333 1.33301V3.99967M1 6.66634H13M2.33333 2.66634H11.6667C12.403 2.66634 13 3.26329 13 3.99967V13.333C13 14.0694 12.403 14.6663 11.6667 14.6663H2.33333C1.59695 14.6663 1 14.0694 1 13.333V3.99967C1 3.26329 1.59695 2.66634 2.33333 2.66634Z"
|
||||
stroke="url(#paint0_linear_5194_8527)"
|
||||
strokeWidth="1.75"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_5194_8527"
|
||||
x1="14.392"
|
||||
y1="5.91967"
|
||||
x2="5.85708"
|
||||
y2="12.2098"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#33D4B7" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#0D9895"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default CustomizedIcon;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
const DateTimeIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="26"
|
||||
height="24"
|
||||
viewBox="0 0 26 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M24.4833 13.75L22.1506 11.4167L19.8166 13.75M22.5 12C22.5 17.799 17.799 22.5 12 22.5C6.20101 22.5 1.5 17.799 1.5 12C1.5 6.20101 6.20101 1.5 12 1.5C15.8522 1.5 19.2199 3.57449 21.0469 6.66727M12 6.16667V12L15.5 14.3333"
|
||||
stroke="black"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateTimeIcon;
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
const DownloadIcon = () => (
|
||||
<svg
|
||||
width="17"
|
||||
height="18"
|
||||
viewBox="0 0 17 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M16 11.5V12.5C16 13.9001 16 14.6002 15.7275 15.135C15.4878 15.6054 15.1054 15.9878 14.635 16.2275C14.1002 16.5 13.4001 16.5 12 16.5H5C3.59987 16.5 2.8998 16.5 2.36502 16.2275C1.89462 15.9878 1.51217 15.6054 1.27248 15.135C1 14.6002 1 13.9001 1 12.5V11.5M12.6667 7.33333L8.5 11.5M8.5 11.5L4.33333 7.33333M8.5 11.5V1.5"
|
||||
stroke="#667085"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default DownloadIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
export default function FilterIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="18"
|
||||
height="16"
|
||||
viewBox="0 0 18 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.5 2.45C1.5 2.02996 1.5 1.81994 1.58175 1.65951C1.65365 1.51839 1.76839 1.40365 1.90951 1.33175C2.06994 1.25 2.27996 1.25 2.7 1.25H15.3C15.72 1.25 15.9301 1.25 16.0905 1.33175C16.2316 1.40365 16.3463 1.51839 16.4183 1.65951C16.5 1.81994 16.5 2.02996 16.5 2.45V2.95205C16.5 3.15364 16.5 3.25444 16.4754 3.34817C16.4535 3.43123 16.4176 3.50993 16.3691 3.58082C16.3144 3.66082 16.2383 3.72684 16.0859 3.85887L11.2891 8.01613C11.1367 8.14816 11.0606 8.21418 11.0059 8.29418C10.9574 8.36507 10.9215 8.44377 10.8996 8.52683C10.875 8.62056 10.875 8.72136 10.875 8.92295V12.8438C10.875 12.9905 10.875 13.0638 10.8513 13.1272C10.8304 13.1832 10.7964 13.2334 10.7522 13.2736C10.7021 13.3192 10.634 13.3464 10.4978 13.4009L7.94783 14.4209C7.67218 14.5311 7.53435 14.5863 7.4237 14.5633C7.32695 14.5432 7.24204 14.4857 7.18744 14.4033C7.125 14.3091 7.125 14.1607 7.125 13.8638V8.92295C7.125 8.72136 7.125 8.62056 7.10037 8.52683C7.07854 8.44377 7.0426 8.36507 6.99413 8.29418C6.93943 8.21418 6.86326 8.14816 6.71092 8.01613L1.91408 3.85887C1.76174 3.72684 1.68557 3.66082 1.63087 3.58082C1.5824 3.50993 1.54646 3.43123 1.52463 3.34817C1.5 3.25444 1.5 3.15364 1.5 2.95205V2.45Z"
|
||||
stroke="black"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
const FlexibleIcon = () => (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 18 18"
|
||||
fill="black"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.83366 14.8334H13.4122C14.9665 14.8334 15.7437 14.8334 16.1849 14.5077C16.5697 14.2238 16.8139 13.7883 16.8556 13.3119C16.9033 12.7656 16.4981 12.1024 15.6876 10.7762L14.8568 9.4167M4.10878 7.83772L2.31306 10.7762C1.50255 12.1024 1.0973 12.7656 1.14509 13.3119C1.18676 13.7883 1.431 14.2238 1.81571 14.5077C2.25698 14.8334 3.03415 14.8334 4.58848 14.8334H6.08366M13.0744 6.50003L11.2757 3.55678C10.5234 2.3257 10.1473 1.71016 9.66299 1.50032C9.24021 1.31711 8.76044 1.31711 8.33766 1.50032C7.8534 1.71016 7.47723 2.3257 6.72491 3.55678L5.87505 4.94745M14.0003 3.16676L13.0853 6.58182L9.6702 5.66676M0.666992 8.6651L4.08206 7.75003L4.99712 11.1651M11.917 17.3334L9.41699 14.8334L11.917 12.3334"
|
||||
stroke="#FCFCFD"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default FlexibleIcon;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
|
||||
const GreenCheckIcon = ({ size }) => {
|
||||
return (
|
||||
<svg
|
||||
className="mr-[11px] inline"
|
||||
width={size ?? "28"}
|
||||
height={size ?? "28"}
|
||||
viewBox="0 0 28 28"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.0333 17.0337L8.76663 13.767C8.56663 13.567 8.32218 13.467 8.03329 13.467C7.7444 13.467 7.49996 13.567 7.29996 13.767C7.07774 13.9892 6.96663 14.2503 6.96663 14.5503C6.96663 14.8503 7.06663 15.1003 7.26663 15.3003L11.3333 19.367C11.5111 19.5448 11.7444 19.6337 12.0333 19.6337C12.3222 19.6337 12.5555 19.5448 12.7333 19.367L20.7333 11.367C20.9333 11.167 21.0333 10.9225 21.0333 10.6337C21.0333 10.3448 20.9222 10.0892 20.7 9.86699C20.5 9.66699 20.25 9.56699 19.95 9.56699C19.65 9.56699 19.3888 9.6781 19.1666 9.90033L12.0333 17.0337ZM14 27.3337C12.1111 27.3337 10.3555 26.9948 8.73329 26.317C7.11107 25.6392 5.69996 24.7003 4.49996 23.5003C3.29996 22.3003 2.36107 20.8892 1.68329 19.267C1.00551 17.6448 0.666626 15.8892 0.666626 14.0003C0.666626 12.1337 1.00551 10.3892 1.68329 8.76699C2.36107 7.14477 3.29996 5.73366 4.49996 4.53366C5.69996 3.33366 7.11107 2.38921 8.73329 1.70033C10.3555 1.01144 12.1111 0.666992 14 0.666992C15.8666 0.666992 17.6111 1.01144 19.2333 1.70033C20.8555 2.38921 22.2666 3.33366 23.4666 4.53366C24.6666 5.73366 25.6111 7.14477 26.3 8.76699C26.9888 10.3892 27.3333 12.1337 27.3333 14.0003C27.3333 15.8892 26.9888 17.6448 26.3 19.267C25.6111 20.8892 24.6666 22.3003 23.4666 23.5003C22.2666 24.7003 20.8555 25.6392 19.2333 26.317C17.6111 26.9948 15.8666 27.3337 14 27.3337Z"
|
||||
fill="url(#paint0_linear_2839_24747)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_2839_24747"
|
||||
x1="30.4266"
|
||||
y1="9.84033"
|
||||
x2="12.9066"
|
||||
y2="24.187"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#33D4B7" />
|
||||
<stop
|
||||
offset="1"
|
||||
stopColor="#0D9895"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default GreenCheckIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const Hamburger = ({ stroke }) => (
|
||||
<svg
|
||||
width="24"
|
||||
height="17"
|
||||
viewBox="0 0 24 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.5 8.5H22.5M1.5 1.5H22.5M1.5 15.5H22.5"
|
||||
stroke={stroke ?? "white"}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Hamburger;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
const HeartIcon = ({ isFav, stroke, favColor }) => {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="17"
|
||||
viewBox="0 0 20 17"
|
||||
fill={isFav ? "#33d4b7" : "none"}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.99413 2.77985C8.328 0.832 5.54963 0.308035 3.46208 2.09168C1.37454 3.87532 1.08064 6.85748 2.72 8.967C4.08302 10.7209 8.20798 14.4201 9.55992 15.6174C9.71117 15.7513 9.7868 15.8183 9.87502 15.8446C9.95201 15.8676 10.0363 15.8676 10.1132 15.8446C10.2015 15.8183 10.2771 15.7513 10.4283 15.6174C11.7803 14.4201 15.9052 10.7209 17.2683 8.967C18.9076 6.85748 18.6496 3.85656 16.5262 2.09168C14.4028 0.326798 11.6603 0.832 9.99413 2.77985Z"
|
||||
stroke={"#33d4b7"}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeartIcon;
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
|
||||
const LocationIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="20"
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.99996 10.9166C10.5187 10.9166 11.75 9.68537 11.75 8.16658C11.75 6.6478 10.5187 5.41659 8.99996 5.41659C7.48118 5.41659 6.24996 6.6478 6.24996 8.16658C6.24996 9.68537 7.48118 10.9166 8.99996 10.9166Z"
|
||||
stroke="#98A2B3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8.99996 19.1666C12.6666 15.4999 16.3333 12.2167 16.3333 8.16658C16.3333 4.1165 13.05 0.833252 8.99996 0.833252C4.94987 0.833252 1.66663 4.1165 1.66663 8.16658C1.66663 12.2167 5.33329 15.4999 8.99996 19.1666Z"
|
||||
stroke="#98A2B3"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default LocationIcon;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
const LogoIcon = ({ fill }) => (
|
||||
<svg
|
||||
width="69"
|
||||
height="20"
|
||||
viewBox="0 0 69 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.4 15.03C6.888 15.03 5.56267 14.75 4.424 14.19C3.28533 13.63 2.39867 12.8087 1.764 11.726C1.148 10.6433 0.84 9.33667 0.84 7.806C0.84 6.35 1.148 5.08067 1.764 3.998C2.39867 2.91533 3.276 2.07533 4.396 1.478C5.516 0.861999 6.832 0.553999 8.344 0.553999C9.78133 0.553999 11.0413 0.815332 12.124 1.338C13.2253 1.842 14.0747 2.58867 14.672 3.578C15.288 4.54867 15.596 5.74333 15.596 7.162C15.596 7.40467 15.5867 7.638 15.568 7.862C15.5493 8.06733 15.5213 8.27267 15.484 8.478H2.772V6.798H13.496L12.712 7.834C12.7307 7.64733 12.74 7.47 12.74 7.302C12.74 7.11533 12.74 6.92867 12.74 6.742C12.74 5.454 12.3667 4.502 11.62 3.886C10.892 3.25133 9.78133 2.934 8.288 2.934C6.62667 2.934 5.44133 3.31667 4.732 4.082C4.02267 4.84733 3.668 5.94867 3.668 7.386V8.114C3.668 9.57 4.02267 10.6807 4.732 11.446C5.44133 12.2113 6.636 12.594 8.316 12.594C9.772 12.594 10.8173 12.37 11.452 11.922C12.1053 11.4553 12.432 10.8113 12.432 9.99V9.766H15.456V10.018C15.456 11.0073 15.148 11.8847 14.532 12.65C13.9347 13.3967 13.104 13.9847 12.04 14.414C10.9947 14.8247 9.78133 15.03 8.4 15.03ZM22.2849 14.75H19.2609V0.834H22.0329V4.782L22.2849 4.922V14.75ZM22.2849 6.938H21.6129V4.586H22.2289C22.3596 3.82067 22.6209 3.13933 23.0129 2.542C23.4049 1.926 23.9276 1.44067 24.5809 1.086C25.2529 0.731332 26.0649 0.553999 27.0169 0.553999C28.0809 0.553999 28.9489 0.777999 29.6209 1.226C30.2929 1.674 30.7783 2.27133 31.0769 3.018C31.3943 3.76467 31.5529 4.57667 31.5529 5.454V7.274H28.5569V6.042C28.5569 5.034 28.3329 4.29667 27.8849 3.83C27.4369 3.36333 26.6903 3.13 25.6449 3.13C24.4503 3.13 23.5916 3.45667 23.0689 4.11C22.5463 4.76333 22.2849 5.706 22.2849 6.938ZM42.1603 19.79C40.779 19.79 39.5657 19.5753 38.5203 19.146C37.475 18.7353 36.6537 18.138 36.0563 17.354C35.4777 16.57 35.1883 15.618 35.1883 14.498H38.1843C38.1843 15.17 38.3243 15.7113 38.6043 16.122C38.8843 16.5327 39.323 16.822 39.9203 16.99C40.5363 17.1767 41.339 17.27 42.3283 17.27C43.3923 17.27 44.2323 17.1487 44.8483 16.906C45.483 16.682 45.9403 16.2807 46.2203 15.702C46.5003 15.1233 46.6403 14.3207 46.6403 13.294V5.146L46.8643 4.95V0.834H49.6363V13.126C49.6363 14.6753 49.3283 15.9353 48.7123 16.906C48.0963 17.8953 47.2283 18.6233 46.1083 19.09C44.9883 19.5567 43.6723 19.79 42.1603 19.79ZM40.9003 13.49C39.519 13.49 38.3337 13.2193 37.3443 12.678C36.3737 12.1367 35.6177 11.3807 35.0763 10.41C34.5537 9.43933 34.2923 8.31 34.2923 7.022C34.2923 5.734 34.563 4.60467 35.1043 3.634C35.6643 2.66333 36.4483 1.90733 37.4563 1.366C38.483 0.824666 39.6963 0.553999 41.0963 0.553999C42.5523 0.553999 43.7843 0.880666 44.7923 1.534C45.819 2.16867 46.4537 3.09267 46.6963 4.306H47.3403L47.1723 6.742H46.6403C46.6403 5.958 46.4537 5.30467 46.0803 4.782C45.707 4.24067 45.175 3.83933 44.4843 3.578C43.7937 3.31667 42.9537 3.186 41.9643 3.186C41.0123 3.186 40.1817 3.30733 39.4723 3.55C38.7817 3.79267 38.2497 4.194 37.8763 4.754C37.5217 5.29533 37.3443 6.05133 37.3443 7.022C37.3443 7.974 37.5217 8.73 37.8763 9.29C38.231 9.85 38.7443 10.2607 39.4163 10.522C40.107 10.7647 40.9283 10.886 41.8803 10.886C43.411 10.886 44.587 10.5687 45.4083 9.934C46.2297 9.29933 46.6403 8.366 46.6403 7.134H47.1723V9.878H46.4443C46.2017 10.942 45.6137 11.81 44.6803 12.482C43.747 13.154 42.487 13.49 40.9003 13.49ZM60.9464 15.03C59.4157 15.03 58.0624 14.7313 56.8864 14.134C55.729 13.518 54.8237 12.6687 54.1704 11.586C53.5357 10.4847 53.2184 9.22467 53.2184 7.806C53.2184 6.35 53.5357 5.08067 54.1704 3.998C54.8237 2.91533 55.729 2.07533 56.8864 1.478C58.0624 0.861999 59.4157 0.553999 60.9464 0.553999C62.5144 0.553999 63.877 0.861999 65.0344 1.478C66.1917 2.07533 67.0877 2.91533 67.7224 3.998C68.3757 5.08067 68.7024 6.35 68.7024 7.806C68.7024 9.22467 68.3757 10.4847 67.7224 11.586C67.0877 12.6687 66.1917 13.518 65.0344 14.134C63.877 14.7313 62.5144 15.03 60.9464 15.03ZM60.9464 12.342C62.645 12.342 63.8677 11.9593 64.6144 11.194C65.361 10.41 65.7344 9.28067 65.7344 7.806C65.7344 6.33133 65.361 5.202 64.6144 4.418C63.8677 3.61533 62.645 3.214 60.9464 3.214C59.2664 3.214 58.053 3.61533 57.3064 4.418C56.5597 5.202 56.1864 6.33133 56.1864 7.806C56.1864 9.28067 56.5597 10.41 57.3064 11.194C58.053 11.9593 59.2664 12.342 60.9464 12.342Z"
|
||||
fill={fill ?? "#FCFCFD"}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default LogoIcon;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
|
||||
export default function LogoutIcon() {
|
||||
return (
|
||||
<svg
|
||||
fill="#000000"
|
||||
height="20px"
|
||||
width="20px"
|
||||
version="1.1"
|
||||
viewBox="0 0 384.971 384.971"
|
||||
>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d="M180.455,360.91H24.061V24.061h156.394c6.641,0,12.03-5.39,12.03-12.03s-5.39-12.03-12.03-12.03H12.03
|
||||
C5.39,0.001,0,5.39,0,12.031V372.94c0,6.641,5.39,12.03,12.03,12.03h168.424c6.641,0,12.03-5.39,12.03-12.03
|
||||
C192.485,366.299,187.095,360.91,180.455,360.91z"
|
||||
/>
|
||||
<path
|
||||
d="M381.481,184.088l-83.009-84.2c-4.704-4.752-12.319-4.74-17.011,0c-4.704,4.74-4.704,12.439,0,17.179l62.558,63.46H96.279
|
||||
c-6.641,0-12.03,5.438-12.03,12.151c0,6.713,5.39,12.151,12.03,12.151h247.74l-62.558,63.46c-4.704,4.752-4.704,12.439,0,17.179
|
||||
c4.704,4.752,12.319,4.752,17.011,0l82.997-84.2C386.113,196.588,386.161,188.756,381.481,184.088z"
|
||||
/>
|
||||
</g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
|
||||
const NextIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="8"
|
||||
height="12"
|
||||
viewBox="0 0 8 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="mx-auto next-icon"
|
||||
>
|
||||
<path
|
||||
d="M1.5 11L6.5 6L1.5 1"
|
||||
stroke="#667085"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default NextIcon;
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
const NotVerifiedIcon = ({ stroke }) => (
|
||||
<svg
|
||||
width="20"
|
||||
height="19"
|
||||
viewBox="0 0 20 19"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.10817 3.60866L15.8915 15.392M18.3332 9.50033C18.3332 14.1027 14.6022 17.8337 9.99984 17.8337C5.39746 17.8337 1.6665 14.1027 1.6665 9.50033C1.6665 4.89795 5.39746 1.16699 9.99984 1.16699C14.6022 1.16699 18.3332 4.89795 18.3332 9.50033Z"
|
||||
stroke={stroke ?? "white"}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default NotVerifiedIcon;
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
const NoteIcon = ({ width }) => (
|
||||
<svg
|
||||
width={width ?? "20"}
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.99935 13.3337V10.0003M9.99935 6.66699H10.0077M18.3327 10.0003C18.3327 14.6027 14.6017 18.3337 9.99935 18.3337C5.39698 18.3337 1.66602 14.6027 1.66602 10.0003C1.66602 5.39795 5.39698 1.66699 9.99935 1.66699C14.6017 1.66699 18.3327 5.39795 18.3327 10.0003Z"
|
||||
stroke="#475467"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default NoteIcon;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
const PencilIcon = ({ stroke }) => {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill={"none"}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.66675 14.3329L5.36626 12.91C5.60289 12.819 5.7212 12.7735 5.83189 12.7141C5.93022 12.6613 6.02395 12.6004 6.11211 12.532C6.21136 12.4549 6.301 12.3653 6.48027 12.186L14.0001 4.66622C14.7365 3.92984 14.7365 2.73593 14.0001 1.99955C13.2637 1.26317 12.0698 1.26317 11.3334 1.99955L3.8136 9.51936C3.63433 9.69864 3.5447 9.78827 3.46768 9.88752C3.39926 9.97569 3.33835 10.0694 3.28557 10.1677C3.22615 10.2784 3.18065 10.3967 3.08964 10.6334L1.66675 14.3329ZM1.66675 14.3329L3.03883 10.7655C3.13701 10.5102 3.1861 10.3826 3.27031 10.3241C3.34389 10.273 3.43495 10.2537 3.52295 10.2705C3.62364 10.2898 3.72034 10.3865 3.91374 10.5799L5.4198 12.0859C5.6132 12.2793 5.7099 12.376 5.72912 12.4767C5.74593 12.5647 5.72661 12.6558 5.67551 12.7293C5.61705 12.8135 5.48941 12.8626 5.23413 12.9608L1.66675 14.3329Z"
|
||||
stroke={stroke ?? "#101828"}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PencilIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const PeopleIcon = () => (
|
||||
<svg
|
||||
width="22"
|
||||
height="20"
|
||||
viewBox="0 0 22 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M21 19V17C21 15.1362 19.7252 13.5701 18 13.126M14.5 1.29076C15.9659 1.88415 17 3.32131 17 5C17 6.67869 15.9659 8.11585 14.5 8.70924M16 19C16 17.1362 16 16.2044 15.6955 15.4693C15.2895 14.4892 14.5108 13.7105 13.5307 13.3045C12.7956 13 11.8638 13 10 13H7C5.13623 13 4.20435 13 3.46927 13.3045C2.48915 13.7105 1.71046 14.4892 1.30448 15.4693C1 16.2044 1 17.1362 1 19M12.5 5C12.5 7.20914 10.7091 9 8.5 9C6.29086 9 4.5 7.20914 4.5 5C4.5 2.79086 6.29086 1 8.5 1C10.7091 1 12.5 2.79086 12.5 5Z"
|
||||
stroke="#98A2B3"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default PeopleIcon;
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
|
||||
const PersonIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="13"
|
||||
height="16"
|
||||
viewBox="0 0 13 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.8334 14.25C11.8334 13.2809 11.8334 12.7963 11.7186 12.402C11.4601 11.5142 10.7931 10.8195 9.94084 10.5502C9.56231 10.4306 9.09712 10.4306 8.16675 10.4306H4.83342C3.90304 10.4306 3.43785 10.4306 3.05932 10.5502C2.20705 10.8195 1.54011 11.5142 1.28157 12.402C1.16675 12.7963 1.16675 13.2809 1.16675 14.25M9.50008 4.875C9.50008 6.60089 8.15694 8 6.50008 8C4.84323 8 3.50008 6.60089 3.50008 4.875C3.50008 3.14911 4.84323 1.75 6.50008 1.75C8.15694 1.75 9.50008 3.14911 9.50008 4.875Z"
|
||||
stroke="#475467"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersonIcon;
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
const PictureIcon = () => (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.18717 12.3543H12.8809L9.87884 8.34391L7.28926 11.6897L5.45592 9.32933L3.18717 12.3543ZM1.85801 15.7918C1.39967 15.7918 1.01009 15.6314 0.689258 15.3106C0.368425 14.9897 0.208008 14.6002 0.208008 14.1418V1.8585C0.208008 1.40016 0.368425 1.01058 0.689258 0.689746C1.01009 0.368913 1.39967 0.208496 1.85801 0.208496H14.1413C14.5997 0.208496 14.9893 0.368913 15.3101 0.689746C15.6309 1.01058 15.7913 1.40016 15.7913 1.8585V14.1418C15.7913 14.6002 15.6309 14.9897 15.3101 15.3106C14.9893 15.6314 14.5997 15.7918 14.1413 15.7918H1.85801ZM1.85801 14.4168H14.1413C14.2025 14.4168 14.2636 14.3863 14.3247 14.3252C14.3858 14.2641 14.4163 14.2029 14.4163 14.1418V1.8585C14.4163 1.79739 14.3858 1.73627 14.3247 1.67516C14.2636 1.61405 14.2025 1.5835 14.1413 1.5835H1.85801C1.7969 1.5835 1.73579 1.61405 1.67467 1.67516C1.61356 1.73627 1.58301 1.79739 1.58301 1.8585V14.1418C1.58301 14.2029 1.61356 14.2641 1.67467 14.3252C1.73579 14.3863 1.7969 14.4168 1.85801 14.4168ZM1.58301 1.5835C1.58301 1.5835 1.58301 1.61038 1.58301 1.66416C1.58301 1.71733 1.58301 1.78211 1.58301 1.8585V14.1418C1.58301 14.2182 1.58301 14.2833 1.58301 14.3371C1.58301 14.3902 1.58301 14.4168 1.58301 14.4168C1.58301 14.4168 1.58301 14.3902 1.58301 14.3371C1.58301 14.2833 1.58301 14.2182 1.58301 14.1418V1.8585C1.58301 1.78211 1.58301 1.71733 1.58301 1.66416C1.58301 1.61038 1.58301 1.5835 1.58301 1.5835Z"
|
||||
fill="#8E8E93"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default PictureIcon;
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
const PrevIcon = () => (
|
||||
<svg
|
||||
width="8"
|
||||
height="12"
|
||||
viewBox="0 0 8 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="mx-auto prev-icon"
|
||||
>
|
||||
<path
|
||||
d="M6.5 11L1.5 6L6.5 1"
|
||||
stroke="#667085"
|
||||
strokeWidth="1.66667"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default PrevIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const RecurringIcon = () => (
|
||||
<svg
|
||||
width="14"
|
||||
height="16"
|
||||
viewBox="0 0 14 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10.3333 0.666992L13 3.33366M13 3.33366L10.3333 6.00033M13 3.33366H3.66667C2.95942 3.33366 2.28115 3.61461 1.78105 4.11471C1.28095 4.6148 1 5.29308 1 6.00033V7.33366M3.66667 15.3337L1 12.667M1 12.667L3.66667 10.0003M1 12.667H10.3333C11.0406 12.667 11.7189 12.386 12.219 11.8859C12.719 11.3858 13 10.7076 13 10.0003V8.66699"
|
||||
stroke="#98A2B3"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default RecurringIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const ResetIcon = () => (
|
||||
<svg
|
||||
width="17"
|
||||
height="16"
|
||||
viewBox="0 0 17 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.5 6.5C1.5 6.5 3.00374 4.45116 4.22538 3.22868C5.44702 2.0062 7.1352 1.25 9 1.25C12.7279 1.25 15.75 4.27208 15.75 8C15.75 11.7279 12.7279 14.75 9 14.75C5.92268 14.75 3.32633 12.6907 2.51382 9.875M1.5 6.5V2M1.5 6.5H6"
|
||||
stroke="#98A2B3"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default ResetIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const SearchIcon = ({ stroke, className }) => (
|
||||
<svg
|
||||
className={className ?? ""}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.75 14.75L10.25 10.25M11.75 6.5C11.75 9.39949 9.39949 11.75 6.5 11.75C3.60051 11.75 1.25 9.39949 1.25 6.5C1.25 3.60051 3.60051 1.25 6.5 1.25C9.39949 1.25 11.75 3.60051 11.75 6.5Z"
|
||||
stroke={stroke ?? "#FCFCFD"}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default SearchIcon;
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
|
||||
const SecurityIcon = () => (
|
||||
<svg
|
||||
width="16"
|
||||
height="20"
|
||||
viewBox="0 0 16 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.99992 2.08363V17.917M14.6666 10.0003C14.6666 14.0907 10.205 17.0656 8.58158 18.0127C8.39708 18.1203 8.30484 18.1741 8.17465 18.2021C8.07362 18.2237 7.92622 18.2237 7.82519 18.2021C7.695 18.1741 7.60275 18.1203 7.41826 18.0127C5.79489 17.0656 1.33325 14.0907 1.33325 10.0003V6.01497C1.33325 5.34871 1.33325 5.01558 1.44222 4.72922C1.53848 4.47625 1.6949 4.25053 1.89797 4.07157C2.12783 3.869 2.43975 3.75203 3.06359 3.51809L7.53175 1.84253C7.705 1.77756 7.79162 1.74508 7.88074 1.7322C7.95978 1.72078 8.04006 1.72078 8.1191 1.7322C8.20821 1.74508 8.29484 1.77756 8.46808 1.84253L12.9362 3.51809C13.5601 3.75203 13.872 3.869 14.1019 4.07157C14.3049 4.25053 14.4614 4.47625 14.5576 4.72922C14.6666 5.01558 14.6666 5.34871 14.6666 6.01497V10.0003Z"
|
||||
stroke="url(#paint0_linear_4734_2207)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_4734_2207"
|
||||
x1="16.2133"
|
||||
y1="7.3978"
|
||||
x2="6.03765"
|
||||
y2="14.1333"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#33D4B7" />
|
||||
<stop
|
||||
offset="1"
|
||||
stopColor="#0D9895"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
export default SecurityIcon;
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
const SmileIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.117 7.89984C12.4684 7.89984 12.7547 7.78923 12.9759 7.568C13.1977 7.34617 13.3087 7.05956 13.3087 6.70817C13.3087 6.35678 13.1977 6.07017 12.9759 5.84834C12.7547 5.62712 12.4684 5.5165 12.117 5.5165C11.7809 5.5165 11.4982 5.62712 11.2691 5.84834C11.0399 6.07017 10.9253 6.35678 10.9253 6.70817C10.9253 7.05956 11.0362 7.34617 11.2581 7.568C11.4793 7.78923 11.7656 7.89984 12.117 7.89984ZM5.88366 7.89984C6.21977 7.89984 6.50241 7.78923 6.73158 7.568C6.96074 7.34617 7.07533 7.05956 7.07533 6.70817C7.07533 6.35678 6.96472 6.07017 6.74349 5.84834C6.52166 5.62712 6.23505 5.5165 5.88366 5.5165C5.53227 5.5165 5.24597 5.62712 5.02474 5.84834C4.80291 6.07017 4.69199 6.35678 4.69199 6.70817C4.69199 7.05956 4.80291 7.34617 5.02474 7.568C5.24597 7.78923 5.53227 7.89984 5.88366 7.89984ZM9.00033 13.7665C9.94755 13.7665 10.8147 13.5068 11.6018 12.9873C12.3883 12.4679 12.9802 11.7498 13.3774 10.8332H4.62324C5.02046 11.7498 5.61263 12.4679 6.39974 12.9873C7.18624 13.5068 8.0531 13.7665 9.00033 13.7665ZM9.00033 17.7082C7.79338 17.7082 6.65916 17.479 5.59766 17.0207C4.53555 16.5623 3.61491 15.9436 2.83574 15.1644C2.05658 14.3853 1.43783 13.4646 0.979492 12.4025C0.521159 11.341 0.291992 10.2068 0.291992 8.99984C0.291992 7.79289 0.521159 6.65837 0.979492 5.59625C1.43783 4.53475 2.05658 3.61442 2.83574 2.83525C3.61491 2.05609 4.53555 1.43734 5.59766 0.979004C6.65916 0.520671 7.79338 0.291504 9.00033 0.291504C10.2073 0.291504 11.3418 0.520671 12.4039 0.979004C13.4654 1.43734 14.3857 2.05609 15.1649 2.83525C15.9441 3.61442 16.5628 4.53475 17.0212 5.59625C17.4795 6.65837 17.7087 7.79289 17.7087 8.99984C17.7087 10.2068 17.4795 11.341 17.0212 12.4025C16.5628 13.4646 15.9441 14.3853 15.1649 15.1644C14.3857 15.9436 13.4654 16.5623 12.4039 17.0207C11.3418 17.479 10.2073 17.7082 9.00033 17.7082ZM9.00033 16.3332C11.0323 16.3332 12.7626 15.6191 14.1914 14.1909C15.6196 12.7621 16.3337 11.0318 16.3337 8.99984C16.3337 6.96789 15.6196 5.23753 14.1914 3.80875C12.7626 2.38059 11.0323 1.6665 9.00033 1.6665C6.96838 1.6665 5.23833 2.38059 3.81016 3.80875C2.38138 5.23753 1.66699 6.96789 1.66699 8.99984C1.66699 11.0318 2.38138 12.7621 3.81016 14.1909C5.23833 15.6191 6.96838 16.3332 9.00033 16.3332Z"
|
||||
fill="#8E8E93"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default SmileIcon;
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
|
||||
const StarIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="17"
|
||||
viewBox="0 0 18 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.00001 14.275L4.85001 16.775C4.66668 16.8917 4.47501 16.9417 4.27501 16.925C4.07501 16.9083 3.90001 16.8417 3.75001 16.725C3.60001 16.6083 3.48334 16.4627 3.40001 16.288C3.31668 16.1127 3.30001 15.9167 3.35001 15.7L4.45001 10.975L0.775009 7.8C0.608343 7.65 0.504343 7.479 0.463009 7.287C0.421009 7.09566 0.433343 6.90833 0.500009 6.725C0.566676 6.54166 0.666676 6.39166 0.800009 6.275C0.933343 6.15833 1.11668 6.08333 1.35001 6.05L6.20001 5.625L8.07501 1.175C8.15834 0.974997 8.28768 0.824997 8.46301 0.724997C8.63768 0.624997 8.81668 0.574997 9.00001 0.574997C9.18334 0.574997 9.36268 0.624997 9.53801 0.724997C9.71268 0.824997 9.84168 0.974997 9.92501 1.175L11.8 5.625L16.65 6.05C16.8833 6.08333 17.0667 6.15833 17.2 6.275C17.3333 6.39166 17.4333 6.54166 17.5 6.725C17.5667 6.90833 17.5793 7.09566 17.538 7.287C17.496 7.479 17.3917 7.65 17.225 7.8L13.55 10.975L14.65 15.7C14.7 15.9167 14.6833 16.1127 14.6 16.288C14.5167 16.4627 14.4 16.6083 14.25 16.725C14.1 16.8417 13.925 16.9083 13.725 16.925C13.525 16.9417 13.3333 16.8917 13.15 16.775L9.00001 14.275Z"
|
||||
fill="url(#paint0_linear_5394_12703)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_5394_12703"
|
||||
x1="19.548"
|
||||
y1="6.20047"
|
||||
x2="8.71717"
|
||||
y2="15.4871"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#33D4B7" />
|
||||
<stop
|
||||
offset="1"
|
||||
stopColor="#0D9895"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default StarIcon;
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const TrashIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="20"
|
||||
viewBox="0 0 18 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.3333 5.00033V4.33366C12.3333 3.40024 12.3333 2.93353 12.1517 2.57701C11.9919 2.2634 11.7369 2.00844 11.4233 1.84865C11.0668 1.66699 10.6001 1.66699 9.66667 1.66699H8.33333C7.39991 1.66699 6.9332 1.66699 6.57668 1.84865C6.26308 2.00844 6.00811 2.2634 5.84832 2.57701C5.66667 2.93353 5.66667 3.40024 5.66667 4.33366V5.00033M1.5 5.00033H16.5M14.8333 5.00033V14.3337C14.8333 15.7338 14.8333 16.4339 14.5608 16.9686C14.3212 17.439 13.9387 17.8215 13.4683 18.0612C12.9335 18.3337 12.2335 18.3337 10.8333 18.3337H7.16667C5.76654 18.3337 5.06647 18.3337 4.53169 18.0612C4.06129 17.8215 3.67883 17.439 3.43915 16.9686C3.16667 16.4339 3.16667 15.7338 3.16667 14.3337V5.00033"
|
||||
stroke="#667085"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default TrashIcon;
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
const TrustedIcon = ({ stroke }) => (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 18 18"
|
||||
fill="black"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.50033 8.99996L8.16699 10.6666L11.917 6.91663M6.66858 16.1674C6.9417 16.1312 7.2176 16.2053 7.43518 16.3729L8.43787 17.1423C8.76933 17.397 9.2304 17.397 9.56093 17.1423L10.6016 16.3433C10.796 16.1942 11.0414 16.1285 11.2839 16.1609L12.5857 16.3322C12.9995 16.3868 13.3986 16.1562 13.5587 15.7701L14.0596 14.5591C14.1531 14.3322 14.3328 14.1526 14.5596 14.0591L15.7706 13.5582C16.1567 13.3989 16.3872 12.9989 16.3326 12.585L16.1678 11.3305C16.1317 11.0573 16.2057 10.7814 16.3733 10.5638L17.1427 9.56106C17.3973 9.22958 17.3973 8.76848 17.1427 8.43794L16.3437 7.39723C16.1946 7.20279 16.1289 6.95742 16.1613 6.71484L16.3326 5.41302C16.3872 4.99915 16.1567 4.60008 15.7706 4.4399L14.5596 3.93899C14.3328 3.84547 14.1531 3.66585 14.0596 3.439L13.5587 2.22793C13.3995 1.84182 12.9995 1.61128 12.5857 1.6659L11.2839 1.8372C11.0414 1.87053 10.796 1.80479 10.6025 1.65664L9.56185 0.857593C9.2304 0.60297 8.76933 0.60297 8.4388 0.857593L7.39815 1.65664C7.20372 1.80479 6.95837 1.87053 6.71579 1.83905L5.41405 1.66776C5.0002 1.61313 4.60116 1.84368 4.44098 2.22978L3.94102 3.44085C3.84659 3.66677 3.66697 3.8464 3.44107 3.94084L2.23006 4.44083C1.84398 4.60101 1.61344 5.00007 1.66807 5.41395L1.83935 6.71576C1.87083 6.95835 1.80509 7.20371 1.65696 7.39723L0.857949 8.43794C0.60334 8.76941 0.60334 9.23051 0.857949 9.56106L1.65696 10.6018C1.80602 10.7962 1.87175 11.0416 1.83935 11.2842L1.66807 12.586C1.61344 12.9998 1.84398 13.3989 2.23006 13.5591L3.44107 14.06C3.6679 14.1535 3.84751 14.3331 3.94102 14.56L4.44191 15.7711C4.60116 16.1572 5.00112 16.3877 5.41498 16.3331L6.66858 16.1674Z"
|
||||
stroke={stroke ?? "#FCFCFD"}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default TrustedIcon;
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
|
||||
const WelcomeIcon = () => (
|
||||
<svg
|
||||
width="19"
|
||||
height="20"
|
||||
viewBox="0 0 19 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M10 1.875V3.95833M10 15V18.3333M4.79167 10H1.875M17.7083 10H16.4583"
|
||||
stroke="url(#paint0_linear_5248_9363)"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_5248_9363"
|
||||
x1="19.545"
|
||||
y1="7.53667"
|
||||
x2="8.8218"
|
||||
y2="15.9842"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#33D4B7" />
|
||||
<stop
|
||||
offset="1"
|
||||
stopColor="#0D9895"
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default WelcomeIcon;
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
|
||||
const WhitePlusIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.31283 13.354H9.68783V9.68734H13.3545V8.31234H9.68783V4.64567H8.31283V8.31234H4.64616V9.68734H8.31283V13.354ZM9.00033 17.7082C7.79338 17.7082 6.65916 17.479 5.59766 17.0207C4.53555 16.5623 3.61491 15.9436 2.83574 15.1644C2.05658 14.3853 1.43783 13.4646 0.979492 12.4025C0.521159 11.341 0.291992 10.2068 0.291992 8.99984C0.291992 7.79289 0.521159 6.65837 0.979492 5.59625C1.43783 4.53475 2.05658 3.61442 2.83574 2.83525C3.61491 2.05609 4.53555 1.43734 5.59766 0.979004C6.65916 0.520671 7.79338 0.291504 9.00033 0.291504C10.2073 0.291504 11.3418 0.520671 12.4039 0.979004C13.4654 1.43734 14.3857 2.05609 15.1649 2.83525C15.9441 3.61442 16.5628 4.53475 17.0212 5.59625C17.4795 6.65837 17.7087 7.79289 17.7087 8.99984C17.7087 10.2068 17.4795 11.341 17.0212 12.4025C16.5628 13.4646 15.9441 14.3853 15.1649 15.1644C14.3857 15.9436 13.4654 16.5623 12.4039 17.0207C11.3418 17.479 10.2073 17.7082 9.00033 17.7082ZM9.00033 16.3332C11.0323 16.3332 12.7626 15.6191 14.1914 14.1909C15.6196 12.7621 16.3337 11.0318 16.3337 8.99984C16.3337 6.96789 15.6196 5.23753 14.1914 3.80875C12.7626 2.38059 11.0323 1.6665 9.00033 1.6665C6.96838 1.6665 5.23833 2.38059 3.81016 3.80875C2.38138 5.23753 1.66699 6.96789 1.66699 8.99984C1.66699 11.0318 2.38138 12.7621 3.81016 14.1909C5.23833 15.6191 6.96838 16.3332 9.00033 16.3332Z"
|
||||
fill="#8E8E93"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export default WhitePlusIcon;
|
||||
@@ -0,0 +1,5 @@
|
||||
import LoadingButton from "./LoadingButton";
|
||||
import AddOnCounter from "./AddOnCounter";
|
||||
import FavoriteButton from "./FavoriteButton";
|
||||
|
||||
export { LoadingButton, AddOnCounter, FavoriteButton };
|
||||
Reference in New Issue
Block a user