@@ -76,7 +76,7 @@ function HubsCloudPage() {
-
+
);
}
diff --git a/src/react-components/auth-dialog.js b/src/react-components/auth-dialog.js
deleted file mode 100644
index ff6fb14b6..000000000
--- a/src/react-components/auth-dialog.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import { injectIntl, FormattedMessage } from "react-intl";
-import DialogContainer from "./dialog-container.js";
-import IfFeature from "./if-feature";
-
-class AuthDialog extends Component {
- static propTypes = {
- intl: PropTypes.object,
- verifying: PropTypes.bool,
- verified: PropTypes.bool,
- authOrigin: PropTypes.string
- };
-
- render() {
- const { authOrigin, verifying, verified } = this.props;
- const { formatMessage } = this.props.intl;
- const title = verifying || !verified ? "" : formatMessage({ id: "auth.verified-title" });
-
- if (!verifying && !verified) {
- return (
-
-
-
- );
- } else {
- return (
-
- {verifying ? (
-
- ) : authOrigin === "spoke" ? (
-
- ) : (
-
- )}
-
- );
- }
- }
-}
-
-export default injectIntl(AuthDialog);
diff --git a/src/react-components/auth/RoomSignInModalContainer.js b/src/react-components/auth/RoomSignInModalContainer.js
new file mode 100644
index 000000000..6255ca090
--- /dev/null
+++ b/src/react-components/auth/RoomSignInModalContainer.js
@@ -0,0 +1,47 @@
+import React, { useState } from "react";
+import PropTypes from "prop-types";
+import configs from "../../utils/configs";
+import { SignInModal, SignInStep, SubmitEmail, WaitForVerification, SignInComplete } from "./SignInModal";
+
+// TODO: Migrate to use AuthContext
+export function RoomSignInModalContainer({ onClose, step, onSubmitEmail, message, continueText, onContinue }) {
+ const [cachedEmail, setCachedEmail] = useState();
+
+ return (
+
+ {step === SignInStep.submit && (
+ {
+ setCachedEmail(email);
+ onSubmitEmail(email);
+ }}
+ initialEmail={cachedEmail}
+ termsUrl={configs.link("terms_of_use", "https://github.com/mozilla/hubs/blob/master/TERMS.md")}
+ showTerms={configs.feature("show_terms")}
+ privacyUrl={configs.link("privacy_notice", "https://github.com/mozilla/hubs/blob/master/PRIVACY.md")}
+ showPrivacy={configs.feature("show_privacy")}
+ message={message}
+ />
+ )}
+ {step === SignInStep.waitForVerification && (
+
+ )}
+ {step === SignInStep.complete && (
+
+ )}
+
+ );
+}
+
+RoomSignInModalContainer.propTypes = {
+ onClose: PropTypes.func,
+ onSubmitEmail: PropTypes.func,
+ step: PropTypes.string,
+ message: PropTypes.string,
+ continueText: PropTypes.string,
+ onContinue: PropTypes.func
+};
diff --git a/src/react-components/auth/SignInModal.js b/src/react-components/auth/SignInModal.js
new file mode 100644
index 000000000..0fd3a98f6
--- /dev/null
+++ b/src/react-components/auth/SignInModal.js
@@ -0,0 +1,134 @@
+import React, { useCallback, useState } from "react";
+import PropTypes from "prop-types";
+import { CloseButton } from "../input/CloseButton";
+import { Modal } from "../modal/Modal";
+import { FormattedMessage } from "react-intl";
+import styles from "./SignInModal.scss";
+import { Button } from "../input/Button";
+import { TextInputField } from "../input/TextInputField";
+
+export const SignInStep = {
+ submit: "submit",
+ waitForVerification: "waitForVerification",
+ complete: "complete"
+};
+
+export function SubmitEmail({ onSubmitEmail, initialEmail, showPrivacy, privacyUrl, showTerms, termsUrl, message }) {
+ const [email, setEmail] = useState(initialEmail);
+
+ const onSubmitForm = useCallback(
+ e => {
+ e.preventDefault();
+ onSubmitEmail(email);
+ },
+ [onSubmitEmail, email]
+ );
+
+ return (
+
+ );
+}
+
+SubmitEmail.defaultProps = {
+ initialEmail: ""
+};
+
+SubmitEmail.propTypes = {
+ message: PropTypes.string,
+ showTerms: PropTypes.bool,
+ termsUrl: PropTypes.string,
+ showPrivacy: PropTypes.bool,
+ privacyUrl: PropTypes.string,
+ initialEmail: PropTypes.string,
+ onSubmitEmail: PropTypes.func.isRequired
+};
+
+export function WaitForVerification({ email, onCancel, showNewsletterSignup }) {
+ return (
+
+ );
+}
+
+WaitForVerification.propTypes = {
+ showNewsletterSignup: PropTypes.bool,
+ email: PropTypes.string.isRequired,
+ onCancel: PropTypes.func.isRequired
+};
+
+export function SignInComplete({ message, continueText, onContinue }) {
+ return (
+
+
{message}
+
{continueText}
+
+
+ );
+}
+
+SignInComplete.propTypes = {
+ message: PropTypes.string.isRequired,
+ continueText: PropTypes.string.isRequired,
+ onContinue: PropTypes.func.isRequired
+};
+
+export function SignInModal({ closeable, onClose, children, ...rest }) {
+ return (
+
} {...rest}>
+ {children}
+
+ );
+}
+
+SignInModal.propTypes = {
+ closeable: PropTypes.bool,
+ onClose: PropTypes.func,
+ children: PropTypes.node
+};
diff --git a/src/react-components/auth/SignInModal.scss b/src/react-components/auth/SignInModal.scss
new file mode 100644
index 000000000..838b0b562
--- /dev/null
+++ b/src/react-components/auth/SignInModal.scss
@@ -0,0 +1,29 @@
+@use "../styles/theme.scss";
+
+:local(.modal-content) {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding: 20px;
+ line-height: 1.25;
+
+ & > * {
+ margin-bottom: 16px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+:local(.terms) {
+ color: theme.$darkgrey;
+ font-size: theme.$font-size-xs;
+}
+
+:local(.newsletter) {
+ color: theme.$darkgrey;
+ font-size: theme.$font-size-sm;
+ line-height: 1.5;
+}
\ No newline at end of file
diff --git a/src/react-components/auth/SignInModal.stories.js b/src/react-components/auth/SignInModal.stories.js
new file mode 100644
index 000000000..0b6cbc351
--- /dev/null
+++ b/src/react-components/auth/SignInModal.stories.js
@@ -0,0 +1,76 @@
+import React from "react";
+import { Center } from "../layout/Center";
+import { Page } from "../layout/Page";
+import { RoomLayout } from "../layout/RoomLayout";
+import { SignInModal, SubmitEmail, WaitForVerification } from "./SignInModal";
+import backgroundUrl from "../../assets/images/home-hero-background-unbranded.png";
+
+export default {
+ title: "SignInModal"
+};
+
+export const PageSubmit = () => (
+
+
+
+
+
+
+
+);
+
+PageSubmit.parameters = {
+ layout: "fullscreen"
+};
+
+export const PageWaitForVerification = () => (
+
+
+
+
+
+
+
+);
+
+PageWaitForVerification.parameters = {
+ layout: "fullscreen"
+};
+
+export const RoomSubmit = () => (
+
+
+
+ }
+ />
+);
+
+RoomSubmit.parameters = {
+ layout: "fullscreen"
+};
+
+export const RoomWaitForVerification = () => (
+
+
+
+ }
+ />
+);
+
+RoomWaitForVerification.parameters = {
+ layout: "fullscreen"
+};
diff --git a/src/react-components/auth/SignInModalContainer.js b/src/react-components/auth/SignInModalContainer.js
new file mode 100644
index 000000000..9a160ca81
--- /dev/null
+++ b/src/react-components/auth/SignInModalContainer.js
@@ -0,0 +1,89 @@
+import React, { useCallback, useReducer, useContext, useEffect } from "react";
+import configs from "../../utils/configs";
+import { AuthContext } from "./AuthContext";
+import { SignInModal, SignInStep, WaitForVerification, SubmitEmail } from "./SignInModal";
+
+const SignInAction = {
+ submitEmail: "submitEmail",
+ verificationReceived: "verificationReceived",
+ cancel: "cancel"
+};
+
+const initialSignInState = {
+ step: SignInStep.submit,
+ email: ""
+};
+
+function loginReducer(state, action) {
+ switch (action.type) {
+ case SignInAction.submitEmail:
+ return { step: SignInStep.waitForVerification, email: action.email };
+ case SignInAction.verificationReceived:
+ return { ...state, step: SignInStep.complete };
+ case SignInAction.cancel:
+ return { ...state, step: SignInStep.submit };
+ }
+}
+
+function useSignIn() {
+ const auth = useContext(AuthContext);
+ const [state, dispatch] = useReducer(loginReducer, initialSignInState);
+
+ const submitEmail = useCallback(
+ email => {
+ auth.signIn(email).then(() => {
+ dispatch({ type: SignInAction.verificationReceived });
+ });
+ dispatch({ type: SignInAction.submitEmail, email });
+ },
+ [auth]
+ );
+
+ const cancel = useCallback(() => {
+ dispatch({ type: SignInAction.cancel });
+ }, []);
+
+ return {
+ step: state.step,
+ email: state.email,
+ submitEmail,
+ cancel
+ };
+}
+
+export function SignInModalContainer() {
+ const qs = new URLSearchParams(location.search);
+ const { step, submitEmail, cancel, email } = useSignIn();
+ const redirectUrl = qs.get("sign_in_destination_url") || "/";
+
+ useEffect(
+ () => {
+ if (step === SignInStep.complete) {
+ window.location = redirectUrl;
+ }
+ },
+ [step, redirectUrl]
+ );
+
+ return (
+
+ {step === SignInStep.submit ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/src/react-components/auth/SignInPage.js b/src/react-components/auth/SignInPage.js
deleted file mode 100644
index 5d4032685..000000000
--- a/src/react-components/auth/SignInPage.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import React, { useCallback, useState, useReducer, useContext, useEffect } from "react";
-import PropTypes from "prop-types";
-import { Page } from "../layout/Page";
-import styles from "./SignInPage.scss";
-import configs from "../../utils/configs";
-import IfFeature from "../if-feature";
-import { FormattedMessage } from "react-intl";
-import { AuthContext } from "../auth/AuthContext";
-
-const SignInStep = {
- submit: "submit",
- waitForVerification: "waitForVerification",
- complete: "complete"
-};
-
-const SignInAction = {
- submitEmail: "submitEmail",
- verificationReceived: "verificationReceived",
- cancel: "cancel"
-};
-
-const initialSignInState = {
- step: SignInStep.submit,
- email: ""
-};
-
-function loginReducer(state, action) {
- switch (action.type) {
- case SignInAction.submitEmail:
- return { step: SignInStep.waitForVerification, email: action.email };
- case SignInAction.verificationReceived:
- return { ...state, step: SignInStep.complete };
- case SignInAction.cancel:
- return { ...state, step: SignInStep.submit };
- }
-}
-
-function useSignIn() {
- const auth = useContext(AuthContext);
- const [state, dispatch] = useReducer(loginReducer, initialSignInState);
-
- const submitEmail = useCallback(
- email => {
- auth.signIn(email).then(() => {
- dispatch({ type: SignInAction.verificationReceived });
- });
- dispatch({ type: SignInAction.submitEmail, email });
- },
- [auth]
- );
-
- const cancel = useCallback(() => {
- dispatch({ type: SignInAction.cancel });
- }, []);
-
- return {
- step: state.step,
- email: state.email,
- submitEmail,
- cancel
- };
-}
-
-function SubmitEmail({ onSubmitEmail, initialEmail }) {
- const [email, setEmail] = useState(initialEmail);
-
- const onSubmitForm = useCallback(
- e => {
- e.preventDefault();
- onSubmitEmail(email);
- },
- [onSubmitEmail, email]
- );
-
- return (
-
- );
-}
-
-SubmitEmail.defaultProps = {
- initialEmail: ""
-};
-
-SubmitEmail.propTypes = {
- initialEmail: PropTypes.string,
- onSubmitEmail: PropTypes.func.isRequired
-};
-
-function WaitForVerification({ email, onCancel }) {
- return (
-
- );
-}
-
-WaitForVerification.propTypes = {
- email: PropTypes.string.isRequired,
- onCancel: PropTypes.func.isRequired
-};
-
-export function SignInPage() {
- const qs = new URLSearchParams(location.search);
- const { step, submitEmail, cancel, email } = useSignIn();
- const redirectUrl = qs.get("sign_in_destination_url") || "/";
-
- useEffect(
- () => {
- if (step === SignInStep.complete) {
- window.location = redirectUrl;
- }
- },
- [step, redirectUrl]
- );
-
- return (
-
- {step === SignInStep.submit ? (
-
- ) : (
-
- )}
-
- );
-}
diff --git a/src/react-components/auth/SignInPage.scss b/src/react-components/auth/SignInPage.scss
deleted file mode 100644
index 4a2d6522d..000000000
--- a/src/react-components/auth/SignInPage.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-@import '../../assets/stylesheets/shared';
-
-main {
- display: flex;
- background-color: #F3F3F3;
- height: 100%;
- justify-content: center;
- align-items: center;
-}
-
-:local(.sign-in-container) {
- @extend %centered-flex-column;
- background-color: white;
- border-radius: 4px;
- width: 480px;
- padding: 2em;
-
- h1 {
- margin-top: 0;
- }
-
- input {
- @extend %input-field;
- margin: 1.5em 0 0.5em 0;
- }
-
- button {
- @extend %action-button;
- }
-
- :local(.terms) {
- font-size: 7pt;
- margin-bottom: 32px;
- }
-}
diff --git a/src/react-components/auth/VerifyModal.js b/src/react-components/auth/VerifyModal.js
new file mode 100644
index 000000000..c16bd3c36
--- /dev/null
+++ b/src/react-components/auth/VerifyModal.js
@@ -0,0 +1,58 @@
+import React from "react";
+import PropTypes from "prop-types";
+import styles from "./VerifyModal.scss";
+import { Spinner } from "../misc/Spinner";
+import { Modal } from "../modal/Modal";
+
+export const VerificationStep = {
+ verifying: "verifying",
+ complete: "complete",
+ error: "error"
+};
+
+export function EmailVerifying() {
+ return (
+
+ Email Verifying
+
+
+ );
+}
+
+export function EmailVerified({ origin }) {
+ return (
+
+
Verification Complete
+
Please close this browser window and return to {origin}.
+
+ );
+}
+
+EmailVerified.propTypes = {
+ origin: PropTypes.string.isRequired
+};
+
+export function VerificationError({ error }) {
+ return (
+
+
Error Verifying Email
+
{(error && error.message) || "Unknown Error"}
+
+ );
+}
+
+VerificationError.propTypes = {
+ error: PropTypes.object
+};
+
+export function VerifyModal({ children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+VerifyModal.propTypes = {
+ children: PropTypes.node
+};
diff --git a/src/react-components/auth/VerifyModal.scss b/src/react-components/auth/VerifyModal.scss
new file mode 100644
index 000000000..8b88b716c
--- /dev/null
+++ b/src/react-components/auth/VerifyModal.scss
@@ -0,0 +1,20 @@
+:local(.modal-content) {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding: 20px;
+ line-height: 1.25;
+
+ svg, p {
+ margin-top: 16px;
+ }
+
+ & > * {
+ margin-bottom: 16px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
diff --git a/src/react-components/auth/VerifyModal.stories.js b/src/react-components/auth/VerifyModal.stories.js
new file mode 100644
index 000000000..25d811e1b
--- /dev/null
+++ b/src/react-components/auth/VerifyModal.stories.js
@@ -0,0 +1,51 @@
+import React from "react";
+import { Center } from "../layout/Center";
+import { Page } from "../layout/Page";
+import { VerifyModal, EmailVerifying, EmailVerified, VerificationError } from "./VerifyModal";
+import backgroundUrl from "../../assets/images/home-hero-background-unbranded.png";
+
+export default {
+ title: "VerifyModal"
+};
+
+export const Verifying = () => (
+
+
+
+
+
+
+
+);
+
+Verifying.parameters = {
+ layout: "fullscreen"
+};
+
+export const Verified = () => (
+
+
+
+
+
+
+
+);
+
+Verified.parameters = {
+ layout: "fullscreen"
+};
+
+export const Error = () => (
+
+
+
+
+
+
+
+);
+
+Error.parameters = {
+ layout: "fullscreen"
+};
diff --git a/src/react-components/auth/VerifyModalContainer.js b/src/react-components/auth/VerifyModalContainer.js
new file mode 100644
index 000000000..be7430c75
--- /dev/null
+++ b/src/react-components/auth/VerifyModalContainer.js
@@ -0,0 +1,61 @@
+import React, { useState, useContext, useEffect } from "react";
+import { AuthContext } from "./AuthContext";
+import { VerifyModal, VerificationError, EmailVerified, EmailVerifying } from "./VerifyModal";
+
+const VerificationStep = {
+ verifying: "verifying",
+ complete: "complete",
+ error: "error"
+};
+
+function useVerify() {
+ const [step, setStep] = useState(VerificationStep.verifying);
+ const [error, setError] = useState();
+ const { verify } = useContext(AuthContext);
+
+ useEffect(
+ () => {
+ const verifyAsync = async () => {
+ try {
+ const qs = new URLSearchParams(location.search);
+
+ const authParams = {
+ topic: qs.get("auth_topic"),
+ token: qs.get("auth_token"),
+ origin: qs.get("auth_origin"),
+ payload: qs.get("auth_payload")
+ };
+
+ await verify(authParams);
+ setStep(VerificationStep.complete);
+ } catch (error) {
+ setStep(VerificationStep.error);
+ setError(error);
+ }
+ };
+
+ verifyAsync();
+ },
+ [verify]
+ );
+
+ return { step, error };
+}
+
+export function VerifyModalContainer() {
+ const { step, error } = useVerify();
+
+ let content;
+
+ if (step === VerificationStep.error) {
+ content = ;
+ } else if (step === VerificationStep.complete) {
+ const qs = new URLSearchParams(location.search);
+ const origin = qs.get("auth_origin");
+ content = ;
+ } else {
+ content = ;
+ }
+
+ return {content};
+}
diff --git a/src/react-components/auth/VerifyPage.js b/src/react-components/auth/VerifyPage.js
deleted file mode 100644
index 5355235f6..000000000
--- a/src/react-components/auth/VerifyPage.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { useState, useContext, useEffect } from "react";
-import PropTypes from "prop-types";
-import { Page } from "../layout/Page";
-import styles from "./SignInPage.scss";
-import { Loader } from "../misc/Loader";
-import { AuthContext } from "../auth/AuthContext";
-import configs from "../../utils/configs";
-
-const VerificationStep = {
- verifying: "verifying",
- complete: "complete",
- error: "error"
-};
-
-function useVerify() {
- const [step, setStep] = useState(VerificationStep.verifying);
- const [error, setError] = useState();
- const auth = useContext(AuthContext);
-
- useEffect(() => {
- const verifyAsync = async () => {
- try {
- const qs = new URLSearchParams(location.search);
-
- const authParams = {
- topic: qs.get("auth_topic"),
- token: qs.get("auth_token"),
- origin: qs.get("auth_origin"),
- payload: qs.get("auth_payload")
- };
-
- await auth.verify(authParams);
- setStep(VerificationStep.complete);
- } catch (error) {
- setStep(VerificationStep.error);
- setError(error);
- }
- };
-
- verifyAsync();
- }, []);
-
- return { step, error };
-}
-
-function EmailVerifying() {
- return (
-
-
Email Verifying
-
-
- );
-}
-
-function EmailVerified() {
- const qs = new URLSearchParams(location.search);
- const origin = qs.get("auth_origin");
-
- return (
-
-
Verification Complete
- Please close this browser window and return to {origin}.
-
- );
-}
-
-function VerificationError({ error }) {
- return (
-
-
Error Verifying Email
- {(error && error.message) || "Unknown Error"}
-
- );
-}
-
-VerificationError.propTypes = {
- error: PropTypes.object
-};
-
-export function VerifyPage() {
- const { step, error } = useVerify();
-
- let content;
-
- if (step === VerificationStep.error) {
- content = ;
- } else if (step === VerificationStep.complete) {
- content = ;
- } else {
- content = ;
- }
-
- return (
- {content}
- );
-}
diff --git a/src/react-components/avatar-editor.js b/src/react-components/avatar-editor.js
index c7400ba4f..91e8f5700 100644
--- a/src/react-components/avatar-editor.js
+++ b/src/react-components/avatar-editor.js
@@ -45,10 +45,8 @@ const fetchAvatar = async avatarId => {
export default class AvatarEditor extends Component {
static propTypes = {
avatarId: PropTypes.string,
- onSignIn: PropTypes.func,
onSave: PropTypes.func,
onClose: PropTypes.func,
- signedIn: PropTypes.bool,
hideDelete: PropTypes.bool,
debug: PropTypes.bool,
className: PropTypes.string
@@ -453,7 +451,7 @@ export default class AvatarEditor extends Component {
- ) : this.props.signedIn ? (
+ ) : (
- ) : (
-
-
-
)}
);
diff --git a/src/react-components/home/HomePage.js b/src/react-components/home/HomePage.js
index 436424921..ae1559cba 100644
--- a/src/react-components/home/HomePage.js
+++ b/src/react-components/home/HomePage.js
@@ -3,7 +3,6 @@ import { FormattedMessage } from "react-intl";
import classNames from "classnames";
import configs from "../../utils/configs";
import IfFeature from "../if-feature";
-import { Page } from "../layout/Page";
import { CreateRoomButton } from "./CreateRoomButton";
import { PWAButton } from "./PWAButton";
import { useFavoriteRooms } from "./useFavoriteRooms";
@@ -14,6 +13,7 @@ import { AuthContext } from "../auth/AuthContext";
import { createAndRedirectToNewHub } from "../../utils/phoenix-utils";
import { MediaGrid } from "./MediaGrid";
import { RoomTile } from "./RoomTile";
+import { PageContainer } from "../layout/PageContainer";
export function HomePage() {
const auth = useContext(AuthContext);
@@ -57,7 +57,7 @@ export function HomePage() {
});
return (
-