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,398 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { X } from "lucide-react";
3
+ import moment from "moment";
4
+ import { useFormik } from "formik";
5
+ import ButtonPrimary from "../../shared/Button/ButtonPrimary";
6
+ import { currencySymbol } from "../../shared/CurrencySymbol";
7
+ import Badge from "../../shared/Badge/Badge";
8
+
9
+ const BookingModal = ({
10
+ isOpen,
11
+ onClose,
12
+ room,
13
+ propertyData,
14
+ onSubmit,
15
+ isLoading,
16
+ }) => {
17
+ const [error, setError] = useState("");
18
+ const [isButtonDisabled, setIsButtonDisabled] = useState(true);
19
+
20
+ const formik = useFormik({
21
+ initialValues: {
22
+ number_of_guests: null,
23
+ booking_start: null,
24
+ booking_end: null,
25
+ room_id: room?.id || null,
26
+ price_per_month: room?.price_per_month || null,
27
+ },
28
+ enableReinitialize: true,
29
+ onSubmit: (values) => {
30
+ onSubmit(values);
31
+ },
32
+ });
33
+
34
+ useEffect(() => {
35
+ const { booking_start, booking_end, number_of_guests } = formik.values;
36
+ if (booking_start && booking_end && number_of_guests) {
37
+ setIsButtonDisabled(false);
38
+ } else {
39
+ setIsButtonDisabled(true);
40
+ }
41
+ }, [formik.values]);
42
+
43
+ const handleBackdropClick = (e) => {
44
+ if (e.target === e.currentTarget) {
45
+ onClose();
46
+ }
47
+ };
48
+
49
+ if (!isOpen || !room) return null;
50
+
51
+ return (
52
+ <div
53
+ className="modal-overlay"
54
+ onClick={handleBackdropClick}
55
+ style={{
56
+ position: "fixed",
57
+ top: 0,
58
+ left: 0,
59
+ right: 0,
60
+ bottom: 0,
61
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
62
+ display: "flex",
63
+ alignItems: "center",
64
+ justifyContent: "center",
65
+ zIndex: 1000,
66
+ padding: "20px",
67
+ }}
68
+ >
69
+ <div
70
+ className="modal-content"
71
+ style={{
72
+ backgroundColor: "white",
73
+ borderRadius: "12px",
74
+ padding: "24px",
75
+ maxWidth: "500px",
76
+ width: "100%",
77
+ maxHeight: "90vh",
78
+ overflowY: "auto",
79
+ position: "relative",
80
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
81
+ }}
82
+ >
83
+ {/* Close Button */}
84
+ <button
85
+ onClick={onClose}
86
+ style={{
87
+ position: "absolute",
88
+ top: "16px",
89
+ right: "16px",
90
+ background: "none",
91
+ border: "none",
92
+ cursor: "pointer",
93
+ padding: "8px",
94
+ borderRadius: "50%",
95
+ display: "flex",
96
+ alignItems: "center",
97
+ justifyContent: "center",
98
+ }}
99
+ >
100
+ <X size={20} />
101
+ </button>
102
+ <div style={{ marginBottom: "24px", paddingRight: "40px" }}>
103
+ <h2 style={{ margin: 0, fontSize: "24px", fontWeight: "600" }}>
104
+ {room?.name}
105
+ </h2>
106
+ </div>
107
+
108
+ <div className="listingSectionSidebar__wrap">
109
+ {room?.status === "inactive" && (
110
+ <Badge
111
+ name={room?.status === "inactive" ? "Room is Inactive" : ""}
112
+ />
113
+ )}
114
+
115
+ <div className="price-parents" style={{ marginBottom: "24px" }}>
116
+ <span
117
+ className="price-header"
118
+ style={{ fontSize: "28px", fontWeight: "600" }}
119
+ >
120
+ {`${currencySymbol()} ${
121
+ Number(room?.price_per_month)?.toFixed(2) || 0
122
+ }`}
123
+ <span
124
+ className="price-period"
125
+ style={{
126
+ fontSize: "16px",
127
+ fontWeight: "400",
128
+ color: "#6b7280",
129
+ }}
130
+ >
131
+ /month
132
+ </span>
133
+ </span>
134
+ </div>
135
+
136
+ <form onSubmit={formik.handleSubmit}>
137
+ <div
138
+ className="form-content"
139
+ style={{ display: "flex", flexDirection: "column", gap: "10px" }}
140
+ >
141
+ <div className="form-group">
142
+ <label
143
+ htmlFor={`booking_start_${room?.id}`}
144
+ style={{
145
+ display: "block",
146
+ marginBottom: "8px",
147
+ fontWeight: "500",
148
+ }}
149
+ >
150
+ Availability Start Date
151
+ </label>
152
+ <input
153
+ id={`booking_start_${room?.id}`}
154
+ type="date"
155
+ className="form-input-large"
156
+ disabled={room?.to_book === false}
157
+ value={
158
+ formik?.values?.booking_start
159
+ ? moment(formik?.values?.booking_start).format(
160
+ "YYYY-MM-DD"
161
+ )
162
+ : ""
163
+ }
164
+ onChange={(e) => {
165
+ setError("");
166
+ formik.setFieldValue("booking_start", e.target.value);
167
+ formik.setFieldValue("booking_end", null);
168
+ formik.setFieldValue("number_of_guests", null);
169
+ formik.setFieldValue("room_id", room?.id);
170
+ formik.setFieldValue(
171
+ "price_per_month",
172
+ room?.price_per_month
173
+ );
174
+ }}
175
+ min={
176
+ room?.to_book === true
177
+ ? typeof room?.next_available_date === "string"
178
+ ? moment(room?.next_available_date).format("YYYY-MM-DD")
179
+ : moment(
180
+ moment(
181
+ room?.booking_start ??
182
+ propertyData?.availability_start
183
+ ).isAfter(moment())
184
+ ? room?.booking_start ??
185
+ propertyData?.availability_start
186
+ : new Date()
187
+ ).format("YYYY-MM-DD")
188
+ : moment(
189
+ moment(
190
+ room?.booking_start ??
191
+ propertyData?.availability_start
192
+ ).isAfter(moment())
193
+ ? room?.booking_start ??
194
+ propertyData?.availability_start
195
+ : new Date()
196
+ ).format("YYYY-MM-DD")
197
+ }
198
+ max={
199
+ room?.booking_end
200
+ ? moment(room?.booking_end).format("YYYY-MM-DD")
201
+ : propertyData?.availability_end
202
+ ? moment(propertyData?.availability_end).format(
203
+ "YYYY-MM-DD"
204
+ )
205
+ : undefined
206
+ }
207
+ style={{
208
+ width: "100%",
209
+ padding: "12px",
210
+ border: "1px solid #d1d5db",
211
+ borderRadius: "8px",
212
+ fontSize: "16px",
213
+ }}
214
+ />
215
+ </div>
216
+
217
+ <div className="form-group">
218
+ <label
219
+ htmlFor={`booking_end_${room?.id}`}
220
+ style={{
221
+ display: "block",
222
+ marginBottom: "8px",
223
+ fontWeight: "500",
224
+ }}
225
+ >
226
+ Availability End Date
227
+ </label>
228
+ <input
229
+ type="date"
230
+ id={`booking_end_${room?.id}`}
231
+ className="form-input-large"
232
+ disabled={room?.to_book === false}
233
+ value={
234
+ formik?.values?.booking_end
235
+ ? moment(formik?.values?.booking_end).format("YYYY-MM-DD")
236
+ : ""
237
+ }
238
+ onChange={(e) => {
239
+ setError("");
240
+ formik.setFieldValue("room_id", room?.id);
241
+ formik.setFieldValue(
242
+ "price_per_month",
243
+ room?.price_per_month
244
+ );
245
+ formik.setFieldValue("booking_end", e.target.value);
246
+ }}
247
+ min={
248
+ formik?.values?.booking_start
249
+ ? moment(formik?.values?.booking_start)
250
+ .add(1, "day")
251
+ .format("YYYY-MM-DD")
252
+ : room?.to_book === true
253
+ ? typeof room.next_available_date === "string"
254
+ ? moment(room.next_available_date).format("YYYY-MM-DD")
255
+ : moment(
256
+ moment(
257
+ room?.booking_start ??
258
+ propertyData?.availability_start
259
+ ).isAfter(moment())
260
+ ? room?.booking_start ??
261
+ propertyData?.availability_start
262
+ : new Date()
263
+ ).format("YYYY-MM-DD")
264
+ : moment(
265
+ moment(
266
+ room?.booking_start ??
267
+ propertyData?.availability_start
268
+ ).isAfter(moment())
269
+ ? room?.booking_start ??
270
+ propertyData?.availability_start
271
+ : new Date()
272
+ ).format("YYYY-MM-DD")
273
+ }
274
+ max={
275
+ room?.booking_end
276
+ ? moment(room?.booking_end).format("YYYY-MM-DD")
277
+ : propertyData?.availability_end
278
+ ? moment(propertyData?.availability_end).format(
279
+ "YYYY-MM-DD"
280
+ )
281
+ : undefined
282
+ }
283
+ style={{
284
+ width: "100%",
285
+ padding: "12px",
286
+ border: "1px solid #d1d5db",
287
+ borderRadius: "8px",
288
+ fontSize: "16px",
289
+ }}
290
+ />
291
+ </div>
292
+
293
+ {error && (
294
+ <span
295
+ className="error-text"
296
+ style={{ color: "#ef4444", fontSize: "14px" }}
297
+ >
298
+ {error}
299
+ </span>
300
+ )}
301
+
302
+ <div className="form-group">
303
+ <label
304
+ htmlFor={`number_of_guests_${room?.id}`}
305
+ style={{
306
+ display: "block",
307
+ marginBottom: "8px",
308
+ fontWeight: "500",
309
+ }}
310
+ >
311
+ Number of Guests
312
+ </label>
313
+ <select
314
+ id={`number_of_guests_${room?.id}`}
315
+ name="number_of_guests"
316
+ className="form-input-large"
317
+ onChange={(e) => {
318
+ formik.setFieldValue("number_of_guests", e.target.value);
319
+ }}
320
+ value={formik.values.number_of_guests || ""}
321
+ disabled={
322
+ !formik.values.booking_start ||
323
+ !formik.values.booking_end ||
324
+ room?.to_book === false
325
+ }
326
+ style={{
327
+ width: "100%",
328
+ padding: "12px",
329
+ border: "1px solid #d1d5db",
330
+ borderRadius: "8px",
331
+ fontSize: "16px",
332
+ backgroundColor: "white",
333
+ }}
334
+ >
335
+ <option value="" disabled>
336
+ Select number of guests
337
+ </option>
338
+ {Array.from({ length: room?.max_guests || 1 }, (_, i) => (
339
+ <option key={i + 1} value={i + 1}>
340
+ {i + 1}
341
+ </option>
342
+ ))}
343
+ </select>
344
+ </div>
345
+ </div>
346
+ </form>
347
+
348
+ <div
349
+ className="price-breakdown"
350
+ style={{
351
+ marginTop: "24px",
352
+ paddingTop: "15px",
353
+ borderTop: "1px solid #e5e7eb",
354
+ }}
355
+ >
356
+ <div
357
+ className="price-row"
358
+ style={{
359
+ display: "flex",
360
+ justifyContent: "space-between",
361
+ alignItems: "center",
362
+ }}
363
+ >
364
+ <div className="info-icon">
365
+ <span style={{ fontWeight: "500" }}>Amount</span>
366
+ </div>
367
+ <span style={{ fontWeight: "600", fontSize: "18px" }}>
368
+ {`${currencySymbol()} ${
369
+ Number(room?.price_per_month)?.toFixed(2) || 0
370
+ }`}
371
+ </span>
372
+ </div>
373
+ </div>
374
+
375
+ <div
376
+ className="button-group"
377
+ style={{ marginTop: "24px", width: "100%" }}
378
+ >
379
+ <ButtonPrimary
380
+ disabled={
381
+ isButtonDisabled ||
382
+ room?.status === "inactive" ||
383
+ room?.to_book === false
384
+ }
385
+ onClick={formik.handleSubmit}
386
+ loading={isLoading}
387
+ className="book-now-btn"
388
+ >
389
+ Book Now
390
+ </ButtonPrimary>
391
+ </div>
392
+ </div>
393
+ </div>
394
+ </div>
395
+ );
396
+ };
397
+
398
+ export default BookingModal;
@@ -0,0 +1,296 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { loadStripe } from "@stripe/stripe-js";
4
+ import {
5
+ Elements,
6
+ CardElement,
7
+ useStripe,
8
+ useElements,
9
+ } from "@stripe/react-stripe-js";
10
+ import {
11
+ getCard,
12
+ deleteCard as deleteCardThunk,
13
+ saveCard,
14
+ createPayment,
15
+ confirmBooking,
16
+ getPaymentmethods,
17
+ } from "../../redux/slices/PropertySlice/PropertySlice";
18
+ import { toast } from "react-toastify";
19
+ import successHandler from "../../utils/helpers/SuccessHandler";
20
+ import { useNavigate } from "react-router-dom";
21
+
22
+ const stripePromise = loadStripe(
23
+ "pk_test_51LCfAAJQIRLPDLbLcKwKmAbKIHjTiJYCUsWXxfbQ0UlF5N3AO2tfLgKXt4GPI7UZYJUVHi94Q4TEol5YN1PQD4AI00N1uBrnnR"
24
+ );
25
+
26
+ const CheckoutForm = ({ bookingId, totalAmount }) => {
27
+ const stripe = useStripe();
28
+ const navigate = useNavigate();
29
+ const elements = useElements();
30
+ const dispatch = useDispatch();
31
+ const [message, setMessage] = useState("");
32
+ const [loading, setLoading] = useState(false);
33
+ const [cardExists, setCardExists] = useState(false);
34
+ const [paymentMethod, setPaymentMethod] = useState(null);
35
+ const [cardId, setCardId] = useState(null);
36
+ const [paymentMethodIds, setPaymentMethodIds] = useState([]);
37
+
38
+ useEffect(() => {
39
+ dispatch(getCard())
40
+ .unwrap()
41
+ .then((data) => {
42
+ if (data?.payment_method_token) {
43
+ setCardExists(true);
44
+ setCardId(data.id);
45
+ setPaymentMethod({
46
+ id: data.payment_method_token,
47
+ last4: data.cc_number?.slice(-4) || "****",
48
+ });
49
+ } else {
50
+ setCardExists(false);
51
+ setPaymentMethod(null);
52
+ }
53
+ })
54
+ .catch((err) => {
55
+ console.error("Failed to get card:", err);
56
+ toast.error("❌ Could not retrieve card");
57
+ setCardExists(false);
58
+ setPaymentMethod(null);
59
+ });
60
+ }, [dispatch]);
61
+
62
+ useEffect(() => {
63
+ const fetchPaymentMethods = async () => {
64
+ try {
65
+ const data = await dispatch(getPaymentmethods()).unwrap();
66
+ const ids = data.map((method) => method.id);
67
+ setPaymentMethodIds(ids);
68
+ } catch (error) {
69
+ console.error("Failed to fetch payment methods:", error);
70
+ }
71
+ };
72
+
73
+ fetchPaymentMethods();
74
+ }, [dispatch]);
75
+
76
+ const handleDeleteCard = async () => {
77
+ try {
78
+ await dispatch(deleteCardThunk(cardId)).unwrap();
79
+ setCardExists(false);
80
+ setPaymentMethod(null);
81
+ successHandler("Card deleted successfully");
82
+ } catch (error) {
83
+ toast.error(`❌ ${error.message}`);
84
+ }
85
+ };
86
+
87
+ const handleSubmit = async (e) => {
88
+ e.preventDefault();
89
+
90
+ if (!stripe || !elements) {
91
+ toast.error("❌ Stripe not ready");
92
+ return;
93
+ }
94
+ setLoading(true);
95
+ try {
96
+ if (!cardExists) {
97
+ const cardElement = elements.getElement(CardElement);
98
+ if (!cardElement) throw new Error("Card element not found");
99
+
100
+ const { token, error } = await stripe.createToken(cardElement);
101
+ if (error) throw new Error(error.message);
102
+ const cardData = {
103
+ cc_number: token?.card?.last4 || "****",
104
+ month: token?.card?.exp_month,
105
+ year: token?.card?.exp_year,
106
+ card_token: token?.id,
107
+ };
108
+
109
+ const saved = await dispatch(saveCard(cardData)).unwrap();
110
+
111
+ setPaymentMethod({
112
+ id: saved.payment_method_token,
113
+ last4: saved.cc_number?.slice(-4) || "****",
114
+ });
115
+ setCardExists(true);
116
+ setCardId(saved.id);
117
+
118
+ toast.success("Card added successfully");
119
+ setLoading(false);
120
+ return;
121
+ }
122
+ if (!paymentMethod?.id) {
123
+ toast.error("Please add your card.");
124
+ setLoading(false);
125
+ return;
126
+ }
127
+
128
+ await dispatch(
129
+ createPayment({
130
+ bookingId,
131
+ paymentMethodId: paymentMethod?.id,
132
+ })
133
+ ).unwrap();
134
+
135
+ await dispatch(
136
+ confirmBooking({
137
+ bookingId,
138
+ paymentMethodId: paymentMethodIds[0],
139
+ amount: totalAmount,
140
+ })
141
+ ).unwrap();
142
+
143
+ successHandler("🎉 Booking confirmed!");
144
+ navigate(`/bookingDetails/${bookingId}`);
145
+ } catch (error) {
146
+ console.error("Payment error:", error);
147
+ toast.error(`❌ ${error.message || "Payment failed"}`);
148
+ }
149
+
150
+ setLoading(false);
151
+ };
152
+
153
+ return (
154
+ <form onSubmit={handleSubmit} style={{ marginTop: 20 }}>
155
+ {!cardExists && (
156
+ <div
157
+ style={{
158
+ padding: 10,
159
+ border: "1px solid #ccc",
160
+ borderRadius: 6,
161
+ background: "#f8f9fa",
162
+ marginBottom: 20,
163
+ }}
164
+ >
165
+ <CardElement
166
+ options={{
167
+ style: {
168
+ base: {
169
+ fontSize: "16px",
170
+ color: "#424770",
171
+ "::placeholder": { color: "#aab7c4" },
172
+ },
173
+ invalid: { color: "#9e2146" },
174
+ },
175
+ }}
176
+ />
177
+ </div>
178
+ )}
179
+
180
+ {cardExists && paymentMethod && (
181
+ <div
182
+ style={{
183
+ padding: 15,
184
+ border: "1px solid #28a745",
185
+ borderRadius: 6,
186
+ backgroundColor: "#d4edda",
187
+ marginBottom: 20,
188
+ color: "#155724",
189
+ position: "relative",
190
+ }}
191
+ >
192
+ ✅ Card on file: ****{paymentMethod.last4}
193
+ <button
194
+ type="button"
195
+ onClick={handleDeleteCard}
196
+ style={{
197
+ position: "absolute",
198
+ right: 10,
199
+ top: 10,
200
+ background: "none",
201
+ border: "none",
202
+ color: "#c82333",
203
+ cursor: "pointer",
204
+ fontWeight: "bold",
205
+ fontSize: 18,
206
+ }}
207
+ >
208
+
209
+ </button>
210
+ </div>
211
+ )}
212
+
213
+ <button
214
+ type="submit"
215
+ disabled={!stripe || loading}
216
+ style={{
217
+ width: "100%",
218
+ padding: "12px 20px",
219
+ backgroundColor:
220
+ !stripe || loading ? "#ccc" : cardExists ? "#28a745" : "#ffcc00",
221
+ color: cardExists ? "#fff" : "#000",
222
+ border: "none",
223
+ borderRadius: 5,
224
+ cursor: loading ? "not-allowed" : "pointer",
225
+ fontSize: 16,
226
+ fontWeight: 600,
227
+ }}
228
+ >
229
+ {loading
230
+ ? cardExists
231
+ ? "Processing Payment..."
232
+ : "Saving Card..."
233
+ : cardExists
234
+ ? `Pay $${(totalAmount / 100).toFixed(2)}`
235
+ : "Add Card"}
236
+ </button>
237
+
238
+ {message && (
239
+ <p
240
+ style={{
241
+ marginTop: 15,
242
+ padding: 10,
243
+ borderRadius: 5,
244
+ backgroundColor: message.includes("❌") ? "#f8d7da" : "#d1ecf1",
245
+ color: message.includes("❌") ? "#721c24" : "#0c5460",
246
+ border: `1px solid ${
247
+ message.includes("❌") ? "#f5c6cb" : "#bee5eb"
248
+ }`,
249
+ textAlign: "center",
250
+ }}
251
+ >
252
+ {message}
253
+ </p>
254
+ )}
255
+ </form>
256
+ );
257
+ };
258
+
259
+ const CheckOutPagePageMain = ({ bookingId, totalAmount }) => {
260
+ return (
261
+ <div
262
+ style={{
263
+ padding: "10px",
264
+ borderRadius: "12px",
265
+ backgroundColor: "#fff",
266
+ }}
267
+ >
268
+ <h2
269
+ style={{
270
+ fontSize: "28px",
271
+ fontWeight: "700",
272
+ marginBottom: "10px",
273
+ textAlign: "center",
274
+ color: "#333",
275
+ }}
276
+ >
277
+ Confirm Your Payment
278
+ </h2>
279
+ <p
280
+ style={{
281
+ textAlign: "center",
282
+ color: "#666",
283
+ marginBottom: "25px",
284
+ fontSize: "16px",
285
+ }}
286
+ >
287
+ Booking ID: {bookingId}
288
+ </p>
289
+ <Elements stripe={stripePromise}>
290
+ <CheckoutForm bookingId={bookingId} totalAmount={totalAmount} />
291
+ </Elements>
292
+ </div>
293
+ );
294
+ };
295
+
296
+ export default CheckOutPagePageMain;