Compare commits
8 Commits
05cd9c8318
...
7cf8caf9da
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cf8caf9da | |||
| 9867509259 | |||
| 084a8f2eb8 | |||
| 7ec8dea0bb | |||
| fff2ac4cde | |||
| 201d9decb3 | |||
| 5046be7584 | |||
| e51d369ba1 |
@@ -9,28 +9,45 @@ export default function ConfirmationModal() {
|
|||||||
if (!state.confirmation) return null;
|
if (!state.confirmation) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"popup-container z-100 flex items-center justify-center normal-case"}>
|
<div
|
||||||
|
className={
|
||||||
|
"popup-container z-100 flex items-center justify-center normal-case"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`${state.confirmation ? "pop-in" : "pop-out"} w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
className={`${
|
||||||
|
state.confirmation ? "pop-in" : "pop-out"
|
||||||
|
} w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<h2 className="mb-4 text-3xl font-semibold">
|
<h2 className='mb-4 text-3xl font-semibold'>
|
||||||
<GreenCheckIcon />
|
<GreenCheckIcon />
|
||||||
{state.confirmationHeading}
|
{state.confirmationHeading}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-4 text-sm text-gray-500">{state.confirmationMsg}</p>
|
<p className='mb-4 text-sm text-gray-500'>{state.confirmationMsg}</p>
|
||||||
<button
|
<div className='flex gap-4 *:w-1/2'>
|
||||||
type="button"
|
<button
|
||||||
className="login-btn-gradient mt-4 w-full rounded py-2 tracking-wide text-white outline-none focus:outline-none"
|
type='button'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (state.confirmationCloseFn) {
|
dispatch({ type: "CLOSE_CONFIRMATION" });
|
||||||
state.confirmationCloseFn();
|
}}
|
||||||
}
|
className='mt-4 w-full rounded border-2 border-gray-300 py-2 tracking-wide text-black outline-none focus:outline-none'
|
||||||
dispatch({ type: "CLOSE_CONFIRMATION" });
|
>
|
||||||
}}
|
Cancel
|
||||||
>
|
</button>
|
||||||
{state.confirmationBtn}
|
<button
|
||||||
</button>
|
type='button'
|
||||||
|
className='login-btn-gradient mt-4 w-full rounded py-2 tracking-wide text-white outline-none focus:outline-none'
|
||||||
|
onClick={() => {
|
||||||
|
if (state.confirmationCloseFn) {
|
||||||
|
state.confirmationCloseFn();
|
||||||
|
}
|
||||||
|
dispatch({ type: "CLOSE_CONFIRMATION" });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{state.confirmationBtn}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,37 +5,51 @@ import { useController } from "react-hook-form";
|
|||||||
import LocationIcon from "./frontend/icons/LocationIcon";
|
import LocationIcon from "./frontend/icons/LocationIcon";
|
||||||
import { GlobalContext } from "@/globalContext";
|
import { GlobalContext } from "@/globalContext";
|
||||||
|
|
||||||
export default function CustomStaticLocationAutoCompleteV2({ type, control, name, setValue, onClear, className, containerClassName, hideIcons, suggestionType, ...restProps }) {
|
export default function CustomStaticLocationAutoCompleteV2({
|
||||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
type,
|
||||||
|
control,
|
||||||
|
name,
|
||||||
|
setValue,
|
||||||
|
onClear,
|
||||||
|
className,
|
||||||
|
containerClassName,
|
||||||
|
hideIcons,
|
||||||
|
suggestionType,
|
||||||
|
...restProps
|
||||||
|
}) {
|
||||||
|
const { dispatch: globalDispatch, state: globalState } =
|
||||||
|
useContext(GlobalContext);
|
||||||
const [location, setLocation] = useState(globalState.location);
|
const [location, setLocation] = useState(globalState.location);
|
||||||
|
|
||||||
|
const { placePredictions, getPlacePredictions, isPlacePredictionsLoading } =
|
||||||
const { placePredictions, getPlacePredictions, isPlacePredictionsLoading } = usePlacesService({
|
usePlacesService({
|
||||||
apiKey: import.meta.env.VITE_GOOGLE_API_KEY,
|
apiKey: import.meta.env.VITE_GOOGLE_API_KEY,
|
||||||
options: { types: suggestionType ?? ["(region)"] },
|
options: { types: suggestionType ?? ["(region)"] },
|
||||||
debounce: 200,
|
debounce: 200,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Combobox
|
<Combobox
|
||||||
as={"div"}
|
as={"div"}
|
||||||
className={`relative w-full normal-case z-100 ${containerClassName ?? ""}`}
|
className={`z-100 relative w-full normal-case ${
|
||||||
|
containerClassName ?? ""
|
||||||
|
}`}
|
||||||
value={location}
|
value={location}
|
||||||
>
|
>
|
||||||
{!hideIcons && <LocationIcon />}
|
{!hideIcons && <LocationIcon />}
|
||||||
|
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
{...restProps}
|
{...restProps}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
className={`w-full truncate text-black ${className ?? ""}`}
|
className={`w-full truncate text-black ${className ?? ""}`}
|
||||||
value={globalState.location}
|
value={location}
|
||||||
onChange={(evt) => {
|
onChange={(evt) => {
|
||||||
setLocation(evt.target.value)
|
setLocation(evt.target.value);
|
||||||
getPlacePredictions({ input: evt.target.value });
|
getPlacePredictions({ input: evt.target.value });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!hideIcons && globalState.location && (
|
{!hideIcons && globalState.location && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setValue("");
|
setValue("");
|
||||||
setLocation("");
|
setLocation("");
|
||||||
@@ -49,56 +63,72 @@ export default function CustomStaticLocationAutoCompleteV2({ type, control, name
|
|||||||
)}
|
)}
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="transition ease-out duration-100"
|
enter='transition ease-out duration-100'
|
||||||
enterFrom="transform opacity-0 scale-95"
|
enterFrom='transform opacity-0 scale-95'
|
||||||
enterTo="transform opacity-100 scale-100"
|
enterTo='transform opacity-100 scale-100'
|
||||||
leave="transition ease-in duration-75"
|
leave='transition ease-in duration-75'
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom='transform opacity-100 scale-100'
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo='transform opacity-0 scale-95'
|
||||||
>
|
>
|
||||||
{isPlacePredictionsLoading ? (
|
{isPlacePredictionsLoading ? (
|
||||||
<div className="absolute left-0 right-0 top-full z-50 mt-2 flex w-full origin-top justify-center rounded-xl border bg-white py-8">
|
<div className='absolute left-0 right-0 top-full z-50 mt-2 flex w-full origin-top justify-center rounded-xl border bg-white py-8'>
|
||||||
<svg
|
<svg
|
||||||
style={{ margin: "auto", background: "none", display: "block", shapeRendering: "auto" }}
|
style={{
|
||||||
width="36px"
|
margin: "auto",
|
||||||
height="36px"
|
background: "none",
|
||||||
viewBox="0 0 100 100"
|
display: "block",
|
||||||
preserveAspectRatio="xMidYMid"
|
shapeRendering: "auto",
|
||||||
|
}}
|
||||||
|
width='36px'
|
||||||
|
height='36px'
|
||||||
|
viewBox='0 0 100 100'
|
||||||
|
preserveAspectRatio='xMidYMid'
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="none"
|
fill='none'
|
||||||
stroke="#d0d5dd"
|
stroke='#d0d5dd'
|
||||||
strokeWidth="10"
|
strokeWidth='10'
|
||||||
strokeDasharray="42.76482137044271 42.76482137044271"
|
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"
|
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"
|
strokeLinecap='round'
|
||||||
style={{ transform: "scale(1)", transformOrigin: "50px 50px" }}
|
style={{ transform: "scale(1)", transformOrigin: "50px 50px" }}
|
||||||
>
|
>
|
||||||
<animate
|
<animate
|
||||||
attributeName="stroke-dashoffset"
|
attributeName='stroke-dashoffset'
|
||||||
repeatCount="indefinite"
|
repeatCount='indefinite'
|
||||||
dur="1.6666666666666667s"
|
dur='1.6666666666666667s'
|
||||||
keyTimes="0;1"
|
keyTimes='0;1'
|
||||||
values="0;256.58892822265625"
|
values='0;256.58892822265625'
|
||||||
></animate>
|
></animate>
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Combobox.Options
|
<Combobox.Options
|
||||||
className={`${placePredictions.length > 0 ? "py-2 shadow-lg ring-1" : ""
|
className={`${
|
||||||
} absolute left-0 right-0 top-full z-50 mt-2 w-full origin-top cursor-pointer divide-y divide-gray-100 rounded-xl bg-white ring-black ring-opacity-5 focus:outline-none`}
|
placePredictions.length > 0 ? "py-2 shadow-lg ring-1" : ""
|
||||||
|
} absolute left-0 right-0 top-full z-50 mt-2 w-full origin-top cursor-pointer divide-y divide-gray-100 rounded-xl bg-white ring-black ring-opacity-5 focus:outline-none`}
|
||||||
>
|
>
|
||||||
{placePredictions.map((place, idx) => (
|
{placePredictions.map((place, idx) => (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
className="flex w-full items-center truncate rounded-pill px-3 py-3 pr-5 text-sm ui-active:bg-gray-100 ui-active:text-black ui-not-active:text-gray-800"
|
className='flex w-full items-center truncate rounded-pill px-3 py-3 pr-5 text-sm ui-active:bg-gray-100 ui-active:text-black ui-not-active:text-gray-800'
|
||||||
key={idx}
|
key={idx}
|
||||||
value={place.structured_formatting.main_text}
|
value={place.structured_formatting.main_text}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setValue(place?.structured_formatting.main_text + ', ' + place.structured_formatting?.secondary_text)
|
setValue(
|
||||||
|
place?.structured_formatting.main_text +
|
||||||
|
", " +
|
||||||
|
place.structured_formatting?.secondary_text
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span>{`${place.structured_formatting.main_text} ${place.structured_formatting?.secondary_text ? "," : ""} ${place.structured_formatting?.secondary_text ? place.structured_formatting?.secondary_text : ""}`}</span>
|
<span>{`${place.structured_formatting.main_text} ${
|
||||||
|
place.structured_formatting?.secondary_text ? "," : ""
|
||||||
|
} ${
|
||||||
|
place.structured_formatting?.secondary_text
|
||||||
|
? place.structured_formatting?.secondary_text
|
||||||
|
: ""
|
||||||
|
}`}</span>
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))}
|
))}
|
||||||
</Combobox.Options>
|
</Combobox.Options>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { Fragment } from "react";
|
||||||
|
|
||||||
|
export default function ProfileImageConfirmModal({
|
||||||
|
modalOpen,
|
||||||
|
modalImage,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Transition appear show={modalOpen} as={Fragment}>
|
||||||
|
<Dialog as='div' className='relative z-10' onClose={onCancel}>
|
||||||
|
<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-sm transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
|
||||||
|
<img
|
||||||
|
src={modalImage || "/default.png"}
|
||||||
|
alt='Profile Preview'
|
||||||
|
className='mb-4 h-64 w-full rounded object-cover'
|
||||||
|
/>
|
||||||
|
<div className='flex justify-center gap-4'>
|
||||||
|
<button
|
||||||
|
className='login-btn-gradient rounded px-4 py-2 text-white'
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='rounded bg-gray-300 px-4 py-2 text-black'
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
}
|
||||||
+1411
-1275
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { GlobalContext } from "@/globalContext";
|
import { GlobalContext } from "@/globalContext";
|
||||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
@@ -8,19 +8,28 @@ import { useState } from "react";
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import MkdSDK from "@/utils/MkdSDK";
|
import MkdSDK from "@/utils/MkdSDK";
|
||||||
|
|
||||||
const PrivacyAndPolicyModal = ({ isOpen, closeModal }) => {
|
const PrivacyAndPolicyModal = ({ isOpen, closeModal, onReadToEnd }) => {
|
||||||
const [privacy, setPrivacy] = useState("");
|
const [privacy, setPrivacy] = useState("");
|
||||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||||
|
const [hasReadToEnd, setHasReadToEnd] = useState(false);
|
||||||
|
|
||||||
async function fetchPrivacyPolicy() {
|
async function fetchPrivacyPolicy() {
|
||||||
globalDispatch({ type: "START_LOADING" });
|
globalDispatch({ type: "START_LOADING" });
|
||||||
const sdk = new MkdSDK();
|
const sdk = new MkdSDK();
|
||||||
sdk.setTable("cms");
|
sdk.setTable("cms");
|
||||||
try {
|
try {
|
||||||
const result = await callCustomAPI("cms", "post", { payload: { content_key: "privacy_policy" }, limit: 1000, page: 1 }, "PAGINATE");
|
const result = await callCustomAPI(
|
||||||
|
"cms",
|
||||||
|
"post",
|
||||||
|
{ payload: { content_key: "privacy_policy" }, limit: 1000, page: 1 },
|
||||||
|
"PAGINATE"
|
||||||
|
);
|
||||||
|
|
||||||
if (Array.isArray(result.list) && result.list.length > 0) {
|
if (Array.isArray(result.list) && result.list.length > 0) {
|
||||||
setPrivacy(result.list.find((stg) => stg.content_key == "privacy_policy")?.content_value);
|
setPrivacy(
|
||||||
|
result.list.find((stg) => stg.content_key == "privacy_policy")
|
||||||
|
?.content_value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
@@ -36,74 +45,79 @@ const PrivacyAndPolicyModal = ({ isOpen, closeModal }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPrivacyPolicy();
|
fetchPrivacyPolicy();
|
||||||
}, []);
|
setHasReadToEnd(false);
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
function handleScroll(e) {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
|
if (!hasReadToEnd && scrollTop + clientHeight >= scrollHeight - 10) {
|
||||||
|
setHasReadToEnd(true);
|
||||||
|
if (onReadToEnd) onReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${isOpen ? "flex" : "hidden"} fixed inset-0 items-center justify-center`}></div>
|
<div
|
||||||
|
className={`${
|
||||||
|
isOpen ? "flex" : "hidden"
|
||||||
|
} fixed inset-0 items-center justify-center`}
|
||||||
|
></div>
|
||||||
|
|
||||||
<Transition
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
appear
|
<Dialog as='div' className='relative z-10' onClose={closeModal}>
|
||||||
show={isOpen}
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
>
|
enter='ease-out duration-300'
|
||||||
<Dialog
|
enterFrom='opacity-0'
|
||||||
as="div"
|
enterTo='opacity-100'
|
||||||
className="relative z-10"
|
leave='ease-in duration-200'
|
||||||
onClose={closeModal}
|
leaveFrom='opacity-100'
|
||||||
>
|
leaveTo='opacity-0'
|
||||||
<Transition.Child
|
>
|
||||||
as={Fragment}
|
<div className='fixed inset-0 bg-black bg-opacity-25' />
|
||||||
enter="ease-out duration-300"
|
</Transition.Child>
|
||||||
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='fixed inset-0 overflow-y-auto'>
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter='ease-out duration-300'
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom='opacity-0 scale-95'
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo='opacity-100 scale-100'
|
||||||
leave="ease-in duration-200"
|
leave='ease-in duration-200'
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom='opacity-100 scale-100'
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo='opacity-0 scale-95'
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className='w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as='h3'
|
||||||
className="text-lg font-medium leading-6 text-gray-900 flex justify-between items-center"
|
className='text-lg flex items-center justify-between font-medium leading-6 text-gray-900'
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
{" "}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={closeModal}
|
|
||||||
className="py-2 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full flex justify-end"
|
|
||||||
>
|
>
|
||||||
✕
|
{" "}
|
||||||
</button>
|
<button
|
||||||
</Dialog.Title>
|
type='button'
|
||||||
<div className="mt-2">
|
onClick={closeModal}
|
||||||
<article
|
className='flex justify-end rounded-full border px-3 py-2 text-2xl font-normal duration-100 hover:bg-gray-200 active:bg-gray-300'
|
||||||
className="sun-editor-editable text-sm max-h-[600px] overflow-y-auto my-8"
|
>
|
||||||
dangerouslySetInnerHTML={{ __html: privacy }}
|
✕
|
||||||
></article>
|
</button>
|
||||||
</div>
|
</Dialog.Title>
|
||||||
</Dialog.Panel>
|
<div className='mt-2'>
|
||||||
</Transition.Child>
|
<article
|
||||||
|
className='sun-editor-editable my-8 max-h-[600px] overflow-y-auto text-sm'
|
||||||
|
dangerouslySetInnerHTML={{ __html: privacy }}
|
||||||
|
onScroll={handleScroll}
|
||||||
|
></article>
|
||||||
|
</div>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Dialog>
|
||||||
</Dialog>
|
</Transition>
|
||||||
</Transition>
|
</>
|
||||||
</>
|
);
|
||||||
)
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default PrivacyAndPolicyModal
|
export default PrivacyAndPolicyModal;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import TermsAndConditionsModal from "./TermsAndConditionsModal";
|
|||||||
import DatePickerV2 from "@/components/frontend/DatePickerV2";
|
import DatePickerV2 from "@/components/frontend/DatePickerV2";
|
||||||
import { LoadingButton } from "@/components/frontend";
|
import { LoadingButton } from "@/components/frontend";
|
||||||
import PrivacyAndPolicyModal from "./PrivacyAndPolicyModal";
|
import PrivacyAndPolicyModal from "./PrivacyAndPolicyModal";
|
||||||
|
import commonPasswords from "@/assets/json/common-passwords.json";
|
||||||
|
|
||||||
export default function SignUpDetailsForm() {
|
export default function SignUpDetailsForm() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -28,6 +29,9 @@ export default function SignUpDetailsForm() {
|
|||||||
const [modalOpen, setModalOpen] = React.useState(false);
|
const [modalOpen, setModalOpen] = React.useState(false);
|
||||||
const [privacyOpen, setPrivacyModalOpen] = React.useState(false);
|
const [privacyOpen, setPrivacyModalOpen] = React.useState(false);
|
||||||
const initialDate = useRef(new Date());
|
const initialDate = useRef(new Date());
|
||||||
|
const [agreedToTerms, setAgreedToTerms] = React.useState(false);
|
||||||
|
const [privacyRead, setPrivacyRead] = React.useState(false);
|
||||||
|
const [passwordErrors, setPasswordErrors] = React.useState([]);
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
@@ -36,11 +40,67 @@ export default function SignUpDetailsForm() {
|
|||||||
setPrivacyModalOpen(false);
|
setPrivacyModalOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- DOB must be at least 18 years ---
|
||||||
|
function isAtLeast18YearsOld(date) {
|
||||||
|
if (!date) return false;
|
||||||
|
const now = new Date();
|
||||||
|
const dob = new Date(date);
|
||||||
|
let age = now.getFullYear() - dob.getFullYear();
|
||||||
|
const m = now.getMonth() - dob.getMonth();
|
||||||
|
if (m < 0 || (m === 0 && now.getDate() < dob.getDate())) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
return age >= 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Password validation ---
|
||||||
|
function validatePassword(password, firstName, lastName, dob) {
|
||||||
|
const errors = [];
|
||||||
|
if (!password) errors.push("Password is required.");
|
||||||
|
if (password && password.length < 10)
|
||||||
|
errors.push("Password must be at least 10 characters long.");
|
||||||
|
if (password && !/[a-z]/.test(password))
|
||||||
|
errors.push("Password must contain at least one lowercase letter.");
|
||||||
|
if (password && !/[A-Z]/.test(password))
|
||||||
|
errors.push("Password must contain at least one uppercase letter.");
|
||||||
|
if (password && !/[0-9]/.test(password))
|
||||||
|
errors.push("Password must contain at least one digit.");
|
||||||
|
if (password && !/[^A-Za-z0-9]/.test(password))
|
||||||
|
errors.push("Password must contain at least one symbol.");
|
||||||
|
if (
|
||||||
|
password &&
|
||||||
|
firstName &&
|
||||||
|
password.toLowerCase().includes(firstName.toLowerCase())
|
||||||
|
)
|
||||||
|
errors.push("Password must not contain your first name.");
|
||||||
|
if (
|
||||||
|
password &&
|
||||||
|
lastName &&
|
||||||
|
password.toLowerCase().includes(lastName.toLowerCase())
|
||||||
|
)
|
||||||
|
errors.push("Password must not contain your last name.");
|
||||||
|
if (password && dob) {
|
||||||
|
const dobStr = new Date(dob).toISOString().slice(0, 10).replace(/-/g, "");
|
||||||
|
if (dobStr && password.includes(dobStr))
|
||||||
|
errors.push("Password must not contain your date of birth.");
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
password &&
|
||||||
|
commonPasswords.some((w) => password.toLowerCase().includes(w))
|
||||||
|
)
|
||||||
|
errors.push("Password must not contain common password words.");
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Validation schema ---
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
firstName: yup.string(),
|
firstName: yup.string().required("First name is required."),
|
||||||
lastName: yup.string(),
|
lastName: yup.string().required("Last name is required."),
|
||||||
dob: yup.date(),
|
dob: yup
|
||||||
password: yup.string()
|
.date()
|
||||||
|
.required("Date of birth is required.")
|
||||||
|
.test("age", "You must be at least 18 years old.", isAtLeast18YearsOld),
|
||||||
|
password: yup.string().required("Password is required."),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -65,6 +125,34 @@ export default function SignUpDetailsForm() {
|
|||||||
|
|
||||||
const data = watch();
|
const data = watch();
|
||||||
|
|
||||||
|
// --- Password error display ---
|
||||||
|
React.useEffect(() => {
|
||||||
|
setPasswordErrors(
|
||||||
|
validatePassword(data.password, data.firstName, data.lastName, data.dob)
|
||||||
|
);
|
||||||
|
}, [data.password, data.firstName, data.lastName, data.dob]);
|
||||||
|
|
||||||
|
// --- Terms and Privacy Modal handlers ---
|
||||||
|
function handleAgreeTerms() {
|
||||||
|
setAgreedToTerms(true);
|
||||||
|
setModalOpen(false);
|
||||||
|
}
|
||||||
|
function handlePrivacyRead() {
|
||||||
|
setPrivacyRead(true);
|
||||||
|
setPrivacyModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Disable submit logic ---
|
||||||
|
const canSubmit =
|
||||||
|
data.firstName &&
|
||||||
|
data.lastName &&
|
||||||
|
data.dob &&
|
||||||
|
isAtLeast18YearsOld(data.dob) &&
|
||||||
|
data.password &&
|
||||||
|
passwordErrors.length === 0 &&
|
||||||
|
agreedToTerms &&
|
||||||
|
privacyRead;
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -74,7 +162,15 @@ export default function SignUpDetailsForm() {
|
|||||||
|
|
||||||
// register device
|
// register device
|
||||||
sdk.setTable("device");
|
sdk.setTable("device");
|
||||||
await sdk.callRestAPI({ active: 1, user_id: result.user_id, last_login_time: new Date().toISOString().split("T")[0], uid: localStorage.getItem("device-uid") }, "POST");
|
await sdk.callRestAPI(
|
||||||
|
{
|
||||||
|
active: 1,
|
||||||
|
user_id: result.user_id,
|
||||||
|
last_login_time: new Date().toISOString().split("T")[0],
|
||||||
|
uid: localStorage.getItem("device-uid"),
|
||||||
|
},
|
||||||
|
"POST"
|
||||||
|
);
|
||||||
|
|
||||||
await callCustomAPI(
|
await callCustomAPI(
|
||||||
"edit-self",
|
"edit-self",
|
||||||
@@ -85,11 +181,13 @@ export default function SignUpDetailsForm() {
|
|||||||
last_name: data.lastName,
|
last_name: data.lastName,
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
dob: isSameDay(data.dob, initialDate.current) ? undefined : moment(data.dob).format("yyyy-MM-DD"),
|
dob: isSameDay(data.dob, initialDate.current)
|
||||||
|
? undefined
|
||||||
|
: moment(data.dob).format("yyyy-MM-DD"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
result.token,
|
result.token
|
||||||
);
|
);
|
||||||
|
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
@@ -97,7 +195,7 @@ export default function SignUpDetailsForm() {
|
|||||||
authDispatch({ type: "ALLOW_CHECK_VERIFICATION" });
|
authDispatch({ type: "ALLOW_CHECK_VERIFICATION" });
|
||||||
navigate("/check-verification");
|
navigate("/check-verification");
|
||||||
localStorage.setItem("first_login", result.user_id);
|
localStorage.setItem("first_login", result.user_id);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (result.validation) {
|
if (result.validation) {
|
||||||
@@ -108,11 +206,9 @@ export default function SignUpDetailsForm() {
|
|||||||
type: "manual",
|
type: "manual",
|
||||||
message: result.validation[field],
|
message: result.validation[field],
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setError("firstName", {
|
setError("firstName", {
|
||||||
@@ -126,122 +222,160 @@ export default function SignUpDetailsForm() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="flex flex-col items-center justify-center bg-white md:w-1/2">
|
<section className='flex flex-col items-center justify-center bg-white md:w-1/2'>
|
||||||
<form
|
<form
|
||||||
className="flex w-full max-w-md flex-col px-6"
|
className='flex w-full max-w-md flex-col px-6'
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
>
|
>
|
||||||
<h1 className="mb-8 text-center text-5xl font-bold">Finish Signing Up</h1>
|
<h1 className='mb-8 text-center text-5xl font-bold'>
|
||||||
<div className="mb-8">
|
Finish Signing Up
|
||||||
|
</h1>
|
||||||
|
<div className='mb-8'>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type='text'
|
||||||
{...register("firstName")}
|
{...register("firstName")}
|
||||||
className="w-full resize-none rounded-md border bg-transparent py-2 px-4 focus:outline-none active:outline-none"
|
className='w-full resize-none rounded-md border bg-transparent px-4 py-2 focus:outline-none active:outline-none'
|
||||||
placeholder="First name"
|
placeholder='First name'
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
/>
|
/>
|
||||||
<p className="text-red-500 text-xs italic mt-2 block">{errors.firstName?.message}</p>
|
<p className='mt-2 block text-xs italic text-red-500'>
|
||||||
|
{errors.firstName?.message}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type='text'
|
||||||
{...register("lastName")}
|
{...register("lastName")}
|
||||||
className="w-full resize-none rounded-md border bg-transparent py-2 px-4 focus:outline-none active:outline-none"
|
className='w-full resize-none rounded-md border bg-transparent px-4 py-2 focus:outline-none active:outline-none'
|
||||||
placeholder="Last name"
|
placeholder='Last name'
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
/>
|
/>
|
||||||
<p className="text-red-500 text-xs italic mt-2 block">{errors.lastName?.message}</p>
|
<p className='mt-2 block text-xs italic text-red-500'>
|
||||||
|
{errors.lastName?.message}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DatePickerV2
|
<DatePickerV2
|
||||||
control={control}
|
control={control}
|
||||||
name="dob"
|
name='dob'
|
||||||
min={new Date("1950-01-01")}
|
min={new Date("1950-01-01")}
|
||||||
max={initialDate.current}
|
max={initialDate.current}
|
||||||
setValue={(v) => setValue("dob", v)}
|
setValue={(v) => setValue("dob", v)}
|
||||||
/>
|
/>
|
||||||
<div className={`${errors.password?.message && dirtyFields.password ? "border rounded-md border-[#C42945]" : "borde"} relative mb-4 flex justify-between rounded-md bg-transparent`}>
|
<p className='mt-2 block text-xs italic text-red-500'>
|
||||||
<input
|
{errors.dob?.message}
|
||||||
autoComplete={showPassword ? "off" : "new-password"}
|
</p>
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
{...register("password", {
|
<div
|
||||||
onChange: () => {
|
className={`${
|
||||||
trigger("password");
|
passwordErrors.length > 0 && dirtyFields.password
|
||||||
},
|
? "rounded-md border border-[#C42945]"
|
||||||
})}
|
: "borde"
|
||||||
className="flex-grow rounded-md border p-2 px-4 focus:outline-none active:outline-none "
|
} relative mb-4 flex flex-col rounded-md bg-transparent`}
|
||||||
placeholder="Password"
|
>
|
||||||
/>{" "}
|
<div className='flex items-center justify-between'>
|
||||||
<button
|
<input
|
||||||
type="button"
|
autoComplete={showPassword ? "off" : "new-password"}
|
||||||
onClick={() => setShowPassword((prev) => !prev)}
|
type={showPassword ? "text" : "password"}
|
||||||
className="absolute right-1 top-[20%]"
|
{...register("password", {
|
||||||
>
|
onChange: () => {
|
||||||
{" "}
|
trigger("password");
|
||||||
{showPassword ? (
|
},
|
||||||
<img
|
})}
|
||||||
src="/show.png"
|
className='flex-grow rounded-md border p-2 px-4 focus:outline-none active:outline-none '
|
||||||
alt=""
|
placeholder='Password'
|
||||||
className="mr-2 w-6"
|
/>
|
||||||
/>
|
<button
|
||||||
) : (
|
type='button'
|
||||||
<img
|
onClick={() => setShowPassword((prev) => !prev)}
|
||||||
src="/invisible.png"
|
className='absolute right-1 top-[20%]'
|
||||||
alt=""
|
>
|
||||||
className="mr-2 w-6"
|
{showPassword ? (
|
||||||
/>
|
<img src='/show.png' alt='' className='mr-2 w-6' />
|
||||||
)}
|
) : (
|
||||||
</button>
|
<img src='/invisible.png' alt='' className='mr-2 w-6' />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{dirtyFields.password && passwordErrors.length > 0 && (
|
||||||
|
<ul className='ml-6 mt-2 list-disc text-xs text-red-500'>
|
||||||
|
{passwordErrors.map((err, idx) => (
|
||||||
|
<li key={idx}>{err}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p className='mb-4 text-sm normal-case text-gray-500'>
|
||||||
<p className="mb-4 text-sm normal-case text-gray-500">
|
Select and agree to{" "}
|
||||||
Select and agree to {" "}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
onClick={() =>setModalOpen(true)}
|
onClick={() => setModalOpen(true)}
|
||||||
className="underline"
|
className='underline'
|
||||||
// target={"_blank"}
|
// target={"_blank"}
|
||||||
>
|
>
|
||||||
{" "} Terms and Conditions
|
{" "}
|
||||||
</button>
|
Terms and Conditions
|
||||||
{" "}
|
</button>{" "}
|
||||||
to continue.
|
to continue.{" "}
|
||||||
{" "}
|
|
||||||
{" "}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
onClick={() =>setPrivacyModalOpen(true)}
|
onClick={() => setPrivacyModalOpen(true)}
|
||||||
className="underline"
|
className='underline'
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className='mb-1 flex flex-col'>
|
||||||
|
{" "}
|
||||||
|
{!agreedToTerms && (
|
||||||
|
<span className='text-xs text-red-500'>
|
||||||
|
You must agree to the Terms and Conditions.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!privacyRead && (
|
||||||
|
<span className='text-xs text-red-500'>
|
||||||
|
You must read the Privacy Policy to the end.
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={loading}
|
loading={loading}
|
||||||
type="submit"
|
type='submit'
|
||||||
className={`disabled:cursor-not-allowed login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"}`}
|
className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none disabled:cursor-not-allowed ${
|
||||||
// disabled={!recaptchaValidated}
|
loading ? "py-1" : "py-2"
|
||||||
|
}`}
|
||||||
|
disabled={!canSubmit}
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
<section
|
<section
|
||||||
style={{ backgroundImage: `url(${role == "host" ? "/host-sign-up.jpg" : "/sign-up-bg.jpg"})`, backgroundSize: "cover", backgroundRepeat: "no-repeat", backgroundPosition: "center" }}
|
style={{
|
||||||
className="hidden w-1/2 md:block"
|
backgroundImage: `url(${
|
||||||
>
|
role == "host" ? "/host-sign-up.jpg" : "/sign-up-bg.jpg"
|
||||||
</section>
|
})`,
|
||||||
<TermsAndConditionsModal
|
backgroundSize: "cover",
|
||||||
isOpen={modalOpen}
|
backgroundRepeat: "no-repeat",
|
||||||
closeModal={closeModal}
|
backgroundPosition: "center",
|
||||||
/>
|
}}
|
||||||
<PrivacyAndPolicyModal
|
className='hidden w-1/2 md:block'
|
||||||
isOpen={privacyOpen}
|
></section>
|
||||||
closeModal={closePrivacyModal}
|
<TermsAndConditionsModal
|
||||||
/>
|
isOpen={modalOpen}
|
||||||
|
closeModal={() => setModalOpen(false)}
|
||||||
|
setIsAgreed={handleAgreeTerms}
|
||||||
|
/>
|
||||||
|
<PrivacyAndPolicyModal
|
||||||
|
isOpen={privacyOpen}
|
||||||
|
closeModal={() => setPrivacyModalOpen(false)}
|
||||||
|
onReadToEnd={handlePrivacyRead}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useSignUpContext } from "./signUpContext";
|
|||||||
import { callCustomAPI, oauthLoginApi } from "@/utils/callCustomAPI";
|
import { callCustomAPI, oauthLoginApi } from "@/utils/callCustomAPI";
|
||||||
import { LoadingButton } from "@/components/frontend";
|
import { LoadingButton } from "@/components/frontend";
|
||||||
import TLDs from "@/assets/json/email-tlds.json";
|
import TLDs from "@/assets/json/email-tlds.json";
|
||||||
|
import ReCAPTCHA from "react-google-recaptcha";
|
||||||
|
|
||||||
const SignUpForm = () => {
|
const SignUpForm = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -15,9 +16,18 @@ const SignUpForm = () => {
|
|||||||
const role = signUpData.role;
|
const role = signUpData.role;
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
email: yup
|
email: yup
|
||||||
.string(),
|
.string()
|
||||||
|
.required("Email is required")
|
||||||
|
.email("Invalid email address")
|
||||||
|
.test("tld-check", "Invalid email TLD", (value) => {
|
||||||
|
if (!value) return false;
|
||||||
|
const tld = value.split(".").pop();
|
||||||
|
return TLDs.includes(tld);
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [recaptchaValue, setRecaptchaValue] = useState(null);
|
||||||
|
const [recaptchaError, setRecaptchaError] = useState("");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@@ -32,12 +42,20 @@ const SignUpForm = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
|
setRecaptchaError("");
|
||||||
|
if (!recaptchaValue) {
|
||||||
|
setRecaptchaError("Please complete the recaptcha.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await callCustomAPI("email-exist", "post", { email: data.email }, "");
|
const result = await callCustomAPI(
|
||||||
|
"email-exist",
|
||||||
|
"post",
|
||||||
|
{ email: data.email },
|
||||||
|
""
|
||||||
|
);
|
||||||
if (result.error || result.exist) throw new Error("User already exists");
|
if (result.error || result.exist) throw new Error("User already exists");
|
||||||
|
|
||||||
dispatch({ type: "SET_EMAIL", payload: data.email });
|
dispatch({ type: "SET_EMAIL", payload: data.email });
|
||||||
navigate("/signup/details" + "?role=" + role);
|
navigate("/signup/details" + "?role=" + role);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -61,77 +79,87 @@ const SignUpForm = () => {
|
|||||||
window.open(result.data, "_self");
|
window.open(result.data, "_self");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (!signUpData.role) return <Navigate to={"/signup/select-role"} />;
|
if (!signUpData.role) return <Navigate to={"/signup/select-role"} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className="flex w-full flex-col items-center justify-center bg-white md:w-1/2">
|
<section className='flex w-full flex-col items-center justify-center bg-white md:w-1/2'>
|
||||||
<form
|
<form
|
||||||
className="flex w-full max-w-md flex-col px-6"
|
className='flex w-full max-w-md flex-col px-6'
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
>
|
>
|
||||||
<h1 className="mb-8 text-center text-3xl font-semibold md:text-5xl md:font-bold">{role == "host" ? "Become a host" : "Sign up"}</h1>
|
<h1 className='mb-8 text-center text-3xl font-semibold md:text-5xl md:font-bold'>
|
||||||
|
{role == "host" ? "Become a host" : "Sign up"}
|
||||||
|
</h1>
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
{...register("email")}
|
{...register("email")}
|
||||||
type="text"
|
type='text'
|
||||||
className="mb-8 resize-none rounded-sm border-2 bg-transparent p-2 px-4 focus:outline-none active:outline-none"
|
className='mb-8 resize-none rounded-sm border-2 bg-transparent p-2 px-4 focus:outline-none active:outline-none'
|
||||||
placeholder="Email"
|
placeholder='Email'
|
||||||
/>
|
/>
|
||||||
{Object.entries(errors).length > 0 ? (
|
{Object.entries(errors).length > 0 ? (
|
||||||
<p className="error-vibrate my-3 rounded-md border border-[#C42945] bg-white py-2 px-3 text-center text-sm normal-case text-[#C42945]">{Object.values(errors)[0].message}</p>
|
<p className='error-vibrate my-3 rounded-md border border-[#C42945] bg-white px-3 py-2 text-center text-sm normal-case text-[#C42945]'>
|
||||||
|
{Object.values(errors)[0].message}
|
||||||
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
<div className='mb-4 flex justify-center'>
|
||||||
|
<ReCAPTCHA
|
||||||
|
sitekey={import.meta.env.VITE_RECAPTCHA_SITE_KEY}
|
||||||
|
onChange={(val) => {
|
||||||
|
setRecaptchaValue(val);
|
||||||
|
setRecaptchaError("");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{recaptchaError && (
|
||||||
|
<p className='mb-2 text-center text-xs italic text-red-500'>
|
||||||
|
{recaptchaError}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={loading}
|
loading={loading}
|
||||||
type="submit"
|
type='submit'
|
||||||
className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"}`}
|
className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${
|
||||||
|
loading ? "py-1" : "py-2"
|
||||||
|
}`}
|
||||||
|
disabled={!recaptchaValue}
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<div className="hr my-6 text-center">OR</div>
|
<div className='hr my-6 text-center'>OR</div>
|
||||||
<div className="oauth flex w-full max-w-md flex-col gap-4 px-6 text-[#344054]">
|
<div className='oauth flex w-full max-w-md flex-col gap-4 px-6 text-[#344054]'>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleGoogleLogin()}
|
onClick={() => handleGoogleLogin()}
|
||||||
className="flex items-center justify-center gap-2 border-2 py-[10px]"
|
className='flex items-center justify-center gap-2 border-2 py-[10px]'
|
||||||
>
|
>
|
||||||
<img
|
<img src='/google-icon.png' className='h-[18px] w-[18px]' />
|
||||||
src="/google-icon.png"
|
|
||||||
className="h-[18px] w-[18px]"
|
|
||||||
/>
|
|
||||||
<span>Sign Up With Google</span>
|
<span>Sign Up With Google</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleFacebookLogin()}
|
onClick={() => handleFacebookLogin()}
|
||||||
className="flex items-center justify-center gap-2 border-2 py-[10px]"
|
className='flex items-center justify-center gap-2 border-2 py-[10px]'
|
||||||
>
|
>
|
||||||
<img
|
<img src='/facebook-icon.png' className='h-[16px] w-[16px]' />
|
||||||
src="/facebook-icon.png"
|
|
||||||
className="h-[16px] w-[16px]"
|
|
||||||
/>
|
|
||||||
<span>Sign Up With Facebook</span>
|
<span>Sign Up With Facebook</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleAppleLogin()}
|
onClick={() => handleAppleLogin()}
|
||||||
className="flex items-center justify-center gap-2 border-2 py-[10px]"
|
className='flex items-center justify-center gap-2 border-2 py-[10px]'
|
||||||
>
|
>
|
||||||
<img
|
<img src='/apple-icon.png' className='h-[16px] w-[16px]' />
|
||||||
src="/apple-icon.png"
|
|
||||||
className="h-[16px] w-[16px]"
|
|
||||||
/>
|
|
||||||
<span>Sign Up With Apple</span>
|
<span>Sign Up With Apple</span>
|
||||||
</button>
|
</button>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-center text-sm normal-case text-gray-800">
|
<h3 className='text-center text-sm normal-case text-gray-800'>
|
||||||
Already have an account?{" "}
|
Already have an account?{" "}
|
||||||
<Link
|
<Link
|
||||||
to={"/login" + "?role=" + role}
|
to={"/login" + "?role=" + role}
|
||||||
className="my-text-gradient mb-8 self-end text-sm font-semibold"
|
className='my-text-gradient mb-8 self-end text-sm font-semibold'
|
||||||
>
|
>
|
||||||
Log In
|
Log In
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
@@ -140,8 +168,15 @@ const SignUpForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section
|
<section
|
||||||
style={{ backgroundImage: `url(${role == "host" ? "/jumbotron1.jpg" : "/sign-up-bg.jpg"})`, backgroundSize: "cover", backgroundRepeat: "no-repeat", backgroundPosition: "center" }}
|
style={{
|
||||||
className="hidden w-1/2 md:block bg-contain"
|
backgroundImage: `url(${
|
||||||
|
role == "host" ? "/jumbotron1.jpg" : "/sign-up-bg.jpg"
|
||||||
|
})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
}}
|
||||||
|
className='hidden w-1/2 bg-contain md:block'
|
||||||
></section>
|
></section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,14 +7,23 @@ import { useContext } from "react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
export default function TermsAndConditionsModal({ isOpen, closeModal, setIsAgreed }) {
|
export default function TermsAndConditionsModal({
|
||||||
|
isOpen,
|
||||||
|
closeModal,
|
||||||
|
setIsAgreed,
|
||||||
|
}) {
|
||||||
const [termsAndConditions, setTermsAndCondition] = useState("");
|
const [termsAndConditions, setTermsAndCondition] = useState("");
|
||||||
const [agreed, setAgreed] = useState(false);
|
const [agreed, setAgreed] = useState(false);
|
||||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||||
|
|
||||||
async function fetchTermsAndConditions() {
|
async function fetchTermsAndConditions() {
|
||||||
try {
|
try {
|
||||||
const result = await callCustomAPI("cms", "post", { where: [`content_key = 'terms_and_conditions'`], limit: 1, page: 1 }, "PAGINATE");
|
const result = await callCustomAPI(
|
||||||
|
"cms",
|
||||||
|
"post",
|
||||||
|
{ where: [`content_key = 'terms_and_conditions'`], limit: 1, page: 1 },
|
||||||
|
"PAGINATE"
|
||||||
|
);
|
||||||
|
|
||||||
if (Array.isArray(result.list) && result.list.length > 0) {
|
if (Array.isArray(result.list) && result.list.length > 0) {
|
||||||
setTermsAndCondition(result.list[0].content_value);
|
setTermsAndCondition(result.list[0].content_value);
|
||||||
@@ -35,73 +44,72 @@ export default function TermsAndConditionsModal({ isOpen, closeModal, setIsAgree
|
|||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${isOpen ? "flex" : "hidden"} fixed inset-0 items-center justify-center`}></div>
|
<div
|
||||||
|
className={`${
|
||||||
|
isOpen ? "flex" : "hidden"
|
||||||
|
} fixed inset-0 items-center justify-center`}
|
||||||
|
></div>
|
||||||
|
|
||||||
<Transition
|
<Transition appear show={isOpen} as={Fragment}>
|
||||||
appear
|
<Dialog as='div' className='relative z-10' onClose={closeModal}>
|
||||||
show={isOpen}
|
|
||||||
as={Fragment}
|
|
||||||
>
|
|
||||||
<Dialog
|
|
||||||
as="div"
|
|
||||||
className="relative z-10"
|
|
||||||
onClose={closeModal}
|
|
||||||
>
|
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter='ease-out duration-300'
|
||||||
enterFrom="opacity-0"
|
enterFrom='opacity-0'
|
||||||
enterTo="opacity-100"
|
enterTo='opacity-100'
|
||||||
leave="ease-in duration-200"
|
leave='ease-in duration-200'
|
||||||
leaveFrom="opacity-100"
|
leaveFrom='opacity-100'
|
||||||
leaveTo="opacity-0"
|
leaveTo='opacity-0'
|
||||||
>
|
>
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
<div className='fixed inset-0 bg-black bg-opacity-25' />
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
||||||
<div className="fixed inset-0 overflow-y-auto">
|
<div className='fixed inset-0 overflow-y-auto'>
|
||||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
<div className='flex min-h-full items-center justify-center p-4 text-center'>
|
||||||
<Transition.Child
|
<Transition.Child
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="ease-out duration-300"
|
enter='ease-out duration-300'
|
||||||
enterFrom="opacity-0 scale-95"
|
enterFrom='opacity-0 scale-95'
|
||||||
enterTo="opacity-100 scale-100"
|
enterTo='opacity-100 scale-100'
|
||||||
leave="ease-in duration-200"
|
leave='ease-in duration-200'
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom='opacity-100 scale-100'
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo='opacity-0 scale-95'
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className='w-full max-w-6xl transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all'>
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as='h3'
|
||||||
className="text-lg font-medium leading-6 text-gray-900 flex justify-between items-center"
|
className='text-lg flex items-center justify-between font-medium leading-6 text-gray-900'
|
||||||
>
|
>
|
||||||
{" "}
|
|
||||||
{" "}
|
{" "}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
onClick={closeModal}
|
onClick={closeModal}
|
||||||
className="py-2 border hover:bg-gray-200 active:bg-gray-300 duration-100 px-3 text-2xl font-normal rounded-full flex justify-end"
|
className='flex justify-end rounded-full border px-3 py-2 text-2xl font-normal duration-100 hover:bg-gray-200 active:bg-gray-300'
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className='mt-2'>
|
||||||
<article
|
<article
|
||||||
className="sun-editor-editable text-sm max-h-[600px] overflow-y-auto my-8"
|
className='sun-editor-editable my-8 max-h-[600px] overflow-y-auto text-sm'
|
||||||
dangerouslySetInnerHTML={{ __html: termsAndConditions }}
|
dangerouslySetInnerHTML={{ __html: termsAndConditions }}
|
||||||
></article>
|
></article>
|
||||||
</div>
|
</div>
|
||||||
<div className="checkbox-container">
|
<div className='checkbox-container'>
|
||||||
<input
|
<input
|
||||||
type={"checkbox"}
|
type={"checkbox"}
|
||||||
name="i-agree"
|
name='i-agree'
|
||||||
id="i-agree"
|
id='i-agree'
|
||||||
checked={agreed}
|
checked={agreed}
|
||||||
onChange={() => {setAgreed((prev) => !prev); setIsAgreed((prev) => !prev); closeModal()}}
|
onChange={() => {
|
||||||
|
setAgreed((prev) => !prev);
|
||||||
|
setIsAgreed((prev) => !prev);
|
||||||
|
closeModal();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="i-agree"
|
htmlFor='i-agree'
|
||||||
className="items-center cursor-pointer remove-select"
|
className='remove-select cursor-pointer items-center'
|
||||||
>
|
>
|
||||||
Yeah, I agree to everything
|
Yeah, I agree to everything
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import MkdSDK from "@/utils/MkdSDK";
|
|||||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||||
import Skeleton from "react-loading-skeleton";
|
import Skeleton from "react-loading-skeleton";
|
||||||
import { formatDate } from "@/utils/date-time-utils";
|
import { formatDate } from "@/utils/date-time-utils";
|
||||||
import { IMAGE_STATUS, NOTIFICATION_STATUS, NOTIFICATION_TYPE } from "@/utils/constants";
|
import {
|
||||||
|
IMAGE_STATUS,
|
||||||
|
NOTIFICATION_STATUS,
|
||||||
|
NOTIFICATION_TYPE,
|
||||||
|
} from "@/utils/constants";
|
||||||
import SwitchBulkMode from "@/components/SwitchBulkMode";
|
import SwitchBulkMode from "@/components/SwitchBulkMode";
|
||||||
import TwoFaDialog from "@/components/Profile/TwoFaDialog";
|
import TwoFaDialog from "@/components/Profile/TwoFaDialog";
|
||||||
import EditProfileModal from "@/components/Profile/EditProfileModal";
|
import EditProfileModal from "@/components/Profile/EditProfileModal";
|
||||||
@@ -18,6 +22,7 @@ import EditAboutModal from "@/components/Profile/EditAboutModal";
|
|||||||
import { parseJsonSafely } from "@/utils/utils";
|
import { parseJsonSafely } from "@/utils/utils";
|
||||||
import EnableEmailDialog from "@/components/Profile/EnableEmailDialog";
|
import EnableEmailDialog from "@/components/Profile/EnableEmailDialog";
|
||||||
import DeleteAccountModal from "@/components/Profile/DeleteAccountModal";
|
import DeleteAccountModal from "@/components/Profile/DeleteAccountModal";
|
||||||
|
import ProfileImageConfirmModal from "@/components/Profile/ProfileImageConfirmModal";
|
||||||
|
|
||||||
function getProfilePhotoMessage(image_status) {
|
function getProfilePhotoMessage(image_status) {
|
||||||
switch (image_status) {
|
switch (image_status) {
|
||||||
@@ -33,7 +38,8 @@ function getProfilePhotoMessage(image_status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CustomerProfilePage() {
|
export default function CustomerProfilePage() {
|
||||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
const { dispatch: globalDispatch, state: globalState } =
|
||||||
|
useContext(GlobalContext);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [twoFa, setTwoFa] = useState(false);
|
const [twoFa, setTwoFa] = useState(false);
|
||||||
const [twoFaDialog, setTwoFaDialog] = useState(false);
|
const [twoFaDialog, setTwoFaDialog] = useState(false);
|
||||||
@@ -49,20 +55,29 @@ export default function CustomerProfilePage() {
|
|||||||
|
|
||||||
const [deleteAccountModal, setDeleteAccountModal] = useState(false);
|
const [deleteAccountModal, setDeleteAccountModal] = useState(false);
|
||||||
|
|
||||||
|
const [showImagePreview, setShowImagePreview] = useState(false);
|
||||||
|
const [selectedImage, setSelectedImage] = useState(null);
|
||||||
|
|
||||||
let sdk = new MkdSDK();
|
let sdk = new MkdSDK();
|
||||||
|
|
||||||
const changeProfilePic = async (e) => {
|
const changeProfilePic = (e) => {
|
||||||
globalDispatch({ type: "START_LOADING" });
|
const file = e.target.files && e.target.files[0];
|
||||||
const file = e.target.files;
|
if (file) {
|
||||||
const formData = new FormData();
|
setSelectedImage(file);
|
||||||
for (let i = 0; i < file.length; i++) {
|
setShowImagePreview(true);
|
||||||
formData.append("file", file[i]);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmUpload = async () => {
|
||||||
|
setShowImagePreview(false);
|
||||||
|
if (!selectedImage) return;
|
||||||
|
globalDispatch({ type: "START_LOADING" });
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", selectedImage);
|
||||||
try {
|
try {
|
||||||
const upload = await sdk.uploadImage(formData);
|
const upload = await sdk.uploadImage(formData);
|
||||||
console.log("upload", upload);
|
|
||||||
sdk.setTable("user");
|
sdk.setTable("user");
|
||||||
const result = await callCustomAPI(
|
await callCustomAPI(
|
||||||
"edit-self",
|
"edit-self",
|
||||||
"post",
|
"post",
|
||||||
{
|
{
|
||||||
@@ -71,9 +86,16 @@ export default function CustomerProfilePage() {
|
|||||||
is_photo_approved: IMAGE_STATUS.IN_REVIEW,
|
is_photo_approved: IMAGE_STATUS.IN_REVIEW,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
""
|
||||||
);
|
);
|
||||||
globalDispatch({ type: "SET_USER_DATA", payload: { ...globalState.user, photo: upload.url, is_photo_approved: IMAGE_STATUS.IN_REVIEW } });
|
globalDispatch({
|
||||||
|
type: "SET_USER_DATA",
|
||||||
|
payload: {
|
||||||
|
...globalState.user,
|
||||||
|
photo: upload.url,
|
||||||
|
is_photo_approved: IMAGE_STATUS.IN_REVIEW,
|
||||||
|
},
|
||||||
|
});
|
||||||
// create notification
|
// create notification
|
||||||
sdk.setTable("notification");
|
sdk.setTable("notification");
|
||||||
await sdk.callRestAPI(
|
await sdk.callRestAPI(
|
||||||
@@ -86,7 +108,7 @@ export default function CustomerProfilePage() {
|
|||||||
type: NOTIFICATION_TYPE.EDIT_USER_PICTURE,
|
type: NOTIFICATION_TYPE.EDIT_USER_PICTURE,
|
||||||
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
||||||
},
|
},
|
||||||
"POST",
|
"POST"
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
@@ -98,9 +120,15 @@ export default function CustomerProfilePage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
globalDispatch({ type: "STOP_LOADING" });
|
globalDispatch({ type: "STOP_LOADING" });
|
||||||
|
setSelectedImage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeProfilePic = async (e) => {
|
const handleCancelUpload = () => {
|
||||||
|
setShowImagePreview(false);
|
||||||
|
setSelectedImage(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeProfilePic = async () => {
|
||||||
try {
|
try {
|
||||||
sdk.setTable("user");
|
sdk.setTable("user");
|
||||||
await callCustomAPI(
|
await callCustomAPI(
|
||||||
@@ -112,9 +140,12 @@ export default function CustomerProfilePage() {
|
|||||||
is_photo_approved: null,
|
is_photo_approved: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
""
|
||||||
);
|
);
|
||||||
globalDispatch({ type: "SET_USER_DATA", payload: { ...globalState.user, photo: null, is_photo_approved: null } });
|
globalDispatch({
|
||||||
|
type: "SET_USER_DATA",
|
||||||
|
payload: { ...globalState.user, photo: null, is_photo_approved: null },
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
type: "SHOW_ERROR",
|
type: "SHOW_ERROR",
|
||||||
@@ -137,14 +168,16 @@ export default function CustomerProfilePage() {
|
|||||||
two_factor_authentication: twoFa != 1 ? 1 : 0,
|
two_factor_authentication: twoFa != 1 ? 1 : 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
""
|
||||||
);
|
);
|
||||||
setTwoFaDialog(false);
|
setTwoFaDialog(false);
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
type: "SHOW_CONFIRMATION",
|
type: "SHOW_CONFIRMATION",
|
||||||
payload: {
|
payload: {
|
||||||
heading: "Success",
|
heading: "Success",
|
||||||
message: `Two factor Authentication ${twoFa == 1 ? "disabled" : "enabled"}`,
|
message: `Two factor Authentication ${
|
||||||
|
twoFa == 1 ? "disabled" : "enabled"
|
||||||
|
}`,
|
||||||
btn: "Ok got it",
|
btn: "Ok got it",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -163,50 +196,66 @@ export default function CustomerProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-[44px] pb-16 normal-case text-[#475467]">
|
<div className='pb-16 pt-[44px] normal-case text-[#475467]'>
|
||||||
<div className="flex flex-wrap-reverse justify-between ">
|
<div className='flex flex-wrap-reverse justify-between '>
|
||||||
<div className="flex max-w-3xl flex-grow flex-col justify-between md:flex-row md:items-center">
|
<div className='flex max-w-3xl flex-grow flex-col justify-between md:flex-row md:items-center'>
|
||||||
<div className="mb-[16px] flex flex-col">
|
<div className='mb-[16px] flex flex-col'>
|
||||||
<h3 className="text-xl font-semibold">Your photo</h3>
|
<h3 className='text-xl font-semibold'>Your photo</h3>
|
||||||
<small className="text-xs md:text-sm">{getProfilePhotoMessage(globalState.user.is_photo_approved)}</small>
|
<small className='text-xs md:text-sm'>
|
||||||
|
{getProfilePhotoMessage(globalState.user.is_photo_approved)}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-tour="photo-step"
|
data-tour='photo-step'
|
||||||
className="flex items-center justify-between">
|
className='flex items-center justify-between'
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={globalState.user.photo ?? "/default.png"}
|
src={globalState.user.photo ?? "/default.png"}
|
||||||
alt=""
|
alt=''
|
||||||
className="photo-step h-[56px] w-[56px] rounded-full object-cover md:mr-[65px] md:h-[64px] md:w-[64px]"
|
className='photo-step h-[56px] w-[56px] rounded-full object-cover md:mr-[65px] md:h-[64px] md:w-[64px]'
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
className="third-step mr-3 cursor-pointer font-semibold underline"
|
className='third-step mr-3 cursor-pointer font-semibold underline'
|
||||||
htmlFor="profilePic"
|
htmlFor='profilePic'
|
||||||
>
|
>
|
||||||
Update{" "}
|
Update{" "}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type='file'
|
||||||
className="hidden"
|
className='hidden'
|
||||||
id="profilePic"
|
id='profilePic'
|
||||||
onChange={changeProfilePic}
|
onChange={changeProfilePic}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
className="underline"
|
className='underline'
|
||||||
onClick={removeProfilePic}
|
onClick={() => {
|
||||||
|
globalDispatch({
|
||||||
|
type: "SHOW_CONFIRMATION",
|
||||||
|
payload: {
|
||||||
|
heading: "Remove Profile Picture?",
|
||||||
|
message:
|
||||||
|
"Are you sure you want to remove your profile picture?",
|
||||||
|
btn: "Yes, Remove",
|
||||||
|
onClose: () => {
|
||||||
|
removeProfilePic();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-12 flex w-full justify-between md:mb-0 md:w-[unset] md:flex-col md:justify-start">
|
<div className='mb-12 flex w-full justify-between md:mb-0 md:w-[unset] md:flex-col md:justify-start'>
|
||||||
<p className="mb-2 self-end">Profile status</p>
|
<p className='mb-2 self-end'>Profile status</p>
|
||||||
<div data-tour="fourth-step" className="flex fourth-step">
|
<div data-tour='fourth-step' className='fourth-step flex'>
|
||||||
{![0, 1].includes(globalState.user.verificationStatus) && (
|
{![0, 1].includes(globalState.user.verificationStatus) && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/verification"
|
to='/account/verification'
|
||||||
className="mr-3 font-semibold text-[#1570EF]"
|
className='mr-3 font-semibold text-[#1570EF]'
|
||||||
>
|
>
|
||||||
Get verified
|
Get verified
|
||||||
</Link>
|
</Link>
|
||||||
@@ -214,7 +263,11 @@ export default function CustomerProfilePage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
`${globalState.user.verificationStatus == 1 ? "login-btn-gradient" : "bg-[#667085]"}` +
|
`${
|
||||||
|
globalState.user.verificationStatus == 1
|
||||||
|
? "login-btn-gradient"
|
||||||
|
: "bg-[#667085]"
|
||||||
|
}` +
|
||||||
" flex min-w-[103px] items-center gap-1 rounded-md p-1 px-2 text-xs uppercase tracking-wider text-white"
|
" flex min-w-[103px] items-center gap-1 rounded-md p-1 px-2 text-xs uppercase tracking-wider text-white"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -255,7 +308,7 @@ export default function CustomerProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className="my-[37px]" />
|
<hr className='my-[37px]' />
|
||||||
<EditProfileModal
|
<EditProfileModal
|
||||||
closeModal={() => setUpdateName(false)}
|
closeModal={() => setUpdateName(false)}
|
||||||
modalOpen={updateName}
|
modalOpen={updateName}
|
||||||
@@ -284,6 +337,16 @@ export default function CustomerProfilePage() {
|
|||||||
isOpen={enableEmailDialog}
|
isOpen={enableEmailDialog}
|
||||||
closeModal={() => setEnableEmailDialog(false)}
|
closeModal={() => setEnableEmailDialog(false)}
|
||||||
/>
|
/>
|
||||||
|
<ProfileImageConfirmModal
|
||||||
|
modalOpen={showImagePreview}
|
||||||
|
modalImage={
|
||||||
|
selectedImage instanceof File
|
||||||
|
? URL.createObjectURL(selectedImage)
|
||||||
|
: selectedImage
|
||||||
|
}
|
||||||
|
onConfirm={handleConfirmUpload}
|
||||||
|
onCancel={handleCancelUpload}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ export default function CustomerVerificationPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
expiry_date: yup.string().test("is-not-in-past", "Not a valid date", (val) => {
|
expiry_date: yup
|
||||||
if (val == "") return false;
|
.string()
|
||||||
const date = new Date(val);
|
.test("is-not-in-past", "Not a valid date", (val) => {
|
||||||
return date.setDate(date.getDate() - 1) > new Date();
|
if (val == "") return false;
|
||||||
}),
|
const date = new Date(val);
|
||||||
|
return date.setDate(date.getDate() - 1) > new Date();
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -35,14 +37,18 @@ export default function CustomerVerificationPage() {
|
|||||||
register,
|
register,
|
||||||
watch,
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({ defaultValues: { selectedType: "Driver's License" }, resolver: yupResolver(schema) });
|
} = useForm({
|
||||||
|
defaultValues: { selectedType: "Driver's License" },
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
const [frontImage, setFrontImage] = useState(null);
|
const [frontImage, setFrontImage] = useState(null);
|
||||||
const [backImage, setBackImage] = useState(null);
|
const [backImage, setBackImage] = useState(null);
|
||||||
const [passport, setPassport] = useState(null);
|
const [passport, setPassport] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
const { dispatch: globalDispatch, state: globalState } =
|
||||||
|
useContext(GlobalContext);
|
||||||
const { dispatch: authDispatch } = useContext(AuthContext);
|
const { dispatch: authDispatch } = useContext(AuthContext);
|
||||||
|
|
||||||
const [verified, setVerified] = useState(false);
|
const [verified, setVerified] = useState(false);
|
||||||
@@ -51,7 +57,8 @@ export default function CustomerVerificationPage() {
|
|||||||
const selectedType = watch("selectedType");
|
const selectedType = watch("selectedType");
|
||||||
|
|
||||||
const isDisabled = () => {
|
const isDisabled = () => {
|
||||||
if (selectedType == "Driver's License" && frontImage && backImage) return false;
|
if (selectedType == "Driver's License" && frontImage && backImage)
|
||||||
|
return false;
|
||||||
if (selectedType == "Passport" && passport) return false;
|
if (selectedType == "Passport" && passport) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -88,7 +95,7 @@ export default function CustomerVerificationPage() {
|
|||||||
image_back: data.backImage ?? null,
|
image_back: data.backImage ?? null,
|
||||||
user_id: Number(localStorage.getItem("user")),
|
user_id: Number(localStorage.getItem("user")),
|
||||||
},
|
},
|
||||||
globalState.user.verificationId ? "PUT" : "POST",
|
globalState.user.verificationId ? "PUT" : "POST"
|
||||||
);
|
);
|
||||||
|
|
||||||
// create notification
|
// create notification
|
||||||
@@ -103,7 +110,7 @@ export default function CustomerVerificationPage() {
|
|||||||
type: NOTIFICATION_TYPE.NEW_ID_VERIFICATION,
|
type: NOTIFICATION_TYPE.NEW_ID_VERIFICATION,
|
||||||
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
||||||
},
|
},
|
||||||
"POST",
|
"POST"
|
||||||
);
|
);
|
||||||
|
|
||||||
setVerified(true);
|
setVerified(true);
|
||||||
@@ -130,66 +137,67 @@ export default function CustomerVerificationPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-16 normal-case">
|
<div className='pb-16 normal-case'>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
className="mr-2 mb-2 inline-flex items-center py-2.5 pr-5 text-center text-sm font-semibold"
|
className='mb-2 mr-2 inline-flex items-center py-2.5 pr-5 text-center text-sm font-semibold'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
type="arrow"
|
type='arrow'
|
||||||
variant="narrow-left"
|
variant='narrow-left'
|
||||||
className="h-4 w-4 stroke-[#667085]"
|
className='h-4 w-4 stroke-[#667085]'
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span className="ml-2">Back</span>
|
<span className='ml-2'>Back</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="mb-4 text-2xl font-semibold md:text-5xl">Identity Verification</h1>
|
<h1 className='mb-4 text-2xl font-semibold md:text-5xl'>
|
||||||
<div className="mb-[32px] max-w-3xl rounded-lg border border-[#EAECF0] bg-[#F9FAFB] px-[24px] py-[16px]">
|
Identity Verification
|
||||||
<h3 className="text-lg flex items-center gap-2 font-semibold">
|
</h1>
|
||||||
|
<div className='mb-[32px] max-w-3xl rounded-lg border border-[#EAECF0] bg-[#F9FAFB] px-[24px] py-[16px]'>
|
||||||
|
<h3 className='text-lg flex items-center gap-2 font-semibold'>
|
||||||
<SecurityIcon />
|
<SecurityIcon />
|
||||||
<span>Safety is our priority</span>
|
<span>Safety is our priority</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="ml-5 max-w-xl text-sm leading-relaxed">
|
<p className='ml-5 max-w-xl text-sm leading-relaxed'>
|
||||||
To establish trust for all parties we verify both hosts and guests. Your personal information is secure. We will never share your information with third parties.
|
To establish trust for all parties we verify both hosts and guests.
|
||||||
|
Your personal information is secure. We will never share your
|
||||||
|
information with third parties.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<p className="mb-2 font-semibold">Verification Documents.</p>
|
<p className='mb-2 font-semibold'>Verification Documents.</p>
|
||||||
<div data-tour="fifth-step" className="fifth-step radio-container mb-8 flex justify-between md:max-w-lg">
|
<div
|
||||||
<label
|
data-tour='sixth-step'
|
||||||
htmlFor="driversLicense"
|
className='sixth-step radio-container mb-8 flex justify-between md:max-w-lg'
|
||||||
className="cursor-pointer"
|
>
|
||||||
>
|
<label htmlFor='driversLicense' className='cursor-pointer'>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type='radio'
|
||||||
id="driversLicense"
|
id='driversLicense'
|
||||||
{...register("selectedType")}
|
{...register("selectedType")}
|
||||||
className="mr-2"
|
className='mr-2'
|
||||||
value="Driver's License"
|
value="Driver's License"
|
||||||
/>
|
/>
|
||||||
<span></span>
|
<span></span>
|
||||||
Driver's License
|
Driver's License
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label htmlFor='passport' className='cursor-pointer'>
|
||||||
htmlFor="passport"
|
|
||||||
className="cursor-pointer"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type='radio'
|
||||||
id="passport"
|
id='passport'
|
||||||
{...register("selectedType")}
|
{...register("selectedType")}
|
||||||
className="mr-2"
|
className='mr-2'
|
||||||
value="Passport"
|
value='Passport'
|
||||||
/>
|
/>
|
||||||
<span></span>
|
<span></span>
|
||||||
Passport
|
Passport
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[#667085]">
|
<div className='text-[#667085]'>
|
||||||
{selectedType == "Driver's License" ? (
|
{selectedType == "Driver's License" ? (
|
||||||
<div className="flex flex-col items-center gap-[16px] md:flex-row">
|
<div className='flex flex-col items-center gap-[16px] md:flex-row'>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
multiple={false}
|
multiple={false}
|
||||||
handleChange={(file) => {
|
handleChange={(file) => {
|
||||||
@@ -197,18 +205,21 @@ export default function CustomerVerificationPage() {
|
|||||||
}}
|
}}
|
||||||
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
||||||
>
|
>
|
||||||
<div className="flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]">
|
<div className='flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]'>
|
||||||
{frontImage?.name ? (
|
{frontImage?.name ? (
|
||||||
<img
|
<img
|
||||||
src={readImage(frontImage, "front-preview")}
|
src={readImage(frontImage, "front-preview")}
|
||||||
id="front-preview"
|
id='front-preview'
|
||||||
className="h-full w-full rounded-sm object-cover"
|
className='h-full w-full rounded-sm object-cover'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h4 className="text-xl font-semibold">Front</h4>
|
<h4 className='text-xl font-semibold'>Front</h4>
|
||||||
<p className="px-[20px]">
|
<p className='px-[20px]'>
|
||||||
<strong className="font-semibold underline">Click to upload</strong> or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
<strong className='font-semibold underline'>
|
||||||
|
Click to upload
|
||||||
|
</strong>{" "}
|
||||||
|
or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -221,18 +232,21 @@ export default function CustomerVerificationPage() {
|
|||||||
}}
|
}}
|
||||||
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
||||||
>
|
>
|
||||||
<div className="flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]">
|
<div className='flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]'>
|
||||||
{backImage?.name ? (
|
{backImage?.name ? (
|
||||||
<img
|
<img
|
||||||
src={readImage(backImage, "back-preview")}
|
src={readImage(backImage, "back-preview")}
|
||||||
id="back-preview"
|
id='back-preview'
|
||||||
className="h-full w-full rounded-sm object-cover"
|
className='h-full w-full rounded-sm object-cover'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h4 className="text-xl font-semibold">Back</h4>
|
<h4 className='text-xl font-semibold'>Back</h4>
|
||||||
<p className="px-[20px]">
|
<p className='px-[20px]'>
|
||||||
<strong className="font-semibold underline">Click to upload</strong> or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
<strong className='font-semibold underline'>
|
||||||
|
Click to upload
|
||||||
|
</strong>{" "}
|
||||||
|
or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -247,18 +261,23 @@ export default function CustomerVerificationPage() {
|
|||||||
}}
|
}}
|
||||||
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
||||||
>
|
>
|
||||||
<div className="flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]">
|
<div className='flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]'>
|
||||||
{passport?.name ? (
|
{passport?.name ? (
|
||||||
<img
|
<img
|
||||||
src={readImage(passport, "passport-preview")}
|
src={readImage(passport, "passport-preview")}
|
||||||
id="passport-preview"
|
id='passport-preview'
|
||||||
className="h-full w-full rounded-sm object-cover"
|
className='h-full w-full rounded-sm object-cover'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h4 className="text-xl font-semibold">Passport page with photo</h4>
|
<h4 className='text-xl font-semibold'>
|
||||||
<p className="px-[20px]">
|
Passport page with photo
|
||||||
<strong className="font-semibold underline">Click to upload</strong> or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
</h4>
|
||||||
|
<p className='px-[20px]'>
|
||||||
|
<strong className='font-semibold underline'>
|
||||||
|
Click to upload
|
||||||
|
</strong>{" "}
|
||||||
|
or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -266,43 +285,61 @@ export default function CustomerVerificationPage() {
|
|||||||
</FileUploader>
|
</FileUploader>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="my-8 max-w-lg">
|
<div className='my-8 max-w-lg'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="expiry_date"
|
htmlFor='expiry_date'
|
||||||
>
|
>
|
||||||
Expiration Date
|
Expiration Date
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type='date'
|
||||||
placeholder="expiry_date"
|
placeholder='expiry_date'
|
||||||
{...register("expiry_date")}
|
{...register("expiry_date")}
|
||||||
className={`focus:shadow-outline !min-h-[40px] w-full rounded border py-2 px-3 leading-tight text-gray-700 focus:outline-none`}
|
className={`focus:shadow-outline !min-h-[40px] w-full rounded border px-3 py-2 leading-tight text-gray-700 focus:outline-none`}
|
||||||
/>
|
/>
|
||||||
{errors.expiry_date?.message && <p className="my-3 rounded-md border border-[#C42945] bg-white py-2 px-3 text-center text-sm normal-case text-[#C42945]">{errors.expiry_date?.message}</p>}
|
{errors.expiry_date?.message && (
|
||||||
|
<p className='my-3 rounded-md border border-[#C42945] bg-white px-3 py-2 text-center text-sm normal-case text-[#C42945]'>
|
||||||
|
{errors.expiry_date?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={loading}
|
loading={loading}
|
||||||
type="submit"
|
type='submit'
|
||||||
className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"} mt-8 w-[333px] max-w-full`}
|
className={`login-btn-gradient submit-doc-btn rounded tracking-wide text-white outline-none focus:outline-none ${
|
||||||
|
loading ? "py-1" : "py-2"
|
||||||
|
} mt-8 w-[333px] max-w-full`}
|
||||||
disabled={isDisabled()}
|
disabled={isDisabled()}
|
||||||
|
data-tour='submit-doc-btn'
|
||||||
>
|
>
|
||||||
Submit Document
|
Submit Document
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</form>
|
</form>
|
||||||
<div className={showVerified ? "popup-container flex items-center justify-center normal-case" : "hidden"}>
|
<div
|
||||||
|
className={
|
||||||
|
showVerified
|
||||||
|
? "popup-container flex items-center justify-center normal-case"
|
||||||
|
: "hidden"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`${verified ? "pop-in" : "pop-out"} w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
className={`${
|
||||||
|
verified ? "pop-in" : "pop-out"
|
||||||
|
} w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<h2 className="mb-4 text-3xl font-semibold">
|
<h2 className='mb-4 text-3xl font-semibold'>
|
||||||
<GreenCheckIcon />
|
<GreenCheckIcon />
|
||||||
Document received
|
Document received
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-4 text-sm text-gray-500">Once we verify your document you will receive an email. It usually takes up to 24 hours.</p>
|
<p className='mb-4 text-sm text-gray-500'>
|
||||||
|
Once we verify your document you will receive an email. It usually
|
||||||
|
takes up to 24 hours.
|
||||||
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
className="login-btn-gradient mt-4 w-full rounded py-2 tracking-wide text-white outline-none focus:outline-none"
|
className='login-btn-gradient mt-4 w-full rounded py-2 tracking-wide text-white outline-none focus:outline-none'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVerified(false);
|
setVerified(false);
|
||||||
navigate(searchParams.get("redirect_uri") ?? -1);
|
navigate(searchParams.get("redirect_uri") ?? -1);
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import MkdSDK from "@/utils/MkdSDK";
|
|||||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||||
import Skeleton from "react-loading-skeleton";
|
import Skeleton from "react-loading-skeleton";
|
||||||
import { formatDate } from "@/utils/date-time-utils";
|
import { formatDate } from "@/utils/date-time-utils";
|
||||||
import { IMAGE_STATUS, NOTIFICATION_STATUS, NOTIFICATION_TYPE } from "@/utils/constants";
|
import {
|
||||||
|
IMAGE_STATUS,
|
||||||
|
NOTIFICATION_STATUS,
|
||||||
|
NOTIFICATION_TYPE,
|
||||||
|
} from "@/utils/constants";
|
||||||
import SwitchBulkMode from "@/components/SwitchBulkMode";
|
import SwitchBulkMode from "@/components/SwitchBulkMode";
|
||||||
import TwoFaDialog from "@/components/Profile/TwoFaDialog";
|
import TwoFaDialog from "@/components/Profile/TwoFaDialog";
|
||||||
import EditProfileModal from "@/components/Profile/EditProfileModal";
|
import EditProfileModal from "@/components/Profile/EditProfileModal";
|
||||||
@@ -18,6 +22,7 @@ import EditAboutModal from "@/components/Profile/EditAboutModal";
|
|||||||
import { parseJsonSafely } from "@/utils/utils";
|
import { parseJsonSafely } from "@/utils/utils";
|
||||||
import EnableEmailDialog from "@/components/Profile/EnableEmailDialog";
|
import EnableEmailDialog from "@/components/Profile/EnableEmailDialog";
|
||||||
import DeleteAccountModal from "@/components/Profile/DeleteAccountModal";
|
import DeleteAccountModal from "@/components/Profile/DeleteAccountModal";
|
||||||
|
import ProfileImageConfirmModal from "@/components/Profile/ProfileImageConfirmModal";
|
||||||
|
|
||||||
function getProfilePhotoMessage(image_status) {
|
function getProfilePhotoMessage(image_status) {
|
||||||
switch (image_status) {
|
switch (image_status) {
|
||||||
@@ -33,7 +38,8 @@ function getProfilePhotoMessage(image_status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function HostProfilePage() {
|
export default function HostProfilePage() {
|
||||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
const { dispatch: globalDispatch, state: globalState } =
|
||||||
|
useContext(GlobalContext);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [twoFa, setTwoFa] = useState(false);
|
const [twoFa, setTwoFa] = useState(false);
|
||||||
const [twoFaDialog, setTwoFaDialog] = useState(false);
|
const [twoFaDialog, setTwoFaDialog] = useState(false);
|
||||||
@@ -49,20 +55,29 @@ export default function HostProfilePage() {
|
|||||||
|
|
||||||
const [deleteAccountModal, setDeleteAccountModal] = useState(false);
|
const [deleteAccountModal, setDeleteAccountModal] = useState(false);
|
||||||
|
|
||||||
|
const [showImagePreview, setShowImagePreview] = useState(false);
|
||||||
|
const [selectedImage, setSelectedImage] = useState(null);
|
||||||
|
|
||||||
let sdk = new MkdSDK();
|
let sdk = new MkdSDK();
|
||||||
|
|
||||||
const changeProfilePic = async (e) => {
|
const changeProfilePic = (e) => {
|
||||||
globalDispatch({ type: "START_LOADING" });
|
const file = e.target.files && e.target.files[0];
|
||||||
const file = e.target.files;
|
if (file) {
|
||||||
const formData = new FormData();
|
setSelectedImage(file);
|
||||||
for (let i = 0; i < file.length; i++) {
|
setShowImagePreview(true);
|
||||||
formData.append("file", file[i]);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmUpload = async () => {
|
||||||
|
setShowImagePreview(false);
|
||||||
|
if (!selectedImage) return;
|
||||||
|
globalDispatch({ type: "START_LOADING" });
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", selectedImage);
|
||||||
try {
|
try {
|
||||||
const upload = await sdk.uploadImage(formData);
|
const upload = await sdk.uploadImage(formData);
|
||||||
console.log("upload", upload);
|
|
||||||
sdk.setTable("user");
|
sdk.setTable("user");
|
||||||
const result = await callCustomAPI(
|
await callCustomAPI(
|
||||||
"edit-self",
|
"edit-self",
|
||||||
"post",
|
"post",
|
||||||
{
|
{
|
||||||
@@ -71,9 +86,16 @@ export default function HostProfilePage() {
|
|||||||
is_photo_approved: IMAGE_STATUS.IN_REVIEW,
|
is_photo_approved: IMAGE_STATUS.IN_REVIEW,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
""
|
||||||
);
|
);
|
||||||
globalDispatch({ type: "SET_USER_DATA", payload: { ...globalState.user, photo: upload.url, is_photo_approved: IMAGE_STATUS.IN_REVIEW } });
|
globalDispatch({
|
||||||
|
type: "SET_USER_DATA",
|
||||||
|
payload: {
|
||||||
|
...globalState.user,
|
||||||
|
photo: upload.url,
|
||||||
|
is_photo_approved: IMAGE_STATUS.IN_REVIEW,
|
||||||
|
},
|
||||||
|
});
|
||||||
// create notification
|
// create notification
|
||||||
sdk.setTable("notification");
|
sdk.setTable("notification");
|
||||||
await sdk.callRestAPI(
|
await sdk.callRestAPI(
|
||||||
@@ -86,7 +108,7 @@ export default function HostProfilePage() {
|
|||||||
type: NOTIFICATION_TYPE.EDIT_USER_PICTURE,
|
type: NOTIFICATION_TYPE.EDIT_USER_PICTURE,
|
||||||
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
||||||
},
|
},
|
||||||
"POST",
|
"POST"
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
@@ -98,9 +120,15 @@ export default function HostProfilePage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
globalDispatch({ type: "STOP_LOADING" });
|
globalDispatch({ type: "STOP_LOADING" });
|
||||||
|
setSelectedImage(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeProfilePic = async (e) => {
|
const handleCancelUpload = () => {
|
||||||
|
setShowImagePreview(false);
|
||||||
|
setSelectedImage(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeProfilePic = async () => {
|
||||||
try {
|
try {
|
||||||
sdk.setTable("user");
|
sdk.setTable("user");
|
||||||
await callCustomAPI(
|
await callCustomAPI(
|
||||||
@@ -112,9 +140,12 @@ export default function HostProfilePage() {
|
|||||||
is_photo_approved: null,
|
is_photo_approved: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
""
|
||||||
);
|
);
|
||||||
globalDispatch({ type: "SET_USER_DATA", payload: { ...globalState.user, photo: null, is_photo_approved: null } });
|
globalDispatch({
|
||||||
|
type: "SET_USER_DATA",
|
||||||
|
payload: { ...globalState.user, photo: null, is_photo_approved: null },
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
type: "SHOW_ERROR",
|
type: "SHOW_ERROR",
|
||||||
@@ -137,14 +168,16 @@ export default function HostProfilePage() {
|
|||||||
two_factor_authentication: twoFa != 1 ? 1 : 0,
|
two_factor_authentication: twoFa != 1 ? 1 : 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"",
|
""
|
||||||
);
|
);
|
||||||
setTwoFaDialog(false);
|
setTwoFaDialog(false);
|
||||||
globalDispatch({
|
globalDispatch({
|
||||||
type: "SHOW_CONFIRMATION",
|
type: "SHOW_CONFIRMATION",
|
||||||
payload: {
|
payload: {
|
||||||
heading: "Success",
|
heading: "Success",
|
||||||
message: `Two factor Authentication ${twoFa == 1 ? "disabled" : "enabled"}`,
|
message: `Two factor Authentication ${
|
||||||
|
twoFa == 1 ? "disabled" : "enabled"
|
||||||
|
}`,
|
||||||
btn: "Ok got it",
|
btn: "Ok got it",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -163,51 +196,67 @@ export default function HostProfilePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pt-[44px] pb-16 normal-case text-[#475467]">
|
<div className='pb-16 pt-[44px] normal-case text-[#475467]'>
|
||||||
<div className="flex flex-wrap-reverse justify-between ">
|
<div className='flex flex-wrap-reverse justify-between '>
|
||||||
<div className="flex max-w-3xl flex-grow flex-col justify-between md:flex-row md:items-center">
|
<div className='flex max-w-3xl flex-grow flex-col justify-between md:flex-row md:items-center'>
|
||||||
<div className="mb-[16px] flex flex-col">
|
<div className='mb-[16px] flex flex-col'>
|
||||||
<h3 className="text-xl font-semibold">Your photo</h3>
|
<h3 className='text-xl font-semibold'>Your photo</h3>
|
||||||
<small className="text-xs md:text-sm">{getProfilePhotoMessage(globalState.user.is_photo_approved)}</small>
|
<small className='text-xs md:text-sm'>
|
||||||
|
{getProfilePhotoMessage(globalState.user.is_photo_approved)}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
data-tour="photo-step"
|
data-tour='photo-step'
|
||||||
className="flex items-center justify-between">
|
className='flex items-center justify-between'
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={globalState.user.photo ?? "/default.png"}
|
src={globalState.user.photo ?? "/default.png"}
|
||||||
alt=""
|
alt=''
|
||||||
className="h-[56px] w-[56px] rounded-full object-cover md:mr-[65px] md:h-[64px] md:w-[64px]"
|
className='h-[56px] w-[56px] rounded-full object-cover md:mr-[65px] md:h-[64px] md:w-[64px]'
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
className="photo-step mr-3 cursor-pointer font-semibold underline"
|
className='photo-step mr-3 cursor-pointer font-semibold underline'
|
||||||
htmlFor="profilePic"
|
htmlFor='profilePic'
|
||||||
>
|
>
|
||||||
Update{" "}
|
Update{" "}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type='file'
|
||||||
className="hidden"
|
className='hidden'
|
||||||
id="profilePic"
|
id='profilePic'
|
||||||
onChange={changeProfilePic}
|
onChange={changeProfilePic}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button
|
<button
|
||||||
className="underline"
|
className='underline'
|
||||||
id="remove_profile_pic"
|
id='remove_profile_pic'
|
||||||
onClick={removeProfilePic}
|
onClick={() => {
|
||||||
|
globalDispatch({
|
||||||
|
type: "SHOW_CONFIRMATION",
|
||||||
|
payload: {
|
||||||
|
heading: "Remove Profile Picture?",
|
||||||
|
message:
|
||||||
|
"Are you sure you want to remove your profile picture?",
|
||||||
|
btn: "Yes, Remove",
|
||||||
|
onClose: () => {
|
||||||
|
removeProfilePic();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-12 flex w-full justify-between md:mb-0 md:w-[unset] md:flex-col md:justify-start">
|
<div className='mb-12 flex w-full justify-between md:mb-0 md:w-[unset] md:flex-col md:justify-start'>
|
||||||
<p className="mb-2 self-end">Profile status</p>
|
<p className='mb-2 self-end'>Profile status</p>
|
||||||
<div data-tour="fourth-step" className="flex fourth-step">
|
<div data-tour='fourth-step' className='fourth-step flex'>
|
||||||
{![0, 1].includes(globalState.user.verificationStatus) && (
|
{![0, 1].includes(globalState.user.verificationStatus) && (
|
||||||
<Link
|
<Link
|
||||||
to="/account/verification"
|
to='/account/verification'
|
||||||
className="mr-3 font-semibold text-[#1570EF]"
|
className='mr-3 font-semibold text-[#1570EF]'
|
||||||
>
|
>
|
||||||
Get verified
|
Get verified
|
||||||
</Link>
|
</Link>
|
||||||
@@ -215,7 +264,11 @@ export default function HostProfilePage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
`${globalState.user.verificationStatus == 1 ? "login-btn-gradient" : "bg-[#667085]"}` +
|
`${
|
||||||
|
globalState.user.verificationStatus == 1
|
||||||
|
? "login-btn-gradient"
|
||||||
|
: "bg-[#667085]"
|
||||||
|
}` +
|
||||||
" flex min-w-[103px] items-center gap-1 rounded-md p-1 px-2 text-xs uppercase tracking-wider text-white"
|
" flex min-w-[103px] items-center gap-1 rounded-md p-1 px-2 text-xs uppercase tracking-wider text-white"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -225,28 +278,28 @@ export default function HostProfilePage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NotVerifiedIcon />
|
<NotVerifiedIcon />
|
||||||
<span className="">Pending</span>
|
<span className=''>Pending</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case 1:
|
case 1:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NotVerifiedIcon />
|
<NotVerifiedIcon />
|
||||||
<span className="">Verified</span>
|
<span className=''>Verified</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case 2:
|
case 2:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NotVerifiedIcon />
|
<NotVerifiedIcon />
|
||||||
<span className="">Verification Declined</span>
|
<span className=''>Verification Declined</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NotVerifiedIcon />
|
<NotVerifiedIcon />
|
||||||
<span className="">Not verified</span>
|
<span className=''>Not verified</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -256,17 +309,17 @@ export default function HostProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className="my-[37px]" />
|
<hr className='my-[37px]' />
|
||||||
<div className="grid sm:flex flex-co items-start gap-4">
|
<div className='flex-co grid items-start gap-4 sm:flex'>
|
||||||
<Link
|
<Link
|
||||||
to={"/account/profile/rules-templates"}
|
to={"/account/profile/rules-templates"}
|
||||||
className="rounded-md border border-primary-dark px-4 py-2 text-sm text-black duration-200 hover:bg-primary-dark hover:text-white"
|
className='rounded-md border border-primary-dark px-4 py-2 text-sm text-black duration-200 hover:bg-primary-dark hover:text-white'
|
||||||
>
|
>
|
||||||
Manage Property Rules Template
|
Manage Property Rules Template
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to={"/account/profile/rules-templates/add"}
|
to={"/account/profile/rules-templates/add"}
|
||||||
className="rounded-md border border-primary-dark px-4 py-2 text-sm text-black duration-200 hover:bg-primary-dark hover:text-white"
|
className='rounded-md border border-primary-dark px-4 py-2 text-sm text-black duration-200 hover:bg-primary-dark hover:text-white'
|
||||||
>
|
>
|
||||||
Add Property Rules Template
|
Add Property Rules Template
|
||||||
</Link>
|
</Link>
|
||||||
@@ -299,6 +352,16 @@ export default function HostProfilePage() {
|
|||||||
isOpen={enableEmailDialog}
|
isOpen={enableEmailDialog}
|
||||||
closeModal={() => setEnableEmailDialog(false)}
|
closeModal={() => setEnableEmailDialog(false)}
|
||||||
/>
|
/>
|
||||||
|
<ProfileImageConfirmModal
|
||||||
|
modalOpen={showImagePreview}
|
||||||
|
modalImage={
|
||||||
|
selectedImage instanceof File
|
||||||
|
? URL.createObjectURL(selectedImage)
|
||||||
|
: selectedImage
|
||||||
|
}
|
||||||
|
onConfirm={handleConfirmUpload}
|
||||||
|
onCancel={handleCancelUpload}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import MkdSDK from "@/utils/MkdSDK";
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { GlobalContext } from "@/globalContext";
|
import { GlobalContext } from "@/globalContext";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { SPACE_CATEGORY_SIZES, NOTIFICATION_STATUS, NOTIFICATION_TYPE, SPACE_STATUS } from "@/utils/constants";
|
import {
|
||||||
|
SPACE_CATEGORY_SIZES,
|
||||||
|
NOTIFICATION_STATUS,
|
||||||
|
NOTIFICATION_TYPE,
|
||||||
|
SPACE_STATUS,
|
||||||
|
} from "@/utils/constants";
|
||||||
import CustomLocationAutoCompleteV2 from "@/components/CustomLocationAutoCompleteV2";
|
import CustomLocationAutoCompleteV2 from "@/components/CustomLocationAutoCompleteV2";
|
||||||
import CustomSelectV2 from "@/components/CustomSelectV2";
|
import CustomSelectV2 from "@/components/CustomSelectV2";
|
||||||
import CounterV2 from "@/components/CounterV2";
|
import CounterV2 from "@/components/CounterV2";
|
||||||
@@ -24,7 +29,8 @@ const ctrl = new AbortController();
|
|||||||
const EditPropertySpacePage = () => {
|
const EditPropertySpacePage = () => {
|
||||||
const { dispatch: authDispatch } = useContext(AuthContext);
|
const { dispatch: authDispatch } = useContext(AuthContext);
|
||||||
const { spaceData, dispatch } = useSpaceContext();
|
const { spaceData, dispatch } = useSpaceContext();
|
||||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
const { dispatch: globalDispatch, state: globalState } =
|
||||||
|
useContext(GlobalContext);
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const mode = searchParams.get("mode");
|
const mode = searchParams.get("mode");
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -41,7 +47,11 @@ const EditPropertySpacePage = () => {
|
|||||||
rate: yup.number().typeError("Must be a number").positive().integer(),
|
rate: yup.number().typeError("Must be a number").positive().integer(),
|
||||||
description: yup.string().required("This field is required"),
|
description: yup.string().required("This field is required"),
|
||||||
rule: yup.string(),
|
rule: yup.string(),
|
||||||
max_capacity: yup.number().required("This field is required").min(1).typeError("This field is required"),
|
max_capacity: yup
|
||||||
|
.number()
|
||||||
|
.required("This field is required")
|
||||||
|
.min(1)
|
||||||
|
.typeError("This field is required"),
|
||||||
additional_guest_rate: yup.string(),
|
additional_guest_rate: yup.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +89,12 @@ const EditPropertySpacePage = () => {
|
|||||||
const where = [`ergo_property_spaces.id = ${id}`];
|
const where = [`ergo_property_spaces.id = ${id}`];
|
||||||
const user_id = localStorage.getItem("user");
|
const user_id = localStorage.getItem("user");
|
||||||
try {
|
try {
|
||||||
const result = await sdk.callRawAPI("/v2/api/custom/ergo/popular/PAGINATE", { page: 1, limit: 1, user_id: Number(user_id), where, all: true }, "POST", ctrl.signal);
|
const result = await sdk.callRawAPI(
|
||||||
|
"/v2/api/custom/ergo/popular/PAGINATE",
|
||||||
|
{ page: 1, limit: 1, user_id: Number(user_id), where, all: true },
|
||||||
|
"POST",
|
||||||
|
ctrl.signal
|
||||||
|
);
|
||||||
if (Array.isArray(result.list) && result.list.length > 0) {
|
if (Array.isArray(result.list) && result.list.length > 0) {
|
||||||
setCurrSpace(result.list[0]);
|
setCurrSpace(result.list[0]);
|
||||||
}
|
}
|
||||||
@@ -102,10 +117,29 @@ const EditPropertySpacePage = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mode === "edit" && currSpace && Object.keys(currSpace).length > 0) {
|
||||||
|
setValue("category", currSpace.space_id ?? "");
|
||||||
|
setValue("id", currSpace.id ?? "");
|
||||||
|
setValue("name", currSpace.name ?? "");
|
||||||
|
setValue("rate", currSpace.rate ?? "");
|
||||||
|
setValue("max_capacity", currSpace.max_capacity ?? 0);
|
||||||
|
setValue("description", currSpace.description ?? "");
|
||||||
|
setValue("rule", currSpace.rule ?? "");
|
||||||
|
setValue("zip", currSpace.zip ?? "");
|
||||||
|
setValue("country", currSpace.country ?? "");
|
||||||
|
setValue("city", currSpace.city ?? "");
|
||||||
|
setValue("address_line_1", currSpace.address_line_1 ?? "");
|
||||||
|
setValue("address_line_2", currSpace.address_line_2 ?? "");
|
||||||
|
setValue("additional_guest_rate", currSpace.additional_guest_rate ?? "");
|
||||||
|
setValue("size", currSpace.size ?? 0);
|
||||||
|
}
|
||||||
|
}, [currSpace, mode, setValue]);
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
const result = extractLocationInfo(data?.city)
|
const result = extractLocationInfo(data?.city);
|
||||||
data.city = (result[0]);
|
data.city = result[0];
|
||||||
data.country = (result[1]);
|
data.country = result[1];
|
||||||
console.log("submitting", data);
|
console.log("submitting", data);
|
||||||
const host_id = localStorage.getItem("user");
|
const host_id = localStorage.getItem("user");
|
||||||
globalDispatch({ type: "START_LOADING" });
|
globalDispatch({ type: "START_LOADING" });
|
||||||
@@ -127,7 +161,7 @@ const EditPropertySpacePage = () => {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
rule: data.rule,
|
rule: data.rule,
|
||||||
},
|
},
|
||||||
"PUT",
|
"PUT"
|
||||||
);
|
);
|
||||||
|
|
||||||
sdk.setTable("property_spaces");
|
sdk.setTable("property_spaces");
|
||||||
@@ -143,7 +177,7 @@ const EditPropertySpacePage = () => {
|
|||||||
additional_guest_rate: data.additional_guest_rate,
|
additional_guest_rate: data.additional_guest_rate,
|
||||||
size: hasSizes ? data.size : SPACE_CATEGORY_SIZES.UNSET,
|
size: hasSizes ? data.size : SPACE_CATEGORY_SIZES.UNSET,
|
||||||
},
|
},
|
||||||
"PUT",
|
"PUT"
|
||||||
);
|
);
|
||||||
|
|
||||||
// create notification
|
// create notification
|
||||||
@@ -158,7 +192,7 @@ const EditPropertySpacePage = () => {
|
|||||||
type: NOTIFICATION_TYPE.EDIT_PROPERTY_SPACE,
|
type: NOTIFICATION_TYPE.EDIT_PROPERTY_SPACE,
|
||||||
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
||||||
},
|
},
|
||||||
"POST",
|
"POST"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (draftType === "continue") {
|
if (draftType === "continue") {
|
||||||
@@ -168,7 +202,10 @@ const EditPropertySpacePage = () => {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
tokenExpireError(authDispatch, err.message);
|
tokenExpireError(authDispatch, err.message);
|
||||||
globalDispatch({ type: "SHOW_ERROR", payload: { heading: "Edit Space Failed", message: err.message } });
|
globalDispatch({
|
||||||
|
type: "SHOW_ERROR",
|
||||||
|
payload: { heading: "Edit Space Failed", message: err.message },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
globalDispatch({ type: "STOP_LOADING" });
|
globalDispatch({ type: "STOP_LOADING" });
|
||||||
};
|
};
|
||||||
@@ -181,166 +218,268 @@ const EditPropertySpacePage = () => {
|
|||||||
{ label: "X-Large", value: SPACE_CATEGORY_SIZES.X_LARGE },
|
{ label: "X-Large", value: SPACE_CATEGORY_SIZES.X_LARGE },
|
||||||
];
|
];
|
||||||
const category = watch("category");
|
const category = watch("category");
|
||||||
const hasSizes = globalState.spaceCategories.find((ctg) => ctg.id == Number(category))?.has_sizes == 1;
|
const hasSizes =
|
||||||
|
globalState.spaceCategories.find((ctg) => ctg.id == Number(category))
|
||||||
|
?.has_sizes == 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen pb-40">
|
<div className='min-h-screen pb-40'>
|
||||||
<form
|
<form
|
||||||
className="text-sm md:max-w-lg"
|
className='text-sm md:max-w-lg'
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
>
|
>
|
||||||
<h1 className="mb-8 text-3xl font-bold md:text-4xl">Space Details</h1>
|
<h1 className='mb-8 text-3xl font-bold md:text-4xl'>Space Details</h1>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="name"
|
htmlFor='name'
|
||||||
>
|
>
|
||||||
Property name {errors.name?.message ? <span className="text-xs font-normal italic text-red-500">{errors.name?.message}</span> : ""}
|
Property name{" "}
|
||||||
|
{errors.name?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.name?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete='off'
|
||||||
placeholder=""
|
placeholder=''
|
||||||
{...register("name")}
|
{...register("name")}
|
||||||
className={`w-full rounded border py-2 px-3 leading-tight text-gray-700 ${errors.name?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
|
errors.name?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="address_line_1"
|
htmlFor='address_line_1'
|
||||||
>
|
>
|
||||||
Address Line 1 {errors.address_line_1?.message ? <span className="text-xs font-normal italic text-red-500">{errors.address_line_1?.message}</span> : ""}
|
Address Line 1{" "}
|
||||||
|
{errors.address_line_1?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.address_line_1?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<CustomLocationAutoCompleteV2
|
<CustomLocationAutoCompleteV2
|
||||||
control={control}
|
control={control}
|
||||||
setValue={(val) => setValue("address_line_1", val)}
|
setValue={(val) => setValue("address_line_1", val)}
|
||||||
name="address_line_1"
|
name='address_line_1'
|
||||||
className={`w-full rounded border py-2 px-3 leading-tight text-gray-700 ${errors.address_line_1?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
placeholder=""
|
errors.address_line_1?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
|
placeholder=''
|
||||||
hideIcons
|
hideIcons
|
||||||
suggestionType={["(cities)"]}
|
suggestionType={["(cities)"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="address_line_2"
|
htmlFor='address_line_2'
|
||||||
>
|
>
|
||||||
Address Line 2 {errors.address_line_2?.message ? <span className="text-xs font-normal italic text-red-500">{errors.address_line_2?.message}</span> : ""}
|
Address Line 2{" "}
|
||||||
|
{errors.address_line_2?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.address_line_2?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<CustomLocationAutoCompleteV2
|
<CustomLocationAutoCompleteV2
|
||||||
control={control}
|
control={control}
|
||||||
setValue={(val) => setValue("address_line_2", val)}
|
setValue={(val) => setValue("address_line_2", val)}
|
||||||
name="address_line_2"
|
name='address_line_2'
|
||||||
className={`w-full rounded border py-2 px-3 leading-tight text-gray-700 ${errors.address_line_2?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
placeholder=""
|
errors.address_line_2?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
|
placeholder=''
|
||||||
hideIcons
|
hideIcons
|
||||||
suggestionType={["(cities)"]}
|
suggestionType={["(cities)"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="city"
|
htmlFor='city'
|
||||||
>
|
>
|
||||||
City {errors.city?.message ? <span className="text-xs font-normal italic text-red-500">{errors.city?.message}</span> : ""}
|
City{" "}
|
||||||
|
{errors.city?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.city?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<CustomLocationAutoCompleteV2
|
<CustomLocationAutoCompleteV2
|
||||||
control={control}
|
control={control}
|
||||||
setValue={(val) => setValue("city", val)}
|
setValue={(val) => setValue("city", val)}
|
||||||
name="city"
|
name='city'
|
||||||
className={`w-full rounded border py-2 px-3 leading-tight text-gray-700 ${errors.city?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
placeholder=""
|
errors.city?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
|
placeholder=''
|
||||||
hideIcons
|
hideIcons
|
||||||
suggestionType={["(cities)"]}
|
suggestionType={["(cities)"]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="zip"
|
htmlFor='zip'
|
||||||
>
|
>
|
||||||
Zip code {errors.zip?.message ? <span className="text-xs font-normal italic text-red-500">{errors.zip?.message}</span> : ""}
|
Zip code{" "}
|
||||||
|
{errors.zip?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.zip?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
placeholder=""
|
placeholder=''
|
||||||
{...register("zip")}
|
{...register("zip")}
|
||||||
className={` focus:shadow-outline $ w-full rounded border py-2 px-3 leading-tight text-gray-700 ${errors.zip?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"
|
className={` focus:shadow-outline $ w-full rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
}`}
|
errors.zip?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="category"
|
htmlFor='category'
|
||||||
>
|
>
|
||||||
Category {errors.category?.message ? <span className="text-xs font-normal italic text-red-500">{errors.category?.message}</span> : ""}
|
Category{" "}
|
||||||
|
{errors.category?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.category?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<CustomSelectV2
|
<CustomSelectV2
|
||||||
items={globalState.spaceCategories}
|
items={globalState.spaceCategories}
|
||||||
labelField="category"
|
labelField='category'
|
||||||
valueField="id"
|
valueField='id'
|
||||||
containerClassName=""
|
containerClassName=''
|
||||||
className={`w-full border py-2 px-3 ${errors.category?.message ? "ring-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full border px-3 py-2 ${
|
||||||
openClassName="ring-primary ring-2"
|
errors.category?.message
|
||||||
|
? "ring-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
|
openClassName='ring-primary ring-2'
|
||||||
placeholder={"Select a category"}
|
placeholder={"Select a category"}
|
||||||
control={control}
|
control={control}
|
||||||
name="category"
|
name='category'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="rate"
|
htmlFor='rate'
|
||||||
>
|
>
|
||||||
Hourly rate {errors.rate?.message ? <span className="text-xs font-normal italic text-red-500">{errors.rate?.message}</span> : ""}
|
Hourly rate{" "}
|
||||||
|
{errors.rate?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.rate?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex">
|
<div className='flex'>
|
||||||
<span className="inline-flex items-center rounded-l-md border border-r-0 border-gray-300 bg-gray-100 px-4 text-sm">$</span>
|
<span className='inline-flex items-center rounded-l-md border border-r-0 border-gray-300 bg-gray-100 px-4 text-sm'>
|
||||||
|
$
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
placeholder=""
|
placeholder=''
|
||||||
type="number"
|
type='number'
|
||||||
{...register("rate")}
|
{...register("rate")}
|
||||||
className={`remove-arrow focus:shadow-outline w-full rounded rounded-l-none border py-2 px-3 leading-tight text-gray-700 ${errors.rate?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"
|
className={`remove-arrow focus:shadow-outline w-full rounded rounded-l-none border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
}`}
|
errors.rate?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="description"
|
htmlFor='description'
|
||||||
>
|
>
|
||||||
Description {errors.description?.message ? <span className="text-xs font-normal italic text-red-500">{errors.description?.message}</span> : ""}
|
Description{" "}
|
||||||
|
{errors.description?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.description?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder=""
|
placeholder=''
|
||||||
{...register("description")}
|
{...register("description")}
|
||||||
className={`w-full resize-none rounded border py-2 px-3 leading-tight text-gray-700 ${errors.description?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"
|
className={`w-full resize-none rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
}`}
|
errors.description?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
rows={10}
|
rows={10}
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="rule"
|
htmlFor='rule'
|
||||||
>
|
>
|
||||||
Property rules
|
Property rules
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder=""
|
placeholder=''
|
||||||
{...register("rule")}
|
{...register("rule")}
|
||||||
className={`w-full resize-none rounded border py-2 px-3 leading-tight text-gray-700 ${errors.rule?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full resize-none rounded border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
|
errors.rule?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
rows={10}
|
rows={10}
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6 flex items-center justify-between">
|
<div className='mb-6 flex items-center justify-between'>
|
||||||
<p className="font-semibold">* Max number of guests {errors.max_capacity?.message ? <span className="text-xs font-normal italic text-red-500">{errors.max_capacity?.message}</span> : ""}</p>
|
<p className='font-semibold'>
|
||||||
|
* Max number of guests{" "}
|
||||||
|
{errors.max_capacity?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.max_capacity?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<CounterV2
|
<CounterV2
|
||||||
name="max_capacity"
|
name='max_capacity'
|
||||||
control={control}
|
control={control}
|
||||||
setValue={(val) => setValue("max_capacity", val)}
|
setValue={(val) => setValue("max_capacity", val)}
|
||||||
/>
|
/>
|
||||||
@@ -348,58 +487,82 @@ const EditPropertySpacePage = () => {
|
|||||||
{hasSizes && (
|
{hasSizes && (
|
||||||
<div className={`mb-8`}>
|
<div className={`mb-8`}>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="size"
|
htmlFor='size'
|
||||||
>
|
>
|
||||||
Size (<span className="text-sm font-normal italic">optional </span>) {errors.size?.message ? <span className="text-xs font-normal italic text-red-500">{errors.size?.message}</span> : ""}
|
Size (
|
||||||
|
<span className='text-sm font-normal italic'>optional </span>){" "}
|
||||||
|
{errors.size?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.size?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<CustomSelectV2
|
<CustomSelectV2
|
||||||
shouldUnregister={false}
|
shouldUnregister={false}
|
||||||
items={SIZES}
|
items={SIZES}
|
||||||
labelField="label"
|
labelField='label'
|
||||||
valueField="value"
|
valueField='value'
|
||||||
containerClassName=""
|
containerClassName=''
|
||||||
className={`w-full border py-2 px-3 ${errors.size?.message ? "ring-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
className={`w-full border px-3 py-2 ${
|
||||||
openClassName="ring-primary ring-2"
|
errors.size?.message
|
||||||
|
? "ring-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
|
openClassName='ring-primary ring-2'
|
||||||
placeholder={"Select size"}
|
placeholder={"Select size"}
|
||||||
control={control}
|
control={control}
|
||||||
name="size"
|
name='size'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className='mb-8'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="additional_guest_rate"
|
htmlFor='additional_guest_rate'
|
||||||
>
|
>
|
||||||
Hourly rate for additional {hasSizes ? "guests" : "guests"} (<span className="text-sm font-normal italic">optional </span>){" "}
|
Hourly rate for additional {hasSizes ? "guests" : "guests"} (
|
||||||
{errors.additional_guest_rate?.message ? <span className="text-xs font-normal italic text-red-500">{errors.additional_guest_rate?.message}</span> : ""}
|
<span className='text-sm font-normal italic'>optional </span>){" "}
|
||||||
|
{errors.additional_guest_rate?.message ? (
|
||||||
|
<span className='text-xs font-normal italic text-red-500'>
|
||||||
|
{errors.additional_guest_rate?.message}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex">
|
<div className='flex'>
|
||||||
<span className="inline-flex items-center rounded-l-md border border-r-0 border-gray-300 bg-gray-100 px-4 text-sm">$</span>
|
<span className='inline-flex items-center rounded-l-md border border-r-0 border-gray-300 bg-gray-100 px-4 text-sm'>
|
||||||
|
$
|
||||||
|
</span>
|
||||||
<input
|
<input
|
||||||
placeholder=""
|
placeholder=''
|
||||||
type="number"
|
type='number'
|
||||||
{...register("additional_guest_rate")}
|
{...register("additional_guest_rate")}
|
||||||
className={`remove-arrow w-full rounded rounded-l-none border py-2 px-3 leading-tight text-gray-700 ${errors.additional_guest_rate?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"
|
className={`remove-arrow w-full rounded rounded-l-none border px-3 py-2 leading-tight text-gray-700 ${
|
||||||
}`}
|
errors.additional_guest_rate?.message
|
||||||
|
? "border-red-500 focus:outline-red-500"
|
||||||
|
: "focus-within:outline-primary"
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr className="my-[48px]" />
|
<hr className='my-[48px]' />
|
||||||
<button
|
<button
|
||||||
onClick={() => setDraftType("continue")}
|
onClick={() => setDraftType("continue")}
|
||||||
type="submit"
|
type='submit'
|
||||||
className="login-btn-gradient rounded py-2 px-4 tracking-wide text-white outline-none focus:outline-none"
|
className='login-btn-gradient rounded px-4 py-2 tracking-wide text-white outline-none focus:outline-none'
|
||||||
>
|
>
|
||||||
Continue
|
Continue
|
||||||
</button>
|
</button>
|
||||||
<br />
|
<br />
|
||||||
<button
|
<button
|
||||||
onClick={() => setDraftType("submit")}
|
onClick={() => setDraftType("submit")}
|
||||||
type="submit"
|
type='submit'
|
||||||
className="login-btn-gradient rounded py-2 mt-3 px-4 tracking-wide text-white outline-none focus:outline-none"
|
className='login-btn-gradient mt-3 rounded px-4 py-2 tracking-wide text-white outline-none focus:outline-none'
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ export default function HostVerificationPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = yup.object({
|
||||||
expiry_date: yup.string().test("is-not-in-past", "Not a valid date", (val) => {
|
expiry_date: yup
|
||||||
if (val == "") return false;
|
.string()
|
||||||
const date = new Date(val);
|
.test("is-not-in-past", "Not a valid date", (val) => {
|
||||||
return date.setDate(date.getDate() - 1) > new Date();
|
if (val == "") return false;
|
||||||
}),
|
const date = new Date(val);
|
||||||
|
return date.setDate(date.getDate() - 1) > new Date();
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -35,14 +37,18 @@ export default function HostVerificationPage() {
|
|||||||
register,
|
register,
|
||||||
watch,
|
watch,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({ defaultValues: { selectedType: "Driver's License" }, resolver: yupResolver(schema) });
|
} = useForm({
|
||||||
|
defaultValues: { selectedType: "Driver's License" },
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
const [frontImage, setFrontImage] = useState(null);
|
const [frontImage, setFrontImage] = useState(null);
|
||||||
const [backImage, setBackImage] = useState(null);
|
const [backImage, setBackImage] = useState(null);
|
||||||
const [passport, setPassport] = useState(null);
|
const [passport, setPassport] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
const { dispatch: globalDispatch, state: globalState } =
|
||||||
|
useContext(GlobalContext);
|
||||||
const { dispatch: authDispatch } = useContext(AuthContext);
|
const { dispatch: authDispatch } = useContext(AuthContext);
|
||||||
|
|
||||||
const [verified, setVerified] = useState(false);
|
const [verified, setVerified] = useState(false);
|
||||||
@@ -51,7 +57,8 @@ export default function HostVerificationPage() {
|
|||||||
const selectedType = watch("selectedType");
|
const selectedType = watch("selectedType");
|
||||||
|
|
||||||
const isDisabled = () => {
|
const isDisabled = () => {
|
||||||
if (selectedType == "Driver's License" && frontImage && backImage) return false;
|
if (selectedType == "Driver's License" && frontImage && backImage)
|
||||||
|
return false;
|
||||||
if (selectedType == "Passport" && passport) return false;
|
if (selectedType == "Passport" && passport) return false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -88,7 +95,7 @@ export default function HostVerificationPage() {
|
|||||||
image_back: data.backImage ?? null,
|
image_back: data.backImage ?? null,
|
||||||
user_id: Number(localStorage.getItem("user")),
|
user_id: Number(localStorage.getItem("user")),
|
||||||
},
|
},
|
||||||
globalState.user.verificationId ? "PUT" : "POST",
|
globalState.user.verificationId ? "PUT" : "POST"
|
||||||
);
|
);
|
||||||
|
|
||||||
// create notification
|
// create notification
|
||||||
@@ -103,7 +110,7 @@ export default function HostVerificationPage() {
|
|||||||
type: NOTIFICATION_TYPE.NEW_ID_VERIFICATION,
|
type: NOTIFICATION_TYPE.NEW_ID_VERIFICATION,
|
||||||
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
status: NOTIFICATION_STATUS.NOT_ADDRESSED,
|
||||||
},
|
},
|
||||||
"POST",
|
"POST"
|
||||||
);
|
);
|
||||||
|
|
||||||
setVerified(true);
|
setVerified(true);
|
||||||
@@ -130,66 +137,69 @@ export default function HostVerificationPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pb-16 normal-case">
|
<div className='pb-16 normal-case'>
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
onClick={() => navigate(-1)}
|
onClick={() => navigate(-1)}
|
||||||
className="mr-2 mb-2 inline-flex items-center py-2.5 pr-5 text-center text-sm font-semibold"
|
className='mb-2 mr-2 inline-flex items-center py-2.5 pr-5 text-center text-sm font-semibold'
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
type="arrow"
|
type='arrow'
|
||||||
variant="narrow-left"
|
variant='narrow-left'
|
||||||
className="h-4 w-4 stroke-[#667085]"
|
className='h-4 w-4 stroke-[#667085]'
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<span className="ml-2">Back</span>
|
<span className='ml-2'>Back</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="mb-4 text-2xl font-semibold md:text-5xl">Identity Verification</h1>
|
<h1 className='mb-4 text-2xl font-semibold md:text-5xl'>
|
||||||
<div className="mb-[32px] max-w-3xl rounded-lg border border-[#EAECF0] bg-[#F9FAFB] px-[24px] py-[16px]">
|
Identity Verification
|
||||||
<h3 className="text-lg flex items-center gap-2 font-semibold">
|
</h1>
|
||||||
|
<div className='mb-[32px] max-w-3xl rounded-lg border border-[#EAECF0] bg-[#F9FAFB] px-[24px] py-[16px]'>
|
||||||
|
<h3 className='text-lg flex items-center gap-2 font-semibold'>
|
||||||
<SecurityIcon />
|
<SecurityIcon />
|
||||||
<span>Safety is our priority</span>
|
<span>Safety is our priority</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="ml-5 max-w-xl text-sm leading-relaxed">
|
<p className='ml-5 max-w-xl text-sm leading-relaxed'>
|
||||||
To establish trust for all parties we verify both hosts and guests. Your personal information is secure. We will never share your information with third parties.
|
To establish trust for all parties we verify both hosts and guests.
|
||||||
|
Your personal information is secure. We will never share your
|
||||||
|
information with third parties.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<p className="mb-2 font-semibold">Explain what document(s) are allowed.</p>
|
<p className='mb-2 font-semibold'>
|
||||||
<div data-tour="fifth-step" className="radio-container mb-8 flex justify-between md:max-w-lg">
|
Explain what document(s) are allowed.
|
||||||
<label
|
</p>
|
||||||
htmlFor="driversLicense"
|
<div
|
||||||
className="cursor-pointer"
|
data-tour='sixth-step'
|
||||||
>
|
className='radio-container sixth-step mb-8 flex justify-between md:max-w-lg'
|
||||||
|
>
|
||||||
|
<label htmlFor='driversLicense' className='cursor-pointer'>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type='radio'
|
||||||
id="driversLicense"
|
id='driversLicense'
|
||||||
{...register("selectedType")}
|
{...register("selectedType")}
|
||||||
className="mr-2"
|
className='mr-2'
|
||||||
value="Driver's License"
|
value="Driver's License"
|
||||||
/>
|
/>
|
||||||
<span></span>
|
<span></span>
|
||||||
Driver's License
|
Driver's License
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label htmlFor='passport' className='cursor-pointer'>
|
||||||
htmlFor="passport"
|
|
||||||
className="cursor-pointer"
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type='radio'
|
||||||
id="passport"
|
id='passport'
|
||||||
{...register("selectedType")}
|
{...register("selectedType")}
|
||||||
className="mr-2"
|
className='mr-2'
|
||||||
value="Passport"
|
value='Passport'
|
||||||
/>
|
/>
|
||||||
<span></span>
|
<span></span>
|
||||||
Passport
|
Passport
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="sixth-step text-[#667085]">
|
<div className='text-[#667085]'>
|
||||||
{selectedType == "Driver's License" ? (
|
{selectedType == "Driver's License" ? (
|
||||||
<div className="flex flex-col items-center gap-[16px] md:flex-row">
|
<div className='flex flex-col items-center gap-[16px] md:flex-row'>
|
||||||
<FileUploader
|
<FileUploader
|
||||||
multiple={false}
|
multiple={false}
|
||||||
handleChange={(file) => {
|
handleChange={(file) => {
|
||||||
@@ -197,18 +207,21 @@ export default function HostVerificationPage() {
|
|||||||
}}
|
}}
|
||||||
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
||||||
>
|
>
|
||||||
<div className="flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]">
|
<div className='flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]'>
|
||||||
{frontImage?.name ? (
|
{frontImage?.name ? (
|
||||||
<img
|
<img
|
||||||
src={readImage(frontImage, "front-preview")}
|
src={readImage(frontImage, "front-preview")}
|
||||||
id="front-preview"
|
id='front-preview'
|
||||||
className="h-full w-full rounded-sm object-cover"
|
className='h-full w-full rounded-sm object-cover'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h4 className="text-xl font-semibold">Front</h4>
|
<h4 className='text-xl font-semibold'>Front</h4>
|
||||||
<p className="px-[20px]">
|
<p className='px-[20px]'>
|
||||||
<strong className="font-semibold underline">Click to upload</strong> or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
<strong className='font-semibold underline'>
|
||||||
|
Click to upload
|
||||||
|
</strong>{" "}
|
||||||
|
or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -221,18 +234,21 @@ export default function HostVerificationPage() {
|
|||||||
}}
|
}}
|
||||||
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
||||||
>
|
>
|
||||||
<div className="flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]">
|
<div className='flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]'>
|
||||||
{backImage?.name ? (
|
{backImage?.name ? (
|
||||||
<img
|
<img
|
||||||
src={readImage(backImage, "back-preview")}
|
src={readImage(backImage, "back-preview")}
|
||||||
id="back-preview"
|
id='back-preview'
|
||||||
className="h-full w-full rounded-sm object-cover"
|
className='h-full w-full rounded-sm object-cover'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h4 className="text-xl font-semibold">Back</h4>
|
<h4 className='text-xl font-semibold'>Back</h4>
|
||||||
<p className="px-[20px]">
|
<p className='px-[20px]'>
|
||||||
<strong className="font-semibold underline">Click to upload</strong> or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
<strong className='font-semibold underline'>
|
||||||
|
Click to upload
|
||||||
|
</strong>{" "}
|
||||||
|
or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -247,18 +263,23 @@ export default function HostVerificationPage() {
|
|||||||
}}
|
}}
|
||||||
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
types={["SVG", "JPEG", "PNG", "GIF", "JPG"]}
|
||||||
>
|
>
|
||||||
<div className="flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]">
|
<div className='flex h-[130px] w-full max-w-full cursor-pointer flex-col items-center justify-center gap-[12px] border-2 border-dashed border-[#D0D5DD] text-sm md:w-[333px]'>
|
||||||
{passport?.name ? (
|
{passport?.name ? (
|
||||||
<img
|
<img
|
||||||
src={readImage(passport, "passport-preview")}
|
src={readImage(passport, "passport-preview")}
|
||||||
id="passport-preview"
|
id='passport-preview'
|
||||||
className="h-full w-full rounded-sm object-cover"
|
className='h-full w-full rounded-sm object-cover'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h4 className="text-xl font-semibold">Passport page with photo</h4>
|
<h4 className='text-xl font-semibold'>
|
||||||
<p className="px-[20px]">
|
Passport page with photo
|
||||||
<strong className="font-semibold underline">Click to upload</strong> or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
</h4>
|
||||||
|
<p className='px-[20px]'>
|
||||||
|
<strong className='font-semibold underline'>
|
||||||
|
Click to upload
|
||||||
|
</strong>{" "}
|
||||||
|
or drag and drop SVG, PNG, JPG or GIF (max. 800x400px)
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -266,43 +287,61 @@ export default function HostVerificationPage() {
|
|||||||
</FileUploader>
|
</FileUploader>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="my-8 max-w-lg">
|
<div className='my-8 max-w-lg'>
|
||||||
<label
|
<label
|
||||||
className="mb-2 block text-sm font-bold text-gray-700"
|
className='mb-2 block text-sm font-bold text-gray-700'
|
||||||
htmlFor="expiry_date"
|
htmlFor='expiry_date'
|
||||||
>
|
>
|
||||||
Expiry Date
|
Expiry Date
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type='date'
|
||||||
placeholder="expiry_date"
|
placeholder='expiry_date'
|
||||||
{...register("expiry_date")}
|
{...register("expiry_date")}
|
||||||
className={`focus:shadow-outline !min-h-[40px] w-full rounded border py-2 px-3 leading-tight text-gray-700 focus:outline-none`}
|
className={`focus:shadow-outline !min-h-[40px] w-full rounded border px-3 py-2 leading-tight text-gray-700 focus:outline-none`}
|
||||||
/>
|
/>
|
||||||
{errors.expiry_date?.message && <p className="my-3 rounded-md border border-[#C42945] bg-white py-2 px-3 text-center text-sm normal-case text-[#C42945]">{errors.expiry_date?.message}</p>}
|
{errors.expiry_date?.message && (
|
||||||
|
<p className='my-3 rounded-md border border-[#C42945] bg-white px-3 py-2 text-center text-sm normal-case text-[#C42945]'>
|
||||||
|
{errors.expiry_date?.message}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
loading={loading}
|
loading={loading}
|
||||||
type="submit"
|
type='submit'
|
||||||
className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"} mt-8 w-[333px] max-w-full`}
|
className={`login-btn-gradient submit-doc-btn rounded tracking-wide text-white outline-none focus:outline-none ${
|
||||||
|
loading ? "py-1" : "py-2"
|
||||||
|
} mt-8 w-[333px] max-w-full`}
|
||||||
disabled={isDisabled()}
|
disabled={isDisabled()}
|
||||||
|
data-tour='submit-doc-btn'
|
||||||
>
|
>
|
||||||
Submit Document
|
Submit Document
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
</form>
|
</form>
|
||||||
<div className={showVerified ? "popup-container flex items-center justify-center normal-case" : "hidden"}>
|
<div
|
||||||
|
className={
|
||||||
|
showVerified
|
||||||
|
? "popup-container flex items-center justify-center normal-case"
|
||||||
|
: "hidden"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`${verified ? "pop-in" : "pop-out"} w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
className={`${
|
||||||
|
verified ? "pop-in" : "pop-out"
|
||||||
|
} w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<h2 className="mb-4 text-3xl font-semibold">
|
<h2 className='mb-4 text-3xl font-semibold'>
|
||||||
<GreenCheckIcon />
|
<GreenCheckIcon />
|
||||||
Document received
|
Document received
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mb-4 text-sm text-gray-500">Once we verify your document you will receive an email. It usually takes up to 24 hours.</p>
|
<p className='mb-4 text-sm text-gray-500'>
|
||||||
|
Once we verify your document you will receive an email. It usually
|
||||||
|
takes up to 24 hours.
|
||||||
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type='button'
|
||||||
className="login-btn-gradient mt-4 w-full rounded py-2 tracking-wide text-white outline-none focus:outline-none"
|
className='login-btn-gradient mt-4 w-full rounded py-2 tracking-wide text-white outline-none focus:outline-none'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVerified(false);
|
setVerified(false);
|
||||||
navigate(searchParams.get("redirect_uri") ?? -1);
|
navigate(searchParams.get("redirect_uri") ?? -1);
|
||||||
|
|||||||
Reference in New Issue
Block a user