Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
training-seat-booking
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Manivasagam S
training-seat-booking
Commits
28a2f91f
Commit
28a2f91f
authored
Jun 16, 2025
by
Manivasagam S
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
code changed
parent
840f2f6f
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
521 additions
and
275 deletions
+521
-275
Auth.jsx
src/Components/Auth/Auth.jsx
+44
-18
Auth.module.css
src/Components/Auth/Auth.module.css
+0
-0
Auth.stories.jsx
src/Components/Auth/Auth.stories.jsx
+57
-0
Button.module.css
src/Components/Base/Buttons/Button.module.css
+2
-1
Button.stories.jsx
src/Components/Base/Buttons/Button.stories.jsx
+0
-1
Screen.stories.jsx
src/Components/Base/Screen/Screen.stories.jsx
+0
-13
SignUp.jsx
src/Components/Form/SignUp/SignUp.jsx
+25
-35
Screen.jsx
src/Components/Shared/Screen/Screen.jsx
+3
-2
Screen.module.css
src/Components/Shared/Screen/Screen.module.css
+0
-0
Screen.stories.jsx
src/Components/Shared/Screen/Screen.stories.jsx
+30
-0
Notification.jsx
src/Components/Top-level/Notification/Notification.jsx
+1
-1
Notification.module.css
...Components/Top-level/Notification/Notification.module.css
+3
-1
Seat.jsx
src/Components/Top-level/Seat/Seat.jsx
+13
-3
Seat.module.css
src/Components/Top-level/Seat/Seat.module.css
+6
-1
SeatLayout.jsx
src/Components/Top-level/SeatLayout/SeatLayout.jsx
+50
-49
SeatLayout.module.css
src/Components/Top-level/SeatLayout/SeatLayout.module.css
+1
-1
SeatLayout.stories.jsx
src/Components/Top-level/SeatLayout/SeatLayout.stories.jsx
+58
-42
Selectseat.jsx
src/Components/Top-level/Seatselect/Selectseat.jsx
+88
-76
Selectseat.module.css
src/Components/Top-level/Seatselect/Selectseat.module.css
+6
-1
Auth.stories.jsx
src/Pages/Auth/Auth.stories.jsx
+0
-29
AuthPage.jsx
src/Pages/AuthPage.jsx
+31
-1
db.json
src/db.json
+103
-0
No files found.
src/
Page
s/Auth/Auth.jsx
→
src/
Component
s/Auth/Auth.jsx
View file @
28a2f91f
import
{
useState
}
from
"react"
;
import
{
useState
,
useEffect
}
from
"react"
;
import
styles
from
"./Auth.module.css"
;
import
{
ToastContainer
,
toast
}
from
"react-toastify"
;
import
{
ToastContainer
}
from
"react-toastify"
;
import
"react-toastify/dist/ReactToastify.css"
;
export
const
Auth
=
({
LoginComponent
,
SignUpComponent
,
onLoginSubmit
,
onSignUpSubmit
})
=>
{
const
[
isLoginMode
,
setIsLoginMode
]
=
useState
(
true
);
export
const
Auth
=
({
LoginComponent
,
SignUpComponent
,
onLoginSubmit
,
onSignUpSubmit
,
loginLabel
=
"Login Form"
,
signupLabel
=
"SignUp Form"
,
loginTabLabel
=
"Login"
,
signupTabLabel
=
"Signup"
,
initialMode
=
"login"
,
showForgotPassword
=
true
,
showSwitchPrompt
=
true
,
})
=>
{
const
[
isLoginMode
,
setIsLoginMode
]
=
useState
(
initialMode
===
"login"
);
useEffect
(()
=>
{
setIsLoginMode
(
initialMode
===
"login"
);
},
[
initialMode
]);
return
(
<
div
className=
{
styles
.
container
}
>
<
div
className=
{
styles
.
formCard
}
>
<
h2
className=
{
styles
.
header
}
>
{
isLoginMode
?
"Login Form"
:
"SignUp Form"
}
</
h2
>
<
h2
className=
{
styles
.
header
}
>
{
isLoginMode
?
loginLabel
:
signupLabel
}
</
h2
>
<
div
className=
{
styles
.
tabContainer
}
>
<
button
className=
{
`${styles.tabButton} ${isLoginMode ? styles.activeTab : ""}`
}
onClick=
{
()
=>
setIsLoginMode
(
true
)
}
>
Login
{
loginTabLabel
}
</
button
>
<
button
className=
{
`${styles.tabButton} ${!isLoginMode ? styles.activeTab : ""}`
}
onClick=
{
()
=>
setIsLoginMode
(
false
)
}
>
Signup
{
signupTabLabel
}
</
button
>
</
div
>
{
isLoginMode
?
(
<
div
className=
{
styles
.
login
}
>
<
LoginComponent
onLogin=
{
onLoginSubmit
}
/>
<
a
href=
"#"
className=
{
styles
.
forgotLink
}
>
Forgot password?
</
a
>
{
showForgotPassword
&&
(
<
a
href=
"#"
className=
{
styles
.
forgotLink
}
>
Forgot password?
</
a
>
)
}
</
div
>
)
:
(
<
div
className=
{
styles
.
signup
}
>
<
SignUpComponent
onSubmit=
{
()
=>
{
setIsLoginMode
(
true
);
if
(
onSignUpSubmit
)
onSignUpSubmit
();
}
}
/>
<
SignUpComponent
onSignUpSubmit=
{
async
(
formData
)
=>
{
if
(
onSignUpSubmit
)
{
await
onSignUpSubmit
(
formData
);
setIsLoginMode
(
true
);
}
}
}
/>
</
div
>
)
}
<
div
className=
{
styles
.
signupPrompt
}
>
{
isLoginMode
?
"Not a member?"
:
"Already a member?"
}{
" "
}
<
a
href=
"#"
onClick=
{
()
=>
setIsLoginMode
(
!
isLoginMode
)
}
>
{
isLoginMode
?
"Signup now"
:
"Login"
}
</
a
>
</
div
>
{
showSwitchPrompt
&&
(
<
div
className=
{
styles
.
signupPrompt
}
>
{
isLoginMode
?
"Not a member?"
:
"Already a member?"
}{
" "
}
<
a
href=
"#"
onClick=
{
()
=>
setIsLoginMode
(
!
isLoginMode
)
}
>
{
isLoginMode
?
"Signup now"
:
"Login"
}
</
a
>
</
div
>
)
}
</
div
>
<
ToastContainer
position=
"top-right"
autoClose=
{
3000
}
/>
...
...
src/
Page
s/Auth/Auth.module.css
→
src/
Component
s/Auth/Auth.module.css
View file @
28a2f91f
File moved
src/Components/Auth/Auth.stories.jsx
0 → 100644
View file @
28a2f91f
import
{
Auth
}
from
"./Auth.jsx"
;
import
{
Login
}
from
"../Form/Login/Login.jsx"
;
import
{
SignUp
}
from
"../Form/SignUp/SignUp.jsx"
;
import
{
action
}
from
"@storybook/addon-actions"
;
export
default
{
title
:
"Auth/Auth"
,
component
:
Auth
,
argTypes
:
{
loginLabel
:
{
control
:
"text"
,
name
:
"Login Title"
},
signupLabel
:
{
control
:
"text"
,
name
:
"Signup Title"
},
loginTabLabel
:
{
control
:
"text"
,
name
:
"Login Tab Label"
},
signupTabLabel
:
{
control
:
"text"
,
name
:
"Signup Tab Label"
},
initialMode
:
{
control
:
{
type
:
"radio"
},
options
:
[
"login"
,
"signup"
],
name
:
"Initial Mode"
,
},
showForgotPassword
:
{
control
:
"boolean"
,
name
:
"Show Forgot Password"
},
showSwitchPrompt
:
{
control
:
"boolean"
,
name
:
"Show Switch Prompt"
},
},
};
const
DummyLogin
=
({
onLogin
})
=>
(
<
div
>
<
Login
onLogin=
{
onLogin
}
/>
</
div
>
);
const
DummySignUp
=
({
onSubmit
})
=>
(
<
div
>
<
SignUp
onSignUpSubmit=
{
async
(
formData
)
=>
{
action
(
'Sign Up Submitted'
)(
formData
);
// logs action
return
new
Promise
((
resolve
)
=>
setTimeout
(
resolve
,
1000
));
// simulate async
}
}
/>
</
div
>
);
export
const
AuthTabs
=
(
args
)
=>
(
<
Auth
{
...
args
}
LoginComponent=
{
DummyLogin
}
SignUpComponent=
{
DummySignUp
}
onLoginSubmit=
{
action
(
"Login Submit"
)
}
onSignUpSubmit=
{
async
(
formData
)
=>
action
(
"Signup Submit"
)
}
/>
);
AuthTabs
.
args
=
{
loginLabel
:
"Login Form"
,
signupLabel
:
"SignUp Form"
,
loginTabLabel
:
"Login"
,
signupTabLabel
:
"Signup"
,
initialMode
:
"login"
,
showForgotPassword
:
true
,
showSwitchPrompt
:
true
,
};
src/Components/Base/Buttons/Button.module.css
View file @
28a2f91f
...
...
@@ -80,7 +80,8 @@
background-color
:
var
(
--secondary
);
color
:
black
;
border-radius
:
15px
;
min-width
:
12rem
;
width
:
100%
;
/* min-width: 12rem; */
}
.secondary
:hover
{
...
...
src/Components/Base/Buttons/Button.stories.jsx
View file @
28a2f91f
import
{
Button
}
from
'./Button'
;
import
styles
from
'../Buttons/Button.module.css'
;
export
default
{
title
:
'Base/Button'
,
component
:
Button
,
...
...
src/Components/Base/Screen/Screen.stories.jsx
deleted
100644 → 0
View file @
840f2f6f
import
{
Screen
}
from
"./Screen"
;
export
default
{
title
:
'Shared/Screen'
,
component
:
Screen
,
parameters
:
{
backgrounds
:
{
default
:
'blue-bg'
,
values
:
[{
name
:
'blue-bg'
,
value
:
'#3444c5'
}],
},
}
}
export
const
Default
=
(
args
)
=>
<
Screen
{
...
args
}
/>
\ No newline at end of file
src/Components/Form/SignUp/SignUp.jsx
View file @
28a2f91f
...
...
@@ -2,13 +2,12 @@ import React, { useState } from 'react';
import
{
Input
}
from
'../../Base/Input/Input'
;
import
{
Button
}
from
'../../Base/Buttons/Button'
;
import
styles
from
'./SignUp.module.css'
;
// import signup from '../../../assets/Signup.png';
import
{
getUserByPhone
,
postUser
}
from
'../../../Api/userApi'
;
import
{
getUserByPhone
}
from
'../../../Api/userApi'
;
import
{
toast
}
from
'react-toastify'
;
import
'react-toastify/dist/ReactToastify.css'
;
export
const
SignUp
=
({
onSubmit
,
onS
ignUpS
ubmit
,
label
=
"SignUp"
,
namePlaceholder
=
"Name"
,
emailPlaceholder
=
"Email"
,
...
...
@@ -34,8 +33,7 @@ export const SignUp = ({
setErrors
((
prev
)
=>
({
...
prev
,
phoneNumber
:
''
}));
};
const
handleSubmit
=
async
(
e
)
=>
{
e
.
preventDefault
();
const
validateForm
=
()
=>
{
const
newErrors
=
{};
if
(
!
name
.
trim
())
newErrors
.
name
=
'Name is required'
;
...
...
@@ -61,47 +59,39 @@ export const SignUp = ({
}
setErrors
(
newErrors
);
return
Object
.
keys
(
newErrors
).
length
===
0
;
};
if
(
Object
.
keys
(
newErrors
).
length
===
0
)
{
try
{
const
res
=
await
getUserByPhone
(
phoneNumber
);
if
(
res
.
data
.
length
>
0
)
{
setErrors
((
prev
)
=>
({
...
prev
,
phoneNumber
:
'Phone number already registered'
,
}));
return
;
}
const
handleSubmit
=
async
(
e
)
=>
{
e
.
preventDefault
();
const
formData
=
{
name
,
email
,
phoneNumber
,
reservedSeats
:
[]
};
const
postRes
=
await
postUser
(
formData
);
setName
(
""
);
setEmail
(
""
);
setPhoneNumber
(
""
);
if
(
postRes
.
status
===
201
)
{
toast
.
success
(
"Account created successfully!"
);
if
(
onSubmit
)
onSubmit
(
formData
);
}
else
{
toast
.
error
(
"Something went wrong. Please try again."
);
}
if
(
!
validateForm
())
return
;
}
catch
(
err
)
{
console
.
error
(
'Error submitting form:'
,
err
);
toast
.
error
(
"Network error or server not available."
);
try
{
const
res
=
await
getUserByPhone
(
phoneNumber
);
if
(
res
.
data
.
length
>
0
)
{
setErrors
((
prev
)
=>
({
...
prev
,
phoneNumber
:
'Phone number already registered'
,
}));
return
;
}
const
formData
=
{
name
,
email
,
phoneNumber
};
await
onSignUpSubmit
?.(
formData
);
setName
(
''
);
setEmail
(
''
);
setPhoneNumber
(
''
);
}
catch
(
error
)
{
toast
.
error
(
"Something went wrong. Please try again."
);
}
};
return
(
<
div
className=
{
styles
.
SignUpContainer
}
>
<
div
className=
{
styles
.
wrapper
}
>
{
/* <div
className={styles.leftPane}
style={{ backgroundImage: `url(${signup})` }}
></div> */
}
<
div
className=
{
styles
.
rightPane
}
>
{
/* <h1 className={styles.text}>Create Account</h1> */
}
<
form
onSubmit=
{
handleSubmit
}
className=
{
styles
.
form
}
>
<
Input
type=
"text"
...
...
src/Components/
Base
/Screen/Screen.jsx
→
src/Components/
Shared
/Screen/Screen.jsx
View file @
28a2f91f
import
styles
from
"../Screen/Screen.module.css"
import
styles
from
"./Screen.module.css"
;
export
const
Screen
=
({
width
=
600
,
height
=
210
})
=>
(
<
div
className=
{
styles
.
shadow
}
>
<
svg
width=
{
width
}
height=
{
height
}
viewBox=
"0 0 300 120"
xmlns=
"http://www.w3.org/2000/svg"
>
...
...
@@ -11,6 +12,6 @@ export const Screen = ({ width = 600, height = 210 }) => (
</
defs
>
<
path
d=
"M30 60 Q150 36 270 60 L285 130 L10 130 Z"
fill=
"url(#lightGradient)"
/>
<
path
d=
"M30 60 Q150 36 270 60"
stroke=
"#fff"
strokeWidth=
"2"
fill=
"none"
strokeLinecap=
"round"
/>
</
svg
>
</
svg
>
</
div
>
);
src/Components/
Base
/Screen/Screen.module.css
→
src/Components/
Shared
/Screen/Screen.module.css
View file @
28a2f91f
File moved
src/Components/Shared/Screen/Screen.stories.jsx
0 → 100644
View file @
28a2f91f
import
React
from
"react"
;
import
{
Screen
}
from
"./Screen"
;
export
default
{
title
:
"Shared/Screen"
,
component
:
Screen
,
parameters
:
{
backgrounds
:
{
default
:
"blue-bg"
,
values
:
[{
name
:
"blue-bg"
,
value
:
"#3444c5"
}],
},
},
argTypes
:
{
width
:
{
control
:
{
type
:
"number"
},
defaultValue
:
600
,
},
height
:
{
control
:
{
type
:
"number"
},
defaultValue
:
210
,
},
},
};
export
const
statndardScreen
=
(
args
)
=>
<
Screen
{
...
args
}
/>;
statndardScreen
.
args
=
{
width
:
600
,
height
:
210
,
};
src/Components/Top-level/Notification/Notification.jsx
View file @
28a2f91f
...
...
@@ -30,7 +30,7 @@ export const Notification = ({ title, type, msg, button, }) => {
</
span
>
<
h2
className=
{
styles
.
title
}
>
{
title
}
</
h2
>
<
p
className=
{
styles
.
msg
}
>
{
msg
}
</
p
>
{
button
&&
button
}
<
div
className=
{
styles
.
button
}
>
{
button
&&
button
}
</
div
>
</
div
>
</>
);
...
...
src/Components/Top-level/Notification/Notification.module.css
View file @
28a2f91f
...
...
@@ -18,7 +18,9 @@ font-size: 2rem;
width
:
100%
;
height
:
100%
;
}
.button
{
max-width
:
12rem
;
}
.container
:hover
{
transform
:
scale
(
1.02
);
opacity
:
0.95
;
...
...
src/Components/Top-level/Seat/Seat.jsx
View file @
28a2f91f
...
...
@@ -3,12 +3,15 @@ import cn from 'classnames';
import
styles
from
'./Seat.module.css'
;
export
const
Seat
=
({
seatNo
,
status
,
onClick
})
=>
{
const
isMine
=
status
===
'mine'
||
status
===
'mine-unselected'
;
const
className
=
cn
(
styles
.
seat
,
{
[
styles
.
noSeat
]:
status
===
'none'
,
[
styles
.
available
]:
status
===
'available'
,
[
styles
.
reserved
]:
status
===
'reserved'
,
[
styles
.
selected
]:
status
===
'selected'
,
[
styles
.
mine
]:
status
===
'mine'
,
[
styles
.
mine
]:
isMine
,
[
styles
.
unselected
]:
status
===
'mine-unselected'
,
});
return
(
...
...
@@ -16,7 +19,7 @@ export const Seat = ({ seatNo, status, onClick }) => {
className=
{
className
}
data
-
tooltip=
{
seatNo
}
role=
"button"
tabIndex=
{
status
===
'available'
||
status
===
'selected'
?
0
:
-
1
}
tabIndex=
{
[
'available'
,
'selected'
,
'mine'
,
'mine-unselected'
].
includes
(
status
)
?
0
:
-
1
}
onClick=
{
onClick
}
>
{
null
}
...
...
@@ -26,6 +29,13 @@ export const Seat = ({ seatNo, status, onClick }) => {
Seat
.
propTypes
=
{
seatNo
:
PropTypes
.
string
.
isRequired
,
status
:
PropTypes
.
oneOf
([
'none'
,
'available'
,
'reserved'
,
'selected'
]),
status
:
PropTypes
.
oneOf
([
'none'
,
'available'
,
'reserved'
,
'selected'
,
'mine'
,
'mine-unselected'
]),
onClick
:
PropTypes
.
func
,
};
src/Components/Top-level/Seat/Seat.module.css
View file @
28a2f91f
...
...
@@ -27,7 +27,12 @@
background-color
:
#5b66cb
;
}
.mine
{
background-color
:
white
;
background-color
:
#01fff7
;
cursor
:
pointer
;
}
.unselected
{
background-color
:
transparent
;
border
:
0.5px
solid
rgba
(
164
,
175
,
255
,
1
);
}
.reserved
:hover
{
...
...
src/Components/Top-level/SeatLayout/SeatLayout.jsx
View file @
28a2f91f
...
...
@@ -2,18 +2,24 @@ import { useState, useEffect } from 'react';
import
PropTypes
from
'prop-types'
;
import
{
Seat
}
from
'../Seat/Seat.jsx'
;
import
styles
from
'./SeatLayout.module.css'
;
import
{
seatsData
}
from
'.
./SeatLayout
/SeatsData.js'
;
import
{
seatsData
}
from
'./SeatsData.js'
;
const
populateSeatsArray
=
(
selectedSeats
,
allReserv
edSeats
)
=>
{
const
populateSeatsArray
=
(
selectedSeats
,
reservedSeats
,
localSelect
edSeats
)
=>
{
return
seatsData
.
map
(({
row
,
seats
})
=>
seats
.
map
((
number
)
=>
{
const
seatNo
=
row
+
number
;
if
(
number
===
'/'
)
{
return
{
seatNo
,
status
:
'none'
};
}
else
if
(
localSelectedSeats
.
includes
(
seatNo
))
{
if
(
selectedSeats
.
includes
(
seatNo
))
{
return
{
seatNo
,
status
:
'mine'
};
}
else
{
return
{
seatNo
,
status
:
'mine-unselected'
};
}
}
else
if
(
reservedSeats
.
includes
(
seatNo
))
{
return
{
seatNo
,
status
:
'reserved'
};
}
else
if
(
selectedSeats
.
includes
(
seatNo
))
{
return
{
seatNo
,
status
:
'selected'
};
}
else
if
(
allReservedSeats
.
includes
(
seatNo
))
{
return
{
seatNo
,
status
:
'reserved'
};
}
else
{
return
{
seatNo
,
status
:
'available'
};
}
...
...
@@ -22,67 +28,62 @@ const populateSeatsArray = (selectedSeats, allReservedSeats) => {
};
export
const
SeatLayout
=
({
initialReservedSeats
=
[],
allReservedSeats
=
[],
selectedSeats
:
initialSelected
=
[],
reservedSeats
=
[],
currentReservedSeats
=
[],
limit
,
onSelectionChange
,
onSubmit
,
})
=>
{
const
[
selectedSeats
,
setSelectedSeats
]
=
useState
(
initial
ReservedSeats
);
const
[
localSelectedSeats
,
setLocalSelectedSeats
]
=
useState
(
current
ReservedSeats
);
useEffect
(()
=>
{
onSelectionChange
?.(
selectedSeats
);
},
[
selectedSeats
,
onSelectionChange
]);
// useEffect(() => {
// setSelectedSeats(initialReservedSeats);
// }, [initialReservedSeats]);
setLocalSelectedSeats
(
currentReservedSeats
);
},
[
currentReservedSeats
]);
const
seatClickHandler
=
(
seat
)
=>
{
if
(
seat
.
status
===
'available'
||
seat
.
status
===
'selected'
)
{
setSelectedSeats
((
prevSelected
)
=>
{
let
newSelected
;
let
actionType
;
useEffect
(()
=>
{
onSelectionChange
?.(
localSelectedSeats
);
},
[
localSelectedSeats
,
onSelectionChange
]);
if
(
prevSelected
.
includes
(
seat
.
seatNo
))
{
newSelected
=
prevSelected
.
filter
((
s
)
=>
s
!==
seat
.
seatNo
);
actionType
=
'unclicked'
;
}
else
if
(
prevSelected
.
length
<
limit
)
{
newSelected
=
[...
prevSelected
,
seat
.
seatNo
];
actionType
=
'clicked'
;
}
else
{
return
prevSelected
;
}
const
seatClickHandler
=
(
seat
)
=>
{
const
{
seatNo
,
status
}
=
seat
;
if
(
status
===
'reserved'
)
return
;
onSubmit
?.(
actionType
);
return
newSelected
;
});
}
};
const
hasExisting
=
currentReservedSeats
.
length
>
0
;
const
maxLimit
=
hasExisting
?
Infinity
:
Number
(
limit
);
setLocalSelectedSeats
((
prev
)
=>
{
if
(
prev
.
includes
(
seatNo
))
{
return
prev
.
filter
((
s
)
=>
s
!==
seatNo
);
}
else
if
(
prev
.
length
<
maxLimit
)
{
return
[...
prev
,
seatNo
];
}
return
prev
;
});
};
const
renderedSeats
=
populateSeatsArray
(
localSelectedSeats
,
reservedSeats
,
currentReservedSeats
);
const
renderedSeats
=
populateSeatsArray
(
selectedSeats
,
allReservedSeats
);
console
.
log
(
selectedSeats
);
return
(
<
div
className=
{
styles
.
container
}
>
{
renderedSeats
.
map
((
rowSeats
,
i
)
=>
(
<
div
key=
{
i
}
className=
{
styles
.
seatcontainer
}
>
{
rowSeats
.
map
((
seat
,
j
)
=>
(
<
Seat
key=
{
j
}
onClick=
{
()
=>
seatClickHandler
(
seat
)
}
{
...
seat
}
/>
))
}
</
div
>
))
}
{
renderedSeats
.
map
((
row
,
i
)
=>
(
<
div
key=
{
i
}
className=
{
styles
.
seatcontainer
}
>
{
row
.
map
((
seat
,
j
)
=>
(
<
Seat
key=
{
j
}
{
...
seat
}
onClick=
{
()
=>
seatClickHandler
(
seat
)
}
/>
))
}
</
div
>
))
}
</
div
>
);
};
SeatLayout
.
propTypes
=
{
allReservedSeats
:
PropTypes
.
arrayOf
(
PropTypes
.
string
),
initialReservedSeats
:
PropTypes
.
arrayOf
(
PropTypes
.
string
),
limit
:
PropTypes
.
number
,
onSelectionChange
:
PropTypes
.
func
,
selectedSeats
:
PropTypes
.
arrayOf
(
PropTypes
.
string
),
reservedSeats
:
PropTypes
.
arrayOf
(
PropTypes
.
string
),
currentReservedSeats
:
PropTypes
.
arrayOf
(
PropTypes
.
string
),
limit
:
PropTypes
.
number
.
isRequired
,
onSelectionChange
:
PropTypes
.
func
.
isRequired
,
};
src/Components/Top-level/SeatLayout/SeatLayout.module.css
View file @
28a2f91f
...
...
@@ -5,7 +5,7 @@
align-items
:
center
;
margin-bottom
:
5rem
;
width
:
100%
;
overflow-x
:
auto
;
/* overflow-x: auto; */
}
.seatcontainer
{
...
...
src/Components/Top-level/SeatLayout/SeatLayout.stories.jsx
View file @
28a2f91f
import
{
SeatLayout
}
from
"./SeatLayout"
;
import
React
from
'react'
;
import
{
SeatLayout
}
from
'./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
,
parameters
:
{
backgrounds
:
{
parameters
:
{
backgrounds
:
{
default
:
'blue-bg'
,
values
:
[
{
name
:
'blue-bg'
,
value
:
'#3444c5'
},
// blue background
],
values
:
[{
name
:
'blue-bg'
,
value
:
'#3444c5'
}],
},
layout
:
"centered"
layout
:
'centered'
,
},
argTypes
:
{
seatType
:
{
control
:
{
type
:
'radio'
,
},
options
:
[
'available'
,
'reserved'
,
'selected'
]
selectedSeats
:
{
control
:
{
type
:
'array'
},
description
:
'Seats currently selected by the user'
,
},
reservedSeats
:
{
control
:
{
type
:
'array'
},
description
:
'Seats reserved by others'
,
},
limit
:
{
control
:
{
type
:
'number'
,
min
:
1
,
max
:
10
},
currentReservedSeats
:
{
control
:
{
type
:
'array'
},
description
:
'Seats reserved by the logged-in user'
,
},
onSubmit
:
{
action
:
'clicked/unclicked'
},
onSelectionChange
:
{
action
:
'selectedseats'
},
// limit is used internally, not exposed in the controls
},
};
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
=
({
seatType
,
limit
,
onSubmit
,
onSelectionChange
})
=>
{
let
allReservedSeats
=
[];
let
initialReservedSeats
=
[];
export
const
Default
=
(
args
)
=>
{
const
config
=
modeConfig
[
args
.
mode
]
||
modeConfig
.
available
;
if
(
seatType
===
'reserved'
)
{
allReservedSeats
=
[
'B1'
,
'B2'
,
'B3'
];
}
else
if
(
seatType
===
'selected'
)
{
initialReservedSeats
=
[
'C1'
,
'C2'
,
'C3'
];
}
return
(
<
SeatLayout
key=
{
seatType
}
allReservedSeats=
{
allReservedSeats
}
initialReservedSeats=
{
initialReservedSeats
}
limit=
{
limit
}
onSubmit=
{
onSubmit
}
onSelectionChange=
{
onSelectionChange
}
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
}
/>
);
};
Default
.
args
=
{
seats
:
[
[
0
,
"A1"
,
"A2"
,
"A3"
,
0
,
"A4"
,
"A5"
,
"A6"
,
0
],
[
"B1"
,
"B2"
,
"B3"
,
"B4"
,
0
,
"B5"
,
"B6"
,
"B7"
,
"B8"
],
[
"C1"
,
"C2"
,
"C3"
,
"C4"
,
0
,
"C5"
,
"C6"
,
"C7"
,
"C8"
],
[
"D1"
,
"D2"
,
"D3"
,
"D4"
,
0
,
"D5"
,
"D6"
,
"D7"
,
"D8"
],
[
"E1"
,
"E2"
,
"E3"
,
"E4"
,
0
,
"E5"
,
"E6"
,
"E7"
,
"E8"
],
[
"F1"
,
"F2"
,
"F3"
,
"F4"
,
0
,
"F5"
,
"F6"
,
"F7"
,
"F8"
],
[
"G1"
,
"G2"
,
"G3"
,
"G4"
,
0
,
"G5"
,
"G6"
,
"G7"
,
"G8"
],
[
0
,
"H1"
,
"H2"
,
"H3"
,
0
,
"H4"
,
"H5"
,
"H7"
,
0
],
],
seatType
:
'available'
,
limit
:
3
,
mode
:
'available'
,
selectedSeats
:
[],
reservedSeats
:
[],
currentReservedSeats
:
[],
};
src/Components/Top-level/Seatselect/Selectseat.jsx
View file @
28a2f91f
...
...
@@ -9,11 +9,13 @@ import "react-toastify/dist/ReactToastify.css";
import
{
toast
,
ToastContainer
}
from
"react-toastify"
;
import
{
Modal
}
from
"../../Base/Modal/Modal"
;
import
{
SeatSelectForm
}
from
"../../Form/SeatSelectForm/SeatSelectForm"
;
import
{
Screen
}
from
"../../
Base
/Screen/Screen"
;
import
{
Screen
}
from
"../../
Shared
/Screen/Screen"
;
import
{
AiOutlineLogout
}
from
"react-icons/ai"
;
export
const
Selectseat
=
({
onLogout
})
=>
{
export
const
Selectseat
=
({
onLogout
})
=>
{
const
[
selectedSeats
,
setSelectedSeats
]
=
useState
([]);
const
[
allReservedSeats
,
setAllReservedSeats
]
=
useState
([]);
const
[
selectedData
,
setselectedData
]
=
useState
([]);
const
[
reservedSeats
,
setreservedSeats
]
=
useState
([]);
const
[
seatCount
,
setSeatCount
]
=
useState
(
1
);
const
[
availableSeats
,
setAvailableSeats
]
=
useState
(
0
);
const
[
showSelect
,
setShowSelect
]
=
useState
(
true
);
...
...
@@ -22,114 +24,124 @@ export const Selectseat = ({onLogout}) => {
getAllUsers
()
.
then
(
res
=>
{
const
users
=
res
.
data
;
const
reserved
=
users
.
flatMap
(
u
=>
u
.
reservedSeats
||
[])
.
filter
(
seat
=>
seat
);
setAllReservedSeats
(
reserved
);
const
totalSeats
=
60
;
const
totalSeats
=
60
;
const
loggedUser
=
getCurrentUser
();
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
);
const
available
=
totalSeats
-
allReserved
.
length
;
setAvailableSeats
(
available
);
const
available
=
totalSeats
-
reserved
.
length
;
setAvailableSeats
(
available
);
})
if
(
userSeats
.
length
>
0
)
{
setSeatCount
(
userSeats
.
length
);
setShowSelect
(
false
);
// skip modal for existing users
}
})
.
catch
(
err
=>
{
console
.
error
(
"Error fetching users"
,
err
);
toast
.
error
(
"Failed to load seat data."
);
});
},
[]);
const
confirmHandler
=
()
=>
{
const
user
=
getCurrentUser
();
const
confirmHandler
=
()
=>
{
const
user
=
getCurrentUser
();
if
(
!
user
||
!
user
.
phoneNumber
?.
trim
())
{
toast
.
warn
(
"User not logged in"
);
return
;
}
if
(
!
user
||
!
user
.
phoneNumber
?.
trim
())
{
toast
.
warn
(
"User not logged in"
);
return
;
}
if
(
selectedSeats
.
length
<
seatCount
)
{
toast
.
info
(
`Please select
${
seatCount
}
seats
`
);
return
;
}
if
(
selectedData
.
length
===
0
&&
selectedSeats
.
length
<
seatCount
)
{
toast
.
info
(
`Please select
${
seatCount
}
seat(s)
`
);
return
;
}
getAllUsers
()
.
then
(
res
=>
{
const
users
=
res
.
data
;
const
currentUser
=
users
.
find
(
u
=>
u
.
phoneNumber
===
user
.
phoneNumber
);
if
(
!
currentUser
)
{
toast
.
error
(
"User not found"
);
return
;
}
getAllUsers
()
.
then
(
res
=>
{
const
users
=
res
.
data
;
const
currentUser
=
users
.
find
(
u
=>
u
.
phoneNumber
===
user
.
phoneNumber
);
const
latestReserved
=
users
.
flatMap
(
u
=>
u
.
reservedSeats
||
[]);
if
(
!
currentUser
)
{
toast
.
error
(
"User not found"
);
return
;
}
const
conflictSeats
=
selectedSeats
.
filter
(
seat
=>
latestReserved
.
includes
(
seat
)
);
const
latestReserved
=
users
.
flatMap
(
u
=>
u
.
reservedSeats
||
[]
)
.
filter
(
seat
=>
!
currentUser
.
reservedSeats
.
includes
(
seat
)
);
if
(
conflictSeats
.
length
>
0
)
{
toast
.
error
(
`The following seat(s) were just taken:
${
conflictSeats
.
join
(
", "
)}
`
const
conflictSeats
=
selectedSeats
.
filter
(
seat
=>
latestReserved
.
includes
(
seat
)
);
setSelectedSeats
([]);
return
;
}
const
updatedSeats
=
Array
.
from
(
new
Set
([...(
currentUser
.
reservedSeats
||
[]),
...
selectedSeats
])
);
return
updateUserReservedSeats
(
currentUser
.
id
,
updatedSeats
);
})
.
then
(
res
=>
{
if
(
res
)
{
toast
.
success
(
"Seats confirmed!"
);
setSelectedSeats
([]);
if
(
conflictSeats
.
length
>
0
)
{
toast
.
error
(
`The following seat(s) were just taken:
${
conflictSeats
.
join
(
", "
)}
`
);
return
;
}
return
updateUserReservedSeats
(
currentUser
.
id
,
selectedSeats
);
})
.
then
(
res
=>
{
if
(
res
)
{
toast
.
success
(
"Seats updated!"
);
setSelectedSeats
([]);
setTimeout
(()
=>
{
window
.
location
.
replace
(
"/success"
);
},
1000
);
}
})
.
catch
(
err
=>
{
console
.
error
(
"Error confirming seats"
,
err
);
toast
.
error
(
"Failed to confirm seats"
);
setTimeout
(()
=>
{
window
.
location
.
replace
(
"/
success
"
);
window
.
location
.
replace
(
"/
error
"
);
},
1000
);
}
})
.
catch
(
err
=>
{
console
.
error
(
"Error confirming seats"
,
err
);
toast
.
error
(
"Failed to confirm seats"
);
setTimeout
(()
=>
{
window
.
location
.
replace
(
"/error"
);
},
1000
);
});
};
});
};
const
handleModal
=
()
=>
{
setShowSelect
(
false
);
};
const
seatOptions
=
Array
.
from
({
length
:
availableSeats
}).
map
((
_
,
i
)
=>
({
const
seatOptions
=
Array
.
from
({
length
:
availableSeats
}).
map
((
_
,
i
)
=>
({
value
:
i
+
1
,
label
:
`
${
i
+
1
}
${
i
+
1
===
1
?
"seat"
:
"seats"
}
`
}));
return
(
<>
<
div
className=
{
styles
.
logoutButton
}
>
<
AiOutlineLogout
onClick=
{
onLogout
}
/>
</
div
>
<
div
className=
{
styles
.
logoutButton
}
>
<
AiOutlineLogout
onClick=
{
onLogout
}
/>
</
div
>
<
div
className=
{
styles
.
container
}
>
{
/* <div className={styles.logoutButton}><Button variant="secondary" label="Logout" size="sm" onClick={onLogout}/></div> */
}
<
h2
className=
{
styles
.
text
}
>
Choose Seats
</
h2
>
<
Screen
/>
<
h2
className=
{
styles
.
text
}
>
Choose Seats
</
h2
>
<
Screen
/>
<
div
className=
{
styles
.
seatcontainer
}
>
<
SeatLayout
allReservedSeats=
{
allReservedSeats
}
onSelectionChange=
{
setSelectedSeats
}
selectedSeats=
{
selectedSeats
}
reservedSeats=
{
reservedSeats
}
currentReservedSeats=
{
selectedData
}
limit=
{
seatCount
}
onSelectionChange=
{
setSelectedSeats
}
/>
</
div
>
<
div
className=
{
styles
.
button
}
>
<
Button
variant=
"secondary"
label=
"Confirm"
size=
'md'
onClick=
{
confirmHandler
}
disabled=
{
selectedSeats
.
length
!=
seatCount
}
/>
<
Button
variant=
"secondary"
label=
"Confirm"
size=
"md"
onClick=
{
confirmHandler
}
disabled=
{
selectedData
.
length
===
0
&&
selectedSeats
.
length
!==
seatCount
}
/>
</
div
>
<
div
className=
{
styles
.
legend
}
>
...
...
@@ -155,6 +167,6 @@ export const Selectseat = ({onLogout}) => {
)
}
<
ToastContainer
position=
"top-right"
autoClose=
{
3000
}
/>
</>
</>
);
};
src/Components/Top-level/Seatselect/Selectseat.module.css
View file @
28a2f91f
...
...
@@ -29,7 +29,8 @@ body {
position
:
relative
;
bottom
:
4rem
;
padding
:
0
1rem
;
max-width
:
12rem
;
margin
:
0
auto
;
}
/*
.shadow {
...
...
@@ -40,6 +41,10 @@ body {
padding: 0 1rem;
} */
/* labels:{
signUp:'sdfs'
/} */
.seatcontainer
{
position
:
relative
;
bottom
:
4rem
;
...
...
src/Pages/Auth/Auth.stories.jsx
deleted
100644 → 0
View file @
840f2f6f
// Auth.stories.jsx
import
{
Auth
}
from
"../Auth/Auth.jsx"
;
import
{
Login
}
from
'../../Components/Form/Login/Login.jsx'
import
{
SignUp
}
from
"../../Components/Form/SignUp/SignUp.jsx"
;
export
default
{
title
:
"Auth/Auth"
,
component
:
Auth
,
};
const
DummyLogin
=
({
onLogin
})
=>
(
<
div
>
<
Login
/>
</
div
>
);
const
DummySignUp
=
({
onSubmit
})
=>
(
<
div
>
<
SignUp
/>
</
div
>
);
export
const
Default
=
()
=>
(
<
Auth
LoginComponent=
{
DummyLogin
}
SignUpComponent=
{
DummySignUp
}
onLoginSubmit=
{
(
data
)
=>
console
.
log
(
"Login Submit:"
,
data
)
}
onSignUpSubmit=
{
()
=>
console
.
log
(
"SignUp Submit"
)
}
/>
);
src/Pages/AuthPage.jsx
View file @
28a2f91f
import
{
Login
}
from
"../Components/Form/Login/Login.jsx"
import
{
SignUp
}
from
"../Components/Form/SignUp/SignUp"
;
import
{
toast
}
from
"react-toastify"
;
import
{
Auth
}
from
"./Auth/Auth.jsx"
;
import
{
Auth
}
from
"../Components/Auth/Auth.jsx"
;
import
{
getUserByPhone
,
postUser
}
from
"../Api/userApi.js"
;
export
const
AuthPage
=
()
=>
{
const
handleLogin
=
async
(
phoneNumber
)
=>
{
try
{
...
...
@@ -21,11 +22,40 @@ 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
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."
);
}
};
return
(
<
Auth
LoginComponent=
{
Login
}
SignUpComponent=
{
SignUp
}
onLoginSubmit=
{
handleLogin
}
onSignUpSubmit=
{
handleSignUp
}
/>
);
};
src/db.json
View file @
28a2f91f
...
...
@@ -18,6 +18,9 @@
"phoneNumber"
:
"9361775481"
,
"name"
:
"mani"
,
"reservedSeats"
:
[
"B3"
,
"B2"
,
"B1"
,
"C4"
]
},
...
...
@@ -26,6 +29,106 @@
"name"
:
"mani"
,
"email"
:
"mani@gmail.com"
,
"phoneNumber"
:
"9934782103"
,
"reservedSeats"
:
[
"B4"
,
"D4"
,
"E3"
,
"A3"
,
"A2"
,
"A1"
,
"H1"
,
"H2"
]
},
{
"id"
:
"2c1f"
,
"name"
:
"mani"
,
"email"
:
"mani@gmail.com"
,
"phoneNumber"
:
"9312345678"
,
"reservedSeats"
:
[]
},
{
"id"
:
"845c"
,
"name"
:
"abinesh"
,
"email"
:
"abi@gmail.com"
,
"phoneNumber"
:
"1234587690"
,
"reservedSeats"
:
[]
},
{
"id"
:
"8ebe"
,
"name"
:
"asdfsa"
,
"email"
:
"adsasdf@gmail.om"
,
"phoneNumber"
:
"12341234234"
,
"reservedSeats"
:
[]
},
{
"id"
:
"b80a"
,
"name"
:
"ganesh"
,
"email"
:
"ganesh@gmail.com"
,
"phoneNumber"
:
"9786543219"
,
"reservedSeats"
:
[]
},
{
"id"
:
"9659"
,
"name"
:
"dinesh"
,
"email"
:
"din@gmail.com"
,
"phoneNumber"
:
"7895432173"
,
"reservedSeats"
:
[]
},
{
"id"
:
"d7df"
,
"name"
:
"janani"
,
"email"
:
"janani@gmail.com"
,
"phoneNumber"
:
"9587923467"
,
"reservedSeats"
:
[]
},
{
"id"
:
"29ef"
,
"name"
:
"viji"
,
"email"
:
"viji@gmail.com"
,
"phoneNumber"
:
"7689543218"
,
"reservedSeats"
:
[]
},
{
"id"
:
"a364"
,
"name"
:
"vijay"
,
"email"
:
"vijay@gmail.com"
,
"phoneNumber"
:
"6789543212"
,
"reservedSeats"
:
[]
},
{
"id"
:
"f999"
,
"name"
:
"kain"
,
"email"
:
"kain@gmail.com"
,
"phoneNumber"
:
"7584932105"
,
"reservedSeats"
:
[]
},
{
"id"
:
"5dcd"
,
"name"
:
"ganesh"
,
"email"
:
"ganesh@gmail.com"
,
"phoneNumber"
:
"8790654321"
,
"reservedSeats"
:
[]
},
{
"id"
:
"5306"
,
"name"
:
"janani"
,
"email"
:
"janani@gmail.com"
,
"phoneNumber"
:
"9867543210"
,
"reservedSeats"
:
[]
},
{
"id"
:
"aaff"
,
"name"
:
"maniv"
,
"email"
:
"maniv@gmail.com"
,
"phoneNumber"
:
"9687543721"
,
"reservedSeats"
:
[]
},
{
"id"
:
"8ec3"
,
"name"
:
"dfgfdg"
,
"email"
:
"dsbfsdbf@gmail.com"
,
"phoneNumber"
:
"4782391204"
,
"reservedSeats"
:
[]
}
]
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment