Untitled
unknown
plain_text
a year ago
29 kB
10
Indexable
// ======= ===v2===========optimize
import CustomImage from "@/app/_components/ui/CustomImage";
import { useCreateBookingMutation, useGetTempBookingMutationMutation, useGetTempBookingQuery } from "@/app/features/booking/bookingApi";
import { useGetRoomsByDateMutationMutation, useGetRoomsByDateQuery, useGetRoomsQuery } from "@/app/features/room/roomApi";
import { useRouter } from "next/navigation";
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { Tooltip } from "rizzui";
import CustomButton from "../../_components/CustomButton";
import { useBookingCtx } from "../context/BookingProvider";
import DateSection from "./DateSection";
import GuestCounter from "./GuestCounter";
import PakageImageSlider from "./PakageImageSlider";
import RoomSelector from "./RoomSelector ";
// Define room images
const roomImages = {
notBooked: [
"/room/not-booked/Room1.png",
"/room/not-booked/Room2.png",
"/room/not-booked/Room3.png",
"/room/not-booked/Room4.png",
"/room/not-booked/Room5.png",
"/room/not-booked/Room6.png"
],
selected: [
"/room/selected/Room1.png",
"/room/selected/Room2.png",
"/room/selected/Room3.png",
"/room/selected/Room4.png",
"/room/selected/Room5.png",
"/room/selected/Room6.png"
],
booked: [
"/room/booked/Room1.png",
"/room/booked/Room2.png",
"/room/booked/Room3.png",
"/room/booked/Room4.png",
"/room/booked/Room5.png",
"/room/booked/Room6.png"
]
};
// Define interfaces for room and booking data
interface RoomData {
id: string;
name: string;
price: number;
maxCapacity: number;
status: string;
discount: number;
images: string[];
videos: string[];
selected?: boolean;
selectedRoomId?: number;
confirmed?: string;
image?: string;
booked?: boolean;
hold?: boolean;
holdByCurrentUser?: any[];
}
interface BookingData {
ownHold?: RoomData[];
totalHolds?: string[];
}
// Enum for different actions
enum ActionType {
SUBMIT = "SUBMIT",
NEXT = "NEXT",
PREVIOUS = "PREVIOUS",
FINAL_SUBMIT = "FINAL_SUBMIT"
}
// Props for BookingHome component
interface BookingHomeProps {
data: any; // Replace with specific type if possible
resource: any; // Replace with specific type if possible
}
// Main component
export default function BookingHome({ data, resource }: BookingHomeProps) {
const {
availableRoom,
setAvailableRoom,
pickerDate,
setPickerDate,
holdData,
setHoldData,
handleImageClick,
userSubmitRoomData,
setUserSubmitRoomData
}: any = useBookingCtx();
const router = useRouter();
const [createBooking] = useCreateBookingMutation({});
const [userInformation, setUserInformation] = useState<{ name: string; phone: string }>({ name: "", phone: "" });
const { data: availableRooms, isLoading: avRoomIsLoading } = useGetRoomsByDateQuery({ paginate: false, startDate: pickerDate.startDate });
const { data: rooms, isLoading } = useGetRoomsQuery<any>({ paginate: false });
const [getRoomsByDateMutation] = useGetRoomsByDateMutationMutation({});
const { data: tempBook, isLoading: tempLoading } = useGetTempBookingQuery({ date: pickerDate.startDate });
const [getTempBookingMutation] = useGetTempBookingMutationMutation({});
// Function to update rooms data based on booking and availability information
const getBookings = useCallback(
async (data: BookingData, available: any) => {
if (!rooms?.data) return;
const ownHoldIds = new Set(data?.ownHold?.map((hold: any) => hold.id));
setHoldData(ownHoldIds);
const updatedRooms = rooms.data.map((room: any, index: number) => {
const isSelected = data?.ownHold?.some((r: RoomData) => r.id === room.id);
return {
...room,
selected: isSelected,
selectedRoomId: index + 1,
confirmed: "start",
image: roomImages.notBooked[index],
booked: !available?.data?.some((avRoom: any) => avRoom.id === room.id),
hold: data?.totalHolds?.includes(room.id),
holdByCurrentUser: data?.ownHold
};
});
setAvailableRoom(updatedRooms);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[rooms?.data, setAvailableRoom]
);
useEffect(() => {
if (rooms?.data || availableRooms || tempBook) {
getBookings(tempBook?.data, availableRooms);
}
if (rooms?.data || availableRooms || tempBook) {
const filteredRoomsWithGuests = availableRoom
?.filter((room: RoomData) => holdData?.has(room.id))
.map((room: RoomData) => ({
guests: 2,
room: room.id
}));
setUserSubmitRoomData(filteredRoomsWithGuests);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rooms?.data, availableRooms, tempBook, pickerDate.startDate, getBookings]);
// Handle date change
const handleDateChange = useCallback(
(date: string) => {
const mainDate = new Date(new Date(date).setUTCHours(0, 0, 0, 0)).toISOString();
const endDate = new Date(new Date(mainDate).getTime() + 24 * 60 * 60 * 1000).toISOString();
setPickerDate({ startDate: mainDate, endDate });
},
[setPickerDate]
);
// State for active room and final room data
const [activeRoom, setActiveRoom] = useState<number>(0);
const [userFinalRoomData, setUserFinalRoomData] = useState<any[]>([]);
const [numberOfGuest, setNumberOfGuest] = useState<number>(2);
useEffect(() => {
if (availableRoom) {
setUserFinalRoomData(availableRoom.filter((room: RoomData) => holdData?.has(room.id)));
}
}, [availableRoom, holdData]);
// Get matched objects between user submitted rooms and final rooms
const getMatchedObjects = useCallback((submitData: any[], finalData: any[]) => {
return submitData?.filter(obj2 => finalData.some(obj1 => obj1.id === obj2.room));
}, []);
const res = useMemo(
() => getMatchedObjects(userSubmitRoomData, userFinalRoomData),
[userSubmitRoomData, userFinalRoomData, getMatchedObjects]
);
// Handle input change for user information
const inputOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setUserInformation(prev => ({ ...prev, [name]: value }));
}, []);
// Handle form submission
const handleSubmit = async ({ action, index, id }: { action: string; index?: number; id?: string | number; guests?: number }) => {
switch (action) {
case ActionType.SUBMIT:
if (!id) {
toast.success("Select a room first");
return;
}
break;
case ActionType.NEXT:
if (index !== undefined) {
setActiveRoom(index);
} else {
setActiveRoom(prev => prev + 1);
}
break;
case ActionType.PREVIOUS:
setActiveRoom(prev => prev - 1);
break;
case ActionType.FINAL_SUBMIT:
if (!userInformation.name || !userInformation.phone) {
toast.error("Please fill in name and phone number");
return;
}
const sendData = {
rooms: res,
name: userInformation.name,
phone: userInformation.phone,
startDate: pickerDate.startDate,
endDate: pickerDate.endDate
};
try {
await createBooking(sendData);
toast.success("Booking created successfully");
setActiveRoom(0);
router.push("/success");
} catch (error) {
toast.error("Error creating booking");
}
break;
default:
break;
}
};
// ==========v2===========optimize
return (
<div className=''>
<div className=''>
<DateSection pickerDate={pickerDate} handleDateChange={handleDateChange} />
</div>
<div>
<p className='px-1 flex justify-center lg:justify-start pb-4 text-xs font-normal capitalize'>Click to select or deselect rooms.</p>
<div className='mb-5 flex justify-center lg:justify-start gap-4 mx-2 lg:mx-2'>
<div className='flex gap-8 items-center'>
<div className='h-5 w-5 rounded-sm border bg-[#F9FAFA]'></div>
<span className='text-[10px] lg:font-medium lg:text-xs'>Available</span>
</div>
<div className='flex gap-3 items-center'>
<div className='h-5 w-5 rounded-sm border bg-[#C7C8CA]'></div>
<span className='text-[10px] lg:font-medium lg:text-xs'>Booked</span>
</div>
<div className='flex gap-3 items-center'>
<div className='h-5 w-5 rounded-sm border bg-[#98C157]'></div>
<span className='text-[10px] lg:font-medium lg:text-xs'>Selected</span>
</div>
</div>
</div>
<div className='grid grid-cols-1 lg:grid-cols-7 gap-4 content-center m-2 lg:m-0 sm:mx-24'>
{/* booking room section */}
<div className='border-2 border-gray-300 col-span-7 lg:col-span-2 w-full h-full flex justify-center items-center'>
<div className='grid grid-cols-2 gap-6 py-5 lg:py-0 '>
{avRoomIsLoading || !availableRoom
? roomImages.notBooked.map((room: string, index: number) => (
<div key={index} className='relative overflow-hidden'>
<CustomImage
src={roomImages.notBooked[index]}
alt='available room'
className='h-28 w-24 sm:h-36 md:h-44 md:w-32 my-2'
/>
<div className='absolute top-0 left-0 z-10'>
<span className='relative flex h-[20rem] w-[20rem]'>
<span className='animate-pulse custom-animation absolute inline-flex rounded-none h-full w-full bg-[#7e7777af] opacity-70 '></span>
</span>
</div>
</div>
))
: availableRoom &&
availableRoom.map((room: RoomData, index: number) => (
<div key={room.id}>
{room.booked ? (
<CustomImage
src={roomImages.booked[index]}
alt='booked room'
className='cursor-not-allowed h-28 w-24 sm:h-36 xl:h-44 md:h-36 md:w-24 xl:w-32'
/>
) : room.selected && room.hold ? (
<Tooltip placement='top' color='invert' className='bg-white' content={room.name}>
<CustomImage
src={roomImages.selected[index]}
alt='selected room'
className='h-28 w-24 sm:h-36 xl:h-44 md:h-36 md:w-24 xl:w-32 my-2'
onClick={() => handleImageClick(room.id, index, numberOfGuest, resource?.data?.booking)}
/>
</Tooltip>
) : room.hold ? (
<CustomImage
src={roomImages.booked[index]}
alt='booked room'
className='cursor-not-allowed h-28 w-24 sm:h-36 xl:h-44 md:h-36 md:w-24 xl:w-32'
/>
) : room.selected ? (
<Tooltip placement='top' color='invert' className='bg-white' content={room.name}>
<CustomImage
src={roomImages.selected[index]}
alt='selected room'
className='h-28 w-24 sm:h-36 xl:h-44 md:h-36 md:w-24 xl:w-32 my-2'
onClick={() => handleImageClick(room.id, index, numberOfGuest, resource?.data?.booking)}
/>
</Tooltip>
) : (
<Tooltip placement='top' color='invert' className='bg-white' content={room.name}>
<CustomImage
src={roomImages.notBooked[index]}
alt='available room'
className='h-28 w-24 sm:h-36 xl:h-44 md:h-36 md:w-24 xl:w-32 my-2'
onClick={() => handleImageClick(room.id, index, numberOfGuest, resource?.data?.booking)}
/>
</Tooltip>
)}
</div>
))}
</div>
</div>
{/* booking room section */}
<div className='col-span-7 mt-5 md:mt-0 lg:col-span-5 w-full h-full flex flex-col justify-center items-end '>
<div className='md:w-[72%] w-[100%] lg:-mt-[51px]'>
<RoomSelector userFinalRoomData={userFinalRoomData} activeRoom={activeRoom} setActiveRoom={setActiveRoom} />
</div>
{/* </div> */}
<div className='w-[100%] lg:w-[72%] max-h-[100%] bg-white shadow-lg rounded-2xl overflow-hidden'>
<div className='w-full'>
<PakageImageSlider images={userFinalRoomData && userFinalRoomData[activeRoom]?.images} />
</div>
<div className='grid grid-cols-1 gap-y-7 pb-3 m-6 md:mt-5 xl:mt-10'>
<GuestCounter
number={numberOfGuest}
setNumber={setNumberOfGuest}
maxCapacity={(userFinalRoomData && userFinalRoomData[activeRoom]?.maxCapacity) ?? 2}
roomId={userFinalRoomData && userFinalRoomData[activeRoom]?.id}
setUserSubmitRoomData={setUserSubmitRoomData}
holdByCurrentUser={availableRoom && availableRoom[0].holdByCurrentUser}
finalData={res || []}
/>
{activeRoom === userFinalRoomData?.length - 1 && (
<div className='flex w-full gap-5 items-center'>
<div className='w-full'>
<input
onChange={inputOnChange}
name='name'
type='text'
value={userInformation.name}
className='w-full border-b border-[#E3DBB7] py-2 text-sm font-light capitalize tracking-wide outline-none lg:text-base'
placeholder='Your Name'
/>
{!userInformation.name && (
<div className='md:text-xs text-[10px] font-light text-red-500 mt-2'>Name field is required</div>
)}
</div>
<div className='w-full'>
<input
onChange={inputOnChange}
name='phone'
type='text'
value={userInformation.phone}
className='w-full border-b border-[#E3DBB7] py-2 text-sm font-light capitalize tracking-wide outline-none'
placeholder='Phone Number'
/>
{!userInformation.phone && (
<div className='md:text-xs text-[10px] font-light text-red-500 mt-2 '>Phone number field is required</div>
)}
</div>
</div>
)}
<div>
<p className='mb-2 text-sm md:text-base font-light capitalize tracking-wider lg:text-md'>
from ৳
{userFinalRoomData.length > 0 ? (
<strong className='text-base font-semibold lg:text-lg mx-2'>
{userFinalRoomData &&
Math.ceil(
userFinalRoomData[activeRoom]?.price[numberOfGuest - 1]?.price /
userFinalRoomData[activeRoom]?.price[numberOfGuest - 1]?.numberOfPerson
)}
</strong>
) : (
<strong className='text-sm md:text-base font-semibold lg:text-lg mx-2'>0</strong>
)}
per person
</p>
<p className='mb-1 text-sm md:text-base font-light capitalize tracking-wider'>
sub total ৳
{userFinalRoomData.length > 0 ? (
<strong className='text-sm md:text-base font-semibold lg:text-xl mx-2'>
{userFinalRoomData && userFinalRoomData[activeRoom]?.price[numberOfGuest - 1]?.price}
</strong>
) : (
<strong className='text-sm md:text-base font-semibold lg:text-lg mx-2'>0</strong>
)}
for this room
</p>
</div>
{/* <div className='my-5'>
{activeRoom > 0 && (
<CustomButton
onClick={() => handleSubmit({ action: "PREVIOUS" })}
customClass='w-[150px] text-white bg-[#1DC5CE] py-2 text-2xl me-2 rounded-none'
text='Previous'
/>
)}
{activeRoom < userFinalRoomData?.length - 1 ? (
<CustomButton
onClick={() =>
handleSubmit({
action: "NEXT",
index: activeRoom + 1,
id: userFinalRoomData[activeRoom]?.id
})
}
customClass='w-[150px] text-white bg-[#1DC5CE] py-2 text-2xl me-2 rounded-none'
text='Next'
/>
) : (
<CustomButton
onClick={() =>
handleSubmit({
action: "FINAL_SUBMIT",
index: activeRoom + 1,
id: userFinalRoomData[activeRoom]?.id
})
}
customClass='w-[150px] text-white bg-[#1DC5CE] py-2 me-2 text-2xl rounded-none'
text='Final Submit'
/>
)}
</div> */}
<div className='flex gap-3'>
{activeRoom > 0 && (
<CustomButton
onClick={() => handleSubmit({ action: "PREVIOUS" })}
customClass='w-[80px] !px-0 font-normal lg:font-medium lg:px-2 lg:w-[100px] text-white bg-[#1DC5CE] py-[11px] !text-xs rounded-sm'
text='PREVIOUS'
/>
)}
{activeRoom < userFinalRoomData?.length - 1 ? (
<CustomButton
onClick={() =>
handleSubmit({
action: "NEXT",
index: activeRoom + 1,
id: userFinalRoomData[activeRoom]?.id
})
}
customClass='w-[80px] !px-0 font-normal lg:font-medium lg:px-2 lg:w-[100px] text-white bg-[#1DC5CE] py-[11px] !text-xs rounded-sm'
text='NEXT'
/>
) : (
<CustomButton
onClick={() =>
handleSubmit({
action: "FINAL_SUBMIT",
index: activeRoom + 1,
id: userFinalRoomData[activeRoom]?.id
})
}
customClass='w-[80px] !px-0 lg:px-2 font-normal lg:font-medium lg:w-[100px] text-white bg-[#1DC5CE] py-[11px] !text-xs rounded-sm'
text='CONFIRM'
/>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
// "use client";
// import CustomImage from "@/app/_components/ui/CustomImage";
// import { useCreateBookingMutation, useGetTempBookingMutationMutation, useGetTempBookingQuery } from "@/app/features/booking/bookingApi";
// import { useGetRoomsByDateMutationMutation, useGetRoomsByDateQuery, useGetRoomsQuery } from "@/app/features/room/roomApi";
// import { ChangeEvent, useCallback, useEffect, useMemo, useState } from "react";
// import toast from "react-hot-toast";
// import { Tooltip } from "rizzui";
// import CustomButton from "../../_components/CustomButton";
// import { useBookingCtx } from "../context/BookingProvider";
// import DateSection from "./DateSection";
// import GuestCounter from "./GuestCounter";
// import PakageImageSlider from "./PakageImageSlider";
// import RoomSelector from "./RoomSelector ";
// const roomImages = {
// notBooked: [
// "/room/not-booked/Room1.png",
// "/room/not-booked/Room2.png",
// "/room/not-booked/Room3.png",
// "/room/not-booked/Room4.png",
// "/room/not-booked/Room5.png",
// "/room/not-booked/Room6.png"
// ],
// selected: [
// "/room/selected/Room1.png",
// "/room/selected/Room2.png",
// "/room/selected/Room3.png",
// "/room/selected/Room4.png",
// "/room/selected/Room5.png",
// "/room/selected/Room6.png"
// ],
// booked: [
// "/room/booked/Room1.png",
// "/room/booked/Room2.png",
// "/room/booked/Room3.png",
// "/room/booked/Room4.png",
// "/room/booked/Room5.png",
// "/room/booked/Room6.png"
// ]
// };
// interface RoomData {
// id: string;
// name: string;
// price: number;
// maxCapacity: number;
// status: string;
// discount: number;
// images: string[];
// videos: string[];
// selected?: boolean;
// selectedRoomId?: number;
// confirmed?: string;
// image?: string;
// booked?: boolean;
// hold?: boolean;
// holdByCurrentUser?: any[];
// }
// interface BookingData {
// ownHold?: RoomData[];
// totalHolds?: string[];
// }
// interface BookingHomeProps {
// data: any;
// resource: any;
// }
// enum ActionType {
// SUBMIT = "SUBMIT",
// NEXT = "NEXT",
// PREVIOUS = "PREVIOUS"
// }
// interface IRoom {
// id: string;
// guests: number;
// }
// export default function BookingHome({ data, resource }: BookingHomeProps) {
// const [createBooking] = useCreateBookingMutation({});
// const {
// availableRoom,
// setAvailableRoom,
// pickerDate,
// setPickerDate,
// toISOString,
// holdData,
// setHoldData,
// handleImageClick,
// tempHoldRooms,
// setSelectedRoomIndex,
// selectedRoomIndex,
// userSubmitRoomData,
// setUserSubmitRoomData
// }: any = useBookingCtx();
// const [roomInfo, setRoomInfo] = useState<RoomData[]>([]);
// const [guests, setGuests] = useState<number>(1);
// const [userInformation, setUserInformation] = useState<{
// name: string;
// phone: string;
// }>({
// name: "",
// phone: ""
// });
// const { data: availableRooms, isLoading: avRoomIsLoading } = useGetRoomsByDateQuery({
// paginate: false,
// startDate: pickerDate.startDate
// });
// const { data: rooms, isLoading } = useGetRoomsQuery<any>({ paginate: false });
// const [getRoomsByDateMutation] = useGetRoomsByDateMutationMutation({});
// const { data: tempBook, isLoading: tempLoading } = useGetTempBookingQuery({
// date: pickerDate.startDate
// });
// const [getTempBookingMutation] = useGetTempBookingMutationMutation({});
// const getBookings = useCallback(
// async (data: BookingData, available: any) => {
// if (!rooms?.data) return;
// const ownHoldIds = new Set(data?.ownHold?.map((hold: any) => hold.id));
// setHoldData(ownHoldIds);
// const updatedRooms = rooms.data.map((room: any, index: number) => {
// const isSelected = data?.ownHold?.some((r: RoomData) => r.id === room.id);
// return {
// ...room,
// selected: isSelected,
// selectedRoomId: index + 1,
// confirmed: "start",
// image: roomImages.notBooked[index],
// booked: !available?.data?.some((avRoom: any) => avRoom.id === room.id),
// hold: data?.totalHolds?.includes(room.id),
// holdByCurrentUser: data?.ownHold
// };
// });
// setAvailableRoom(updatedRooms);
// },
// // eslint-disable-next-line react-hooks/exhaustive-deps
// [rooms?.data, setAvailableRoom]
// );
// useEffect(() => {
// if (rooms?.data || availableRooms || tempBook) {
// getBookings(tempBook?.data, availableRooms);
// }
// if (rooms?.data || availableRooms || tempBook) {
// const filteredRoomsWithGuests = availableRoom
// ?.filter((room: RoomData) => holdData?.has(room.id))
// .map((room: RoomData) => ({
// guests: 2,
// room: room.id
// }));
// setUserSubmitRoomData(filteredRoomsWithGuests);
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [rooms?.data, availableRooms, tempBook, pickerDate.startDate, getBookings]);
// const handleGuestChange = useCallback(
// (delta: number) => {
// if (selectedRoomIndex !== null) {
// setRoomInfo(prevRoomInfo =>
// prevRoomInfo.map((room: any, i: number) =>
// i === selectedRoomIndex
// ? {
// ...room,
// guests: Math.min(4, Math.max(1, room.guests + delta))
// }
// : room
// )
// );
// }
// },
// [selectedRoomIndex]
// );
// const handleDateChange = useCallback(
// (date: string) => {
// const mainDate = new Date(new Date(date).setUTCHours(0, 0, 0, 0)).toISOString();
// const endDate = new Date(new Date(mainDate).getTime() + 24 * 60 * 60 * 1000).toISOString();
// setPickerDate({ startDate: mainDate, endDate });
// },
// [setPickerDate]
// );
// const [activeRoom, setActiveRoom] = useState<number>(0);
// const [userFinalRoomData, setUserFinalRoomData] = useState<any[]>([]);
// const [numberOfGuest, setNumberOfGuest] = useState<number>(2);
// useEffect(() => {
// if (availableRoom) {
// setUserFinalRoomData(availableRoom.filter((room: RoomData) => holdData?.has(room.id)));
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [availableRoom, holdData]);
// const getMatchedObjects = useCallback((submitData: any[], finalData: any[]) => {
// return submitData?.filter(obj2 => finalData.some(obj1 => obj1.id === obj2.room));
// }, []);
// const res = useMemo(
// () => getMatchedObjects(userSubmitRoomData, userFinalRoomData),
// [userSubmitRoomData, userFinalRoomData, getMatchedObjects]
// );
// const inputOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
// const { name, value } = e.target;
// setUserInformation(prev => ({ ...prev, [name]: value }));
// setRoomInfo(prevRoomInfo => prevRoomInfo.map(room => ({ ...room, [name]: value })));
// }, []);
// const handleSubmit = async ({ action, index, id }: { action: string; index?: number; id?: string | number; guests?: number }) => {
// switch (action) {
// case "SUBMIT":
// if (!id) {
// toast.success("select the room?");
// return;
// }
// break;
// case "NEXT":
// if (index !== undefined) {
// setActiveRoom(index);
// } else {
// setActiveRoom(prev => prev + 1);
// }
// break;
// case "PREVIOUS":
// setActiveRoom(prev => prev - 1);
// break;
// case "FINAL_SUBMIT":
// if (!userInformation.name || !userInformation.phone) {
// toast.error("Fill name or phone number");
// return;
// }
// const sendData = {
// rooms: res,
// name: userInformation.name,
// phone: userInformation.phone,
// startDate: pickerDate.startDate,
// endDate: pickerDate.endDate
// };
// try {
// await createBooking(sendData);
// toast.success("Booking created successfully");
// setActiveRoom(0);
// } catch (error) {
// toast.error("Error creating booking");
// }
// break;
// default:
// break;
// }
// };
Editor is loading...
Leave a Comment