This commit includes: - Django backend with multi-tenancy (django-tenants) - React + TypeScript frontend with Vite - Platform administration API with role-based access control - Authentication system with token-based auth - Quick login dev tools for testing different user roles - CORS and CSRF configuration for local development - Docker development environment setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
134 lines
4.6 KiB
JavaScript
134 lines
4.6 KiB
JavaScript
import React, { useState } from 'react';
|
||
import { format } from 'date-fns';
|
||
import './BookingForm.css';
|
||
|
||
const BookingForm = ({ service, resources, onSubmit, onCancel, loading }) => {
|
||
const [formData, setFormData] = useState({
|
||
resource: resources?.[0]?.id || '',
|
||
date: '',
|
||
time: '',
|
||
});
|
||
|
||
const [errors, setErrors] = useState({});
|
||
|
||
const handleChange = (e) => {
|
||
const { name, value } = e.target;
|
||
setFormData(prev => ({ ...prev, [name]: value }));
|
||
// Clear error for this field
|
||
if (errors[name]) {
|
||
setErrors(prev => ({ ...prev, [name]: '' }));
|
||
}
|
||
};
|
||
|
||
const validate = () => {
|
||
const newErrors = {};
|
||
|
||
if (!formData.resource) {
|
||
newErrors.resource = 'Please select a resource';
|
||
}
|
||
if (!formData.date) {
|
||
newErrors.date = 'Please select a date';
|
||
}
|
||
if (!formData.time) {
|
||
newErrors.time = 'Please select a time';
|
||
}
|
||
|
||
setErrors(newErrors);
|
||
return Object.keys(newErrors).length === 0;
|
||
};
|
||
|
||
const handleSubmit = (e) => {
|
||
e.preventDefault();
|
||
|
||
if (!validate()) {
|
||
return;
|
||
}
|
||
|
||
// Combine date and time into ISO format
|
||
const startDateTime = new Date(`${formData.date}T${formData.time}`);
|
||
const endDateTime = new Date(startDateTime.getTime() + service.duration * 60000);
|
||
|
||
const appointmentData = {
|
||
service: service.id,
|
||
resource: parseInt(formData.resource),
|
||
start_time: startDateTime.toISOString(),
|
||
end_time: endDateTime.toISOString(),
|
||
};
|
||
|
||
onSubmit(appointmentData);
|
||
};
|
||
|
||
return (
|
||
<div className="booking-form-container">
|
||
<div className="booking-form-header">
|
||
<h2>Book: {service.name}</h2>
|
||
<button onClick={onCancel} className="close-btn">×</button>
|
||
</div>
|
||
|
||
<div className="service-summary">
|
||
<p><strong>Duration:</strong> {service.duration} minutes</p>
|
||
<p><strong>Price:</strong> ${service.price}</p>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit} className="booking-form">
|
||
<div className="form-group">
|
||
<label htmlFor="resource">Select Provider</label>
|
||
<select
|
||
id="resource"
|
||
name="resource"
|
||
value={formData.resource}
|
||
onChange={handleChange}
|
||
className={errors.resource ? 'error' : ''}
|
||
>
|
||
<option value="">Choose a provider...</option>
|
||
{resources?.map((resource) => (
|
||
<option key={resource.id} value={resource.id}>
|
||
{resource.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
{errors.resource && <span className="error-message">{errors.resource}</span>}
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="date">Date</label>
|
||
<input
|
||
type="date"
|
||
id="date"
|
||
name="date"
|
||
value={formData.date}
|
||
onChange={handleChange}
|
||
min={format(new Date(), 'yyyy-MM-dd')}
|
||
className={errors.date ? 'error' : ''}
|
||
/>
|
||
{errors.date && <span className="error-message">{errors.date}</span>}
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="time">Time</label>
|
||
<input
|
||
type="time"
|
||
id="time"
|
||
name="time"
|
||
value={formData.time}
|
||
onChange={handleChange}
|
||
className={errors.time ? 'error' : ''}
|
||
/>
|
||
{errors.time && <span className="error-message">{errors.time}</span>}
|
||
</div>
|
||
|
||
<div className="form-actions">
|
||
<button type="button" onClick={onCancel} className="btn-cancel">
|
||
Cancel
|
||
</button>
|
||
<button type="submit" className="btn-submit" disabled={loading}>
|
||
{loading ? 'Booking...' : 'Confirm Booking'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default BookingForm;
|