stay_commerce-frontend 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/builds/404error-RVKQJ4S4.digested.jpg +0 -0
  6. data/app/assets/builds/Facebook-5HJUILVR.digested.svg +3 -0
  7. data/app/assets/builds/Google-CZ3UPVSC.digested.svg +6 -0
  8. data/app/assets/builds/application.js +95 -0
  9. data/app/assets/builds/application.js.map +7 -0
  10. data/app/assets/builds/beach-KAZW5GM3.digested.jpg +0 -0
  11. data/app/assets/builds/beach2-3ARC34NS.digested.jpg +0 -0
  12. data/app/assets/builds/beach3-PSTOH5FV.digested.jpg +0 -0
  13. data/app/assets/builds/beach4-PWNRPD3S.digested.jpg +0 -0
  14. data/app/assets/builds/beach5-SBOKKRHF.digested.jpg +0 -0
  15. data/app/assets/builds/beach6-SXZ5Y5AI.digested.jpg +0 -0
  16. data/app/assets/builds/beach7-FNBMFVKK.digested.jpg +0 -0
  17. data/app/assets/builds/beach8-TD7LTRQM.digested.jpg +0 -0
  18. data/app/assets/builds/bg-image-H7UGUMV2.digested.jpg +0 -0
  19. data/app/assets/builds/bgimage-DU3PCXKN.digested.jpg +0 -0
  20. data/app/assets/builds/bundle.css +30914 -0
  21. data/app/assets/builds/bundle.css.map +7 -0
  22. data/app/assets/builds/bundle.js +74569 -0
  23. data/app/assets/builds/bundle.js.map +7 -0
  24. data/app/assets/builds/hotel1-HGBXPKJK.digested.jpg +0 -0
  25. data/app/assets/builds/logo_updated-KQWAXLYL.digested.png +0 -0
  26. data/app/assets/builds/styles.css +6422 -0
  27. data/app/assets/builds/styles.css.map +7 -0
  28. data/app/assets/config/manifest.js +2 -0
  29. data/app/assets/images/favicon.ico +0 -0
  30. data/app/assets/images/stay_commerce/frontend/beach-666122_1280 (1).jpg +0 -0
  31. data/app/assets/images/stay_commerce/frontend/beach-666122_1280.jpg +0 -0
  32. data/app/assets/stylesheets/stay_commerce/frontend/application.css +15 -0
  33. data/app/controllers/stay_commerce/frontend/application_controller.rb +8 -0
  34. data/app/controllers/stay_commerce/frontend/welcome_controller.rb +6 -0
  35. data/app/helpers/stay_commerce/frontend/application_helper.rb +6 -0
  36. data/app/javascript/Images/404error.jpg +0 -0
  37. data/app/javascript/Images/Facebook.svg +3 -0
  38. data/app/javascript/Images/Google.svg +6 -0
  39. data/app/javascript/Images/beach-2245867_1280.jpg +0 -0
  40. data/app/javascript/Images/beach.jpg +0 -0
  41. data/app/javascript/Images/beach2.jpg +0 -0
  42. data/app/javascript/Images/beach3.jpg +0 -0
  43. data/app/javascript/Images/beach4.jpg +0 -0
  44. data/app/javascript/Images/beach5.jpg +0 -0
  45. data/app/javascript/Images/beach6.jpg +0 -0
  46. data/app/javascript/Images/beach7.jpg +0 -0
  47. data/app/javascript/Images/beach8.jpg +0 -0
  48. data/app/javascript/Images/bg-image.jpg +0 -0
  49. data/app/javascript/Images/bgimage.jpg +0 -0
  50. data/app/javascript/Images/blog-1.jpg +0 -0
  51. data/app/javascript/Images/gallery.png +0 -0
  52. data/app/javascript/Images/home 5.jpg +0 -0
  53. data/app/javascript/Images/home1.jpg +0 -0
  54. data/app/javascript/Images/home2.jpg +0 -0
  55. data/app/javascript/Images/home3.jpg +0 -0
  56. data/app/javascript/Images/home6.jpg +0 -0
  57. data/app/javascript/Images/hotel1.jpg +0 -0
  58. data/app/javascript/Images/room1.jpg +0 -0
  59. data/app/javascript/Images/room2.jpg +0 -0
  60. data/app/javascript/Images/room3.jpg +0 -0
  61. data/app/javascript/Images/room4.jpg +0 -0
  62. data/app/javascript/Images/wine-4520213_1280.jpg +0 -0
  63. data/app/javascript/application.js +1 -0
  64. data/app/javascript/react/Api/apiConstants.js +47 -0
  65. data/app/javascript/react/assets/beach-2245867_1280.jpg +0 -0
  66. data/app/javascript/react/assets/logo.png +0 -0
  67. data/app/javascript/react/assets/logo_updated.png +0 -0
  68. data/app/javascript/react/components/AboutUsPage/AboutPage.jsx +41 -0
  69. data/app/javascript/react/components/AboutusFront/About.jsx +37 -0
  70. data/app/javascript/react/components/Accommodation/Accommodation.jsx +32 -0
  71. data/app/javascript/react/components/Accommodation/NormalListing.jsx +50 -0
  72. data/app/javascript/react/components/Accommodation/SpecialListing.jsx +51 -0
  73. data/app/javascript/react/components/Accountpage/AccountInfo.jsx +217 -0
  74. data/app/javascript/react/components/Accountpage/AccountPage.jsx +27 -0
  75. data/app/javascript/react/components/Accountpage/CommonPage.jsx +24 -0
  76. data/app/javascript/react/components/AddNewProperty/CommonLayout.jsx +68 -0
  77. data/app/javascript/react/components/AddNewProperty/Description.jsx +229 -0
  78. data/app/javascript/react/components/AddNewProperty/Details.jsx +234 -0
  79. data/app/javascript/react/components/AddNewProperty/Images.jsx +196 -0
  80. data/app/javascript/react/components/AddNewProperty/Location.jsx +239 -0
  81. data/app/javascript/react/components/AddNewProperty/Room.jsx +1132 -0
  82. data/app/javascript/react/components/AvatarDropdown/AvatarDropDown.jsx +142 -0
  83. data/app/javascript/react/components/BlogDesign/BlogsLatest.jsx +67 -0
  84. data/app/javascript/react/components/ContactUs/Contact.jsx +89 -0
  85. data/app/javascript/react/components/FacilitiesSection/Facilities.jsx +66 -0
  86. data/app/javascript/react/components/FixedNavbar/FixedNav.jsx +88 -0
  87. data/app/javascript/react/components/ForgetPassword/ForgetPassword.jsx +103 -0
  88. data/app/javascript/react/components/Gallery/Gallery.jsx +164 -0
  89. data/app/javascript/react/components/GalleryModalLight/GalleryModalLight.jsx +35 -0
  90. data/app/javascript/react/components/GallerySlider/GallerySlider.jsx +58 -0
  91. data/app/javascript/react/components/Headers/PropertyCard.jsx +46 -0
  92. data/app/javascript/react/components/HeroSectionDesign/BookingForm.jsx +178 -0
  93. data/app/javascript/react/components/HeroSectionDesign/HeroSection.jsx +29 -0
  94. data/app/javascript/react/components/HeroSectionDesign/MyPropertiesListing.jsx +104 -0
  95. data/app/javascript/react/components/HeroSectionDesign/PropertiesPage.jsx +122 -0
  96. data/app/javascript/react/components/HotelListing/Listing.jsx +48 -0
  97. data/app/javascript/react/components/Layout/Layout.js +18 -0
  98. data/app/javascript/react/components/Listing-stay-Detail/AmenitiesFeatures.jsx +58 -0
  99. data/app/javascript/react/components/Listing-stay-Detail/AmenitiesModal.jsx +250 -0
  100. data/app/javascript/react/components/Listing-stay-Detail/ApartmentCard.jsx +120 -0
  101. data/app/javascript/react/components/Listing-stay-Detail/BookingModal.jsx +398 -0
  102. data/app/javascript/react/components/Listing-stay-Detail/CheckoutForm.jsx +296 -0
  103. data/app/javascript/react/components/Listing-stay-Detail/ListingStayDetailPage.jsx +512 -0
  104. data/app/javascript/react/components/Listing-stay-Detail/PropertyDescription.jsx +76 -0
  105. data/app/javascript/react/components/Listing-stay-Detail/PropertyDetailsCard.jsx +62 -0
  106. data/app/javascript/react/components/Listing-stay-Detail/Reviews.jsx +132 -0
  107. data/app/javascript/react/components/Listing-stay-Detail/RoomDescriptionModal.jsx +105 -0
  108. data/app/javascript/react/components/Listing-stay-Detail/Rules.jsx +23 -0
  109. data/app/javascript/react/components/ListingImageGallery/ListingImageGallery.jsx +30 -0
  110. data/app/javascript/react/components/LoginPage/LoginPage.jsx +115 -0
  111. data/app/javascript/react/components/MobileNav/MobileMenu.jsx +47 -0
  112. data/app/javascript/react/components/Navbar/Navbar.jsx +25 -0
  113. data/app/javascript/react/components/Page404/Page404.jsx +30 -0
  114. data/app/javascript/react/components/PropertyListing/MyProperties.jsx +146 -0
  115. data/app/javascript/react/components/PropertyListing/StayBooking/BookingDetails.jsx +178 -0
  116. data/app/javascript/react/components/PropertyListing/StayBooking/MyBooking.jsx +83 -0
  117. data/app/javascript/react/components/ResetPassword/ResetPassword.jsx +117 -0
  118. data/app/javascript/react/components/SignupPage/SignupPage.jsx +185 -0
  119. data/app/javascript/react/components/SmallNavbar/SmallNav.jsx +51 -0
  120. data/app/javascript/react/components/SocialAuth/SocialAuth.jsx +21 -0
  121. data/app/javascript/react/components/StayCard/StayCard.jsx +69 -0
  122. data/app/javascript/react/components/StayCard/StayCard2.jsx +45 -0
  123. data/app/javascript/react/components/StayCard/StayCard3.jsx +45 -0
  124. data/app/javascript/react/components/TestimonialSection/Testimonial.jsx +113 -0
  125. data/app/javascript/react/components/Unauthorized/Unauthorized.jsx +12 -0
  126. data/app/javascript/react/data/jsons/__countryListing.json +201 -0
  127. data/app/javascript/react/packs/App.js +26 -0
  128. data/app/javascript/react/packs/index.jsx +38 -0
  129. data/app/javascript/react/packs/routes/ParentRoute.jsx +14 -0
  130. data/app/javascript/react/packs/routes/Route.jsx +163 -0
  131. data/app/javascript/react/pages/AccommodationList.jsx +21 -0
  132. data/app/javascript/react/pages/Home.jsx +32 -0
  133. data/app/javascript/react/redux/slices/AuthSlice/AuthSlice.jsx +100 -0
  134. data/app/javascript/react/redux/slices/PropertySlice/PropertySlice.jsx +722 -0
  135. data/app/javascript/react/redux/slices/PropertySlice/Searchslice.jsx +36 -0
  136. data/app/javascript/react/redux/slices/UserSlice/UserSlice.jsx +215 -0
  137. data/app/javascript/react/redux/store.js +35 -0
  138. data/app/javascript/react/shared/Avatar/Avatar.jsx +32 -0
  139. data/app/javascript/react/shared/Badge/Badge.jsx +33 -0
  140. data/app/javascript/react/shared/Button/Button.jsx +67 -0
  141. data/app/javascript/react/shared/Button/ButtonPrimary.jsx +11 -0
  142. data/app/javascript/react/shared/Button/ButtonSelect.jsx +9 -0
  143. data/app/javascript/react/shared/Checkbox/Checkbox.jsx +38 -0
  144. data/app/javascript/react/shared/CurrencySymbol.jsx +6 -0
  145. data/app/javascript/react/shared/DateField/CustomDatePicker.jsx +69 -0
  146. data/app/javascript/react/shared/FooterSection/Footer.jsx +75 -0
  147. data/app/javascript/react/shared/FormField/FormField.jsx +75 -0
  148. data/app/javascript/react/shared/FormItem/FormItem.jsx +20 -0
  149. data/app/javascript/react/shared/Input/Input.jsx +27 -0
  150. data/app/javascript/react/shared/Label/Label.jsx +12 -0
  151. data/app/javascript/react/shared/Modal.jsx +20 -0
  152. data/app/javascript/react/shared/NcImage/NcImage.jsx +101 -0
  153. data/app/javascript/react/shared/NcImage/PlaceIcon.jsx +31 -0
  154. data/app/javascript/react/shared/NcImage/placecImageIcon.svg +6 -0
  155. data/app/javascript/react/shared/Select/Select.jsx +20 -0
  156. data/app/javascript/react/styles/404error.scss +58 -0
  157. data/app/javascript/react/styles/ApartmentCard.scss +126 -0
  158. data/app/javascript/react/styles/BookingDetails.scss +457 -0
  159. data/app/javascript/react/styles/Modal.scss +36 -0
  160. data/app/javascript/react/styles/PropertiesPage.scss +219 -0
  161. data/app/javascript/react/styles/RenderSection.scss +480 -0
  162. data/app/javascript/react/styles/about.scss +97 -0
  163. data/app/javascript/react/styles/accountinfo.scss +67 -0
  164. data/app/javascript/react/styles/accountpage.scss +36 -0
  165. data/app/javascript/react/styles/amenitiesfeatures.scss +223 -0
  166. data/app/javascript/react/styles/application.scss +87 -0
  167. data/app/javascript/react/styles/avatar.scss +39 -0
  168. data/app/javascript/react/styles/avatardropdown.scss +57 -0
  169. data/app/javascript/react/styles/badge.scss +90 -0
  170. data/app/javascript/react/styles/blog.scss +100 -0
  171. data/app/javascript/react/styles/bookingform.scss +124 -0
  172. data/app/javascript/react/styles/button.scss +44 -0
  173. data/app/javascript/react/styles/buttonprimary.scss +32 -0
  174. data/app/javascript/react/styles/checkbox.scss +37 -0
  175. data/app/javascript/react/styles/commonlayout.scss +83 -0
  176. data/app/javascript/react/styles/commonpage.scss +51 -0
  177. data/app/javascript/react/styles/contact.scss +173 -0
  178. data/app/javascript/react/styles/customdatepicker.scss +120 -0
  179. data/app/javascript/react/styles/description.scss +21 -0
  180. data/app/javascript/react/styles/details.scss +88 -0
  181. data/app/javascript/react/styles/facilities.scss +131 -0
  182. data/app/javascript/react/styles/fixednavbar.scss +137 -0
  183. data/app/javascript/react/styles/fonts.scss +22 -0
  184. data/app/javascript/react/styles/footer.scss +300 -0
  185. data/app/javascript/react/styles/forgetpassword.scss +68 -0
  186. data/app/javascript/react/styles/formfield.scss +39 -0
  187. data/app/javascript/react/styles/formitem.scss +20 -0
  188. data/app/javascript/react/styles/gallery.scss +142 -0
  189. data/app/javascript/react/styles/gallerymodallight.scss +137 -0
  190. data/app/javascript/react/styles/galleryslider.scss +114 -0
  191. data/app/javascript/react/styles/header.scss +49 -0
  192. data/app/javascript/react/styles/herosection.scss +61 -0
  193. data/app/javascript/react/styles/images.scss +112 -0
  194. data/app/javascript/react/styles/input.scss +58 -0
  195. data/app/javascript/react/styles/label.scss +11 -0
  196. data/app/javascript/react/styles/listing.scss +94 -0
  197. data/app/javascript/react/styles/listingimagegallery.scss +57 -0
  198. data/app/javascript/react/styles/listingstaydetailpage.scss +887 -0
  199. data/app/javascript/react/styles/location.scss +66 -0
  200. data/app/javascript/react/styles/loginpage.scss +150 -0
  201. data/app/javascript/react/styles/mobilemenu.scss +53 -0
  202. data/app/javascript/react/styles/mybooking.scss +104 -0
  203. data/app/javascript/react/styles/myproperty.scss +51 -0
  204. data/app/javascript/react/styles/ncimage.scss +24 -0
  205. data/app/javascript/react/styles/normallisting.scss +95 -0
  206. data/app/javascript/react/styles/property-description.scss +75 -0
  207. data/app/javascript/react/styles/propertycard.scss +48 -0
  208. data/app/javascript/react/styles/propertydetailscard.scss +302 -0
  209. data/app/javascript/react/styles/resetpassword.scss +79 -0
  210. data/app/javascript/react/styles/reviews.scss +185 -0
  211. data/app/javascript/react/styles/room.scss +275 -0
  212. data/app/javascript/react/styles/rooms.scss +0 -0
  213. data/app/javascript/react/styles/select.scss +44 -0
  214. data/app/javascript/react/styles/signuppage.scss +132 -0
  215. data/app/javascript/react/styles/smallnav.scss +94 -0
  216. data/app/javascript/react/styles/socialauth.scss +62 -0
  217. data/app/javascript/react/styles/speciallisting.scss +94 -0
  218. data/app/javascript/react/styles/staycard.scss +77 -0
  219. data/app/javascript/react/styles/staycard2.scss +115 -0
  220. data/app/javascript/react/styles/testimonial.scss +216 -0
  221. data/app/javascript/react/styles/unauthorized.scss +22 -0
  222. data/app/javascript/react/styles/variables.scss +3 -0
  223. data/app/javascript/react/styles/wrapper.scss +4 -0
  224. data/app/javascript/react/utils/formSchema.js +120 -0
  225. data/app/javascript/react/utils/helpers/APIHelper.jsx +55 -0
  226. data/app/javascript/react/utils/helpers/ErrorHandler.js +21 -0
  227. data/app/javascript/react/utils/helpers/InfoHandler.js +15 -0
  228. data/app/javascript/react/utils/helpers/SuccessHandler.js +12 -0
  229. data/app/javascript/react/utils/helpers/isInViewPortIntersectionObserver.jsx +39 -0
  230. data/app/jobs/stay_commerce/frontend/application_job.rb +6 -0
  231. data/app/mailers/stay_commerce/frontend/application_mailer.rb +8 -0
  232. data/app/models/stay_commerce/frontend/application_record.rb +7 -0
  233. data/app/views/layouts/stay_commerce/frontend/application.html.erb +61 -0
  234. data/app/views/stay_commerce/frontend/welcome/index.html.erb +2 -0
  235. data/config/initializers/cors.rb +6 -0
  236. data/config/initializers/devise.rb +359 -0
  237. data/config/routes.rb +11 -0
  238. data/lib/stay_commerce/frontend/engine.rb +14 -0
  239. data/lib/stay_commerce/frontend/version.rb +5 -0
  240. data/lib/stay_commerce/frontend.rb +8 -0
  241. data/lib/tasks/stay_commerce/frontend_tasks.rake +4 -0
  242. metadata +370 -0
@@ -0,0 +1,1132 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { useNavigate, useParams } from "react-router-dom";
4
+ import { Formik, useFormikContext } from "formik";
5
+ import ButtonPrimary from "../../shared/Button/ButtonPrimary";
6
+
7
+ import Input from "../../shared/Input/Input";
8
+ import Select from "../../shared/Select/Select";
9
+ import FormItem from "../../shared/FormItem/FormItem";
10
+ import {
11
+ getallupdateProperties,
12
+ getRoomAmenitiesProperty,
13
+ getRoomFeaturesProperty,
14
+ getBedType,
15
+ getRoomType,
16
+ uploadImageProperties,
17
+ deleteImage,
18
+ } from "../../redux/slices/PropertySlice/PropertySlice";
19
+ import successHandler from "../../utils/helpers/SuccessHandler";
20
+
21
+ import Checkbox from "../../shared/Checkbox/Checkbox";
22
+ import CommonLayout from "./CommonLayout";
23
+ import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
24
+ import CustomDatePicker from "../../shared/DateField/CustomDatePicker";
25
+ import { useDropzone } from "react-dropzone";
26
+ import NcImage from "../../shared/NcImage/NcImage";
27
+ import moment from "moment";
28
+ import "../../styles/room.scss";
29
+
30
+ const Rooms = () => {
31
+ const dispatch = useDispatch();
32
+ const navigate = useNavigate();
33
+ const { globalId } = useSelector((state) => state.property);
34
+ const { slug } = useParams();
35
+ const id = slug;
36
+ const [property, setProperty] = useState(null);
37
+
38
+ const initialRoomValues = {
39
+ id: "",
40
+ name: "",
41
+ price_per_month: "",
42
+ status: "",
43
+ booking_start: "",
44
+ booking_end: "",
45
+ description: "",
46
+ bed_type_id: "",
47
+ size: "",
48
+ room_type_id: "",
49
+ amenities: [],
50
+ features: [],
51
+ room_images: [],
52
+ max_guests: "",
53
+ };
54
+
55
+ const [initialValues, setInitialValues] = useState({
56
+ rooms_attributes: [initialRoomValues],
57
+ });
58
+
59
+ const [amenities, setAmenities] = useState([]);
60
+ const [features, setFeatures] = useState([]);
61
+ const [bedTypes, setBedTypes] = useState([]);
62
+ const [reorderImages, setReorderImages] = useState([]);
63
+ const [totalRooms, setTotalRooms] = useState(null);
64
+ const RouteID = id;
65
+ const [roomTypes, setRoomTypes] = useState([]);
66
+ const [count, setCount] = useState(1);
67
+ const [roomImagePreviews, setRoomImagePreviews] = useState({});
68
+ const [roomImagesToDelete, setRoomImagesToDelete] = useState({});
69
+ const [isLoading, setIsLoading] = useState(true);
70
+ useEffect(() => {
71
+ if (initialValues?.rooms_attributes?.length > 0) {
72
+ setCount(initialValues?.rooms_attributes?.length);
73
+ }
74
+ }, [initialValues]);
75
+
76
+ useEffect(() => {
77
+ if (id) {
78
+ const fetchPropertyData = async () => {
79
+ const response = await dispatch(
80
+ getallupdateProperties({ propertyId: id })
81
+ ).unwrap();
82
+ if (response.payload?.success) {
83
+ setProperty(response);
84
+ }
85
+ if (response?.rooms) {
86
+ const mappedRooms = response?.rooms.map((room) => {
87
+ const sortedPlaceImages = [...room?.room_images].sort(
88
+ (a, b) => a.position - b.position
89
+ );
90
+ return {
91
+ id: room.id,
92
+ name: room.name,
93
+ price_per_month:
94
+ room.price_per_month === "0.0"
95
+ ? ""
96
+ : room.price_per_month?.toString() || "",
97
+ status: room.status,
98
+ booking_start: room.booking_start
99
+ ? room.booking_start.split("T")[0]
100
+ : "",
101
+ booking_end: room.booking_end
102
+ ? room.booking_end.split("T")[0]
103
+ : "",
104
+ description: room.description || "",
105
+ bed_type_id: room.bed_type?.id?.toString() || "",
106
+ size: room.size?.toString() || "",
107
+ room_type_id: room.room_type?.id?.toString() || "",
108
+ amenities: room.amenities || [],
109
+ features: room.features || [],
110
+ room_images: [],
111
+ existing_room_images: sortedPlaceImages || [],
112
+ max_guests: room.max_guests?.toString() || "",
113
+ };
114
+ });
115
+ const uniqueRooms = Array.from(
116
+ new Map(mappedRooms.map((room) => [room?.id, room])).values()
117
+ );
118
+ setInitialValues({
119
+ rooms_attributes:
120
+ uniqueRooms.length > 0
121
+ ? uniqueRooms
122
+ : initialValues.rooms_attributes?.map((item) => ({
123
+ ...item,
124
+ })),
125
+ });
126
+ }
127
+ };
128
+ fetchPropertyData();
129
+ }
130
+ }, [dispatch, globalId, id]);
131
+
132
+ const handleAmenitiesChange = (
133
+ amenityId,
134
+ roomIndex,
135
+ values,
136
+ setFieldValue
137
+ ) => {
138
+ const updatedAmenities = values.rooms_attributes.map((room, index) => {
139
+ if (index === roomIndex) {
140
+ const currentAmenities = room.amenities || [];
141
+
142
+ const newAmenities = currentAmenities.some(
143
+ (item) => item.amenity_id === amenityId || item.id === amenityId
144
+ )
145
+ ? currentAmenities.filter(
146
+ (item) => item.amenity_id !== amenityId && item.id !== amenityId
147
+ )
148
+ : [...currentAmenities, { amenity_id: amenityId }];
149
+
150
+ return { ...room, amenities: newAmenities };
151
+ }
152
+ return room;
153
+ });
154
+
155
+ setFieldValue("rooms_attributes", updatedAmenities);
156
+ };
157
+
158
+ const handleFeaturesChange = (
159
+ featureId,
160
+ roomIndex,
161
+ values,
162
+ setFieldValue
163
+ ) => {
164
+ const updatedFeatures = values.rooms_attributes.map((room, index) => {
165
+ if (index === roomIndex) {
166
+ const currentFeatures = room.features || [];
167
+ const newFeatures = currentFeatures.some(
168
+ (item) => item.feature_id === featureId || item.id === featureId
169
+ )
170
+ ? currentFeatures.filter(
171
+ (item) => item.feature_id !== featureId && item.id !== featureId
172
+ )
173
+ : [...currentFeatures, { feature_id: featureId }];
174
+
175
+ return { ...room, features: newFeatures };
176
+ }
177
+ return room;
178
+ });
179
+
180
+ setFieldValue("rooms_attributes", updatedFeatures);
181
+ };
182
+
183
+ const handleSubmit = async (values) => {
184
+ const formData = new FormData();
185
+
186
+ if (id) {
187
+ formData.append(`property[id]`, id);
188
+ }
189
+
190
+ const hasImagesToDelete = Object.values(roomImagesToDelete).some(
191
+ (arr) => arr.length > 0
192
+ );
193
+
194
+ if (hasImagesToDelete) {
195
+ try {
196
+ const allImageIds = Object.values(roomImagesToDelete)
197
+ .flatMap((ids) => ids)
198
+ .filter((id) => id !== undefined);
199
+
200
+ if (allImageIds.length > 0) {
201
+ await dispatch(deleteImage({ id: allImageIds })).unwrap();
202
+ }
203
+ } catch (error) {
204
+ console.error("Error removing room images:", error);
205
+ }
206
+ }
207
+ for (const [index, room] of values.rooms_attributes.entries()) {
208
+ const modifiedAmenities = room.amenities?.map((item) => ({
209
+ amenity_id: item.id || item.amenity_id,
210
+ }));
211
+
212
+ const modifiedFeatures = room.features?.map((item) => ({
213
+ feature_id: item.id || item.feature_id,
214
+ }));
215
+
216
+ if (room.id) {
217
+ formData.append(
218
+ `property[rooms_attributes][${index}][id]`,
219
+ room.id.toString()
220
+ );
221
+ }
222
+ formData.append(`property[rooms_attributes][${index}][name]`, room.name);
223
+ formData.append(
224
+ `property[rooms_attributes][${index}][price_per_month]`,
225
+ room.price_per_month ? room.price_per_month.toString() : ""
226
+ );
227
+ formData.append(
228
+ `property[rooms_attributes][${index}][status]`,
229
+ room.status
230
+ );
231
+ formData.append(
232
+ `property[rooms_attributes][${index}][booking_start]`,
233
+ moment(room.booking_start).format("YYYY-MM-DD") || ""
234
+ );
235
+ formData.append(
236
+ `property[rooms_attributes][${index}][booking_end]`,
237
+ moment(room.booking_end).format("YYYY-MM-DD") || ""
238
+ );
239
+ formData.append(
240
+ `property[rooms_attributes][${index}][description]`,
241
+ room.description
242
+ );
243
+ formData.append(
244
+ `property[rooms_attributes][${index}][bed_type_id]`,
245
+ room.bed_type_id.toString()
246
+ );
247
+ formData.append(
248
+ `property[rooms_attributes][${index}][size]`,
249
+ room.size.toString()
250
+ );
251
+ formData.append(
252
+ `property[rooms_attributes][${index}][room_type_id]`,
253
+ room.room_type_id.toString()
254
+ );
255
+ formData.append(
256
+ `property[rooms_attributes][${index}][max_guests]`,
257
+ room.max_guests.toString()
258
+ );
259
+
260
+ if (modifiedAmenities) {
261
+ modifiedAmenities.forEach((amenity, amenityIndex) => {
262
+ formData.append(
263
+ `property[rooms_attributes][${index}][room_amenities_attributes][${amenityIndex}][amenity_id]`,
264
+ amenity.amenity_id
265
+ );
266
+ });
267
+ }
268
+
269
+ if (modifiedFeatures) {
270
+ modifiedFeatures.forEach((feature, featureIndex) => {
271
+ formData.append(
272
+ `property[rooms_attributes][${index}][room_features_attributes][${featureIndex}][feature_id]`,
273
+ feature.feature_id
274
+ );
275
+ });
276
+ }
277
+
278
+ const newRoomImages = room.room_images?.filter(
279
+ (image) => image instanceof File
280
+ );
281
+ const existingImages = room.existing_room_images || [];
282
+
283
+ if (newRoomImages && newRoomImages.length > 0) {
284
+ newRoomImages.forEach((image) => {
285
+ if (image instanceof File) {
286
+ formData.append(
287
+ `property[rooms_attributes][${index}][room_images][]`,
288
+ image
289
+ );
290
+ }
291
+ });
292
+ }
293
+
294
+ if (existingImages.length > 0) {
295
+ existingImages.forEach((image) => {
296
+ if (image && image.id) {
297
+ formData.append(
298
+ `property[rooms_attributes][${index}][existing_room_image_ids][]`,
299
+ image.id
300
+ );
301
+ }
302
+ });
303
+ }
304
+ }
305
+ try {
306
+ const response = await dispatch(uploadImageProperties(formData)).unwrap();
307
+ if (response) {
308
+ successHandler(response?.message || "Updated successfully");
309
+ navigate(`/listing-stay-detail/${slug}`);
310
+ }
311
+ } catch (error) {
312
+ console.error("Error updating property: ", error);
313
+ }
314
+ };
315
+
316
+ function arraysEqual(arr1, arr2) {
317
+ if (arr1.length !== arr2.length) return false;
318
+ for (let i = 0; i < arr1.length; i++) {
319
+ if (JSON.stringify(arr1[i]) !== JSON.stringify(arr2[i])) return false;
320
+ }
321
+ return true;
322
+ }
323
+
324
+ const handleRoomImagesChange = (e, index, setFieldValue, values) => {
325
+ const files = e.target.files;
326
+ const newFiles = files ? Array.from(files) : [];
327
+ if (files) {
328
+ const newPreviews = newFiles.map((file) => URL.createObjectURL(file));
329
+
330
+ setRoomImagePreviews((prevPreviews) => ({
331
+ ...prevPreviews,
332
+ [index]: [...(prevPreviews[index] || []), ...newPreviews],
333
+ }));
334
+
335
+ const existingImages =
336
+ values.rooms_attributes?.[index]?.room_images || [];
337
+ const combinedImages = [...existingImages, ...newFiles];
338
+ setFieldValue(`rooms_attributes[${index}].room_images`, combinedImages);
339
+ }
340
+ };
341
+
342
+ const handleRemoveRoomImage = async (
343
+ previewIndex,
344
+ index,
345
+ values,
346
+ setFieldValue
347
+ ) => {
348
+ const existingImages =
349
+ values?.rooms_attributes[index]?.existing_room_images || [];
350
+ const isExistingImage = previewIndex < existingImages.length;
351
+
352
+ if (isExistingImage) {
353
+ const imageIdToRemove = existingImages[previewIndex]?.id;
354
+ setRoomImagesToDelete((prev) => {
355
+ const updatedMap = { ...prev };
356
+ if (!updatedMap[index]) {
357
+ updatedMap[index] = [];
358
+ }
359
+ updatedMap[index] = [...updatedMap[index], imageIdToRemove];
360
+ return updatedMap;
361
+ });
362
+ const updatedExistingImages = existingImages.filter(
363
+ (_, idx) => idx !== previewIndex
364
+ );
365
+ setFieldValue(
366
+ `rooms_attributes[${index}].existing_room_images`,
367
+ updatedExistingImages
368
+ );
369
+ } else {
370
+ const newPreviews = roomImagePreviews[index] || [];
371
+ const updatedPreviews = newPreviews.filter(
372
+ (_, idx) => idx !== previewIndex - existingImages.length
373
+ );
374
+
375
+ setRoomImagePreviews((prevPreviews) => {
376
+ const updatedPreviewsMap = { ...prevPreviews };
377
+ updatedPreviewsMap[index] = updatedPreviews;
378
+ return updatedPreviewsMap;
379
+ });
380
+ const newImages = values.rooms_attributes[index]?.room_images || [];
381
+ const updatedNewImages = newImages.filter(
382
+ (_, idx) => idx !== previewIndex - existingImages.length
383
+ );
384
+ setFieldValue(`rooms_attributes[${index}].room_images`, updatedNewImages);
385
+ }
386
+ };
387
+
388
+ useEffect(() => {
389
+ dispatch(getRoomFeaturesProperty())
390
+ .unwrap()
391
+ .then((response) => setFeatures(response.data))
392
+ .catch((error) => console.error("Error fetching features:", error));
393
+
394
+ dispatch(getRoomAmenitiesProperty())
395
+ .unwrap()
396
+ .then((response) => setAmenities(response.data))
397
+ .catch((error) => console.error("Error fetching features:", error));
398
+ }, [dispatch]);
399
+
400
+ useEffect(() => {
401
+ const fetchBedTypes = async () => {
402
+ try {
403
+ const response = await dispatch(getBedType()).unwrap();
404
+ if (response.success) {
405
+ setBedTypes(response.data);
406
+ } else {
407
+ console.error("Failed to fetch bed types:", response.message);
408
+ }
409
+ } catch (error) {
410
+ console.error("Error fetching bed types:", error);
411
+ }
412
+ };
413
+
414
+ const fetchRoomTypes = async () => {
415
+ try {
416
+ const response = await dispatch(getRoomType()).unwrap();
417
+ if (response.success) {
418
+ setRoomTypes(response.data);
419
+ } else {
420
+ console.error("Failed to fetch room types:", response.message);
421
+ }
422
+ } catch (error) {
423
+ console.error("Error fetching room types:", error);
424
+ }
425
+ };
426
+ fetchBedTypes();
427
+ fetchRoomTypes();
428
+ }, [dispatch]);
429
+
430
+ const useRoomsImagesDropzone = (index, values, setFieldValue) => {
431
+ return useDropzone({
432
+ onDrop: (acceptedFiles) => {
433
+ const files = acceptedFiles.map((file) => URL.createObjectURL(file));
434
+
435
+ setRoomImagePreviews((prevPreviews) => ({
436
+ ...prevPreviews,
437
+ [index]: [...(prevPreviews[index] || []), ...files],
438
+ }));
439
+
440
+ const existingImages =
441
+ values.rooms_attributes?.[index]?.room_images || [];
442
+ const combinedImages = [...existingImages, ...acceptedFiles];
443
+ setFieldValue(`rooms_attributes[${index}].room_images`, combinedImages);
444
+ },
445
+ });
446
+ };
447
+
448
+ const RoomImagesUploader = ({ index }) => {
449
+ const { values, setFieldValue } = useFormikContext();
450
+ const handleImageUpload = (files) => {
451
+ if (!files || files.length === 0) return;
452
+ const newFiles = Array.from(files);
453
+ const newPreviews = newFiles.map((file) => ({
454
+ previewUrl: URL.createObjectURL(file),
455
+ file,
456
+ }));
457
+ setRoomImagePreviews((prevPreviews) => ({
458
+ ...prevPreviews,
459
+ [index]: [...(prevPreviews[index] || []), ...newPreviews],
460
+ }));
461
+ const existingImages =
462
+ values.rooms_attributes?.[index]?.room_images || [];
463
+ const combinedImages = [...existingImages, ...newFiles];
464
+ setFieldValue(`rooms_attributes[${index}].room_images`, combinedImages);
465
+ };
466
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
467
+ accept: {
468
+ "image/*": [".jpeg", ".jpg", ".png", ".gif"],
469
+ },
470
+ onDrop: handleImageUpload,
471
+ });
472
+
473
+ return (
474
+ <div
475
+ {...getRootProps()}
476
+ className={`upload-container ${isDragActive ? "drag-active" : ""}`}
477
+ >
478
+ <input
479
+ {...getInputProps()}
480
+ id={`room-image-upload-${index}`}
481
+ name={`room-image-upload-${index}`}
482
+ type="file"
483
+ className="sr-only"
484
+ multiple
485
+ // No need for onChange here as the dropzone will handle all inputs
486
+ />
487
+ <div className="upload-content">
488
+ <svg
489
+ className="upload-icon"
490
+ stroke="currentColor"
491
+ fill="none"
492
+ viewBox="0 0 48 48"
493
+ aria-hidden="true"
494
+ >
495
+ <path
496
+ d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
497
+ strokeWidth="2"
498
+ strokeLinecap="round"
499
+ strokeLinejoin="round"
500
+ />
501
+ </svg>
502
+ <div className="upload-text">
503
+ <span className="upload-link">Upload multiple files</span>
504
+ <p className="pl-1">or drag and drop</p>
505
+ </div>
506
+ <p className="upload-info">PNG, JPG, GIF up to 10MB</p>
507
+ </div>
508
+ </div>
509
+ );
510
+ };
511
+
512
+ useEffect(() => {
513
+ setTimeout(() => setIsLoading(false), 1500);
514
+ }, []);
515
+
516
+ return (
517
+ <CommonLayout currentHref="/property-4" PropertyID={RouteID}>
518
+ <div className="description-container">
519
+ <h2 className="rooms-title">Room Details</h2>
520
+ <span className="rooms-subtitle">
521
+ Provide detailed information about your property to help guests make
522
+ informed decisions.
523
+ </span>
524
+ <div className="divider" />
525
+ <Formik
526
+ initialValues={initialValues}
527
+ // validationSchema={roomRuleValidationSchema}
528
+ onSubmit={handleSubmit}
529
+ enableReinitialize={true}
530
+ >
531
+ {({
532
+ errors,
533
+ values,
534
+ handleChange,
535
+ handleSubmit,
536
+ isSubmitting,
537
+ getFieldProps,
538
+ setFieldValue,
539
+ }) => (
540
+ <form onSubmit={handleSubmit} className="form-container">
541
+ {values.rooms_attributes.map((room, index) => (
542
+ <div
543
+ key={index}
544
+ className={`room-section ${
545
+ index > 0 ? "room-section-bordered" : ""
546
+ }`}
547
+ >
548
+ <h3 className="room-title">Room {index + 1}</h3>
549
+ <FormItem label="Room Name" className="form-item">
550
+ <Input
551
+ type="text"
552
+ placeholder="Enter Room name"
553
+ {...getFieldProps(`rooms_attributes[${index}].name`)}
554
+ className="common-input"
555
+ />
556
+ {errors.rooms_attributes &&
557
+ Array.isArray(errors.rooms_attributes) &&
558
+ typeof errors.rooms_attributes[index] === "object" &&
559
+ errors.rooms_attributes[index] !== null &&
560
+ errors.rooms_attributes[index].name && (
561
+ <div className="error-text">
562
+ {errors.rooms_attributes[index].name}
563
+ </div>
564
+ )}
565
+ </FormItem>
566
+ <FormItem label="Price per Month" className="form-item">
567
+ <div className="relative">
568
+ <Input
569
+ type="text"
570
+ name={`rooms_attributes[${index}].price_per_month`}
571
+ placeholder="Please enter price per month"
572
+ onChange={handleChange}
573
+ value={values.rooms_attributes[index]?.price_per_month}
574
+ className="common-input"
575
+ />
576
+ </div>
577
+ {errors.rooms_attributes &&
578
+ Array.isArray(errors.rooms_attributes) &&
579
+ typeof errors.rooms_attributes[index] === "object" &&
580
+ errors.rooms_attributes[index] !== null &&
581
+ errors.rooms_attributes[index].price_per_month && (
582
+ <div className="error-text">
583
+ {errors.rooms_attributes[index].price_per_month}
584
+ </div>
585
+ )}
586
+ </FormItem>
587
+ <FormItem label="Status" className="form-item">
588
+ <Select
589
+ name={`rooms_attributes[${index}].status`}
590
+ onChange={handleChange}
591
+ value={room.status}
592
+ className="common-input"
593
+ >
594
+ <option value="">Select status</option>
595
+ <option value="active">Active</option>
596
+ <option value="inactive">Inactive</option>
597
+ </Select>
598
+ {errors.rooms_attributes &&
599
+ Array.isArray(errors.rooms_attributes) &&
600
+ typeof errors.rooms_attributes[index] === "object" &&
601
+ errors.rooms_attributes[index] !== null &&
602
+ errors.rooms_attributes[index].status && (
603
+ <div className="error-text">
604
+ {errors.rooms_attributes[index].status}
605
+ </div>
606
+ )}
607
+ </FormItem>
608
+ <FormItem label="Max Guests" className="form-item">
609
+ <div className="relative">
610
+ <Input
611
+ type="text"
612
+ name={`rooms_attributes[${index}].max_guests`}
613
+ placeholder="Max-Guests"
614
+ onChange={handleChange}
615
+ value={values.rooms_attributes[index]?.max_guests}
616
+ className="common-input"
617
+ />
618
+ </div>
619
+ {errors.rooms_attributes &&
620
+ Array.isArray(errors.rooms_attributes) &&
621
+ typeof errors.rooms_attributes[index] === "object" &&
622
+ errors.rooms_attributes[index] !== null &&
623
+ errors.rooms_attributes[index].max_guests && (
624
+ <div className="error-text">
625
+ {errors.rooms_attributes[index].max_guests}
626
+ </div>
627
+ )}
628
+ </FormItem>
629
+ <div className="date-picker-container">
630
+ <FormItem label="Availability Start Date">
631
+ <input
632
+ type="date"
633
+ className="common-input"
634
+ value={
635
+ room.booking_start
636
+ ? room.booking_start.split("T")[0]
637
+ : ""
638
+ }
639
+ onChange={(e) => {
640
+ const selectedDate = e.target.value;
641
+ setFieldValue(
642
+ `rooms_attributes[${index}].booking_start`,
643
+ selectedDate
644
+ );
645
+ setFieldValue(
646
+ `rooms_attributes[${index}].booking_end`,
647
+ null
648
+ );
649
+ }}
650
+ min={new Date().toISOString().split("T")[0]}
651
+ />
652
+ {errors.rooms_attributes &&
653
+ Array.isArray(errors.rooms_attributes) &&
654
+ errors.rooms_attributes[index] &&
655
+ typeof errors.rooms_attributes[index] === "object" &&
656
+ errors.rooms_attributes[index] !== null &&
657
+ errors.rooms_attributes[index].booking_start && (
658
+ <div className="error-text">
659
+ {typeof errors.rooms_attributes[index]
660
+ .booking_start === "string" &&
661
+ errors.rooms_attributes[index].booking_start}
662
+ </div>
663
+ )}
664
+ </FormItem>
665
+
666
+ <FormItem
667
+ label="Availability End Date"
668
+ className="form-item"
669
+ >
670
+ <input
671
+ type="date"
672
+ className="common-input"
673
+ value={
674
+ room.booking_end ? room.booking_end.split("T")[0] : ""
675
+ }
676
+ onChange={(e) => {
677
+ const selectedDate = e.target.value;
678
+ setFieldValue(
679
+ `rooms_attributes[${index}].booking_end`,
680
+ selectedDate
681
+ );
682
+ }}
683
+ min={
684
+ room.booking_start
685
+ ? new Date(
686
+ new Date(room.booking_start).getTime() +
687
+ 86400000
688
+ )
689
+ .toISOString()
690
+ .split("T")[0]
691
+ : new Date().toISOString().split("T")[0]
692
+ }
693
+ />
694
+ {errors.rooms_attributes &&
695
+ Array.isArray(errors.rooms_attributes) &&
696
+ errors.rooms_attributes[index] &&
697
+ typeof errors.rooms_attributes[index] === "object" &&
698
+ errors.rooms_attributes[index] !== null &&
699
+ errors.rooms_attributes[index].booking_end && (
700
+ <div className="error-text">
701
+ {typeof errors.rooms_attributes[index]
702
+ .booking_end === "string" &&
703
+ errors.rooms_attributes[index].booking_end}
704
+ </div>
705
+ )}
706
+ </FormItem>
707
+ </div>
708
+ <FormItem label="Description" className="form-item">
709
+ <textarea
710
+ className="description-textarea"
711
+ name={`rooms_attributes[${index}].description`}
712
+ placeholder="Enter room description"
713
+ onChange={handleChange}
714
+ value={room.description}
715
+ />
716
+ {errors.rooms_attributes &&
717
+ Array.isArray(errors.rooms_attributes) &&
718
+ typeof errors.rooms_attributes[index] === "object" &&
719
+ errors.rooms_attributes[index] !== null &&
720
+ errors.rooms_attributes[index].description && (
721
+ <div className="error-text">
722
+ {errors.rooms_attributes[index].description}
723
+ </div>
724
+ )}
725
+ </FormItem>
726
+ <div className="room-details-container">
727
+ <FormItem label="Bed Type" className="form-item">
728
+ <Select
729
+ name={`rooms_attributes[${index}].bed_type_id`}
730
+ onChange={handleChange}
731
+ value={room.bed_type_id}
732
+ className="common-input"
733
+ >
734
+ <option value="">Select bed type</option>
735
+ {bedTypes.map((bedType) => (
736
+ <option key={bedType.id} value={bedType.id}>
737
+ {bedType.name}
738
+ </option>
739
+ ))}
740
+ </Select>
741
+ {errors.rooms_attributes &&
742
+ Array.isArray(errors.rooms_attributes) &&
743
+ typeof errors.rooms_attributes[index] === "object" &&
744
+ errors.rooms_attributes[index] !== null &&
745
+ errors.rooms_attributes[index].bed_type_id && (
746
+ <div className="error-text">
747
+ {errors.rooms_attributes[index].bed_type_id}
748
+ </div>
749
+ )}
750
+ </FormItem>
751
+ <FormItem label="Room Size (sq ft)" className="form-item">
752
+ <Input
753
+ type="text"
754
+ name={`rooms_attributes[${index}].size`}
755
+ placeholder="Enter room size"
756
+ onChange={handleChange}
757
+ value={room.size}
758
+ className="common-input"
759
+ />
760
+ {errors.rooms_attributes &&
761
+ Array.isArray(errors.rooms_attributes) &&
762
+ typeof errors.rooms_attributes[index] === "object" &&
763
+ errors.rooms_attributes[index] !== null &&
764
+ errors.rooms_attributes[index].size && (
765
+ <div className="error-text">
766
+ {errors.rooms_attributes[index].size}
767
+ </div>
768
+ )}
769
+ </FormItem>
770
+ <FormItem label="Room Type" className="form-item">
771
+ <Select
772
+ name={`rooms_attributes[${index}].room_type_id`}
773
+ onChange={handleChange}
774
+ value={room.room_type_id}
775
+ className="common-input"
776
+ >
777
+ <option value="">Select room type</option>
778
+ {roomTypes.map((roomType) => (
779
+ <option key={roomType.id} value={roomType.id}>
780
+ {roomType.name}
781
+ </option>
782
+ ))}
783
+ </Select>
784
+ {errors.rooms_attributes &&
785
+ Array.isArray(errors.rooms_attributes) &&
786
+ typeof errors.rooms_attributes[index] === "object" &&
787
+ errors.rooms_attributes[index] !== null &&
788
+ errors.rooms_attributes[index].room_type_id && (
789
+ <div className="error-text">
790
+ {errors.rooms_attributes[index].room_type_id}
791
+ </div>
792
+ )}
793
+ </FormItem>
794
+ </div>
795
+ <FormItem>
796
+ <h2 className="section-title">Amenities</h2>
797
+ <div className="checkbox-grid">
798
+ {amenities.map((amenity) => (
799
+ <Checkbox
800
+ key={amenity.id.toString()}
801
+ id={amenity.id.toString()}
802
+ label={amenity.name}
803
+ name={`rooms_attributes[${index}].amenities`}
804
+ onChange={(checked) =>
805
+ handleAmenitiesChange(
806
+ amenity?.id,
807
+ index,
808
+ values,
809
+ setFieldValue
810
+ )
811
+ }
812
+ checked={values.rooms_attributes[
813
+ index
814
+ ]?.amenities?.some(
815
+ (item) =>
816
+ item.amenity_id === amenity.id ||
817
+ item.id === amenity.id
818
+ )}
819
+ />
820
+ ))}
821
+ </div>
822
+ </FormItem>
823
+ <FormItem>
824
+ <h2 className="section-title">Features</h2>
825
+ <div className="checkbox-grid">
826
+ {features.map((feature) => (
827
+ <Checkbox
828
+ key={feature.id.toString()}
829
+ id={feature.id.toString()}
830
+ label={feature.name}
831
+ className="common-input"
832
+ name={`rooms_attributes[${index}].features`}
833
+ onChange={(checked) =>
834
+ handleFeaturesChange(
835
+ feature?.id,
836
+ index,
837
+ values,
838
+ setFieldValue
839
+ )
840
+ }
841
+ checked={values.rooms_attributes[
842
+ index
843
+ ]?.features?.some(
844
+ (item) =>
845
+ item.feature_id === feature.id ||
846
+ item.id === feature.id
847
+ )}
848
+ />
849
+ ))}
850
+ </div>
851
+ </FormItem>
852
+ <div key={index}>
853
+ <div className="image-upload-section">
854
+ <span className="image-upload-title">Room images</span>
855
+ <div className="image-upload-content">
856
+ <RoomImagesUploader key={index} index={index} />
857
+ {isLoading ? (
858
+ <div className="image-preview-container">
859
+ {Array.from({
860
+ length: room.existing_room_images?.length,
861
+ }).map((_, previewIndex) => (
862
+ <div
863
+ key={previewIndex}
864
+ className="image-placeholder"
865
+ ></div>
866
+ ))}
867
+ </div>
868
+ ) : (
869
+ <DragDropContext
870
+ onDragEnd={(result) => {
871
+ const { source, destination } = result;
872
+ if (!destination) return;
873
+ const existingImages =
874
+ room.existing_room_images || [];
875
+ const newImages = room.room_images || [];
876
+ const combinedImages = [
877
+ ...existingImages,
878
+ ...newImages,
879
+ ];
880
+
881
+ // Reorder the images
882
+ const reorderedImages = [...combinedImages];
883
+ const [removed] = reorderedImages.splice(
884
+ source.index,
885
+ 1
886
+ );
887
+ reorderedImages.splice(
888
+ destination.index,
889
+ 0,
890
+ removed
891
+ );
892
+ const newPosition = destination.index + 1;
893
+ if (removed.id) {
894
+ setReorderImages((prev) => {
895
+ const updated = prev.filter(
896
+ (item) =>
897
+ item.position !== newPosition ||
898
+ item.blob_id === removed.id
899
+ );
900
+ return [
901
+ ...updated,
902
+ {
903
+ blob_id: removed.id,
904
+ position: newPosition,
905
+ },
906
+ ];
907
+ });
908
+ }
909
+
910
+ const updatedExistingRoomImages =
911
+ reorderedImages.filter((item) => item.id);
912
+ const updatedRoomImages = reorderedImages.filter(
913
+ (item) => !item.id
914
+ );
915
+ setFieldValue(
916
+ `rooms_attributes[${index}].existing_room_images`,
917
+ updatedExistingRoomImages
918
+ );
919
+ setFieldValue(
920
+ `rooms_attributes[${index}].room_images`,
921
+ updatedRoomImages
922
+ );
923
+ }}
924
+ >
925
+ <Droppable
926
+ droppableId={`room-images-${index}`}
927
+ direction="horizontal"
928
+ >
929
+ {(provided) => (
930
+ <div
931
+ className="image-preview-container"
932
+ {...provided.droppableProps}
933
+ ref={provided.innerRef}
934
+ >
935
+ {(room.existing_room_images || []).map(
936
+ (image, previewIndex) => (
937
+ <Draggable
938
+ key={`existing-image-${
939
+ image.id || previewIndex
940
+ }`}
941
+ draggableId={`existing-image-${
942
+ image.id || previewIndex
943
+ }`}
944
+ index={previewIndex}
945
+ >
946
+ {(provided) => (
947
+ <div
948
+ ref={provided.innerRef}
949
+ {...provided.draggableProps}
950
+ {...provided.dragHandleProps}
951
+ className="image-wrapper"
952
+ >
953
+ <div className="image-container">
954
+ <NcImage
955
+ src={image.url || image}
956
+ alt={`Room ${index + 1} Image ${
957
+ previewIndex + 1
958
+ }`}
959
+ className="image"
960
+ />
961
+ </div>
962
+ <button
963
+ type="button"
964
+ onClick={() =>
965
+ handleRemoveRoomImage(
966
+ previewIndex,
967
+ index,
968
+ values,
969
+ setFieldValue
970
+ )
971
+ }
972
+ className="remove-image-button"
973
+ >
974
+ <svg
975
+ className="remove-image-icon"
976
+ xmlns="http://www.w3.org/2000/svg"
977
+ fill="none"
978
+ viewBox="0 0 24 24"
979
+ stroke="currentColor"
980
+ >
981
+ <path
982
+ strokeLinecap="round"
983
+ strokeLinejoin="round"
984
+ strokeWidth={2}
985
+ d="M6 18L18 6M6 6l12 12"
986
+ />
987
+ </svg>
988
+ </button>
989
+ </div>
990
+ )}
991
+ </Draggable>
992
+ )
993
+ )}
994
+ {(room.room_images || []).map(
995
+ (image, newIndex) => {
996
+ const previewIndex =
997
+ (room.existing_room_images?.length ||
998
+ 0) + newIndex;
999
+ const previewUrl =
1000
+ roomImagePreviews[index]?.[newIndex]
1001
+ ?.previewUrl ||
1002
+ (image instanceof File
1003
+ ? URL.createObjectURL(image)
1004
+ : null);
1005
+
1006
+ return (
1007
+ <Draggable
1008
+ key={`new-image-${previewIndex}`}
1009
+ draggableId={`new-image-${previewIndex}`}
1010
+ index={previewIndex}
1011
+ >
1012
+ {(provided) => (
1013
+ <div
1014
+ ref={provided.innerRef}
1015
+ {...provided.draggableProps}
1016
+ {...provided.dragHandleProps}
1017
+ className="image-wrapper"
1018
+ >
1019
+ <div className="image-container">
1020
+ <NcImage
1021
+ src={previewUrl || image}
1022
+ alt={`Room ${
1023
+ index + 1
1024
+ } Image ${previewIndex + 1}`}
1025
+ className="image"
1026
+ />
1027
+ </div>
1028
+ <button
1029
+ type="button"
1030
+ onClick={() =>
1031
+ handleRemoveRoomImage(
1032
+ previewIndex,
1033
+ index,
1034
+ values,
1035
+ setFieldValue
1036
+ )
1037
+ }
1038
+ className="remove-image-button"
1039
+ >
1040
+ <svg
1041
+ className="remove-image-icon"
1042
+ xmlns="http://www.w3.org/2000/svg"
1043
+ fill="none"
1044
+ viewBox="0 0 24 24"
1045
+ stroke="currentColor"
1046
+ >
1047
+ <path
1048
+ strokeLinecap="round"
1049
+ strokeLinejoin="round"
1050
+ strokeWidth={2}
1051
+ d="M6 18L18 6M6 6l12 12"
1052
+ />
1053
+ </svg>
1054
+ </button>
1055
+ </div>
1056
+ )}
1057
+ </Draggable>
1058
+ );
1059
+ }
1060
+ )}
1061
+ {provided.placeholder}
1062
+ </div>
1063
+ )}
1064
+ </Droppable>
1065
+ </DragDropContext>
1066
+ )}
1067
+ </div>
1068
+ </div>
1069
+ </div>
1070
+ </div>
1071
+ ))}
1072
+ <div
1073
+ className="room-actions-container"
1074
+ style={{
1075
+ display: "flex",
1076
+ gap: "10px",
1077
+ flexWrap: "wrap",
1078
+ }}
1079
+ >
1080
+ {values.rooms_attributes.length > 1 && (
1081
+ <ButtonPrimary
1082
+ type="button"
1083
+ onClick={() => {
1084
+ setCount((prev) => prev - 1);
1085
+ setFieldValue(
1086
+ "rooms_attributes",
1087
+ values.rooms_attributes.filter(
1088
+ (_, idx) => idx !== values.rooms_attributes.length - 1
1089
+ )
1090
+ );
1091
+ }}
1092
+ className="action-button"
1093
+ >
1094
+ Remove Last Room
1095
+ </ButtonPrimary>
1096
+ )}
1097
+ <ButtonPrimary
1098
+ type="button"
1099
+ onClick={() => {
1100
+ setCount((prev) => prev + 1);
1101
+ setFieldValue("rooms_attributes", [
1102
+ ...values.rooms_attributes,
1103
+ initialRoomValues,
1104
+ ]);
1105
+ }}
1106
+ className="action-button"
1107
+ >
1108
+ Add Another Room
1109
+ </ButtonPrimary>
1110
+ </div>
1111
+
1112
+ <div className="form-actions">
1113
+ <ButtonPrimary href={`/property-4/${slug}`}>
1114
+ Go back
1115
+ </ButtonPrimary>
1116
+ <ButtonPrimary
1117
+ type="submit"
1118
+ onClick={handleSubmit}
1119
+ disabled={isSubmitting}
1120
+ >
1121
+ {isSubmitting ? "Loading..." : "Continue"}
1122
+ </ButtonPrimary>
1123
+ </div>
1124
+ </form>
1125
+ )}
1126
+ </Formik>
1127
+ </div>
1128
+ </CommonLayout>
1129
+ );
1130
+ };
1131
+
1132
+ export default Rooms;