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
@@ -0,0 +1,285 @@
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
2
|
+
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
3
|
+
import { useDispatch } from "react-redux";
|
4
|
+
import { toast } from "react-toastify";
|
5
|
+
import { CreditCard, Plus, X, Trash2, Lock, Shield } from "lucide-react";
|
6
|
+
import "../../styles/CardManager.scss";
|
7
|
+
import {
|
8
|
+
deleteCard,
|
9
|
+
getCard,
|
10
|
+
saveCard,
|
11
|
+
} from "../../redux/slices/PropertySlice/PropertySlice";
|
12
|
+
|
13
|
+
const CardManager = () => {
|
14
|
+
const dispatch = useDispatch();
|
15
|
+
const stripe = useStripe();
|
16
|
+
const elements = useElements();
|
17
|
+
|
18
|
+
const [savedCards, setSavedCards] = useState([]);
|
19
|
+
const [loading, setLoading] = useState(false);
|
20
|
+
const [addingCards, setAddingCards] = useState([]);
|
21
|
+
const [deletingCard, setDeletingCard] = useState(null);
|
22
|
+
|
23
|
+
useEffect(() => {
|
24
|
+
dispatch(getCard())
|
25
|
+
.unwrap()
|
26
|
+
.then((data) => {
|
27
|
+
if (Array.isArray(data)) {
|
28
|
+
setSavedCards(data);
|
29
|
+
} else if (data?.payment_method_token) {
|
30
|
+
setSavedCards([data]);
|
31
|
+
} else {
|
32
|
+
setSavedCards([]);
|
33
|
+
}
|
34
|
+
})
|
35
|
+
.catch(() => {
|
36
|
+
toast.error("❌ Could not retrieve cards");
|
37
|
+
setSavedCards([]);
|
38
|
+
});
|
39
|
+
}, [dispatch]);
|
40
|
+
|
41
|
+
const handleDeleteCard = async (cardId) => {
|
42
|
+
setDeletingCard(cardId);
|
43
|
+
try {
|
44
|
+
await dispatch(deleteCard(cardId)).unwrap();
|
45
|
+
setSavedCards((prev) => prev.filter((card) => card.id !== cardId));
|
46
|
+
toast.success("Card deleted successfully");
|
47
|
+
} catch (error) {
|
48
|
+
toast.error(`❌ ${error.message}`);
|
49
|
+
} finally {
|
50
|
+
setDeletingCard(null);
|
51
|
+
}
|
52
|
+
};
|
53
|
+
|
54
|
+
const handleAddCard = () => {
|
55
|
+
const formId = Date.now();
|
56
|
+
setAddingCards((prev) => [...prev, formId]);
|
57
|
+
};
|
58
|
+
|
59
|
+
const handleSaveCard = async (formId) => {
|
60
|
+
if (!stripe || !elements) {
|
61
|
+
toast.error("❌ Stripe not ready");
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
|
65
|
+
const cardElement = elements.getElement(CardElement);
|
66
|
+
if (!cardElement) {
|
67
|
+
toast.error("Card element not found");
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
setLoading(true);
|
72
|
+
try {
|
73
|
+
const { token, error } = await stripe.createToken(cardElement);
|
74
|
+
if (error) throw new Error(error.message);
|
75
|
+
|
76
|
+
const cardData = {
|
77
|
+
cc_number: token?.card?.last4 || "****",
|
78
|
+
month: token?.card?.exp_month,
|
79
|
+
year: token?.card?.exp_year,
|
80
|
+
card_token: token?.id,
|
81
|
+
};
|
82
|
+
|
83
|
+
const saved = await dispatch(saveCard(cardData)).unwrap();
|
84
|
+
|
85
|
+
setSavedCards((prev) => [...prev, saved]);
|
86
|
+
setAddingCards((prev) => prev.filter((id) => id !== formId));
|
87
|
+
toast.success("Card added successfully");
|
88
|
+
} catch (error) {
|
89
|
+
toast.error(`❌ ${error.message}`);
|
90
|
+
} finally {
|
91
|
+
setLoading(false);
|
92
|
+
}
|
93
|
+
};
|
94
|
+
|
95
|
+
const handleCancelAdd = (formId) => {
|
96
|
+
setAddingCards((prev) => prev.filter((id) => id !== formId));
|
97
|
+
};
|
98
|
+
|
99
|
+
const getCardIcon = (brand) => {
|
100
|
+
switch (brand?.toLowerCase()) {
|
101
|
+
case "visa":
|
102
|
+
return "💳";
|
103
|
+
case "mastercard":
|
104
|
+
return "💳";
|
105
|
+
case "amex":
|
106
|
+
return "💳";
|
107
|
+
default:
|
108
|
+
return "💳";
|
109
|
+
}
|
110
|
+
};
|
111
|
+
|
112
|
+
return (
|
113
|
+
<div className="card-manager">
|
114
|
+
<div className="card-manager__container">
|
115
|
+
{/* Header */}
|
116
|
+
<div className="card-manager__header">
|
117
|
+
<div className="card-manager__header-icon">
|
118
|
+
<CreditCard className="icon" />
|
119
|
+
</div>
|
120
|
+
<h1 className="card-manager__title">Payment Cards</h1>
|
121
|
+
<p className="card-manager__subtitle">
|
122
|
+
Manage your saved payment methods securely
|
123
|
+
</p>
|
124
|
+
</div>
|
125
|
+
<div className="card-manager__content">
|
126
|
+
<div className="saved-cards">
|
127
|
+
<h2 className="saved-cards__title">
|
128
|
+
<Lock className="icon" />
|
129
|
+
Saved Cards ({savedCards?.length})
|
130
|
+
</h2>
|
131
|
+
|
132
|
+
{savedCards?.length > 0 ? (
|
133
|
+
<div
|
134
|
+
className={`saved-cards__grid ${
|
135
|
+
savedCards?.length === 1 ? "single-card" : ""
|
136
|
+
}`}
|
137
|
+
>
|
138
|
+
{savedCards?.map((card) => (
|
139
|
+
<div
|
140
|
+
key={card.id}
|
141
|
+
className={`credit-card credit-card--${
|
142
|
+
card.brand || "default"
|
143
|
+
}`}
|
144
|
+
>
|
145
|
+
<div className="credit-card__pattern"></div>
|
146
|
+
<div className="credit-card__content">
|
147
|
+
<div className="credit-card__header">
|
148
|
+
<div className="credit-card__icon">
|
149
|
+
{getCardIcon(card.brand)}
|
150
|
+
</div>
|
151
|
+
<button
|
152
|
+
onClick={() => handleDeleteCard(card.id)}
|
153
|
+
disabled={deletingCard === card.id}
|
154
|
+
className="credit-card__delete-btn"
|
155
|
+
>
|
156
|
+
{deletingCard === card.id ? (
|
157
|
+
<div className="Card_spinner"></div>
|
158
|
+
) : (
|
159
|
+
<Trash2 className="icon" />
|
160
|
+
)}
|
161
|
+
</button>
|
162
|
+
</div>
|
163
|
+
|
164
|
+
<div className="credit-card__number">
|
165
|
+
<p>•••• •••• •••• {card.cc_number?.slice(-4)}</p>
|
166
|
+
</div>
|
167
|
+
|
168
|
+
<div className="credit-card__details">
|
169
|
+
<div className="credit-card__expiry">
|
170
|
+
<p className="label">EXPIRES</p>
|
171
|
+
<p className="value">
|
172
|
+
{String(card.exp_month || card.month).padStart(
|
173
|
+
2,
|
174
|
+
"0"
|
175
|
+
)}
|
176
|
+
/{String(card.exp_year || card.year).slice(-2)}
|
177
|
+
</p>
|
178
|
+
</div>
|
179
|
+
<div className="credit-card__type">
|
180
|
+
<p className="label">TYPE</p>
|
181
|
+
<p className="value">{card.brand || "Card"}</p>
|
182
|
+
</div>
|
183
|
+
</div>
|
184
|
+
</div>
|
185
|
+
</div>
|
186
|
+
))}
|
187
|
+
</div>
|
188
|
+
) : (
|
189
|
+
<div className="empty-state">
|
190
|
+
<div className="empty-state__icon">
|
191
|
+
<CreditCard className="icon" />
|
192
|
+
</div>
|
193
|
+
<p className="empty-state__title">No cards saved yet</p>
|
194
|
+
<p className="empty-state__subtitle">
|
195
|
+
Add your first payment method to get started
|
196
|
+
</p>
|
197
|
+
</div>
|
198
|
+
)}
|
199
|
+
</div>
|
200
|
+
{addingCards.map((formId) => (
|
201
|
+
<div key={formId} className="card-form">
|
202
|
+
<div className="card-form__container">
|
203
|
+
<div className="card-form__header">
|
204
|
+
<h3 className="card-form__title">
|
205
|
+
<Plus className="icon" />
|
206
|
+
Add New Card
|
207
|
+
</h3>
|
208
|
+
<button
|
209
|
+
onClick={() => handleCancelAdd(formId)}
|
210
|
+
className="card-form__close-btn"
|
211
|
+
>
|
212
|
+
<X className="icon" />
|
213
|
+
</button>
|
214
|
+
</div>
|
215
|
+
<div className="card-form__input">
|
216
|
+
<CardElement
|
217
|
+
className="stripe-card-element"
|
218
|
+
options={{
|
219
|
+
hidePostalCode: true,
|
220
|
+
style: {
|
221
|
+
base: {
|
222
|
+
fontSize: "16px",
|
223
|
+
color: "#424770",
|
224
|
+
"::placeholder": {
|
225
|
+
color: "#aab7c4",
|
226
|
+
},
|
227
|
+
},
|
228
|
+
},
|
229
|
+
}}
|
230
|
+
/>
|
231
|
+
</div>
|
232
|
+
|
233
|
+
<div className="card-form__actions">
|
234
|
+
<button
|
235
|
+
onClick={() => handleSaveCard(formId)}
|
236
|
+
disabled={loading || !stripe || !elements}
|
237
|
+
className="btn btn--primary"
|
238
|
+
>
|
239
|
+
{loading ? (
|
240
|
+
<>
|
241
|
+
<div className="Card_spinner"></div>
|
242
|
+
Saving...
|
243
|
+
</>
|
244
|
+
) : (
|
245
|
+
<>
|
246
|
+
<Shield className="icon" />
|
247
|
+
Save Card Securely
|
248
|
+
</>
|
249
|
+
)}
|
250
|
+
</button>
|
251
|
+
<button
|
252
|
+
onClick={() => handleCancelAdd(formId)}
|
253
|
+
className="btn btn--secondary"
|
254
|
+
>
|
255
|
+
Cancel
|
256
|
+
</button>
|
257
|
+
</div>
|
258
|
+
</div>
|
259
|
+
</div>
|
260
|
+
))}
|
261
|
+
<div className="add-card-section">
|
262
|
+
<button
|
263
|
+
onClick={handleAddCard}
|
264
|
+
disabled={addingCards.length > 0}
|
265
|
+
className="btn btn--add-card"
|
266
|
+
>
|
267
|
+
<Plus className="icon" />
|
268
|
+
Add New Payment Method
|
269
|
+
</button>
|
270
|
+
</div>
|
271
|
+
</div>
|
272
|
+
|
273
|
+
{/* Security Footer */}
|
274
|
+
<div className="card-manager__footer">
|
275
|
+
<p>
|
276
|
+
<Lock className="icon" />
|
277
|
+
Your payment information is encrypted and secure
|
278
|
+
</p>
|
279
|
+
</div>
|
280
|
+
</div>
|
281
|
+
</div>
|
282
|
+
);
|
283
|
+
};
|
284
|
+
|
285
|
+
export default CardManager;
|
@@ -30,33 +30,29 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
30
30
|
const dispatch = useDispatch();
|
31
31
|
const [message, setMessage] = useState("");
|
32
32
|
const [loading, setLoading] = useState(false);
|
33
|
-
const [
|
34
|
-
const [
|
35
|
-
const [
|
33
|
+
const [cards, setCards] = useState([]);
|
34
|
+
const [selectedCard, setSelectedCard] = useState(null);
|
35
|
+
const [showNewCardForm, setShowNewCardForm] = useState(true);
|
36
36
|
const [paymentMethodIds, setPaymentMethodIds] = useState([]);
|
37
|
-
|
38
37
|
useEffect(() => {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
id: data.payment_method_token,
|
47
|
-
last4: data.cc_number?.slice(-4) || "****",
|
48
|
-
});
|
38
|
+
const fetchCards = async () => {
|
39
|
+
try {
|
40
|
+
const data = await dispatch(getCard()).unwrap();
|
41
|
+
setCards(data || []);
|
42
|
+
if (data && data.length > 0) {
|
43
|
+
setSelectedCard(data[0].payment_method_token);
|
44
|
+
setShowNewCardForm(false); // Hide new card form if cards exist
|
49
45
|
} else {
|
50
|
-
|
51
|
-
setPaymentMethod(null);
|
46
|
+
setShowNewCardForm(true); // Show new card form if no cards
|
52
47
|
}
|
53
|
-
})
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
48
|
+
} catch (err) {
|
49
|
+
console.error("Failed to get cards:", err);
|
50
|
+
toast.error("❌ Could not retrieve cards");
|
51
|
+
setShowNewCardForm(true); // Show new card form on error
|
52
|
+
}
|
53
|
+
};
|
54
|
+
|
55
|
+
fetchCards();
|
60
56
|
}, [dispatch]);
|
61
57
|
|
62
58
|
useEffect(() => {
|
@@ -73,17 +69,38 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
73
69
|
fetchPaymentMethods();
|
74
70
|
}, [dispatch]);
|
75
71
|
|
76
|
-
const handleDeleteCard = async () => {
|
72
|
+
const handleDeleteCard = async (cardId) => {
|
77
73
|
try {
|
78
74
|
await dispatch(deleteCardThunk(cardId)).unwrap();
|
79
|
-
|
80
|
-
|
75
|
+
const updatedCards = cards.filter((card) => card.id !== cardId);
|
76
|
+
setCards(updatedCards);
|
77
|
+
|
78
|
+
if (selectedCard === cardId) {
|
79
|
+
setSelectedCard(
|
80
|
+
updatedCards.length > 0 ? updatedCards[0].payment_method_token : null
|
81
|
+
);
|
82
|
+
}
|
83
|
+
|
84
|
+
if (updatedCards.length === 0) {
|
85
|
+
setShowNewCardForm(true);
|
86
|
+
}
|
87
|
+
|
81
88
|
successHandler("Card deleted successfully");
|
82
89
|
} catch (error) {
|
83
90
|
toast.error(`❌ ${error.message}`);
|
84
91
|
}
|
85
92
|
};
|
86
93
|
|
94
|
+
const handleCardSelection = (paymentMethodToken) => {
|
95
|
+
setSelectedCard(paymentMethodToken);
|
96
|
+
setShowNewCardForm(false);
|
97
|
+
};
|
98
|
+
|
99
|
+
const handleAddNewCard = () => {
|
100
|
+
setShowNewCardForm(true);
|
101
|
+
setSelectedCard(null);
|
102
|
+
};
|
103
|
+
|
87
104
|
const handleSubmit = async (e) => {
|
88
105
|
e.preventDefault();
|
89
106
|
|
@@ -91,14 +108,17 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
91
108
|
toast.error("❌ Stripe not ready");
|
92
109
|
return;
|
93
110
|
}
|
111
|
+
|
94
112
|
setLoading(true);
|
95
113
|
try {
|
96
|
-
|
114
|
+
// If adding a new card
|
115
|
+
if (showNewCardForm) {
|
97
116
|
const cardElement = elements.getElement(CardElement);
|
98
117
|
if (!cardElement) throw new Error("Card element not found");
|
99
118
|
|
100
119
|
const { token, error } = await stripe.createToken(cardElement);
|
101
120
|
if (error) throw new Error(error.message);
|
121
|
+
|
102
122
|
const cardData = {
|
103
123
|
cc_number: token?.card?.last4 || "****",
|
104
124
|
month: token?.card?.exp_month,
|
@@ -107,28 +127,23 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
107
127
|
};
|
108
128
|
|
109
129
|
const saved = await dispatch(saveCard(cardData)).unwrap();
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
last4: saved.cc_number?.slice(-4) || "****",
|
114
|
-
});
|
115
|
-
setCardExists(true);
|
116
|
-
setCardId(saved.id);
|
117
|
-
|
130
|
+
setCards([...cards, saved]);
|
131
|
+
setSelectedCard(saved.payment_method_token);
|
132
|
+
setShowNewCardForm(false);
|
118
133
|
toast.success("Card added successfully");
|
119
134
|
setLoading(false);
|
120
|
-
return;
|
135
|
+
return; // Return after adding card, let user explicitly click pay
|
121
136
|
}
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
137
|
+
|
138
|
+
// Proceed with payment
|
139
|
+
if (!selectedCard) {
|
140
|
+
throw new Error("Please select or add a payment method");
|
126
141
|
}
|
127
142
|
|
128
143
|
await dispatch(
|
129
144
|
createPayment({
|
130
145
|
bookingId,
|
131
|
-
paymentMethodId:
|
146
|
+
paymentMethodId: selectedCard,
|
132
147
|
})
|
133
148
|
).unwrap();
|
134
149
|
|
@@ -152,7 +167,85 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
152
167
|
|
153
168
|
return (
|
154
169
|
<form onSubmit={handleSubmit} style={{ marginTop: 20 }}>
|
155
|
-
{
|
170
|
+
{cards.length > 0 && (
|
171
|
+
<div style={{ marginBottom: 20 }}>
|
172
|
+
<h3 style={{ marginBottom: 15 }}>Saved Payment Methods</h3>
|
173
|
+
{cards.map((card) => (
|
174
|
+
<div
|
175
|
+
key={card.id}
|
176
|
+
style={{
|
177
|
+
padding: 15,
|
178
|
+
border: `1px solid ${
|
179
|
+
selectedCard === card.payment_method_token
|
180
|
+
? "rgb(212 235 255)"
|
181
|
+
: "#ccc"
|
182
|
+
}`,
|
183
|
+
borderRadius: 6,
|
184
|
+
backgroundColor:
|
185
|
+
selectedCard === card.payment_method_token
|
186
|
+
? "rgb(212 235 255)"
|
187
|
+
: "#f8f9fa",
|
188
|
+
marginBottom: 10,
|
189
|
+
color: "#155724",
|
190
|
+
position: "relative",
|
191
|
+
display: "flex",
|
192
|
+
alignItems: "center",
|
193
|
+
}}
|
194
|
+
>
|
195
|
+
<input
|
196
|
+
type="radio"
|
197
|
+
id={`card-${card.id}`}
|
198
|
+
name="paymentMethod"
|
199
|
+
checked={
|
200
|
+
selectedCard === card.payment_method_token && !showNewCardForm
|
201
|
+
}
|
202
|
+
onChange={() => handleCardSelection(card.payment_method_token)}
|
203
|
+
style={{ marginRight: 10 }}
|
204
|
+
/>
|
205
|
+
<label htmlFor={`card-${card.id}`} style={{ flex: 1 }}>
|
206
|
+
****{card.cc_number} - Expires {card.month}/{card.year}
|
207
|
+
</label>
|
208
|
+
<button
|
209
|
+
type="button"
|
210
|
+
onClick={() => handleDeleteCard(card.id)}
|
211
|
+
style={{
|
212
|
+
background: "none",
|
213
|
+
border: "none",
|
214
|
+
color: "#c82333",
|
215
|
+
cursor: "pointer",
|
216
|
+
fontWeight: "bold",
|
217
|
+
fontSize: 18,
|
218
|
+
}}
|
219
|
+
>
|
220
|
+
✕
|
221
|
+
</button>
|
222
|
+
</div>
|
223
|
+
))}
|
224
|
+
</div>
|
225
|
+
)}
|
226
|
+
|
227
|
+
{!showNewCardForm && cards.length > 0 && (
|
228
|
+
<button
|
229
|
+
type="button"
|
230
|
+
onClick={handleAddNewCard}
|
231
|
+
style={{
|
232
|
+
width: "100%",
|
233
|
+
padding: "10px",
|
234
|
+
backgroundColor: "var(--primary-color)",
|
235
|
+
border: "1px dashed #ccc",
|
236
|
+
borderRadius: 5,
|
237
|
+
cursor: "pointer",
|
238
|
+
fontSize: 16,
|
239
|
+
marginBottom: 20,
|
240
|
+
color: "#fff",
|
241
|
+
fontWeight: 600,
|
242
|
+
}}
|
243
|
+
>
|
244
|
+
+ Add New Card
|
245
|
+
</button>
|
246
|
+
)}
|
247
|
+
|
248
|
+
{showNewCardForm && (
|
156
249
|
<div
|
157
250
|
style={{
|
158
251
|
padding: 10,
|
@@ -177,48 +270,17 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
177
270
|
</div>
|
178
271
|
)}
|
179
272
|
|
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
273
|
<button
|
214
274
|
type="submit"
|
215
|
-
disabled={!stripe || loading}
|
275
|
+
disabled={!stripe || loading || (!selectedCard && !showNewCardForm)}
|
216
276
|
style={{
|
217
277
|
width: "100%",
|
218
278
|
padding: "12px 20px",
|
219
279
|
backgroundColor:
|
220
|
-
!stripe || loading
|
221
|
-
|
280
|
+
!stripe || loading
|
281
|
+
? "var(--primary-color)"
|
282
|
+
: "var(--primary-color)",
|
283
|
+
color: "#fff",
|
222
284
|
border: "none",
|
223
285
|
borderRadius: 5,
|
224
286
|
cursor: loading ? "not-allowed" : "pointer",
|
@@ -227,12 +289,12 @@ const CheckoutForm = ({ bookingId, totalAmount }) => {
|
|
227
289
|
}}
|
228
290
|
>
|
229
291
|
{loading
|
230
|
-
?
|
231
|
-
? "
|
232
|
-
: "
|
233
|
-
:
|
234
|
-
?
|
235
|
-
:
|
292
|
+
? showNewCardForm
|
293
|
+
? "Adding Card..."
|
294
|
+
: "Processing Payment..."
|
295
|
+
: showNewCardForm
|
296
|
+
? "Add Card"
|
297
|
+
: `Pay $${(totalAmount / 100).toFixed(2)}`}
|
236
298
|
</button>
|
237
299
|
|
238
300
|
{message && (
|
@@ -286,6 +348,7 @@ const CheckOutPagePageMain = ({ bookingId, totalAmount }) => {
|
|
286
348
|
>
|
287
349
|
Booking ID: {bookingId}
|
288
350
|
</p>
|
351
|
+
|
289
352
|
<Elements stripe={stripePromise}>
|
290
353
|
<CheckoutForm bookingId={bookingId} totalAmount={totalAmount} />
|
291
354
|
</Elements>
|
@@ -105,7 +105,6 @@ const StayDetailPageContainer = () => {
|
|
105
105
|
const start = moment(bookingStart, "YYYY-MM-DD");
|
106
106
|
const end = moment(bookingEnd, "YYYY-MM-DD");
|
107
107
|
const uniqueMonths = new Set();
|
108
|
-
const MAX_CHAR_LIMIT = 250;
|
109
108
|
let current = start.clone();
|
110
109
|
while (current.isBefore(end, "day") || current.isSame(end, "day")) {
|
111
110
|
uniqueMonths.add(`${current.year()}-${current.month() + 1}`);
|
@@ -141,9 +140,6 @@ const StayDetailPageContainer = () => {
|
|
141
140
|
try {
|
142
141
|
const action = await dispatch(reserveBooking(postData));
|
143
142
|
if (action?.type === "payment/reserveBooking/fulfilled" && action) {
|
144
|
-
successHandler(
|
145
|
-
"Booking request sent. Please ! wait for owner's confirmation!!"
|
146
|
-
);
|
147
143
|
const bookingId = action?.payload?.id;
|
148
144
|
if (bookingId) {
|
149
145
|
setError("");
|
@@ -445,7 +441,6 @@ const StayDetailPageContainer = () => {
|
|
445
441
|
</div>
|
446
442
|
);
|
447
443
|
})}
|
448
|
-
|
449
444
|
<BookingModal
|
450
445
|
isOpen={isBookingModalOpen}
|
451
446
|
onClose={closeBookingModal}
|
@@ -457,7 +452,6 @@ const StayDetailPageContainer = () => {
|
|
457
452
|
</>
|
458
453
|
);
|
459
454
|
};
|
460
|
-
|
461
455
|
return (
|
462
456
|
<div className="container listing-detail-page">
|
463
457
|
<header className="listing-header" onClick={handleOpenModalImageGallery}>
|
@@ -497,7 +491,7 @@ const StayDetailPageContainer = () => {
|
|
497
491
|
<section className="similar-properties-section">
|
498
492
|
{/* <RoomSection propertyData={propertyData} /> */}
|
499
493
|
<RenderSection />
|
500
|
-
<PropertyDetailsCard propertyData={propertyData} />
|
494
|
+
{/* <PropertyDetailsCard propertyData={propertyData} /> */}
|
501
495
|
</section>
|
502
496
|
|
503
497
|
{imageModal && (
|