layout and styling changes for RentItem

This commit is contained in:
jackiettran
2026-01-01 17:17:02 -05:00
parent fd2312fe47
commit 9e41f328e0

View File

@@ -25,7 +25,10 @@ const validateDates = (
// Check if end datetime is after start datetime // Check if end datetime is after start datetime
if (endDateTime <= startDateTime) { if (endDateTime <= startDateTime) {
return { isValid: false, error: "End date/time must be after start date/time" }; return {
isValid: false,
error: "End date/time must be after start date/time",
};
} }
return { isValid: true, error: null }; return { isValid: true, error: null };
@@ -65,7 +68,9 @@ const RentItem: React.FC = () => {
const [costLoading, setCostLoading] = useState(false); const [costLoading, setCostLoading] = useState(false);
const [costError, setCostError] = useState<string | null>(null); const [costError, setCostError] = useState<string | null>(null);
const [completed, setCompleted] = useState(false); const [completed, setCompleted] = useState(false);
const [dateValidationError, setDateValidationError] = useState<string | null>(null); const [dateValidationError, setDateValidationError] = useState<string | null>(
null
);
const formatDateTime = (date: Date | null) => { const formatDateTime = (date: Date | null) => {
if (!date) return ""; if (!date) return "";
@@ -229,266 +234,272 @@ const RentItem: React.FC = () => {
} }
return ( return (
<div className="container mt-5"> <div style={{ backgroundColor: "#F7F9FB", minHeight: "100vh" }}>
<div className="row justify-content-center"> <div className="container pt-5">
<div className="col-md-10"> <div className="row justify-content-center">
<h1>Renting {item.name}</h1> <div className="col-md-10">
<h1>Complete Your Request</h1>
{/* Email Verification Warning Banner */} {/* Email Verification Warning Banner */}
{user && !user.isVerified && ( {user && !user.isVerified && (
<div className="alert alert-warning d-flex align-items-center mb-4"> <div className="alert alert-warning d-flex align-items-center mb-4">
<i className="bi bi-exclamation-triangle-fill me-2"></i> <i className="bi bi-exclamation-triangle-fill me-2"></i>
<div className="flex-grow-1"> <div className="flex-grow-1">
<strong>Email verification required.</strong> Verify your email <strong>Email verification required.</strong> Verify your
to book rentals. email to book rentals.
</div>
<button
className="btn btn-warning btn-sm ms-3"
onClick={() => setShowVerificationModal(true)}
>
Verify Now
</button>
</div> </div>
<button )}
className="btn btn-warning btn-sm ms-3"
onClick={() => setShowVerificationModal(true)}
>
Verify Now
</button>
</div>
)}
{error && ( {error && (
<div className="alert alert-danger" role="alert"> <div className="alert alert-danger" role="alert">
{error} {error}
</div> </div>
)} )}
<div className="row"> <div className="row">
{/* Pricing Card - appears first on mobile, right side on desktop */} {/* Pricing Card - appears first on mobile, right side on desktop */}
<div className="col-md-4 order-first order-md-last mb-4 mb-md-0"> <div className="col-md-4 order-first order-md-last mb-4 mb-md-0">
<div className="card"> <div className="card">
<div className="card-body"> <div className="card-body">
{item.imageFilenames && item.imageFilenames[0] && ( {item.imageFilenames && item.imageFilenames[0] && (
<img <img
src={getImageUrl(item.imageFilenames[0], 'medium')} src={getImageUrl(item.imageFilenames[0], "medium")}
alt={item.name} alt={item.name}
className="img-fluid rounded mb-3" className="img-fluid rounded mb-3"
onError={(e) => { onError={(e) => {
const target = e.currentTarget; const target = e.currentTarget;
if (!target.dataset.fallback) { if (!target.dataset.fallback) {
target.dataset.fallback = 'true'; target.dataset.fallback = "true";
target.src = getImageUrl(item.imageFilenames[0], 'original'); target.src = getImageUrl(
} item.imageFilenames[0],
}} "original"
style={{ );
width: "100%", }
height: "150px", }}
objectFit: "contain", style={{
backgroundColor: "#f8f9fa", width: "100%",
}} height: "150px",
/> objectFit: "contain",
)} backgroundColor: "#f8f9fa",
}}
<h6>{item.name}</h6> />
<p className="text-muted small">
{item.city && item.state
? `${item.city}, ${item.state}`
: ""}
</p>
<hr />
{/* Pricing */}
<div className="mb-3 text-center">
{totalCost === 0 ? (
<h6>Free to Borrow</h6>
) : (
<>
{item.pricePerHour && Number(item.pricePerHour) > 0 && (
<h6>${Math.floor(Number(item.pricePerHour))}/Hour</h6>
)}
{item.pricePerDay && Number(item.pricePerDay) > 0 && (
<h6>${Math.floor(Number(item.pricePerDay))}/Day</h6>
)}
</>
)} )}
</div>
{/* Selected Dates */} <h6>{item.name}</h6>
{rentalDates.startDateTime && rentalDates.endDateTime && ( <p className="text-muted small">
<div className="mb-3"> {item.city && item.state
<div className="small mb-1"> ? `${item.city}, ${item.state}`
<strong>Check-in:</strong>{" "} : ""}
{formatDateTime(rentalDates.startDateTime)} </p>
</div>
<div className="small">
<strong>Check-out:</strong>{" "}
{formatDateTime(rentalDates.endDateTime)}
</div>
</div>
)}
{/* Total Cost */}
<>
<hr /> <hr />
<div className="d-flex justify-content-between">
<strong>Total:</strong> {/* Pricing */}
{costLoading ? ( <div className="mb-3 text-center">
<div {totalCost === 0 ? (
className="spinner-border spinner-border-sm" <h6>Free to Borrow</h6>
role="status"
>
<span className="visually-hidden">
Calculating...
</span>
</div>
) : costError ? (
<small className="text-danger">Error</small>
) : totalCost > 0 ? (
<strong>${totalCost}</strong>
) : ( ) : (
<strong>$0</strong> <>
{item.pricePerHour &&
Number(item.pricePerHour) > 0 && (
<h6>
${Math.floor(Number(item.pricePerHour))}/Hour
</h6>
)}
{item.pricePerDay && Number(item.pricePerDay) > 0 && (
<h6>${Math.floor(Number(item.pricePerDay))}/Day</h6>
)}
</>
)} )}
</div> </div>
</>
{/* Selected Dates */}
{rentalDates.startDateTime && rentalDates.endDateTime && (
<div className="mb-3">
<div className="small mb-1">
<strong>Check-in:</strong>{" "}
{formatDateTime(rentalDates.startDateTime)}
</div>
<div className="small">
<strong>Check-out:</strong>{" "}
{formatDateTime(rentalDates.endDateTime)}
</div>
</div>
)}
{/* Total Cost */}
<>
<hr />
<div className="d-flex justify-content-between">
<strong>Total:</strong>
{costLoading ? (
<div
className="spinner-border spinner-border-sm"
role="status"
>
<span className="visually-hidden">
Calculating...
</span>
</div>
) : costError ? (
<small className="text-danger">Error</small>
) : totalCost > 0 ? (
<strong>${totalCost}</strong>
) : (
<strong>$0</strong>
)}
</div>
</>
</div>
</div> </div>
</div> </div>
</div>
{/* Form - appears second on mobile, left side on desktop */} {/* Form - appears second on mobile, left side on desktop */}
<div className="col-md-8"> <div className="col-md-8">
{completed ? ( {completed ? (
<div className="card mb-4"> <div className="mb-4">
<div className="card-body text-center"> <div className="text-center">
<div className="alert alert-success"> <div className="alert alert-success">
<i className="bi bi-check-circle-fill display-1 text-success mb-3"></i> <i className="bi bi-check-circle-fill display-1 text-success mb-3"></i>
<h3>Rental Request Sent!</h3> <h3>Rental Request Sent!</h3>
<p className="mb-3"> <p className="mb-3">
Your rental request has been submitted to the owner. Your rental request has been submitted to the owner.
You'll only be charged if they approve your request. You'll only be charged if they approve your request.
</p> </p>
<div className="d-grid gap-2 d-md-block"> <div className="d-grid gap-2 d-md-block">
<button <button
className="btn btn-primary me-2" className="btn btn-primary me-2"
onClick={() => navigate("/renting")} onClick={() => navigate("/renting")}
> >
View My Rentals View My Rentals
</button> </button>
<button <button
className="btn btn-outline-secondary" className="btn btn-outline-secondary"
onClick={() => navigate("/")} onClick={() => navigate("/")}
> >
Continue Browsing Continue Browsing
</button> </button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> ) : (
) : ( <div className="mb-4">
<div className="card mb-4"> <div>
<div className="card-body"> <div className="card mb-3">
<h5 className="card-title"> <div className="card-body">
{totalCost === 0 <h6 className="mb-3">
? "Complete Your Borrow Request" What will you use this for?{" "}
: "Complete Your Rental Request"} <span className="text-muted">(Optional)</span>
</h5> </h6>
{totalCost > 0 && ( <textarea
<p className="text-muted small mb-3"> id="intendedUse"
Add your payment method to complete your rental request. name="intendedUse"
You'll only be charged if the owner approves your className="form-control"
request. rows={3}
</p> value={formData.intendedUse}
)} onChange={handleChange}
placeholder="Let the owner know how you plan to use their item..."
<div className="mb-3"> maxLength={500}
<label htmlFor="intendedUse" className="form-label"> />
What will you use this for?{" "} <div className="form-text">
<span className="text-muted">(Optional)</span> {formData.intendedUse.length}/500 characters
</label> </div>
<textarea </div>
id="intendedUse"
name="intendedUse"
className="form-control"
rows={3}
value={formData.intendedUse}
onChange={handleChange}
placeholder="Let the owner know how you plan to use their item..."
maxLength={500}
/>
<div className="form-text">
{formData.intendedUse.length}/500 characters
</div> </div>
{dateValidationError ? (
<div className="alert alert-danger">
<i className="bi bi-exclamation-triangle me-2"></i>
{dateValidationError}
</div>
) : !rentalDates.startDateTime ||
!rentalDates.endDateTime ||
!getRentalData() ? (
<div className="alert alert-info">
<i className="bi bi-info-circle me-2"></i>
Please complete the rental dates and details above to
proceed with{" "}
{totalCost === 0
? "your borrow request"
: "payment setup"}
.
</div>
) : totalCost === 0 ? (
<>
<div className="d-grid gap-2">
<button
type="button"
className="btn btn-primary"
onClick={handleFreeBorrow}
>
Confirm Borrow Request
</button>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => navigate(`/items/${id}`)}
>
Cancel Request
</button>
</div>
</>
) : (
<>
<div className="card mb-3">
<div className="card-body">
<h6 className="mb-3">
Add your payment method to complete your
request. You'll only be charged if the owner
approves your request
</h6>
<EmbeddedStripeCheckout
rentalData={getRentalData()}
onSuccess={() => setCompleted(true)}
onError={(error) => setError(error)}
/>
</div>
</div>
<div className="text-center mt-3">
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => navigate(`/items/${id}`)}
>
Cancel Request
</button>
</div>
</>
)}
</div> </div>
{dateValidationError ? (
<div className="alert alert-danger">
<i className="bi bi-exclamation-triangle me-2"></i>
{dateValidationError}
</div>
) : !rentalDates.startDateTime ||
!rentalDates.endDateTime ||
!getRentalData() ? (
<div className="alert alert-info">
<i className="bi bi-info-circle me-2"></i>
Please complete the rental dates and details above to
proceed with{" "}
{totalCost === 0
? "your borrow request"
: "payment setup"}
.
</div>
) : totalCost === 0 ? (
<>
<div className="d-grid gap-2">
<button
type="button"
className="btn btn-primary"
onClick={handleFreeBorrow}
>
Confirm Borrow Request
</button>
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => navigate(`/items/${id}`)}
>
Cancel Request
</button>
</div>
</>
) : (
<>
<EmbeddedStripeCheckout
rentalData={getRentalData()}
onSuccess={() => setCompleted(true)}
onError={(error) => setError(error)}
/>
<div className="text-center mt-3">
<button
type="button"
className="btn btn-outline-secondary"
onClick={() => navigate(`/items/${id}`)}
>
Cancel Request
</button>
</div>
</>
)}
</div> </div>
</div> )}
)} </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{/* Email Verification Modal */} {/* Email Verification Modal */}
{user && ( {user && (
<VerificationCodeModal <VerificationCodeModal
show={showVerificationModal} show={showVerificationModal}
onHide={() => { onHide={() => {
setShowVerificationModal(false); setShowVerificationModal(false);
setPendingSubmit(false); setPendingSubmit(false);
}} }}
email={user.email || ""} email={user.email || ""}
onVerified={handleVerificationSuccess} onVerified={handleVerificationSuccess}
/> />
)} )}
</div>
</div> </div>
); );
}; };