From 9842cc3a4a9b773edea9342ea78a1acf20f72b71 Mon Sep 17 00:00:00 2001 From: Iliyan Angelov Date: Fri, 21 Nov 2025 19:44:42 +0200 Subject: [PATCH] updates --- .../booking_routes.cpython-312.pyc | Bin 93489 -> 93619 bytes .../schemas/__pycache__/auth.cpython-312.pyc | Bin 6596 -> 6872 bytes Backend/src/schemas/auth.py | 6 +- Frontend/src/App.tsx | 18 - .../components/booking/CancelBookingModal.tsx | 232 +++ .../components/booking/InvoiceInfoModal.tsx | 145 ++ .../components/booking/LuxuryBookingModal.tsx | 1081 ++++++++++ .../components/payments/CashPaymentModal.tsx | 124 ++ .../payments/DepositPaymentModal.tsx | 417 ++++ .../payments/PayPalPaymentModal.tsx | 188 ++ .../components/payments/StripePaymentForm.tsx | 145 +- .../payments/StripePaymentModal.tsx | 185 ++ .../src/pages/customer/BookingDetailPage.tsx | 111 +- Frontend/src/pages/customer/BookingPage.tsx | 1816 ----------------- .../src/pages/customer/BookingSuccessPage.tsx | 175 +- .../src/pages/customer/DepositPaymentPage.tsx | 505 ----- .../src/pages/customer/MyBookingsPage.tsx | 110 +- .../src/pages/customer/PayPalCancelPage.tsx | 4 +- .../src/pages/customer/PayPalReturnPage.tsx | 22 +- .../src/pages/customer/PaymentResultPage.tsx | 4 +- Frontend/src/pages/customer/ProfilePage.tsx | 4 +- .../src/pages/customer/RoomDetailPage.tsx | 155 +- Frontend/src/utils/validationSchemas.ts | 4 +- Frontend/src/validators/bookingValidator.ts | 4 +- 24 files changed, 2801 insertions(+), 2654 deletions(-) create mode 100644 Frontend/src/components/booking/CancelBookingModal.tsx create mode 100644 Frontend/src/components/booking/InvoiceInfoModal.tsx create mode 100644 Frontend/src/components/booking/LuxuryBookingModal.tsx create mode 100644 Frontend/src/components/payments/CashPaymentModal.tsx create mode 100644 Frontend/src/components/payments/DepositPaymentModal.tsx create mode 100644 Frontend/src/components/payments/PayPalPaymentModal.tsx create mode 100644 Frontend/src/components/payments/StripePaymentModal.tsx delete mode 100644 Frontend/src/pages/customer/BookingPage.tsx delete mode 100644 Frontend/src/pages/customer/DepositPaymentPage.tsx diff --git a/Backend/src/routes/__pycache__/booking_routes.cpython-312.pyc b/Backend/src/routes/__pycache__/booking_routes.cpython-312.pyc index dcb492648163a8f493e952f8290ec652f8fe6e62..ef2fbfa7f0f2b260cce5509ac953e69e80666237 100644 GIT binary patch delta 8328 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 GBL5BTa81_$ delta 8294 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 diff --git a/Backend/src/schemas/__pycache__/auth.cpython-312.pyc b/Backend/src/schemas/__pycache__/auth.cpython-312.pyc index 56c5532d4eb793539a93d1d3f91593a69baab2a8..a6dd20722d6200a73479c1fa129a3aa71293ede8 100644 GIT binary patch delta 1510 zcmZuwS!+{46uy(CP0Vi7o11Ql3uvfYEomioxvrJAwn`OojW?#~6rje`b3 zptg(|9yz?0Ea?+M=AInPx_r)!EVfG^Xf7P|LBO=wsgo<-EV)6(2@$UNVU0UP8sNR9 zZr7@ol-94L^tM$k$wWGFVwkp7F}W@=_$d4w6<*aw?rgZLOse1tfB zVUR2DPbHLeBCg{ZgYeYS5^iFvQDN3*{~$`^$!kfrc( zj4ZTzUPtg})|6cFVyKC5i`c z;F#decrejmB8J3x*Bb{haA+D1k`NOP9t@m3Io}in7TjciJ3I5u|NHj;%_c8?tun3{ z3|fv}Tm2s4llG%AD?3+BU*X=lN3p<7BWxx`mZC@4M28L;me@6o7EyW>MNzhmctMyKUW~q;eBZrfS?v2<^S!)f!|GlwtX;F#t?KJY zlU794WJ&u_r_QkKsJTqXZfQuuK)4oA2hafO0ha+yfGmoXCL)SsxEbF4fGR*C zpm85=Ql`Iz8cA3`JfM(mgBZ|B%d!LtnO0Z@6w_NWQ#|F8#U(R=xzd+a^X2QBl61~i zs$W3-h6bl1$3f|S@=Nd5K{_g-%bmSJ3Jv+oYleUdRS=K)3#uerlo!ZbOK(4Owvxs& z`JgukD|DF=tYYXYwgNH%K7isYI$#b^N_wq+)UiygZgl#nV#*Z5;&>=5%UFmcWM0C4 zvYKlj?4#SjUH(OCzN-f0YL(O9TLa4H+E|>$S7Ll z$h#Tr1YHcxIVR5H4~ZXiQ1QhBAP#6HL7Nv1?6v07`^GqwWPs38%P@#rd4=}*`Xyr?3Lx| zc-VAsYGd#UVCF>sEyfP*GOS_*vp@DK>|&>$8Ocrq>tYxdn9IxrRv}AqwDdpO_2XhRF~&FG1Sxd7^>$>L6LvZJ K*sUOKPVyI5S|Z;7 diff --git a/Backend/src/schemas/auth.py b/Backend/src/schemas/auth.py index a5b7e9da..661095be 100644 --- a/Backend/src/schemas/auth.py +++ b/Backend/src/schemas/auth.py @@ -21,8 +21,10 @@ class RegisterRequest(BaseModel): @validator('phone') def validate_phone(cls, v): - if v and (not v.isdigit()) or (v and len(v) not in [10, 11]): - raise ValueError('Phone must be 10-11 digits') + if v: + cleaned = ''.join(c for c in v if c.isdigit()) + if len(cleaned) < 5: + raise ValueError('Phone number must contain at least 5 digits') return v class LoginRequest(BaseModel): diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx index 9086bfd0..b566429b 100644 --- a/Frontend/src/App.tsx +++ b/Frontend/src/App.tsx @@ -42,10 +42,8 @@ const RoomDetailPage = lazy(() => import('./pages/customer/RoomDetailPage')); const SearchResultsPage = lazy(() => import('./pages/customer/SearchResultsPage')); const FavoritesPage = lazy(() => import('./pages/customer/FavoritesPage')); const MyBookingsPage = lazy(() => import('./pages/customer/MyBookingsPage')); -const BookingPage = lazy(() => import('./pages/customer/BookingPage')); const BookingSuccessPage = lazy(() => import('./pages/customer/BookingSuccessPage') as Promise<{ default: React.ComponentType }>); const BookingDetailPage = lazy(() => import('./pages/customer/BookingDetailPage')); -const DepositPaymentPage = lazy(() => import('./pages/customer/DepositPaymentPage')); const FullPaymentPage = lazy(() => import('./pages/customer/FullPaymentPage')); const PaymentConfirmationPage = lazy(() => import('./pages/customer/PaymentConfirmationPage')); const PaymentResultPage = lazy(() => import('./pages/customer/PaymentResultPage')); @@ -234,14 +232,6 @@ function App() { } /> - - - - } - /> } /> - - - - } - /> void; + onSuccess: () => void; +} + +const CancelBookingModal: React.FC = ({ + isOpen, + booking, + onClose, + onSuccess, +}) => { + const { formatCurrency } = useFormatCurrency(); + const [cancelling, setCancelling] = useState(false); + + if (!isOpen || !booking) return null; + + // Check if booking is fully paid + const isFullyPaid = (() => { + // Check payment_status first + if (booking.payment_status === 'paid') { + return true; + } + + // Check payment_balance + if (booking.payment_balance?.is_fully_paid === true) { + return true; + } + + // Check payments array - sum all completed payments + if (booking.payments && Array.isArray(booking.payments)) { + const totalPaid = booking.payments + .filter((p: any) => p.payment_status === 'completed') + .reduce((sum: number, p: any) => sum + parseFloat(p.amount?.toString() || '0'), 0); + + return totalPaid >= booking.total_price - 0.01; // Allow small rounding differences + } + + return false; + })(); + + const cancellationFee = booking.total_price * 0.2; + const refundAmount = isFullyPaid ? booking.total_price - cancellationFee : 0; + + const handleCancel = async () => { + try { + setCancelling(true); + const response = await cancelBooking(booking.id); + + if (response.success || (response as any).status === 'success') { + toast.error( + `Booking ${booking.booking_number} has been cancelled` + ); + onSuccess(); + onClose(); + } else { + throw new Error(response.message || 'Unable to cancel booking'); + } + } catch (err: any) { + console.error('Error cancelling booking:', err); + const message = + err.response?.data?.detail || + err.response?.data?.message || + err.message || + 'Unable to cancel booking. Please try again.'; + toast.error(message); + } finally { + setCancelling(false); + } + }; + + return ( +
+
+
+ {/* Header */} +
+
+
+
+ +
+
+

+ Cancel Booking +

+

+ Booking #{booking.booking_number} +

+
+
+ +
+
+ + {/* Content */} +
+ {/* Warning Message */} +
+
+ +
+

+ Are you sure you want to cancel this booking? +

+

+ This action cannot be undone. The room will be made available for other guests. +

+
+
+
+ + {/* Booking Details */} +
+

Booking Details

+
+
+ Room + + {booking.room?.room_number || 'N/A'} + +
+
+ Check-in + + {new Date(booking.check_in_date).toLocaleDateString('en-US')} + +
+
+ Check-out + + {new Date(booking.check_out_date).toLocaleDateString('en-US')} + +
+
+ Total Amount + + {formatCurrency(booking.total_price)} + +
+
+
+ + {/* Cancellation Policy */} +
+
+ +
+

+ Cancellation Policy +

+
+
+ Cancellation Fee (20%) + + -{formatCurrency(cancellationFee)} + +
+ {isFullyPaid && ( + <> +
+ Refund Amount (80%) + + {formatCurrency(refundAmount)} + +
+

+ The refund will be processed to your original payment method within 5-7 business days. +

+ + )} + {!isFullyPaid && ( +

+ Since this booking is not fully paid, no refund will be issued. The cancellation fee applies to the total booking amount. +

+ )} +
+
+
+
+ + {/* Action Buttons */} +
+ + +
+
+
+
+ ); +}; + +export default CancelBookingModal; + diff --git a/Frontend/src/components/booking/InvoiceInfoModal.tsx b/Frontend/src/components/booking/InvoiceInfoModal.tsx new file mode 100644 index 00000000..4b6fb49a --- /dev/null +++ b/Frontend/src/components/booking/InvoiceInfoModal.tsx @@ -0,0 +1,145 @@ +import React, { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { X, Building2, Save } from 'lucide-react'; + +interface InvoiceInfoModalProps { + isOpen: boolean; + onClose: () => void; + onSave: (invoiceInfo: any) => void; +} + +interface InvoiceFormData { + company_name: string; + company_address: string; + company_tax_id: string; + customer_tax_id: string; +} + +const InvoiceInfoModal: React.FC = ({ + isOpen, + onClose, + onSave, +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + company_name: '', + company_address: '', + company_tax_id: '', + customer_tax_id: '', + }, + }); + + const onSubmit = (data: InvoiceFormData) => { + onSave(data); + }; + + if (!isOpen) return null; + + return ( +
+
+
+ {/* Header */} +
+
+
+ +

+ Invoice Information +

+
+ +
+
+ + {/* Content */} +
+
+
+ + +
+ +
+ +