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.
- checksums.yaml +4 -4
- data/app/assets/builds/bundle.css +8665 -6069
- data/app/assets/builds/bundle.css.map +3 -3
- data/app/assets/builds/bundle.js +37877 -27557
- data/app/assets/builds/bundle.js.map +4 -4
- data/app/assets/builds/styles.css +6328 -5031
- data/app/assets/builds/styles.css.map +3 -3
- data/app/javascript/react/components/Accountpage/AccountInfo.jsx +2 -1
- data/app/javascript/react/components/AddNewProperty/CommonLayout.jsx +0 -2
- data/app/javascript/react/components/AddNewProperty/Description.jsx +51 -52
- data/app/javascript/react/components/AddNewProperty/Details.jsx +212 -129
- data/app/javascript/react/components/AddNewProperty/Images.jsx +122 -45
- data/app/javascript/react/components/AddNewProperty/Location.jsx +21 -14
- data/app/javascript/react/components/AddNewProperty/Room.jsx +639 -548
- data/app/javascript/react/components/AvatarDropdown/AvatarDropDown.jsx +9 -1
- data/app/javascript/react/components/FacilitiesSection/Facilities.jsx +18 -0
- data/app/javascript/react/components/FixedNavbar/FixedNav.jsx +20 -14
- data/app/javascript/react/components/HeroSectionDesign/BookingForm.jsx +136 -88
- data/app/javascript/react/components/HeroSectionDesign/MyPropertiesListing.jsx +79 -69
- data/app/javascript/react/components/Layout/Layout.js +8 -1
- data/app/javascript/react/components/Listing-stay-Detail/ApartmentCard.jsx +3 -3
- data/app/javascript/react/components/Listing-stay-Detail/BookingModal.jsx +167 -122
- data/app/javascript/react/components/Listing-stay-Detail/CardManager.jsx +285 -0
- data/app/javascript/react/components/Listing-stay-Detail/CheckoutForm.jsx +147 -84
- data/app/javascript/react/components/Listing-stay-Detail/ListingStayDetailPage.jsx +1 -7
- data/app/javascript/react/components/Listing-stay-Detail/PropertiesPage.jsx +464 -0
- data/app/javascript/react/components/MobileNav/MobileMenu.jsx +1 -4
- data/app/javascript/react/components/PropertyListing/MyProperties.jsx +45 -44
- data/app/javascript/react/components/PropertyListing/StayBooking/BookingDetails.jsx +4 -4
- data/app/javascript/react/components/PropertyListing/StayBooking/MyBooking.jsx +41 -29
- data/app/javascript/react/components/StayCard/StayCard.jsx +5 -3
- data/app/javascript/react/packs/index.jsx +1 -0
- data/app/javascript/react/packs/routes/Route.jsx +18 -1
- data/app/javascript/react/pages/Home.jsx +6 -4
- data/app/javascript/react/redux/slices/PropertySlice/PropertySlice.jsx +21 -21
- data/app/javascript/react/redux/slices/PropertySlice/Searchslice.jsx +53 -6
- data/app/javascript/react/redux/slices/UserSlice/UserSlice.jsx +1 -0
- data/app/javascript/react/shared/Avatar/Avatar.jsx +5 -8
- data/app/javascript/react/shared/Button/SecondryButton.jsx +9 -0
- data/app/javascript/react/shared/Loader.jsx +13 -0
- data/app/javascript/react/shared/Pagination.jsx +53 -0
- data/app/javascript/react/shared/Schema/FormSchema +143 -0
- data/app/javascript/react/styles/BookingDetails.scss +1 -0
- data/app/javascript/react/styles/CardManager.scss +608 -0
- data/app/javascript/react/styles/Loader.scss +30 -0
- data/app/javascript/react/styles/Pagination.scss +33 -0
- data/app/javascript/react/styles/PropertiesPage.scss +0 -4
- data/app/javascript/react/styles/RenderSection.scss +1 -0
- data/app/javascript/react/styles/accountpage.scss +3 -0
- data/app/javascript/react/styles/application.scss +13 -1
- data/app/javascript/react/styles/bookingform.scss +56 -28
- data/app/javascript/react/styles/buttonSecondry.scss +24 -0
- data/app/javascript/react/styles/checkbox.scss +34 -35
- data/app/javascript/react/styles/commonlayout.scss +7 -2
- data/app/javascript/react/styles/commonpage.scss +5 -1
- data/app/javascript/react/styles/description.scss +3 -0
- data/app/javascript/react/styles/facilities.scss +2 -1
- data/app/javascript/react/styles/fixednavbar.scss +8 -0
- data/app/javascript/react/styles/listingstaydetailpage.scss +5 -0
- data/app/javascript/react/styles/mobilemenu.scss +0 -1
- data/app/javascript/react/styles/mybooking.scss +20 -0
- data/app/javascript/react/styles/myproperty.scss +26 -0
- data/app/javascript/react/styles/propertydetailscard.scss +265 -267
- data/app/javascript/react/styles/react-datepicker/react-datepicker.css +869 -0
- data/app/javascript/react/styles/room.scss +13 -8
- data/app/javascript/react/utils/helpers/ToastErros.js +12 -0
- data/db/migrate/20250627101451_add_role_to_stay_users.rb +5 -0
- data/lib/stay_commerce/frontend/version.rb +1 -1
- metadata +15 -5
- data/app/javascript/react/components/HeroSectionDesign/PropertiesPage.jsx +0 -122
- data/app/javascript/react/shared/DateField/CustomDatePicker.jsx +0 -69
- data/app/javascript/react/styles/customdatepicker.scss +0 -120
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, {
|
1
|
+
import React, { useEffect, useState } from "react";
|
2
2
|
import "../../../styles/mybooking.scss";
|
3
3
|
import StayCard from "../../StayCard/StayCard";
|
4
4
|
import {
|
@@ -7,6 +7,8 @@ import {
|
|
7
7
|
} from "../../../redux/slices/PropertySlice/PropertySlice";
|
8
8
|
import { useDispatch, useSelector } from "react-redux";
|
9
9
|
import { useNavigate } from "react-router-dom";
|
10
|
+
import Pagination from "../../../shared/Pagination";
|
11
|
+
import Loader from "../../../shared/Loader";
|
10
12
|
|
11
13
|
const MyBooking = () => {
|
12
14
|
const dispatch = useDispatch();
|
@@ -14,35 +16,32 @@ const MyBooking = () => {
|
|
14
16
|
const [bookingData, setBookingData] = useState(null);
|
15
17
|
const [query, setQuery] = useState("");
|
16
18
|
const [isLoading, setIsLoading] = useState(false);
|
19
|
+
const [currentPage, setCurrentPage] = useState(1);
|
17
20
|
const { data: currentUser } = useSelector((state) => state.user);
|
18
21
|
|
19
|
-
|
20
|
-
async (page) => {
|
22
|
+
useEffect(() => {
|
23
|
+
const fetchBookings = async (page) => {
|
24
|
+
if (!currentUser) return;
|
25
|
+
|
21
26
|
setIsLoading(true);
|
22
27
|
try {
|
23
|
-
|
24
|
-
|
25
|
-
getHostBookingsDetails({ page })
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
const response = await dispatch(
|
30
|
-
getUserBookingsDetails({ page })
|
31
|
-
).unwrap();
|
32
|
-
setBookingData(response);
|
33
|
-
}
|
28
|
+
const response = await dispatch(
|
29
|
+
currentUser?.is_host
|
30
|
+
? getHostBookingsDetails({ page })
|
31
|
+
: getUserBookingsDetails({ page })
|
32
|
+
).unwrap();
|
33
|
+
setBookingData(response);
|
34
34
|
} catch (error) {
|
35
35
|
console.error("Error fetching bookings:", error);
|
36
36
|
} finally {
|
37
37
|
setIsLoading(false);
|
38
38
|
}
|
39
|
-
}
|
40
|
-
[dispatch, currentUser]
|
41
|
-
);
|
39
|
+
};
|
42
40
|
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
if (currentUser) {
|
42
|
+
fetchBookings(currentPage);
|
43
|
+
}
|
44
|
+
}, [dispatch, currentUser, currentPage]);
|
46
45
|
|
47
46
|
return (
|
48
47
|
<div className="my-booking-container">
|
@@ -52,6 +51,7 @@ const MyBooking = () => {
|
|
52
51
|
View the complete list of all your booking requests.
|
53
52
|
</p>
|
54
53
|
|
54
|
+
{/* Optional Search Bar */}
|
55
55
|
{/* <div className="search-bar">
|
56
56
|
<FormField
|
57
57
|
type="text"
|
@@ -63,16 +63,28 @@ const MyBooking = () => {
|
|
63
63
|
<ButtonPrimary className="search-button">Search</ButtonPrimary>
|
64
64
|
</div> */}
|
65
65
|
|
66
|
-
{/*
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
bookingData.bookings.map((booking) => (
|
66
|
+
{/* Loader or Bookings List */}
|
67
|
+
{isLoading ? (
|
68
|
+
<Loader text="Loading your bookings..." />
|
69
|
+
) : bookingData?.bookings?.length > 0 ? (
|
70
|
+
<div className="stay-cards">
|
71
|
+
{bookingData.bookings.map((booking) => (
|
72
72
|
<StayCard key={booking.id} booking={booking} />
|
73
|
-
))
|
74
|
-
|
75
|
-
|
73
|
+
))}
|
74
|
+
</div>
|
75
|
+
) : (
|
76
|
+
<p className="no-property-message">No bookings found.</p>
|
77
|
+
)}
|
78
|
+
|
79
|
+
{/* Pagination */}
|
80
|
+
<div className="pagination_header">
|
81
|
+
{bookingData?.meta?.total_pages > 1 && (
|
82
|
+
<Pagination
|
83
|
+
currentPage={bookingData.meta.current_page}
|
84
|
+
totalPages={bookingData.meta.total_pages}
|
85
|
+
onPageChange={setCurrentPage}
|
86
|
+
isLoading={isLoading}
|
87
|
+
/>
|
76
88
|
)}
|
77
89
|
</div>
|
78
90
|
</div>
|
@@ -21,8 +21,10 @@ const StayCard = ({ booking }) => {
|
|
21
21
|
number_of_guests,
|
22
22
|
total_amount,
|
23
23
|
id,
|
24
|
+
room_images,
|
25
|
+
booked_room,
|
24
26
|
} = booking;
|
25
|
-
const {
|
27
|
+
const { title } = property;
|
26
28
|
|
27
29
|
const handleCardClick = () => {
|
28
30
|
navigate(`/bookingDetails/${id}`);
|
@@ -34,10 +36,10 @@ const StayCard = ({ booking }) => {
|
|
34
36
|
onClick={handleCardClick}
|
35
37
|
style={{ cursor: "pointer" }}
|
36
38
|
>
|
37
|
-
<GallerySlider images={
|
39
|
+
<GallerySlider images={room_images} style={{ height: "300px" }} />
|
38
40
|
|
39
41
|
<div className="stay-card__content">
|
40
|
-
<h2 className="stay-card__title">{
|
42
|
+
<h2 className="stay-card__title">{booked_room}</h2>
|
41
43
|
{currentUser?.is_host && (
|
42
44
|
<div className="stay-card__info">
|
43
45
|
<span>Request by:</span>{" "}
|
@@ -13,6 +13,7 @@ import "@mantine/core/styles.css";
|
|
13
13
|
import "@mantine/dates/styles.css";
|
14
14
|
import { Elements } from "@stripe/react-stripe-js";
|
15
15
|
import { loadStripe } from "@stripe/stripe-js";
|
16
|
+
import "react-datepicker/dist/react-datepicker.css";
|
16
17
|
const stripePromise = loadStripe(
|
17
18
|
"pk_test_51LCfAAJQIRLPDLbLcKwKmAbKIHjTiJYCUsWXxfbQ0UlF5N3AO2tfLgKXt4GPI7UZYJUVHi94Q4TEol5YN1PQD4AI00N1uBrnnR"
|
18
19
|
);
|
@@ -24,8 +24,9 @@ import ForgetPassword from "../../components/ForgetPassword/ForgetPassword";
|
|
24
24
|
import ResetPassword from "../../components/ResetPassword/ResetPassword";
|
25
25
|
import ListingStayDetailPage from "../../components/Listing-stay-Detail/ListingStayDetailPage";
|
26
26
|
import ApartmentCard from "../../components/Listing-stay-Detail/ApartmentCard";
|
27
|
-
import PropertiesPage from "../../components/HeroSectionDesign/PropertiesPage";
|
28
27
|
import BookingInterface from "../../components/PropertyListing/StayBooking/BookingDetails";
|
28
|
+
import CardManager from "../../components/Listing-stay-Detail/CardManager";
|
29
|
+
import PropertiesPage from "../../components/Listing-stay-Detail/PropertiesPage";
|
29
30
|
|
30
31
|
const RequireHost = ({ children }) => {
|
31
32
|
const user = useSelector((state) => state.user?.data);
|
@@ -35,6 +36,14 @@ const RequireHost = ({ children }) => {
|
|
35
36
|
return children;
|
36
37
|
};
|
37
38
|
|
39
|
+
const RequireUser = ({ children }) => {
|
40
|
+
const user = useSelector((state) => state.user?.data);
|
41
|
+
if (!user?.is_user) {
|
42
|
+
return <Navigate to="/unauthorized" />;
|
43
|
+
}
|
44
|
+
return children;
|
45
|
+
};
|
46
|
+
|
38
47
|
const router = createBrowserRouter([
|
39
48
|
{
|
40
49
|
path: "/",
|
@@ -156,6 +165,14 @@ const router = createBrowserRouter([
|
|
156
165
|
path: "*",
|
157
166
|
element: <Page404 />,
|
158
167
|
},
|
168
|
+
{
|
169
|
+
path: "/AddCard",
|
170
|
+
element: (
|
171
|
+
<RequireUser>
|
172
|
+
<CardManager />
|
173
|
+
</RequireUser>
|
174
|
+
),
|
175
|
+
},
|
159
176
|
],
|
160
177
|
},
|
161
178
|
]);
|
@@ -15,12 +15,14 @@ function Home() {
|
|
15
15
|
<div>
|
16
16
|
{/* Page Content */}
|
17
17
|
|
18
|
-
<
|
19
|
-
|
18
|
+
<div style={{ paddingLeft: "20px", paddingRight: "20px" }}>
|
19
|
+
<HeroSection />
|
20
|
+
</div>
|
21
|
+
<div className="container">
|
20
22
|
<BookingForm />
|
21
|
-
</div>
|
23
|
+
</div>
|
22
24
|
</div>
|
23
|
-
<Listing />
|
25
|
+
{/* <Listing /> */}
|
24
26
|
<FeaturesSection />
|
25
27
|
<AboutUs />
|
26
28
|
<TestimonialSection />
|
@@ -194,27 +194,27 @@ export const getCard = createAsyncThunk(
|
|
194
194
|
}
|
195
195
|
);
|
196
196
|
|
197
|
-
export const searchProperties = createAsyncThunk(
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
);
|
197
|
+
// export const searchProperties = createAsyncThunk(
|
198
|
+
// "properties/searchProperties",
|
199
|
+
// async (searchQuery, { rejectWithValue }) => {
|
200
|
+
// try {
|
201
|
+
// const response = await API.get(ENDPOINTS.SEARCH_PROPERTIES(searchQuery));
|
202
|
+
// return {
|
203
|
+
// success: true,
|
204
|
+
// data: response.data,
|
205
|
+
// };
|
206
|
+
// } catch (error) {
|
207
|
+
// if (error.response && error.response.status === 404) {
|
208
|
+
// toast.error(error.response.data.error || error.response.data.message, {
|
209
|
+
// position: "top-right",
|
210
|
+
// });
|
211
|
+
// return rejectWithValue(error.response.data);
|
212
|
+
// }
|
213
|
+
// ErrorHandler(error);
|
214
|
+
// return rejectWithValue(error.message);
|
215
|
+
// }
|
216
|
+
// }
|
217
|
+
// );
|
218
218
|
|
219
219
|
export const getPaymentmethods = createAsyncThunk(
|
220
220
|
"payment/getCard",
|
@@ -1,11 +1,37 @@
|
|
1
|
-
import { createSlice } from "@reduxjs/toolkit";
|
2
|
-
import
|
1
|
+
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
2
|
+
import API from "../../../utils/helpers/APIHelper";
|
3
|
+
import { ENDPOINTS } from "../../../Api/apiConstants";
|
4
|
+
import ErrorHandler from "../../../utils/helpers/ErrorHandler";
|
5
|
+
import { toast } from "react-toastify";
|
6
|
+
|
7
|
+
export const searchProperties = createAsyncThunk(
|
8
|
+
"search/searchProperties",
|
9
|
+
async (searchQuery, { rejectWithValue }) => {
|
10
|
+
try {
|
11
|
+
const response = await API.get(ENDPOINTS.SEARCH_PROPERTIES(searchQuery));
|
12
|
+
return response;
|
13
|
+
} catch (error) {
|
14
|
+
if (error.response && error.response.status === 404) {
|
15
|
+
toast.error(error.response.data.message || "Not found");
|
16
|
+
return rejectWithValue(error.response.data);
|
17
|
+
}
|
18
|
+
ErrorHandler(error);
|
19
|
+
return rejectWithValue(error.message);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
);
|
3
23
|
|
4
24
|
const initialState = {
|
5
25
|
searchResults: [],
|
6
26
|
startDate: null,
|
7
27
|
endDate: null,
|
8
28
|
totalGuests: 1,
|
29
|
+
address: null,
|
30
|
+
latitude: null,
|
31
|
+
longitude: null,
|
32
|
+
loading: false,
|
33
|
+
error: null,
|
34
|
+
data: null,
|
9
35
|
};
|
10
36
|
|
11
37
|
const searchSlice = createSlice({
|
@@ -17,18 +43,39 @@ const searchSlice = createSlice({
|
|
17
43
|
state.startDate = null;
|
18
44
|
state.endDate = null;
|
19
45
|
state.totalGuests = 1;
|
46
|
+
state.address = null;
|
47
|
+
state.latitude = null;
|
48
|
+
state.longitude = null;
|
49
|
+
state.error = null;
|
50
|
+
state.data = null;
|
20
51
|
},
|
21
52
|
setSearchMeta: (state, action) => {
|
22
|
-
const { startDate, endDate, totalGuests } =
|
53
|
+
const { startDate, endDate, totalGuests, address, latitude, longitude } =
|
54
|
+
action.payload;
|
23
55
|
state.startDate = startDate;
|
24
56
|
state.endDate = endDate;
|
25
57
|
state.totalGuests = totalGuests;
|
58
|
+
state.address = address;
|
59
|
+
state.latitude = latitude;
|
60
|
+
state.longitude = longitude;
|
26
61
|
},
|
27
62
|
},
|
28
63
|
extraReducers: (builder) => {
|
29
|
-
builder
|
30
|
-
|
31
|
-
|
64
|
+
builder
|
65
|
+
.addCase(searchProperties.pending, (state) => {
|
66
|
+
state.loading = true;
|
67
|
+
state.error = null;
|
68
|
+
})
|
69
|
+
.addCase(searchProperties.fulfilled, (state, action) => {
|
70
|
+
state.searchResults = action.payload?.properties || [];
|
71
|
+
state.data = action.payload;
|
72
|
+
state.loading = false;
|
73
|
+
state.error = null;
|
74
|
+
})
|
75
|
+
.addCase(searchProperties.rejected, (state, action) => {
|
76
|
+
state.loading = false;
|
77
|
+
state.error = action.payload || "Failed to search properties";
|
78
|
+
});
|
32
79
|
},
|
33
80
|
});
|
34
81
|
|
@@ -2,6 +2,7 @@ import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
|
2
2
|
import axios from "axios";
|
3
3
|
import { ENDPOINTS } from "../../../Api/apiConstants";
|
4
4
|
import ErrorHandler from "../../../utils/helpers/ErrorHandler";
|
5
|
+
|
5
6
|
export const getCurrentUser = createAsyncThunk(
|
6
7
|
"user/getCurrentUser",
|
7
8
|
async (_, { rejectWithValue }) => {
|
@@ -1,14 +1,11 @@
|
|
1
|
-
import React from "react";
|
1
|
+
import React, { useEffect } from "react";
|
2
2
|
import { useSelector } from "react-redux";
|
3
3
|
import "../../styles/avatar.scss";
|
4
4
|
|
5
|
-
const Avatar = ({ onClick, sizeClass = "" }) => {
|
6
|
-
const userData = useSelector((state) => state.user.data);
|
7
|
-
|
5
|
+
const Avatar = ({ onClick, sizeClass = "", userData }) => {
|
8
6
|
if (!userData) return null;
|
9
|
-
|
10
|
-
const
|
11
|
-
const firstLetter = userData.first_name?.charAt(0).toUpperCase() || "?";
|
7
|
+
const profileImage = userData?.image;
|
8
|
+
const firstLetter = userData?.first_name?.charAt(0).toUpperCase() || "?";
|
12
9
|
|
13
10
|
return (
|
14
11
|
<div className={`avatar-container ${sizeClass}`} onClick={onClick}>
|
@@ -19,7 +16,7 @@ const Avatar = ({ onClick, sizeClass = "" }) => {
|
|
19
16
|
className="avatar-image"
|
20
17
|
onError={(e) => {
|
21
18
|
e.target.onerror = null;
|
22
|
-
// e.target.src = "/default-avatar.png";
|
19
|
+
// e.target.src = "/default-avatar.png";
|
23
20
|
}}
|
24
21
|
/>
|
25
22
|
) : (
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import Button from "./Button";
|
2
|
+
import React from "react";
|
3
|
+
import "../../styles/buttonSecondry.scss"; // Import SCSS for styling
|
4
|
+
|
5
|
+
const SecondryButton = ({ className = "", ...args }) => {
|
6
|
+
return <Button className={`sec-primary ${className}`} {...args} />;
|
7
|
+
};
|
8
|
+
|
9
|
+
export default SecondryButton;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import "../styles/Loader.scss"; // Adjust the path as necessary
|
3
|
+
|
4
|
+
const Loader = ({ text = "Loading..." }) => {
|
5
|
+
return (
|
6
|
+
<div className="app-loader">
|
7
|
+
<div className="app-loader__spinner" />
|
8
|
+
<p className="app-loader__text">{text}</p>
|
9
|
+
</div>
|
10
|
+
);
|
11
|
+
};
|
12
|
+
|
13
|
+
export default Loader;
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import "../styles/Pagination.scss"; // Adjust the path as necessary
|
3
|
+
|
4
|
+
const Pagination = ({
|
5
|
+
currentPage,
|
6
|
+
totalPages,
|
7
|
+
onPageChange,
|
8
|
+
isLoading = false,
|
9
|
+
}) => {
|
10
|
+
const renderPageNumbers = () => {
|
11
|
+
const pages = [];
|
12
|
+
|
13
|
+
for (let i = 1; i <= totalPages; i++) {
|
14
|
+
pages.push(
|
15
|
+
<button
|
16
|
+
key={i}
|
17
|
+
className={`pagination__button ${
|
18
|
+
i === currentPage ? "pagination__button--active" : ""
|
19
|
+
}`}
|
20
|
+
onClick={() => onPageChange(i)}
|
21
|
+
>
|
22
|
+
{i}
|
23
|
+
</button>
|
24
|
+
);
|
25
|
+
}
|
26
|
+
|
27
|
+
return pages;
|
28
|
+
};
|
29
|
+
|
30
|
+
return (
|
31
|
+
<div className="pagination">
|
32
|
+
<button
|
33
|
+
className="pagination__button"
|
34
|
+
onClick={() => onPageChange(currentPage - 1)}
|
35
|
+
disabled={currentPage === 1 || isLoading}
|
36
|
+
>
|
37
|
+
Prev
|
38
|
+
</button>
|
39
|
+
|
40
|
+
{renderPageNumbers()}
|
41
|
+
|
42
|
+
<button
|
43
|
+
className="pagination__button"
|
44
|
+
onClick={() => onPageChange(currentPage + 1)}
|
45
|
+
disabled={currentPage === totalPages}
|
46
|
+
>
|
47
|
+
Next
|
48
|
+
</button>
|
49
|
+
</div>
|
50
|
+
);
|
51
|
+
};
|
52
|
+
|
53
|
+
export default Pagination;
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import * as Yup from "yup";
|
2
|
+
|
3
|
+
export const descriptionValidationSchemas = Yup.object().shape({
|
4
|
+
title: Yup.string()
|
5
|
+
.required("Title is required")
|
6
|
+
.max(100, "Title cannot exceed 100 characters"),
|
7
|
+
property_category: Yup.string().required("Property category is required"),
|
8
|
+
property_type_id: Yup.string().required("Property type is required"),
|
9
|
+
country: Yup.string().required("Country is required"),
|
10
|
+
description: Yup.string()
|
11
|
+
.required("Description is required")
|
12
|
+
.min(200, "Description must be at least 200 characters long"),
|
13
|
+
address: Yup.string().required("Address is required"),
|
14
|
+
});
|
15
|
+
|
16
|
+
export const ImagevalidationSchema = Yup.object({
|
17
|
+
place_images: Yup.array()
|
18
|
+
.min(1, "Please upload at least one image")
|
19
|
+
.required("Images are required"),
|
20
|
+
});
|
21
|
+
|
22
|
+
// Validation schema
|
23
|
+
export const detailsvalidationSchema = Yup.object({
|
24
|
+
property_size: Yup.number()
|
25
|
+
.required("Property size is required")
|
26
|
+
.positive("Property size must be a positive number")
|
27
|
+
.integer("Property size must be a whole number"),
|
28
|
+
total_rooms: Yup.number()
|
29
|
+
.required("Total rooms is required")
|
30
|
+
.positive("Total rooms must be a positive number")
|
31
|
+
.integer("Total rooms must be a whole number")
|
32
|
+
.max(50, "Total rooms cannot exceed 50"),
|
33
|
+
additionalRules: Yup.array().of(
|
34
|
+
Yup.object({
|
35
|
+
name: Yup.string().required("Rule name is required"),
|
36
|
+
})
|
37
|
+
),
|
38
|
+
});
|
39
|
+
|
40
|
+
export const LocationSchema = Yup.object().shape({
|
41
|
+
city: Yup.string().required("City is required"),
|
42
|
+
state: Yup.string().required("State is required"),
|
43
|
+
zipcode: Yup.string().required("Zipcode is required"),
|
44
|
+
latitude: Yup.number()
|
45
|
+
.typeError("Latitude must be a number")
|
46
|
+
.required("Latitude is required"),
|
47
|
+
longitude: Yup.number()
|
48
|
+
.typeError("Longitude must be a number")
|
49
|
+
.required("Longitude is required"),
|
50
|
+
});
|
51
|
+
|
52
|
+
export const roomValidationSchema = Yup.object().shape({
|
53
|
+
rooms_attributes: Yup.array().of(
|
54
|
+
Yup.object().shape({
|
55
|
+
name: Yup.string()
|
56
|
+
.required("Room name is required")
|
57
|
+
.min(2, "Room name must be at least 2 characters")
|
58
|
+
.max(100, "Room name must not exceed 100 characters"),
|
59
|
+
price_per_month: Yup.number()
|
60
|
+
.required("Price per month is required")
|
61
|
+
.positive("Price must be positive")
|
62
|
+
.min(1, "Price must be at least 1"),
|
63
|
+
status: Yup.string()
|
64
|
+
.required("Status is required")
|
65
|
+
.oneOf(
|
66
|
+
["active", "inactive"],
|
67
|
+
"Status must be either active or inactive"
|
68
|
+
),
|
69
|
+
booking_start: Yup.date().required("Availability start date is required"),
|
70
|
+
booking_end: Yup.date()
|
71
|
+
.required("Availability end date is required")
|
72
|
+
.min(Yup.ref("booking_start"), "End date must be after start date"),
|
73
|
+
description: Yup.string()
|
74
|
+
.required("Description is required")
|
75
|
+
.min(200, "Description must be at least 200 characters"),
|
76
|
+
bed_type_id: Yup.string().required("Bed type is required"),
|
77
|
+
size: Yup.number()
|
78
|
+
.required("Room size is required")
|
79
|
+
.positive("Size must be positive")
|
80
|
+
.min(1, "Size must be at least 1 sq ft"),
|
81
|
+
room_type_id: Yup.string().required("Room type is required"),
|
82
|
+
max_guests: Yup.number()
|
83
|
+
.required("Max guests is required")
|
84
|
+
.positive("Max guests must be positive")
|
85
|
+
.integer("Max guests must be a whole number")
|
86
|
+
.min(1, "At least 1 guest must be allowed"),
|
87
|
+
room_images: Yup.array()
|
88
|
+
.test(
|
89
|
+
"room-images-required",
|
90
|
+
"At least one room image is required",
|
91
|
+
function (value) {
|
92
|
+
const { existing_room_images } = this.parent;
|
93
|
+
const totalImages =
|
94
|
+
(value?.length || 0) + (existing_room_images?.length || 0);
|
95
|
+
return totalImages >= 1;
|
96
|
+
}
|
97
|
+
)
|
98
|
+
.test(
|
99
|
+
"max-images",
|
100
|
+
"Maximum 10 images are allowed per room",
|
101
|
+
function (value) {
|
102
|
+
const { existing_room_images } = this.parent;
|
103
|
+
const totalImages =
|
104
|
+
(value?.length || 0) + (existing_room_images?.length || 0);
|
105
|
+
return totalImages <= 10;
|
106
|
+
}
|
107
|
+
)
|
108
|
+
.test(
|
109
|
+
"file-size",
|
110
|
+
"Each image must be less than 10MB",
|
111
|
+
function (value) {
|
112
|
+
if (!value || value.length === 0) return true;
|
113
|
+
return value.every((file) => {
|
114
|
+
if (file instanceof File) {
|
115
|
+
return file.size <= 10 * 1024 * 1024; // 10MB
|
116
|
+
}
|
117
|
+
return true;
|
118
|
+
});
|
119
|
+
}
|
120
|
+
)
|
121
|
+
.test(
|
122
|
+
"file-type",
|
123
|
+
"Only PNG, JPG, JPEG, and GIF files are allowed",
|
124
|
+
function (value) {
|
125
|
+
if (!value || value.length === 0) return true;
|
126
|
+
const allowedTypes = [
|
127
|
+
"image/png",
|
128
|
+
"image/jpg",
|
129
|
+
"image/jpeg",
|
130
|
+
"image/gif",
|
131
|
+
];
|
132
|
+
return value.every((file) => {
|
133
|
+
if (file instanceof File) {
|
134
|
+
return allowedTypes.includes(file.type);
|
135
|
+
}
|
136
|
+
return true;
|
137
|
+
});
|
138
|
+
}
|
139
|
+
),
|
140
|
+
existing_room_images: Yup.array(),
|
141
|
+
})
|
142
|
+
),
|
143
|
+
});
|