Components BookingPage/Booking.jsx
Booking.jsx
import React, { useState } from "react";
const Booking = () => {
const [selectedTheater, setSelectedTheater] = useState(null);
const [selectedDate, setSelectedDate] = useState(null);
const [selectedTime, setSelectedTime] = useState(null);
const [selectedSeats, setSelectedSeats] = useState([]);
return (
<div>
<h2>Book Film</h2>
{/*Välja salong*/}
<div>
<h3>Choose salon</h3>
<div>
{theaters.map((theater) => (
<button
key={theater.theater_id}
onClick={() => setSelectedTheater(theater.theater_id)}
>
{theater.theater_id}
</button>
))}
</div>
</div>
{/*Välj datum*/}
{selectedTheater && (
<div>
<h3>Choose date</h3>
<div>
{dates.maps((date) => (
<button key={date} onClick={() => setSelectedDate(date)}>
{date}
</button>
))}
</div>
</div>
)}
{/*Välj tid*/}
{selectedDate && (
<div>
<h3>Choose time</h3>
<div>
{times.maps((time) => (
<button key={time} onClick={() => setSelectedTime(time)}>
{time}
</button>
))}
</div>
</div>
)}
{/*Välj platser*/}
{selectedTime && (
<div>
<h3>Choose seats</h3>
<div>
{seats.map((seat) => (
<button key={seat} onClick={() => toggleSeat(seat)}>
{seat}
</button>
))}
</div>
</div>
)}
<div>
<button onClick={handleBooking}>Book</button>
</div>
</div>
);
};
export default Booking;Det fanns en till
import { useNavigate, useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import "../styles/BookingPage.css";
import MovieInfo from "../components/BookingPage/MovieInfo";
import TicketSelector from "../components/BookingPage/TicketSelector";
import SeatSelector from "../components/BookingPage/SeatSelector";
function BookingPage() {
const { screening_id } = useParams();
const navigate = useNavigate();
const [screening, setScreening] = useState(null);
const [movie, setMovie] = useState(null);
const [currentStep, setCurrentStep] = useState(1);
const [availableSeats, setAvailableSeats] = useState([]);
const [selectedSeats, setSelectedSeats] = useState([]);
const [ticketCounts, setTicketCounts] = useState({
adult: 0,
child: 0,
senior: 0,
});
// Hämta visnings- och filminformation
useEffect(() => {
document.body.style.backgroundColor = "#222831";
fetch(`/api/screenings/${screening_id}`)
.then((res) => {
if (!res.ok) {
console.error("Status:", res.status);
return res.text().then((text) => {
console.error("Error response:", text);
throw new Error("Kunde inte hämta visning");
});
}
return res.json();
})
.then((data) => {
setScreening(data);
return fetch(`/api/movies/${data.movie_id}`);
})
.then((res) => res.json())
.then((data) => {
setMovie(data);
})
.catch((error) => {
console.error("Fel vid hämtning av data:", error);
});
return () => {
document.body.style.backgroundColor = "";
};
}, [screening_id]);
// Hämta tillgängliga platser
useEffect(() => {
fetch(`/api/screenings/${screening_id}/seats`)
.then((res) => res.json())
.then((data) => {
setAvailableSeats(data);
})
.catch((err) => {
console.error("Fel vid hämtning av platser:", err);
});
}, [screening_id]);
// Funktion för att hantera val av platser
const handleSeatSelection = (seatId) => {
if (selectedSeats.includes(seatId)) {
setSelectedSeats(selectedSeats.filter((id) => id !== seatId));
return;
}
if (selectedSeats.length >= totalTickets) {
alert(`Du kan max välja ${totalTickets} platser`);
return;
}
setSelectedSeats([...selectedSeats, seatId]);
};
// Funktion för att slutföra bokningen
const handleCompleteBooking = () => {
if (selectedSeats.length !== totalTickets) {
alert(`Du måste välja exakt ${totalTickets} platser`);
return;
}
const bookingData = {
screening_id: parseInt(screening_id),
tickets: ticketCounts,
seats: selectedSeats,
total_price: totalPrice,
};
fetch("/api/bookings", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(bookingData),
})
.then((res) => {
if (!res.ok) throw new Error("Kunde inte spara bokning");
return res.json();
})
.then((data) => {
alert(
`Bokning slutförd! Ditt bokningsnummer är: ${data.booking_number}`
);
navigate("/");
})
.catch((err) => {
console.error("Fel vid bokning:", err);
alert("Något gick fel vid bokningen. Försök igen.");
});
};
const handleTicketChange = (type, value) => {
// Säkerställ att värdet inte blir negativt
const newValue = Math.max(0, value);
setTicketCounts({
...ticketCounts,
[type]: newValue,
});
};
const totalTickets =
ticketCounts.adult + ticketCounts.child + ticketCounts.senior;
const totalPrice = screening
? ticketCounts.adult * screening.price_adult +
ticketCounts.child * screening.price_child +
ticketCounts.senior * screening.price_senior
: 0;
const moveToSeatSelection = () => {
if (totalTickets > 0) {
setCurrentStep(2);
} else {
alert("Du måste välja minst en biljett");
}
};
if (!screening || !movie) return <p>Laddar visning...</p>;
const screeningDate = new Date(screening.screening_time);
const formattedDate = new Intl.DateTimeFormat("sv-SE", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(screeningDate);
return (
<div className="booking-page">
<h1>Boka biljetter</h1>
<div className="movie-info">
{movie.poster_url && (
<img
src={movie.poster_url}
alt={`Poster för ${movie.title}`}
className="booking-page-poster"
/>
)}
<div>
<h2 className="theater-name">{screening.theater_name}</h2>
<h2>{movie.title}</h2>
<p>
<strong>Tid:</strong> {formattedDate}
</p>
</div>
</div>
{currentStep === 1 && (
<div className="ticket-selection">
<h2>Välj antal biljetter</h2>
<div className="ticket-type">
<div className="ticket-info">
<span>Vuxna ({screening.price_adult} kr)</span>
</div>
<div className="ticket-controls">
<button
onClick={() =>
handleTicketChange("adult", ticketCounts.adult - 1)
}
disabled={ticketCounts.adult === 0}
>
-
</button>
<span>{ticketCounts.adult}</span>
<button
onClick={() =>
handleTicketChange("adult", ticketCounts.adult + 1)
}
>
+
</button>
</div>
</div>
<div className="ticket-type">
<div className="ticket-info">
<span>Barn ({screening.price_child} kr)</span>
</div>
<div className="ticket-controls">
<button
onClick={() =>
handleTicketChange("child", ticketCounts.child - 1)
}
disabled={ticketCounts.child === 0}
>
-
</button>
<span>{ticketCounts.child}</span>
<button
onClick={() =>
handleTicketChange("child", ticketCounts.child + 1)
}
>
+
</button>
</div>
</div>
<div className="ticket-type">
<div className="ticket-info">
<span>Pensionärer ({screening.price_senior} kr)</span>
</div>
<div className="ticket-controls">
<button
onClick={() =>
handleTicketChange("senior", ticketCounts.senior - 1)
}
disabled={ticketCounts.senior === 0}
>
-
</button>
<span>{ticketCounts.senior}</span>
<button
onClick={() =>
handleTicketChange("senior", ticketCounts.senior + 1)
}
>
+
</button>
</div>
</div>
<div className="booking-summary">
<h3>Totalpris: {totalPrice} kr</h3>
<button
className="next-button"
onClick={moveToSeatSelection}
disabled={totalTickets === 0}
>
Gå vidare till platsval
</button>
</div>
</div>
)}
{currentStep === 2 && (
<div className="seat-selection">
<h3>Välj platser</h3>
<p>Välj {totalTickets} platser</p>
<div className="seat-grid">
{availableSeats.map((seat) => (
<button
key={seat.seat_id}
className={`seat ${
selectedSeats.includes(seat.seat_id) ? "selected" : ""
}`}
onClick={() => handleSeatSelection(seat.seat_id)}
disabled={!seat.is_available}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-armchair-icon lucide-armchair"
>
<path d="M19 9V6a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v3" />
<path d="M3 16a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5a2 2 0 0 0-4 0v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V11a2 2 0 0 0-4 0z" />
<path d="M5 18v2" />
<path d="M19 18v2" />
</svg>
</button>
))}
</div>
<div className="booking-actions">
<button onClick={() => setCurrentStep(1)}>Tillbaka</button>
<button
className="complete-btn"
onClick={handleCompleteBooking}
disabled={selectedSeats.length !== totalTickets}
>
Slutför bokning
</button>
</div>
</div>
)}
</div>
);
}
export default BookingPage;Stylingen:
/* BookingPage.css - spara i /client/src/styles/ */
.booking-page {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.booking-page h1 {
color: white;
display: flex;
justify-content: center;
margin-bottom: 5px;
}
.movie-info {
background: rgba(255, 255, 255, 0.5);
border-radius: 16px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(2.4px);
-webkit-backdrop-filter: blur(2.4px);
border: 1px solid rgba(255, 255, 255, 0.48);
padding: 10px;
margin-bottom: 15px;
border-radius: 8px;
color: black;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 10px;
letter-spacing: 1px;
}
.booking-page-poster {
border-radius: 50%;
width: 8rem;
height: 8rem;
}
.movie-info h2,
.movie-info p {
margin: 0;
padding: 0;
margin-left: 20px;
}
.theater-name {
color: #fbbf24;
font-size: 1.2rem;
}
.ticket-selection {
background: rgba(255, 255, 255, 0.5);
border-radius: 16px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(2.4px);
-webkit-backdrop-filter: blur(2.4px);
border: 1px solid rgba(255, 255, 255, 0.48);
padding: 20px;
border-radius: 8px;
}
.ticket-type {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #e0e0e0;
margin-bottom: 10px;
}
.ticket-controls {
display: flex;
align-items: center;
gap: 10px;
}
.ticket-controls button {
width: 30px;
height: 30px;
font-size: 16px;
border: 1px solid #ddd;
background-color: #fbbf24;
border-radius: 4px;
cursor: pointer;
}
.ticket-controls button:disabled {
background-color: #f0f0f0;
cursor: not-allowed;
}
.booking-summary {
margin-top: 20px;
text-align: left;
}
.next-button {
background-color: #fbbf24;
color: black;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.next-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.seat-selection {
margin-top: 20px;
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.5);
border-radius: 16px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(2.4px);
-webkit-backdrop-filter: blur(2.4px);
border: 1px solid rgba(255, 255, 255, 0.48);
}
.seat-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
margin: 20px 0;
}
.seat {
padding: 0;
border: none;
background-color: transparent;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.seat svg {
width: 40px;
height: 40px;
stroke: black;
transition: all 0.3s ease;
}
.seat.selected svg {
stroke: #fbbf24;
}
.seat:disabled svg {
stroke: #ffdddd;
}
.booking-actions {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.booking-actions button {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.complete-btn {
background-color: #fbbf24;
color: black;
}
.complete-btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
}