fixed sticky bottom pricing card for mobile

This commit is contained in:
jackiettran
2025-12-30 17:35:48 -05:00
parent 4bb4e7bcb6
commit e3acf45ba0
3 changed files with 230 additions and 4 deletions

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"

View File

@@ -64,6 +64,7 @@ main {
right: 0; right: 0;
background: white; background: white;
padding: 12px 16px; padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000; z-index: 1000;
border-top: 1px solid #e0e0e0; border-top: 1px solid #e0e0e0;
@@ -74,7 +75,7 @@ main {
/* Make sticky card non-sticky on mobile */ /* Make sticky card non-sticky on mobile */
.sticky-pricing-card { .sticky-pricing-card {
position: static !important; position: static !important;
margin-bottom: 80px; margin-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
} }
} }

View File

@@ -525,10 +525,235 @@ const ItemDetail: React.FC = () => {
<div>Replacement Cost: ${item.replacementCost}</div> <div>Replacement Cost: ${item.replacementCost}</div>
</div> </div>
</div> </div>
{/* Mobile Pricing Card - shown inline on mobile */}
<div className="d-md-none mb-4">
<div className="card sticky-pricing-card">
<div className="card-body text-center">
{(() => {
const hasAnyPositivePrice =
(item.pricePerHour !== undefined &&
Number(item.pricePerHour) > 0) ||
(item.pricePerDay !== undefined &&
Number(item.pricePerDay) > 0) ||
(item.pricePerWeek !== undefined &&
Number(item.pricePerWeek) > 0) ||
(item.pricePerMonth !== undefined &&
Number(item.pricePerMonth) > 0);
const hasAnyZeroPrice =
(item.pricePerHour !== undefined &&
Number(item.pricePerHour) === 0) ||
(item.pricePerDay !== undefined &&
Number(item.pricePerDay) === 0) ||
(item.pricePerWeek !== undefined &&
Number(item.pricePerWeek) === 0) ||
(item.pricePerMonth !== undefined &&
Number(item.pricePerMonth) === 0);
if (!hasAnyPositivePrice && hasAnyZeroPrice) {
return (
<div className="mb-4">
<h4>Free to Borrow</h4>
</div>
);
}
return (
<>
{item.pricePerHour !== undefined &&
Number(item.pricePerHour) > 0 && (
<div className="mb-2">
<h4>
${Math.floor(Number(item.pricePerHour))}/Hour
</h4>
</div>
)}
{item.pricePerDay !== undefined &&
Number(item.pricePerDay) > 0 && (
<div className="mb-2">
<h4>
${Math.floor(Number(item.pricePerDay))}/Day
</h4>
</div>
)}
{item.pricePerWeek !== undefined &&
Number(item.pricePerWeek) > 0 && (
<div className="mb-2">
<h4>
${Math.floor(Number(item.pricePerWeek))}/Week
</h4>
</div>
)}
{item.pricePerMonth !== undefined &&
Number(item.pricePerMonth) > 0 && (
<div className="mb-2">
<h4>
${Math.floor(Number(item.pricePerMonth))}/Month
</h4>
</div>
)}
</>
);
})()}
{/* Rental Period Selection - Only show for non-owners */}
{!isOwner && item.isAvailable && !isAlreadyRenting && (
<>
<hr />
<div className="text-start">
<div className="mb-3">
<label className="form-label fw-medium mb-2">Start</label>
<div className="input-group input-group-lg">
<input
type="date"
className="form-control"
value={rentalDates.startDate}
onChange={(e) =>
handleDateTimeChange(
"startDate",
e.target.value
)
}
min={new Date().toLocaleDateString()}
style={{ flex: "1 1 50%" }}
/>
<select
className="form-select time-select"
value={rentalDates.startTime}
onChange={(e) =>
handleDateTimeChange(
"startTime",
e.target.value
)
}
style={{ flex: "1 1 50%" }}
disabled={
!!(
rentalDates.startDate &&
generateTimeOptions(
item,
rentalDates.startDate
).every(
(opt) => opt.label === "Not Available"
)
)
}
>
{generateTimeOptions(
item,
rentalDates.startDate
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
</div>
<div className="mb-3">
<label className="form-label fw-medium mb-2">End</label>
<div className="input-group input-group-lg">
<input
type="date"
className="form-control"
value={rentalDates.endDate}
onChange={(e) =>
handleDateTimeChange("endDate", e.target.value)
}
min={
rentalDates.startDate ||
new Date().toLocaleDateString()
}
style={{ flex: "1 1 50%" }}
/>
<select
className="form-select time-select"
value={rentalDates.endTime}
onChange={(e) =>
handleDateTimeChange("endTime", e.target.value)
}
style={{ flex: "1 1 50%" }}
disabled={
!!(
(rentalDates.endDate ||
rentalDates.startDate) &&
generateTimeOptions(
item,
rentalDates.endDate || rentalDates.startDate
).every(
(opt) => opt.label === "Not Available"
)
)
}
>
{generateTimeOptions(
item,
rentalDates.endDate || rentalDates.startDate
).map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
</div>
{rentalDates.startDate && rentalDates.endDate && (
<div className="mb-3 p-2 bg-light rounded text-center">
{costLoading ? (
<div
className="spinner-border spinner-border-sm"
role="status"
>
<span className="visually-hidden">
Calculating...
</span>
</div>
) : costError ? (
<small className="text-danger">{costError}</small>
) : totalCost > 0 ? (
<strong>Total: ${totalCost}</strong>
) : null}
</div>
)}
</div>
</>
)}
{/* Action Buttons */}
{!isOwner && item.isAvailable && !isAlreadyRenting && (
<div className="d-grid">
<button
className="btn btn-primary"
onClick={handleRent}
disabled={
!rentalDates.startDate || !rentalDates.endDate
}
>
Rent Now
</button>
</div>
)}
{!isOwner && isAlreadyRenting && (
<div className="d-grid">
<button
className="btn btn-success"
disabled
style={{ opacity: 0.8 }}
>
Renting
</button>
</div>
)}
</div>
</div>
</div>
</div> </div>
{/* Right Side - Sticky Pricing Card */} {/* Right Side - Sticky Pricing Card (hidden on mobile, shown on desktop) */}
<div className="col-md-4"> <div className="col-md-4 d-none d-md-block">
<div <div
className="card sticky-pricing-card" className="card sticky-pricing-card"
id="pricing-card" id="pricing-card"