initial commit
@@ -0,0 +1,9 @@
|
||||
.hidden-scrollbar {
|
||||
overflow: -moz-scrollbars-none; /* For older Firefox versions */
|
||||
scrollbar-width: none; /* For Firefox */
|
||||
-ms-overflow-style: none; /* For Internet Explorer and Edge */
|
||||
}
|
||||
|
||||
.hidden-scrollbar::-webkit-scrollbar {
|
||||
display: none; /* For WebKit browsers */
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import AuthProvider, { AuthContext } from "./authContext";
|
||||
import GlobalProvider, { GlobalContext } from "./globalContext";
|
||||
import Main from "./main";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { loadStripe } from "@stripe/stripe-js";
|
||||
import { Elements } from "@stripe/react-stripe-js";
|
||||
import "@uppy/core/dist/style.css";
|
||||
import "@uppy/dashboard/dist/style.css";
|
||||
// Import Swiper styles
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
import '@mantine/core/styles.css';
|
||||
import '@mantine/notifications/styles.css';
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { Notifications } from '@mantine/notifications';
|
||||
|
||||
const stripePromise = loadStripe(import.meta.env.VITE_REACT_STRIPE_PUBLIC_KEY);
|
||||
|
||||
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<MantineProvider
|
||||
theme={{
|
||||
primaryColor: "violet"
|
||||
}}
|
||||
>
|
||||
<Notifications position="top-right" />
|
||||
<AuthProvider>
|
||||
<GlobalProvider>
|
||||
<Router>
|
||||
<Elements stripe={stripePromise}>
|
||||
<Main />
|
||||
</Elements>
|
||||
</Router>
|
||||
</GlobalProvider>
|
||||
</AuthProvider>
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 7H1M1 7L7 13M1 7L7 1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 204 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 7H17M17 7L11 1M17 7L11 13" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 208 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 8V12M17 6V10M16 1C18.4487 1 19.7731 1.37476 20.4321 1.66544C20.5199 1.70415 20.5638 1.72351 20.6904 1.84437C20.7663 1.91682 20.9049 2.12939 20.9405 2.22809C21 2.39274 21 2.48274 21 2.66274V13.4111C21 14.3199 21 14.7743 20.8637 15.0079C20.7251 15.2454 20.5914 15.3559 20.3319 15.4472C20.0769 15.5369 19.562 15.438 18.5322 15.2401C17.8114 15.1017 16.9565 15 16 15C13 15 10 17 6 17C3.55129 17 2.22687 16.6252 1.56788 16.3346C1.48012 16.2958 1.43624 16.2765 1.3096 16.1556C1.23369 16.0832 1.09512 15.8706 1.05947 15.7719C1 15.6073 1 15.5173 1 15.3373L1 4.58885C1 3.68009 1 3.2257 1.13628 2.99214C1.2749 2.75456 1.40859 2.64412 1.66806 2.55281C1.92314 2.46305 2.43803 2.56198 3.46783 2.75985C4.18862 2.89834 5.04348 3 6 3C9 3 12 1 16 1ZM13.5 9C13.5 10.3807 12.3807 11.5 11 11.5C9.61929 11.5 8.5 10.3807 8.5 9C8.5 7.61929 9.61929 6.5 11 6.5C12.3807 6.5 13.5 7.61929 13.5 9Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.33325 5.5C1.33325 4.09987 1.33325 3.3998 1.60574 2.86502C1.84542 2.39462 2.22787 2.01217 2.69828 1.77248C3.23306 1.5 3.93312 1.5 5.33325 1.5H10.6666C12.0667 1.5 12.7668 1.5 13.3016 1.77248C13.772 2.01217 14.1544 2.39462 14.3941 2.86502C14.6666 3.3998 14.6666 4.09987 14.6666 5.5V16.5L12.3749 14.8333L10.2916 16.5L7.99992 14.8333L5.70825 16.5L3.62492 14.8333L1.33325 16.5V5.5Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 19V13.6C13 13.0399 13 12.7599 12.891 12.546C12.7951 12.3578 12.6422 12.2049 12.454 12.109C12.2401 12 11.9601 12 11.4 12H8.6C8.03995 12 7.75992 12 7.54601 12.109C7.35785 12.2049 7.20487 12.3578 7.10899 12.546C7 12.7599 7 13.0399 7 13.6V19M17 19V4.2C17 3.0799 17 2.51984 16.782 2.09202C16.5903 1.71569 16.2843 1.40973 15.908 1.21799C15.4802 1 14.9201 1 13.8 1H6.2C5.07989 1 4.51984 1 4.09202 1.21799C3.71569 1.40973 3.40973 1.71569 3.21799 2.09202C3 2.51984 3 3.0799 3 4.2V19M19 19H1M7.5 6H7.51M12.5 6H12.51M8 6C8 6.27614 7.77614 6.5 7.5 6.5C7.22386 6.5 7 6.27614 7 6C7 5.72386 7.22386 5.5 7.5 5.5C7.77614 5.5 8 5.72386 8 6ZM13 6C13 6.27614 12.7761 6.5 12.5 6.5C12.2239 6.5 12 6.27614 12 6C12 5.72386 12.2239 5.5 12.5 5.5C12.7761 5.5 13 5.72386 13 6Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 932 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 9H1M14 1V5M6 1V5M5.8 21H14.2C15.8802 21 16.7202 21 17.362 20.673C17.9265 20.3854 18.3854 19.9265 18.673 19.362C19 18.7202 19 17.8802 19 16.2V7.8C19 6.11984 19 5.27976 18.673 4.63803C18.3854 4.07354 17.9265 3.6146 17.362 3.32698C16.7202 3 15.8802 3 14.2 3H5.8C4.11984 3 3.27976 3 2.63803 3.32698C2.07354 3.6146 1.6146 4.07354 1.32698 4.63803C1 5.27976 1 6.11984 1 7.8V16.2C1 17.8802 1 18.7202 1.32698 19.362C1.6146 19.9265 2.07354 20.3854 2.63803 20.673C3.27976 21 4.11984 21 5.8 21Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 666 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1L7 7L13 1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 191 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="19" height="4" viewBox="0 0 19 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 3C10.0523 3 10.5 2.55228 10.5 2C10.5 1.44772 10.0523 1 9.5 1C8.94772 1 8.5 1.44772 8.5 2C8.5 2.55228 8.94772 3 9.5 3Z" stroke="#98A2B3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.5 3C17.0523 3 17.5 2.55228 17.5 2C17.5 1.44772 17.0523 1 16.5 1C15.9477 1 15.5 1.44772 15.5 2C15.5 2.55228 15.9477 3 16.5 3Z" stroke="#98A2B3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.5 3C3.05228 3 3.5 2.55228 3.5 2C3.5 1.44772 3.05228 1 2.5 1C1.94772 1 1.5 1.44772 1.5 2C1.5 2.55228 1.94772 3 2.5 3Z" stroke="#98A2B3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 752 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 1.26953V5.40007C11 5.96012 11 6.24015 11.109 6.45406C11.2049 6.64222 11.3578 6.7952 11.546 6.89108C11.7599 7.00007 12.0399 7.00007 12.6 7.00007H16.7305M6 15L8 17L12.5 12.5M11 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V16.2C1 17.8802 1 18.7202 1.32698 19.362C1.6146 19.9265 2.07354 20.3854 2.63803 20.673C3.27976 21 4.11984 21 5.8 21H12.2C13.8802 21 14.7202 21 15.362 20.673C15.9265 20.3854 16.3854 19.9265 16.673 19.362C17 18.7202 17 17.8802 17 16.2V7L11 1Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 712 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 1.26953V5.40007C11 5.96012 11 6.24015 11.109 6.45406C11.2049 6.64222 11.3578 6.7952 11.546 6.89108C11.7599 7.00007 12.0399 7.00007 12.6 7.00007H16.7305M9 17V11M6 14H12M11 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V16.2C1 17.8802 1 18.7202 1.32698 19.362C1.6146 19.9265 2.07354 20.3854 2.63803 20.673C3.27976 21 4.11984 21 5.8 21H12.2C13.8802 21 14.7202 21 15.362 20.673C15.9265 20.3854 16.3854 19.9265 16.673 19.362C17 18.7202 17 17.8802 17 16.2V7L11 1Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="22" viewBox="0 0 18 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 1.26953V5.40007C11 5.96012 11 6.24015 11.109 6.45406C11.2049 6.64222 11.3578 6.7952 11.546 6.89108C11.7599 7.00007 12.0399 7.00007 12.6 7.00007H16.7305M6.84998 11.0022C7.02617 10.5014 7.37395 10.079 7.83171 9.80998C8.28947 9.54095 8.82767 9.4426 9.35099 9.53237C9.87431 9.62213 10.349 9.89421 10.6909 10.3004C11.0329 10.7066 11.22 11.2207 11.2192 11.7517C11.2192 13.2506 8.97089 14 8.97089 14M9 17H9.01M11 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V16.2C1 17.8802 1 18.7202 1.32698 19.362C1.6146 19.9265 2.07354 20.3854 2.63803 20.673C3.27976 21 4.11984 21 5.8 21H12.2C13.8802 21 14.7202 21 15.362 20.673C15.9265 20.3854 16.3854 19.9265 16.673 19.362C17 18.7202 17 17.8802 17 16.2V7L11 1Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 943 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="22" viewBox="0 0 20 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 9.5V5.8C17 4.11984 17 3.27976 16.673 2.63803C16.3854 2.07354 15.9265 1.6146 15.362 1.32698C14.7202 1 13.8802 1 12.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V16.2C1 17.8802 1 18.7202 1.32698 19.362C1.6146 19.9265 2.07354 20.3854 2.63803 20.673C3.27976 21 4.11984 21 5.8 21H8.5M19 21L17.5 19.5M18.5 17C18.5 18.933 16.933 20.5 15 20.5C13.067 20.5 11.5 18.933 11.5 17C11.5 15.067 13.067 13.5 15 13.5C16.933 13.5 18.5 15.067 18.5 17Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 685 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.4 3H4.6C4.03995 3 3.75992 3 3.54601 3.10899C3.35785 3.20487 3.20487 3.35785 3.10899 3.54601C3 3.75992 3 4.03995 3 4.6V8.4C3 8.96005 3 9.24008 3.10899 9.45399C3.20487 9.64215 3.35785 9.79513 3.54601 9.89101C3.75992 10 4.03995 10 4.6 10H8.4C8.96005 10 9.24008 10 9.45399 9.89101C9.64215 9.79513 9.79513 9.64215 9.89101 9.45399C10 9.24008 10 8.96005 10 8.4V4.6C10 4.03995 10 3.75992 9.89101 3.54601C9.79513 3.35785 9.64215 3.20487 9.45399 3.10899C9.24008 3 8.96005 3 8.4 3Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.4 3H15.6C15.0399 3 14.7599 3 14.546 3.10899C14.3578 3.20487 14.2049 3.35785 14.109 3.54601C14 3.75992 14 4.03995 14 4.6V8.4C14 8.96005 14 9.24008 14.109 9.45399C14.2049 9.64215 14.3578 9.79513 14.546 9.89101C14.7599 10 15.0399 10 15.6 10H19.4C19.9601 10 20.2401 10 20.454 9.89101C20.6422 9.79513 20.7951 9.64215 20.891 9.45399C21 9.24008 21 8.96005 21 8.4V4.6C21 4.03995 21 3.75992 20.891 3.54601C20.7951 3.35785 20.6422 3.20487 20.454 3.10899C20.2401 3 19.9601 3 19.4 3Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.4 14H15.6C15.0399 14 14.7599 14 14.546 14.109C14.3578 14.2049 14.2049 14.3578 14.109 14.546C14 14.7599 14 15.0399 14 15.6V19.4C14 19.9601 14 20.2401 14.109 20.454C14.2049 20.6422 14.3578 20.7951 14.546 20.891C14.7599 21 15.0399 21 15.6 21H19.4C19.9601 21 20.2401 21 20.454 20.891C20.6422 20.7951 20.7951 20.6422 20.891 20.454C21 20.2401 21 19.9601 21 19.4V15.6C21 15.0399 21 14.7599 20.891 14.546C20.7951 14.3578 20.6422 14.2049 20.454 14.109C20.2401 14 19.9601 14 19.4 14Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.4 14H4.6C4.03995 14 3.75992 14 3.54601 14.109C3.35785 14.2049 3.20487 14.3578 3.10899 14.546C3 14.7599 3 15.0399 3 15.6V19.4C3 19.9601 3 20.2401 3.10899 20.454C3.20487 20.6422 3.35785 20.7951 3.54601 20.891C3.75992 21 4.03995 21 4.6 21H8.4C8.96005 21 9.24008 21 9.45399 20.891C9.64215 20.7951 9.79513 20.6422 9.89101 20.454C10 20.2401 10 19.9601 10 19.4V15.6C10 15.0399 10 14.7599 9.89101 14.546C9.79513 14.3578 9.64215 14.2049 9.45399 14.109C9.24008 14 8.96005 14 8.4 14Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 16H14M9.0177 1.764L2.23539 7.03912C1.78202 7.39175 1.55534 7.56806 1.39203 7.78886C1.24737 7.98444 1.1396 8.20478 1.07403 8.43905C1 8.70352 1 8.9907 1 9.56505V16.8C1 17.9201 1 18.4801 1.21799 18.908C1.40973 19.2843 1.71569 19.5903 2.09202 19.782C2.51984 20 3.07989 20 4.2 20H15.8C16.9201 20 17.4802 20 17.908 19.782C18.2843 19.5903 18.5903 19.2843 18.782 18.908C19 18.4801 19 17.9201 19 16.8V9.56505C19 8.9907 19 8.70352 18.926 8.43905C18.8604 8.20478 18.7526 7.98444 18.608 7.78886C18.4447 7.56806 18.218 7.39175 17.7646 7.03913L10.9823 1.764C10.631 1.49075 10.4553 1.35412 10.2613 1.3016C10.0902 1.25526 9.9098 1.25526 9.73865 1.3016C9.54468 1.35412 9.36902 1.49075 9.0177 1.764Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 864 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="21" viewBox="0 0 22 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 20V12.6C8 12.0399 8 11.7599 8.109 11.546C8.20487 11.3578 8.35785 11.2049 8.54601 11.109C8.75993 11 9.03995 11 9.6 11H12.4C12.9601 11 13.2401 11 13.454 11.109C13.6422 11.2049 13.7951 11.3578 13.891 11.546C14 11.7599 14 12.0399 14 12.6V20M1 8.5L10.04 1.72C10.3843 1.46181 10.5564 1.33271 10.7454 1.28294C10.9123 1.23902 11.0877 1.23902 11.2546 1.28295C11.4436 1.33271 11.6157 1.46181 11.96 1.72L21 8.5M3 7V16.8C3 17.9201 3 18.4802 3.21799 18.908C3.40974 19.2843 3.7157 19.5903 4.09202 19.782C4.51985 20 5.0799 20 6.2 20H15.8C16.9201 20 17.4802 20 17.908 19.782C18.2843 19.5903 18.5903 19.2843 18.782 18.908C19 18.4802 19 17.9201 19 16.8V7L12.92 2.44C12.2315 1.92361 11.8872 1.66542 11.5091 1.56589C11.1754 1.47804 10.8246 1.47804 10.4909 1.56589C10.1128 1.66542 9.76852 1.92361 9.08 2.44L3 7Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.27209 18.7279L9.86863 12.1314C10.2646 11.7354 10.4627 11.5373 10.691 11.4632C10.8918 11.3979 11.1082 11.3979 11.309 11.4632C11.5373 11.5373 11.7354 11.7354 12.1314 12.1314L18.6839 18.6839M13 13L15.8686 10.1314C16.2646 9.73535 16.4627 9.53735 16.691 9.46316C16.8918 9.3979 17.1082 9.3979 17.309 9.46316C17.5373 9.53735 17.7354 9.73535 18.1314 10.1314L21 13M9 7C9 8.10457 8.10457 9 7 9C5.89543 9 5 8.10457 5 7C5 5.89543 5.89543 5 7 5C8.10457 5 9 5.89543 9 7ZM5.8 19H16.2C17.8802 19 18.7202 19 19.362 18.673C19.9265 18.3854 20.3854 17.9265 20.673 17.362C21 16.7202 21 15.8802 21 14.2V5.8C21 4.11984 21 3.27976 20.673 2.63803C20.3854 2.07354 19.9265 1.6146 19.362 1.32698C18.7202 1 17.8802 1 16.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V14.2C1 15.8802 1 16.7202 1.32698 17.362C1.6146 17.9265 2.07354 18.3854 2.63803 18.673C3.27976 19 4.11984 19 5.8 19Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,405 @@
|
||||
[
|
||||
"2g1c",
|
||||
"2 girls 1 cup",
|
||||
"acrotomophilia",
|
||||
"alabama hot pocket",
|
||||
"alaskan pipeline",
|
||||
"anal",
|
||||
"anilingus",
|
||||
"anus",
|
||||
"apeshit",
|
||||
"arsehole",
|
||||
"ass",
|
||||
"asshole",
|
||||
"assmunch",
|
||||
"auto erotic",
|
||||
"autoerotic",
|
||||
"babeland",
|
||||
"baby batter",
|
||||
"baby juice",
|
||||
"ball gag",
|
||||
"ball gravy",
|
||||
"ball kicking",
|
||||
"ball licking",
|
||||
"ball sack",
|
||||
"ball sucking",
|
||||
"bangbros",
|
||||
"bangbus",
|
||||
"bareback",
|
||||
"barely legal",
|
||||
"barenaked",
|
||||
"bastard",
|
||||
"bastardo",
|
||||
"bastinado",
|
||||
"bbw",
|
||||
"bdsm",
|
||||
"beaner",
|
||||
"beaners",
|
||||
"beaver cleaver",
|
||||
"beaver lips",
|
||||
"beastiality",
|
||||
"bestiality",
|
||||
"big black",
|
||||
"big breasts",
|
||||
"big knockers",
|
||||
"big tits",
|
||||
"bimbos",
|
||||
"birdlock",
|
||||
"bitch",
|
||||
"bitches",
|
||||
"black cock",
|
||||
"blonde action",
|
||||
"blonde on blonde action",
|
||||
"blowjob",
|
||||
"blow job",
|
||||
"blow your load",
|
||||
"blue waffle",
|
||||
"blumpkin",
|
||||
"bollocks",
|
||||
"bondage",
|
||||
"boner",
|
||||
"boob",
|
||||
"boobs",
|
||||
"booty call",
|
||||
"brown showers",
|
||||
"brunette action",
|
||||
"bukkake",
|
||||
"bulldyke",
|
||||
"bullet vibe",
|
||||
"bullshit",
|
||||
"bung hole",
|
||||
"bunghole",
|
||||
"busty",
|
||||
"butt",
|
||||
"buttcheeks",
|
||||
"butthole",
|
||||
"camel toe",
|
||||
"camgirl",
|
||||
"camslut",
|
||||
"camwhore",
|
||||
"carpet muncher",
|
||||
"carpetmuncher",
|
||||
"chocolate rosebuds",
|
||||
"cialis",
|
||||
"circlejerk",
|
||||
"cleveland steamer",
|
||||
"clit",
|
||||
"clitoris",
|
||||
"clover clamps",
|
||||
"clusterfuck",
|
||||
"cock",
|
||||
"cocks",
|
||||
"coprolagnia",
|
||||
"coprophilia",
|
||||
"cornhole",
|
||||
"coon",
|
||||
"coons",
|
||||
"creampie",
|
||||
"cum",
|
||||
"cumming",
|
||||
"cumshot",
|
||||
"cumshots",
|
||||
"cunnilingus",
|
||||
"cunt",
|
||||
"darkie",
|
||||
"date rape",
|
||||
"daterape",
|
||||
"deep throat",
|
||||
"deepthroat",
|
||||
"dendrophilia",
|
||||
"dick",
|
||||
"dildo",
|
||||
"dingleberry",
|
||||
"dingleberries",
|
||||
"dirty pillows",
|
||||
"dirty sanchez",
|
||||
"doggie style",
|
||||
"doggiestyle",
|
||||
"doggy style",
|
||||
"doggystyle",
|
||||
"dog style",
|
||||
"dolcett",
|
||||
"domination",
|
||||
"dominatrix",
|
||||
"dommes",
|
||||
"donkey punch",
|
||||
"double dong",
|
||||
"double penetration",
|
||||
"dp action",
|
||||
"dry hump",
|
||||
"dvda",
|
||||
"eat my ass",
|
||||
"ecchi",
|
||||
"ejaculation",
|
||||
"erotic",
|
||||
"erotism",
|
||||
"escort",
|
||||
"eunuch",
|
||||
"fag",
|
||||
"faggot",
|
||||
"fecal",
|
||||
"felch",
|
||||
"fellatio",
|
||||
"feltch",
|
||||
"female squirting",
|
||||
"femdom",
|
||||
"figging",
|
||||
"fingerbang",
|
||||
"fingering",
|
||||
"fisting",
|
||||
"foot fetish",
|
||||
"footjob",
|
||||
"frotting",
|
||||
"fuck",
|
||||
"fuck buttons",
|
||||
"fuckin",
|
||||
"fucking",
|
||||
"fucktards",
|
||||
"fudge packer",
|
||||
"fudgepacker",
|
||||
"futanari",
|
||||
"gangbang",
|
||||
"gang bang",
|
||||
"gay sex",
|
||||
"genitals",
|
||||
"giant cock",
|
||||
"girl on",
|
||||
"girl on top",
|
||||
"girls gone wild",
|
||||
"goatcx",
|
||||
"goatse",
|
||||
"god damn",
|
||||
"gokkun",
|
||||
"golden shower",
|
||||
"goodpoop",
|
||||
"goo girl",
|
||||
"goregasm",
|
||||
"grope",
|
||||
"group sex",
|
||||
"g-spot",
|
||||
"guro",
|
||||
"hand job",
|
||||
"handjob",
|
||||
"hard core",
|
||||
"hardcore",
|
||||
"hentai",
|
||||
"homoerotic",
|
||||
"honkey",
|
||||
"hooker",
|
||||
"horny",
|
||||
"hot carl",
|
||||
"hot chick",
|
||||
"how to kill",
|
||||
"how to murder",
|
||||
"huge fat",
|
||||
"humping",
|
||||
"incest",
|
||||
"intercourse",
|
||||
"jack off",
|
||||
"jail bait",
|
||||
"jailbait",
|
||||
"jelly donut",
|
||||
"jerk off",
|
||||
"jigaboo",
|
||||
"jiggaboo",
|
||||
"jiggerboo",
|
||||
"jizz",
|
||||
"juggs",
|
||||
"kike",
|
||||
"kinbaku",
|
||||
"kinkster",
|
||||
"kinky",
|
||||
"knobbing",
|
||||
"leather restraint",
|
||||
"leather straight jacket",
|
||||
"lemon party",
|
||||
"livesex",
|
||||
"lolita",
|
||||
"lovemaking",
|
||||
"make me come",
|
||||
"male squirting",
|
||||
"masturbate",
|
||||
"masturbating",
|
||||
"masturbation",
|
||||
"menage a trois",
|
||||
"milf",
|
||||
"missionary position",
|
||||
"mong",
|
||||
"motherfucker",
|
||||
"mound of venus",
|
||||
"mr hands",
|
||||
"muff diver",
|
||||
"muffdiving",
|
||||
"nambla",
|
||||
"nawashi",
|
||||
"negro",
|
||||
"neonazi",
|
||||
"nigga",
|
||||
"nigger",
|
||||
"nig nog",
|
||||
"nimphomania",
|
||||
"nipple",
|
||||
"nipples",
|
||||
"nsfw",
|
||||
"nsfw images",
|
||||
"nude",
|
||||
"nudity",
|
||||
"nutten",
|
||||
"nympho",
|
||||
"nymphomania",
|
||||
"octopussy",
|
||||
"omorashi",
|
||||
"one cup two girls",
|
||||
"one guy one jar",
|
||||
"orgasm",
|
||||
"orgy",
|
||||
"paedophile",
|
||||
"paki",
|
||||
"panties",
|
||||
"panty",
|
||||
"pedobear",
|
||||
"pedophile",
|
||||
"pegging",
|
||||
"penis",
|
||||
"phone sex",
|
||||
"piece of shit",
|
||||
"pikey",
|
||||
"pissing",
|
||||
"piss pig",
|
||||
"pisspig",
|
||||
"playboy",
|
||||
"pleasure chest",
|
||||
"pole smoker",
|
||||
"ponyplay",
|
||||
"poof",
|
||||
"poon",
|
||||
"poontang",
|
||||
"punany",
|
||||
"poop chute",
|
||||
"poopchute",
|
||||
"porn",
|
||||
"porno",
|
||||
"pornography",
|
||||
"prince albert piercing",
|
||||
"pthc",
|
||||
"pubes",
|
||||
"pussy",
|
||||
"queaf",
|
||||
"queef",
|
||||
"quim",
|
||||
"raghead",
|
||||
"raging boner",
|
||||
"rape",
|
||||
"raping",
|
||||
"rapist",
|
||||
"rectum",
|
||||
"reverse cowgirl",
|
||||
"rimjob",
|
||||
"rimming",
|
||||
"rosy palm",
|
||||
"rosy palm and her 5 sisters",
|
||||
"rusty trombone",
|
||||
"sadism",
|
||||
"santorum",
|
||||
"scat",
|
||||
"schlong",
|
||||
"scissoring",
|
||||
"semen",
|
||||
"sex",
|
||||
"sexcam",
|
||||
"sexo",
|
||||
"sexy",
|
||||
"sexual",
|
||||
"sexually",
|
||||
"sexuality",
|
||||
"shaved beaver",
|
||||
"shaved pussy",
|
||||
"shemale",
|
||||
"shibari",
|
||||
"shit",
|
||||
"shitblimp",
|
||||
"shitty",
|
||||
"shota",
|
||||
"shrimping",
|
||||
"skeet",
|
||||
"slanteye",
|
||||
"slut",
|
||||
"s&m",
|
||||
"smut",
|
||||
"snatch",
|
||||
"snowballing",
|
||||
"sodomize",
|
||||
"sodomy",
|
||||
"spastic",
|
||||
"spic",
|
||||
"splooge",
|
||||
"splooge moose",
|
||||
"spooge",
|
||||
"spread legs",
|
||||
"spunk",
|
||||
"strap on",
|
||||
"strapon",
|
||||
"strappado",
|
||||
"strip club",
|
||||
"style doggy",
|
||||
"suck",
|
||||
"sucks",
|
||||
"suicide girls",
|
||||
"sultry women",
|
||||
"swastika",
|
||||
"swinger",
|
||||
"tainted love",
|
||||
"taste my",
|
||||
"tea bagging",
|
||||
"threesome",
|
||||
"throating",
|
||||
"thumbzilla",
|
||||
"tied up",
|
||||
"tight white",
|
||||
"tit",
|
||||
"tits",
|
||||
"titties",
|
||||
"titty",
|
||||
"tongue in a",
|
||||
"topless",
|
||||
"tosser",
|
||||
"towelhead",
|
||||
"tranny",
|
||||
"tribadism",
|
||||
"tub girl",
|
||||
"tubgirl",
|
||||
"tushy",
|
||||
"twat",
|
||||
"twink",
|
||||
"twinkie",
|
||||
"two girls one cup",
|
||||
"undressing",
|
||||
"upskirt",
|
||||
"urethra play",
|
||||
"urophilia",
|
||||
"vagina",
|
||||
"venus mound",
|
||||
"viagra",
|
||||
"vibrator",
|
||||
"violet wand",
|
||||
"vorarephilia",
|
||||
"voyeur",
|
||||
"voyeurweb",
|
||||
"voyuer",
|
||||
"vulva",
|
||||
"wank",
|
||||
"wetback",
|
||||
"wet dream",
|
||||
"white power",
|
||||
"whore",
|
||||
"worldsex",
|
||||
"wrapping men",
|
||||
"wrinkled starfish",
|
||||
"xx",
|
||||
"xxx",
|
||||
"yaoi",
|
||||
"yellow showers",
|
||||
"yiffy",
|
||||
"zoophilia",
|
||||
"🖕"
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="69" height="25" viewBox="0 0 69 25" fill=" none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.95426 19.1764C6.3634 19.1764 4.96896 18.8828 3.77091 18.2957C2.57286 17.7086 1.63995 16.8476 0.972187 15.7125C0.324062 14.5775 0 13.2076 0 11.6029C0 10.0764 0.324062 8.74567 0.972187 7.61063C1.63995 6.47558 2.56304 5.59494 3.74145 4.9687C4.91986 4.3229 6.30448 4 7.89534 4C9.40763 4 10.7333 4.27398 11.8725 4.82193C13.0312 5.35032 13.9249 6.13311 14.5533 7.1703C15.2015 8.18793 15.5255 9.4404 15.5255 10.9277C15.5255 11.1821 15.5157 11.4267 15.4961 11.6616C15.4764 11.8768 15.447 12.0921 15.4077 12.3074H2.03275V10.5461H13.316L12.4911 11.6322C12.5108 11.4365 12.5206 11.2506 12.5206 11.0745C12.5206 10.8788 12.5206 10.6831 12.5206 10.4874C12.5206 9.13707 12.1278 8.13901 11.3422 7.49321C10.5762 6.82783 9.40763 6.49515 7.83642 6.49515C6.08844 6.49515 4.84129 6.89633 4.09497 7.69869C3.34864 8.50105 2.97548 9.65567 2.97548 11.1625V11.9258C2.97548 13.4522 3.34864 14.6166 4.09497 15.419C4.84129 16.2213 6.09826 16.6225 7.86588 16.6225C9.39781 16.6225 10.4977 16.3877 11.1654 15.918C11.8528 15.4288 12.1965 14.7536 12.1965 13.8925V13.6577H15.3782V13.9219C15.3782 14.9591 15.0542 15.8789 14.406 16.6812C13.7776 17.464 12.9036 18.0805 11.7841 18.5306C10.6842 18.9611 9.40763 19.1764 7.95426 19.1764Z"/>
|
||||
<path d="M21.6795 18.8828H18.4978V4.29355H21.4143V8.43256L21.6795 8.57933V18.8828ZM21.6795 10.6929H20.9724V8.22707H21.6206C21.7581 7.42471 22.033 6.71042 22.4455 6.08418C22.8579 5.43838 23.4078 4.92957 24.0952 4.55774C24.8023 4.18591 25.6566 4 26.6583 4C27.7778 4 28.691 4.23484 29.3981 4.70451C30.1051 5.17419 30.6158 5.80042 30.93 6.58321C31.2639 7.366 31.4308 8.21729 31.4308 9.13707V11.0451H28.2786V9.75352C28.2786 8.69675 28.0429 7.92374 27.5715 7.4345C27.1002 6.94525 26.3146 6.70063 25.2147 6.70063C23.9577 6.70063 23.0543 7.0431 22.5044 7.72804C21.9545 8.41299 21.6795 9.40126 21.6795 10.6929Z"/>
|
||||
<path d="M41.7076 24.1667C40.2542 24.1667 38.9776 23.9416 37.8778 23.4915C36.7779 23.061 35.9138 22.4347 35.2853 21.6128C34.6764 20.7909 34.372 19.7928 34.372 18.6186H37.5243C37.5243 19.3231 37.6716 19.8907 37.9662 20.3212C38.2608 20.7517 38.7223 21.0551 39.3508 21.2312C39.9989 21.4269 40.8434 21.5247 41.8844 21.5247C43.0038 21.5247 43.8877 21.3975 44.5358 21.1431C45.2035 20.9083 45.6847 20.4875 45.9793 19.8809C46.2739 19.2742 46.4212 18.4327 46.4212 17.3564V8.81417L46.6569 8.60868V4.29355H49.5735V17.1803C49.5735 18.8045 49.2494 20.1255 48.6013 21.1431C47.9532 22.1803 47.0399 22.9436 45.8615 23.4328C44.6831 23.922 43.2984 24.1667 41.7076 24.1667ZM40.3819 17.5619C38.9285 17.5619 37.6814 17.2781 36.6404 16.7106C35.6192 16.1431 34.8237 15.3505 34.2542 14.3328C33.7042 13.3152 33.4293 12.1312 33.4293 10.7809C33.4293 9.43062 33.7141 8.24664 34.2836 7.22901C34.8728 6.21139 35.6977 5.41881 36.7583 4.85129C37.8385 4.28376 39.1151 4 40.5881 4C42.12 4 43.4163 4.34247 44.4769 5.02741C45.5571 5.69279 46.2248 6.66149 46.4802 7.93353H47.1577L46.981 10.4874H46.4212C46.4212 9.66545 46.2248 8.98051 45.832 8.43256C45.4392 7.86503 44.8795 7.44428 44.1528 7.1703C43.4261 6.89633 42.5423 6.75934 41.5014 6.75934C40.4997 6.75934 39.6257 6.88654 38.8794 7.14095C38.1527 7.39536 37.593 7.81611 37.2002 8.4032C36.827 8.97073 36.6404 9.7633 36.6404 10.7809C36.6404 11.779 36.827 12.5716 37.2002 13.1587C37.5733 13.7458 38.1135 14.1763 38.8205 14.4503C39.5472 14.7047 40.4113 14.8319 41.413 14.8319C43.0235 14.8319 44.2608 14.4992 45.125 13.8338C45.9891 13.1684 46.4212 12.19 46.4212 10.8983H46.981V13.7751H46.215C45.9597 14.8906 45.341 15.8006 44.359 16.5051C43.377 17.2096 42.0513 17.5619 40.3819 17.5619Z"/>
|
||||
<path d="M60.5895 19.1764C58.979 19.1764 57.5551 18.8633 56.3178 18.237C55.1001 17.5912 54.1476 16.7008 53.4601 15.5657C52.7924 14.4111 52.4585 13.0902 52.4585 11.6029C52.4585 10.0764 52.7924 8.74567 53.4601 7.61063C54.1476 6.47558 55.1001 5.59494 56.3178 4.9687C57.5551 4.3229 58.979 4 60.5895 4C62.2393 4 63.673 4.3229 64.8907 4.9687C66.1084 5.59494 67.0511 6.47558 67.7189 7.61063C68.4063 8.74567 68.75 10.0764 68.75 11.6029C68.75 13.0902 68.4063 14.4111 67.7189 15.5657C67.0511 16.7008 66.1084 17.5912 64.8907 18.237C63.673 18.8633 62.2393 19.1764 60.5895 19.1764ZM60.5895 16.3583C62.3768 16.3583 63.6632 15.9571 64.4488 15.1548C65.2344 14.3328 65.6272 13.1489 65.6272 11.6029C65.6272 10.0568 65.2344 8.87288 64.4488 8.05095C63.6632 7.20944 62.3768 6.78869 60.5895 6.78869C58.8219 6.78869 57.5453 7.20944 56.7597 8.05095C55.9741 8.87288 55.5813 10.0568 55.5813 11.6029C55.5813 13.1489 55.9741 14.3328 56.7597 15.1548C57.5453 15.9571 58.8219 16.3583 60.5895 16.3583Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.3333 13.1667L16.5 9M16.5 9L12.3333 4.83333M16.5 9H6.5M6.5 1.5H5.5C4.09987 1.5 3.3998 1.5 2.86502 1.77248C2.39462 2.01217 2.01217 2.39462 1.77248 2.86502C1.5 3.3998 1.5 4.09987 1.5 5.5V12.5C1.5 13.9001 1.5 14.6002 1.77248 15.135C2.01217 15.6054 2.39462 15.9878 2.86502 16.2275C3.3998 16.5 4.09987 16.5 5.5 16.5H6.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 497 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 4L9.16492 9.71544C9.82609 10.1783 10.1567 10.4097 10.5163 10.4993C10.8339 10.5785 11.1661 10.5785 11.4837 10.4993C11.8433 10.4097 12.1739 10.1783 12.8351 9.71544L21 4M5.8 17H16.2C17.8802 17 18.7202 17 19.362 16.673C19.9265 16.3854 20.3854 15.9265 20.673 15.362C21 14.7202 21 13.8802 21 12.2V5.8C21 4.11984 21 3.27976 20.673 2.63803C20.3854 2.07354 19.9265 1.6146 19.362 1.32698C18.7202 1 17.8802 1 16.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V12.2C1 13.8802 1 14.7202 1.32698 15.362C1.6146 15.9265 2.07354 16.3854 2.63803 16.673C3.27976 17 4.11984 17 5.8 17Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 815 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="2" viewBox="0 0 17 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.5 1H15.5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 204 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.332 2.00004C11.5071 1.82494 11.715 1.68605 11.9438 1.59129C12.1725 1.49653 12.4177 1.44775 12.6654 1.44775C12.913 1.44775 13.1582 1.49653 13.387 1.59129C13.6157 1.68605 13.8236 1.82494 13.9987 2.00004C14.1738 2.17513 14.3127 2.383 14.4074 2.61178C14.5022 2.84055 14.551 3.08575 14.551 3.33337C14.551 3.58099 14.5022 3.82619 14.4074 4.05497C14.3127 4.28374 14.1738 4.49161 13.9987 4.66671L4.9987 13.6667L1.33203 14.6667L2.33203 11L11.332 2.00004Z" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 632 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.5 1V15M1.5 8H15.5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 215 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="21" height="22" viewBox="0 0 21 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0005 14C12.6573 14 14.0005 12.6569 14.0005 11C14.0005 9.34315 12.6573 8 11.0005 8C9.34363 8 8.00049 9.34315 8.00049 11C8.00049 12.6569 9.34363 14 11.0005 14Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.28957 18.3711L8.87402 19.6856C9.04776 20.0768 9.3313 20.4093 9.69024 20.6426C10.0492 20.8759 10.4681 21.0001 10.8962 21C11.3244 21.0001 11.7433 20.8759 12.1022 20.6426C12.4612 20.4093 12.7447 20.0768 12.9185 19.6856L13.5029 18.3711C13.711 17.9047 14.0609 17.5159 14.5029 17.26C14.9477 17.0034 15.4622 16.8941 15.9729 16.9478L17.4029 17.1C17.8286 17.145 18.2582 17.0656 18.6396 16.8713C19.021 16.6771 19.3379 16.3763 19.5518 16.0056C19.766 15.635 19.868 15.2103 19.8455 14.7829C19.823 14.3555 19.677 13.9438 19.4251 13.5978L18.5785 12.4344C18.277 12.0171 18.1159 11.5148 18.1185 11C18.1184 10.4866 18.281 9.98635 18.5829 9.57111L19.4296 8.40778C19.6814 8.06175 19.8275 7.65007 19.85 7.22267C19.8725 6.79528 19.7704 6.37054 19.5562 6C19.3423 5.62923 19.0255 5.32849 18.644 5.13423C18.2626 4.93997 17.833 4.86053 17.4074 4.90556L15.9774 5.05778C15.4667 5.11141 14.9521 5.00212 14.5074 4.74556C14.0645 4.48825 13.7144 4.09736 13.5074 3.62889L12.9185 2.31444C12.7447 1.92317 12.4612 1.59072 12.1022 1.3574C11.7433 1.12408 11.3244 0.99993 10.8962 1C10.4681 0.99993 10.0492 1.12408 9.69024 1.3574C9.3313 1.59072 9.04776 1.92317 8.87402 2.31444L8.28957 3.62889C8.0825 4.09736 7.73245 4.48825 7.28957 4.74556C6.84479 5.00212 6.33024 5.11141 5.81957 5.05778L4.38513 4.90556C3.95946 4.86053 3.52987 4.93997 3.14844 5.13423C2.76702 5.32849 2.45014 5.62923 2.23624 6C2.02206 6.37054 1.92002 6.79528 1.94251 7.22267C1.96499 7.65007 2.11103 8.06175 2.36291 8.40778L3.20957 9.57111C3.51151 9.98635 3.67411 10.4866 3.67402 11C3.67411 11.5134 3.51151 12.0137 3.20957 12.4289L2.36291 13.5922C2.11103 13.9382 1.96499 14.3499 1.94251 14.7773C1.92002 15.2047 2.02206 15.6295 2.23624 16C2.45036 16.3706 2.76727 16.6712 3.14864 16.8654C3.53001 17.0596 3.95949 17.1392 4.38513 17.0944L5.81513 16.9422C6.3258 16.8886 6.84034 16.9979 7.28513 17.2544C7.72966 17.511 8.08134 17.902 8.28957 18.3711Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 10V14.2C19 15.8802 19 16.7202 18.673 17.362C18.3854 17.9265 17.9265 18.3854 17.362 18.673C16.7202 19 15.8802 19 14.2 19H5.8C4.11984 19 3.27976 19 2.63803 18.673C2.07354 18.3854 1.6146 17.9265 1.32698 17.362C1 16.7202 1 15.8802 1 14.2V10M14 5L10 1M10 1L6 5M10 1V13" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.5305 16.3677L14.0759 16.1595C14.0235 16.274 13.9536 16.3632 13.8657 16.4344C13.7965 16.4905 13.717 16.524 13.6123 16.5331C13.5358 16.5398 13.4554 16.5237 13.3567 16.4582L13.3568 16.4581L13.3463 16.4515L9.19625 13.8473L8.93049 13.6805L8.66473 13.8473L4.51473 16.4515L4.51464 16.4513L4.50423 16.4582C4.40555 16.5237 4.32514 16.5398 4.24873 16.5331C4.14394 16.524 4.06449 16.4905 3.99524 16.4344C3.90747 16.3633 3.83772 16.2743 3.7853 16.16C3.75378 16.0908 3.7385 15.9998 3.76856 15.8638C3.76859 15.8636 3.76862 15.8635 3.76865 15.8634L4.86845 10.9424L4.9322 10.6572L4.71496 10.4617L1.04419 7.15819C0.945727 7.06488 0.900968 6.97672 0.883143 6.89047L0.883147 6.89047L0.882745 6.88857C0.859403 6.7778 0.86615 6.67532 0.902553 6.57104C0.945215 6.44883 1.0025 6.3661 1.06731 6.30703C1.10669 6.27113 1.1849 6.22473 1.34132 6.19965L6.17594 5.75835L6.47965 5.73062L6.59401 5.44791L8.46901 0.812492L8.46973 0.810696C8.51654 0.693678 8.57769 0.628401 8.64887 0.586111L8.64959 0.585682C8.75552 0.522513 8.84694 0.5 8.93049 0.5C9.01398 0.5 9.10586 0.522488 9.21263 0.585824C9.28315 0.627946 9.34427 0.693252 9.39125 0.810696L9.39197 0.81249L11.267 5.44791L11.3813 5.73062L11.685 5.75835L16.5197 6.19965C16.6761 6.22473 16.7543 6.27113 16.7937 6.30703C16.8585 6.3661 16.9158 6.44883 16.9584 6.57105C16.995 6.67572 17.0019 6.7786 16.979 6.88955C16.9607 6.97634 16.9153 7.06483 16.8168 7.15818L13.146 10.4617L12.9288 10.6572L12.9925 10.9424L14.0923 15.8634C14.0924 15.8635 14.0924 15.8636 14.0924 15.8637C14.1225 15.9999 14.1072 16.0909 14.0756 16.1602L14.5305 16.3677ZM14.5305 16.3677C14.6138 16.1851 14.6305 15.9809 14.5805 15.7552L3.33049 16.3677C3.41382 16.5497 3.53049 16.7014 3.68049 16.8229C3.83049 16.9444 4.00549 17.0139 4.20549 17.0312C4.40549 17.0486 4.59716 16.9965 4.78049 16.875L8.93049 14.2708L13.0805 16.875C13.2638 16.9965 13.4555 17.0486 13.6555 17.0312C13.8555 17.0139 14.0305 16.9444 14.1805 16.8229C14.3305 16.7014 14.4472 16.5497 14.5305 16.3677Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.3333 5.00008V4.33341C12.3333 3.39999 12.3333 2.93328 12.1517 2.57676C11.9919 2.26316 11.7369 2.00819 11.4233 1.8484C11.0668 1.66675 10.6001 1.66675 9.66667 1.66675H8.33333C7.39991 1.66675 6.9332 1.66675 6.57668 1.8484C6.26308 2.00819 6.00811 2.26316 5.84832 2.57676C5.66667 2.93328 5.66667 3.39999 5.66667 4.33341V5.00008M1.5 5.00008H16.5M14.8333 5.00008V14.3334C14.8333 15.7335 14.8333 16.4336 14.5608 16.9684C14.3212 17.4388 13.9387 17.8212 13.4683 18.0609C12.9335 18.3334 12.2335 18.3334 10.8333 18.3334H7.16667C5.76654 18.3334 5.06647 18.3334 4.53169 18.0609C4.06129 17.8212 3.67883 17.4388 3.43915 16.9684C3.16667 16.4336 3.16667 15.7335 3.16667 14.3334V5.00008" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 850 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.3163 18.4384C4.92462 17.0052 6.34492 16 8 16H14C15.6551 16 17.0754 17.0052 17.6837 18.4384M15 8.5C15 10.7091 13.2091 12.5 11 12.5C8.79086 12.5 7 10.7091 7 8.5C7 6.29086 8.79086 4.5 11 4.5C13.2091 4.5 15 6.29086 15 8.5ZM21 11C21 16.5228 16.5228 21 11 21C5.47715 21 1 16.5228 1 11C1 5.47715 5.47715 1 11 1C16.5228 1 21 5.47715 21 11Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 514 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.00002 20.8174C3.6026 21 4.41649 21 5.8 21H16.2C17.5835 21 18.3974 21 19 20.8174M3.00002 20.8174C2.87082 20.7783 2.75133 20.7308 2.63803 20.673C2.07354 20.3854 1.6146 19.9265 1.32698 19.362C1 18.7202 1 17.8802 1 16.2V5.8C1 4.11984 1 3.27976 1.32698 2.63803C1.6146 2.07354 2.07354 1.6146 2.63803 1.32698C3.27976 1 4.11984 1 5.8 1H16.2C17.8802 1 18.7202 1 19.362 1.32698C19.9265 1.6146 20.3854 2.07354 20.673 2.63803C21 3.27976 21 4.11984 21 5.8V16.2C21 17.8802 21 18.7202 20.673 19.362C20.3854 19.9265 19.9265 20.3854 19.362 20.673C19.2487 20.7308 19.1292 20.7783 19 20.8174M3.00002 20.8174C3.00035 20.0081 3.00521 19.5799 3.07686 19.2196C3.39249 17.6329 4.63288 16.3925 6.21964 16.0769C6.60603 16 7.07069 16 8 16H14C14.9293 16 15.394 16 15.7804 16.0769C17.3671 16.3925 18.6075 17.6329 18.9231 19.2196C18.9948 19.5799 18.9996 20.0081 19 20.8174M15 8.5C15 10.7091 13.2091 12.5 11 12.5C8.79086 12.5 7 10.7091 7 8.5C7 6.29086 8.79086 4.5 11 4.5C13.2091 4.5 15 6.29086 15 8.5Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 19V17C21 15.1362 19.7252 13.5701 18 13.126M14.5 1.29076C15.9659 1.88415 17 3.32131 17 5C17 6.67869 15.9659 8.11585 14.5 8.70924M16 19C16 17.1362 16 16.2044 15.6955 15.4693C15.2895 14.4892 14.5108 13.7105 13.5307 13.3045C12.7956 13 11.8638 13 10 13H7C5.13623 13 4.20435 13 3.46927 13.3045C2.48915 13.7105 1.71046 14.4892 1.30448 15.4693C1 16.2044 1 17.1362 1 19M12.5 5C12.5 7.20914 10.7091 9 8.5 9C6.29086 9 4.5 7.20914 4.5 5C4.5 2.79086 6.29086 1 8.5 1C10.7091 1 12.5 2.79086 12.5 5Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 667 B |
@@ -0,0 +1,177 @@
|
||||
import React, { useReducer, useState } from "react";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
|
||||
export const AuthContext = React.createContext({ state: {} });
|
||||
|
||||
const initialState = {
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
token: null,
|
||||
role: null,
|
||||
originalRole: null,
|
||||
sessionExpired: false,
|
||||
allowCheckVerification: false,
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "LOGIN":
|
||||
localStorage.setItem("user", Number(action.payload.user_id));
|
||||
localStorage.setItem("token", action.payload.token ?? action.payload.access_token);
|
||||
localStorage.setItem("role", action.payload.role);
|
||||
localStorage.setItem("originalRole", ((action.payload.originalRole === undefined) || action.payload.originalRole === "undefined") ? "customer" : action.payload.originalRole);
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: true,
|
||||
user: Number(localStorage.getItem("user")),
|
||||
token: localStorage.getItem("token"),
|
||||
role: localStorage.getItem("role"),
|
||||
originalRole: localStorage.getItem("originalRole"),
|
||||
};
|
||||
case "LOGOUT":
|
||||
localStorage.removeItem("user");
|
||||
localStorage.removeItem("token");
|
||||
return {
|
||||
...state,
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
sessionExpired: false,
|
||||
role: null,
|
||||
originalRole: null,
|
||||
};
|
||||
case "SESSION_EXPIRED":
|
||||
return {
|
||||
...state,
|
||||
sessionExpired: true,
|
||||
};
|
||||
case "SWITCH_TO_HOST":
|
||||
localStorage.setItem("role", "host");
|
||||
return {
|
||||
...state,
|
||||
role: "host",
|
||||
};
|
||||
case "SWITCH_TO_CUSTOMER":
|
||||
localStorage.setItem("role", "customer");
|
||||
return {
|
||||
...state,
|
||||
role: "customer",
|
||||
};
|
||||
case "SWITCH_TO_ADMIN":
|
||||
localStorage.setItem("role", "admin");
|
||||
return {
|
||||
...state,
|
||||
role: "admin",
|
||||
};
|
||||
case "ALLOW_CHECK_VERIFICATION":
|
||||
return {
|
||||
...state,
|
||||
allowCheckVerification: true,
|
||||
};
|
||||
case "DISALLOW_CHECK_VERIFICATION":
|
||||
return {
|
||||
...state,
|
||||
allowCheckVerification: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
let sdk = new MkdSDK();
|
||||
|
||||
export const tokenExpireError = (dispatch, errorMessage) => {
|
||||
/**
|
||||
* either this or we pass the role as a parameter
|
||||
*/
|
||||
const role = localStorage.getItem("role");
|
||||
if (errorMessage === "TOKEN_EXPIRED") {
|
||||
dispatch({ type: "SESSION_EXPIRED" });
|
||||
}
|
||||
};
|
||||
|
||||
const AuthProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
const user = localStorage.getItem("user");
|
||||
const token = localStorage.getItem("token");
|
||||
const role = localStorage.getItem("role");
|
||||
const originalRole = localStorage.getItem("originalRole");
|
||||
|
||||
if (!token) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
(async function () {
|
||||
setLoading(true);
|
||||
try {
|
||||
await sdk.check(originalRole);
|
||||
dispatch({
|
||||
type: "LOGIN",
|
||||
payload: {
|
||||
user_id: user,
|
||||
token,
|
||||
role: role,
|
||||
originalRole: originalRole,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (role) {
|
||||
dispatch({
|
||||
type: "LOGOUT",
|
||||
});
|
||||
window.location.href = "/" + role + "/login";
|
||||
} else {
|
||||
dispatch({
|
||||
type: "LOGOUT",
|
||||
});
|
||||
window.location.href = "/";
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
if (loading) return <div className="popup-container flex items-center justify-center">
|
||||
<div className="">
|
||||
<svg
|
||||
style={{ margin: "auto", background: "transparent", display: " block", shapeRendering: "auto" }}
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
className="md:w-[100px] md:h-[100px] w-[80px] h-[80px]"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#d0d5dd"
|
||||
strokeWidth="6"
|
||||
strokeDasharray="42.76482137044271 42.76482137044271"
|
||||
d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z"
|
||||
strokeLinecap="round"
|
||||
style={{ transform: "scale(0.8)", transformOrigin: "50px 50px" }}
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
repeatCount="indefinite"
|
||||
dur="1.882051282051282s"
|
||||
keyTimes="0;1"
|
||||
values="0;256.58892822265625"
|
||||
></animate>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
state,
|
||||
dispatch,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthProvider;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { PlusCircleIcon } from "@heroicons/react/24/outline";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
const AddButton = ({ link, text }) => {
|
||||
return (
|
||||
<>
|
||||
<NavLink
|
||||
to={link}
|
||||
className="ml-5 mb-1 flex items-center rounded !bg-gradient-to-r from-[#33D4B7] to-[#0D9895] px-6 py-2 text-sm font-semibold text-white outline-none focus:outline-none"
|
||||
>
|
||||
<PlusCircleIcon className="h-6 w-6" />
|
||||
<span className="ml-2">{text ? text : ""}</span>
|
||||
</NavLink>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddButton;
|
||||
@@ -0,0 +1,207 @@
|
||||
import React from "react";
|
||||
import { NavLink, useNavigate } from "react-router-dom";
|
||||
import { GlobalContext, showToast } from "@/globalContext";
|
||||
import { useEffect } from "react";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { NOTIFICATION_STATUS } from "@/utils/constants";
|
||||
import { AuthContext, tokenExpireError } from "@/authContext";
|
||||
import { useContext } from "react";
|
||||
import adminNavigationItems from "@/utils/adminNavigationItems";
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/solid";
|
||||
import { ArrowLeftOnRectangleIcon, ArrowsRightLeftIcon } from "@heroicons/react/24/outline";
|
||||
import LogoIcon from "./Icons/LogoIcon";
|
||||
|
||||
export const AdminHeader = () => {
|
||||
const { state, dispatch: globalDispatch } = React.useContext(GlobalContext);
|
||||
const { dispatch, state: authState } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function fetchNotificationCount() {
|
||||
const sdk = new MkdSDK();
|
||||
sdk.setTable("notification");
|
||||
|
||||
try {
|
||||
const result = await sdk.callRestAPI({ payload: { status: NOTIFICATION_STATUS.NOT_ADDRESSED } }, "GETALL");
|
||||
const g = result?.list?.filter((not) => Number(not?.status) == 0)
|
||||
|
||||
globalDispatch({ type: "SET_NOTIFICATION_COUNT", payload: g.length });
|
||||
} catch (err) {
|
||||
showToast(globalDispatch, err.message);
|
||||
tokenExpireError(dispatch, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function switchToHost() {
|
||||
dispatch({ type: "SWITCH_TO_HOST" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a host`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
function switchToCustomer() {
|
||||
dispatch({ type: "SWITCH_TO_CUSTOMER" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a customer`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let interval = setInterval(() => {
|
||||
fetchNotificationCount();
|
||||
}, 10000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={`sidebar-holder overflow-y-auto border-r-4 border-gray-100 ${!state.isOpen ? "open-nav" : ""}`}>
|
||||
<div className="sticky top-0 h-fit pb-8">
|
||||
<div className="mt-4 w-full p-4">
|
||||
<div className="mx-auto w-10/12 text-center text-2xl font-bold text-black">
|
||||
<LogoIcon fill={"#1D2939"} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sidebar-list w-full">
|
||||
<ul className="flex flex-wrap">
|
||||
{adminNavigationItems.map((item) => {
|
||||
if (item.sub_categories) {
|
||||
return (
|
||||
<li
|
||||
key={item.path}
|
||||
style={{ display: "relative" }}
|
||||
className={`super-nav relative mx-auto my-auto block w-10/12 list-none justify-between rounded-lg ${item.sub_categories.length > 7 ? "larger" : ""} ${item.sub_categories.length > 2 ? "large" : ""} ${item.sub_categories.length < 2 ? "small" : ""
|
||||
}`}
|
||||
onClick={(e) => e.currentTarget.classList.toggle("open")}
|
||||
>
|
||||
<NavLink
|
||||
to={`/admin/${item.path}`}
|
||||
className={`flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white ${state.path == item.path ? "bg-[#1D2939] stroke-white text-white" : ""}`}
|
||||
>
|
||||
<span className="mr-3">{item.icon}</span>
|
||||
<span>{item.title}</span>
|
||||
<span className="flex flex-grow justify-end">
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</span>
|
||||
</NavLink>
|
||||
<div className="nav-item-dropdown absolute w-full">
|
||||
{item.sub_categories.map((sub, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`group mx-auto my-auto block w-10/12 list-none justify-between truncate rounded-lg`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<NavLink
|
||||
to={`/admin/${sub.path}`}
|
||||
className={`flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white ${state.path == sub.path ? "bg-[#1D2939] stroke-white text-white" : ""}`}
|
||||
>
|
||||
<span className="mr-3">{sub.icon}</span>
|
||||
<span>{sub.title}</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.path == "notification") {
|
||||
return (
|
||||
<li
|
||||
key={item.path}
|
||||
className={`group relative mx-auto my-auto block w-10/12 list-none justify-between rounded-lg`}
|
||||
>
|
||||
<NavLink
|
||||
to={`/admin/${item.path}`}
|
||||
className={`flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white ${state.path == item.path ? "bg-[#1D2939] stroke-white text-white" : ""}`}
|
||||
>
|
||||
<span className="mr-3">{item.icon}</span>
|
||||
<strong
|
||||
className={`${state.adminNotificationCount > 0 ? "inline" : "hidden"
|
||||
} absolute right-1 flex h-8 w-8 items-center justify-center rounded-full border bg-red-400 px-2 text-xs text-white`}
|
||||
>
|
||||
{state.adminNotificationCount}
|
||||
</strong>
|
||||
<span>{item.title}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
key={item.path}
|
||||
className={`group mx-auto my-auto block w-10/12 list-none justify-between rounded-lg`}
|
||||
>
|
||||
<NavLink
|
||||
to={`/admin/${item.path}`}
|
||||
className={`flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white ${state.path == item.path ? "bg-[#1D2939] stroke-white text-white" : ""}`}
|
||||
>
|
||||
<span className="mr-3">{item.icon}</span>
|
||||
<span>{item.title}</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
<li className="group group mx-auto w-10/12 !cursor-pointer list-none rounded-lg">
|
||||
<a
|
||||
className="flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white"
|
||||
onClick={() => {
|
||||
globalDispatch({
|
||||
type: "SHOWMODAL",
|
||||
payload: {
|
||||
showModal: true,
|
||||
modalShowTitle: "Are you sure?",
|
||||
modalShowMessage: "You are about to log out.",
|
||||
modalBtnText: "Yes, Log Out",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="mr-3">{<ArrowLeftOnRectangleIcon className="h-6 w-6" />}</span>
|
||||
<span>Logout</span>
|
||||
</a>
|
||||
</li>
|
||||
<li className="group group mx-auto w-10/12 !cursor-pointer list-none rounded-lg">
|
||||
<a
|
||||
className="flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white"
|
||||
onClick={switchToCustomer}
|
||||
>
|
||||
<span className="mr-3">
|
||||
<ArrowsRightLeftIcon className="h-6 w-6" />
|
||||
</span>
|
||||
<span>Switch to customer</span>
|
||||
</a>
|
||||
</li>
|
||||
<li className="group group mx-auto w-10/12 !cursor-pointer list-none rounded-lg">
|
||||
<a
|
||||
className="flex items-center rounded-lg group-hover:bg-[#1D2939] group-hover:text-white"
|
||||
onClick={switchToHost}
|
||||
>
|
||||
<span className="mr-3">
|
||||
<ArrowsRightLeftIcon className="h-6 w-6" />
|
||||
</span>
|
||||
<span>Switch to Host</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminHeader;
|
||||
@@ -0,0 +1,59 @@
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { CardCvcElement, CardExpiryElement, CardNumberElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { LoadingButton } from "../frontend";
|
||||
|
||||
export default function AddCardMethodModal({ modalOpen, closeModal, onSuccess }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const sdk = new MkdSDK();
|
||||
const [ctrl] = useState(new AbortController());
|
||||
|
||||
const addNewCard = async (e) => {
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
// create stripe token
|
||||
try {
|
||||
const cardNum = elements.getElement("cardNumber");
|
||||
const result = await stripe.createToken(cardNum).then(async (r) => {
|
||||
if (r.error) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
message: r.error?.message ? r.error?.message : r?.trace?.raw?.message,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await sdk.createCustomerStripeCard({ sourceToken: r ? r.token.id : result.token.id }, ctrl.signal);
|
||||
closeModal();
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.name == "AbortError") {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
console.log(error)
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
message: error?.message ? error?.message : "Declined",
|
||||
},
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Add Modal UI here to allow card to be created */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { AuthContext, tokenExpireError } from "@/authContext";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useContext, useState } from "react";
|
||||
import { LoadingButton } from "../frontend";
|
||||
|
||||
export default function DeleteCardMethodModal({ modalOpen, closeModal, onSuccess, card }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { dispatch } = useContext(AuthContext);
|
||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const sdk = new MkdSDK();
|
||||
const [ctrl] = useState(new AbortController());
|
||||
|
||||
async function onSubmit(e) {
|
||||
setLoading(true);
|
||||
e.preventDefault();
|
||||
try {
|
||||
await sdk.deleteCustomerStripeCard(card.id, ctrl.signal);
|
||||
closeModal();
|
||||
onSuccess();
|
||||
} catch (err) {
|
||||
if (err.name == "AbortError") {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
tokenExpireError(dispatch, err.message);
|
||||
globalDispatch({ type: "SHOW_ERROR", payload: { heading: "Card Deletion Failed", message: err.message } });
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="form"
|
||||
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div className="mb-[18px] flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="mb-[8px] text-2xl font-semibold"
|
||||
>
|
||||
Delete Payment Method
|
||||
</Dialog.Title>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="rounded-full border p-1 px-3 text-2xl font-normal duration-100 hover:bg-gray-200 active:bg-gray-300"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p>You are about to remove card ending with {card.last4}</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 flex-grow rounded border-2 border-[#98A2B3] py-2 tracking-wide outline-none focus:outline-none"
|
||||
onClick={() => {
|
||||
ctrl.abort();
|
||||
closeModal();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`mt-4 flex-grow rounded bg-[#D92D20] ${loading ? "py-1 px-4" : "py-2"} tracking-wide text-white outline-none focus:outline-none`}
|
||||
>
|
||||
Yes, remove
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
|
||||
const Button = ({ text, setResetClicked }) => {
|
||||
return (<button onClick={() => setResetClicked(false)} type="submit" className=" ml-2 rounded-md font-inter px-[66px] py-[10px] bg-gradient-to-r from-[#33D4B7] to-[#0D9895] bg-clip-text text-transparent border border-[#33D4B7]">
|
||||
{text}
|
||||
</button>)
|
||||
}
|
||||
|
||||
export default Button
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
export default function CaretDownIcon() {
|
||||
return (
|
||||
<svg
|
||||
className=" w-6 float-right inline-block"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
export default function CaretUpIcon() {
|
||||
return (
|
||||
<svg
|
||||
className="w-6 inline-block float-right"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import React from "react";
|
||||
import { useContext } from "react";
|
||||
import GreenCheckIcon from "./frontend/icons/GreenCheckIcon";
|
||||
|
||||
export default function ConfirmationModal() {
|
||||
const { state, dispatch } = useContext(GlobalContext);
|
||||
|
||||
if (!state.confirmation) return null;
|
||||
|
||||
return (
|
||||
<div className={"popup-container z-100 flex items-center justify-center normal-case"}>
|
||||
<div
|
||||
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()}
|
||||
>
|
||||
<h2 className="mb-4 text-3xl font-semibold">
|
||||
<GreenCheckIcon />
|
||||
{state.confirmationHeading}
|
||||
</h2>
|
||||
<p className="mb-4 text-sm text-gray-500">{state.confirmationMsg}</p>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { useController } from "react-hook-form";
|
||||
|
||||
export default function CounterV2({ setValue, name, maxCount, minCount, control }) {
|
||||
const { field, fieldState, formState } = useController({ control, name });
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-[10px] p-2 font-semibold">
|
||||
<button
|
||||
type="button"
|
||||
className={"rounded-md border-2 border-black px-3 text-2xl disabled:border-[#D0D5DD]"}
|
||||
onClick={() => setValue(Number(field.value) - 1)}
|
||||
disabled={Number(field.value) <= (minCount || 0)}
|
||||
onBlur={field.onBlur}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span>{field.value}</span>
|
||||
<button
|
||||
type="button"
|
||||
className={"rounded-md border-2 border-black px-3 text-2xl disabled:border-[#D0D5DD]"}
|
||||
onClick={() => setValue(Number(field.value) + 1)}
|
||||
disabled={Number(field.value) >= maxCount}
|
||||
onBlur={field.onBlur}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useController } from "react-hook-form";
|
||||
|
||||
export default function CustomComboBox({ control, name, setValue, containerClassName, valueField, labelField, items, ...restProps }) {
|
||||
const { field, fieldState, formState } = useController({ control, name });
|
||||
|
||||
const filteredItems =
|
||||
field.value === ""
|
||||
? items
|
||||
: items
|
||||
.filter((item) => item[labelField].toLowerCase().replace(/\s+/g, "").includes(field.value.toLowerCase().replace(/\s+/g, "")))
|
||||
.sort((a, b) => {
|
||||
if (a[labelField].toLowerCase().indexOf(field.value.toLowerCase()) > b[labelField].toLowerCase().indexOf(field.value.toLowerCase())) {
|
||||
return 1;
|
||||
} else if (a[labelField].toLowerCase().indexOf(field.value.toLowerCase()) < b[labelField].toLowerCase().indexOf(field.value.toLowerCase())) {
|
||||
return -1;
|
||||
} else {
|
||||
if (a[labelField] > b[labelField]) return 1;
|
||||
else return -1;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as={"div"}
|
||||
className={`${containerClassName ?? ""}`}
|
||||
value={field.value}
|
||||
onChange={setValue}
|
||||
>
|
||||
<Combobox.Input
|
||||
{...restProps}
|
||||
{...field}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Combobox.Options
|
||||
className={`tiny-scroll absolute left-0 right-0 top-full z-50 mt-2 max-h-60 w-full origin-top cursor-pointer divide-y divide-gray-100 overflow-y-auto rounded-xl bg-white ring-black ring-opacity-5 focus:outline-none ${
|
||||
filteredItems.length > 0 ? "py-2 shadow-lg ring-1" : ""
|
||||
}`}
|
||||
>
|
||||
{filteredItems.map((item, idx) => (
|
||||
<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"
|
||||
key={idx}
|
||||
value={item[valueField]}
|
||||
>
|
||||
{item[labelField]}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { useController } from "react-hook-form";
|
||||
|
||||
export default function CustomComboBoxV2({ control, name, setValue, className, valueField, labelField, getItems, ...restProps }) {
|
||||
const { field, fieldState, formState } = useController({ control, name });
|
||||
const [items, setItems] = useState([]);
|
||||
const [query, setQuery] = useState("");
|
||||
const [selected, setSelected] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
getItems("", setItems, field.value);
|
||||
}, [field.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected[labelField]) {
|
||||
setQuery(selected[labelField]);
|
||||
}
|
||||
}, [selected[valueField]]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(items.find((item) => item[valueField] == field.value) ?? {});
|
||||
}, [field.value, items.length]);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as={"div"}
|
||||
className={`${className ?? ""} ${fieldState.error ? "border-red-500" : ""} normal-case`}
|
||||
value={field.value}
|
||||
onChange={setValue}
|
||||
>
|
||||
<Combobox.Input
|
||||
{...restProps}
|
||||
className="w-full truncate border-0 text-black focus:outline-none"
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
if (e.target.value.trim() == "") {
|
||||
setValue("");
|
||||
}
|
||||
getItems(e.target.value, setItems, field.value);
|
||||
}}
|
||||
value={query}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
name={field.name}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Combobox.Options
|
||||
className={`tiny-scroll absolute left-0 right-0 top-full z-50 mt-2 max-h-60 w-full origin-top cursor-pointer divide-y divide-gray-100 overflow-y-auto rounded-xl bg-white ring-black ring-opacity-5 focus:outline-none ${
|
||||
items.length > 0 ? "py-2 shadow-lg ring-1" : ""
|
||||
}`}
|
||||
>
|
||||
{items.map((item, idx) => (
|
||||
<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"
|
||||
key={idx}
|
||||
value={item[valueField]}
|
||||
>
|
||||
{item[labelField]}
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import usePlacesService from "react-google-autocomplete/lib/usePlacesAutocompleteService";
|
||||
import { useController } from "react-hook-form";
|
||||
import LocationIcon from "./frontend/icons/LocationIcon";
|
||||
|
||||
export default function CustomLocationAutoCompleteV2({ type, control, name, setValue, onClear, className, containerClassName, hideIcons, suggestionType, ...restProps }) {
|
||||
const { field } = useController({ control, name });
|
||||
|
||||
const { placePredictions, getPlacePredictions, isPlacePredictionsLoading } = usePlacesService({
|
||||
apiKey: import.meta.env.VITE_GOOGLE_API_KEY,
|
||||
options: { types: suggestionType ?? ["(region)"] },
|
||||
debounce: 200,
|
||||
});
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
as={"div"}
|
||||
className={`relative w-full normal-case z-100 ${containerClassName ?? ""}`}
|
||||
value={(typeof field.value === "object") ? field.value : (field.value)?.replace(', undefined', '')}
|
||||
>
|
||||
{!hideIcons && <LocationIcon />}
|
||||
|
||||
<Combobox.Input
|
||||
{...restProps}
|
||||
autoComplete="off"
|
||||
className={`w-full truncate text-black ${className ?? ""}`}
|
||||
onBlur={field.onBlur}
|
||||
value={(typeof field.value === "object") ? field.value : (field.value)?.replace(', undefined', '')}
|
||||
onChange={(evt) => {
|
||||
field.onChange(evt);
|
||||
getPlacePredictions({ input: evt.target.value });
|
||||
}}
|
||||
/>
|
||||
{!hideIcons && field.value && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setValue("");
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
{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">
|
||||
<svg
|
||||
style={{ margin: "auto", background: "none", display: "block", shapeRendering: "auto" }}
|
||||
width="36px"
|
||||
height="36px"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#d0d5dd"
|
||||
strokeWidth="10"
|
||||
strokeDasharray="42.76482137044271 42.76482137044271"
|
||||
d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z"
|
||||
strokeLinecap="round"
|
||||
style={{ transform: "scale(1)", transformOrigin: "50px 50px" }}
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
repeatCount="indefinite"
|
||||
dur="1.6666666666666667s"
|
||||
keyTimes="0;1"
|
||||
values="0;256.58892822265625"
|
||||
></animate>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<Combobox.Options
|
||||
className={`${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) => (
|
||||
<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"
|
||||
key={idx}
|
||||
value={place.structured_formatting.main_text}
|
||||
onClick={() => 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>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
)}
|
||||
</Transition>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { useController } from "react-hook-form";
|
||||
import { ChevronUpDownIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
export default function CustomSelectV2({ control, name, containerClassName, items, labelField, valueField, placeholder, shouldUnregister, ...restProps }) {
|
||||
const { field, fieldState } = useController({ control, name, shouldUnregister: shouldUnregister ?? true });
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const selected = items.find((item) => item[valueField] === (typeof field.value !== "number" ? field.value : +field.value));
|
||||
const defaultImage = items.find((item) => item["type"] === "1");
|
||||
|
||||
return (
|
||||
<div className={`relative rounded-md focus:outline-none active:outline-none ${containerClassName}`}>
|
||||
<Listbox
|
||||
as={"fragment"}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
>
|
||||
<Listbox.Button
|
||||
className={`flex h-full w-full items-center justify-between ${field.value === "" ? "text-gray-500" : ""} ${restProps.className ?? ""} ${dropdownOpen ? restProps.openClassName ?? "" : ""}`}
|
||||
>
|
||||
<span className="block truncate">{selected ? selected[labelField] : defaultImage === undefined ? placeholder : defaultImage["name"]}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronUpDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
afterEnter={field.onBlur}
|
||||
beforeEnter={() => setDropdownOpen(true)}
|
||||
afterLeave={() => setDropdownOpen(false)}
|
||||
>
|
||||
<Listbox.Options
|
||||
className={`absolute z-50 mt-1 w-full max-h-60 md:max-w-lg overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm`}
|
||||
>
|
||||
{items.map((item, idx) => (
|
||||
<Listbox.Option
|
||||
key={idx}
|
||||
className={`relative cursor-pointer select-none py-2 pr-4 pl-4 ui-active:bg-amber-100 ui-active:text-amber-900 ui-not-active:text-gray-900`}
|
||||
value={item[valueField]}
|
||||
>
|
||||
{item[labelField]}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useContext, useState } from "react";
|
||||
import usePlacesService from "react-google-autocomplete/lib/usePlacesAutocompleteService";
|
||||
import { useController } from "react-hook-form";
|
||||
import LocationIcon from "./frontend/icons/LocationIcon";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
|
||||
export default function CustomStaticLocationAutoCompleteV2({ type, control, name, setValue, onClear, className, containerClassName, hideIcons, suggestionType, ...restProps }) {
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const [location, setLocation] = useState(globalState.location);
|
||||
|
||||
|
||||
const { placePredictions, getPlacePredictions, isPlacePredictionsLoading } = usePlacesService({
|
||||
apiKey: import.meta.env.VITE_GOOGLE_API_KEY,
|
||||
options: { types: suggestionType ?? ["(region)"] },
|
||||
debounce: 200,
|
||||
});
|
||||
return (
|
||||
<Combobox
|
||||
as={"div"}
|
||||
className={`relative w-full normal-case z-100 ${containerClassName ?? ""}`}
|
||||
value={location}
|
||||
>
|
||||
{!hideIcons && <LocationIcon />}
|
||||
|
||||
<Combobox.Input
|
||||
{...restProps}
|
||||
autoComplete="off"
|
||||
className={`w-full truncate text-black ${className ?? ""}`}
|
||||
value={globalState.location}
|
||||
onChange={(evt) => {
|
||||
setLocation(evt.target.value)
|
||||
getPlacePredictions({ input: evt.target.value });
|
||||
}}
|
||||
/>
|
||||
{!hideIcons && globalState.location && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setValue("");
|
||||
setLocation("");
|
||||
if (onClear) {
|
||||
onClear();
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
{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">
|
||||
<svg
|
||||
style={{ margin: "auto", background: "none", display: "block", shapeRendering: "auto" }}
|
||||
width="36px"
|
||||
height="36px"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#d0d5dd"
|
||||
strokeWidth="10"
|
||||
strokeDasharray="42.76482137044271 42.76482137044271"
|
||||
d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z"
|
||||
strokeLinecap="round"
|
||||
style={{ transform: "scale(1)", transformOrigin: "50px 50px" }}
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
repeatCount="indefinite"
|
||||
dur="1.6666666666666667s"
|
||||
keyTimes="0;1"
|
||||
values="0;256.58892822265625"
|
||||
></animate>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<Combobox.Options
|
||||
className={`${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) => (
|
||||
<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"
|
||||
key={idx}
|
||||
value={place.structured_formatting.main_text}
|
||||
onClick={() =>
|
||||
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>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
)}
|
||||
</Transition>
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTour } from "@reactour/tour";
|
||||
|
||||
export default function CustomerGettingStartedTour() {
|
||||
const navigate = useNavigate();
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const { dispatch } = useContext(AuthContext);
|
||||
const [modalOpen, setModalOpen] = useState(true);
|
||||
const [gettingStarted, setGettingStarted] = useState();
|
||||
const { pathname } = useLocation();
|
||||
const sdk = new MkdSDK();
|
||||
|
||||
const { setIsOpen } = useTour()
|
||||
|
||||
async function markAsNotFirstTimeUser() {
|
||||
try {
|
||||
await sdk.callRawAPI("/v2/api/custom/ergo/edit-self", { profile: { getting_started: 1 } }, "POST");
|
||||
globalDispatch({
|
||||
type: "SET_USER_DATA",
|
||||
payload: {
|
||||
...globalState.user,
|
||||
getting_started: 1,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
tokenExpireError(dispatch, err.message);
|
||||
console.log("err", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalState.user.id) return null;
|
||||
|
||||
const fetchUser = async () => {
|
||||
const result = await sdk.callRawAPI("/rest/profile/GETALL", {
|
||||
"payload": {
|
||||
"user_id": Number(globalState.user.id)
|
||||
},
|
||||
"selectStr": "*"
|
||||
},
|
||||
"POST");
|
||||
setGettingStarted(result.list[0]?.getting_started)
|
||||
}
|
||||
|
||||
fetchUser()
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen && gettingStarted == 0}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={() => setModalOpen(false)}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
First time login?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">Would you like a tour of the site?</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
markAsNotFirstTimeUser();
|
||||
}}
|
||||
>
|
||||
No thanks
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`login-btn-gradient inline-flex justify-center rounded-md py-2 px-4 text-sm font-medium text-white`}
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
setIsOpen(true)
|
||||
globalDispatch({ type: "START_TOUR" });
|
||||
}}
|
||||
>
|
||||
Yes please
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import { GlobalContext, showToast } from "@/globalContext";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { sleep } from "@/utils/utils";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { Link } from "react-router-dom";
|
||||
import { AuthContext, tokenExpireError } from "../authContext";
|
||||
import LogoIcon from "./frontend/icons/LogoIcon";
|
||||
import NavMenu from "./frontend/NavMenu";
|
||||
import StaticSearchBar from "./frontend/StaticSearchBar";
|
||||
|
||||
const getNavBarVariant = (path) => {
|
||||
if (path.startsWith("/account") || path.startsWith("/property") || path.startsWith("/help")) {
|
||||
return "light";
|
||||
}
|
||||
switch (path) {
|
||||
case "/contact-us":
|
||||
case "/faq":
|
||||
return "white";
|
||||
case "/search":
|
||||
case "/explore":
|
||||
case "/favorites":
|
||||
case "/become-a-host":
|
||||
case "/reset-password":
|
||||
return "light";
|
||||
default:
|
||||
return "transparent";
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomerHeader = () => {
|
||||
const { state: authState, dispatch } = React.useContext(AuthContext);
|
||||
const { state: globalState, dispatch: globalDispatch } = React.useContext(GlobalContext);
|
||||
const navigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
const [variant, setVariant] = useState(getNavBarVariant(pathname));
|
||||
|
||||
async function fetchProfile() {
|
||||
const sdk = new MkdSDK();
|
||||
try {
|
||||
const result = await sdk.getProfileCustom();
|
||||
globalDispatch({ type: "SET_USER_DATA", payload: result });
|
||||
} catch (err) {
|
||||
showToast(globalDispatch, err.message, 4000, "ERROR");
|
||||
tokenExpireError(dispatch, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
if (pathname == "/") {
|
||||
if (window.scrollY > 10) {
|
||||
setVariant("white");
|
||||
} else {
|
||||
setVariant("transparent");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("scroll", onScroll);
|
||||
fetchProfile();
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
setVariant(getNavBarVariant(pathname));
|
||||
}, [pathname]);
|
||||
|
||||
const joinAsHostAdmin = async () => {
|
||||
let newLogin = { ...authState, role: "host" };
|
||||
globalDispatch({ type: "START_LOADING" });
|
||||
await sleep(500);
|
||||
globalDispatch({ type: "STOP_LOADING" });
|
||||
navigate("/");
|
||||
dispatch({ type: "LOGOUT" });
|
||||
dispatch({ type: "LOGIN", payload: newLogin });
|
||||
showToast(globalDispatch, "Joined as Host", 2000);
|
||||
};
|
||||
|
||||
async function becomeAHost() {
|
||||
// check if all fields are ready to go
|
||||
if (!(globalState.verificationType && globalState.dob && globalState.city && globalState.country && globalState.about)) {
|
||||
navigate("/become-a-host");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await callCustomAPI(
|
||||
"edit-self",
|
||||
"post",
|
||||
{
|
||||
user: { role: "host" },
|
||||
},
|
||||
"",
|
||||
);
|
||||
dispatch({ type: "SWITCH_TO_HOST" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a host`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
} catch (err) { }
|
||||
}
|
||||
|
||||
function switchToHost() {
|
||||
dispatch({ type: "SWITCH_TO_HOST" });
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: `You are now signed in as a host`,
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
if (pathname.includes("/login") || pathname.includes("/signup")) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
className={`fixed top-0 left-0 z-50 flex w-screen flex-wrap items-center justify-between py-4 px-4 text-sm duration-500 md:flex-nowrap md:rounded-br-[32px] md:rounded-bl-[32px] md:px-12 header-${variant}`}
|
||||
>
|
||||
<nav className={`gap-6`}>
|
||||
<Link
|
||||
to="/"
|
||||
className=""
|
||||
>
|
||||
<LogoIcon fill={variant == "transparent" || variant == "light" ? undefined : "#101828"} />
|
||||
</Link>
|
||||
</nav>
|
||||
<StaticSearchBar className="hidden lg:block" />
|
||||
|
||||
<div className="flex gap-4 space-x-4">
|
||||
<nav className={`z-50 inline `}>
|
||||
{" "}
|
||||
{pathname.startsWith("/account") && (
|
||||
<button
|
||||
className={`self-stretch rounded-md border px-6 py-[5px] pb-[7px] my-border-${variant} ${variant == "transparent" ? "" : "border-white"}`}
|
||||
onClick={()=>navigate("/search?location=&booking_start_time=&max_capacity=&capacity=&size=")}
|
||||
>
|
||||
<span>Explore Spaces</span>
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<nav className="z-50 flex items-center gap-6">
|
||||
{authState.originalRole != "customer" ? (
|
||||
<button
|
||||
onClick={switchToHost}
|
||||
className={`self-stretch rounded-md border px-6 py-[5px] pb-[7px] my-border-${variant} hidden whitespace-nowrap md:inline`}
|
||||
>
|
||||
<span>Join as host</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={becomeAHost}
|
||||
className={`self-stretch rounded-md border px-6 py-[5px] pb-[7px] my-border-${variant} hidden whitespace-nowrap md:inline`}
|
||||
>
|
||||
<span>Become a host</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<NavMenu variant={variant} />
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
<StaticSearchBar className="flex w-full justify-center py-4 md:hidden" />
|
||||
</header>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomerHeader;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { formatDate } from "@/utils/date-time-utils";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useEffect } from "react";
|
||||
import { Calendar } from "react-calendar";
|
||||
import { useController } from "react-hook-form";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import CalendarIcon from "./frontend/icons/CalendarIcon";
|
||||
import NextIcon from "./frontend/icons/NextIcon";
|
||||
import PrevIcon from "./frontend/icons/PrevIcon";
|
||||
|
||||
export default function DatePickerV3({ control, name, placeholder, labelClassName, reset, min }) {
|
||||
const { field, fieldState, formState } = useController({ control, name });
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
return (
|
||||
<div className={`w-full`}>
|
||||
<Popover className="lg:relative">
|
||||
<Popover.Button className={`flex w-full justify-between focus:outline-none ui-open:text-opacity-90`}>
|
||||
<div className={`flex gap-2 ${labelClassName ?? ""}`}>
|
||||
{!fieldState.isDirty ? (
|
||||
<CalendarIcon />
|
||||
) : (
|
||||
<span
|
||||
className={`self-end`}
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
reset();
|
||||
searchParams.delete(name);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</span>
|
||||
)}
|
||||
<span className={`${!fieldState.isDirty ? "text-gray-400" : ""}`}>{fieldState.isDirty || searchParams.get(name) ? formatDate(field.value) : placeholder}</span>
|
||||
</div>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
afterEnter={field.onBlur}
|
||||
>
|
||||
<Popover.Panel className={`absolute left-1/2 z-10 mt-3 -translate-x-1/2 transform px-4 pb-12 sm:px-0`}>
|
||||
{({ close }) => (
|
||||
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5">
|
||||
<Calendar
|
||||
onChange={(val) => {
|
||||
field.onChange(val);
|
||||
close();
|
||||
}}
|
||||
value={field.value}
|
||||
className={`calendar date-picker`}
|
||||
nextLabel={<NextIcon />}
|
||||
prevLabel={<PrevIcon />}
|
||||
next2Label={
|
||||
<div
|
||||
className="h-full w-full cursor-default"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></div>
|
||||
}
|
||||
prev2Label={
|
||||
<div
|
||||
className="h-full w-full cursor-default"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
></div>
|
||||
}
|
||||
maxDetail="month"
|
||||
minDate={min}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import React, { Fragment } from "react";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function ErrorModal() {
|
||||
const { state, dispatch } = useContext(GlobalContext);
|
||||
|
||||
if (!state.error) return null;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={state.error}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-50"
|
||||
onClose={() => dispatch({ type: "CLOSE_ERROR" })}
|
||||
>
|
||||
<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 z-10 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-50 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-lg transform overflow-hidden rounded-2xl bg-white p-6 text-center align-middle z-1000 shadow-xl transition-all">
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => dispatch({ type: "CLOSE_ERROR" })}
|
||||
className="text-gray-500 duration-100 hover:text-black"
|
||||
>
|
||||
<XMarkIcon className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-center">
|
||||
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="mt-8 text-2xl"
|
||||
>
|
||||
{state.errorHeading}
|
||||
</Dialog.Title>
|
||||
|
||||
<p className="tiny-scroll mt-4 max-h-[300px] overflow-y-auto text-wrap break-normal text-sm text-gray-500">{state.errorMsg}</p>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import React, { useState, useContext } from "react";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Icon from "./Icons";
|
||||
import "suneditor/dist/css/suneditor.min.css";
|
||||
|
||||
const Faq = ({ data }) => {
|
||||
const [showAnswer, setShowAnswer] = useState(false);
|
||||
const { dispatch } = useContext(GlobalContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="w-full p-1">
|
||||
<div className="flex justify-between">
|
||||
<div></div>
|
||||
<div>
|
||||
<button
|
||||
className="pr-2 bg-gradient-to-r from-[#33D4B7] to-[#0D9895] bg-clip-text text-transparent font-bold border-r border-gray-200"
|
||||
onClick={() => {
|
||||
navigate(`/admin/edit-faq/${data.id}`, {
|
||||
state: data,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
className="font-semibold text-sm py-2.5 text-center inline-flex items-center mr-2 mb-2"
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: "SHOWMODAL",
|
||||
payload: {
|
||||
showModal: true,
|
||||
modalShowMessage: "Are you sure you want to delete this question?",
|
||||
modalShowTitle: "Confirm Changes",
|
||||
type: "BaasDelete",
|
||||
modalBtnText: "Yes, Delete",
|
||||
itemId: data.id,
|
||||
table1: "faq",
|
||||
backTo: "/admin/faq",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="ml-2"> Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 px-4 bg-white bg-opacity-60 border-[1px] border-gray-200">
|
||||
<div className="flex flex-wrap justify-between">
|
||||
<div className="flex-1 p-2">
|
||||
<div className={`${showAnswer ? " mb-4" : ""}`}>
|
||||
<h4 className="text-lg font-semibold leading-normal">{data.question}</h4>
|
||||
</div>
|
||||
{showAnswer && (
|
||||
<p
|
||||
className="text-gray-600 font-medium sun-editor-editable"
|
||||
dangerouslySetInnerHTML={{ __html: data.answer }}
|
||||
></p>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-auto p-2">
|
||||
{showAnswer ? (
|
||||
<Icon
|
||||
type="minus"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
onClick={() => setShowAnswer(!showAnswer)}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
type="plus"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
onClick={() => setShowAnswer(!showAnswer)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Faq;
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useController } from "react-hook-form";
|
||||
import StarIcon from "./frontend/icons/StarIcon";
|
||||
|
||||
export default function FilterCheckBoxesV2({ name, control, setValue, reset, title, labelField, valueField, options }) {
|
||||
const { field, fieldState, formState } = useController({ control, name });
|
||||
|
||||
return (
|
||||
<div className="mb-[34px]">
|
||||
<Disclosure defaultOpen>
|
||||
<div className="mb-[12px] flex justify-between">
|
||||
<h4 className="flex w-full justify-between text-[16px] font-semibold lg:block">
|
||||
<span className="lg:mr-2 lg:border-r lg:pr-2">{title}</span>
|
||||
<button
|
||||
className="text-sm font-normal lowercase lg:text-xs"
|
||||
onClick={reset}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</h4>
|
||||
<Disclosure.Button className="hidden duration-200 ui-open:rotate-180 lg:inline">
|
||||
{" "}
|
||||
<svg
|
||||
width="14"
|
||||
height="8"
|
||||
viewBox="0 0 14 8"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M13 7L7 1L1 7"
|
||||
stroke="#475467"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition-all ease duration-500"
|
||||
enterFrom="max-h-0"
|
||||
enterTo="max-h-[900px]"
|
||||
leave="transition-all ease duration-500"
|
||||
leaveFrom="max-h-[900px]"
|
||||
leaveTo="max-h-0"
|
||||
>
|
||||
<Disclosure.Panel className="overflow-hidden text-gray-500 duration-500">
|
||||
{options.map((op, idx) => (
|
||||
<div
|
||||
className="checkbox-container flex gap-2 items-center mb-[12px]"
|
||||
key={idx}
|
||||
>
|
||||
<input
|
||||
type={`${name === "capacity" ? "radio" : "checkbox"}`}
|
||||
id={op[valueField]}
|
||||
value={op[valueField]}
|
||||
className={`text-xl w-5 h-8 rounded ${name === "capacity" ? "accent-[#0D9895]" : ""}`}
|
||||
name={name}
|
||||
checked={ field.value.includes(op[valueField])}
|
||||
onChange={() => {
|
||||
if (name === "capacity") {
|
||||
field.onChange(op[valueField]);
|
||||
} else {
|
||||
const exists = field.value.includes(op[valueField]);
|
||||
if (exists) {
|
||||
field.onChange(field.value.filter((item) => item !== op[valueField]));
|
||||
} else {
|
||||
field.onChange([...field.value, op[valueField]]);
|
||||
}
|
||||
}
|
||||
}}
|
||||
// onChange={() => {
|
||||
// // remove if in array else add
|
||||
// const exists = field.value.includes(op[valueField]);
|
||||
// if (exists && op[name] !== "capacity") {
|
||||
// field.onChange(field.value.filter((item) => item != op[valueField]));
|
||||
// return;
|
||||
// }
|
||||
// field.onChange([...field.value, op[valueField]]);
|
||||
// }}
|
||||
onBlur={field.onBlur}
|
||||
/>
|
||||
|
||||
{name !== "capacity" ?
|
||||
<label htmlFor={op[valueField]}>
|
||||
{op[labelField]}{" "}
|
||||
{title == "Reviews"
|
||||
? Array(Number(op[valueField]))
|
||||
.fill("")
|
||||
.map((_, idx) => (
|
||||
<span
|
||||
className="ml-1"
|
||||
key={idx}
|
||||
>
|
||||
<StarIcon />
|
||||
</span>
|
||||
))
|
||||
: null}
|
||||
</label>
|
||||
:
|
||||
<span >
|
||||
{op[labelField]}{" "}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</Transition>
|
||||
</Disclosure>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import React, { Fragment } from "react";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import PaginationBar from "./PaginationBar";
|
||||
import PaginationHeader from "./PaginationHeader";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import Icon from "./Icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { secondsToHour } from "@/utils/utils";
|
||||
import moment from "moment";
|
||||
import { ID_PREFIX } from "@/utils/constants";
|
||||
|
||||
const History = ({ id, table }) => {
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = React.useState("");
|
||||
const [data, setCurrentTableData] = React.useState([]);
|
||||
const [pageSize, setPageSize] = React.useState(10);
|
||||
const [pageCount, setPageCount] = React.useState(0);
|
||||
const [dataTotal, setDataTotal] = React.useState(0);
|
||||
const [currentPage, setPage] = React.useState(0);
|
||||
const [canPreviousPage, setCanPreviousPage] = React.useState(false);
|
||||
const [canNextPage, setCanNextPage] = React.useState(false);
|
||||
|
||||
const statusMapping = [
|
||||
{ key: "0", value: "Pending" },
|
||||
{ key: "1", value: "Upcoming" },
|
||||
{ key: "2", value: "Ongoing" },
|
||||
{ key: "3", value: "Complete" },
|
||||
{ key: "4", value: "Declined" },
|
||||
{ key: "5", value: "Cancelled" }
|
||||
];
|
||||
|
||||
function updatePageSize(limit) {
|
||||
(async function () {
|
||||
setPageSize(limit);
|
||||
await getData(0, limit);
|
||||
})();
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
(async function () {
|
||||
await getData(currentPage - 1 > 0 ? currentPage - 1 : 0, pageSize);
|
||||
})();
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
(async function () {
|
||||
await getData(currentPage + 1 <= pageCount ? currentPage + 1 : 0, pageSize);
|
||||
})();
|
||||
}
|
||||
|
||||
async function getData(pageNum, limitNum) {
|
||||
try {
|
||||
let sdk = new MkdSDK();
|
||||
const result = await sdk.callRawAPI(
|
||||
"/v2/api/custom/ergo/booking/PAGINATE",
|
||||
{
|
||||
where: [
|
||||
table ? `${table === "customer" ? `customer.id = ${id}` : "1"} AND ${table === "host" ? `ergo_user.id = ${id}` : "1"} AND ${table == "property" ? `ergo_property.id = ${id}` : "1"}` : 1
|
||||
],
|
||||
page: pageNum,
|
||||
limit: limitNum
|
||||
},
|
||||
"POST"
|
||||
);
|
||||
|
||||
const { list, total, limit, num_pages, page } = result;
|
||||
|
||||
setCurrentTableData(list);
|
||||
setPageSize(limit);
|
||||
setPageCount(num_pages);
|
||||
setPage(page);
|
||||
setDataTotal(total);
|
||||
setCanPreviousPage(page > 1);
|
||||
setCanNextPage(page + 1 <= num_pages);
|
||||
} catch (error) {
|
||||
console.log("ERROR", error);
|
||||
tokenExpireError(dispatch, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
(async function () {
|
||||
await getData(1, pageSize);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PaginationHeader
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalNumber={dataTotal}
|
||||
updatePageSize={updatePageSize}
|
||||
/>
|
||||
<div className="overflow-x-auto p-5 bg-white shadow rounded">
|
||||
{data.map((data) => (
|
||||
<div
|
||||
key={data.id}
|
||||
className="border rounded px-5 py-4 flex justify-between flex-col lg:flex-row mb-4"
|
||||
>
|
||||
<div>{ID_PREFIX.BOOKINGS + data.id}</div>
|
||||
<img
|
||||
src={data.image_url}
|
||||
className="h-24 max-w-[135px]"
|
||||
alt="property_image"
|
||||
/>
|
||||
<div className="min-w-[219px] max-w-[219px] mb-4">
|
||||
<p className="font-semibold text-xl text-[#101828] mb-1">{data.property_name}</p>
|
||||
<p className="text-xs font-medium mb-1">{data.space_category}</p>
|
||||
<p className="bg-gray-200 text-xs p-2 w-fit rounded">{statusMapping.find((status) => status.key == data.status)?.value}</p>
|
||||
</div>
|
||||
<div className="min-w-[219px] max-w-[219px] mb-4">
|
||||
<p className="text-xs mb-1 font-medium ">Host</p>
|
||||
<p className="mb-1 text-sm">
|
||||
{data.host_last_name}, {data.host_first_name}{" "}
|
||||
</p>
|
||||
<p className="text-xs mb-1 font-medium ">Customer</p>
|
||||
<p className="mb-1 text-xs">
|
||||
{data.customer_last_name}, {data.customer_first_name}{" "}
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-w-[72px] max-w-[72px] mb-4">
|
||||
<p className="text-xs mb-1 font-medium ">Date</p>
|
||||
<p className="mb-1 text-sm">{moment(data.booking_start_time).format("MM/DD/YY")} </p>
|
||||
<p className="text-xs mb-1 font-medium ">Duration</p>
|
||||
<p className="mb-1 text-xs">{secondsToHour(data.duration)} </p>
|
||||
</div>
|
||||
<div className="min-w-[72px] max-w-[72px] mb-4">
|
||||
<p className="text-xs mb-1 font-medium ">Rate</p>
|
||||
<p className="mb-1 text-sm">${data?.rate?.toFixed(2)} </p>
|
||||
<p className="text-xs mb-1 font-medium ">Tax</p>
|
||||
<p className="mb-1 text-xs">${data?.tax?.toFixed(2)}</p>
|
||||
</div>
|
||||
<div className="min-w-[72px] max-w-[72px] mb-4">
|
||||
<p className="text-xs mb-1 font-medium ">Total</p>
|
||||
<p className="mb-1 text-xs">${data?.total?.toFixed(2)} </p>
|
||||
<p className="text-xs mb-1 font-medium ">Commission</p>
|
||||
<p className="mb-1 text-xs">${data?.commission?.toFixed(2)}</p>
|
||||
</div>
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative min-w-[60px] max-w-[60px] inline-block text-left"
|
||||
>
|
||||
<div className="">
|
||||
<Menu.Button className="inline-flex justify-center rounded-md border border-gray-300 bg-white px-1 py-3 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-[#33D4B7] focus:ring-offset-2 focus:ring-offset-gray-100">
|
||||
<Icon type="dots" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-10 mt-0 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() => navigate(`/admin/edit-booking/${data.id}`)}
|
||||
className={`${active ? "bg-gray-100 text-gray-900" : "text-gray-700"} w-full text-left block px-4 py-2 text-sm`}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<PaginationBar
|
||||
currentPage={currentPage}
|
||||
pageCount={pageCount}
|
||||
pageSize={pageSize}
|
||||
totalNumber={dataTotal}
|
||||
canPreviousPage={canPreviousPage}
|
||||
canNextPage={canNextPage}
|
||||
updatePageSize={updatePageSize}
|
||||
previousPage={previousPage}
|
||||
nextPage={nextPage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default History;
|
||||
@@ -0,0 +1,140 @@
|
||||
import { GlobalContext, showToast } from "@/globalContext";
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useContext } from "react";
|
||||
import GreenCheckIcon from "./frontend/icons/GreenCheckIcon";
|
||||
import { useNavigate } from "react-router";
|
||||
import { AuthContext, tokenExpireError } from "@/authContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { LoadingButton } from "./frontend";
|
||||
|
||||
export default function HostAddAddonsModal({setAddOnModal, getData}) {
|
||||
let sdk = new MkdSDK();
|
||||
const { state, dispatch: globalDispatch } = React.useContext(GlobalContext);
|
||||
const [loading, setLoading] = React.useState();
|
||||
const schema = yup
|
||||
.object({
|
||||
name: yup.string().required("Name is required"),
|
||||
cost: yup.number().required().typeError("Cost must be a number"),
|
||||
})
|
||||
.required();
|
||||
|
||||
const { dispatch } = React.useContext(AuthContext);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setError,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
sdk.setTable("add_on");
|
||||
|
||||
const result = await sdk.callRestAPI(
|
||||
{
|
||||
name: data.name,
|
||||
cost: data.cost,
|
||||
creator_id: Number(localStorage.getItem("user")),
|
||||
space_id: data.space_id || null,
|
||||
},
|
||||
"POST",
|
||||
);
|
||||
if (!result.error) {
|
||||
getData();
|
||||
showToast(globalDispatch, "Added");
|
||||
setLoading(false)
|
||||
setAddOnModal(false)
|
||||
} else {
|
||||
if (result.validation) {
|
||||
const keys = Object.keys(result.validation);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const field = keys[i];
|
||||
setError(field, {
|
||||
type: "manual",
|
||||
message: result.validation[field],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
console.log("Error", error);
|
||||
setError("name", {
|
||||
type: "manual",
|
||||
message: error.message,
|
||||
});
|
||||
tokenExpireError(dispatch, error.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={"popup-container flex items-center justify-center normal-case"}>
|
||||
<div
|
||||
className={`w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<form
|
||||
className=" w-full max-w-lg"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
|
||||
<div className="mb-4 ">
|
||||
<label
|
||||
className="mb-2 block text-sm font-bold text-gray-700"
|
||||
htmlFor="add_on_id"
|
||||
>
|
||||
Add-Ons
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="mb-3 w-full rounded border py-2 px-3 leading-tight text-gray-700 focus:outline-none"
|
||||
{...register("name")}
|
||||
placeholder="Addon Name"
|
||||
/>
|
||||
<p className="text-xs normal-case italic text-red-500">{errors.name?.message}</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 ">
|
||||
<label
|
||||
className="mb-2 block text-sm font-bold text-gray-700"
|
||||
htmlFor="add_on_cost"
|
||||
>
|
||||
Add-On Cost
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="mb-3 w-full rounded border py-2 px-3 leading-tight text-gray-700 focus:outline-none"
|
||||
{...register("cost")}
|
||||
placeholder="Addon Cost"
|
||||
/>
|
||||
<p className="text-xs normal-case italic text-red-500">{errors.cost?.message}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
onClick={() =>setAddOnModal(false)}
|
||||
className="mb-1 flex-1 rounded border border-[#667085] !bg-gradient-to-r px-6 py-2 text-sm font-semibold text-[#667085] outline-none focus:outline-none"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`ml-5 mb-1 flex-1 rounded !bg-gradient-to-r from-[#33D4B7] to-[#0D9895] px-6 py-2 text-sm font-semibold text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"}`}>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { GlobalContext, showToast } from "@/globalContext";
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
import { useContext } from "react";
|
||||
import GreenCheckIcon from "./frontend/icons/GreenCheckIcon";
|
||||
import { useNavigate } from "react-router";
|
||||
import { AuthContext, tokenExpireError } from "@/authContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { LoadingButton } from "./frontend";
|
||||
|
||||
export default function HostAddAmenityModal({setAmenityModal,getData}) {
|
||||
let sdk = new MkdSDK();
|
||||
const { state, dispatch: globalDispatch } = React.useContext(GlobalContext);
|
||||
const [loading, setLoading] = React.useState();
|
||||
const schema = yup
|
||||
.object({
|
||||
name: yup.string().required("Name is required"),
|
||||
})
|
||||
.required();
|
||||
|
||||
const { dispatch } = React.useContext(AuthContext);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setError,
|
||||
formState: { errors },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
sdk.setTable("amenity");
|
||||
|
||||
const result = await sdk.callRestAPI(
|
||||
{
|
||||
name: data.name,
|
||||
creator_id: Number(localStorage.getItem("user")),
|
||||
space_id: data.space_id || null,
|
||||
},
|
||||
"POST",
|
||||
);
|
||||
if (!result.error) {
|
||||
getData();
|
||||
showToast(globalDispatch, "Amenity Added");
|
||||
setLoading(false)
|
||||
setAmenityModal(false)
|
||||
} else {
|
||||
if (result.validation) {
|
||||
const keys = Object.keys(result.validation);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const field = keys[i];
|
||||
setError(field, {
|
||||
type: "manual",
|
||||
message: result.validation[field],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
console.log("Error", error);
|
||||
setError("name", {
|
||||
type: "manual",
|
||||
message: error.message,
|
||||
});
|
||||
tokenExpireError(dispatch, error.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={"popup-container flex items-center justify-center normal-case"}>
|
||||
<div
|
||||
className={`w-[510px] max-w-[80%] rounded-lg bg-white p-5 px-3 md:px-5`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<form
|
||||
className=" w-full max-w-lg"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
|
||||
<div className="mb-4 ">
|
||||
<label
|
||||
className="mb-2 block text-sm font-bold text-gray-700"
|
||||
htmlFor="add_on_id"
|
||||
>
|
||||
Amenity
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="mb-3 w-full rounded border py-2 px-3 leading-tight text-gray-700 focus:outline-none"
|
||||
{...register("name")}
|
||||
placeholder="Amenity Name"
|
||||
/>
|
||||
<p className="text-xs normal-case italic text-red-500">{errors.name?.message}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
onClick={() =>setAmenityModal(false)}
|
||||
className="mb-1 flex-1 rounded border border-[#667085] !bg-gradient-to-r px-6 py-2 text-sm font-semibold text-[#667085] outline-none focus:outline-none"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`ml-5 mb-1 flex-1 rounded !bg-gradient-to-r from-[#33D4B7] to-[#0D9895] px-6 py-2 text-sm font-semibold text-white outline-none focus:outline-none ${loading ? "py-1" : "py-2"}`}>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import React, { Fragment, useContext, useEffect, useState } from "react";
|
||||
import { AuthContext } from "@/authContext";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useTour } from "@reactour/tour";
|
||||
|
||||
export default function HostGettingStartedTour() {
|
||||
const navigate = useNavigate();
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const { dispatch } = useContext(AuthContext);
|
||||
const [modalOpen, setModalOpen] = useState(true);
|
||||
const [gettingStarted, setGettingStarted] = useState();
|
||||
const sdk = new MkdSDK();
|
||||
|
||||
const { setIsOpen } = useTour()
|
||||
|
||||
async function markAsNotFirstTimeUser() {
|
||||
try {
|
||||
await sdk.callRawAPI("/v2/api/custom/ergo/edit-self", { profile: { getting_started: 1 } }, "POST");
|
||||
globalDispatch({
|
||||
type: "SET_USER_DATA",
|
||||
payload: {
|
||||
...globalState.user,
|
||||
getting_started: 1,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
tokenExpireError(dispatch, err.message);
|
||||
console.log("err", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalState.user.id) return null;
|
||||
|
||||
const fetchUser = async () => {
|
||||
const result = await sdk.callRawAPI("/rest/profile/GETALL", {
|
||||
"payload": {
|
||||
"user_id": Number(globalState.user.id)
|
||||
},
|
||||
"selectStr": "*"
|
||||
},
|
||||
"POST");
|
||||
setGettingStarted(result.list[0]?.getting_started)
|
||||
}
|
||||
|
||||
fetchUser()
|
||||
|
||||
const setTour = () => {
|
||||
setModalOpen(false);
|
||||
globalDispatch({ type: "START_TOUR" });
|
||||
setIsOpen(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen && gettingStarted == 0}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={() => setModalOpen(false)}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
First time login?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">Would you like a tour of the site?</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
markAsNotFirstTimeUser();
|
||||
}}
|
||||
>
|
||||
No thanks
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`login-btn-gradient inline-flex justify-center rounded-md py-2 px-4 text-sm font-medium text-white`}
|
||||
onClick={() => {
|
||||
setTour()
|
||||
}}
|
||||
>
|
||||
Yes please
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import React, { useEffect, useState, useContext } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { Link } from "react-router-dom";
|
||||
import LogoIcon from "./frontend/icons/LogoIcon";
|
||||
import ReactTestUtils from "react-dom/test-utils";
|
||||
import StaticSearchBar from "./frontend/StaticSearchBar";
|
||||
import NavMenu from "./frontend/NavMenu";
|
||||
import { GlobalContext, showToast } from "@/globalContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { AuthContext, tokenExpireError } from "@/authContext";
|
||||
|
||||
const getNavBarVariant = (path) => {
|
||||
if (path.startsWith("/account") || path.startsWith("/property") || path.startsWith("/spaces") || path.startsWith("/help")) {
|
||||
return "light";
|
||||
}
|
||||
switch (path) {
|
||||
case "/contact-us":
|
||||
case "/faq":
|
||||
return "white";
|
||||
case "/search":
|
||||
case "/explore":
|
||||
case "/favorites":
|
||||
case "/reset-password":
|
||||
return "light";
|
||||
default:
|
||||
return "transparent";
|
||||
}
|
||||
};
|
||||
|
||||
export const HostHeader = () => {
|
||||
const { pathname } = useLocation();
|
||||
const [variant, setVariant] = useState(getNavBarVariant(pathname));
|
||||
const { dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const { dispatch } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
async function fetchProfile() {
|
||||
const sdk = new MkdSDK();
|
||||
try {
|
||||
const result = await sdk.getProfileCustom();
|
||||
globalDispatch({ type: "SET_USER_DATA", payload: result });
|
||||
} catch (err) {
|
||||
showToast(globalDispatch, err.message, 4000, "ERROR");
|
||||
tokenExpireError(dispatch, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
if (pathname == "/") {
|
||||
if (window.scrollY > 10) {
|
||||
setVariant("white");
|
||||
} else {
|
||||
setVariant("transparent");
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("scroll", onScroll);
|
||||
|
||||
fetchProfile();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
setVariant(getNavBarVariant(pathname));
|
||||
}, [pathname]);
|
||||
|
||||
const saveAsDraft = () => {
|
||||
console.log("clicked");
|
||||
const saveDraftBtn = document.getElementById("save-as-draft");
|
||||
if (saveDraftBtn) {
|
||||
ReactTestUtils.Simulate.click(saveDraftBtn);
|
||||
}
|
||||
};
|
||||
|
||||
if (pathname.includes("/login") || pathname.includes("/signup")) return null;
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`fixed top-0 left-0 z-50 flex w-screen flex-wrap items-center justify-between py-4 px-4 text-sm duration-500 md:flex-nowrap md:rounded-br-[32px] md:rounded-bl-[32px] md:px-12 header-${variant}`}
|
||||
>
|
||||
<nav className={`flex gap-6`}>
|
||||
<Link
|
||||
to="/"
|
||||
className=""
|
||||
>
|
||||
<LogoIcon fill={variant == "transparent" || variant == "light" ? undefined : "#101828"} />
|
||||
</Link>
|
||||
</nav>
|
||||
<StaticSearchBar className="hidden md:block" />
|
||||
|
||||
<div className="flex">
|
||||
<nav className={`z-50 inline `}>
|
||||
{" "}
|
||||
{pathname.startsWith("/account") && (
|
||||
<button
|
||||
className={`self-stretch rounded-sm border px-6 py-[5px] pb-[7px] ${variant == "transparent" ? "" : "border-white"}`}
|
||||
onClick={()=>navigate("/search?location=&booking_start_time=&max_capacity=&capacity=&size=")}
|
||||
>
|
||||
<span>Explore Spaces</span>
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<nav className="hidden items-center gap-6 md:flex">
|
||||
{["/spaces/add/4", "/spaces/add/5"].includes(pathname) || !pathname.startsWith("/spaces") ? (
|
||||
<>
|
||||
{" "}
|
||||
<Link
|
||||
to="/contact-us"
|
||||
className={`self-stretch rounded-md px-6 py-[5px] pb-[7px] font-normal my-border-${variant}`}
|
||||
>
|
||||
<span>Support</span>
|
||||
</Link>
|
||||
<NavMenu variant={variant} />
|
||||
</>
|
||||
) : (
|
||||
<button
|
||||
className={`self-stretch rounded-sm border px-6 py-[5px] pb-[7px] ${variant == "transparent" ? "" : "border-white"}`}
|
||||
onClick={saveAsDraft}
|
||||
>
|
||||
<span>Save as draft</span>
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
<nav className={`z-50 inline md:hidden`}>
|
||||
{" "}
|
||||
{pathname.startsWith("/spaces") && pathname != "/spaces/add/5" && pathname != "/spaces/add/4" ? (
|
||||
<button
|
||||
className={`self-stretch rounded-sm border px-6 py-[5px] pb-[7px] ${variant == "transparent" ? "" : "border-white"}`}
|
||||
onClick={saveAsDraft}
|
||||
>
|
||||
<span>Save as draft</span>
|
||||
</button>
|
||||
) : (
|
||||
<NavMenu variant={variant} />
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<StaticSearchBar className="flex w-full justify-center py-4 md:hidden" />
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default HostHeader;
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as ArrowNarrowLeft } from "../../assets/arrow-narrow-left.svg";
|
||||
import { ReactComponent as ArrowNarrowRight } from "../../assets/arrow-narrow-right.svg";
|
||||
|
||||
|
||||
const ArrowSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "narrow-right": return <ArrowNarrowRight
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
case "narrow-left": return <ArrowNarrowLeft
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default ArrowSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as BankNoteOne } from "../../assets/bank-note-one.svg";
|
||||
|
||||
const BankNoteSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "one": return <BankNoteOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default BankNoteSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as BuildingOne } from "../../assets/building-one.svg";
|
||||
|
||||
const BuildingSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "one": return <BuildingOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default BuildingSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Calender } from "../../assets/calender.svg";
|
||||
|
||||
const CalenderSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
default: return <Calender
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default CalenderSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as ChevronDown } from "../../assets/chevron-down.svg";
|
||||
|
||||
const CalenderSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case 'down': return <ChevronDown
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default CalenderSvg
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Dots } from "../../assets/dots.svg";
|
||||
|
||||
const DotsSvg = ({ className = "", id, onClick, onKeyUp }) => (
|
||||
<Dots
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default DotsSvg
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as FileCheckThree } from "../../assets/file-check-three.svg";
|
||||
import { ReactComponent as FilePlusThree } from "../../assets/file-plus-three.svg";
|
||||
import { ReactComponent as FileQuestionThree } from "../../assets/file-question-three.svg";
|
||||
import { ReactComponent as FileSearchOne } from "../../assets/file-search-one.svg";
|
||||
|
||||
|
||||
const BuildingSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "check-three": return <FileCheckThree
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
case "plus-three": return <FilePlusThree
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
case "question-three": return <FileQuestionThree
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
case "search-one": return <FileSearchOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default BuildingSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as GridOne } from "../../assets/grid-one.svg";
|
||||
|
||||
const GridSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "one": return <GridOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default GridSvg
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as HomeThree } from "../../assets/home-three.svg";
|
||||
import { ReactComponent as HomeLine } from "../../assets/home-line.svg";
|
||||
|
||||
const HomeSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "three": return <HomeThree
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
case "line": return <HomeLine
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default HomeSvg
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
const HouseIcon = () => (
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.40156 1.8778C8.59363 1.48869 8.68967 1.29413 8.82004 1.23197C8.93347 1.17789 9.06525 1.17789 9.17869 1.23197C9.30906 1.29413 9.4051 1.48869 9.59717 1.8778L11.4194 5.56944C11.4761 5.68432 11.5045 5.74176 11.5459 5.78635C11.5826 5.82584 11.6266 5.85783 11.6754 5.88056C11.7306 5.90623 11.794 5.91549 11.9208 5.93402L15.9968 6.5298C16.4261 6.59253 16.6407 6.6239 16.74 6.72874C16.8264 6.81995 16.8671 6.94529 16.8506 7.06985C16.8317 7.21302 16.6763 7.36436 16.3656 7.66702L13.4172 10.5387C13.3253 10.6282 13.2794 10.673 13.2497 10.7263C13.2235 10.7734 13.2066 10.8252 13.2001 10.8788C13.1928 10.9393 13.2036 11.0025 13.2253 11.129L13.921 15.1851C13.9944 15.6129 14.031 15.8269 13.9621 15.9538C13.9021 16.0642 13.7955 16.1417 13.6719 16.1646C13.5299 16.1909 13.3378 16.0899 12.9536 15.8879L9.30966 13.9716C9.19613 13.9119 9.13936 13.882 9.07955 13.8703C9.0266 13.8599 8.97213 13.8599 8.91918 13.8703C8.85937 13.882 8.8026 13.9119 8.68906 13.9716L5.04512 15.8879C4.66095 16.0899 4.46886 16.1909 4.32683 16.1646C4.20325 16.1417 4.09662 16.0642 4.03663 15.9538C3.96768 15.8269 4.00437 15.6129 4.07774 15.1851L4.77342 11.129C4.79511 11.0025 4.80595 10.9393 4.79862 10.8788C4.79212 10.8252 4.77528 10.7734 4.74902 10.7263C4.71937 10.673 4.67341 10.6282 4.5815 10.5387L1.63315 7.66702C1.3224 7.36436 1.16703 7.21302 1.14812 7.06985C1.13167 6.94529 1.17231 6.81995 1.25872 6.72874C1.35804 6.6239 1.57266 6.59253 2.00189 6.5298L6.07794 5.93402C6.2047 5.91549 6.26808 5.90623 6.32328 5.88056C6.37215 5.85783 6.41615 5.82584 6.45284 5.78635C6.49427 5.74176 6.52262 5.68432 6.57933 5.56944L8.40156 1.8778Z"
|
||||
stroke="#667085"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default HouseIcon;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as ImageThree } from "../../assets/image-three.svg";
|
||||
|
||||
const ImageSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "three": return <ImageThree
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default ImageSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
export default function LogoIcon({ fill }) {
|
||||
return (
|
||||
<svg
|
||||
width="69"
|
||||
height="25"
|
||||
viewBox="0 0 69 25"
|
||||
fill={fill ?? "black"}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M7.95426 19.1764C6.3634 19.1764 4.96896 18.8828 3.77091 18.2957C2.57286 17.7086 1.63995 16.8476 0.972187 15.7125C0.324062 14.5775 0 13.2076 0 11.6029C0 10.0764 0.324062 8.74567 0.972187 7.61063C1.63995 6.47558 2.56304 5.59494 3.74145 4.9687C4.91986 4.3229 6.30448 4 7.89534 4C9.40763 4 10.7333 4.27398 11.8725 4.82193C13.0312 5.35032 13.9249 6.13311 14.5533 7.1703C15.2015 8.18793 15.5255 9.4404 15.5255 10.9277C15.5255 11.1821 15.5157 11.4267 15.4961 11.6616C15.4764 11.8768 15.447 12.0921 15.4077 12.3074H2.03275V10.5461H13.316L12.4911 11.6322C12.5108 11.4365 12.5206 11.2506 12.5206 11.0745C12.5206 10.8788 12.5206 10.6831 12.5206 10.4874C12.5206 9.13707 12.1278 8.13901 11.3422 7.49321C10.5762 6.82783 9.40763 6.49515 7.83642 6.49515C6.08844 6.49515 4.84129 6.89633 4.09497 7.69869C3.34864 8.50105 2.97548 9.65567 2.97548 11.1625V11.9258C2.97548 13.4522 3.34864 14.6166 4.09497 15.419C4.84129 16.2213 6.09826 16.6225 7.86588 16.6225C9.39781 16.6225 10.4977 16.3877 11.1654 15.918C11.8528 15.4288 12.1965 14.7536 12.1965 13.8925V13.6577H15.3782V13.9219C15.3782 14.9591 15.0542 15.8789 14.406 16.6812C13.7776 17.464 12.9036 18.0805 11.7841 18.5306C10.6842 18.9611 9.40763 19.1764 7.95426 19.1764Z" />
|
||||
<path d="M21.6795 18.8828H18.4978V4.29355H21.4143V8.43256L21.6795 8.57933V18.8828ZM21.6795 10.6929H20.9724V8.22707H21.6206C21.7581 7.42471 22.033 6.71042 22.4455 6.08418C22.8579 5.43838 23.4078 4.92957 24.0952 4.55774C24.8023 4.18591 25.6566 4 26.6583 4C27.7778 4 28.691 4.23484 29.3981 4.70451C30.1051 5.17419 30.6158 5.80042 30.93 6.58321C31.2639 7.366 31.4308 8.21729 31.4308 9.13707V11.0451H28.2786V9.75352C28.2786 8.69675 28.0429 7.92374 27.5715 7.4345C27.1002 6.94525 26.3146 6.70063 25.2147 6.70063C23.9577 6.70063 23.0543 7.0431 22.5044 7.72804C21.9545 8.41299 21.6795 9.40126 21.6795 10.6929Z" />
|
||||
<path d="M41.7076 24.1667C40.2542 24.1667 38.9776 23.9416 37.8778 23.4915C36.7779 23.061 35.9138 22.4347 35.2853 21.6128C34.6764 20.7909 34.372 19.7928 34.372 18.6186H37.5243C37.5243 19.3231 37.6716 19.8907 37.9662 20.3212C38.2608 20.7517 38.7223 21.0551 39.3508 21.2312C39.9989 21.4269 40.8434 21.5247 41.8844 21.5247C43.0038 21.5247 43.8877 21.3975 44.5358 21.1431C45.2035 20.9083 45.6847 20.4875 45.9793 19.8809C46.2739 19.2742 46.4212 18.4327 46.4212 17.3564V8.81417L46.6569 8.60868V4.29355H49.5735V17.1803C49.5735 18.8045 49.2494 20.1255 48.6013 21.1431C47.9532 22.1803 47.0399 22.9436 45.8615 23.4328C44.6831 23.922 43.2984 24.1667 41.7076 24.1667ZM40.3819 17.5619C38.9285 17.5619 37.6814 17.2781 36.6404 16.7106C35.6192 16.1431 34.8237 15.3505 34.2542 14.3328C33.7042 13.3152 33.4293 12.1312 33.4293 10.7809C33.4293 9.43062 33.7141 8.24664 34.2836 7.22901C34.8728 6.21139 35.6977 5.41881 36.7583 4.85129C37.8385 4.28376 39.1151 4 40.5881 4C42.12 4 43.4163 4.34247 44.4769 5.02741C45.5571 5.69279 46.2248 6.66149 46.4802 7.93353H47.1577L46.981 10.4874H46.4212C46.4212 9.66545 46.2248 8.98051 45.832 8.43256C45.4392 7.86503 44.8795 7.44428 44.1528 7.1703C43.4261 6.89633 42.5423 6.75934 41.5014 6.75934C40.4997 6.75934 39.6257 6.88654 38.8794 7.14095C38.1527 7.39536 37.593 7.81611 37.2002 8.4032C36.827 8.97073 36.6404 9.7633 36.6404 10.7809C36.6404 11.779 36.827 12.5716 37.2002 13.1587C37.5733 13.7458 38.1135 14.1763 38.8205 14.4503C39.5472 14.7047 40.4113 14.8319 41.413 14.8319C43.0235 14.8319 44.2608 14.4992 45.125 13.8338C45.9891 13.1684 46.4212 12.19 46.4212 10.8983H46.981V13.7751H46.215C45.9597 14.8906 45.341 15.8006 44.359 16.5051C43.377 17.2096 42.0513 17.5619 40.3819 17.5619Z" />
|
||||
<path d="M60.5895 19.1764C58.979 19.1764 57.5551 18.8633 56.3178 18.237C55.1001 17.5912 54.1476 16.7008 53.4601 15.5657C52.7924 14.4111 52.4585 13.0902 52.4585 11.6029C52.4585 10.0764 52.7924 8.74567 53.4601 7.61063C54.1476 6.47558 55.1001 5.59494 56.3178 4.9687C57.5551 4.3229 58.979 4 60.5895 4C62.2393 4 63.673 4.3229 64.8907 4.9687C66.1084 5.59494 67.0511 6.47558 67.7189 7.61063C68.4063 8.74567 68.75 10.0764 68.75 11.6029C68.75 13.0902 68.4063 14.4111 67.7189 15.5657C67.0511 16.7008 66.1084 17.5912 64.8907 18.237C63.673 18.8633 62.2393 19.1764 60.5895 19.1764ZM60.5895 16.3583C62.3768 16.3583 63.6632 15.9571 64.4488 15.1548C65.2344 14.3328 65.6272 13.1489 65.6272 11.6029C65.6272 10.0568 65.2344 8.87288 64.4488 8.05095C63.6632 7.20944 62.3768 6.78869 60.5895 6.78869C58.8219 6.78869 57.5453 7.20944 56.7597 8.05095C55.9741 8.87288 55.5813 10.0568 55.5813 11.6029C55.5813 13.1489 55.9741 14.3328 56.7597 15.1548C57.5453 15.9571 58.8219 16.3583 60.5895 16.3583Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Logo } from "../../assets/logo.svg";
|
||||
|
||||
const LogoSvg = ({ className = "",fill, id, onClick, onKeyUp }) => (
|
||||
<Logo
|
||||
id={id}
|
||||
className={`${className || ""} ${fill || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default LogoSvg
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Logout } from "../../assets/logout.svg";
|
||||
|
||||
const LogoutSvg = ({ className = "", id, onClick, onKeyUp }) => (
|
||||
<Logout
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default LogoutSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as MailOne } from "../../assets/mail-one.svg";
|
||||
|
||||
const SettingsSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
default : return <MailOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsSvg
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Minus } from "../../assets/minus.svg";
|
||||
|
||||
const MinusSvg = ({ className = "", id, onClick, onKeyUp }) => (
|
||||
<Minus
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default MinusSvg
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Pencil } from "../../assets/pencil.svg";
|
||||
|
||||
const PencilSvg = ({ className = "", id, onClick, onKeyUp }) => (
|
||||
<Pencil
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default PencilSvg
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Plus } from "../../assets/plus.svg";
|
||||
|
||||
const PlusSvg = ({ className = "", id, onClick, onKeyUp }) => (
|
||||
<Plus
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default PlusSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as BookingReceipt } from "../../assets/booking-receipt.svg";
|
||||
|
||||
const ReceiptSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "booking": return <BookingReceipt
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default ReceiptSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as SettingsTwo } from "../../assets/settings-two.svg";
|
||||
|
||||
const SettingsSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "two": return <SettingsTwo
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default SettingsSvg
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as ShareOne } from "../../assets/share-one.svg";
|
||||
|
||||
const ShareSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
switch (variant) {
|
||||
case "one": return <ShareOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default ShareSvg
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as Star } from "../../assets/star.svg";
|
||||
|
||||
const StarSvg = ({ className = "", id, onClick, onKeyUp }) => (
|
||||
<Star
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
);
|
||||
|
||||
export default StarSvg;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as TrashTwo } from "../../assets/trash-two.svg";
|
||||
|
||||
const TrashSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "two": return <TrashTwo
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default TrashSvg
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as UserSquare } from "../../assets/user-square.svg";
|
||||
import { ReactComponent as UserCircle } from "../../assets/user-circle.svg";
|
||||
|
||||
const UserSvg = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "square": return <UserSquare
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
case "circle": return <UserCircle
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default UserSvg
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { ReactComponent as UsersOne } from "../../assets/users-one.svg";
|
||||
|
||||
const Users = ({ className = "", id, onClick, onKeyUp, variant }) => {
|
||||
|
||||
switch (variant) {
|
||||
case "one": return <UsersOne
|
||||
id={id}
|
||||
className={`${className || ""}`}
|
||||
onClick={onClick}
|
||||
onKeyUp={onKeyUp}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export default Users
|
||||
@@ -0,0 +1,75 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import LogoSvg from "./LogoSvg"
|
||||
import LogoutSvg from "./LogoutSvg"
|
||||
import ShareSvg from "./ShareSvg"
|
||||
import GridSvg from "./GridSvg"
|
||||
import UserSvg from "./UserSvg"
|
||||
import ImageSvg from "./ImageSvg"
|
||||
import BankNoteSvg from "./BankNoteSvg"
|
||||
import BuildingSvg from "./BuildingSvg"
|
||||
import UsersSvg from "./UsersSvg"
|
||||
import HomeSvg from "./HomeSvg"
|
||||
import FileSvg from "./FileSvg"
|
||||
import CalenderSvg from "./Calender";
|
||||
import ReceiptSvg from "./ReceiptSvg";
|
||||
import MailSvg from "./MailSvg"
|
||||
import SettingsSvg from "./SettingsSvg"
|
||||
import ArrowSvg from "./ArrowSvg"
|
||||
import ChevronSvg from "./ChevronSvg"
|
||||
import TrashSvg from "./TrashSvg"
|
||||
import PencilSvg from "./PencilSvg"
|
||||
import PlusSvg from "./PlusSvg"
|
||||
import MinusSvg from "./MinusSvg"
|
||||
import DotsSvg from "./DotsSvg"
|
||||
import StarSvg from "./StarSvg"
|
||||
|
||||
|
||||
const getIcon = (type, className, id, fill, onClick, onKeyUp, variant) => {
|
||||
const icons = {
|
||||
logo: <LogoSvg onClick={onClick} onKeyUp={onKeyUp} className={className} fill={fill} />,
|
||||
logout: <LogoutSvg onClick={onClick} onKeyUp={onKeyUp} className={className} />,
|
||||
pencil: <PencilSvg onClick={onClick} onKeyUp={onKeyUp} className={className} />,
|
||||
share: <ShareSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
grid: <GridSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
user: <UserSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
image: <ImageSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
banknote: <BankNoteSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
building: <BuildingSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
users: <UsersSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
home: <HomeSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
file: <FileSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
calender: <CalenderSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
receipt: <ReceiptSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
mail: <MailSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
settings: <SettingsSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
arrow: <ArrowSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
chevron: <ChevronSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
trash: <TrashSvg onClick={onClick} onKeyUp={onKeyUp} className={className} variant={variant} />,
|
||||
plus: <PlusSvg onClick={onClick} onKeyUp={onKeyUp} className={className} />,
|
||||
minus: <MinusSvg onClick={onClick} onKeyUp={onKeyUp} className={className} />,
|
||||
dots: <DotsSvg onClick={onClick} onKeyUp={onKeyUp} className={className} />,
|
||||
star: <StarSvg onClick={onClick} onKeyUp={onKeyUp} className={className} />
|
||||
}
|
||||
|
||||
return icons[type] || null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Icon = ({ className, id, fill = '', onClick, onKeyUp, type, variant }) => {
|
||||
const [icon, setIcon] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (type) {
|
||||
// Remove all white space from the string, with the regex
|
||||
const iconType = type.toLocaleLowerCase().replace(/\s+/g, '');
|
||||
|
||||
// set the icon based on icon type change, useful for conditional icon renderings
|
||||
setIcon(getIcon(iconType, className, id, fill, onClick, onKeyUp, variant));
|
||||
}
|
||||
}, [type, className]);
|
||||
|
||||
return icon;
|
||||
};
|
||||
|
||||
export default Icon
|
||||
@@ -0,0 +1,40 @@
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import React from "react";
|
||||
import { useContext } from "react";
|
||||
|
||||
export default function LoadingSpinner() {
|
||||
const { state } = useContext(GlobalContext);
|
||||
|
||||
if (!state.loading) return null;
|
||||
|
||||
return (
|
||||
<div className="popup-container flex items-center justify-center">
|
||||
<div className="">
|
||||
<svg
|
||||
style={{ margin: "auto", background: "transparent", display: " block", shapeRendering: "auto" }}
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
className="md:w-[100px] md:h-[100px] w-[80px] h-[80px]"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#d0d5dd"
|
||||
strokeWidth="6"
|
||||
strokeDasharray="42.76482137044271 42.76482137044271"
|
||||
d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40 C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z"
|
||||
strokeLinecap="round"
|
||||
style={{ transform: "scale(0.8)", transformOrigin: "50px 50px" }}
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dashoffset"
|
||||
repeatCount="indefinite"
|
||||
dur="1.882051282051282s"
|
||||
keyTimes="0;1"
|
||||
values="0;256.58892822265625"
|
||||
></animate>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import React from "react";
|
||||
import { GlobalContext, showToast } from "@/globalContext";
|
||||
import { AuthContext } from "../authContext";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import moment from "moment";
|
||||
|
||||
let sdk = new MkdSDK();
|
||||
|
||||
export default function Modal({ showModal, modalShowTitle, modalShowMessage, type, modalBtnText, itemId, itemId2, table1, table2, backTo }) {
|
||||
const { dispatch: globalDispatch } = React.useContext(GlobalContext);
|
||||
const { dispatch } = React.useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal ? (
|
||||
<>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto overflow-x-hidden outline-none focus:outline-none">
|
||||
<div className="relative my-6 mx-auto w-auto max-w-3xl md:min-w-[35rem]">
|
||||
{/*content*/}
|
||||
<div className="relative flex w-full flex-col rounded-lg border-0 bg-white shadow-lg outline-none focus:outline-none">
|
||||
{/*header*/}
|
||||
<div className="flex items-start justify-between rounded-t border-solid border-slate-200 px-5 pt-6">
|
||||
<h3 className="text-xl font-semibold">{modalShowTitle}</h3>
|
||||
<button
|
||||
className="float-right ml-auto border-0 bg-transparent p-1 text-3xl font-semibold leading-none text-black outline-none focus:outline-none"
|
||||
onClick={() =>
|
||||
globalDispatch({
|
||||
type: "SHOWMODAL",
|
||||
payload: {
|
||||
showModal: false,
|
||||
modalShowMessage: "",
|
||||
modalBtnText: "",
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<span className="block h-6 w-6 bg-transparent text-2xl text-black outline-none focus:outline-none">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{/*body*/}
|
||||
<div className="relative flex-auto px-6 py-2">
|
||||
<p className="text-lg my-2 normal-case leading-relaxed text-slate-500">{modalShowMessage}</p>
|
||||
</div>
|
||||
{/*footer*/}
|
||||
<div className="flex items-center justify-end rounded-b border-solid border-slate-200 px-6 pb-6">
|
||||
<button
|
||||
className="background-transparent mr-1 mb-1 rounded border border-[##98A2B3] px-6 py-2 text-sm font-bold text-[#667085] outline-none focus:outline-none"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
globalDispatch({
|
||||
type: "SHOWMODAL",
|
||||
payload: {
|
||||
showModal: false,
|
||||
modalShowMessage: "",
|
||||
modalBtnText: "",
|
||||
},
|
||||
});
|
||||
|
||||
if (type === "Delete") {
|
||||
return;
|
||||
}
|
||||
|
||||
globalDispatch({
|
||||
type: "SHOWMODAL",
|
||||
payload: {
|
||||
showModal: false,
|
||||
modalShowMessage: "You are about to log out.",
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="ml-5 mb-1 rounded border border-[##98A2B3] !bg-gradient-to-r from-primary to-primary-dark px-6 py-2 text-sm font-medium text-white outline-none focus:outline-none"
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
globalDispatch({
|
||||
type: "SHOWMODAL",
|
||||
payload: {
|
||||
showModal: false,
|
||||
modalShowMessage: "",
|
||||
modalBtnText: "",
|
||||
itemId: "",
|
||||
itemId2: "",
|
||||
table1: "",
|
||||
table2: "",
|
||||
},
|
||||
});
|
||||
|
||||
// if (type === "Delete") {
|
||||
// try {
|
||||
// if (table1 == "user") {
|
||||
// sdk.setTable("device");
|
||||
// await sdk.callRestAPI({ user_id: itemId }, "DELETEALL");
|
||||
// sdk.setTable("notification");
|
||||
// await sdk.callRestAPI({ user_id: itemId }, "DELETEALL");
|
||||
// }
|
||||
// sdk.setTable(table1);
|
||||
// await sdk.callRestAPI(
|
||||
// {
|
||||
// id: itemId,
|
||||
// },
|
||||
// "DELETE",
|
||||
// );
|
||||
// if (table1 == "property_spaces_images") {
|
||||
// sdk.setTable("photo");
|
||||
// await sdk.callRestAPI({ id: itemId2 }, "DELETE");
|
||||
// }
|
||||
// showToast(globalDispatch, "Successful");
|
||||
// if (table2) {
|
||||
// sdk.setTable(table2);
|
||||
// await sdk.callRestAPI(
|
||||
// {
|
||||
// user_id: itemId,
|
||||
// },
|
||||
// "DELETEALL",
|
||||
// );
|
||||
// }
|
||||
// globalDispatch({
|
||||
// type: "DELETED",
|
||||
// payload: {
|
||||
// deleted: true,
|
||||
// },
|
||||
// });
|
||||
// if (backTo) {
|
||||
// navigate(backTo);
|
||||
// }
|
||||
// } catch (error) {
|
||||
// showToast(globalDispatch, error.message, 4000, "ERROR");
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (type == "Delete") {
|
||||
try {
|
||||
// sdk.setTable(table1);
|
||||
// await sdk.callRestAPI({ id: itemId, deleted_at: moment().format("yyyy-MM-DD HH:mm:ss") }, "PUT");
|
||||
await sdk.callRawAPI("/v2/api/custom/ergo/soft-delete", { id: itemId, entity: table1, type: "delete" }, "POST");
|
||||
showToast(globalDispatch, "Successful");
|
||||
globalDispatch({
|
||||
type: "DELETED",
|
||||
payload: {
|
||||
deleted: true,
|
||||
},
|
||||
});
|
||||
if (backTo) {
|
||||
navigate(backTo);
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(globalDispatch, err.message, 4000, "ERROR");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type == "BaasDelete") {
|
||||
try {
|
||||
sdk.setTable(table1);
|
||||
await sdk.callRestAPI({ id: itemId, deleted_at: moment().format("yyyy-MM-DD HH:mm:ss") }, "PUT");
|
||||
// await sdk.callRawAPI("/v2/api/custom/ergo/property-space-images", { id: itemId, entity: table1, type: "delete" }, "POST");
|
||||
showToast(globalDispatch, "Successful");
|
||||
globalDispatch({
|
||||
type: "DELETED",
|
||||
payload: {
|
||||
deleted: true,
|
||||
},
|
||||
});
|
||||
if (backTo) {
|
||||
navigate(backTo);
|
||||
}
|
||||
} catch (err) {
|
||||
showToast(globalDispatch, err.message, 4000, "ERROR");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (type === "Edit") {
|
||||
globalDispatch({
|
||||
type: "SAVE_CHANGES",
|
||||
payload: {
|
||||
saveChanges: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "LOGOUT",
|
||||
});
|
||||
|
||||
navigate("/admin/login");
|
||||
}}
|
||||
>
|
||||
{modalBtnText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="fixed inset-0 z-40 bg-black opacity-25"></div>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
|
||||
export default function NotVerifiedModal() {
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const { state: authState } = useContext(AuthContext);
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={globalState.notVerifiedModal}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={() => globalDispatch({ type: "CLOSE_NOT_VERIFIED_MODAL" })}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
User not verified
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">Please verify your account to proceed with booking</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={() => globalDispatch({ type: "CLOSE_NOT_VERIFIED_MODAL" })}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
navigate(`/account/verification?redirect_uri=${pathname}`);
|
||||
globalDispatch({ type: "CLOSE_NOT_VERIFIED_MODAL" });
|
||||
}}
|
||||
className={`login-btn-gradient inline-flex justify-center rounded-md py-2 px-4 text-sm font-medium text-white`}
|
||||
>
|
||||
Get verified
|
||||
</button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from "react";
|
||||
import Icon from "./Icons";
|
||||
const PaginationBar = ({ currentPage, pageSize, canPreviousPage, canNextPage, previousPage, nextPage, totalNumber, className }) => {
|
||||
return (
|
||||
<>
|
||||
<div className={`flex justify-between px-6 text-[#667085] font-medium py-2 items-center text-sm ${className ?? ""}`}>
|
||||
<div className="md:mt-2">
|
||||
<span>
|
||||
Showing{" "}
|
||||
<strong>
|
||||
{totalNumber < 1 ? 0 : currentPage > 1 ? (currentPage - 1) * pageSize + 1 : currentPage} - {currentPage * pageSize < totalNumber ? currentPage * pageSize : totalNumber} of {totalNumber}
|
||||
</strong>{" "}
|
||||
</span>
|
||||
</div>
|
||||
{/* */}
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={previousPage}
|
||||
disabled={!canPreviousPage}
|
||||
className="disabled:opacity-50 font-semibold text-sm md:px-5 px-3 py-2.5 text-center inline-flex items-center md:mr-2 md:mb-2"
|
||||
>
|
||||
<Icon
|
||||
type="arrow"
|
||||
variant="narrow-left"
|
||||
className="stroke-[#667085] h-4 w-4"
|
||||
/>{" "}
|
||||
<span className="ml-2">Prev</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={nextPage}
|
||||
disabled={!canNextPage}
|
||||
className="disabled:opacity-50 font-semibold text-sm md:px-5 px-3 py-2.5 text-center inline-flex items-center mr-2 md:mb-2"
|
||||
>
|
||||
<span className="mr-2">Next</span>
|
||||
<Icon
|
||||
type="arrow"
|
||||
variant="narrow-right"
|
||||
className="stroke-[#667085] h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaginationBar;
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
|
||||
const PaginationBar = ({ currentPage, pageSize, updatePageSize, totalNumber, noBorder }) => {
|
||||
return (
|
||||
<>
|
||||
<div className={"flex justify-between bg-white py-4 font-medium items-center text-[#667085] " + (noBorder ? "" : "border px-6")}>
|
||||
<div className="">
|
||||
<p className="text-sm mb-0">
|
||||
Showing{" "}
|
||||
<span>
|
||||
{totalNumber < 1 ? 0 : currentPage > 1 ? (currentPage - 1) * pageSize + 1 : currentPage}-{currentPage * pageSize < totalNumber ? currentPage * pageSize : totalNumber} of {totalNumber}
|
||||
</span>{" "}
|
||||
</p>
|
||||
</div>
|
||||
{/* */}
|
||||
<div>
|
||||
<span className="mr-2 text-sm">Results per page:</span>
|
||||
<select
|
||||
className="md:mt-2 cursor-pointer border w-[53px] py-1 text-sm bg-white"
|
||||
value={pageSize}
|
||||
onChange={(e) => {
|
||||
updatePageSize(Number(e.target.value));
|
||||
}}
|
||||
>
|
||||
{[5, 10, 20, 30, 40, 50, "ALL"].map((pageSize) => (
|
||||
<option
|
||||
key={pageSize}
|
||||
value={pageSize == "ALL" ? 100000 : pageSize}
|
||||
>
|
||||
{pageSize}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaginationBar;
|
||||
@@ -0,0 +1,181 @@
|
||||
import React, { Fragment } from "react";
|
||||
import MkdSDK from "@/utils/MkdSDK";
|
||||
import PaginationBar from "./PaginationBar";
|
||||
import PaginationHeader from "./PaginationHeader";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import Icon from "./Icons";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { secondsToHour } from "@/utils/utils";
|
||||
import moment from "moment";
|
||||
import { ID_PREFIX } from "@/utils/constants";
|
||||
|
||||
const Payment = ({ id, table }) => {
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = React.useState("");
|
||||
const [data, setCurrentTableData] = React.useState([]);
|
||||
const [pageSize, setPageSize] = React.useState(10);
|
||||
const [pageCount, setPageCount] = React.useState(0);
|
||||
const [dataTotal, setDataTotal] = React.useState(0);
|
||||
const [currentPage, setPage] = React.useState(0);
|
||||
const [canPreviousPage, setCanPreviousPage] = React.useState(false);
|
||||
const [canNextPage, setCanNextPage] = React.useState(false);
|
||||
|
||||
const payoutMapping = [
|
||||
{ key: "0", value: "Pending" },
|
||||
{ key: "1", value: "Initiated" },
|
||||
{ key: "2", value: "Paid" },
|
||||
{ key: "3", value: "Cancelled" }
|
||||
];
|
||||
|
||||
function updatePageSize(limit) {
|
||||
(async function () {
|
||||
setPageSize(limit);
|
||||
await getData(0, limit);
|
||||
})();
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
(async function () {
|
||||
await getData(currentPage - 1 > 0 ? currentPage - 1 : 0, pageSize);
|
||||
})();
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
(async function () {
|
||||
await getData(currentPage + 1 <= pageCount ? currentPage + 1 : 0, pageSize);
|
||||
})();
|
||||
}
|
||||
|
||||
async function getData(pageNum, limitNum) {
|
||||
try {
|
||||
let sdk = new MkdSDK();
|
||||
const result = await sdk.callRawAPI(
|
||||
"/v2/api/custom/ergo/payout/PAGINATE",
|
||||
{
|
||||
where: [table ? `${table === "host" ? `ergo_user.id = ${id}` : "1"}` : 1],
|
||||
page: pageNum,
|
||||
limit: limitNum
|
||||
},
|
||||
"POST"
|
||||
);
|
||||
|
||||
const { list, total, limit, num_pages, page } = result;
|
||||
|
||||
setCurrentTableData(list);
|
||||
setPageSize(limit);
|
||||
setPageCount(num_pages);
|
||||
setPage(page);
|
||||
setDataTotal(total);
|
||||
setCanPreviousPage(page > 1);
|
||||
setCanNextPage(page + 1 <= num_pages);
|
||||
} catch (error) {
|
||||
console.log("ERROR", error);
|
||||
tokenExpireError(dispatch, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
(async function () {
|
||||
await getData(1, pageSize);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PaginationHeader
|
||||
currentPage={currentPage}
|
||||
pageSize={pageSize}
|
||||
totalNumber={dataTotal}
|
||||
updatePageSize={updatePageSize}
|
||||
/>
|
||||
<div className="overflow-x-auto p-5 bg-white shadow rounded">
|
||||
{data.map((data, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border rounded px-5 py-4 flex justify-between flex-col lg:flex-row mb-4"
|
||||
>
|
||||
<div>{ID_PREFIX.PAYOUT + data.id}</div>
|
||||
<div className="min-w-[219px] max-w-[219px] mr-[22px]">
|
||||
<p className="text-xs mb-1 font-medium ">Host</p>
|
||||
<p className="mb-1 text-sm">
|
||||
{data.host_last_name}, {data.host_first_name}{" "}
|
||||
</p>
|
||||
<p className="text-xs mb-1 font-medium ">Customer</p>
|
||||
<p className="mb-1 text-sm">
|
||||
{data.customer_last_name}, {data.customer_first_name}{" "}
|
||||
</p>
|
||||
</div>
|
||||
<div className="min-w-[219px] max-w-[219px] mr-[22px]">
|
||||
<p className="text-xs mb-1 font-medium ">Booking Date</p>
|
||||
<p className="mb-1 text-sm">{data.create_at} </p>
|
||||
<p className="text-xs mb-1 font-medium ">Order Number</p>
|
||||
<p className="mb-1 text-sm">{data.id}</p>
|
||||
</div>
|
||||
<div className="min-w-[72px] max-w-[72px] mb-4">
|
||||
<p className="text-xs mb-1 font-medium ">Total</p>
|
||||
<p className="mb-1 text-sm">${data?.total?.toFixed(2)} </p>
|
||||
<p className="text-xs mb-1 font-medium ">Tax</p>
|
||||
<p className="mb-1 text-sm">${data?.tax?.toFixed(2)}</p>
|
||||
</div>
|
||||
<div className="min-w-[72px] max-w-[72px] mb-4">
|
||||
<p className="text-xs mb-1 font-medium ">Commission</p>
|
||||
<p className="mb-1 text-sm">${data?.commission?.toFixed(2)} </p>
|
||||
<p className="text-xs mb-1 font-medium ">Payout Date</p>
|
||||
<p className="mb-1 text-sm">{data?.initiated_at ? moment(data.initiated_at).add(7, "days").format("MM/DD/YY") : ""}</p>
|
||||
</div>
|
||||
<div className="flex min-w-[60px] max-w-[60px] mr-[22px] items-center justify-center">
|
||||
<p>{payoutMapping.find((status) => status.key == data.status)?.value}</p>
|
||||
</div>
|
||||
<Menu
|
||||
as="div"
|
||||
className="relative min-w-[60px] max-w-[60px] inline-block text-left"
|
||||
>
|
||||
<div className="h-full flex items-center">
|
||||
<Menu.Button className="inline-flex justify-center rounded-md border border-gray-300 bg-white px-1 py-3 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-[#33D4B7] focus:ring-offset-2 focus:ring-offset-gray-100">
|
||||
<Icon type="dots" />
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-10 mt-0 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
onClick={() => navigate(`/admin/edit-payout/${data.id}`)}
|
||||
className={`${active ? "bg-gray-100 text-gray-900" : "text-gray-700"} w-full text-left block px-4 py-2 text-sm`}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<PaginationBar
|
||||
currentPage={currentPage}
|
||||
pageCount={pageCount}
|
||||
pageSize={pageSize}
|
||||
totalNumber={dataTotal}
|
||||
canPreviousPage={canPreviousPage}
|
||||
canNextPage={canNextPage}
|
||||
updatePageSize={updatePageSize}
|
||||
previousPage={previousPage}
|
||||
nextPage={nextPage}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Payment;
|
||||
@@ -0,0 +1,102 @@
|
||||
import { AuthContext } from "@/authContext";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
|
||||
export default function DeleteAccountModal({ modalOpen, closeModal }) {
|
||||
const { dispatch: authDispatch, state: authState } = useContext(AuthContext);
|
||||
const { state: globalState, dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function requestAccountDelete() {
|
||||
setLoading(true);
|
||||
try {
|
||||
await callCustomAPI("confirm-delete-email", "post", { user_id: globalState.user.id, email: globalState.user.email, role: authState.role }, "");
|
||||
navigate("/account/delete/check");
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
Are you sure you want to delete your account?
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">You will receive an email to confirm this action</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex gap-4 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="button"
|
||||
className={`inline-flex justify-center rounded-md ${loading ? "py-1 px-6" : "py-2 px-4"} text-sm font-medium bg-red-500 text-white`}
|
||||
onClick={requestAccountDelete}
|
||||
>
|
||||
Proceed
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
export default function EditAboutModal({ modalOpen, closeModal }) {
|
||||
const { state: globalState, dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const { handleSubmit, register } = useForm({ defaultValues: { about: globalState.user.about } });
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function onSubmit(data) {
|
||||
console.log("submitting", data);
|
||||
setLoading(true);
|
||||
try {
|
||||
await callCustomAPI(
|
||||
"edit-self",
|
||||
"post",
|
||||
{
|
||||
profile: data,
|
||||
},
|
||||
"",
|
||||
);
|
||||
closeModal();
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: "About change successful",
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
globalDispatch({
|
||||
type: "SET_USER_DATA",
|
||||
payload: {
|
||||
...globalState.user,
|
||||
about: data.about,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="form"
|
||||
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-[18px]">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-2xl font-semibold"
|
||||
>
|
||||
Edit About
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 duration-300 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>{" "}
|
||||
</div>
|
||||
<hr className="mb-4" />
|
||||
<textarea
|
||||
{...register("about")}
|
||||
cols="30"
|
||||
rows="10"
|
||||
className="w-full focus:outline-none border-2 p-2 resize-none text-sm text-gray-900"
|
||||
></textarea>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="tracking-wide outline-none focus:outline-none rounded py-2 border-2 border-[#98A2B3] mt-4 flex-grow"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`login-btn-gradient text-white tracking-wide outline-none focus:outline-none rounded ${loading ? "py-1 px-4" : "py-2"} mt-4 flex-grow`}
|
||||
>
|
||||
Update
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import CustomLocationAutoCompleteV2 from "../CustomLocationAutoCompleteV2";
|
||||
import StickyCustomLocationAutoComplete from "../StickyCustomLocationAutoComplete";
|
||||
|
||||
export default function EditLocationModal({ modalOpen, closeModal }) {
|
||||
const { state: globalState, dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const { handleSubmit, register, setValue, control, formState: { errors } } = useForm({ defaultValues: { city: globalState.user.city, country: globalState.user.country } });
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function onSubmit(data) {
|
||||
console.log("submitting", data);
|
||||
// const parts = data.city.split(", ");
|
||||
// data.city = parts[0]
|
||||
// data.country = parts[1]
|
||||
setLoading(true);
|
||||
try {
|
||||
await callCustomAPI(
|
||||
"edit-self",
|
||||
"post",
|
||||
{
|
||||
profile: data,
|
||||
},
|
||||
"",
|
||||
);
|
||||
closeModal();
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: "Location changed successful",
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
globalDispatch({
|
||||
type: "SET_USER_DATA",
|
||||
payload: {
|
||||
...globalState.user,
|
||||
city: data.city,
|
||||
country: data.country,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto z-10 ">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="form"
|
||||
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-[18px]">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-2xl font-semibold"
|
||||
>
|
||||
Edit Location
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 duration-300 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>{" "}
|
||||
</div>
|
||||
<hr className="mb-4" />
|
||||
<div className="mb-4 ">
|
||||
<label
|
||||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="city"
|
||||
>
|
||||
Location
|
||||
</label>
|
||||
<StickyCustomLocationAutoComplete
|
||||
control={control}
|
||||
setValue={(val) => setValue("city", val)}
|
||||
name="city"
|
||||
className={`w-full z-20 rounded relative border py-2 px-3 leading-tight text-gray-700 ${errors.city?.message ? "border-red-500 focus:outline-red-500" : "focus-within:outline-primary"}`}
|
||||
placeholder=""
|
||||
hideIcons
|
||||
suggestionType={["(cities)"]}
|
||||
/>
|
||||
{/* <input
|
||||
autoComplete="off"
|
||||
id="city"
|
||||
type="text"
|
||||
{...register("city")}
|
||||
className={`resize-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none`}
|
||||
/> */}
|
||||
</div>
|
||||
{/* <div className="mb-4 ">
|
||||
<label
|
||||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="country"
|
||||
>
|
||||
Country
|
||||
</label>
|
||||
<input
|
||||
autoComplete="off"
|
||||
id="country"
|
||||
type="text"
|
||||
{...register("country")}
|
||||
className={`resize-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none`}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="tracking-wide outline-none focus:outline-none rounded py-2 border-2 border-[#98A2B3] mt-4 flex-grow"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`login-btn-gradient text-white tracking-wide outline-none focus:outline-none rounded ${loading ? "py-1 px-4" : "py-2"} mt-4 flex-grow`}
|
||||
>
|
||||
Update
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import * as yup from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import React, { Fragment } from "react";
|
||||
import { useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import commonPasswords from "@/assets/json/common-passwords.json";
|
||||
import moment from "moment";
|
||||
|
||||
export default function EditPasswordModal({ modalOpen, closeModal }) {
|
||||
const { dispatch: globalDispatch, state: globalState } = useContext(GlobalContext);
|
||||
const [showOldPassword, setShowOldPassword] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
const schema = yup.object({
|
||||
current_password: yup.string().required("This field is required"),
|
||||
password: yup
|
||||
.string()
|
||||
.required("Password is required")
|
||||
.min(10, "Password must be at least 10 characters long")
|
||||
.matches(/^(?=.*[0-9])/, "Password must contain at least one digit(0-9)")
|
||||
.matches(/^(?=.*[a-z])/, "Password must contain at least one lowercase letter")
|
||||
.matches(/^(?=.*[A-Z])/, "Password must contain at least one uppercase letter")
|
||||
.matches(/^(?=.*[!@#\$%\^&\*])/, "Password must contain at least one symbol")
|
||||
.test("is-not-dictionary", "Password must not contain a common word", (val) => {
|
||||
return commonPasswords.every((pass) => !val.includes(pass));
|
||||
})
|
||||
.test("does-not-contain-user-info", "Password must not contain your name or date of birth", (val) => {
|
||||
const d = moment(globalState.user.dob);
|
||||
return [
|
||||
globalState.user.first_name,
|
||||
globalState.user.last_name,
|
||||
d.format("yyyyMMDD"),
|
||||
d.format("DDMMyyyy"),
|
||||
d.format("MMDDyyyy"),
|
||||
d.format("YYMMDD"),
|
||||
d.format("MMDDYY"),
|
||||
d.format("DDMMYY"),
|
||||
].every((field) => field.trim() == "" || !val.toLowerCase().includes(field.toLowerCase()));
|
||||
}),
|
||||
confirm_password: yup
|
||||
.string()
|
||||
.oneOf([yup.ref("password"), null], "Passwords don't match")
|
||||
.required("This field is required"),
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
trigger,
|
||||
formState: { errors, dirtyFields },
|
||||
} = useForm({ defaultValues: { current_password: "", password: "", confirm_password: "" }, resolver: yupResolver(schema), criteriaMode: "all" });
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function onSubmit(data) {
|
||||
console.log("submitting", data);
|
||||
setLoading(true);
|
||||
try {
|
||||
await callCustomAPI(
|
||||
"edit-self",
|
||||
"post",
|
||||
{
|
||||
user: { password: data.password, oldPassword: data.current_password },
|
||||
},
|
||||
"",
|
||||
);
|
||||
closeModal();
|
||||
reset();
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: "Password change successful",
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
function getPasswordErrors() {
|
||||
var arr = [];
|
||||
if (Array.isArray(errors.password?.types.matches)) {
|
||||
arr = [...errors.password.types.matches];
|
||||
}
|
||||
if (typeof errors.password?.types.matches === "string") {
|
||||
arr.push(errors.password.types.matches);
|
||||
}
|
||||
if (errors.password?.types.min) {
|
||||
arr.push(errors.password.types.min);
|
||||
}
|
||||
if (errors.password?.types["does-not-contain-user-info"]) {
|
||||
arr.push(errors.password?.types["does-not-contain-user-info"]);
|
||||
}
|
||||
if (errors.password?.types["is-not-dictionary"]) {
|
||||
arr.push(errors.password?.types["is-not-dictionary"]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
const passwordErrors = getPasswordErrors();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="form"
|
||||
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="mb-[18px] flex items-center justify-between">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-2xl font-semibold"
|
||||
>
|
||||
Change Password
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="rounded-full border p-1 px-3 text-2xl font-normal duration-300 hover:bg-gray-200"
|
||||
>
|
||||
✕
|
||||
</button>{" "}
|
||||
</div>
|
||||
<hr className="mb-4" />
|
||||
|
||||
<p className="mb-4">In order to set new password provide the current one:</p>
|
||||
|
||||
<div className="mb-8">
|
||||
<div className="relative flex justify-between rounded-sm border bg-transparent">
|
||||
<input
|
||||
type={showOldPassword ? "text" : "password"}
|
||||
{...register("current_password")}
|
||||
className="flex-grow border-0 p-2 px-4 focus:outline-none active:outline-none "
|
||||
placeholder="Type current password"
|
||||
/>{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowOldPassword((prev) => !prev)}
|
||||
className="absolute right-1 top-[20%]"
|
||||
>
|
||||
{" "}
|
||||
{showOldPassword ? (
|
||||
<img
|
||||
src="/show.png"
|
||||
alt=""
|
||||
className="mr-2 ml-2 w-6"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src="/invisible.png"
|
||||
alt=""
|
||||
className="mr-2 ml-2 w-6"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="my-[32px] md:my-[32px]" />
|
||||
<div className="mb-8">
|
||||
<div className="relative mb-4 flex justify-between rounded-sm border bg-transparent">
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
{...register("password", {
|
||||
onChange: (e) => {
|
||||
trigger("password");
|
||||
},
|
||||
})}
|
||||
className="flex-grow border-0 p-2 px-4 focus:outline-none active:outline-none "
|
||||
placeholder="Type new password"
|
||||
autoComplete="new-password"
|
||||
/>{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword((prev) => !prev)}
|
||||
className="absolute right-1 top-[20%]"
|
||||
>
|
||||
{" "}
|
||||
{showPassword ? (
|
||||
<img
|
||||
src="/show.png"
|
||||
alt=""
|
||||
className="mr-2 ml-2 w-6"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src="/invisible.png"
|
||||
alt=""
|
||||
className="mr-2 ml-2 w-6"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{dirtyFields.password && (
|
||||
<div className="fade-in mb-4 space-y-2 rounded-sm border border-[#C42945] p-3 text-sm normal-case text-[#C42945] empty:hidden">
|
||||
{passwordErrors.map((msg) => (
|
||||
<p>{msg}</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="relative flex justify-between rounded-sm border bg-transparent">
|
||||
<input
|
||||
type={showConfirmPassword ? "text" : "password"}
|
||||
{...register("confirm_password")}
|
||||
className="flex-grow border-0 p-2 px-4 focus:outline-none active:outline-none "
|
||||
placeholder="Retype new password"
|
||||
/>{" "}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowConfirmPassword((prev) => !prev)}
|
||||
className="absolute right-1 top-[20%]"
|
||||
>
|
||||
{" "}
|
||||
{showConfirmPassword ? (
|
||||
<img
|
||||
src="/show.png"
|
||||
alt=""
|
||||
className="mr-2 ml-2 w-6"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src="/invisible.png"
|
||||
alt=""
|
||||
className="mr-2 ml-2 w-6"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{Object.entries(errors).length > 0 && dirtyFields.password && !errors.password ? (
|
||||
<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>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="mt-4 flex-grow rounded border-2 border-[#98A2B3] py-2 tracking-wide outline-none focus:outline-none"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`login-btn-gradient rounded tracking-wide text-white outline-none focus:outline-none ${loading ? "py-1 px-4" : "py-2"} mt-4 flex-grow`}
|
||||
>
|
||||
Update
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import { useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
export default function EditProfileModal({ modalOpen, closeModal }) {
|
||||
const { state: globalState, dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const { handleSubmit, register } = useForm({ defaultValues: { first_name: globalState.user.first_name, last_name: globalState.user.last_name } });
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function onSubmit(data) {
|
||||
console.log("submitting", data);
|
||||
setLoading(true);
|
||||
try {
|
||||
await callCustomAPI(
|
||||
"edit-self",
|
||||
"post",
|
||||
{
|
||||
user: data,
|
||||
},
|
||||
"",
|
||||
);
|
||||
closeModal();
|
||||
globalDispatch({
|
||||
type: "SHOW_CONFIRMATION",
|
||||
payload: {
|
||||
heading: "Success",
|
||||
message: "Name change successful",
|
||||
btn: "Ok got it",
|
||||
},
|
||||
});
|
||||
globalDispatch({
|
||||
type: "SET_USER_DATA",
|
||||
payload: {
|
||||
...globalState.user,
|
||||
first_name: data.first_name,
|
||||
last_name: data.last_name,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
globalDispatch({
|
||||
type: "SHOW_ERROR",
|
||||
payload: {
|
||||
heading: "Operation failed",
|
||||
message: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
appear
|
||||
show={modalOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
as="form"
|
||||
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-[18px]">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-2xl font-semibold"
|
||||
>
|
||||
Edit Profile
|
||||
</Dialog.Title>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="p-1 border hover:bg-gray-200 duration-300 px-3 text-2xl font-normal rounded-full"
|
||||
>
|
||||
✕
|
||||
</button>{" "}
|
||||
</div>
|
||||
<hr className="mb-4" />
|
||||
<div className="mb-4 ">
|
||||
<label
|
||||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="first_name"
|
||||
>
|
||||
First Name
|
||||
</label>
|
||||
<input
|
||||
autoComplete="off"
|
||||
id="first_name"
|
||||
type="text"
|
||||
{...register("first_name")}
|
||||
className={`resize-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none`}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4 ">
|
||||
<label
|
||||
className="block text-gray-700 text-sm font-bold mb-2"
|
||||
htmlFor="last_name"
|
||||
>
|
||||
Last Name
|
||||
</label>
|
||||
<input
|
||||
autoComplete="off"
|
||||
id="last_name"
|
||||
type="text"
|
||||
{...register("last_name")}
|
||||
className={`resize-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none`}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
type="button"
|
||||
className="tracking-wide outline-none focus:outline-none rounded py-2 border-2 border-[#98A2B3] mt-4 flex-grow"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="submit"
|
||||
className={`login-btn-gradient text-white tracking-wide outline-none focus:outline-none rounded ${loading ? "py-1 px-4" : "py-2"} mt-4 flex-grow`}
|
||||
>
|
||||
Update
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { GlobalContext } from "@/globalContext";
|
||||
import { callCustomAPI } from "@/utils/callCustomAPI";
|
||||
import { parseJsonSafely, sleep } from "@/utils/utils";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { useState } from "react";
|
||||
import { useContext } from "react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export default function EnableEmailDialog({ isOpen, closeModal }) {
|
||||
const { state: globalState, dispatch: globalDispatch } = useContext(GlobalContext);
|
||||
const isEnabled = parseJsonSafely(globalState.user.settings, {}).email_on_booking_declined == true;
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function toggleEmailPreference() {
|
||||
setLoading(true);
|
||||
let newSettings;
|
||||
if (!isEnabled) {
|
||||
newSettings = {
|
||||
...parseJsonSafely(globalState.user.settings, {}),
|
||||
email_on_space_image_declined: true,
|
||||
email_on_booking_declined: true,
|
||||
email_on_profile_photo_declined: true,
|
||||
email_on_new_chat_message: true,
|
||||
email_on_space_booked: true,
|
||||
email_on_booking_cancelled: true,
|
||||
email_on_booking_accepted: true,
|
||||
};
|
||||
} else {
|
||||
newSettings = {
|
||||
...parseJsonSafely(globalState.user.settings, {}),
|
||||
email_on_space_image_declined: false,
|
||||
email_on_booking_declined: false,
|
||||
email_on_profile_photo_declined: false,
|
||||
email_on_new_chat_message: false,
|
||||
email_on_space_booked: false,
|
||||
email_on_booking_cancelled: false,
|
||||
email_on_booking_accepted: false,
|
||||
};
|
||||
}
|
||||
try {
|
||||
await callCustomAPI(
|
||||
"edit-self",
|
||||
"post",
|
||||
{
|
||||
profile: { settings: JSON.stringify(newSettings) },
|
||||
},
|
||||
"",
|
||||
);
|
||||
closeModal();
|
||||
await sleep(200);
|
||||
globalDispatch({ type: "SET_USER_DATA", payload: { ...globalState.user, settings: JSON.stringify(newSettings) } });
|
||||
} catch (err) {
|
||||
globalDispatch({ type: "SHOW_ERROR", payload: { heading: "Operation Failed", message: err.message } });
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isOpen && <div className="fixed inset-0 flex items-center justify-center"></div>}
|
||||
<Transition
|
||||
appear
|
||||
show={isOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
{isEnabled ? "Turn Off Email Notifications" : "Enable email notifications?"}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
{!isEnabled ? "Enable email notifications on site activity such as booking when booking is declined by host" : "Disabling email notifications on site activity"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex gap-4 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="button"
|
||||
className={`inline-flex justify-center rounded-md ${loading ? "py-1 px-6" : "py-2 px-4"} text-sm font-medium login-btn-gradient text-white`}
|
||||
onClick={toggleEmailPreference}
|
||||
>
|
||||
Proceed
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { LoadingButton } from "@/components/frontend";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export default function TwoFaDialog({ isOpen, closeModal, isEnabled, onProceed, loading }) {
|
||||
return (
|
||||
<>
|
||||
{isOpen && <div className="fixed inset-0 flex items-center justify-center"></div>}
|
||||
<Transition
|
||||
appear
|
||||
show={isOpen}
|
||||
as={Fragment}
|
||||
>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={closeModal}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
{isEnabled ? "Turn Off 2-Step Verification?" : "Protect your account with 2-Step Verification"}
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
{!isEnabled
|
||||
? "Prevent hackers from accessing your account with an additional layer of security. When you sign in, 2-Step Verification helps make sure that your personal information stays private, safe and secure."
|
||||
: "Turning off 2-Step Verification will remove the extra security on your account, and you’ll only use your password to sign in."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex gap-4 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border px-4 py-2 text-sm font-medium focus:outline-none"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
type="button"
|
||||
className={`inline-flex justify-center rounded-md ${loading ? "py-1 px-6" : "py-2 px-4"} text-sm font-medium login-btn-gradient text-white`}
|
||||
onClick={onProceed}
|
||||
>
|
||||
Proceed
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||