Commit 3b117683 by Sujeeth AV

Your commit message here

parent 5e7a4928
......@@ -91,127 +91,115 @@
],
"users": [
{
"id": "9500403346",
"Mobile": "9500403346",
"Name": "sujeeth"
},
{
"id": "7418601450",
"Mobile": "7418601450",
"Name": "ravi"
"id": "9677655288",
"Mobile": "9677655288",
"Name": "Anitha"
},
{
"id": "9087654321",
"Mobile": "9087654321",
"Name": "oggy"
"id": "9597003337",
"Mobile": "9597003337",
"Name": "venkatesh"
},
{
"id": "7894561230",
"Mobile": "7894561230",
"Name": "khan"
"id": "9500403346",
"Mobile": "9500403346",
"Name": "Sujeeth"
},
{
"id": "8097654321",
"Mobile": "8097654321",
"Name": "john"
"id": "9976051171",
"Mobile": "9976051171",
"Name": "JK"
},
{
"id": "9677655288",
"Mobile": "9677655288",
"Name": "anitha"
"id": "7418601450",
"Mobile": "7418601450",
"Name": "Hari"
},
{
"id": "9597003337",
"Mobile": "9597003337",
"Name": "venkatesh"
"id": "7890654312",
"Mobile": "7890654312",
"Name": "Guru"
},
{
"id": "6379757115",
"Mobile": "6379757115",
"Name": "Shaganaz "
"id": "6543217890",
"Mobile": "6543217890",
"Name": "praba"
}
],
"seats": [
{
"userId": "9087654321",
"seats": [
"D4"
],
"status": "booked",
"id": "9192"
},
{
"userId": "9500403346",
"seats": [
"A1",
"A2"
],
"status": "booked",
"id": "7685"
},
{
"id": "4d7f",
"userId": "7418601450",
"userId": "9677655288",
"seats": [
"D1",
"D2",
"D3"
],
"status": "booked"
"status": "booked",
"id": "4271"
},
{
"userId": "8097654321",
"userId": "9597003337",
"seats": [
"E6",
"E7"
"E1",
"E2",
"E3",
"E4"
],
"status": "booked",
"id": "164e"
"id": "4fcc"
},
{
"userId": "9677655288",
"userId": "9500403346",
"seats": [
"H1"
"F3"
],
"status": "booked",
"id": "aa34"
"id": "b4fe"
},
{
"userId": "9597003337",
"userId": "9976051171",
"seats": [
"E1",
"E2"
"F1",
"F2"
],
"status": "booked",
"id": "9213"
"id": "40ef"
},
{
"userId": "6379757115",
"userId": "7418601450",
"seats": [
"B1",
"B2"
"G5",
"G6",
"G7",
"G8"
],
"status": "booked",
"id": "89cd"
"id": "a74a"
},
{
"id": "7ace",
"userId": null,
"userId": "7890654312",
"seats": [
"B1",
"B2",
"B3",
"B4",
"E5",
"E8"
"B5",
"B8",
"B6",
"B7",
"C1",
"C2",
"C3",
"C4"
],
"status": "booked"
"status": "booked",
"id": "cf53"
},
{
"id": "ab59",
"userId": null,
"id": "b883",
"userId": "6543217890",
"seats": [
"B3",
"B4",
"E5",
"E7",
"E8"
],
"status": "booked"
......
......@@ -6,12 +6,14 @@
}
body {
background-color: #303fb6;
font-family: "Times New Roman", Times, serif;
font-family: "Poppins", sans-serif;
min-height: 50svh;
height: 100svh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
margin: 0;
}
h2 {
font-size: 1.5rem;
......
......@@ -9,7 +9,6 @@ export const loginUser = async (mobile) => {
console.log("Login User API response:", res.data);
return res.data.length ? res.data[0] : null;
};
export const saveSeat=async({userId,seats})=>{
const response=await axios.post(`${BASE_URL}/seats`,{
userId,
......
......@@ -5,15 +5,44 @@ import { SeatLimit } from "./pages/SeatLimit";
import { Summary } from "./components/Response/Index";
import { LoginPage } from "./pages/Forms";
import { ContextProvider } from "./context/Index";
import { ProtectedRoute } from "./ProtectedRoute";
function App() {
return (
<ContextProvider>
<Routes>
<Route path="/signup" element={<LoginPage />} />
<Route path="/" element={<LoginPage />} />
<Route path="/seat-limit" element={<SeatLimit />} />
<Route path="/booking" element={<SeatBooking />} />
<Route path="booking-summary" element={<Summary />} />
<Route
path="/signup"
element={
<ProtectedRoute>
<LoginPage />
</ProtectedRoute>
}
/>
<Route
path="/seat-limit"
element={
<ProtectedRoute>
<SeatLimit />
</ProtectedRoute>
}
/>
<Route
path="/booking"
element={
<ProtectedRoute>
<SeatBooking />
</ProtectedRoute>
}
/>
<Route
path="Ticket"
element={
<ProtectedRoute>
<Summary />
</ProtectedRoute>
}
/>
</Routes>
</ContextProvider>
);
......
src/Assets/Success.png

265 KB | W: | H:

src/Assets/Success.png

257 KB | W: | H:

src/Assets/Success.png
src/Assets/Success.png
src/Assets/Success.png
src/Assets/Success.png
  • 2-up
  • Swipe
  • Onion skin
import { Navigate } from "react-router-dom";
export const ProtectedRoute = ({ children }) => {
const isLoggedIn = !!(
localStorage.getItem("userId") || sessionStorage.getItem("userId")
);
return isLoggedIn ? children : <Navigate to="/" replace />;
};
import styles from "./Styles.module.css";
export const Input = ({ value, onChange, placeholder, type, ...props }) => {
export const Input = ({
value,
onChange,
placeholder,
type,
icon: Icon,
...props
}) => {
return (
<div className={styles.input}>
{Icon && (
<span className={styles.icon}>
<Icon />
</span>
)}
<input
value={value}
placeholder={placeholder}
......
......@@ -6,6 +6,7 @@ input {
font-size: 1rem;
width: 100%;
margin-bottom: 1rem;
padding-left: 1.3rem;
}
input:focus {
outline: none;
......@@ -15,3 +16,15 @@ input:focus {
display: flex;
flex-direction: start;
}
.icon {
position: absolute;
display: flex;
justify-content: start;
align-items: end;
margin-top: 17px;
margin-left: 9px;
color: grey;
}
input {
padding-left: 30px;
}
import React, { useState } from "react";
import { useState } from "react";
import { Header } from "../../Layout/Card/Header";
import { Input } from "../../Form/form/Input";
import { Button } from "../../Form/Button/Button";
import styles from "./Login.module.css";
import { toast, ToastContainer } from "react-toastify";
import Home from "../../../Assets/Login.png";
import { Link } from "react-router-dom";
import { Sign } from "../SignUp/Sign";
import { FaPhoneAlt } from "react-icons/fa";
export const Login = ({ onClick, onSwitch }) => {
const [value, setValue] = useState("");
const [islesser, setIsLesser] = useState(false);
const InputChange = (e) => {
setValue(e.target.value);
const updated = e.target.value;
console.log("executed");
if (/^\d{0,10}$/.test(updated)) {
setValue(updated);
}
};
const HandleButton = (e) => {
e.preventDefault();
if (!/^[6-9]\d{9}$/.test(value)) {
toast.info("Please enter a valid 10-digit number");
return;
}
if (value.length < 10) {
setIsLesser(true);
toast.error("Mobile Number is less than 10");
......@@ -39,11 +45,13 @@ export const Login = ({ onClick, onSwitch }) => {
<div className={styles.form}>
<div className={styles.inp}>
<Input
placeholder="Enter Your Number"
type="text"
placeholder="Enter Your Mobile Number"
value={value}
onChange={InputChange}
required
type="tel"
pattern="[6-9][0-9]{9}"
min="0"
icon={FaPhoneAlt}
/>
</div>
{islesser && (
......
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap");
@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
body {
font-family: "Poppins", sans-serif;
}
.container {
min-height: 100vh;
display: flex;
......@@ -42,7 +38,7 @@ body {
.form {
display: flex;
flex-direction: column;
gap: 1rem;
gap: 0.3rem;
}
.inp {
......
......@@ -5,6 +5,8 @@ import { Header } from "../../Layout/Card/Header";
import style from "./Styles.module.css";
import { ToastContainer } from "react-toastify";
import { toast } from "react-toastify";
import { FaPhoneAlt } from "react-icons/fa";
import { TiSortAlphabetically } from "react-icons/ti";
export const Sign = ({ onClick, onChange, onSwitch }) => {
const [num, setNum] = useState("");
......@@ -65,6 +67,7 @@ export const Sign = ({ onClick, onChange, onSwitch }) => {
type="text"
pattern="[6-9][0-9]{9}"
min="0"
icon={FaPhoneAlt}
/>
</div>
<div className={style.Inpname}>
......@@ -74,6 +77,7 @@ export const Sign = ({ onClick, onChange, onSwitch }) => {
onChange={Name}
type="text"
maxLength={255}
icon={TiSortAlphabetically}
/>
</div>
<div className={style.signbtn}>
......
import React from "react";
import styles from "./Styles.module.css";
import { useLocation, useNavigate } from "react-router-dom";
import { Button } from "../Form/Button/Button";
import { IoMdLogOut } from "react-icons/io";
import { MdHome, MdCheckCircle } from "react-icons/md";
import { RiArrowGoBackLine } from "react-icons/ri";
import styles from "./Styles.module.css";
import Success from "../../Assets/Success.png";
import Failed from "../../Assets/Failed.png";
import QRCode from "../../Assets/QRcode.png";
import Ticket from "../../Assets/Success.png";
export const Summary = ({ success: propSuccess }) => {
const navigate = useNavigate();
const location = useLocation();
const currentDateTime = new Date().toLocaleString();
const success = propSuccess ?? location.state?.success;
const onLogout = () => {
console.log("exit");
navigate("/");
};
const bookingInfo = location.state?.bookingInfo;
const onLogout = () => navigate("/");
const onBack = () => navigate("/seat-limit");
return (
<div>
<div className={styles.pageContainer}>
{success === true ? (
<div className={styles.success}>
{console.log("Worked")}
<img src={Ticket} alt="Success" className={styles.symbol} />
<h2 className={styles.hd}>🎉 Hooray!!! Successfully Booked</h2>
<img src={QRCode} alt="QR-Code" className={styles.qr} />
<p className={styles.timeStamp}>Booked On: {currentDateTime}</p>
<div className={styles.logout}>
<Button onClick={onLogout}>
<IoMdLogOut className={styles.logoutIcon} />
</Button>
<div>
<MdCheckCircle className={styles.symbol} />
</div>
<h1 className={styles.hd}>Booking Confirmed!</h1>
<p className={styles.subtitle}>
Your tickets are ready. Enjoy the show!
</p>
<div className={styles.qrContainer}>
<img src={Success} alt="QR code" className={styles.qr} />
<p className={styles.qrNote}>Scan this QR code at the entrance</p>
</div>
{bookingInfo && (
<div className={styles.info}>
<div className={styles.row}>
<span className={styles.label}>Movie</span>
<span className={styles.value}>Thug Life</span>
</div>
<div className={styles.row}>
<span className={styles.label}>Seats</span>
<div className={styles.seatWrapper}>
{bookingInfo.seats?.map((seat, i) => (
<span key={i} className={styles.seatPill}>
{seat}
</span>
))}
</div>
</div>
<div className={styles.row}>
<span className={styles.label}>User Number</span>
<span className={styles.value}>{bookingInfo.userId}</span>
</div>
<div className={styles.row}>
<span className={styles.label}>Booked On</span>
<span className={styles.value}>{bookingInfo.time}</span>
</div>
</div>
)}
<button onClick={onBack} className={styles.downloadBtn}>
<RiArrowGoBackLine className={styles.icon} />
<span>Book More Tickets</span>
</button>
<button onClick={onLogout} className={styles.homeBtn}>
<MdHome className={styles.icon} />
<span>Back to Home</span>
</button>
</div>
) : (
<div className={styles.error}>
{console.log("Failed")}
<img src={Failed} alt="Failed" className={styles.symbol} />
<h2>Something Went Wrong</h2>
<p className={styles.timeStamp}>Booked On: {currentDateTime}</p>
<div className={styles.logout}>
<Button onClick={onLogout}>
<IoMdLogOut className={styles.logoutIcon} />
</Button>
</div>
<h2 className={styles.hdError}>Something Went Wrong</h2>
<p className={styles.timeStamp}>
Booked On: {new Date().toLocaleString()}
</p>
<button onClick={onLogout} className={styles.homeBtn}>
<IoMdLogOut className={styles.icon} />
<span>Back to Home</span>
</button>
</div>
)}
</div>
......
.success,
.error {
.pageContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
text-align: center;
min-height: 80vh;
min-height: 100vh;
padding: 1rem;
}
.success {
background-color: #ffffff;
padding: 2rem;
border-radius: 1.5rem;
max-width: 500px;
width: 100%;
text-align: center;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.error {
background-color: #fff1f1;
padding: 2rem;
border-radius: 1.5rem;
max-width: 500px;
width: 100%;
text-align: center;
border: 1px solid #fca5a5;
box-shadow: 0 10px 25px rgba(239, 68, 68, 0.1);
}
.symbol {
width: 380px;
height: 300px;
margin-bottom: 1.5rem;
font-size: 64px;
color: #22c55e;
margin-bottom: 1rem;
}
.qr {
width: 128px;
height: 128px;
border-radius: 0.5rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.qrContainer {
background-color: #f3f4f6;
padding: 1.5rem;
border-radius: 1rem;
margin-bottom: 2rem;
}
.qrNote {
font-size: 0.75rem;
color: #6b7280;
margin-top: 0.5rem;
}
.hd {
font-size: 1.5rem;
font-weight: 700;
color: #28a745; /* Success Green */
margin-bottom: 1rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.5rem;
}
.error .hd {
color: #dc3545; /* Error Red */
.hdError {
font-size: 1.25rem;
font-weight: 600;
color: #dc2626;
margin-bottom: 0.5rem;
}
.qr {
width: 160px;
height: auto;
margin: 1rem 0;
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 0.5rem;
.subtitle {
color: #4b5563;
margin-bottom: 2rem;
}
.timeStamp {
font-size: 0.9rem;
color: #555;
margin: 1rem 0;
.info {
margin-bottom: 2rem;
font-size: 0.875rem;
}
.row {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.label {
color: #6b7280;
}
.value {
color: #1f2937;
font-weight: 500;
}
.seatWrapper {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-left: 2rem;
}
.seatPill {
background-color: #e0e7ff;
color: #4338ca;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
}
.downloadBtn {
width: 100%;
background-color: #4f46e5;
color: white;
font-weight: 500;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
border: none;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
transition: background-color 0.2s ease;
cursor: pointer;
}
.downloadBtn:hover {
background-color: #4338ca;
}
.homeBtn {
margin-top: 1rem;
width: 100%;
background-color: #e5e7eb;
color: #374151;
font-weight: 500;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
border: none;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
transition: background-color 0.2s ease;
cursor: pointer;
}
.homeBtn:hover {
background-color: #d1d5db;
}
.logout {
margin-top: 1.5rem;
.icon {
font-size: 1.25rem;
}
.logoutIcon {
font-size: 1.2rem;
color: #000;
.timeStamp {
font-size: 0.875rem;
color: #6b7280;
margin-bottom: 1rem;
}
body {
font-family: "Poppins", sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1rem;
margin: 0;
}
.container {
width: 100%;
max-width: 15rem;
......
import React, { useState } from 'react'
import { Header } from '../../Layout/Card/Header'
import {Screen} from '../Screen/Screen';
import { Index } from '../Seatposition/Index';
import styles from './Styles.module.css'
import { Button } from '../../Form/Button/Button';
import { Legend } from '../../Layout/Legend/Legend';
export const Layout = () => {
const[selectedseat,SetSelectedSeat]=useState([]);
const Confirm=()=>{
if(selectedseat.length > 0){
alert('Are You Sure To Book Tickets');
}
else{
alert("Choose Minimun One Ticket To Proceed");
}
}
return (
<>
<Header className={styles.head} title="Choose Your Seats" Color="white"/>
<div className={styles.center}>
<div className={styles.screen}>
<Screen/>
</div>
<div className={styles.seat}>
<Index selectedseat={selectedseat} SetSelectedseat={SetSelectedSeat} className='seats'/>
</div>
</div>
<div className={styles.btn}>
<Button backgroundColor="white"
label="Confirm"
primary={true}
onClick={Confirm}
/>
</div>
<div className='legend'>
<Legend/>
</div>
</>
)
}
* {
margin: 0;
padding: 0;
}
body {
width: 100%;
height: 60svh;
}
.head {
text-align: center;
margin-top: 25rem;
}
.seat {
overflow: visible;
position: relative;
}
.seat::before {
content: attr(data-tooltip);
position: relative;
bottom: 120%;
left: 50%;
transform: translateX(-50%);
color: #fff;
padding: 3px 6px;
font-size: 12px;
white-space: nowrap;
border-radius: 4px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease-in-out;
z-index: 10;
}
.seat:hover::before {
opacity: 1;
}
.screen {
display: flex;
justify-content: center;
margin-bottom: -5rem;
position: relative;
}
.center {
display: flex;
flex-direction: column;
align-items: center;
gap: 0;
width: 100%;
}
.btn {
margin-top: 4rem;
display: flex;
justify-content: center;
height: 3rem;
}
button {
height: 3rem;
width: 25%;
border-radius: 10px;
cursor: pointer;
font-weight: 700;
}
.legend {
display: flex;
justify-content: center;
align-items: center;
}
.log {
display: flex;
justify-content: end;
}
......@@ -81,6 +81,7 @@ export const SeatSelectionComponent = ({
children="Confirm"
backgroundColor="#fff"
onClick={ConfirmSeat}
disabled={selectedseats.length < seatLimit}
/>
</div>
</div>
......
import { createContext, useContext, useEffect, useState } from "react";
import * as api from "../Api/Api";
import { toast } from "react-toastify";
import { toast, ToastContainer } from "react-toastify";
import { useNavigate } from "react-router-dom";
export const AppContext = createContext();
......@@ -18,30 +18,50 @@ export function ContextProvider({ children }) {
console.log(reservedSeats);
const loginOrRegister = async ({ mobile, name }) => {
const loginUser = async ({ mobile }) => {
try {
const user = await api.loginUser(mobile);
if (!user) {
console.log("registering:", mobile, name);
const response = await api.register({ mobile, name });
console.log("Registered:", response);
sessionStorage.setItem("userId", response?.id);
await getAllReservedSeats();
navigate("/seat-limit");
return response;
} else {
sessionStorage.setItem("userId", user?.id);
const response = await getAllReservedSeats();
const userSelectedSeats = response.find((e) => e.userId == user?.id);
sessionStorage.setItem(
"seatLimit",
userSelectedSeats?.seats?.length || 0
);
navigate("/booking");
return user;
toast.error("User not found. Sign up now.");
console.log("New User");
return;
}
sessionStorage.setItem("userId", user.id);
const response = await api.getSeat();
const userSelectedSeats = response.find((e) => e.userId == user.id);
sessionStorage.setItem(
"seatLimit",
userSelectedSeats?.seats?.length || 0
);
navigate("/seat-limit");
return user;
} catch (err) {
toast.error(err.message);
toast.error(err.message || "Login failed");
}
};
const registerUser = async ({ mobile, name }) => {
try {
const existingUser = await api.loginUser(mobile);
if (existingUser) {
toast.error("Mobile number already registered. Please log in.");
navigate("/");
return;
}
const newUser = await api.register({ mobile, name });
sessionStorage.setItem("userId", newUser.id);
sessionStorage.setItem("seatLimit", "0");
toast.success("Signup successful!");
navigate("/");
return newUser;
} catch (err) {
toast.error(err.message || "Signup failed");
}
};
......@@ -83,9 +103,11 @@ export function ContextProvider({ children }) {
api.getUserReservedSeats(id),
]);
setReservedSeats(allReserved);
setUserSeats(userReserved);
setUserSeats({ userId: id, seats: userReserved });
return true;
} catch (err) {
toast.error(err.message || "Something went wrong.");
return false;
}
};
......@@ -129,11 +151,17 @@ export function ContextProvider({ children }) {
reservedSeats,
isLoading,
seatLayout,
loginOrRegister,
loginUser,
registerUser,
addOrUpdateSeats,
getAllReservedSeats,
getSeatLayout,
};
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
return (
<AppContext.Provider value={value}>
{children}
<ToastContainer position="top-right" autoClose={3000} />
</AppContext.Provider>
);
}
......@@ -7,7 +7,7 @@ import { useNavigate } from "react-router-dom";
export const LoginPage = () => {
const navigate = useNavigate();
const { loginOrRegister } = useAppContext();
const { loginUser, registerUser } = useAppContext();
const [showLogin, setShowLogin] = useState(true);
useEffect(() => {
......@@ -15,12 +15,14 @@ export const LoginPage = () => {
}, []);
const handlelogin = async ({ mobile }) => {
await loginOrRegister({ mobile });
navigate("/seat-limit");
const res = await loginUser({ mobile });
if (res) {
navigate("/seat-limit");
}
};
const handleSignup = async ({ name, mobile }) => {
const res = await loginOrRegister({ name, mobile });
const res = await registerUser({ name, mobile });
console.log("registered", res);
setShowLogin(true);
};
......
......@@ -112,7 +112,16 @@ function SeatBooking() {
sessionStorage.setItem("selectedSeats", JSON.stringify(validSeats));
if (typeof clearSelection === "function") clearSelection();
navigate("/booking-summary", { state: { success: true } });
navigate("/Ticket", {
state: {
success: true,
bookingInfo: {
userId,
seats: validSeats,
time: new Date().toLocaleString(),
},
},
});
} catch (error) {
console.error("Error saving seats:", error);
toast.error("Failed to reserve seats. Please try again.");
......
......@@ -2,6 +2,7 @@ import React from "react";
import Counter from "../components/Top-level/Counter/Counter";
import { useAppContext } from "../context/Index";
import styles from "./Styles.module.css";
import { MdEventSeat } from "react-icons/md";
export const SeatLimit = () => {
const totalSeats = 60;
const { reservedSeats } = useAppContext();
......@@ -14,7 +15,10 @@ export const SeatLimit = () => {
return (
<>
<p className={styles.line}>Remaining Seats Available:{Seat}</p>
<div className={styles.seat}>
<MdEventSeat className={styles.icon} />
<p className={styles.line}>Remaining Seats Available: {Seat}</p>
</div>
<Counter onSubmit={handleSeat} />
</>
);
......
......@@ -42,12 +42,20 @@
color: #c7d2fe;
font-weight: 500;
margin-bottom: 1rem;
text-align: center;
font-family: "Inter", sans-serif;
display: flex;
justify-content: center;
margin-left: 1rem;
}
.line p {
text-align: center;
}
.icon {
color: #fff;
position: absolute;
margin-left: 0.5rem;
margin-top: 4px;
}
@media (min-width: 768px) {
.container {
display: flex;
......@@ -62,4 +70,10 @@
.login {
min-width: 40rem;
}
.icon {
margin-left: 3.5rem;
}
.line {
margin-left: 2rem;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment