diff --git a/src/core/client/stream/components/Stream.tsx b/src/core/client/stream/components/Stream.tsx
index ef1ed5bda..7f1ea183c 100644
--- a/src/core/client/stream/components/Stream.tsx
+++ b/src/core/client/stream/components/Stream.tsx
@@ -7,6 +7,7 @@ import { Button, Flex } from "talk-ui/components";
import CommentContainer from "../containers/CommentContainer";
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
import ReplyListContainer from "../containers/ReplyListContainer";
+import UserBoxContainer from "../containers/UserBoxContainer";
import * as styles from "./Stream.css";
export interface StreamProps {
@@ -20,7 +21,8 @@ export interface StreamProps {
const Stream: StatelessComponent
= props => {
return (
-
+
);
};
diff --git a/src/core/client/stream/components/UserBox.css b/src/core/client/stream/components/UserBox.css
new file mode 100644
index 000000000..272b33bdb
--- /dev/null
+++ b/src/core/client/stream/components/UserBox.css
@@ -0,0 +1,3 @@
+.joinText {
+ margin-right: var(--spacing-unit);
+}
diff --git a/src/core/client/stream/components/UserBox.tsx b/src/core/client/stream/components/UserBox.tsx
new file mode 100644
index 000000000..cff9dcb0a
--- /dev/null
+++ b/src/core/client/stream/components/UserBox.tsx
@@ -0,0 +1,40 @@
+import React, { StatelessComponent } from "react";
+
+import { Button, Flex, Typography } from "talk-ui/components";
+
+import * as styles from "./UserBox.css";
+
+export interface UserBoxProps {
+ onSignIn: () => void;
+ onRegister: () => void;
+}
+
+const UserBox: StatelessComponent = props => {
+ return (
+
+
+ Join the conversation
+
+
+ |
+
+
+
+
+ );
+};
+
+export default UserBox;
diff --git a/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap b/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap
index 5da869b02..84cb64ea1 100644
--- a/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap
+++ b/src/core/client/stream/components/__snapshots__/Stream.spec.tsx.snap
@@ -1,9 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders correctly 1`] = `
-
+
@@ -55,13 +58,16 @@ exports[`renders correctly 1`] = `
/>
-
+
`;
exports[`when there is more disables load more button 1`] = `
-
+
@@ -127,13 +133,16 @@ exports[`when there is more disables load more button 1`] = `
-
+
`;
exports[`when there is more renders a load more button 1`] = `
-
+
@@ -199,5 +208,5 @@ exports[`when there is more renders a load more button 1`] = `
-
+
`;
diff --git a/src/core/client/stream/containers/UserBoxContainer.tsx b/src/core/client/stream/containers/UserBoxContainer.tsx
new file mode 100644
index 000000000..2e780fd61
--- /dev/null
+++ b/src/core/client/stream/containers/UserBoxContainer.tsx
@@ -0,0 +1,72 @@
+import * as React from "react";
+import { Component } from "react";
+
+import { graphql, withLocalStateContainer } from "talk-framework/lib/relay";
+import { UserBoxContainerLocal as Local } from "talk-stream/__generated__/UserBoxContainerLocal.graphql";
+import {
+ SetAuthPopupStateMutation,
+ ShowAuthPopupMutation,
+ withSetAuthPopupStateMutation,
+ withShowAuthPopupMutation,
+} from "talk-stream/mutations";
+import { Popup } from "talk-ui/components";
+
+import UserBox from "../components/UserBox";
+
+interface InnerProps {
+ local: Local;
+ showAuthPopup: ShowAuthPopupMutation;
+ setAuthPopupState: SetAuthPopupStateMutation;
+}
+
+class UserBoxContainer extends Component {
+ private handleFocus = () => this.props.setAuthPopupState({ focus: true });
+ private handleBlur = () => this.props.setAuthPopupState({ focus: false });
+ private handleClose = () => this.props.setAuthPopupState({ open: false });
+ private handleSignIn = () => this.props.showAuthPopup({ view: "SIGN_IN" });
+ private handleRegister = () => this.props.showAuthPopup({ view: "SIGN_UP" });
+
+ public render() {
+ const {
+ local: {
+ authPopup: { open, focus, view },
+ },
+ } = this.props;
+ return (
+ <>
+
+
+ >
+ );
+ }
+}
+
+const enhanced = withSetAuthPopupStateMutation(
+ withShowAuthPopupMutation(
+ withLocalStateContainer(
+ graphql`
+ fragment UserBoxContainerLocal on Local {
+ authPopup {
+ open
+ focus
+ view
+ }
+ }
+ `
+ )(UserBoxContainer)
+ )
+);
+
+export default enhanced;
diff --git a/src/core/client/stream/local/constants.ts b/src/core/client/stream/local/constants.ts
index 599c79497..d4f4bd473 100644
--- a/src/core/client/stream/local/constants.ts
+++ b/src/core/client/stream/local/constants.ts
@@ -4,3 +4,6 @@
export const NETWORK_TYPE = "Network";
export const NETWORK_ID = "client:root.local.network";
+
+export const AUTH_POPUP_TYPE = "AuthPopup";
+export const AUTH_POPUP_ID = "client:root.local.authPopup";
diff --git a/src/core/client/stream/local/initLocalState.ts b/src/core/client/stream/local/initLocalState.ts
index 434c60e32..827e90c50 100644
--- a/src/core/client/stream/local/initLocalState.ts
+++ b/src/core/client/stream/local/initLocalState.ts
@@ -7,7 +7,12 @@ import {
LOCAL_TYPE,
} from "talk-framework/lib/relay";
-import { NETWORK_ID, NETWORK_TYPE } from "./constants";
+import {
+ AUTH_POPUP_ID,
+ AUTH_POPUP_TYPE,
+ NETWORK_ID,
+ NETWORK_TYPE,
+} from "./constants";
/**
* Initializes the local state, before we start the App.
@@ -18,6 +23,7 @@ export default async function initLocalState(environment: Environment) {
// Create the Local Record which is the Root for the client states.
const localRecord = createAndRetain(environment, s, LOCAL_ID, LOCAL_TYPE);
+ root.setLinkedRecord(localRecord, "local");
// Parse query params
const query = qs.parse(location.search);
@@ -47,6 +53,17 @@ export default async function initLocalState(environment: Environment) {
);
networkRecord.setValue(false, "isOffline");
localRecord.setLinkedRecord(networkRecord, "network");
- root.setLinkedRecord(localRecord, "local");
+
+ // Create authPopup Record
+ const authPopupRecord = createAndRetain(
+ environment,
+ s,
+ AUTH_POPUP_ID,
+ AUTH_POPUP_TYPE
+ );
+ authPopupRecord.setValue(false, "open");
+ authPopupRecord.setValue(false, "focus");
+ authPopupRecord.setValue("", "href");
+ localRecord.setLinkedRecord(authPopupRecord, "authPopup");
});
}
diff --git a/src/core/client/stream/local/local.graphql b/src/core/client/stream/local/local.graphql
index 9a2991607..52604d35b 100644
--- a/src/core/client/stream/local/local.graphql
+++ b/src/core/client/stream/local/local.graphql
@@ -3,11 +3,24 @@ type Network {
isOffline: Boolean!
}
+enum View {
+ SIGN_UP
+ SIGN_IN
+ FORGOT_PASSWORD
+}
+
+type AuthPopup {
+ open: Boolean!
+ focus: Boolean!
+ view: View
+}
+
type Local {
network: Network!
assetID: String
- commentID: String
assetURL: String
+ commentID: String
+ authPopup: AuthPopup!
}
extend type Query {
diff --git a/src/core/client/stream/mutations/SetAuthPopupStateMutation.spec.ts b/src/core/client/stream/mutations/SetAuthPopupStateMutation.spec.ts
new file mode 100644
index 000000000..2e03b736c
--- /dev/null
+++ b/src/core/client/stream/mutations/SetAuthPopupStateMutation.spec.ts
@@ -0,0 +1,52 @@
+import { Environment, RecordSource } from "relay-runtime";
+
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+
+import { AUTH_POPUP_ID, AUTH_POPUP_TYPE } from "../local";
+import { commit } from "./SetAuthPopupStateMutation";
+
+let environment: Environment;
+const source: RecordSource = new RecordSource();
+
+beforeAll(() => {
+ environment = createRelayEnvironment({
+ source,
+ initLocalState: (localRecord, sourceProxy) => {
+ const record = sourceProxy.create(AUTH_POPUP_ID, AUTH_POPUP_TYPE);
+ record.setValue(false, "open");
+ record.setValue(false, "focus");
+ localRecord.setLinkedRecord(record, "authPopup");
+ },
+ });
+});
+
+it("sets state", () => {
+ commit(environment, { open: true, focus: false });
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(false);
+});
+
+it("sets state", () => {
+ commit(environment, {
+ open: false,
+ focus: true,
+ href: "https://coralproject.net",
+ });
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(false);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.href).toEqual("https://coralproject.net");
+});
+
+it("keep previous state", () => {
+ commit(environment, { open: false, focus: true });
+ commit(environment, {});
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(false);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(true);
+});
+
+it("change only one", () => {
+ commit(environment, { open: false, focus: true });
+ commit(environment, { open: true });
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(true);
+});
diff --git a/src/core/client/stream/mutations/SetAuthPopupStateMutation.ts b/src/core/client/stream/mutations/SetAuthPopupStateMutation.ts
new file mode 100644
index 000000000..f3984a69a
--- /dev/null
+++ b/src/core/client/stream/mutations/SetAuthPopupStateMutation.ts
@@ -0,0 +1,38 @@
+import { commitLocalUpdate, Environment } from "relay-runtime";
+
+import { createMutationContainer } from "talk-framework/lib/relay";
+
+import { AUTH_POPUP_ID } from "../local";
+
+export interface SetAuthPopupStateInput {
+ open?: boolean;
+ focus?: boolean;
+ href?: string;
+}
+
+export type SetAuthPopupStateMutation = (
+ input: SetAuthPopupStateInput
+) => Promise;
+
+export async function commit(
+ environment: Environment,
+ input: SetAuthPopupStateInput
+) {
+ return commitLocalUpdate(environment, store => {
+ const record = store.get(AUTH_POPUP_ID)!;
+ if (input.open !== undefined) {
+ record.setValue(input.open, "open");
+ }
+ if (input.focus !== undefined) {
+ record.setValue(input.focus, "focus");
+ }
+ if (input.href !== undefined) {
+ record.setValue(input.href, "href");
+ }
+ });
+}
+
+export const withSetAuthPopupStateMutation = createMutationContainer(
+ "setAuthPopupState",
+ commit
+);
diff --git a/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts b/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts
index 1a6196c45..2ca97bea9 100644
--- a/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts
+++ b/src/core/client/stream/mutations/SetNetworkStatusMutation.spec.ts
@@ -3,7 +3,6 @@ import { Environment, RecordSource } from "relay-runtime";
import { createRelayEnvironment } from "talk-framework/testHelpers";
import { NETWORK_ID, NETWORK_TYPE } from "../local";
-
import { commit } from "./SetNetworkStatusMutation";
let environment: Environment;
diff --git a/src/core/client/stream/mutations/ShowAuthPopupMutation.spec.ts b/src/core/client/stream/mutations/ShowAuthPopupMutation.spec.ts
new file mode 100644
index 000000000..17d612d89
--- /dev/null
+++ b/src/core/client/stream/mutations/ShowAuthPopupMutation.spec.ts
@@ -0,0 +1,42 @@
+import { Environment, RecordSource } from "relay-runtime";
+
+import { createRelayEnvironment } from "talk-framework/testHelpers";
+
+import { AUTH_POPUP_ID, AUTH_POPUP_TYPE } from "../local";
+import { commit } from "./ShowAuthPopupMutation";
+
+let environment: Environment;
+const source: RecordSource = new RecordSource();
+
+beforeAll(() => {
+ environment = createRelayEnvironment({
+ source,
+ initLocalState: (localRecord, sourceProxy) => {
+ const record = sourceProxy.create(AUTH_POPUP_ID, AUTH_POPUP_TYPE);
+ record.setValue(false, "open");
+ record.setValue(false, "focus");
+ localRecord.setLinkedRecord(record, "authPopup");
+ },
+ });
+});
+
+it("opens popup", () => {
+ commit(environment, { view: "SIGN_IN" });
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(false);
+ expect(source.get(AUTH_POPUP_ID)!.view).toEqual("SIGN_IN");
+});
+
+it("focuses popup", () => {
+ commit(environment, { view: "SIGN_IN" });
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.view).toEqual("SIGN_IN");
+});
+
+it("only change view when opened and focused", () => {
+ commit(environment, { view: "FORGOT_PASSWORD" });
+ expect(source.get(AUTH_POPUP_ID)!.open).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.focus).toEqual(true);
+ expect(source.get(AUTH_POPUP_ID)!.view).toEqual("FORGOT_PASSWORD");
+});
diff --git a/src/core/client/stream/mutations/ShowAuthPopupMutation.ts b/src/core/client/stream/mutations/ShowAuthPopupMutation.ts
new file mode 100644
index 000000000..2d6936d53
--- /dev/null
+++ b/src/core/client/stream/mutations/ShowAuthPopupMutation.ts
@@ -0,0 +1,36 @@
+import { commitLocalUpdate, Environment } from "relay-runtime";
+
+import { createMutationContainer } from "talk-framework/lib/relay";
+
+import { AUTH_POPUP_ID } from "../local";
+
+export interface ShowAuthPopupInput {
+ view: "SIGN_IN" | "SIGN_UP" | "FORGOT_PASSWORD";
+}
+
+export type ShowAuthPopupMutation = (
+ input: ShowAuthPopupInput
+) => Promise;
+
+export async function commit(
+ environment: Environment,
+ input: ShowAuthPopupInput
+) {
+ return commitLocalUpdate(environment, store => {
+ const record = store.get(AUTH_POPUP_ID)!;
+ record.setValue(input.view, "view");
+ if (!record.getValue("open")) {
+ record.setValue(true, "open");
+ return;
+ }
+ if (!record.getValue("focus")) {
+ record.setValue(true, "focus");
+ return;
+ }
+ });
+}
+
+export const withShowAuthPopupMutation = createMutationContainer(
+ "showAuthPopup",
+ commit
+);
diff --git a/src/core/client/stream/mutations/index.ts b/src/core/client/stream/mutations/index.ts
index 1b71335e7..324fddbdc 100644
--- a/src/core/client/stream/mutations/index.ts
+++ b/src/core/client/stream/mutations/index.ts
@@ -13,3 +13,11 @@ export {
SetCommentIDMutation,
SetCommentIDInput,
} from "./SetCommentIDMutation";
+export {
+ withShowAuthPopupMutation,
+ ShowAuthPopupMutation,
+} from "./ShowAuthPopupMutation";
+export {
+ withSetAuthPopupStateMutation,
+ SetAuthPopupStateMutation,
+} from "./SetAuthPopupStateMutation";
diff --git a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap
index 619f3269d..95be74360 100644
--- a/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap
+++ b/src/core/client/stream/test/__snapshots__/loadMore.spec.tsx.snap
@@ -5,10 +5,49 @@ exports[`loads more comments 1`] = `
className="Flex-root App-root Flex-justifyCenter Flex-alignCenter"
>
+
+
+ Join the conversation
+
+
+ |
+
+
+
+