Files
rentall-app/frontend/src/pages/RentItem.tsx

273 lines
8.4 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { useParams, useNavigate, useSearchParams } from "react-router-dom";
import { Item } from "../types";
import { useAuth } from "../contexts/AuthContext";
import { itemAPI, rentalAPI } from "../services/api";
import StripePaymentForm from "../components/StripePaymentForm";
const RentItem: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { user } = useAuth();
const [searchParams] = useSearchParams();
const [item, setItem] = useState<Item | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [formData, setFormData] = useState({
deliveryMethod: "pickup" as "pickup" | "delivery",
deliveryAddress: "",
});
const [manualSelection, setManualSelection] = useState({
startDate: searchParams.get("startDate") || "",
startTime: searchParams.get("startTime") || "09:00",
endDate: searchParams.get("endDate") || "",
endTime: searchParams.get("endTime") || "17:00",
});
const [totalCost, setTotalCost] = useState(0);
const formatDate = (dateString: string) => {
if (!dateString) return "";
return new Date(dateString).toLocaleDateString();
};
const formatTime = (timeString: string) => {
if (!timeString) return "";
const [hour, minute] = timeString.split(":");
const hour12 =
parseInt(hour) === 0
? 12
: parseInt(hour) > 12
? parseInt(hour) - 12
: parseInt(hour);
const period = parseInt(hour) < 12 ? "AM" : "PM";
return `${hour12}:${minute} ${period}`;
};
const calculateTotalCost = () => {
if (!item || !manualSelection.startDate || !manualSelection.endDate) {
setTotalCost(0);
return;
}
const startDateTime = new Date(
`${manualSelection.startDate}T${manualSelection.startTime}`
);
const endDateTime = new Date(
`${manualSelection.endDate}T${manualSelection.endTime}`
);
const diffMs = endDateTime.getTime() - startDateTime.getTime();
const diffHours = Math.ceil(diffMs / (1000 * 60 * 60));
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
let cost = 0;
if (item.pricePerHour && diffHours <= 24) {
cost = diffHours * Number(item.pricePerHour);
} else if (item.pricePerDay) {
cost = diffDays * Number(item.pricePerDay);
}
setTotalCost(cost);
};
useEffect(() => {
fetchItem();
}, [id]);
useEffect(() => {
calculateTotalCost();
}, [item, manualSelection]);
const fetchItem = async () => {
try {
const response = await itemAPI.getItem(id!);
setItem(response.data);
// Check if item is available
if (!response.data.availability) {
setError("This item is not available for rent");
}
// Check if user is trying to rent their own item
if (response.data.ownerId === user?.id) {
setError("You cannot rent your own item");
}
} catch (err: any) {
setError(err.response?.data?.message || "Failed to fetch item");
} finally {
setLoading(false);
}
};
const handlePaymentSuccess = () => {
console.log("Stripe checkout session created successfully");
};
const handleChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
if (loading) {
return (
<div className="container mt-5">
<div className="text-center">
<div className="spinner-border" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
</div>
);
}
if (
!item ||
error === "You cannot rent your own item" ||
error === "This item is not available for rent"
) {
return (
<div className="container mt-5">
<div className="alert alert-danger" role="alert">
{error || "Item not found"}
</div>
<button className="btn btn-secondary" onClick={() => navigate(-1)}>
Go Back
</button>
</div>
);
}
return (
<div className="container mt-4">
<div className="row">
<div className="col-md-8">
<h1>Renting {item.name}</h1>
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<div className="row">
<div className="col-md-8">
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">Payment</h5>
<StripePaymentForm
total={totalCost}
itemName={item.name}
rentalData={{
itemId: item.id,
startDate: manualSelection.startDate,
endDate: manualSelection.endDate,
startTime: manualSelection.startTime,
endTime: manualSelection.endTime,
totalAmount: totalCost,
deliveryMethod: "pickup",
}}
onSuccess={handlePaymentSuccess}
onError={(error) => setError(error)}
disabled={
!manualSelection.startDate || !manualSelection.endDate
}
/>
<button
type="button"
className="btn btn-secondary mt-2"
onClick={() => navigate(`/items/${id}`)}
>
Cancel
</button>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card">
<div className="card-body">
{item.images && item.images[0] && (
<img
src={item.images[0]}
alt={item.name}
className="img-fluid rounded mb-3"
style={{
width: "100%",
height: "150px",
objectFit: "cover",
}}
/>
)}
<h6>{item.name}</h6>
<p className="text-muted small">
{item.city && item.state
? `${item.city}, ${item.state}`
: item.location}
</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 */}
{totalCost > 0 && (
<>
<hr />
<div className="d-flex justify-content-between">
<strong>Total:</strong>
<strong>${totalCost}</strong>
</div>
</>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default RentItem;