diff --git a/src/ui/components.tsx b/src/ui/components.tsx index 96ee8ee..0cc4c82 100644 --- a/src/ui/components.tsx +++ b/src/ui/components.tsx @@ -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' @@ -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 ( <> @@ -35,10 +34,7 @@ const Header = ({ compact, logo }: { compact: boolean; logo: string }) => { {!compact ? (
-

Welcome

-

- We’ll email you a login link for a password-free sign in. -

+

Log In

) : ( '' @@ -51,9 +47,11 @@ const EmailLogin = ({ loginWithOTPStart, email, setEmail, + mode, }: { email: string setEmail: Dispatch> + mode: Theme } & Pick) => { const [disabled, setDisabled] = useState(true) const onInput: JSXInternal.GenericEventHandler = (e) => { @@ -78,50 +76,112 @@ const EmailLogin = ({ useEffect(() => { setDisabled(!isEmail(email)) }, []) + return (
- - +
) } const Separator = ({ text }: { text: string }) => { - return
{text}
+ return ( +
+ {text} +
+ ) } const SocialLogin = ({ loginWithSocial, loginList, mode, -}: Pick & { mode: Theme }) => { + setShowMore, +}: { setShowMore: Dispatch> } & Pick< + ModalParams, + 'loginWithSocial' | 'loginList' +> & { mode: Theme }) => { const clickHandler = (p: string) => { return loginWithSocial(p) } return (
- {loginList.map((l) => { - return ( -
clickHandler(l)} - > - {`${l} -
- ) - })} + {loginList.length <= 4 + ? loginList.map((l: string) => { + return ( +
clickHandler(l)} + > + {`${l} +

Continue with {l.charAt(0).toUpperCase() + l.slice(1)}

+
+ ) + }) + : loginList.slice(0, 5).map((l, i) => { + return i === 0 ? ( +
clickHandler(l)} + > + {`${l} +

Continue with {l.charAt(0).toUpperCase() + l.slice(1)}

+
+ ) : ( +
clickHandler(l)} + style={ + loginList.length === 5 + ? { width: '60px', height: '44px', borderRadius: '40%' } + : {} + } + > + {`${l} +
+ ) + })} + {loginList.length > 5 ? ( +
setShowMore(true)} + > + more +
+ ) : ( + '' + )}
) } @@ -135,7 +195,7 @@ const Footer = ({ mode }: { mode: Theme }) => { target="_blank" className="xar-footer-img__link" > - Secured By Arcana + Powered By Arcana ) @@ -150,15 +210,7 @@ const Loader = (props: { }) => { return ( <> - {props.header ? ( - props.header - ) : ( - - )} + {props.header ? props.header : } {props.text ?

{props.text}

: ''} {props.children ? <>{props.children} : ''} @@ -171,6 +223,9 @@ const OTPEntry = ({ setError, closeFunc, compact, + email, + mode, + toHome, }: { loginWithOtpStart: () => Promise loginWithOtpComplete: ( @@ -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) @@ -368,7 +426,7 @@ const OTPEntry = ({ if (loader.loading) { return ( <> - +
{loader.text}
) @@ -376,54 +434,70 @@ const OTPEntry = ({ return ( <> -
Verification
-
- Please enter the OTP that was sent to your
- email address +
+ +

Enter OTP

-
- {Array(numInputs) - .fill(null) - .map((_, i) => { - return ( - (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' - } - /> - ) - })} +
+ email +
+
+ We’ve sent a verification code to +
+ {email}
- {isInvalidOTP ? ( -
-

- Incorrect OTP. {attempts} attempts left. -

+
+
+ {Array(numInputs) + .fill(null) + .map((_, i) => { + return ( + (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' + } + /> + ) + })}
- ) : ( - '' - )} + {isInvalidOTP ? ( +
+

+ Incorrect OTP. {attempts} attempts left. +

+
+ ) : ( + '' + )} +
- 0} - text={ - counter > 0 ? `Resend code in ${counter} seconds` : 'Resend code' - } - method={resendCode} - /> + {counter > 0 ? ( + Resend code in {counter} seconds + ) : ( +
+ Did not receive your code yet? + +
+ )}
) @@ -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 ( <> - +

Login Failed

Please check credentials and try again

) diff --git a/src/ui/icons.ts b/src/ui/icons.ts index 87d1217..a07df4c 100644 --- a/src/ui/icons.ts +++ b/src/ui/icons.ts @@ -1,10 +1,12 @@ const BASE_URL = 'https://auth-icons.s3.ap-south-1.amazonaws.com' +const LOADING_ICON = `${BASE_URL}/loading.svg` + const SOCIAL_LOGO: { [k: string]: string } = { google: `${BASE_URL}/google.png`, twitter: `${BASE_URL}/twitter.png`, - github: `${BASE_URL}/github-light.png`, - github_light: `${BASE_URL}/github.png`, + github: `${BASE_URL}/github.png`, + github_light: `${BASE_URL}/github-light.png`, twitch: `${BASE_URL}/twitch.png`, discord: `${BASE_URL}/discord.png`, aws: `${BASE_URL}/aws.png`, @@ -12,6 +14,33 @@ const SOCIAL_LOGO: { [k: string]: string } = { steam: `${BASE_URL}/steam.png`, } +const MISC_ICONS = { + light: { + arrow: `${BASE_URL}/arrow-light.svg`, + success: `${BASE_URL}/success.svg`, + failed: `${BASE_URL}/failed.svg`, + email: `${BASE_URL}/email.svg`, + 'try-again': `${BASE_URL}/try-again-light.svg`, + change: `${BASE_URL}/change.svg`, + send: `${BASE_URL}/send.svg`, + 'dots-horizontal': `${BASE_URL}/dots-horizontal-light.svg`, + shrink: `${BASE_URL}/shrink-light.svg`, + 'back-arrow': `${BASE_URL}/back-arrow-light.svg`, + }, + dark: { + arrow: `${BASE_URL}/arrow-dark.svg`, + success: `${BASE_URL}/success.svg`, + failed: `${BASE_URL}/failed.svg`, + email: `${BASE_URL}/email.svg`, + 'try-again': `${BASE_URL}/try-again-dark.svg`, + change: `${BASE_URL}/change.svg`, + send: `${BASE_URL}/send.svg`, + 'dots-horizontal': `${BASE_URL}/dots-horizontal-dark.svg`, + shrink: `${BASE_URL}/shrink-dark.svg`, + 'back-arrow': `${BASE_URL}/back-arrow-dark.svg`, + }, +} + function getSocialLogo(provider: string, theme: 'light' | 'dark') { if (SOCIAL_LOGO[`${provider}_${theme}`]) { return SOCIAL_LOGO[`${provider}_${theme}`] @@ -24,4 +53,4 @@ const ARCANA_LOGO = { dark: `${BASE_URL}/secured-by-arcana-dark.svg`, } -export { getSocialLogo, ARCANA_LOGO } +export { getSocialLogo, ARCANA_LOGO, MISC_ICONS, LOADING_ICON } diff --git a/src/ui/loader.tsx b/src/ui/loader.tsx index 0198a7d..d83cad7 100644 --- a/src/ui/loader.tsx +++ b/src/ui/loader.tsx @@ -1,64 +1,5 @@ -const RADIUS = 20 +import { LOADING_ICON } from './icons' -interface LoaderProps { - stroke: number - secondaryColor: string - strokeColor?: string - compact?: boolean - width?: number -} - -export default function Loader(props: LoaderProps) { - const width = props.width ? props.width : props.compact ? 60 : 80 - const { stroke = 8, secondaryColor } = props - return ( -
- - - - - - - - - - -
- ) -} - -const getViewBoxSize = (strokeWidth: number, radius: number) => { - const startingPoint = -radius - strokeWidth / 2 + 1 - const endpoint = radius * 2 + strokeWidth - return [startingPoint, startingPoint, endpoint, endpoint].join(' ') -} - -const getPath = (radius: number) => { - return ['M' + radius + ' 0c0-9.94-8.06', radius, radius, radius].join('-') +export default function Loader() { + return loading } diff --git a/src/ui/modal.tsx b/src/ui/modal.tsx index c99d49e..61e4c7a 100644 --- a/src/ui/modal.tsx +++ b/src/ui/modal.tsx @@ -10,6 +10,7 @@ import { OTPError, } from './components' import { Overlay } from './overlay' +import More from './more' import { useReducer, useState } from 'preact/hooks' const WAIT_TEXT = { @@ -58,6 +59,7 @@ const reducer = ( const Modal = (props: ModalParams) => { const [loaderState, dispatch] = useReducer(reducer, initLoaderState) const [email, setEmail] = useState('') + const [showMore, setShowMore] = useState(false) const socialLogin = async (kind: string) => { dispatch('SOCIAL') @@ -74,17 +76,24 @@ const Modal = (props: ModalParams) => { return login } + function onShowMore(val: boolean) { + setShowMore(val) + } + if (loaderState.loading) { return ( {loaderState.type == 'OTP_SENT' ? ( dispatch('RESET')} loginWithOtpStart={() => props.loginWithOTPStart(email)} setError={() => dispatch('OTP_ERROR')} closeFunc={props.closeFunc} loginWithOtpComplete={props.loginWithOTPComplete} compact={props.options.compact} + email={email} + mode={props.mode} /> ) : ( { {loaderState.type == 'OTP_ERROR' ? ( - dispatch('RESET')} /> + dispatch('RESET')} mode={props.mode} /> ) : ( <>
@@ -110,17 +119,29 @@ const Modal = (props: ModalParams) => { email={email} setEmail={setEmail} loginWithOTPStart={otpLogin} + mode={props.mode} /> {props.loginList.length > 0 ? ( <> - + onShowMore(true)} /> ) : null} + {showMore ? ( + onShowMore(false)} + mode={props.mode} + onLoginClick={socialLogin} + /> + ) : ( + '' + )} )} diff --git a/src/ui/more.tsx b/src/ui/more.tsx new file mode 100644 index 0000000..f7797fa --- /dev/null +++ b/src/ui/more.tsx @@ -0,0 +1,55 @@ +import { StateUpdater, Dispatch } from 'preact/hooks' +import { getSocialLogo, MISC_ICONS } from './icons' +import { Theme } from '../typings' + +interface MoreProps { + list: Array + setShow: Dispatch> + onLoginClick: (kind: string) => void + mode: Theme +} + +export default function More(props: MoreProps) { + const { list, setShow, mode, onLoginClick } = props + + const onSocialLoginClick = (kind: string) => { + onLoginClick(kind) + setShow(false) + } + + return ( +
+
setShow(false)} style={{ flex: 1 }}>
+
e.preventDefault()}> +
+ close setShow(false)} + /> +
+

Continue with a social account

+
+ {list.map((l) => { + return ( +
onSocialLoginClick(l)} + > +
+ {`${l} +
+

{l}

+
+ ) + })} +
+
+
+ ) +} diff --git a/src/ui/style.css b/src/ui/style.css index 17b91e4..bbf3754 100644 --- a/src/ui/style.css +++ b/src/ui/style.css @@ -1,26 +1,51 @@ @import url('https://fonts.googleapis.com/css2?family=Sora:wght@100;400;600;700&display=block'); @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700&family=Sora:wght@400;600&display=block'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap'); + +@font-face { + font-family: 'Nohemi'; + src: url('https://new-fonts.s3.ap-south-1.amazonaws.com/Nohemi-SemiBold.woff2') + format('woff2'); + font-weight: 400; + font-style: normal; +} .xar-light-mode { - --fg: #333333; - --bg: #eff1f3; - --background: #fcfcfc; + --fg: #e4e9eb; + --bg: #e4e9eb; + --background: #f7f7f7; --inputShadow: 0 0 8px 3px rgba(126, 126, 126, 0.25); - --text-color: #101010; + --text-color: #1d2a31; + --sub-text-color: #829299; --otp-bg: #eeeeee; --otp-shadow: 1px 1px 2px 0px #aeaec033 inset, -1px -1px 1px 0px #ffffffb2 inset; + --action-link-text-color: #bbccd6; + --separator-color: #c8d5d9; + --separator-text-color: #74919c; + --error-text-color: #f61d1d; + --more-icon-bg: #47545b; + --email-id-color: #1d2a31; + --social-login-fw-bg: #1d2a31; } .xar-dark-mode { - --fg: #ffffff; - --bg: #313131; - --background: #262626; + --fg: #47545b; + --bg: #39444a; + --background: #2d363b; --inputShadow: 0 0 8px 3px rgba(0, 0, 0, 0.05); --text-color: #f7f7f7; + --sub-text-color: #829299; --otp-bg: linear-gradient(141.48deg, #161616 -4.56%, #151515 135.63%); --otp-shadow: -50px 49px 29px 22px #1c1c1cd6 inset, 5px 5px 10px 0px #0b0b0b80 inset; + --action-link-text-color: #bbccd6; + --separator-color: #39444a; + --separator-text-color: #829299; + --error-text-color: #fa3636; + --more-icon-bg: #47545b; + --email-id-color: #d4d7d8; + --social-login-fw-bg: #f7f7f7; } .compact { @@ -32,11 +57,11 @@ } .full { - --modal-height: 480px; - --loader-font-size: 20px; - --loader-font-weight: 700; + --modal-height: 200px; + --loader-font-size: 12px; + --loader-font-weight: 400; --success-img-width: 100px; - --action-link-size: 15px; + --action-link-size: 12px; } #xar-modal { @@ -67,13 +92,14 @@ } .xar-header-logo__empty-container { - width: 70px; + /* width: 70px; height: 70px; border-radius: 50%; margin: 0 auto; display: flex; align-items: center; - justify-content: center; + justify-content: center; */ + display: none; } .xar-header-logo { @@ -81,20 +107,30 @@ max-height: 60px; margin: 0 auto; display: inline-block; + border-radius: 100%; } .xar-header-heading { - font-family: 'Sora', sans-serif; + font-family: 'Nohemi', 'Sora', sans-serif; text-align: center; + font-size: 20px; + color: var(--text-color); } .xar-header-subtext { - font-family: 'Sora', sans-serif; + font-family: 'Inter', sans-serif; font-size: 12px; font-weight: 400; max-width: 200px; } +.xar-sub-text { + font-family: 'Inter', sans-serif; + font-size: 12px; + font-weight: 400; + color: var(--sub-text-color); +} + .xar-email-login { display: flex; flex-direction: column; @@ -116,25 +152,61 @@ max-width: var(--success-img-width); } +.xar-email-login__input-container { + display: flex; + min-height: 45px; + position: relative; +} + .xar-email-login__input { + flex: 1; height: 45px; - padding: 0 16px; font-family: 'Sora', sans-serif; - font-size: 14px; - font-weight: 400; - color: var(--fg); + font-size: 16px; + font-weight: 500; + color: var(--text-color); background: var(--bg); border: none; - border-radius: 5px; outline: none; - box-shadow: var(--inputShadow); + border-radius: 8px; + padding: 10px; + box-sizing: border-box; +} + +input[type='text']:focus { + border: 1px solid var(--sub-text-color); } .xar-social-container { display: flex; - justify-content: center; + justify-content: space-between; gap: 1rem; flex-wrap: wrap; + width: 100%; +} + +.xar-social-icon__wrapper-full-width { + display: flex; + background: var(--social-login-fw-bg); + width: 100%; + height: 44px; + align-items: center; + justify-content: center; + gap: 4px; + border-radius: 100px; + cursor: pointer; +} + +.xar-social-icon__wrapper-full-width img { + margin: 0; + height: 22px; + width: 22px; +} + +.xar-social-icon__wrapper-full-width p { + font-size: 14px; + font-weight: 600; + font-family: 'Inter', sans-serif; } .xar-social-icon__wrapper { @@ -159,44 +231,127 @@ } .xar-container { - padding: 30px 30px 20px; - width: 325px; + padding: 20px; + width: 360px; min-height: var(--modal-height); background-color: var(--background); color: var(--fg); margin: 0 auto; - font-family: 'Sora', sans-serif; + font-family: 'Inter', sans-serif; box-shadow: 4px 5px 4px rgba(0, 0, 0, 0.25); border-radius: 10px; - box-sizing: content-box; + box-sizing: border-box; transition: all 1s ease; + display: flex; + flex-direction: column; + justify-content: space-between; } .xar-inner-container { - min-height: inherit; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; + position: relative; + margin: auto; + width: 100%; + gap: 20px; } -.xar-inner-container > *:not(:first-child) { - margin-top: 20px; +.xar-more-sheet-container { + background: rgb(18 18 18 / 90%); + position: absolute; + height: 108%; + width: 112.4%; + border-radius: 10px; + bottom: 0px; + display: flex; } -.xar-btn { - margin: 0 auto; - padding: 0; +.xar-more-sheet { + padding: 14px; + box-sizing: border-box; + border-radius: 10px; + display: flex; + flex-direction: column; + gap: 10px; + background: var(--background); + transition: all 0.3s ease-in-out; + animation: slide-up 0.3s ease-in-out forwards; + position: absolute; width: 100%; - height: 2.75rem; - font-size: 14px; + height: 80%; + bottom: 0px; +} + +.xar-more-shrink-icon { + width: 50px; + height: 10px; + cursor: pointer; +} + +.xar-more-sheet__title { + color: var(--text-color); + font-size: 20px; font-weight: 600; - text-transform: uppercase; - color: var(--bg); - background: var(--fg); +} + +.xar-more-sheet__list-container { + display: flex; + flex-direction: column; + gap: 16px; + border-radius: 10px; + background: var(--bg); + padding: 10px; + flex: 1; + overflow-y: auto; +} + +.xar-more-sheet__list-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; +} + +.xar-more-sheet__list-icon-container { + background: var(--more-icon-bg); + border-radius: 50%; + display: flex; +} + +.xar-more-sheet__list-icon { + padding: 6px; + width: 16px; + height: 16px; +} + +.xar-more-sheet__list-text { + font-size: 14px; + font-weight: 500; + color: var(--text-color); + text-transform: capitalize; +} + +.xar-btn__input-arrow { + position: absolute; + left: 290px; + height: 100%; + display: inline-flex; + justify-content: right; +} + +.xar-btn__input-arrow:disabled img { + filter: opacity(25%); +} + +.xar-btn { + background: transparent; border: none; - border-radius: 5px; + display: flex; + align-items: center; + gap: 0.5rem; } .xar-btn:disabled { @@ -239,13 +394,21 @@ display: flex; align-items: center; text-align: center; + color: var(--separator-color); +} + +.xar-separator__text { + font-size: 12px; + font-weight: 400; + font-family: 'Inter', sans-serif; + color: var(--separator-text-color); } .xar-separator:before, .xar-separator:after { content: ''; flex: 1 1 auto; - border-bottom: 1px solid var(--fg); + border-bottom: 1px solid var(--separator-color); } .xar-separator:before { @@ -259,13 +422,9 @@ .xar-action__link { border: 0; background: none; - text-underline-offset: 3px; - text-transform: uppercase; - text-decoration: underline; - color: #3e9aff; - font-weight: 700; + color: var(--action-link-text-color); + font-weight: 400; font-size: var(--action-link-size); - line-height: 19px; cursor: pointer; } @@ -274,22 +433,35 @@ cursor: not-allowed; } +.xar-loading__header { + font-size: 20px; + font-weight: 600; + color: var(--text-color); +} + .xar-loader__text { font-size: var(--loader-font-size); font-weight: var(--loader-font-weight); + color: var(--text-color); } .xar-loader-circle { stroke: var(--fg); } +.xar-otp-box-container { + display: flex; + flex-direction: column; + gap: 0.5em; +} + .xar-otp-box { display: flex; gap: 0.5em; } .xar-otp-input { - color: var(--fg); + color: var(--text-color); font-size: 20px; font-weight: 400; border: none; @@ -297,17 +469,19 @@ text-align: center; width: 30px; height: 35px; - border-radius: 10px; - background: var(--otp-bg); - box-shadow: var(--otp-shadow); + border-radius: 8px; + background: var(--bg); } .xar-invalid-otp { - border: 1px solid #b43030; + border: 1px solid var(--error-text-color); } .xar-invalid-otp-text { - color: #b43030; + color: var(--error-text-color); + font-size: 10px; + font-weight: 400; + text-align: left; } .xar-otp-input:disabled { @@ -315,30 +489,67 @@ } .xar-otp-heading { - font-family: 'Montserrat', sans-serif; - font-size: 24px; - font-weight: 700; + font-family: 'Nohemi', sans-serif; + font-size: 20px; + font-weight: 600; text-align: center; color: var(--text-color); + flex: 1; } .xar-otp-sub-heading { - font-family: 'Montserrat', sans-serif; + font-family: 'Inter', sans-serif; font-size: 12px; font-weight: 400; text-align: center; color: var(--text-color); } -.xar-otp-error-heading { - color: var(--fg); +.xar-otp-email { + font-size: 14px; font-weight: 700; - font-size: 24px; - font-family: 'Montserrat', sans-serif; + color: var(--email-id-color); } -.xar-otp-error-subheading { + +.xar-otp-error-heading { color: var(--text-color); + font-weight: 600; + font-size: 20px; + font-family: 'Nohemi', sans-serif; +} +.xar-otp-error-subheading { + color: var(--sub-text-color); font-weight: 400; font-size: 12px; - font-family: 'Montserrat', sans-serif; + font-family: 'Inter', sans-serif; +} + +.xar-otp-heading-container { + display: flex; + flex-direction: row; + gap: 0.5rem; + width: 100%; + position: relative; + align-items: center; +} + +.xar-loader { + animation: spin 1s linear infinite; +} + +@-moz-keyframes spin { + 100% { + -moz-transform: rotate(360deg); + } +} +@-webkit-keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + } +} +@keyframes spin { + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } }