fixed sticky bottom pricing card for mobile
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user