Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
maf-gateway-revamp
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
1
Merge Requests
1
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
Arunachalam P
maf-gateway-revamp
Commits
6b577681
Commit
6b577681
authored
Mar 27, 2026
by
krds-arun
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(login): designed new 2fa and forgot password flow
parent
068388c8
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
428 additions
and
1 deletion
+428
-1
LoginModal.stories.tsx
components/widgets/LoginModal/LoginModal.stories.tsx
+25
-0
LoginModal.tsx
components/widgets/LoginModal/LoginModal.tsx
+232
-1
login-modal.css
components/widgets/LoginModal/login-modal.css
+171
-0
No files found.
components/widgets/LoginModal/LoginModal.stories.tsx
View file @
6b577681
...
...
@@ -18,7 +18,32 @@ export const Open: Story = {
open
:
true
,
onClose
:
()
=>
{},
onMicrosoftSignIn
:
()
=>
{},
onContractorSignIn
:
()
=>
{},
onVerifyTwoFactor
:
()
=>
{},
onSendResetLink
:
()
=>
{},
onContactSupport
:
()
=>
{},
},
};
export
const
ContractorLogin
:
Story
=
{
args
:
{
...
Open
.
args
,
initialAccountType
:
"contractor"
,
initialView
:
"login"
,
},
};
export
const
TwoFactorVerification
:
Story
=
{
args
:
{
...
Open
.
args
,
initialView
:
"twoFactor"
,
},
};
export
const
ForgotPassword
:
Story
=
{
args
:
{
...
Open
.
args
,
initialView
:
"forgotPassword"
,
},
};
components/widgets/LoginModal/LoginModal.tsx
View file @
6b577681
...
...
@@ -5,12 +5,24 @@ import { ModalShell } from "@/components/widgets/ModalShell/ModalShell";
import
"./login-modal.css"
;
export
type
LoginAccountType
=
"employee"
|
"contractor"
;
export
type
LoginModalView
=
"login"
|
"twoFactor"
|
"forgotPassword"
;
type
ContractorCredentials
=
{
username
:
string
;
password
:
string
;
rememberMe
:
boolean
;
};
export
type
LoginModalProps
=
{
open
:
boolean
;
onClose
:
()
=>
void
;
initialView
?:
LoginModalView
;
initialAccountType
?:
LoginAccountType
;
onContactSupport
?:
()
=>
void
;
onMicrosoftSignIn
?:
(
accountType
:
LoginAccountType
)
=>
void
;
onContractorSignIn
?:
(
credentials
:
ContractorCredentials
)
=>
void
;
onVerifyTwoFactor
?:
(
code
:
string
)
=>
void
;
onSendResetLink
?:
(
email
:
string
)
=>
void
;
className
?:
string
;
};
...
...
@@ -48,14 +60,75 @@ function MicrosoftGlyph({ className }: { className?: string }) {
);
}
function
LockGlyph
({
className
}:
{
className
?:
string
})
{
return
(
<
svg
className=
{
className
}
viewBox=
"0 0 24 24"
aria
-
hidden
>
<
path
d=
"M7.5 10V8.25a4.5 4.5 0 1 1 9 0V10"
fill=
"none"
stroke=
"currentColor"
strokeWidth=
"1.6"
strokeLinecap=
"round"
/>
<
rect
x=
"5.5"
y=
"10"
width=
"13"
height=
"9.5"
rx=
"2.4"
fill=
"none"
stroke=
"currentColor"
strokeWidth=
"1.6"
/>
</
svg
>
);
}
function
MailGlyph
({
className
}:
{
className
?:
string
})
{
return
(
<
svg
className=
{
className
}
viewBox=
"0 0 24 24"
aria
-
hidden
>
<
rect
x=
"3"
y=
"5.5"
width=
"18"
height=
"13"
rx=
"2.2"
fill=
"none"
stroke=
"currentColor"
strokeWidth=
"1.6"
/>
<
path
d=
"m4.8 7.2 7.2 6 7.2-6"
fill=
"none"
stroke=
"currentColor"
strokeWidth=
"1.6"
strokeLinecap=
"round"
strokeLinejoin=
"round"
/>
</
svg
>
);
}
export
function
LoginModal
({
open
,
onClose
,
initialView
=
"login"
,
initialAccountType
=
"employee"
,
onContactSupport
,
onMicrosoftSignIn
,
onContractorSignIn
,
onVerifyTwoFactor
,
onSendResetLink
,
className
=
""
,
}:
LoginModalProps
)
{
const
[
accountType
,
setAccountType
]
=
useState
<
LoginAccountType
>
(
"employee"
);
const
[
accountType
,
setAccountType
]
=
useState
<
LoginAccountType
>
(
initialAccountType
);
const
[
view
,
setView
]
=
useState
<
LoginModalView
>
(
initialView
);
const
[
username
,
setUsername
]
=
useState
(
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
[
rememberMe
,
setRememberMe
]
=
useState
(
false
);
const
[
email
,
setEmail
]
=
useState
(
""
);
const
[
usernameError
,
setUsernameError
]
=
useState
(
""
);
const
[
otpDigits
,
setOtpDigits
]
=
useState
<
string
[]
>
([
""
,
""
,
""
,
""
,
""
,
""
]);
const
helperCopy
=
useMemo
(()
=>
{
if
(
accountType
===
"employee"
)
{
...
...
@@ -70,6 +143,35 @@ export function LoginModal({
};
},
[
accountType
]);
const
otpCode
=
otpDigits
.
join
(
""
);
const
isEmployee
=
accountType
===
"employee"
;
const
handleContractorSubmit
=
(
event
:
React
.
FormEvent
<
HTMLFormElement
>
)
=>
{
event
.
preventDefault
();
if
(
!
username
.
trim
())
{
setUsernameError
(
"Please enter your username."
);
return
;
}
setUsernameError
(
""
);
onContractorSignIn
?.({
username
:
username
.
trim
(),
password
,
rememberMe
});
};
const
handleOtpInput
=
(
index
:
number
,
value
:
string
)
=>
{
const
nextValue
=
value
.
replace
(
/
\D
/g
,
""
).
slice
(
0
,
1
);
const
updated
=
[...
otpDigits
];
updated
[
index
]
=
nextValue
;
setOtpDigits
(
updated
);
if
(
nextValue
&&
index
<
5
)
{
const
nextElement
=
document
.
getElementById
(
`maf-otp-
${
index
+
1
}
`
)
as
HTMLInputElement
|
null
;
nextElement
?.
focus
();
}
};
const
handleBackToLogin
=
()
=>
setView
(
"login"
);
return
(
<
ModalShell
open=
{
open
}
...
...
@@ -79,6 +181,14 @@ export function LoginModal({
title=
"Welcome back"
className=
{
`maf-login-modal ${className}`
.
trim
()
}
>
{
view
!==
"login"
?
(
<
button
type=
"button"
className=
"maf-login-modal__backLink"
onClick=
{
handleBackToLogin
}
>
← Back to sign in
</
button
>
)
:
null
}
{
view
===
"login"
?
(
<>
<
p
className=
"maf-login-modal__subtitle"
>
Choose your account type to continue
</
p
>
<
div
className=
"maf-login-modal__tabs"
role=
"tablist"
aria
-
label=
"Account type"
>
...
...
@@ -102,6 +212,8 @@ export function LoginModal({
</
button
>
</
div
>
{
isEmployee
?
(
<>
<
section
className=
"maf-login-modal__info"
aria
-
label=
"Sign in information"
>
<
span
className=
"maf-login-modal__infoIcon"
aria
-
hidden
>
<
InfoGlyph
className=
"maf-login-modal__infoSvg"
/>
...
...
@@ -126,6 +238,125 @@ export function LoginModal({
</
span
>
<
span
>
Secure redirect via Microsoft — your credentials never touch our servers
</
span
>
</
div
>
</>
)
:
(
<
form
className=
"maf-login-modal__form"
onSubmit=
{
handleContractorSubmit
}
>
<
label
className=
"maf-login-modal__label"
htmlFor=
"maf-login-username"
>
Company username
</
label
>
<
input
id=
"maf-login-username"
className=
{
`maf-login-modal__input ${usernameError ? "is-error" : ""}`
.
trim
()
}
type=
"text"
value=
{
username
}
onChange=
{
(
event
)
=>
setUsername
(
event
.
target
.
value
)
}
autoComplete=
"username"
placeholder=
"Enter your username"
/>
{
usernameError
?
<
p
className=
"maf-login-modal__error"
>
{
usernameError
}
</
p
>
:
null
}
<
label
className=
"maf-login-modal__label"
htmlFor=
"maf-login-password"
>
Password
</
label
>
<
input
id=
"maf-login-password"
className=
"maf-login-modal__input"
type=
"password"
value=
{
password
}
onChange=
{
(
event
)
=>
setPassword
(
event
.
target
.
value
)
}
autoComplete=
"current-password"
placeholder=
"Your password"
/>
<
div
className=
"maf-login-modal__row"
>
<
label
className=
"maf-login-modal__remember"
>
<
input
type=
"checkbox"
checked=
{
rememberMe
}
onChange=
{
(
event
)
=>
setRememberMe
(
event
.
target
.
checked
)
}
/>
<
span
>
Remember me
</
span
>
</
label
>
<
button
type=
"button"
className=
"maf-login-modal__linkButton"
onClick=
{
()
=>
setView
(
"forgotPassword"
)
}
>
Forgot password?
</
button
>
</
div
>
<
button
type=
"submit"
className=
"maf-login-modal__primaryButton"
>
Log in
</
button
>
</
form
>
)
}
</>
)
:
null
}
{
view
===
"twoFactor"
?
(
<
section
>
<
span
className=
"maf-login-modal__pill"
>
<
LockGlyph
className=
"maf-login-modal__pillIcon"
/>
Two-factor authentication
</
span
>
<
h3
className=
"maf-login-modal__viewTitle font-serif"
>
Verify it
'
s you
</
h3
>
<
p
className=
"maf-login-modal__subtitle"
>
Enter the 6-digit code from your authenticator app. If you don
'
t have the app, we can
send a code to your email.
</
p
>
<
div
className=
"maf-login-modal__otp"
role=
"group"
aria
-
label=
"One-time password"
>
{
otpDigits
.
map
((
digit
,
index
)
=>
(
<
input
key=
{
index
}
id=
{
`maf-otp-${index}`
}
className=
"maf-login-modal__otpInput"
type=
"text"
inputMode=
"numeric"
autoComplete=
"one-time-code"
value=
{
digit
}
onChange=
{
(
event
)
=>
handleOtpInput
(
index
,
event
.
target
.
value
)
}
aria
-
label=
{
`Digit ${index + 1}`
}
/>
))
}
</
div
>
<
button
type=
"button"
className=
"maf-login-modal__primaryButton"
onClick=
{
()
=>
onVerifyTwoFactor
?.(
otpCode
)
}
>
Verify
&
continue
</
button
>
<
button
type=
"button"
className=
"maf-login-modal__linkCta"
>
Send code to my email instead
</
button
>
</
section
>
)
:
null
}
{
view
===
"forgotPassword"
?
(
<
section
>
<
div
className=
"maf-login-modal__iconBadge"
aria
-
hidden
>
<
MailGlyph
className=
"maf-login-modal__iconBadgeSvg"
/>
</
div
>
<
h3
className=
"maf-login-modal__viewTitle font-serif"
>
Reset your password
</
h3
>
<
p
className=
"maf-login-modal__subtitle"
>
Enter the email address linked to your account. We
'
ll send you a secure link to create a
new password.
</
p
>
<
label
className=
"maf-login-modal__label"
htmlFor=
"maf-reset-email"
>
Email address
</
label
>
<
input
id=
"maf-reset-email"
className=
"maf-login-modal__input"
type=
"email"
autoComplete=
"email"
value=
{
email
}
onChange=
{
(
event
)
=>
setEmail
(
event
.
target
.
value
)
}
placeholder=
"name@example.com"
/>
<
button
type=
"button"
className=
"maf-login-modal__primaryButton"
onClick=
{
()
=>
onSendResetLink
?.(
email
.
trim
())
}
>
Send reset link
</
button
>
</
section
>
)
:
null
}
<
div
className=
"maf-login-modal__footer"
>
<
span
className=
"maf-login-modal__footerMuted"
>
Need help accessing your account?
</
span
>
...
...
components/widgets/LoginModal/login-modal.css
View file @
6b577681
...
...
@@ -2,6 +2,16 @@
margin-top
:
0.35rem
;
}
.maf-login-modal__backLink
{
margin
:
0
0
0.7rem
;
border
:
0
;
background
:
transparent
;
color
:
color-mix
(
in
srgb
,
var
(
--foreground
,
#f4f0e8
)
56%
,
transparent
);
font-size
:
0.98rem
;
cursor
:
pointer
;
padding
:
0
;
}
.maf-login-modal__subtitle
{
margin
:
0.55rem
0
1.1rem
;
color
:
color-mix
(
in
srgb
,
var
(
--foreground
,
#f4f0e8
)
62%
,
transparent
);
...
...
@@ -169,3 +179,164 @@
outline-offset
:
3px
;
}
.maf-login-modal__form
{
margin-top
:
1.1rem
;
}
.maf-login-modal__label
{
display
:
block
;
margin
:
1rem
0
0.45rem
;
text-transform
:
uppercase
;
letter-spacing
:
0.04em
;
font-size
:
0.78rem
;
font-weight
:
700
;
color
:
color-mix
(
in
srgb
,
var
(
--foreground
,
#f4f0e8
)
70%
,
transparent
);
}
.maf-login-modal__input
{
width
:
100%
;
border-radius
:
12px
;
border
:
1px
solid
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
22%
,
transparent
);
background
:
rgba
(
0
,
0
,
0
,
0.28
);
color
:
rgba
(
255
,
255
,
255
,
0.92
);
padding
:
0.85rem
0.9rem
;
font-size
:
0.96rem
;
}
.maf-login-modal__input
::placeholder
{
color
:
color-mix
(
in
srgb
,
var
(
--foreground
,
#f4f0e8
)
48%
,
transparent
);
}
.maf-login-modal__input
:focus-visible
{
outline
:
2px
solid
var
(
--accent
,
#c4a574
);
outline-offset
:
2px
;
}
.maf-login-modal__input.is-error
{
border-color
:
rgba
(
255
,
88
,
110
,
0.85
);
}
.maf-login-modal__error
{
margin
:
0.4rem
0
0
;
color
:
rgba
(
255
,
105
,
126
,
0.95
);
font-size
:
0.82rem
;
}
.maf-login-modal__row
{
margin-top
:
0.9rem
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
}
.maf-login-modal__remember
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
0.45rem
;
color
:
color-mix
(
in
srgb
,
var
(
--foreground
,
#f4f0e8
)
65%
,
transparent
);
font-size
:
0.95rem
;
}
.maf-login-modal__linkButton
,
.maf-login-modal__linkCta
{
border
:
0
;
background
:
transparent
;
color
:
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
92%
,
transparent
);
text-decoration
:
underline
;
text-underline-offset
:
0.2em
;
cursor
:
pointer
;
}
.maf-login-modal__primaryButton
{
margin-top
:
1.1rem
;
width
:
100%
;
border
:
0
;
border-radius
:
14px
;
padding
:
0.9rem
1rem
;
font-size
:
1.02rem
;
font-weight
:
800
;
letter-spacing
:
0.01em
;
color
:
#fff
;
background
:
linear-gradient
(
145deg
,
#9f103f
0%
,
#da2f6c
100%
);
box-shadow
:
0
16px
42px
rgba
(
194
,
24
,
84
,
0.34
),
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.2
);
cursor
:
pointer
;
}
.maf-login-modal__primaryButton
:hover
{
filter
:
brightness
(
1.06
);
}
.maf-login-modal__pill
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
0.5rem
;
margin-top
:
0.2rem
;
padding
:
0.42rem
0.9rem
;
border-radius
:
999px
;
border
:
1px
solid
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
25%
,
transparent
);
background
:
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
10%
,
transparent
);
color
:
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
90%
,
transparent
);
font-size
:
0.94rem
;
}
.maf-login-modal__pillIcon
{
width
:
16px
;
height
:
16px
;
}
.maf-login-modal__viewTitle
{
margin
:
1rem
0
0.35rem
;
font-size
:
2rem
;
line-height
:
1.08
;
color
:
color-mix
(
in
srgb
,
var
(
--foreground
,
#f4f0e8
)
92%
,
transparent
);
font-weight
:
520
;
}
.maf-login-modal__otp
{
margin-top
:
1.1rem
;
display
:
grid
;
grid-template-columns
:
repeat
(
6
,
minmax
(
0
,
1
fr
));
gap
:
0.5rem
;
}
.maf-login-modal__otpInput
{
width
:
100%
;
border-radius
:
10px
;
border
:
1px
solid
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
25%
,
transparent
);
background
:
rgba
(
0
,
0
,
0
,
0.25
);
color
:
rgba
(
255
,
255
,
255
,
0.92
);
text-align
:
center
;
font-size
:
1.1rem
;
height
:
3.15rem
;
}
.maf-login-modal__otpInput
:focus-visible
{
outline
:
2px
solid
var
(
--accent
,
#c4a574
);
outline-offset
:
2px
;
}
.maf-login-modal__linkCta
{
width
:
100%
;
text-align
:
center
;
margin-top
:
0.95rem
;
}
.maf-login-modal__iconBadge
{
width
:
68px
;
height
:
68px
;
border-radius
:
999px
;
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
border
:
1px
solid
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
28%
,
transparent
);
background
:
radial-gradient
(
circle
at
50%
35%
,
rgba
(
212
,
173
,
99
,
0.25
),
rgba
(
0
,
0
,
0
,
0.18
));
}
.maf-login-modal__iconBadgeSvg
{
width
:
26px
;
height
:
26px
;
color
:
color-mix
(
in
srgb
,
var
(
--accent
,
#c4a574
)
90%
,
transparent
);
}
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