rental price calculation bug, sticky pricing cards on mobile, bigger font app wide, removed delivery options from frontened, searching by location with zipcode works when there's multiple zipcodes in the area,
This commit is contained in:
@@ -33,8 +33,8 @@ class RentalDurationCalculator {
|
||||
// Calculate base amount based on duration (tiered pricing)
|
||||
let totalAmount;
|
||||
|
||||
if (item.pricePerHour && diffHours <= 24) {
|
||||
// Use hourly rate for rentals <= 24 hours
|
||||
if (item.pricePerHour && diffHours < 24) {
|
||||
// Use hourly rate for rentals under 24 hours
|
||||
totalAmount = diffHours * Number(item.pricePerHour);
|
||||
} else if (diffDays <= 7 && item.pricePerDay) {
|
||||
// Use daily rate for rentals <= 7 days
|
||||
|
||||
@@ -55,3 +55,60 @@ main {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Sticky Bottom Bar for Item Detail */
|
||||
.mobile-sticky-bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
/* Mobile-specific styles */
|
||||
@media (max-width: 767.98px) {
|
||||
/* Make sticky card non-sticky on mobile */
|
||||
.sticky-pricing-card {
|
||||
position: static !important;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Pricing card input sizing - applies to all screen sizes */
|
||||
.sticky-pricing-card .form-label {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sticky-pricing-card .input-group-lg .form-control,
|
||||
.sticky-pricing-card .input-group-lg .form-select {
|
||||
font-size: 1rem;
|
||||
padding: 12px 16px;
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
/* Style the date input specifically */
|
||||
.sticky-pricing-card input[type="date"] {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Make the calendar icon larger on webkit browsers */
|
||||
.sticky-pricing-card input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Time select dropdown */
|
||||
.sticky-pricing-card .time-select {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Style options in the time dropdown */
|
||||
.sticky-pricing-card .time-select option {
|
||||
font-size: 1rem;
|
||||
padding: 8px;
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DeliveryOptionsProps {
|
||||
pickUpAvailable: boolean;
|
||||
inPlaceUseAvailable: boolean;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
const DeliveryOptions: React.FC<DeliveryOptionsProps> = ({
|
||||
pickUpAvailable,
|
||||
inPlaceUseAvailable,
|
||||
onChange
|
||||
}) => {
|
||||
return (
|
||||
<div className="card mb-4">
|
||||
<div className="card-body">
|
||||
<label className="form-label">
|
||||
How will renters receive this item?
|
||||
</label>
|
||||
<div className="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
id="pickUpAvailable"
|
||||
name="pickUpAvailable"
|
||||
checked={pickUpAvailable}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="pickUpAvailable">
|
||||
Pick-Up/Drop-off
|
||||
<div className="small text-muted">
|
||||
You and the renter coordinate pick-up and drop-off
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
id="inPlaceUseAvailable"
|
||||
name="inPlaceUseAvailable"
|
||||
checked={inPlaceUseAvailable}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="inPlaceUseAvailable"
|
||||
>
|
||||
In-Place Use
|
||||
<div className="small text-muted">
|
||||
They use at your location
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeliveryOptions;
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { mapsAPI } from "../services/api";
|
||||
|
||||
interface LocationPromptModalProps {
|
||||
show: boolean;
|
||||
onClose: () => void;
|
||||
onLocationSelect: (
|
||||
location: { lat: number; lng: number } | { city?: string; zipCode?: string }
|
||||
) => void;
|
||||
onLocationSelect: (location: { lat: number; lng: number }) => void;
|
||||
}
|
||||
|
||||
const LocationPromptModal: React.FC<LocationPromptModalProps> = ({
|
||||
@@ -53,15 +52,31 @@ const LocationPromptModal: React.FC<LocationPromptModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleManualSubmit = () => {
|
||||
const handleManualSubmit = async () => {
|
||||
const trimmed = manualLocation.trim();
|
||||
if (!trimmed) return;
|
||||
|
||||
// Check if it looks like a ZIP code
|
||||
if (/^\d{5}(-\d{4})?$/.test(trimmed)) {
|
||||
onLocationSelect({ zipCode: trimmed });
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// Geocode the input (works for both ZIP codes and city names)
|
||||
const response = await mapsAPI.geocode({
|
||||
address: trimmed,
|
||||
componentRestrictions: { country: "US" },
|
||||
});
|
||||
|
||||
const { latitude, longitude } = response.data;
|
||||
|
||||
if (latitude && longitude) {
|
||||
onLocationSelect({ lat: latitude, lng: longitude });
|
||||
} else {
|
||||
onLocationSelect({ city: trimmed });
|
||||
setError("Could not find that location. Please try a different city or ZIP code.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError("Could not find that location. Please try a different city or ZIP code.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
html {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
|
||||
@@ -48,7 +48,6 @@ export const mockRental = {
|
||||
totalAmount: 25,
|
||||
status: 'pending' as const,
|
||||
paymentStatus: 'pending' as const,
|
||||
deliveryMethod: 'pickup' as const,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ import AvailabilitySettings from "../components/AvailabilitySettings";
|
||||
import ImageUpload from "../components/ImageUpload";
|
||||
import ItemInformation from "../components/ItemInformation";
|
||||
import LocationForm from "../components/LocationForm";
|
||||
import DeliveryOptions from "../components/DeliveryOptions";
|
||||
import PricingForm from "../components/PricingForm";
|
||||
import RulesForm from "../components/RulesForm";
|
||||
import VerificationCodeModal from "../components/VerificationCodeModal";
|
||||
@@ -17,8 +16,6 @@ import { IMAGE_LIMITS } from "../config/imageLimits";
|
||||
interface ItemFormData {
|
||||
name: string;
|
||||
description: string;
|
||||
pickUpAvailable: boolean;
|
||||
inPlaceUseAvailable: boolean;
|
||||
pricePerHour?: number | string;
|
||||
pricePerDay?: number | string;
|
||||
pricePerWeek?: number | string;
|
||||
@@ -57,8 +54,6 @@ const CreateItem: React.FC = () => {
|
||||
const [formData, setFormData] = useState<ItemFormData>({
|
||||
name: "",
|
||||
description: "",
|
||||
pickUpAvailable: false,
|
||||
inPlaceUseAvailable: false,
|
||||
pricePerDay: "",
|
||||
replacementCost: "",
|
||||
address1: "",
|
||||
@@ -539,12 +534,6 @@ const CreateItem: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeliveryOptions
|
||||
pickUpAvailable={formData.pickUpAvailable}
|
||||
inPlaceUseAvailable={formData.inPlaceUseAvailable}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
{/* Availability Card */}
|
||||
<div className="card mb-4">
|
||||
<div className="card-body">
|
||||
|
||||
@@ -8,7 +8,6 @@ import AvailabilitySettings from "../components/AvailabilitySettings";
|
||||
import ImageUpload from "../components/ImageUpload";
|
||||
import ItemInformation from "../components/ItemInformation";
|
||||
import LocationForm from "../components/LocationForm";
|
||||
import DeliveryOptions from "../components/DeliveryOptions";
|
||||
import PricingForm from "../components/PricingForm";
|
||||
import RulesForm from "../components/RulesForm";
|
||||
import { IMAGE_LIMITS } from "../config/imageLimits";
|
||||
@@ -16,8 +15,6 @@ import { IMAGE_LIMITS } from "../config/imageLimits";
|
||||
interface ItemFormData {
|
||||
name: string;
|
||||
description: string;
|
||||
pickUpAvailable: boolean;
|
||||
inPlaceUseAvailable: boolean;
|
||||
pricePerHour?: number | string;
|
||||
pricePerDay?: number | string;
|
||||
pricePerWeek?: number | string;
|
||||
@@ -78,8 +75,6 @@ const EditItem: React.FC = () => {
|
||||
const [formData, setFormData] = useState<ItemFormData>({
|
||||
name: "",
|
||||
description: "",
|
||||
pickUpAvailable: false,
|
||||
inPlaceUseAvailable: false,
|
||||
pricePerHour: "",
|
||||
pricePerDay: "",
|
||||
replacementCost: "",
|
||||
@@ -135,8 +130,6 @@ const EditItem: React.FC = () => {
|
||||
setFormData({
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
pickUpAvailable: item.pickUpAvailable || false,
|
||||
inPlaceUseAvailable: item.inPlaceUseAvailable || false,
|
||||
pricePerHour: item.pricePerHour || "",
|
||||
pricePerDay: item.pricePerDay || "",
|
||||
pricePerWeek: item.pricePerWeek || "",
|
||||
@@ -578,12 +571,6 @@ const EditItem: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeliveryOptions
|
||||
pickUpAvailable={formData.pickUpAvailable}
|
||||
inPlaceUseAvailable={formData.inPlaceUseAvailable}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
|
||||
<PricingForm
|
||||
pricePerHour={formData.pricePerHour || ""}
|
||||
pricePerDay={formData.pricePerDay || ""}
|
||||
|
||||
@@ -531,6 +531,7 @@ const ItemDetail: React.FC = () => {
|
||||
<div className="col-md-4">
|
||||
<div
|
||||
className="card sticky-pricing-card"
|
||||
id="pricing-card"
|
||||
style={{
|
||||
position: "sticky",
|
||||
top: "20px",
|
||||
@@ -610,9 +611,9 @@ const ItemDetail: React.FC = () => {
|
||||
<>
|
||||
<hr />
|
||||
<div className="text-start">
|
||||
<div className="mb-2">
|
||||
<label className="form-label small mb-1">Start</label>
|
||||
<div className="input-group input-group-sm">
|
||||
<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"
|
||||
@@ -627,7 +628,7 @@ const ItemDetail: React.FC = () => {
|
||||
style={{ flex: "1 1 50%" }}
|
||||
/>
|
||||
<select
|
||||
className="form-select"
|
||||
className="form-select time-select"
|
||||
value={rentalDates.startTime}
|
||||
onChange={(e) =>
|
||||
handleDateTimeChange(
|
||||
@@ -661,8 +662,8 @@ const ItemDetail: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label small mb-1">End</label>
|
||||
<div className="input-group input-group-sm">
|
||||
<label className="form-label fw-medium mb-2">End</label>
|
||||
<div className="input-group input-group-lg">
|
||||
<input
|
||||
type="date"
|
||||
className="form-control"
|
||||
@@ -677,7 +678,7 @@ const ItemDetail: React.FC = () => {
|
||||
style={{ flex: "1 1 50%" }}
|
||||
/>
|
||||
<select
|
||||
className="form-select"
|
||||
className="form-select time-select"
|
||||
value={rentalDates.endTime}
|
||||
onChange={(e) =>
|
||||
handleDateTimeChange("endTime", e.target.value)
|
||||
@@ -787,6 +788,62 @@ const ItemDetail: React.FC = () => {
|
||||
reasonPlaceholder="Enter reason for deletion (e.g., policy violation, inappropriate content)"
|
||||
reasonRequired={true}
|
||||
/>
|
||||
|
||||
{/* Mobile Sticky Bottom Bar */}
|
||||
{!isOwner && item.isAvailable && (
|
||||
<div className="mobile-sticky-bottom-bar d-md-none">
|
||||
<div className="d-flex align-items-center justify-content-between">
|
||||
<div className="price-display">
|
||||
{(() => {
|
||||
const prices = [
|
||||
{ value: Number(item.pricePerHour), label: "/hour" },
|
||||
{ value: Number(item.pricePerDay), label: "/day" },
|
||||
{ value: Number(item.pricePerWeek), label: "/week" },
|
||||
{ value: Number(item.pricePerMonth), label: "/month" },
|
||||
].filter((p) => p.value > 0);
|
||||
|
||||
if (prices.length === 0) {
|
||||
return <span className="fw-bold">Free to Borrow</span>;
|
||||
}
|
||||
|
||||
const lowestPrice = prices[0];
|
||||
return (
|
||||
<span>
|
||||
<span className="fw-bold fs-5">
|
||||
${Math.floor(lowestPrice.value)}
|
||||
</span>
|
||||
<span className="text-muted">{lowestPrice.label}</span>
|
||||
</span>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
{isAlreadyRenting ? (
|
||||
<button
|
||||
className="btn btn-success"
|
||||
disabled
|
||||
style={{ opacity: 0.8 }}
|
||||
>
|
||||
Renting
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-primary btn-lg"
|
||||
onClick={() => {
|
||||
const pricingCard = document.getElementById("pricing-card");
|
||||
if (pricingCard) {
|
||||
pricingCard.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Check Availability
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ const ItemList: React.FC = () => {
|
||||
if (locationCheckDone.current) return;
|
||||
|
||||
const hasLocation = searchParams.has("lat") || searchParams.has("city") || searchParams.has("zipCode");
|
||||
const hasSearchTerm = searchParams.has("search");
|
||||
|
||||
if (!hasLocation) {
|
||||
// Check user's saved address for lat/lng
|
||||
@@ -46,10 +47,13 @@ const ItemList: React.FC = () => {
|
||||
params.set("radius", "25");
|
||||
locationCheckDone.current = true;
|
||||
navigate(`/items?${params.toString()}`, { replace: true });
|
||||
} else {
|
||||
// No saved address with coordinates - show location prompt
|
||||
} else if (!hasSearchTerm) {
|
||||
// No saved address and no search term - show location prompt
|
||||
locationCheckDone.current = true;
|
||||
setShowLocationPrompt(true);
|
||||
} else {
|
||||
// Has search term but no location - just show results without location filter
|
||||
locationCheckDone.current = true;
|
||||
}
|
||||
} else {
|
||||
locationCheckDone.current = true;
|
||||
@@ -72,26 +76,15 @@ const ItemList: React.FC = () => {
|
||||
});
|
||||
}, [searchParams]);
|
||||
|
||||
const handleLocationSelect = (
|
||||
location: { lat: number; lng: number } | { city?: string; zipCode?: string }
|
||||
) => {
|
||||
const handleLocationSelect = (location: { lat: number; lng: number }) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
|
||||
if ("lat" in location) {
|
||||
params.set("lat", location.lat.toString());
|
||||
params.set("lng", location.lng.toString());
|
||||
params.set("radius", "25");
|
||||
// Remove city/zipCode if using coordinates
|
||||
// Remove city/zipCode since we're using coordinates
|
||||
params.delete("city");
|
||||
params.delete("zipCode");
|
||||
} else {
|
||||
if (location.city) params.set("city", location.city);
|
||||
if (location.zipCode) params.set("zipCode", location.zipCode);
|
||||
// Remove lat/lng if using city/zip
|
||||
params.delete("lat");
|
||||
params.delete("lng");
|
||||
params.delete("radius");
|
||||
}
|
||||
|
||||
navigate(`/items?${params.toString()}`, { replace: true });
|
||||
setShowLocationPrompt(false);
|
||||
|
||||
@@ -19,8 +19,6 @@ const RentItem: React.FC = () => {
|
||||
const [pendingSubmit, setPendingSubmit] = useState(false);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
deliveryMethod: "pickup" as "pickup" | "delivery",
|
||||
deliveryAddress: "",
|
||||
intendedUse: "",
|
||||
});
|
||||
|
||||
@@ -146,8 +144,6 @@ const RentItem: React.FC = () => {
|
||||
itemId: id,
|
||||
startDateTime,
|
||||
endDateTime,
|
||||
deliveryMethod: formData.deliveryMethod,
|
||||
deliveryAddress: formData.deliveryAddress,
|
||||
intendedUse: formData.intendedUse || undefined,
|
||||
totalAmount: totalCost,
|
||||
};
|
||||
@@ -230,9 +226,9 @@ const RentItem: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
<div className="row">
|
||||
<div className="col-md-8">
|
||||
<div className="container mt-5">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-10">
|
||||
<h1>Renting {item.name}</h1>
|
||||
|
||||
{/* Email Verification Warning Banner */}
|
||||
@@ -259,6 +255,93 @@ const RentItem: React.FC = () => {
|
||||
)}
|
||||
|
||||
<div className="row">
|
||||
{/* 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="card">
|
||||
<div className="card-body">
|
||||
{item.imageFilenames && item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[0])}
|
||||
alt={item.name}
|
||||
className="img-fluid rounded mb-3"
|
||||
style={{
|
||||
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 */}
|
||||
{manualSelection.startDate && manualSelection.endDate && (
|
||||
<div className="mb-3">
|
||||
<div className="small mb-1">
|
||||
<strong>Check-in:</strong>{" "}
|
||||
{formatDate(manualSelection.startDate)} at{" "}
|
||||
{formatTime(manualSelection.startTime)}
|
||||
</div>
|
||||
<div className="small">
|
||||
<strong>Check-out:</strong>{" "}
|
||||
{formatDate(manualSelection.endDate)} at{" "}
|
||||
{formatTime(manualSelection.endTime)}
|
||||
</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>
|
||||
|
||||
{/* Form - appears second on mobile, left side on desktop */}
|
||||
<div className="col-md-8">
|
||||
{completed ? (
|
||||
<div className="card mb-4">
|
||||
@@ -377,91 +460,6 @@ const RentItem: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-md-4">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
{item.imageFilenames && item.imageFilenames[0] && (
|
||||
<img
|
||||
src={getPublicImageUrl(item.imageFilenames[0])}
|
||||
alt={item.name}
|
||||
className="img-fluid rounded mb-3"
|
||||
style={{
|
||||
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 */}
|
||||
{manualSelection.startDate && manualSelection.endDate && (
|
||||
<div className="mb-3">
|
||||
<div className="small mb-1">
|
||||
<strong>Check-in:</strong>{" "}
|
||||
{formatDate(manualSelection.startDate)} at{" "}
|
||||
{formatTime(manualSelection.startTime)}
|
||||
</div>
|
||||
<div className="small">
|
||||
<strong>Check-out:</strong>{" "}
|
||||
{formatDate(manualSelection.endDate)} at{" "}
|
||||
{formatTime(manualSelection.endTime)}
|
||||
</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>
|
||||
|
||||
@@ -66,11 +66,6 @@ export interface Item {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
pickUpAvailable?: boolean;
|
||||
localDeliveryAvailable?: boolean;
|
||||
localDeliveryRadius?: number;
|
||||
shippingAvailable?: boolean;
|
||||
inPlaceUseAvailable?: boolean;
|
||||
pricePerHour?: number;
|
||||
pricePerDay?: number;
|
||||
pricePerWeek?: number;
|
||||
@@ -147,8 +142,6 @@ export interface Rental {
|
||||
payoutStatus?: "pending" | "completed" | "failed" | null;
|
||||
payoutProcessedAt?: string;
|
||||
stripeTransferId?: string;
|
||||
deliveryMethod: "pickup" | "delivery";
|
||||
deliveryAddress?: string;
|
||||
intendedUse?: string;
|
||||
rating?: number;
|
||||
review?: string;
|
||||
|
||||
Reference in New Issue
Block a user