appontment / index.html
jasondos's picture
Add 2 files
823bd3a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Appointment Scheduler</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style type="text/css">
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.time-slot {
transition: all 0.2s ease;
}
.time-slot:hover {
transform: scale(1.05);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
</style>
</head>
<body class="bg-gray-50">
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
const services = [
{ id: 1, name: "Haircut", duration: 30, price: 30 },
{ id: 2, name: "Beard Trim", duration: 15, price: 15 },
{ id: 3, name: "Haircut + Beard", duration: 45, price: 40 },
{ id: 4, name: "Hot Towel Shave", duration: 30, price: 35 },
{ id: 5, name: "Hair Coloring", duration: 60, price: 60 }
];
const timeSlots = [
"09:00 AM", "09:30 AM", "10:00 AM", "10:30 AM", "11:00 AM", "11:30 AM",
"12:00 PM", "12:30 PM", "01:00 PM", "01:30 PM", "02:00 PM", "02:30 PM",
"03:00 PM", "03:30 PM", "04:00 PM", "04:30 PM", "05:00 PM", "05:30 PM"
];
const professionals = [
{ id: 1, name: "John Doe", specialty: "Haircuts & Styling" },
{ id: 2, name: "Jane Smith", specialty: "Coloring & Treatments" },
{ id: 3, name: "Mike Johnson", specialty: "Beard Grooming" }
];
function App() {
const [step, setStep] = useState(1);
const [selectedService, setSelectedService] = useState(null);
const [selectedDate, setSelectedDate] = useState(null);
const [selectedTime, setSelectedTime] = useState(null);
const [selectedProfessional, setSelectedProfessional] = useState(null);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [phone, setPhone] = useState("");
const [notes, setNotes] = useState("");
const [bookedSlots, setBookedSlots] = useState([]);
const [appointments, setAppointments] = useState([]);
// Generate dates for the next 7 days
const dates = Array.from({ length: 7 }, (_, i) => {
const date = new Date();
date.setDate(date.getDate() + i);
return date;
});
// Simulate fetching booked slots from an API
useEffect(() => {
const dummyBookedSlots = [
{ date: dates[0].toDateString(), time: "09:00 AM", professional: 1 },
{ date: dates[0].toDateString(), time: "10:00 AM", professional: 2 },
{ date: dates[1].toDateString(), time: "02:30 PM", professional: 1 },
];
setBookedSlots(dummyBookedSlots);
}, []);
const isSlotAvailable = (time, professionalId) => {
if (!selectedDate) return true;
const isBooked = bookedSlots.some(
slot =>
slot.date === selectedDate.toDateString() &&
slot.time === time &&
slot.professional === professionalId
);
return !isBooked;
};
const handleSubmit = (e) => {
e.preventDefault();
const newAppointment = {
id: Date.now(),
service: selectedService.name,
date: selectedDate.toDateString(),
time: selectedTime,
professional: professionals.find(p => p.id === selectedProfessional).name,
customer: { name, email, phone },
notes,
status: "Confirmed"
};
setAppointments([...appointments, newAppointment]);
alert("Appointment booked successfully!");
resetForm();
};
const resetForm = () => {
setStep(1);
setSelectedService(null);
setSelectedDate(null);
setSelectedTime(null);
setSelectedProfessional(null);
setName("");
setEmail("");
setPhone("");
setNotes("");
};
const formatDate = (date) => {
return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
};
return (
<div className="min-h-screen py-8 px-4">
<div className="max-w-4xl mx-auto bg-white rounded-xl shadow-md overflow-hidden">
<div className="p-8">
<h1 className="text-3xl font-bold text-center text-gray-800 mb-2">Book Your Appointment</h1>
<p className="text-center text-gray-600 mb-8">Schedule your visit in just a few clicks</p>
{/* Progress Steps */}
<div className="flex items-center justify-between mb-8 relative">
<div className="absolute w-full h-1 bg-gray-200 top-1/2 transform -translate-y-1/2 z-0"></div>
{[1, 2, 3, 4].map((s) => (
<div key={s} className="flex flex-col items-center relative z-10">
<div className={`w-10 h-10 rounded-full flex items-center justify-center ${step >= s ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600'} font-semibold`}>
{s}
</div>
<div className={`text-xs mt-2 font-medium ${step >= s ? 'text-blue-600' : 'text-gray-500'}`}>
{s === 1 && 'Service'}
{s === 2 && 'Schedule'}
{s === 3 && 'Professional'}
{s === 4 && 'Details'}
</div>
</div>
))}
</div>
{/* Step 1: Select Service */}
{step === 1 && (
<div className="fade-in">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Select a Service</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{services.map((service) => (
<div
key={service.id}
className={`p-4 border rounded-lg cursor-pointer transition-all ${selectedService?.id === service.id ? 'border-blue#500 bg-blue-50' : 'border-gray-200 hover:border-blue-300'}`}
onClick={() => setSelectedService(service)}
>
<div className="font-semibold text-gray-800">{service.name}</div>
<div className="flex justify-between items-center mt-2">
<span className="text-sm text-gray-600">{service.duration} min</span>
<span className="text-sm font-medium text-blue-600">${service.price}</span>
</div>
</div>
))}
</div>
<div className="mt-6 flex justify-end">
<button
onClick={() => setStep(2)}
disabled={!selectedService}
className={`px-6 py-2 rounded-md ${selectedService ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-gray-300 text-gray-500 cursor-not-allowed'} transition-colors`}
>
Next
</button>
</div>
</div>
)}
{/* Step 2: Select Date & Time */}
{step === 2 && (
<div className="fade-in">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Choose Date and Time</h2>
<div className="mb-6">
<h3 className="text-lg font-medium text-gray-700 mb-3">Available Dates</h3>
<div className="flex overflow-x-auto pb-2 gap-3">
{dates.map((date, index) => (
<div
key={index}
className={`flex-shrink-0 w-24 py-3 px-1 rounded-lg text-center cursor-pointer ${selectedDate?.toDateString() === date.toDateString() ? 'bg-blue-100 border border-blue-500' : 'bg-gray-50 border border-gray-200 hover:bg-gray-100'}`}
onClick={() => setSelectedDate(date)}
>
<div className="font-medium text-gray-700">{formatDate(date)}</div>
</div>
))}
</div>
</div>
{selectedDate && (
<div className="mt-6">
<h3 className="text-lg font-medium text-gray-700 mb-3">Available Time Slots</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
{timeSlots.map((time, index) => (
<div
key={index}
className={`time-slot py-2 px-1 rounded-md text-center cursor-pointer ${selectedTime === time ? 'bg-blue-600 text-white' : isSlotAvailable(time, selectedProfessional || 1) ? 'bg-gray-100 hover:bg-blue-100 text-gray-800' : 'bg-gray-200 text-gray-400 cursor-not-allowed'}`}
onClick={() => isSlotAvailable(time, selectedProfessional || 1) && setSelectedTime(time)}
>
{time}
</div>
))}
</div>
</div>
)}
<div className="mt-8 flex justify-between">
<button
onClick={() => setStep(1)}
className="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors"
>
Back
</button>
<button
onClick={() => setStep(3)}
disabled={!selectedDate || !selectedTime}
className={`px-6 py-2 rounded-md ${selectedDate && selectedTime ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-gray-300 text-gray-500 cursor-not-allowed'} transition-colors`}
>
Next
</button>
</div>
</div>
)}
{/* Step 3: Select Professional */}
{step === 3 && (
<div className="fade-in">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Choose Your Professional</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{professionals.map((professional) => (
<div
key={professional.id}
className={`p-4 border rounded-lg cursor-pointer transition-all ${selectedProfessional === professional.id ? 'border-blue-500 bg-blue-50' : 'border-gray-200 hover:border-blue-300'}`}
onClick={() => setSelectedProfessional(professional.id)}
>
<div className="w-16 h-16 rounded-full bg-gray-300 flex items-center justify-center text-2xl text-gray-500 mb-3 mx-auto">
<i className="fas fa-user"></i>
</div>
<div className="text-center">
<div className="font-semibold text-gray-800">{professional.name}</div>
<div className="text-sm text-gray-600 mt-1">{professional.specialty}</div>
{selectedProfessional === professional.id && (
<div className="mt-2 text-sm text-green-600">
<i className="fas fa-check-circle mr-1"></i> Selected
</div>
)}
</div>
</div>
))}
</div>
<div className="mt-8 flex justify-between">
<button
onClick={() => setStep(2)}
className="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors"
>
Back
</button>
<button
onClick={() => setStep(4)}
disabled={!selectedProfessional}
className={`px-6 py-2 rounded-md ${selectedProfessional ? 'bg-blue-600 hover:bg-blue-700 text-white' : 'bg-gray-300 text-gray-500 cursor-not-allowed'} transition-colors`}
>
Next
</button>
</div>
</div>
)}
{/* Step 4: Customer Details */}
{step === 4 && (
<form onSubmit={handleSubmit} className="fade-in">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Your Information</h2>
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
<h3 className="font-medium text-gray-700 mb-3">Appointment Summary</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">Service:</span>
<span className="font-medium">{selectedService.name}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Date:</span>
<span className="font-medium">{selectedDate && formatDate(selectedDate)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Time:</span>
<span className="font-medium">{selectedTime}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Professional:</span>
<span className="font-medium">{professionals.find(p => p.id === selectedProfessional).name}</span>
</div>
<div className="flex justify-between pt-2 mt-2 border-t border-gray-200">
<span className="text-gray-600">Total:</span>
<span className="font-bold text-blue-600">${selectedService.price}</span>
</div>
</div>
</div>
<div className="space-y-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">Full Name *</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">Email *</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">Phone *</label>
<input
type="tel"
id="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
required
/>
</div>
</div>
<div>
<label htmlFor="notes" className="block text-sm font-medium text-gray-700 mb-1">Special Requests</label>
<textarea
id="notes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows="3"
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div className="mt-8 flex justify-between">
<button
onClick={() => setStep(3)}
type="button"
className="px-6 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors"
>
Back
</button>
<button
type="submit"
className="px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors flex items-center"
>
<i className="fas fa-calendar-check mr-2"></i> Confirm Booking
</button>
</div>
</form>
)}
{appointments.length > 0 && (
<div className="mt-12">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Your Appointments</h2>
<div className="space-y-3">
{appointments.map((appointment) => (
<div key={appointment.id} className="p-4 border border-gray-200 rounded-lg">
<div className="flex justify-between items-start">
<div>
<div className="font-medium">{appointment.service}</div>
<div className="text-sm text-gray-600">
{appointment.date} at {appointment.time} with {appointment.professional}
</div>
</div>
<div className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full flex items-center">
<i className="fas fa-check-circle mr-1"></i> {appointment.status}
</div>
</div>
<div className="mt-2 pt-2 border-t border-gray-100">
<div className="text-sm"><span className="font-medium">Customer:</span> {appointment.customer.name}</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=jasondos/appontment" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>