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;
}