Skip to content

Commit

Permalink
[AR-8266] wallet update login flow screens (#229)
Browse files Browse the repository at this point in the history
* login flow ui update

* impl new loader

* close sheet after click

* disable if the value is not email

* set color, height

* fix icon and color

* change color

* change sub text color

* fix css

* set Nohemi font

* if less than 4 logins show in full width

* place icons as per design

* ui fixes

* set padding

* padding and vertical spacing fix
  • Loading branch information
karthik-durai committed Aug 8, 2024
1 parent d5156dd commit 7fe8d75
Show file tree
Hide file tree
Showing 6 changed files with 550 additions and 218 deletions.
255 changes: 165 additions & 90 deletions src/ui/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import {
useRef,
Dispatch,
} from 'preact/hooks'
import { ARCANA_LOGO, getSocialLogo } from './icons'
import { ICONS } from '../utils'
import { ARCANA_LOGO, getSocialLogo, MISC_ICONS } from './icons'
import { ModalParams } from './typings'
import { Theme } from '../typings'
import { JSXInternal } from 'preact/src/jsx'
Expand All @@ -17,7 +16,7 @@ import './style.css'
const Header = ({ compact, logo }: { compact: boolean; logo: string }) => {
const [loaded, setLoaded] = useState(false)
const showLogoContainer = () => {
setLoaded(false)
setLoaded(true)
}
return (
<>
Expand All @@ -35,10 +34,7 @@ const Header = ({ compact, logo }: { compact: boolean; logo: string }) => {
</div>
{!compact ? (
<div className="xar-header-text">
<h1 className="xar-header-heading">Welcome</h1>
<p className="xar-header-subtext">
We’ll email you a login link for a password-free sign in.
</p>
<h1 className="xar-header-heading">Log In</h1>
</div>
) : (
''
Expand All @@ -51,9 +47,11 @@ const EmailLogin = ({
loginWithOTPStart,
email,
setEmail,
mode,
}: {
email: string
setEmail: Dispatch<StateUpdater<string>>
mode: Theme
} & Pick<ModalParams, 'loginWithOTPStart'>) => {
const [disabled, setDisabled] = useState(true)
const onInput: JSXInternal.GenericEventHandler<HTMLInputElement> = (e) => {
Expand All @@ -78,50 +76,112 @@ const EmailLogin = ({
useEffect(() => {
setDisabled(!isEmail(email))
}, [])

return (
<form className="xar-email-login">
<input
value={email}
onInput={onInput}
className="xar-email-login__input"
type="text"
placeholder={'Enter your email'}
/>
<button disabled={disabled} onClick={clickHandler} className="xar-btn">
Get Login OTP
</button>
<div class="xar-email-login__input-container">
<input
value={email}
onInput={onInput}
className="xar-email-login__input"
type="text"
placeholder={'Enter your email'}
/>
<button
disabled={disabled}
onClick={clickHandler}
class="xar-btn xar-btn__input-arrow"
>
<img src={MISC_ICONS[mode].arrow} alt="proceed" />
</button>
</div>
</form>
)
}

const Separator = ({ text }: { text: string }) => {
return <div className="xar-separator">{text}</div>
return (
<div className="xar-separator">
<span class="xar-separator__text">{text}</span>
</div>
)
}

const SocialLogin = ({
loginWithSocial,
loginList,
mode,
}: Pick<ModalParams, 'loginWithSocial' | 'loginList'> & { mode: Theme }) => {
setShowMore,
}: { setShowMore: Dispatch<StateUpdater<boolean>> } & Pick<
ModalParams,
'loginWithSocial' | 'loginList'
> & { mode: Theme }) => {
const clickHandler = (p: string) => {
return loginWithSocial(p)
}
return (
<div className="xar-social-container">
{loginList.map((l) => {
return (
<div
className="xar-social-icon__wrapper"
onClick={() => clickHandler(l)}
>
<img
src={getSocialLogo(l, mode)}
alt={`${l} logo`}
className="xar-social-icon"
/>
</div>
)
})}
{loginList.length <= 4
? loginList.map((l: string) => {
return (
<div
className="xar-social-icon__wrapper-full-width"
onClick={() => clickHandler(l)}
>
<img
src={getSocialLogo(l, mode)}
alt={`${l} logo`}
className="xar-social-icon"
/>
<p>Continue with {l.charAt(0).toUpperCase() + l.slice(1)}</p>
</div>
)
})
: loginList.slice(0, 5).map((l, i) => {
return i === 0 ? (
<div
className="xar-social-icon__wrapper-full-width"
onClick={() => clickHandler(l)}
>
<img
src={getSocialLogo(l, mode)}
alt={`${l} logo`}
className="xar-social-icon"
/>
<p>Continue with {l.charAt(0).toUpperCase() + l.slice(1)}</p>
</div>
) : (
<div
className="xar-social-icon__wrapper"
onClick={() => clickHandler(l)}
style={
loginList.length === 5
? { width: '60px', height: '44px', borderRadius: '40%' }
: {}
}
>
<img
src={getSocialLogo(l, mode)}
alt={`${l} logo`}
className="xar-social-icon"
/>
</div>
)
})}
{loginList.length > 5 ? (
<div
className="xar-social-icon__wrapper"
onClick={() => setShowMore(true)}
>
<img
src={MISC_ICONS[mode]['dots-horizontal']}
alt="more"
className="xar-social-icon"
/>
</div>
) : (
''
)}
</div>
)
}
Expand All @@ -135,7 +195,7 @@ const Footer = ({ mode }: { mode: Theme }) => {
target="_blank"
className="xar-footer-img__link"
>
<img className="xar-footer-img" src={logo} alt="Secured By Arcana" />
<img className="xar-footer-img" src={logo} alt="Powered By Arcana" />
</a>
</div>
)
Expand All @@ -150,15 +210,7 @@ const Loader = (props: {
}) => {
return (
<>
{props.header ? (
props.header
) : (
<ProgressOval
compact={props.compact}
stroke={8}
secondaryColor="#8D8D8D"
/>
)}
{props.header ? props.header : <ProgressOval />}
{props.text ? <p className="xar-loader__text">{props.text}</p> : ''}
{props.children ? <>{props.children}</> : ''}
</>
Expand All @@ -171,6 +223,9 @@ const OTPEntry = ({
setError,
closeFunc,
compact,
email,
mode,
toHome,
}: {
loginWithOtpStart: () => Promise<unknown>
loginWithOtpComplete: (
Expand All @@ -180,6 +235,9 @@ const OTPEntry = ({
setError(): void
closeFunc(): void
compact: boolean
email: string
mode: Theme
toHome(): void
}) => {
const { counter, resetCounter } = useCounter(30)
const [attempts, setAttempts] = useState(3)
Expand Down Expand Up @@ -368,62 +426,78 @@ const OTPEntry = ({
if (loader.loading) {
return (
<>
<ProgressOval stroke={8} secondaryColor="#8D8D8D" compact={compact} />
<ProgressOval />
<div class="xar-loader__text">{loader.text}</div>
</>
)
}

return (
<>
<div class="xar-otp-heading">Verification</div>
<div class="xar-otp-sub-heading">
Please enter the OTP that was sent to your <br />
email address
<div class="xar-otp-heading-container">
<button
class="xar-btn"
style={{ position: 'absolute' }}
onClick={toHome}
>
<img src={MISC_ICONS[mode]['back-arrow']} alt="back" />
</button>
<p class="xar-otp-heading">Enter OTP</p>
</div>
<div className="xar-otp-box">
{Array(numInputs)
.fill(null)
.map((_, i) => {
return (
<input
value={getOTPValue()[i] ?? ''}
key={i}
type="text"
maxLength={1}
autoComplete="off"
ref={(el) => (inputRefs.current[i] = el)}
onFocus={(event) => handleFocus(event)(i)}
onInput={handleInputChange}
onKeyDown={handleKeyDown}
onPaste={handlePaste}
className={
isInvalidOTP
? 'xar-otp-input xar-invalid-otp'
: 'xar-otp-input'
}
/>
)
})}
<div>
<img src={MISC_ICONS[mode].email} alt="email" />
</div>
<div class="xar-otp-sub-heading">
We’ve sent a verification code to
<br />
<span class="xar-otp-email">{email}</span>
</div>
{isInvalidOTP ? (
<div>
<p class="xar-invalid-otp-text">
Incorrect OTP. {attempts} attempts left.
</p>
<div className="xar-otp-box-container">
<div class="xar-otp-box">
{Array(numInputs)
.fill(null)
.map((_, i) => {
return (
<input
value={getOTPValue()[i] ?? ''}
key={i}
type="text"
maxLength={1}
autoComplete="off"
ref={(el) => (inputRefs.current[i] = el)}
onFocus={(event) => handleFocus(event)(i)}
onInput={handleInputChange}
onKeyDown={handleKeyDown}
onPaste={handlePaste}
className={
isInvalidOTP
? 'xar-otp-input xar-invalid-otp'
: 'xar-otp-input'
}
/>
)
})}
</div>
) : (
''
)}
{isInvalidOTP ? (
<div>
<p class="xar-invalid-otp-text">
Incorrect OTP. {attempts} attempts left.
</p>
</div>
) : (
''
)}
</div>

<div>
<Action
disabled={counter > 0}
text={
counter > 0 ? `Resend code in ${counter} seconds` : 'Resend code'
}
method={resendCode}
/>
{counter > 0 ? (
<span class="xar-sub-text">Resend code in {counter} seconds</span>
) : (
<div>
<span class="xar-sub-text">Did not receive your code yet?</span>
<Action text={'Re-send code'} method={resendCode} />
</div>
)}
</div>
</>
)
Expand All @@ -450,16 +524,17 @@ const useCounter = (time = 60) => {
return { counter, resetCounter }
}

const OTPError = ({ action }: { action: () => void }) => {
const OTPError = ({ action, mode }: { action: () => void; mode: Theme }) => {
return (
<>
<img class="xar-header-logo" src={ICONS.fail} alt="failed" />
<img class="xar-header-logo" src={MISC_ICONS[mode].failed} alt="failed" />
<h2 class="xar-otp-error-heading">Login Failed</h2>
<p class="xar-otp-error-subheading">
Please check credentials and try again
</p>
<button className="xar-btn" onClick={action}>
Go Home
<img src={MISC_ICONS[mode]['try-again']} alt="Try again" />
<span class="xar-action__link">Try Again</span>
</button>
</>
)
Expand Down
Loading

0 comments on commit 7fe8d75

Please sign in to comment.