stay_commerce-frontend 0.1.0 → 0.1.1

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/bundle.css +8665 -6069
  3. data/app/assets/builds/bundle.css.map +3 -3
  4. data/app/assets/builds/bundle.js +37877 -27557
  5. data/app/assets/builds/bundle.js.map +4 -4
  6. data/app/assets/builds/styles.css +6328 -5031
  7. data/app/assets/builds/styles.css.map +3 -3
  8. data/app/javascript/react/components/Accountpage/AccountInfo.jsx +2 -1
  9. data/app/javascript/react/components/AddNewProperty/CommonLayout.jsx +0 -2
  10. data/app/javascript/react/components/AddNewProperty/Description.jsx +51 -52
  11. data/app/javascript/react/components/AddNewProperty/Details.jsx +212 -129
  12. data/app/javascript/react/components/AddNewProperty/Images.jsx +122 -45
  13. data/app/javascript/react/components/AddNewProperty/Location.jsx +21 -14
  14. data/app/javascript/react/components/AddNewProperty/Room.jsx +639 -548
  15. data/app/javascript/react/components/AvatarDropdown/AvatarDropDown.jsx +9 -1
  16. data/app/javascript/react/components/FacilitiesSection/Facilities.jsx +18 -0
  17. data/app/javascript/react/components/FixedNavbar/FixedNav.jsx +20 -14
  18. data/app/javascript/react/components/HeroSectionDesign/BookingForm.jsx +136 -88
  19. data/app/javascript/react/components/HeroSectionDesign/MyPropertiesListing.jsx +79 -69
  20. data/app/javascript/react/components/Layout/Layout.js +8 -1
  21. data/app/javascript/react/components/Listing-stay-Detail/ApartmentCard.jsx +3 -3
  22. data/app/javascript/react/components/Listing-stay-Detail/BookingModal.jsx +167 -122
  23. data/app/javascript/react/components/Listing-stay-Detail/CardManager.jsx +285 -0
  24. data/app/javascript/react/components/Listing-stay-Detail/CheckoutForm.jsx +147 -84
  25. data/app/javascript/react/components/Listing-stay-Detail/ListingStayDetailPage.jsx +1 -7
  26. data/app/javascript/react/components/Listing-stay-Detail/PropertiesPage.jsx +464 -0
  27. data/app/javascript/react/components/MobileNav/MobileMenu.jsx +1 -4
  28. data/app/javascript/react/components/PropertyListing/MyProperties.jsx +45 -44
  29. data/app/javascript/react/components/PropertyListing/StayBooking/BookingDetails.jsx +4 -4
  30. data/app/javascript/react/components/PropertyListing/StayBooking/MyBooking.jsx +41 -29
  31. data/app/javascript/react/components/StayCard/StayCard.jsx +5 -3
  32. data/app/javascript/react/packs/index.jsx +1 -0
  33. data/app/javascript/react/packs/routes/Route.jsx +18 -1
  34. data/app/javascript/react/pages/Home.jsx +6 -4
  35. data/app/javascript/react/redux/slices/PropertySlice/PropertySlice.jsx +21 -21
  36. data/app/javascript/react/redux/slices/PropertySlice/Searchslice.jsx +53 -6
  37. data/app/javascript/react/redux/slices/UserSlice/UserSlice.jsx +1 -0
  38. data/app/javascript/react/shared/Avatar/Avatar.jsx +5 -8
  39. data/app/javascript/react/shared/Button/SecondryButton.jsx +9 -0
  40. data/app/javascript/react/shared/Loader.jsx +13 -0
  41. data/app/javascript/react/shared/Pagination.jsx +53 -0
  42. data/app/javascript/react/shared/Schema/FormSchema +143 -0
  43. data/app/javascript/react/styles/BookingDetails.scss +1 -0
  44. data/app/javascript/react/styles/CardManager.scss +608 -0
  45. data/app/javascript/react/styles/Loader.scss +30 -0
  46. data/app/javascript/react/styles/Pagination.scss +33 -0
  47. data/app/javascript/react/styles/PropertiesPage.scss +0 -4
  48. data/app/javascript/react/styles/RenderSection.scss +1 -0
  49. data/app/javascript/react/styles/accountpage.scss +3 -0
  50. data/app/javascript/react/styles/application.scss +13 -1
  51. data/app/javascript/react/styles/bookingform.scss +56 -28
  52. data/app/javascript/react/styles/buttonSecondry.scss +24 -0
  53. data/app/javascript/react/styles/checkbox.scss +34 -35
  54. data/app/javascript/react/styles/commonlayout.scss +7 -2
  55. data/app/javascript/react/styles/commonpage.scss +5 -1
  56. data/app/javascript/react/styles/description.scss +3 -0
  57. data/app/javascript/react/styles/facilities.scss +2 -1
  58. data/app/javascript/react/styles/fixednavbar.scss +8 -0
  59. data/app/javascript/react/styles/listingstaydetailpage.scss +5 -0
  60. data/app/javascript/react/styles/mobilemenu.scss +0 -1
  61. data/app/javascript/react/styles/mybooking.scss +20 -0
  62. data/app/javascript/react/styles/myproperty.scss +26 -0
  63. data/app/javascript/react/styles/propertydetailscard.scss +265 -267
  64. data/app/javascript/react/styles/react-datepicker/react-datepicker.css +869 -0
  65. data/app/javascript/react/styles/room.scss +13 -8
  66. data/app/javascript/react/utils/helpers/ToastErros.js +12 -0
  67. data/db/migrate/20250627101451_add_role_to_stay_users.rb +5 -0
  68. data/lib/stay_commerce/frontend/version.rb +1 -1
  69. metadata +15 -5
  70. data/app/javascript/react/components/HeroSectionDesign/PropertiesPage.jsx +0 -122
  71. data/app/javascript/react/shared/DateField/CustomDatePicker.jsx +0 -69
  72. data/app/javascript/react/styles/customdatepicker.scss +0 -120
@@ -12,12 +12,8 @@ import "../../styles/images.scss";
12
12
  import ButtonPrimary from "../../shared/Button/ButtonPrimary";
13
13
  import { useDropzone } from "react-dropzone";
14
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
- });
15
+ import { ImagevalidationSchema } from "../../shared/Schema/FormSchema";
16
+ import SecondryButton from "../../shared/Button/SecondryButton";
21
17
 
22
18
  const Images = () => {
23
19
  const dispatch = useDispatch();
@@ -28,6 +24,7 @@ const Images = () => {
28
24
  const { PropertyToEdit } = useSelector((state) => state.property);
29
25
  const [previews, setPreviews] = useState([]);
30
26
  const [isEditing, setIsEditing] = useState(false);
27
+ const [initialImageState, setInitialImageState] = useState([]);
31
28
 
32
29
  const initialValues = {
33
30
  place_images: PropertyToEdit?.place_images || [],
@@ -36,29 +33,55 @@ const Images = () => {
36
33
  const formik = useFormik({
37
34
  initialValues,
38
35
  enableReinitialize: true,
39
- validationSchema,
36
+ validationSchema: ImagevalidationSchema,
40
37
  onSubmit: async (values, { setSubmitting }) => {
41
- setSubmitting(true);
38
+ try {
39
+ setSubmitting(true);
42
40
 
43
- const formData = new FormData();
44
- formData.append("property[id]", slug);
41
+ if (slug && isEditing) {
42
+ const formData = new FormData();
43
+ formData.append("property[id]", slug);
45
44
 
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");
45
+ // Append only new File objects to FormData
46
+ let hasNewFiles = false;
47
+ values.place_images.forEach((file) => {
48
+ if (file instanceof File) {
49
+ formData.append("property[place_images][]", file);
50
+ hasNewFiles = true;
51
+ }
52
+ });
53
+ if (hasNewFiles) {
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("Upload failed:", response);
62
+ }
63
+ } else {
64
+ navigate(`/property-3/${slug}`);
65
+ }
66
+ } else if (slug && !isEditing) {
59
67
  navigate(`/property-3/${slug}`);
60
68
  } else {
61
- console.error("Operation failed:", response);
69
+ const formData = new FormData();
70
+ values.place_images.forEach((file) => {
71
+ if (file instanceof File) {
72
+ formData.append("property[place_images][]", file);
73
+ }
74
+ });
75
+
76
+ const response = await dispatch(
77
+ uploadImageProperties(formData)
78
+ ).unwrap();
79
+ if (response) {
80
+ successHandler("Images uploaded successfully");
81
+ navigate(`/property-3/${response?.property?.slug || slug}`);
82
+ } else {
83
+ console.error("Create failed:", response);
84
+ }
62
85
  }
63
86
  } catch (error) {
64
87
  console.error("Error uploading images:", error);
@@ -68,29 +91,53 @@ const Images = () => {
68
91
  },
69
92
  });
70
93
 
71
- const initialFormValues = JSON.stringify(initialValues);
72
-
94
+ // Track changes in images
73
95
  useEffect(() => {
74
- const currentFormValues = JSON.stringify(formik.values);
75
- setIsEditing(currentFormValues !== initialFormValues);
76
- }, [formik.values, initialFormValues]);
96
+ const currentImages = formik.values.place_images || [];
97
+ const hasChanges = !arraysEqual(currentImages, initialImageState);
98
+ setIsEditing(hasChanges);
99
+ }, [formik.values.place_images, initialImageState]);
100
+
101
+ // Helper function to compare arrays (considering File objects and URLs)
102
+ const arraysEqual = (a, b) => {
103
+ if (a.length !== b.length) return false;
104
+
105
+ for (let i = 0; i < a.length; i++) {
106
+ const itemA = a[i];
107
+ const itemB = b[i];
108
+
109
+ // Compare File objects by name and size
110
+ if (itemA instanceof File && itemB instanceof File) {
111
+ if (itemA.name !== itemB.name || itemA.size !== itemB.size) {
112
+ return false;
113
+ }
114
+ } else if (typeof itemA === "string" && typeof itemB === "string") {
115
+ // Compare URLs
116
+ if (itemA !== itemB) return false;
117
+ } else {
118
+ // Different types
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ };
77
124
 
78
125
  const onDrop = (acceptedFiles) => {
79
126
  const newImages = [...formik.values.place_images, ...acceptedFiles];
80
127
  formik.setFieldValue("place_images", newImages);
81
- setPreviews((prev) => [
82
- ...prev,
83
- ...acceptedFiles.map((file) => URL.createObjectURL(file)),
84
- ]);
128
+
129
+ const newPreviews = acceptedFiles.map((file) => URL.createObjectURL(file));
130
+ setPreviews((prev) => [...prev, ...newPreviews]);
85
131
  };
86
132
 
87
133
  const handleRemoveImage = (index) => {
88
134
  const newImages = [...formik.values.place_images];
89
135
  const newPreviews = [...previews];
90
-
136
+ if (newPreviews[index] && newPreviews[index].startsWith("blob:")) {
137
+ URL.revokeObjectURL(newPreviews[index]);
138
+ }
91
139
  newImages.splice(index, 1);
92
140
  newPreviews.splice(index, 1);
93
-
94
141
  formik.setFieldValue("place_images", newImages);
95
142
  setPreviews(newPreviews);
96
143
  };
@@ -107,25 +154,53 @@ const Images = () => {
107
154
  }, [dispatch, slug]);
108
155
 
109
156
  useEffect(() => {
110
- const existingUrls = Array.isArray(PropertyToEdit?.place_images)
111
- ? PropertyToEdit.place_images.filter((img) => typeof img === "string")
157
+ const existingImages = PropertyToEdit?.place_images || [];
158
+ const existingUrls = Array.isArray(existingImages)
159
+ ? existingImages.filter((img) => typeof img === "string")
112
160
  : [];
113
-
114
- if (existingUrls.length > 0) {
161
+ if (existingImages.length > 0) {
115
162
  setPreviews(existingUrls);
116
- formik.setFieldValue("place_images", PropertyToEdit.place_images);
163
+ formik.setFieldValue("place_images", existingImages);
164
+ setInitialImageState([...existingImages]); // Set initial state for comparison
165
+ } else {
166
+ // Reset states if no existing images
167
+ setPreviews([]);
168
+ formik.setFieldValue("place_images", []);
169
+ setInitialImageState([]);
117
170
  }
118
171
  }, [PropertyToEdit]);
119
172
 
120
- const isSubmitDisabled =
121
- formik.isSubmitting || formik.values.place_images.length === 0;
173
+ // Handle form submission with validation
174
+ const handleSubmit = (e) => {
175
+ e.preventDefault();
176
+ formik.setTouched({
177
+ place_images: true,
178
+ });
179
+
180
+ if (formik.isValid) {
181
+ formik.handleSubmit(e);
182
+ } else {
183
+ console.log("Form validation failed, not submitting");
184
+ }
185
+ };
186
+
187
+ // Clean up blob URLs on unmount
188
+ useEffect(() => {
189
+ return () => {
190
+ previews.forEach((preview) => {
191
+ if (preview && preview.startsWith("blob:")) {
192
+ URL.revokeObjectURL(preview);
193
+ }
194
+ });
195
+ };
196
+ }, []);
122
197
 
123
198
  return (
124
199
  <CommonLayout currentHref="/property-2" PropertyID={routeId}>
125
200
  <div className="image-step-container">
126
201
  <h2 className="title">Upload Property Images</h2>
127
202
  <div className="divider" />
128
- <form className="image-form" onSubmit={formik.handleSubmit}>
203
+ <form className="image-form" onSubmit={handleSubmit}>
129
204
  <div className="form-group">
130
205
  <label>Images</label>
131
206
  <div {...getRootProps()} className="dropzone">
@@ -178,11 +253,13 @@ const Images = () => {
178
253
  </div>
179
254
  )}
180
255
  <div className="actions">
181
- <ButtonPrimary href={`/property-1/${slug}`}>Go back</ButtonPrimary>
256
+ <SecondryButton href={`/property-1/${slug}`}>
257
+ Go back
258
+ </SecondryButton>
182
259
  <ButtonPrimary
183
260
  type="submit"
184
261
  className="submit-btn"
185
- disabled={isSubmitDisabled}
262
+ disabled={formik.isSubmitting}
186
263
  >
187
264
  {formik.isSubmitting ? "Saving..." : "Save"}
188
265
  </ButtonPrimary>
@@ -14,6 +14,8 @@ import FormField from "../../shared/FormField/FormField";
14
14
  import ButtonPrimary from "../../shared/Button/ButtonPrimary";
15
15
  import "../../styles/location.scss";
16
16
  import successHandler from "../../utils/helpers/SuccessHandler";
17
+ import { LocationSchema } from "../../shared/Schema/FormSchema";
18
+ import SecondryButton from "../../shared/Button/SecondryButton";
17
19
 
18
20
  const Location = () => {
19
21
  const dispatch = useDispatch();
@@ -57,15 +59,23 @@ const Location = () => {
57
59
  latitude: PropertyToEdit?.latitude || "",
58
60
  longitude: PropertyToEdit?.longitude || "",
59
61
  },
60
- validate: (values) => {
61
- const errors = {};
62
- if (!values.city) errors.city = "City is required";
63
- if (!values.state) errors.state = "State is required";
64
- if (!values.zipcode) errors.zipcode = "Zipcode is required";
65
- return errors;
66
- },
62
+ validationSchema: LocationSchema,
67
63
  onSubmit: async (values) => {
64
+ const hasChanges = [
65
+ "city",
66
+ "state",
67
+ "zipcode",
68
+ "latitude",
69
+ "longitude",
70
+ ].some((key) => values[key] !== PropertyToEdit?.[key]);
71
+ if (!hasChanges) {
72
+ navigate(`/property-5/${slug}`);
73
+ return;
74
+ }
75
+
68
76
  try {
77
+ setSubmitting(true);
78
+
69
79
  const payload = {
70
80
  id: slug,
71
81
  city: values.city,
@@ -75,8 +85,6 @@ const Location = () => {
75
85
  longitude: values.longitude,
76
86
  };
77
87
 
78
- formik.setSubmitting(true);
79
-
80
88
  const response = await dispatch(
81
89
  updateProperties({ property: payload })
82
90
  ).unwrap();
@@ -85,14 +93,13 @@ const Location = () => {
85
93
  successHandler("Location updated successfully");
86
94
  navigate(`/property-5/${slug}`);
87
95
  } else {
88
- console.error("Update failed:", response);
89
96
  alert("Failed to update property. Please try again.");
90
97
  }
91
98
  } catch (error) {
92
99
  console.error("Error updating property:", error);
93
100
  alert("An error occurred while updating the property.");
94
101
  } finally {
95
- formik.setSubmitting(false);
102
+ setSubmitting(false);
96
103
  }
97
104
  },
98
105
  });
@@ -222,11 +229,11 @@ const Location = () => {
222
229
  </div>
223
230
 
224
231
  <div className="action-buttons">
225
- <ButtonPrimary href={`/property-3/${slug}`}>
232
+ <SecondryButton href={`/property-3/${slug}`}>
226
233
  Go back
227
- </ButtonPrimary>
234
+ </SecondryButton>
228
235
  <ButtonPrimary type="submit" disabled={isSubmitting}>
229
- {isSubmitting ? "Loading..." : "Continue"}
236
+ {isSubmitting ? "Saving..." : "Save"}
230
237
  </ButtonPrimary>
231
238
  </div>
232
239
  </div>