diff --git a/images/home-testimonials.jpg b/images/login_background.jpg similarity index 100% rename from images/home-testimonials.jpg rename to images/login_background.jpg diff --git a/src/App/styles.less b/src/App/styles.less index 729407d0d..57aea9d36 100644 --- a/src/App/styles.less +++ b/src/App/styles.less @@ -48,6 +48,7 @@ --poster-shape-ratio: 1.464; --window-min-width: 800px; --window-min-height: 600px; + --focusable-border-size: 2px; } * { @@ -59,6 +60,7 @@ user-select: none; text-decoration: none; outline: none; + appearance: none; } html, body, :global(#app) { @@ -77,6 +79,10 @@ html, body, :global(#app) { height: 100%; } + input { + user-select: text; + } + ::-webkit-scrollbar { width: var(--scroll-bar-width); } diff --git a/src/common/Checkbox/styles.less b/src/common/Checkbox/styles.less index 10986c425..ffe321f53 100644 --- a/src/common/Checkbox/styles.less +++ b/src/common/Checkbox/styles.less @@ -16,8 +16,8 @@ opacity: 0; height: 0; width: 0; - top: -99999999px; - left: -99999999px; + top: 0; + left: 0; } &:global(.checked) { diff --git a/src/common/TextInput/TextInput.js b/src/common/Input/Input.js similarity index 50% rename from src/common/TextInput/TextInput.js rename to src/common/Input/Input.js index 1c1bed044..1766e975d 100644 --- a/src/common/TextInput/TextInput.js +++ b/src/common/Input/Input.js @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { withFocusable } from 'stremio-common'; -class TextInput extends PureComponent { +class Input extends PureComponent { render() { const { forwardedRef, focusable, ...props } = this.props; return ( @@ -15,21 +15,21 @@ class TextInput extends PureComponent { } } -TextInput.propTypes = { +Input.propTypes = { focusable: PropTypes.bool.isRequired }; -TextInput.defaultProps = { +Input.defaultProps = { focusable: false }; -const TextInputWithFocusable = withFocusable(TextInput); +const InputWithFocusable = withFocusable(Input); -TextInputWithFocusable.displayName = 'TextInputWithFocusable'; +InputWithFocusable.displayName = 'InputWithFocusable'; -const TextInputWithForwardedRef = React.forwardRef((props, ref) => ( - +const InputWithForwardedRef = React.forwardRef((props, ref) => ( + )); -TextInputWithForwardedRef.displayName = 'TextInputWithForwardedRef'; +InputWithForwardedRef.displayName = 'InputWithForwardedRef'; -export default TextInputWithForwardedRef; +export default InputWithForwardedRef; diff --git a/src/common/Input/index.js b/src/common/Input/index.js new file mode 100644 index 000000000..a122bd4be --- /dev/null +++ b/src/common/Input/index.js @@ -0,0 +1,3 @@ +import Input from './Input'; + +export default Input; diff --git a/src/common/Link/Link.js b/src/common/Link/Link.js new file mode 100644 index 000000000..dadb27377 --- /dev/null +++ b/src/common/Link/Link.js @@ -0,0 +1,35 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { withFocusable } from 'stremio-common'; + +class Link extends PureComponent { + render() { + const { forwardedRef, focusable, ...props } = this.props; + return ( + + ); + } +} + +Link.propTypes = { + focusable: PropTypes.bool.isRequired +}; +Link.defaultProps = { + focusable: false +}; + +const LinkWithFocusable = withFocusable(Link); + +LinkWithFocusable.displayName = 'LinkWithFocusable'; + +const LinkWithForwardedRef = React.forwardRef((props, ref) => ( + +)); + +LinkWithForwardedRef.displayName = 'LinkWithForwardedRef'; + +export default LinkWithForwardedRef; diff --git a/src/common/Link/index.js b/src/common/Link/index.js new file mode 100644 index 000000000..18a195368 --- /dev/null +++ b/src/common/Link/index.js @@ -0,0 +1,3 @@ +import Link from './Link'; + +export default Link; diff --git a/src/common/TextInput/index.js b/src/common/TextInput/index.js deleted file mode 100644 index a93ada305..000000000 --- a/src/common/TextInput/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import TextInput from './TextInput'; - -export default TextInput; diff --git a/src/common/index.js b/src/common/index.js index 3a135f8db..d6fb24585 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -2,7 +2,8 @@ import Slider from './Slider'; import { FocusableProvider, withFocusable } from './Focusable'; import Button from './Button'; import Checkbox from './Checkbox'; -import TextInput from './TextInput'; +import Input from './Input'; +import Link from './Link'; import { Modal, ModalsContainerContext, withModalsContainer } from './Modal'; import Popup from './Popup'; import Router from './Router'; @@ -13,7 +14,8 @@ import UserPanel from './UserPanel'; export { Checkbox, - TextInput, + Input, + Link, Popup, NavBar, ModalsContainerContext, diff --git a/src/routes/Intro/CheckboxLabel/index.js b/src/routes/Intro/CheckboxLabel/index.js deleted file mode 100644 index 830cfc415..000000000 --- a/src/routes/Intro/CheckboxLabel/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import CheckboxLabel from './CheckboxLabel'; - -export default CheckboxLabel; diff --git a/src/routes/Intro/CheckboxLabel/CheckboxLabel.js b/src/routes/Intro/ConsentCheckbox/ConsentCheckbox.js similarity index 69% rename from src/routes/Intro/CheckboxLabel/CheckboxLabel.js rename to src/routes/Intro/ConsentCheckbox/ConsentCheckbox.js index d3e4974ed..9dfd91105 100644 --- a/src/routes/Intro/CheckboxLabel/CheckboxLabel.js +++ b/src/routes/Intro/ConsentCheckbox/ConsentCheckbox.js @@ -4,8 +4,8 @@ import classnames from 'classnames'; import { Checkbox } from 'stremio-common'; import styles from './styles'; -const CheckboxLabel = React.forwardRef(({ className, label, link, href, checked, onClick }, ref) => ( -
{label} @@ -14,9 +14,9 @@ const CheckboxLabel = React.forwardRef(({ className, label, link, href, checked, )); -CheckboxLabel.displayName = 'CheckboxLabel'; +ConsentCheckbox.displayName = 'ConsentCheckbox'; -CheckboxLabel.propTypes = { +ConsentCheckbox.propTypes = { className: PropTypes.string, onClick: PropTypes.func, label: PropTypes.string, @@ -25,4 +25,4 @@ CheckboxLabel.propTypes = { checked: PropTypes.bool }; -export default CheckboxLabel; +export default ConsentCheckbox; diff --git a/src/routes/Intro/ConsentCheckbox/index.js b/src/routes/Intro/ConsentCheckbox/index.js new file mode 100644 index 000000000..b31edc668 --- /dev/null +++ b/src/routes/Intro/ConsentCheckbox/index.js @@ -0,0 +1,3 @@ +import ConsentCheckbox from './ConsentCheckbox'; + +export default ConsentCheckbox; diff --git a/src/routes/Intro/CheckboxLabel/styles.less b/src/routes/Intro/ConsentCheckbox/styles.less similarity index 96% rename from src/routes/Intro/CheckboxLabel/styles.less rename to src/routes/Intro/ConsentCheckbox/styles.less index 2fc58adf8..c1067f69d 100644 --- a/src/routes/Intro/CheckboxLabel/styles.less +++ b/src/routes/Intro/ConsentCheckbox/styles.less @@ -1,4 +1,4 @@ -.checkbox-label-container { +.consent-checkbox-container { display: flex; flex-direction: row; align-items: center; diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 6869aee85..e392f8c19 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -1,7 +1,7 @@ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import Icon from 'stremio-icons/dom'; -import { Button, TextInput } from 'stremio-common'; -import CheckboxLabel from './CheckboxLabel'; +import { Button, Input, Link } from 'stremio-common'; +import ConsentCheckbox from './ConsentCheckbox'; import styles from './styles'; const FORMS = { @@ -19,9 +19,10 @@ class Intro extends Component { this.termsRef = React.createRef(); this.privacyPolicyRef = React.createRef(); this.marketingRef = React.createRef(); + this.errorRef = React.createRef(); this.state = { - selectedForm: FORMS.SIGN_UP, + selectedForm: FORMS.LOGIN, termsAccepted: false, privacyPolicyAccepted: false, marketingAccepted: false, @@ -33,6 +34,7 @@ class Intro extends Component { } changeSelectedForm = (event) => { + event.currentTarget.blur(); this.setState({ selectedForm: event.currentTarget.dataset.option, termsAccepted: false, @@ -86,6 +88,16 @@ class Intro extends Component { nextState.error !== this.state.error; } + componentDidUpdate(prevProps, prevState) { + if (prevState.error !== this.state.error && this.state.error.length > 0) { + this.errorRef.current.scrollIntoView(); + } + + if (prevState.selectedForm !== this.state.selectedForm) { + this.emailRef.current.focus(); + } + } + loginOnSubmit = (event) => { event.preventDefault(); if (this.state.email.length < 8) { @@ -97,6 +109,8 @@ class Intro extends Component { if (this.state.password.length === 0) { this.setState({ error: 'Invalid password' }); + } else { + this.setState({ error: '' }); } } } @@ -137,6 +151,7 @@ class Intro extends Component { if (this.privacyPolicyRef.current === document.activeElement) { this.marketingRef.current.focus(); } + this.setState({ error: '' }); } } @@ -145,58 +160,11 @@ class Intro extends Component { } } - validateGuestLogin = (event) => { + guestLoginOnSubmit = () => { if (!this.state.termsAccepted) { - event.preventDefault(); this.setState({ error: 'You must accept the Terms of Service' }); - } - } - - renderSignUpForm = () => { - return ( -
- - - - - - - { - this.state.error.length > 0 ? -
{this.state.error}
- : - null - } - - - ); - } - - renderLoginForm = () => { - return ( -
- - - Forgot password? - { - this.state.error.length > 0 ? -
{this.state.error}
- : - null - } - -
- ); - } - - renderSelectedMenu = () => { - switch (this.state.selectedForm) { - case FORMS.SIGN_UP: - return this.renderSignUpForm(); - case FORMS.LOGIN: - return this.renderLoginForm(); - default: - return null; + } else { + this.setState({ error: '' }); } } @@ -204,20 +172,43 @@ class Intro extends Component { return (
-
- -
We won't post anything on your behalf
- {this.renderSelectedMenu()} -
{this.state.selectedForm === FORMS.SIGN_UP ? 'LOG IN' : 'SING UP WITH EMAIL'}
- { - this.state.selectedForm === FORMS.SIGN_UP ? - GUEST LOGIN - : - null - } +
+
+ +
We won't post anything on your behalf
+
+ + + { + this.state.selectedForm === FORMS.LOGIN ? + Forgot password? + : + + + + + + + } + { + this.state.error.length > 0 ? +
{this.state.error}
+ : + null + } + +
+ + { + this.state.selectedForm === FORMS.SIGN_UP ? + + : + null + } +
); diff --git a/src/routes/Intro/styles.less b/src/routes/Intro/styles.less index 10dccf8dd..57c81ceac 100644 --- a/src/routes/Intro/styles.less +++ b/src/routes/Intro/styles.less @@ -9,7 +9,7 @@ z-index: 0; width: 100%; height: 100%; - background-image: url('/images/home-testimonials.jpg'); + background-image: url('/images/login_background.jpg'); background-size: cover; background-attachment: fixed; @@ -23,123 +23,172 @@ background-color: var(--color-backgrounddark80); } - .intro { + .scroll-content { + display: flex; + flex-direction: row; position: absolute; z-index: 1; top: 0; left: 0; right: 0; bottom: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; + overflow-y: auto; + overflow-x: hidden; - .facebook-button { + .intro { + margin: auto; + padding: calc(var(--login-form-width) * 0.2) 0; width: var(--login-form-width); - height: calc(var(--login-form-width) * 0.25); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: calc(var(--spacing) * 1.25) 0; - background: var(--color-secondarydark); - .icon { - height: 100%; - margin-right: var(--spacing); - fill: var(--color-surfacelighter); - } - - .label { - font-size: 1.1em; - color: var(--color-surfacelighter); - } - } - - .facebook-subtext { - margin: calc(var(--spacing) * 0.5) 0 calc(var(--spacing) * 4) 0; - color: var(--color-surface); - } - - .form-container { - width: var(--login-form-width); - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - - .email { - width: 100%; - margin-bottom: var(--spacing); - padding: 0.5em 0; - border-bottom: 1px solid var(--color-surface); - font-size: 1.1em; - color: var(--color-surfacelighter); - background: none; - - &::placeholder { - color: var(--color-surfacelight); - } - } - - .password { - width: 100%; - margin-bottom: var(--spacing); - padding: 0.5em 0; - border-bottom: 1px solid var(--color-surface); - font-size: 1.1em; - color: var(--color-surfacelighter); - background: none; - - &::placeholder { - color: var(--color-surfacelight); - } - } - - .checkbox-label { - width: 100%; - margin-bottom: var(--spacing); - } - - - .forgot-password { + .facebook-button { + width: var(--login-form-width); + height: calc(var(--login-form-width) * 0.25); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + border: var(--focusable-border-size) solid transparent; + background: var(--color-secondarydark); cursor: pointer; - margin-bottom: var(--spacing); - color: var(--color-surfacelight); - &:hover { + .icon { + height: 1.7em; + margin-right: var(--spacing); + fill: var(--color-surfacelighter); + } + + .label { + font-size: 1.1em; color: var(--color-surfacelighter); } - } - .error { - margin-bottom: var(--spacing); - text-align: center; - color: var(--color-signal1); - } + &:focus { + border-color: var(--color-surfacelighter); + } - .submit-button { - width: 100%; - cursor: pointer; - padding: calc(var(--spacing) * 1.1); - font-size: 1.1em; - font-weight: 600; - color: var(--color-surfacelighter); - background-color: var(--color-primarydark); - - &:hover, &:focus { - background-color: var(--color-primary); + &:hover { + border-color: transparent; } } - } - .option { - margin-top: calc(var(--spacing) * 2); - cursor: pointer; - font-size: 1.1em; - font-weight: 600; - color: var(--color-surfacelighter); + .facebook-statement { + margin: calc(var(--spacing) * 0.5) 0 calc(var(--spacing) * 4) 0; + text-align: center; + color: var(--color-surface); + } + + .form-container { + width: var(--login-form-width); + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: center; + + .text-input { + margin: 0 calc(var(--spacing) * 0.5) var(--spacing) calc(var(--spacing) * 0.5); + padding: 0.5em 0; + border-bottom: 1px solid var(--color-surface); + font-size: 1.1em; + color: var(--color-surfacelighter); + background: none; + + &::placeholder { + color: var(--color-surfacelight); + } + + &:hover, &:focus { + border-bottom-color: var(--color-surfacelighter); + + &::placeholder { + color: var(--color-surfacelighter); + } + } + } + + .consent-checkbox { + width: 100%; + margin-bottom: var(--spacing); + padding: calc(var(--spacing) * 0.5); + border: var(--focusable-border-size) solid transparent; + + &:focus-within { + border-color: var(--color-surfacelighter); + } + } + + + .forgot-password { + align-self: flex-end; + margin-bottom: var(--spacing); + padding: calc(var(--spacing) * 0.5); + border: var(--focusable-border-size) solid transparent; + color: var(--color-surfacelight); + cursor: pointer; + + &:hover, &:focus { + color: var(--color-surfacelighter); + } + + &:focus { + border-color: var(--color-surfacelighter); + } + + &:hover { + border-color: transparent; + } + } + + .error { + margin-bottom: var(--spacing); + text-align: center; + color: var(--color-signal1); + } + + .submit-button { + width: 100%; + outline: none; + padding: calc(var(--spacing) * 1.1); + font-size: 1.1em; + font-weight: 600; + border: var(--focusable-border-size) solid transparent; + color: var(--color-surfacelighter); + background-color: var(--color-primarydark); + cursor: pointer; + + &:hover, &:focus { + background-color: var(--color-primary); + } + + &:focus { + border-color: var(--color-surfacelighter); + } + + &:hover { + border-color: transparent; + } + } + } + + .switch-form-button, .guest-login-button { + width: 100%; + display: inline-block; + margin-top: var(--spacing); + padding: calc(var(--spacing) * 0.5); + text-align: center; + font-size: 1.1em; + font-weight: 600; + border: var(--focusable-border-size) solid transparent; + color: var(--color-surfacelighter); + cursor: pointer; + + &:focus { + border-color: var(--color-surfacelighter); + } + + &:hover { + border-color: transparent; + } + } } } } \ No newline at end of file