Commit 7260cb25 by Manivasagam S

code changes

parent d47e22b7
......@@ -13,7 +13,6 @@ export const Auth = ({
loginTabLabel = "Login",
signupTabLabel = "Signup",
initialMode = "login",
showForgotPassword = true,
showSwitchPrompt = true,
}) => {
const [isLoginMode, setIsLoginMode] = useState(initialMode === "login");
......
......@@ -16,7 +16,6 @@ export default {
options: ["login", "signup"],
name: "Initial Mode",
},
showForgotPassword: { control: "boolean", name: "Show Forgot Password" },
showSwitchPrompt: { control: "boolean", name: "Show Switch Prompt" },
},
parameters: {
......@@ -60,6 +59,5 @@ ModeSwitcher.args = {
loginTabLabel: "Login",
signupTabLabel: "Signup",
initialMode: "login",
showForgotPassword: true,
showSwitchPrompt: true,
};
......@@ -29,7 +29,6 @@
}
.primary:disabled {
opacity: 0.5;
cursor: not-allowed;
opacity: 0.7;
......@@ -48,7 +47,7 @@
}
.secondary:disabled {
background-color: #979595;
opacity: 0.7;
cursor: not-allowed;
}
......
......@@ -40,7 +40,7 @@
gap: 1rem;
}
/*
.modalselect {
padding: 10px;
border: 1px solid #d1d5db;
......@@ -57,4 +57,4 @@
}
*/
import styles from '@/Components/Base/Select/Select.module.css';
export const Select = ({ label, options, value, onChange }) => {
export const Select = ({ label, options, value, onChange, errorMessage }) => {
return (
<div className={styles.container}>
{label && <label className={styles.label}>{label}</label>}
<select className={styles.select} value={value} onChange={onChange} >
<select className={`${styles.select} ${errorMessage ? styles.errorSelect : ''}`} value={value} onChange={onChange}>
{(options || []).map((opt, index) => (
<option key={index} value={opt.value} className={styles.option}>
{opt.label}
</option>
))}
</select>
{errorMessage && <p className={styles.errorMessage}>{errorMessage}</p>}
</div>
);
};
......@@ -7,9 +7,7 @@
.label {
font-weight: bold;
}
.select:focus{
outline-color:var(--primary);
}
.select {
padding: 0.6rem;
font-size: 1em;
......@@ -26,3 +24,7 @@
border-radius: 4px;
}
.errorMessage{
color: red;
margin-left:3px;
}
\ No newline at end of file
......@@ -6,30 +6,38 @@ export default {
component: Select,
argTypes: {
label: { control: 'text' },
onChange: { action: 'onChange'},
onChange: { action: 'onChange' },
},
};
export const Default = (args) => {
const options = [
{ value: '1', label: '1 seat' },
{ value: '2', label: '2 seats' },
{ value: '3', label: '3 seats' },
];
return (
<Select
{...args}
options={options}
onChange={(e) => {
args.onChange(e);
}}
/>
);
};
Default.args = {
label: 'Choose a seat',
};
const options = [
{ value: '', label: 'Select seat' },
{ value: '1', label: '1 seat' },
{ value: '2', label: '2 seats' },
{ value: '3', label: '3 seats' },
];
export const ValidandInvalid = (args) => (
<div>
<div>
<Select
{...args}
label="Select without error"
value="1"
options={options}
errorMessage=""
onChange={(e) => args.onChange(e)}
/>
</div>
<div>
<Select
{...args}
label="Select with error"
value=""
options={options}
errorMessage="Please select a seat."
onChange={(e) => args.onChange(e)}
/>
</div>
</div>
);
......@@ -4,7 +4,6 @@ import { Button } from "@/Components//Base/Buttons/Button";
import styles from "@/Components/Form/Login/Login.module.css";
export const Login = ({
loginHeading = "Login With Your Mobile Number",
placeholder = "Enter Mobile Number",
onLogin,
}) => {
......
......@@ -4,7 +4,6 @@
}
.headerNew {
font-size: 1.7em;
font-family:'Segoe UI';
color: black;
margin-bottom: 1.5rem;
text-align: center;
......
......@@ -2,7 +2,7 @@
.modalform {
display: flex;
flex-direction: column;
gap: 16px;
gap: 1rem;
min-width: auto;
}
......
......@@ -59,15 +59,6 @@ export const SignUp = ({
if (!validateForm()) return;
try {
const res = await getUserByPhone(formData.phoneNumber);
if (res.data.length > 0) {
setErrors((prev) => ({
...prev,
phoneNumber: 'Phone number already registered',
}));
return;
}
await onSignUpSubmit?.(formData);
setFormData({ name: '', email: '', phoneNumber: '' });
} catch (error) {
......
......@@ -13,12 +13,12 @@
border-radius: 2rem;
}
.text {
/* .text {
margin-bottom: 2rem;
color: #00020c;
font-family: 'Segoe UI';
/* font-family: 'Segoe UI';} */
}
.form {
display: flex;
flex-direction: column;
......
......@@ -3,12 +3,12 @@
justify-content: center;
align-items: center;
width: 100%;
min-width: 30rem;
min-width: 15rem;
position: relative;
bottom: 1rem;
}
@media(min-width:320px){
@media(min-width:640px){
.shadow{
min-width: 15rem;
min-width: 30rem;
}
}
......@@ -32,7 +32,7 @@
.title,
.msg {
color: #100f0f;
color:black;
font-weight: bold;
}
......@@ -119,7 +119,7 @@
.seatListWrapper {
margin-top: 1rem;
background-color: #f5f5f5;
background-color:white;
padding: 1rem;
border-radius: 1rem;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
......
......@@ -3,15 +3,15 @@ import cn from 'classnames';
import styles from '@/Components/Top-level/Seat/Seat.module.css';
export const Seat = ({ seatNo, status, onClick }) => {
const isMine = status === 'mine' || status === 'mine-unselected';
const isMine = status === 'selected' || status === 'unselected';
const className = cn(styles.seat, {
[styles.noSeat]: status === 'none',
[styles.available]: status === 'available',
[styles.reserved]: status === 'reserved',
[styles.selected]: status === 'selected',
[styles.mine]: isMine,
[styles.unselected]: status === 'mine-unselected',
[styles.selected]: isMine,
[styles.unselected]: status === 'unselected',
});
return (
......@@ -19,7 +19,7 @@ export const Seat = ({ seatNo, status, onClick }) => {
className={className}
data-tooltip={seatNo}
role="button"
tabIndex={['available', 'selected', 'mine', 'mine-unselected'].includes(status) ? 0 : -1}
tabIndex={['available', 'selected',"reserved", 'unselected'].includes(status) ? 0 : -1}
onClick={onClick}
>
{null}
......
......@@ -25,10 +25,7 @@
.reserved {
background-color:var(--reserved);
}
.mine{
background-color: var(--selected);
cursor: pointer;
}
.unselected {
background-color:transparent;
border: 0.5px solid rgba(164, 175, 255, 1);
......@@ -67,7 +64,7 @@
width: 3.75rem;
border-radius: 3px;
background-color: #f7f4f4;
color: #080808;
color:black;
content: attr(data-tooltip);
text-align: center;
font-size: 0.75rem;
......
import { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useState, useEffect } from 'react';
import { Seat } from '@/Components/Top-level/Seat/Seat.jsx';
import styles from '@/Components/Top-level/SeatLayout/SeatLayout.module.css';
import { seatsData } from '@/Components/Top-level/SeatLayout/SeatsData.js';
import { toast } from 'react-toastify';
const populateSeatsArray = (selectedSeats, reservedSeats, currentReservedSeats) => {
const populateSeatsArray = (selectedSeats, reservedSeats) => {
return seatsData.map(({ row, seats }) =>
seats.map((number) => {
const seatNo = row + number;
if (number === '/') return { seatNo, status: 'none' };
const isMine = currentReservedSeats.includes(seatNo);
const isSelected = selectedSeats.includes(seatNo);
const isReserved = reservedSeats.includes(seatNo);
if (isMine && isSelected) return { seatNo, status: 'selected' };
if (isMine && !isSelected) return { seatNo, status: 'mine-unselected' };
if (isReserved) return { seatNo, status: 'reserved' };
if (isSelected) return { seatNo, status: 'selected' };
if (reservedSeats.includes(seatNo)) return { seatNo, status: 'reserved' };
if (selectedSeats.includes(seatNo)) return { seatNo, status: 'selected' };
return { seatNo, status: 'available' };
})
);
......@@ -26,58 +19,34 @@ const populateSeatsArray = (selectedSeats, reservedSeats, currentReservedSeats)
export const SeatLayout = ({
selectedSeats: initialSelected = [],
reservedSeats = [],
currentReservedSeats = [],
reservedSeats,
limit,
onSelectionChange,
}) => {
const [localSelectedSeats, setLocalSelectedSeats] = useState(currentReservedSeats);
const [selectedSeats, setSelectedSeats] = useState(initialSelected);
useEffect(() => {
setLocalSelectedSeats(currentReservedSeats);
}, [currentReservedSeats]);
useEffect(() => {
onSelectionChange?.(localSelectedSeats);
}, [localSelectedSeats]);
const seatClickHandler = (seat) => {
const { seatNo, status } = seat;
const isMine = currentReservedSeats.includes(seatNo);
const isSelected = localSelectedSeats.includes(seatNo);
setSelectedSeats(initialSelected);
}, [initialSelected]);
const seatClickHandler = ({ seatNo, status }) => {
if (status === 'reserved') return;
setLocalSelectedSeats((prev) => {
const updated = [...prev];
const isSelected = selectedSeats.includes(seatNo);
const updated = isSelected
? selectedSeats.filter((s) => s !== seatNo)
: [...selectedSeats, seatNo];
if (isSelected) {
return updated.filter((s) => s !== seatNo); // unselect
} else {
const newlySelected = updated.filter(
(s) => !currentReservedSeats.includes(s)
);
const unselectedMine = currentReservedSeats.filter(
(s) => !updated.includes(s)
);
if (!isSelected && updated.length > limit) {
toast.warn(`Only ${limit} seat${limit > 1 ? 's' : ''} allowed`);
return;
}
const totalAllowed = limit + unselectedMine.length;
if (newlySelected.length < totalAllowed) {
return [...updated, seatNo];
} else {
return updated;
}
}
});
setSelectedSeats(updated);
onSelectionChange(updated); // fire callback
};
const renderedSeats = populateSeatsArray(
localSelectedSeats,
reservedSeats,
currentReservedSeats
);
const renderedSeats = populateSeatsArray(selectedSeats, reservedSeats);
return (
<div className={styles.container}>
......@@ -94,8 +63,7 @@ export const SeatLayout = ({
SeatLayout.propTypes = {
selectedSeats: PropTypes.arrayOf(PropTypes.string),
reservedSeats: PropTypes.arrayOf(PropTypes.string),
currentReservedSeats: PropTypes.arrayOf(PropTypes.string),
reservedSeats: PropTypes.arrayOf(PropTypes.string).isRequired,
limit: PropTypes.number.isRequired,
onSelectionChange: PropTypes.func.isRequired,
onSelectionChange: PropTypes.func,
};
import { SeatLayout } from '@/Components/Top-level/SeatLayout/SeatLayout';
import { action } from '@storybook/addon-actions';
const reservedSeatsSample = ['A1', 'B2', 'C3'];
const currentUserReservedSeats = ['A3', 'B4'];
const logSelectionChange = action('Clicked/Unclicked');
export default {
title: 'Toplevel/SeatLayout',
component: SeatLayout,
......@@ -15,63 +11,18 @@ export default {
layout: 'centered',
},
argTypes: {
selectedSeats: {
control: { type: 'array' },
description: 'Seats currently selected by the user',
},
reservedSeats: {
control: { type: 'array' },
description: 'Seats reserved by others',
},
currentReservedSeats: {
control: { type: 'array' },
description: 'Seats reserved by the logged-in user',
},
selectedSeats: { control: 'array' },
reservedSeats: { control: 'array' },
limit: { control: 'number' },
},
};
const modeConfig = {
available: {
selectedSeats: [],
reservedSeats: [],
currentReservedSeats: [],
limit: 3,
},
reserved: {
selectedSeats: [],
reservedSeats: reservedSeatsSample,
currentReservedSeats: [],
limit: 2,
},
selected: {
selectedSeats: [],
reservedSeats: [],
currentReservedSeats: currentUserReservedSeats,
limit: 1,
},
};
export const Default = (args) => {
const config = modeConfig[args.mode] || modeConfig.available;
return (
<SeatLayout
selectedSeats={args.selectedSeats.length ? args.selectedSeats : config.selectedSeats}
reservedSeats={args.reservedSeats.length ? args.reservedSeats : config.reservedSeats}
currentReservedSeats={
args.currentReservedSeats.length
? args.currentReservedSeats
: config.currentReservedSeats
}
limit={config.limit}
onSelectionChange={logSelectionChange}
/>
);
};
export const Default = (args) => (
<SeatLayout {...args} onSelectionChange={action('Clicked/Unclicked')} />
);
Default.args = {
mode: 'available',
selectedSeats: [],
reservedSeats: [],
currentReservedSeats: [],
selectedSeats: ['B1'],
reservedSeats: ['A1', 'B2'],
limit: 3,
};
......@@ -14,8 +14,8 @@ import { AiOutlineLogout } from "react-icons/ai";
export const Selectseat = ({ onLogout }) => {
const [selectedSeats, setSelectedSeats] = useState([]);
const [selectedData, setselectedData] = useState([]);
const [reservedSeats, setreservedSeats] = useState([]);
const [reservedSeats, setReservedSeats] = useState([]);
const [userSeats, setUserSeats] = useState([]);
const [seatCount, setSeatCount] = useState(1);
const [availableSeats, setAvailableSeats] = useState(0);
const [showSelect, setShowSelect] = useState(true);
......@@ -24,31 +24,33 @@ export const Selectseat = ({ onLogout }) => {
useEffect(() => {
getAllUsers()
.then(res => {
.then((res) => {
const users = res.data;
const totalSeats = 60;
const loggedUser = getCurrentUser();
const totalSeats = 60;
const allReserved = users.flatMap(u => u.reservedSeats || []);
const currentUser = users.find(u => u.phoneNumber === loggedUser.phoneNumber);
const userSeats = currentUser?.reservedSeats || [];
const othersReserved = allReserved.filter(seat => !userSeats.includes(seat));
setselectedData(userSeats);
setreservedSeats(othersReserved);
setSeatCount(userSeats.length);
const currentUser = users.find(
(u) => u.phoneNumber === loggedUser.phoneNumber
);
const userReserved = currentUser?.reservedSeats || [];
const allReserved = users.flatMap((u) => u.reservedSeats || []);
const othersReserved = allReserved.filter(
(seat) => !userReserved.includes(seat)
);
setUserSeats(userReserved);
setSelectedSeats(userReserved);
setReservedSeats(othersReserved);
setSeatCount(userReserved.length);
setAvailableSeats(totalSeats - allReserved.length);
setShowSelect(true);
const available = totalSeats - allReserved.length;
setAvailableSeats(available);
if (currentUser?.name && !hasWelcomed.current) {
toast.success(`Welcome😊, ${currentUser.name}`);
toast.success(`Welcome 😊, ${currentUser.name}`);
hasWelcomed.current = true;
}
})
.catch(err => {
.catch((err) => {
console.error("Error fetching users", err);
toast.error("Failed to load seat data.");
});
......@@ -56,60 +58,72 @@ export const Selectseat = ({ onLogout }) => {
const confirmHandler = () => {
const user = getCurrentUser();
if (!user || !user.phoneNumber?.trim()) {
toast.warn("User not logged in");
return;
}
const unselectedReserved = selectedData.filter(seat => !selectedSeats.includes(seat));
const newlySelected = selectedSeats.filter(s => !selectedData.includes(s));
console.log("test",unselectedReserved);
if (unselectedReserved.length > 0 && newlySelected.length < unselectedReserved.length) {
toast.error(`You unselected ${unselectedReserved.length} reserved seat(s), so you must select ${unselectedReserved.length} new seat(s).`);
return;
}
if (newlySelected.length < seatCount - selectedData.length) {
toast.error(`You must select exactly ${seatCount - selectedData.length} new seats`);
const unselectedMine = userSeats.filter(
(seat) => !selectedSeats.includes(seat)
);
const newlySelected = selectedSeats.filter(
(seat) => !userSeats.includes(seat)
);
if (
unselectedMine.length > 0 &&
newlySelected.length < unselectedMine.length
) {
toast.error(
`You unselected ${unselectedMine.length} seat(s), select ${unselectedMine.length} new.`
);
hasWelcomed.current = true;
return;
}
if (selectedSeats.length !== seatCount) {
toast.error(`You must select exactly ${seatCount} seat(s).`);
hasWelcomed.current = true;
return;
}
getAllUsers()
.then(res => {
.then((res) => {
const users = res.data;
const currentUser = users.find(u => u.phoneNumber === user.phoneNumber);
const currentUser = users.find(
(u) => u.phoneNumber === user.phoneNumber
);
if (!currentUser) {
toast.error("User not found");
return;
}
const latestReserved = users
.flatMap(u => u.reservedSeats || [])
.filter(seat => !currentUser.reservedSeats.includes(seat));
const conflictSeats = selectedSeats.filter(seat => latestReserved.includes(seat));
const latestOthersReserved = users
.flatMap((u) => u.reservedSeats || [])
.filter((seat) => !currentUser.reservedSeats.includes(seat));
if (conflictSeats.length > 0) {
toast.error(`The following seats were just taken: ${conflictSeats.join(", ")}`);
hasWelcomed.current = true;
const conflict = selectedSeats.filter((seat) =>
latestOthersReserved.includes(seat)
);
if (conflict.length > 0) {
toast.error(`These seats were just taken: ${conflict.join(", ")}`);
return;
}
return updateUserReservedSeats(currentUser.id, selectedSeats);
})
.then(res => {
.then((res) => {
if (res) {
toast.success("Seats booked!");
hasWelcomed.current = true;
hasWelcomed.current = true;
localStorage.setItem("bookedSeats", JSON.stringify(selectedSeats));
setSelectedSeats([]);
setTimeout(() => window.location.replace("/success"), 1000);
}
})
.catch(err => {
.catch((err) => {
console.error("Error confirming seats", err);
toast.error("Failed to confirm seats");
toast.error("Failed to confirm seats.");
setTimeout(() => window.location.replace("/error"), 1000);
});
};
......@@ -117,8 +131,8 @@ export const Selectseat = ({ onLogout }) => {
const handleModal = () => setShowSelect(false);
const seatOptions = Array.from({ length: availableSeats }).map((_, i) => ({
value: selectedData.length + i + 0,
label: `${selectedData.length + i + 0} seats`
value: userSeats.length + i + 0,
label: `${userSeats.length + i + 0} seats`,
}));
return (
......@@ -129,27 +143,23 @@ export const Selectseat = ({ onLogout }) => {
<div className={styles.container}>
<h2 className={styles.text}>Choose Seats</h2>
<Screen />
<div className={styles.seatcontainer}>
<SeatLayout
selectedSeats={selectedSeats}
reservedSeats={reservedSeats}
currentReservedSeats={selectedData}
limit={seatCount - selectedData.length}
limit={seatCount}
onSelectionChange={setSelectedSeats}
/>
</div>
<div className={styles.button}>
<Button
variant="secondary"
label="Confirm"
size="md"
onClick={confirmHandler}
disabled={seatCount === 0 || selectedSeats.length < seatCount - selectedData.length}
disabled={selectedSeats.length !== seatCount}
/>
</div>
<div className={styles.legend}>
<Legend />
</div>
......@@ -157,11 +167,11 @@ export const Selectseat = ({ onLogout }) => {
{showSelect && (
<Modal
title={
selectedData.length > 0
? `You have already booked ${selectedData.length} seat(s) Add more?`
title={
userSeats.length > 0
? `You have already booked ${userSeats.length} seat(s). Add more?`
: "Choose Number of Seats"
}
}
availableSeats={availableSeats}
seatCount={seatCount}
setSeatCount={setSeatCount}
......
......@@ -22,33 +22,34 @@ export const AuthPage = () => {
}
};
const handleSignUp = async (formData) => {
try {
if (!formData || !formData.phoneNumber) {
toast.error("Invalid form data");
return;
}
const response = await getUserByPhone(formData.phoneNumber);
if (response.data.length > 0) {
toast.warn("Phone number already registered.");
return;
}
const handleSignUp = async (formData) => {
try {
if (!formData || !formData.phoneNumber) {
toast.error("📛 Invalid form data");
return;
}
const newUser = { ...formData, reservedSeats: [] };
const postRes = await postUser(newUser);
// ✅ Check if phone number already exists
const response = await getUserByPhone(formData.phoneNumber);
if (response.data.length > 0) {
toast.warn("📱 Phone number already registered.");
return;
}
const newUser = { ...formData, reservedSeats: [] };
const postRes = await postUser(newUser);
if (postRes.status === 201) {
localStorage.setItem("user", JSON.stringify(postRes.data));
toast.success("Account created successfully!");
} else {
toast.error("Failed to create account. Try again.");
}
} catch (error) {
console.error("Sign up error:", error);
toast.error("Network error or server not available.");
if (postRes.status === 201) {
localStorage.setItem("user", JSON.stringify(postRes.data));
toast.success("Account created successfully!");
} else {
toast.error("Failed to create account. Try again.");
}
};
} catch (error) {
console.error("Sign up error:", error);
toast.error("🌐 Network error or server not available.");
}
};
return (
<Auth
......
......@@ -23,23 +23,33 @@
"F2",
"F1",
"F8",
"D8",
"E7",
"G2",
"B3",
"D6",
"G5",
"F5",
"F6",
"E6",
"D5",
"G6",
"E5",
"G7",
"A4",
"A5",
"A6",
"A3"
"B2",
"B3",
"D8",
"B1",
"H4",
"H5",
"C1",
"A3",
"A2",
"C2",
"G5",
"G6",
"G1",
"H3",
"H6",
"F3",
"E5",
"F6",
"B8"
]
},
{
......@@ -189,6 +199,27 @@
"D3",
"C4"
]
},
{
"id": "f58c",
"name": "mani",
"email": "kani@gmail.com",
"phoneNumber": "1234567598",
"reservedSeats": []
},
{
"id": "c905",
"name": "mani",
"email": "kani@gmail.com",
"phoneNumber": "1234567894",
"reservedSeats": []
},
{
"id": "c9d6",
"name": "mani",
"email": "mani@gmail.com",
"phoneNumber": "2134567809",
"reservedSeats": []
}
]
}
\ No newline at end of file
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