date selection

This commit is contained in:
jackiettran
2025-07-18 00:31:23 -04:00
parent 1dbe821e70
commit f289022b5d
9 changed files with 852 additions and 253 deletions

View File

@@ -435,7 +435,7 @@ const CreateItem: React.FC = () => {
onPeriodsChange={(periods) =>
setFormData(prev => ({ ...prev, unavailablePeriods: periods }))
}
priceType={priceType}
mode="owner"
/>
</div>

View File

@@ -546,7 +546,7 @@ const EditItem: React.FC = () => {
const userPeriods = periods.filter(p => !p.isAcceptedRental);
setFormData(prev => ({ ...prev, unavailablePeriods: userPeriods }));
}}
priceType={priceType}
mode="owner"
/>
</div>

View File

@@ -18,6 +18,9 @@ const ItemDetail: React.FC = () => {
useEffect(() => {
fetchItem();
}, [id]);
useEffect(() => {
if (user) {
checkIfAlreadyRenting();
}
@@ -207,7 +210,7 @@ const ItemDetail: React.FC = () => {
<ItemReviews itemId={item.id} />
<div className="d-flex gap-2">
<div className="d-flex gap-2 mb-5">
{isOwner ? (
<button className="btn btn-primary" onClick={handleEdit}>
Edit Listing

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const Login: React.FC = () => {
@@ -9,6 +9,9 @@ const Login: React.FC = () => {
const [loading, setLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -17,7 +20,7 @@ const Login: React.FC = () => {
try {
await login(email, password);
navigate('/');
navigate(from, { replace: true });
} catch (err: any) {
setError(err.response?.data?.error || 'Failed to login');
} finally {
@@ -75,7 +78,7 @@ const Login: React.FC = () => {
<div className="text-center mt-3">
<p className="mb-0">
Don't have an account?{' '}
<Link to="/register" className="text-decoration-none">
<Link to="/register" state={{ from: location.state?.from }} className="text-decoration-none">
Sign up
</Link>
</p>

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const Register: React.FC = () => {
@@ -15,6 +15,9 @@ const Register: React.FC = () => {
const [loading, setLoading] = useState(false);
const { register } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({
@@ -30,7 +33,7 @@ const Register: React.FC = () => {
try {
await register(formData);
navigate('/');
navigate(from, { replace: true });
} catch (err: any) {
setError(err.response?.data?.error || 'Failed to create account');
} finally {

View File

@@ -22,6 +22,13 @@ const RentItem: React.FC = () => {
cardCVC: '',
cardName: ''
});
const [manualSelection, setManualSelection] = useState({
startDate: '',
startTime: '09:00',
endDate: '',
endTime: '17:00'
});
const [selectedPeriods, setSelectedPeriods] = useState<Array<{
id: string;
@@ -41,6 +48,48 @@ const RentItem: React.FC = () => {
useEffect(() => {
calculateTotal();
}, [selectedPeriods, item]);
useEffect(() => {
// Sync manual selection with selected periods
if (selectedPeriods.length > 0) {
const period = selectedPeriods[0];
// Extract hours from the Date objects if startTime/endTime not provided
let startTimeStr = period.startTime;
let endTimeStr = period.endTime;
if (!startTimeStr) {
const startHour = period.startDate.getHours();
startTimeStr = `${startHour.toString().padStart(2, '0')}:00`;
}
if (!endTimeStr) {
const endHour = period.endDate.getHours();
// If the end hour is 23:59:59, show it as 00:00 of the next day
if (endHour === 23 && period.endDate.getMinutes() === 59) {
endTimeStr = '00:00';
// Adjust the end date to show the next day
const adjustedEndDate = new Date(period.endDate);
adjustedEndDate.setDate(adjustedEndDate.getDate() + 1);
setManualSelection({
startDate: period.startDate.toISOString().split('T')[0],
startTime: startTimeStr,
endDate: adjustedEndDate.toISOString().split('T')[0],
endTime: endTimeStr
});
return;
} else {
endTimeStr = `${endHour.toString().padStart(2, '0')}:00`;
}
}
setManualSelection({
startDate: period.startDate.toISOString().split('T')[0],
startTime: startTimeStr,
endDate: period.endDate.toISOString().split('T')[0],
endTime: endTimeStr
});
}
}, [selectedPeriods]);
const fetchItem = async () => {
try {
@@ -176,6 +225,59 @@ const RentItem: React.FC = () => {
setFormData(prev => ({ ...prev, [name]: value }));
}
};
const handleManualSelectionChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const updatedSelection = {
...manualSelection,
[e.target.name]: e.target.value
};
setManualSelection(updatedSelection);
// Automatically apply selection if both dates are set
if (updatedSelection.startDate && updatedSelection.endDate) {
// Create dates with time set to midnight to avoid timezone issues
const start = new Date(updatedSelection.startDate + 'T00:00:00');
const end = new Date(updatedSelection.endDate + 'T00:00:00');
// Add time for both hourly and daily rentals
const [startHour, startMin] = updatedSelection.startTime.split(':').map(Number);
const [endHour, endMin] = updatedSelection.endTime.split(':').map(Number);
start.setHours(startHour, startMin, 0, 0);
end.setHours(endHour, endMin, 0, 0);
// Note: We keep the times as selected by the user
// The calendar will interpret 00:00 correctly
// Validate dates
if (end < start) {
setError('End date/time must be after start date/time');
return;
}
// Check if dates are available
const unavailable = item?.unavailablePeriods?.some(period => {
const periodStart = new Date(period.startDate);
const periodEnd = new Date(period.endDate);
return (start >= periodStart && start <= periodEnd) ||
(end >= periodStart && end <= periodEnd) ||
(start <= periodStart && end >= periodEnd);
});
if (unavailable) {
setError('Selected dates include unavailable periods');
return;
}
setError(null);
setSelectedPeriods([{
id: Date.now().toString(),
startDate: start,
endDate: end,
startTime: updatedSelection.startTime,
endTime: updatedSelection.endTime
}]);
}
};
if (loading) {
return (
@@ -223,42 +325,117 @@ const RentItem: React.FC = () => {
<h5 className="card-title">Select Rental Period</h5>
<AvailabilityCalendar
unavailablePeriods={[
...(item.unavailablePeriods || []),
...selectedPeriods.map(p => ({ ...p, isRentalSelection: true }))
]}
onPeriodsChange={(periods) => {
// Only handle rental selections
const rentalSelections = periods.filter(p => p.isRentalSelection);
setSelectedPeriods(rentalSelections.map(p => {
const { isRentalSelection, ...rest } = p;
return rest;
}));
unavailablePeriods={item.unavailablePeriods || []}
onPeriodsChange={() => {}} // Read-only for renters
mode="renter"
selectedRentalPeriod={selectedPeriods.length > 0 ? {
id: selectedPeriods[0].id,
startDate: selectedPeriods[0].startDate,
endDate: selectedPeriods[0].endDate,
startTime: selectedPeriods[0].startTime,
endTime: selectedPeriods[0].endTime,
isRentalSelection: true
} : undefined}
onRentalSelect={(period) => {
// Update selected periods
setSelectedPeriods([{
id: Date.now().toString(),
startDate: period.startDate,
endDate: period.endDate,
startTime: period.startTime,
endTime: period.endTime
}]);
// Update manual selection to match calendar
setManualSelection({
startDate: period.startDate.toISOString().split('T')[0],
startTime: period.startTime || '09:00',
endDate: period.endDate.toISOString().split('T')[0],
endTime: period.endTime || '17:00'
});
}}
priceType={showHourlyOptions ? "hour" : "day"}
isRentalMode={true}
/>
<div className="mt-4">
<div className="row g-3">
<div className="col-md-3">
<label htmlFor="startDate" className="form-label">Start Date</label>
<input
type="date"
className="form-control"
id="startDate"
name="startDate"
value={manualSelection.startDate}
onChange={handleManualSelectionChange}
min={new Date().toISOString().split('T')[0]}
/>
</div>
<div className="col-md-3">
<label htmlFor="startTime" className="form-label">Start Time</label>
<select
className="form-select"
id="startTime"
name="startTime"
value={manualSelection.startTime}
onChange={handleManualSelectionChange}
>
{Array.from({ length: 24 }, (_, i) => {
const hour = i === 0 ? 12 : i > 12 ? i - 12 : i;
const period = i < 12 ? 'AM' : 'PM';
return (
<option key={i} value={`${i.toString().padStart(2, '0')}:00`}>
{hour}:00 {period}
</option>
);
})}
</select>
</div>
<div className="col-md-3">
<label htmlFor="endDate" className="form-label">End Date</label>
<input
type="date"
className="form-control"
id="endDate"
name="endDate"
value={manualSelection.endDate}
onChange={handleManualSelectionChange}
min={manualSelection.startDate || new Date().toISOString().split('T')[0]}
/>
</div>
<div className="col-md-3">
<label htmlFor="endTime" className="form-label">End Time</label>
<select
className="form-select"
id="endTime"
name="endTime"
value={manualSelection.endTime}
onChange={handleManualSelectionChange}
>
{Array.from({ length: 24 }, (_, i) => {
const hour = i === 0 ? 12 : i > 12 ? i - 12 : i;
const period = i < 12 ? 'AM' : 'PM';
return (
<option key={i} value={`${i.toString().padStart(2, '0')}:00`}>
{hour}:00 {period}
</option>
);
})}
</select>
</div>
</div>
</div>
{rentalDuration.days < minDays && rentalDuration.days > 0 && !showHourlyOptions && (
<div className="alert alert-warning mt-3">
Minimum rental period is {minDays} days
</div>
)}
{selectedPeriods.length === 0 && (
<div className="alert alert-info mt-3">
Please select your rental dates on the calendar above
</div>
)}
</div>
</div>
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">Delivery Options</h5>
<div className="mb-3">
<label htmlFor="deliveryMethod" className="form-label">Delivery Method *</label>
<div className="my-3">
<select
className="form-select"
id="deliveryMethod"