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,229 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { useFormik } from "formik";
3
+ import { useDispatch, useSelector } from "react-redux";
4
+ import CommonLayout from "./CommonLayout";
5
+ import "../../styles/description.scss";
6
+ import FormField from "../../shared/FormField/FormField";
7
+ import ButtonPrimary from "../../shared/Button/ButtonPrimary";
8
+ import {
9
+ createProperties,
10
+ getallupdateProperties,
11
+ getPropertyCategories,
12
+ getPropertyType,
13
+ setId,
14
+ updateProperties,
15
+ clearPropertyToEdit,
16
+ } from "../../redux/slices/PropertySlice/PropertySlice";
17
+ import { useNavigate } from "react-router-dom";
18
+ import { useParams } from "react-router-dom";
19
+ import successHandler from "../../utils/helpers/SuccessHandler";
20
+
21
+ const Description = () => {
22
+ const dispatch = useDispatch();
23
+ const navigate = useNavigate();
24
+ const { slug } = useParams();
25
+ const id = slug;
26
+ const RouteID = slug;
27
+
28
+ const [filteredPropertyTypes, setFilteredPropertyTypes] = useState([]);
29
+ const [isEditing, setIsEditing] = useState(false);
30
+ const [loading, setLoading] = useState(false);
31
+
32
+ const { globalId, propertiesCategories, propertyTypes, PropertyToEdit } =
33
+ useSelector((state) => state.property);
34
+
35
+ const initialValues = {
36
+ title: PropertyToEdit?.title || "",
37
+ property_category: PropertyToEdit?.property_category?.id || "",
38
+ property_type_id: PropertyToEdit?.property_type?.id || "",
39
+ country: PropertyToEdit?.country || "",
40
+ address: PropertyToEdit?.address || "",
41
+
42
+ description: PropertyToEdit?.description || "",
43
+ };
44
+
45
+ const formik = useFormik({
46
+ initialValues,
47
+ enableReinitialize: true,
48
+ // validationSchema: descriptionValidationSchemas,
49
+ onSubmit: (values, { setSubmitting, resetForm }) => {
50
+ const payload = {
51
+ title: values.title,
52
+ property_category_id: values.property_category,
53
+ property_type_id: values.property_type_id,
54
+ address: values.address,
55
+ state: values.state,
56
+ country: values.country,
57
+ description: values.description,
58
+
59
+ ...(slug ? { id: slug } : {}),
60
+ };
61
+
62
+ setSubmitting(true);
63
+
64
+ if (id) {
65
+ if (isEditing) {
66
+ dispatch(updateProperties({ property: payload }))
67
+ .unwrap()
68
+ .then((response) => {
69
+ const newSlug = response?.property?.slug || slug;
70
+ if (response?.success) {
71
+ successHandler("Updated successfully");
72
+ navigate(`/property-2/${newSlug}`);
73
+ resetForm();
74
+ }
75
+ })
76
+ .catch(console.error)
77
+ .finally(() => setSubmitting(false));
78
+ } else {
79
+ navigate(`/property-2/${slug}`);
80
+ }
81
+ } else {
82
+ dispatch(createProperties({ property: payload }))
83
+ .unwrap()
84
+ .then((response) => {
85
+ if (response?.success) {
86
+ successHandler("Created successfully");
87
+ dispatch(setId(response.property.id));
88
+ resetForm();
89
+ navigate(`/property-2/${response?.property?.slug}`);
90
+ }
91
+ })
92
+ .catch(console.error)
93
+ .finally(() => {
94
+ setLoading(false);
95
+ setSubmitting(false);
96
+ });
97
+ }
98
+ },
99
+ });
100
+
101
+ const initialFormValues = JSON.stringify(initialValues);
102
+
103
+ useEffect(() => {
104
+ const currentFormValues = JSON.stringify(formik.values);
105
+ setIsEditing(currentFormValues !== initialFormValues);
106
+ }, [formik.values, initialFormValues]);
107
+
108
+ useEffect(() => {
109
+ dispatch(getPropertyCategories());
110
+ dispatch(getPropertyType());
111
+ if (id) {
112
+ dispatch(getallupdateProperties({ propertyId: id }));
113
+ } else {
114
+ dispatch(clearPropertyToEdit()); // 🧹 Clear old edit data
115
+ }
116
+ }, [dispatch, id]);
117
+
118
+ useEffect(() => {
119
+ if (Array.isArray(propertyTypes)) {
120
+ setFilteredPropertyTypes(propertyTypes);
121
+ }
122
+ }, [propertyTypes]);
123
+
124
+ return (
125
+ <CommonLayout currentHref="/property-1" PropertyID={RouteID}>
126
+ <div className="description-container">
127
+ <h2 className="title">Listing Details</h2>
128
+ <div className="divider" />
129
+
130
+ <form onSubmit={formik.handleSubmit} className="form-container">
131
+ <FormField
132
+ label="Title"
133
+ name="title"
134
+ type="text"
135
+ placeholder="Enter title"
136
+ value={formik.values.title}
137
+ onChange={formik.handleChange}
138
+ onBlur={formik.handleBlur}
139
+ error={formik.touched.title && formik.errors.title}
140
+ />
141
+
142
+ <FormField
143
+ label="Choose a property Category"
144
+ name="property_category"
145
+ type="select"
146
+ value={formik.values.property_category}
147
+ onChange={(e) => {
148
+ formik.handleChange(e);
149
+ formik.setFieldValue("property_type_id", "");
150
+ }}
151
+ onBlur={formik.handleBlur}
152
+ options={
153
+ propertiesCategories?.map((item) => ({
154
+ value: String(item.id),
155
+ label: item.name,
156
+ })) || []
157
+ }
158
+ error={
159
+ formik.touched.property_category &&
160
+ formik.errors.property_category
161
+ ? formik.errors.property_category
162
+ : undefined
163
+ }
164
+ />
165
+
166
+ <FormField
167
+ label="Choose a property type"
168
+ name="property_type_id"
169
+ type="select"
170
+ value={formik.values.property_type_id}
171
+ onChange={formik.handleChange}
172
+ onBlur={formik.handleBlur}
173
+ options={filteredPropertyTypes.map((type) => ({
174
+ value: String(type.id),
175
+ label: type.name,
176
+ }))}
177
+ error={
178
+ formik.touched.property_type_id && formik.errors.property_type_id
179
+ ? formik.errors.property_type_id
180
+ : undefined
181
+ }
182
+ />
183
+
184
+ <FormField
185
+ label="Country"
186
+ name="country"
187
+ type="text"
188
+ placeholder="Enter country"
189
+ value={formik.values.country}
190
+ onChange={formik.handleChange}
191
+ onBlur={formik.handleBlur}
192
+ error={formik.touched.country && formik.errors.country}
193
+ />
194
+
195
+ <FormField
196
+ label="Address"
197
+ name="address"
198
+ type="text"
199
+ placeholder="Enter address"
200
+ value={formik.values.address}
201
+ onChange={formik.handleChange}
202
+ onBlur={formik.handleBlur}
203
+ error={formik.touched.address && formik.errors.address}
204
+ />
205
+
206
+ <FormField
207
+ label="Description"
208
+ name="description"
209
+ type="textarea"
210
+ placeholder="Enter description"
211
+ value={formik.values.description}
212
+ onChange={formik.handleChange}
213
+ onBlur={formik.handleBlur}
214
+ error={formik.touched.description && formik.errors.description}
215
+ />
216
+ <ButtonPrimary
217
+ type="submit"
218
+ className="submit-btn"
219
+ disabled={loading}
220
+ >
221
+ {loading ? "Submitting..." : "Submit"}
222
+ </ButtonPrimary>
223
+ </form>
224
+ </div>
225
+ </CommonLayout>
226
+ );
227
+ };
228
+
229
+ export default Description;
@@ -0,0 +1,234 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { useLocation, useParams, useNavigate } from "react-router-dom";
3
+ import { Formik } from "formik";
4
+ import { useDispatch } from "react-redux";
5
+ import CommonLayout from "./CommonLayout";
6
+ import "../../styles/description.scss";
7
+ import FormField from "../../shared/FormField/FormField";
8
+ import ButtonPrimary from "../../shared/Button/ButtonPrimary";
9
+ import {
10
+ getallupdateProperties,
11
+ updateProperties,
12
+ } from "../../redux/slices/PropertySlice/PropertySlice";
13
+ import "../../styles/details.scss";
14
+ import { useSelector } from "react-redux";
15
+ import successHandler from "../../utils/helpers/SuccessHandler";
16
+
17
+ const Details = () => {
18
+ const dispatch = useDispatch();
19
+ const navigate = useNavigate();
20
+ const { slug } = useParams();
21
+ const routeId = slug;
22
+ const [isEdit, setIsEdit] = useState({ state: false, itemId: null });
23
+ const [initialValues, setInitialValues] = useState({
24
+ property_size: "",
25
+ total_rooms: "",
26
+ additionalRules: [],
27
+ });
28
+ const [newRuleInput, setNewRuleInput] = useState("");
29
+ const [deletedRuleIds, setDeletedRuleIds] = useState([]);
30
+ const [loading, setLoading] = useState(true);
31
+ useEffect(() => {
32
+ if (slug) {
33
+ dispatch(getallupdateProperties({ propertyId: slug }))
34
+ .unwrap()
35
+ .then((response) => {
36
+ if (response) {
37
+ setInitialValues({
38
+ property_size: response.property_size || "",
39
+ total_rooms: response.total_rooms || "",
40
+ additionalRules: response.additional_rules || [],
41
+ });
42
+ }
43
+ });
44
+ }
45
+ }, [dispatch, slug]);
46
+
47
+ const submitHandler = (values, { setSubmitting }) => {
48
+ const dirty =
49
+ values.property_size !== initialValues.property_size ||
50
+ values.total_rooms !== initialValues.total_rooms;
51
+
52
+ const additional_rules_attributes = [
53
+ ...values.additionalRules.map((rule) => ({
54
+ id: String(rule.id).startsWith("temp_") ? undefined : rule.id,
55
+ name: rule.name,
56
+ })),
57
+ ...deletedRuleIds.map((id) => ({ id, _destroy: true })),
58
+ ];
59
+
60
+ const payload = {
61
+ id: slug,
62
+ property_size: values.property_size,
63
+ total_rooms: values.total_rooms,
64
+ additional_rules_attributes,
65
+ };
66
+
67
+ if (dirty || additional_rules_attributes.length) {
68
+ dispatch(updateProperties({ property: payload }))
69
+ .unwrap()
70
+ .then((response) => {
71
+ if (response?.property) {
72
+ successHandler("Updated successfully");
73
+ setDeletedRuleIds([]);
74
+ navigate(`/property-4/${slug}`);
75
+ }
76
+ })
77
+ .catch((error) => {
78
+ console.error("Error in operation:", error);
79
+ })
80
+ .finally(() => setSubmitting(false));
81
+ } else {
82
+ navigate(`/property-4/${slug}`);
83
+ }
84
+ };
85
+
86
+ const handleAddRule = (values, setFieldValue) => {
87
+ if (newRuleInput.trim() === "") return;
88
+
89
+ const newRule = {
90
+ id: `temp_${Date.now()}`,
91
+ name: newRuleInput,
92
+ };
93
+
94
+ if (isEdit.state) {
95
+ const updated = values.additionalRules.map((rule) =>
96
+ rule.id === isEdit.itemId ? { ...rule, name: newRuleInput } : rule
97
+ );
98
+ setFieldValue("additionalRules", updated);
99
+ } else {
100
+ setFieldValue("additionalRules", [...values.additionalRules, newRule]);
101
+ }
102
+
103
+ setNewRuleInput("");
104
+ setIsEdit({ state: false, itemId: null });
105
+ };
106
+
107
+ const handleEditRule = (rule) => {
108
+ setNewRuleInput(rule.name);
109
+ setIsEdit({ state: true, itemId: rule.id });
110
+ };
111
+
112
+ const handleDeleteRule = (id, values, setFieldValue) => {
113
+ const filtered = values.additionalRules.filter((rule) => rule.id !== id);
114
+ if (filtered.length > 0) {
115
+ setFieldValue("additionalRules", filtered);
116
+
117
+ if (!String(id).startsWith("temp_")) {
118
+ setDeletedRuleIds((prev) => [...prev, id]);
119
+ }
120
+
121
+ setIsEdit({ state: false, itemId: null });
122
+ setNewRuleInput("");
123
+ }
124
+ };
125
+
126
+ useEffect(() => {
127
+ setTimeout(() => setLoading(false), 1000);
128
+ }, []);
129
+
130
+ return (
131
+ <CommonLayout currentHref="/property-2" PropertyID={routeId}>
132
+ <div className="description-container">
133
+ <h2 className="title">Property Details</h2>
134
+ <div className="divider" />
135
+ <Formik
136
+ enableReinitialize
137
+ initialValues={initialValues}
138
+ onSubmit={submitHandler}
139
+ >
140
+ {({
141
+ values,
142
+ errors,
143
+ touched,
144
+ handleChange,
145
+ handleBlur,
146
+ setFieldValue,
147
+ isSubmitting,
148
+ handleSubmit,
149
+ }) => (
150
+ <form onSubmit={handleSubmit} className="form-container">
151
+ <FormField
152
+ label="Property Size (sq ft)"
153
+ name="property_size"
154
+ type="number"
155
+ placeholder="Enter property size"
156
+ value={values.property_size}
157
+ onChange={handleChange}
158
+ onBlur={handleBlur}
159
+ error={touched.property_size && errors.property_size}
160
+ />
161
+
162
+ <FormField
163
+ label="Total Rooms"
164
+ name="total_rooms"
165
+ type="number"
166
+ placeholder="Enter total rooms"
167
+ value={values.total_rooms}
168
+ onChange={handleChange}
169
+ onBlur={handleBlur}
170
+ error={touched.total_rooms && errors.total_rooms}
171
+ />
172
+
173
+ <div className="additional-rules">
174
+ <h3>Additional Rules</h3>
175
+ <div className="rules-list">
176
+ {values.additionalRules.map((rule, index) => (
177
+ <div className="rule-item" key={rule.id || index}>
178
+ <span>{rule.name}</span>
179
+ <div className="rule-actions">
180
+ <button
181
+ type="button"
182
+ onClick={() => handleEditRule(rule)}
183
+ >
184
+ ✏️
185
+ </button>
186
+ <button
187
+ type="button"
188
+ onClick={() =>
189
+ handleDeleteRule(rule.id, values, setFieldValue)
190
+ }
191
+ >
192
+ 🗑️
193
+ </button>
194
+ </div>
195
+ </div>
196
+ ))}
197
+ </div>
198
+ <div className="rule-input-group">
199
+ <input
200
+ type="text"
201
+ placeholder="No smoking, no pets..."
202
+ value={newRuleInput}
203
+ onChange={(e) => setNewRuleInput(e.target.value)}
204
+ />
205
+ <ButtonPrimary
206
+ type="button"
207
+ onClick={() => handleAddRule(values, setFieldValue)}
208
+ >
209
+ {isEdit.state ? "Update Rule" : "Add Rule"}
210
+ </ButtonPrimary>
211
+ </div>
212
+ </div>
213
+
214
+ <div className="actions">
215
+ <ButtonPrimary href={`/property-2/${slug}`}>
216
+ Go back
217
+ </ButtonPrimary>
218
+ <ButtonPrimary
219
+ type="submit"
220
+ className="submit-btn"
221
+ disabled={isSubmitting}
222
+ >
223
+ {isSubmitting ? "Saving..." : "Save"}
224
+ </ButtonPrimary>
225
+ </div>
226
+ </form>
227
+ )}
228
+ </Formik>
229
+ </div>
230
+ </CommonLayout>
231
+ );
232
+ };
233
+
234
+ export default Details;
@@ -0,0 +1,196 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { useFormik } from "formik";
3
+ import { useDispatch, useSelector } from "react-redux";
4
+ import * as Yup from "yup";
5
+ import { useNavigate, useParams } from "react-router-dom";
6
+ import {
7
+ uploadImageProperties,
8
+ getallupdateProperties,
9
+ } from "../../redux/slices/PropertySlice/PropertySlice";
10
+ import CommonLayout from "./CommonLayout";
11
+ import "../../styles/images.scss";
12
+ import ButtonPrimary from "../../shared/Button/ButtonPrimary";
13
+ import { useDropzone } from "react-dropzone";
14
+ import successHandler from "../../utils/helpers/SuccessHandler";
15
+
16
+ const validationSchema = Yup.object({
17
+ place_images: Yup.array()
18
+ .min(1, "Please upload at least one image")
19
+ .required("Images are required"),
20
+ });
21
+
22
+ const Images = () => {
23
+ const dispatch = useDispatch();
24
+ const navigate = useNavigate();
25
+ const { slug } = useParams();
26
+ const routeId = slug;
27
+
28
+ const { PropertyToEdit } = useSelector((state) => state.property);
29
+ const [previews, setPreviews] = useState([]);
30
+ const [isEditing, setIsEditing] = useState(false);
31
+
32
+ const initialValues = {
33
+ place_images: PropertyToEdit?.place_images || [],
34
+ };
35
+
36
+ const formik = useFormik({
37
+ initialValues,
38
+ enableReinitialize: true,
39
+ validationSchema,
40
+ onSubmit: async (values, { setSubmitting }) => {
41
+ setSubmitting(true);
42
+
43
+ const formData = new FormData();
44
+ formData.append("property[id]", slug);
45
+
46
+ // Append only new File objects to FormData
47
+ values.place_images.forEach((file) => {
48
+ if (file instanceof File) {
49
+ formData.append("property[place_images][]", file);
50
+ }
51
+ });
52
+
53
+ try {
54
+ const response = await dispatch(
55
+ uploadImageProperties(formData)
56
+ ).unwrap();
57
+ if (response) {
58
+ successHandler("Images uploaded successfully");
59
+ navigate(`/property-3/${slug}`);
60
+ } else {
61
+ console.error("Operation failed:", response);
62
+ }
63
+ } catch (error) {
64
+ console.error("Error uploading images:", error);
65
+ } finally {
66
+ setSubmitting(false);
67
+ }
68
+ },
69
+ });
70
+
71
+ const initialFormValues = JSON.stringify(initialValues);
72
+
73
+ useEffect(() => {
74
+ const currentFormValues = JSON.stringify(formik.values);
75
+ setIsEditing(currentFormValues !== initialFormValues);
76
+ }, [formik.values, initialFormValues]);
77
+
78
+ const onDrop = (acceptedFiles) => {
79
+ const newImages = [...formik.values.place_images, ...acceptedFiles];
80
+ formik.setFieldValue("place_images", newImages);
81
+ setPreviews((prev) => [
82
+ ...prev,
83
+ ...acceptedFiles.map((file) => URL.createObjectURL(file)),
84
+ ]);
85
+ };
86
+
87
+ const handleRemoveImage = (index) => {
88
+ const newImages = [...formik.values.place_images];
89
+ const newPreviews = [...previews];
90
+
91
+ newImages.splice(index, 1);
92
+ newPreviews.splice(index, 1);
93
+
94
+ formik.setFieldValue("place_images", newImages);
95
+ setPreviews(newPreviews);
96
+ };
97
+
98
+ const { getRootProps, getInputProps } = useDropzone({
99
+ accept: { "image/*": [] },
100
+ onDrop,
101
+ });
102
+
103
+ useEffect(() => {
104
+ if (slug) {
105
+ dispatch(getallupdateProperties({ propertyId: slug }));
106
+ }
107
+ }, [dispatch, slug]);
108
+
109
+ useEffect(() => {
110
+ const existingUrls = Array.isArray(PropertyToEdit?.place_images)
111
+ ? PropertyToEdit.place_images.filter((img) => typeof img === "string")
112
+ : [];
113
+
114
+ if (existingUrls.length > 0) {
115
+ setPreviews(existingUrls);
116
+ formik.setFieldValue("place_images", PropertyToEdit.place_images);
117
+ }
118
+ }, [PropertyToEdit]);
119
+
120
+ const isSubmitDisabled =
121
+ formik.isSubmitting || formik.values.place_images.length === 0;
122
+
123
+ return (
124
+ <CommonLayout currentHref="/property-2" PropertyID={routeId}>
125
+ <div className="image-step-container">
126
+ <h2 className="title">Upload Property Images</h2>
127
+ <div className="divider" />
128
+ <form className="image-form" onSubmit={formik.handleSubmit}>
129
+ <div className="form-group">
130
+ <label>Images</label>
131
+ <div {...getRootProps()} className="dropzone">
132
+ <input {...getInputProps()} />
133
+ <div className="dropzone-content">
134
+ <svg
135
+ className="upload-icon"
136
+ stroke="currentColor"
137
+ fill="none"
138
+ viewBox="0 0 48 48"
139
+ aria-hidden="true"
140
+ >
141
+ <path
142
+ 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"
143
+ strokeWidth="2"
144
+ strokeLinecap="round"
145
+ strokeLinejoin="round"
146
+ />
147
+ </svg>
148
+ <p>
149
+ <span className="highlight">Upload multiple files</span> or
150
+ drag and drop
151
+ </p>
152
+ <p className="note">PNG, JPG, GIF up to 10MB</p>
153
+ </div>
154
+ </div>
155
+ {formik.touched.place_images && formik.errors.place_images && (
156
+ <div className="error">{formik.errors.place_images}</div>
157
+ )}
158
+ </div>
159
+
160
+ {previews.length > 0 && (
161
+ <div className="preview-images">
162
+ {previews.map((src, idx) => (
163
+ <div key={idx} className="image-wrapper">
164
+ <img
165
+ src={src}
166
+ alt={`Preview ${idx}`}
167
+ className="preview-img"
168
+ />
169
+ <button
170
+ type="button"
171
+ onClick={() => handleRemoveImage(idx)}
172
+ className="remove-btn"
173
+ >
174
+
175
+ </button>
176
+ </div>
177
+ ))}
178
+ </div>
179
+ )}
180
+ <div className="actions">
181
+ <ButtonPrimary href={`/property-1/${slug}`}>Go back</ButtonPrimary>
182
+ <ButtonPrimary
183
+ type="submit"
184
+ className="submit-btn"
185
+ disabled={isSubmitDisabled}
186
+ >
187
+ {formik.isSubmitting ? "Saving..." : "Save"}
188
+ </ButtonPrimary>
189
+ </div>
190
+ </form>
191
+ </div>
192
+ </CommonLayout>
193
+ );
194
+ };
195
+
196
+ export default Images;