aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/features/onboarding/actionTypes.js39
-rw-r--r--app/features/onboarding/actions.js70
-rw-r--r--app/features/onboarding/components/ConferenceURLSpotlight.js70
-rw-r--r--app/features/onboarding/components/EmailSettingSpotlight.js69
-rw-r--r--app/features/onboarding/components/NameSettingSpotlight.js69
-rw-r--r--app/features/onboarding/components/Onboarding.js66
-rw-r--r--app/features/onboarding/components/OnboardingModal.js89
-rw-r--r--app/features/onboarding/components/ServerSettingSpotlight.js69
-rw-r--r--app/features/onboarding/components/SettingsDrawerSpotlight.js68
-rw-r--r--app/features/onboarding/components/StartMutedTogglesSpotlight.js78
-rw-r--r--app/features/onboarding/components/index.js10
-rw-r--r--app/features/onboarding/constants.js35
-rw-r--r--app/features/onboarding/index.js7
-rw-r--r--app/features/onboarding/middleware.js37
-rw-r--r--app/features/onboarding/reducer.js66
-rw-r--r--app/features/redux/middleware.js2
-rw-r--r--app/features/redux/reducers.js2
-rw-r--r--app/features/redux/store.js1
-rw-r--r--app/features/settings/components/SettingsButton.js8
-rw-r--r--app/features/settings/components/SettingsDrawer.js70
-rw-r--r--app/features/welcome/components/Welcome.js36
-rw-r--r--app/images/onboarding.pngbin0 -> 5935 bytes
-rw-r--r--app/index.js6
-rw-r--r--package-lock.json127
-rw-r--r--package.json2
-rw-r--r--webpack.config.js4
26 files changed, 1048 insertions, 52 deletions
diff --git a/app/features/onboarding/actionTypes.js b/app/features/onboarding/actionTypes.js
new file mode 100644
index 0000000..58dbe4d
--- /dev/null
+++ b/app/features/onboarding/actionTypes.js
@@ -0,0 +1,39 @@
+/**
+ * The type of (redux) action that continues the onboarding by processing
+ * the next step.
+ *
+ * {
+ * type: CONTINUE_ONBOARDING
+ * }
+ */
+export const CONTINUE_ONBOARDING = Symbol('CONTINUE_ONBOARDING');
+
+/**
+ * The type of (redux) action that sets active onboarding.
+ *
+ * {
+ * type: SET_ACTIVE_ONBOARDING,
+ * name: string,
+ * section: string
+ * }
+ */
+export const SET_ACTIVE_ONBOARDING = Symbol('SET_ACTIVE_ONBOARDING');
+
+/**
+ * The type of (redux) action that starts Onboarding.
+ *
+ * {
+ * type: START_ONBOARDING,
+ * section: string
+ * }
+ */
+export const START_ONBOARDING = Symbol('START_ONBOARDING');
+
+/**
+ * The type of (redux) action that skips all onboarding.
+ *
+ * {
+ * type: SKIP_ONBOARDING
+ * }
+ */
+export const SKIP_ONBOARDING = Symbol('SKIP_ONBOARDING');
diff --git a/app/features/onboarding/actions.js b/app/features/onboarding/actions.js
new file mode 100644
index 0000000..30f6d42
--- /dev/null
+++ b/app/features/onboarding/actions.js
@@ -0,0 +1,70 @@
+// @flow
+
+import {
+ CONTINUE_ONBOARDING,
+ SET_ACTIVE_ONBOARDING,
+ SKIP_ONBOARDING,
+ START_ONBOARDING
+} from './actionTypes';
+
+/**
+ * Continues the onboarding procedure by activating the next step of the current
+ * section.
+ *
+ * @returns {{
+ * type: CONTINUE_ONBOARDING
+ * }}
+ */
+export function continueOnboarding() {
+ return {
+ type: CONTINUE_ONBOARDING
+ };
+}
+
+/**
+ * Set active onboarding.
+ *
+ * @param {string} name - Name of onboarding component.
+ * @param {string} section - Onboarding section.
+ * @returns {{
+ * type: SET_ACTIVE_ONBOARDING,
+ * name: string,
+ * section: string
+ * }}
+ */
+export function setActiveOnboarding(name: string, section: string) {
+ return {
+ type: SET_ACTIVE_ONBOARDING,
+ name,
+ section
+ };
+}
+
+/**
+ * Skips onboarding.
+ *
+ * @returns {{
+ * type: SKIP_ONBOARDING
+ * }}
+ */
+export function skipOnboarding() {
+ return {
+ type: SKIP_ONBOARDING
+ };
+}
+
+/**
+ * Start onboarding.
+ *
+ * @param {string} section - Onboarding section.
+ * @returns {{
+ * type: START_ONBOARDING,
+ * section: string
+ * }}
+ */
+export function startOnboarding(section: string) {
+ return {
+ type: START_ONBOARDING,
+ section
+ };
+}
diff --git a/app/features/onboarding/components/ConferenceURLSpotlight.js b/app/features/onboarding/components/ConferenceURLSpotlight.js
new file mode 100644
index 0000000..178e114
--- /dev/null
+++ b/app/features/onboarding/components/ConferenceURLSpotlight.js
@@ -0,0 +1,70 @@
+// @flow
+
+import { Spotlight } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Conference URL Spotlight Component.
+ */
+class ConferenceURLSpotlight extends Component<Props, *> {
+ /**
+ * Initializes a new {@code ComponentURLSpotlight} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Spotlight
+ actions = { [
+ {
+ onClick: this._next,
+ text: 'Next'
+ }
+ ] }
+ dialogPlacement = 'bottom center'
+ target = { 'conference-url' } >
+ Enter the name (or full URL) of the room you want to join. You
+ may make a name up, just let others know so they enter the same
+ name.
+ </Spotlight>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component.
+ *
+ * @returns {void}
+ */
+ _next() {
+ this.props.dispatch(continueOnboarding());
+ }
+}
+
+export default connect()(ConferenceURLSpotlight);
+
diff --git a/app/features/onboarding/components/EmailSettingSpotlight.js b/app/features/onboarding/components/EmailSettingSpotlight.js
new file mode 100644
index 0000000..ad1237e
--- /dev/null
+++ b/app/features/onboarding/components/EmailSettingSpotlight.js
@@ -0,0 +1,69 @@
+// @flow
+
+import { Spotlight } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Email Setting Spotlight Component.
+ */
+class EmailSettingSpotlight extends Component<Props, *> {
+ /**
+ * Initializes a new {@code EmailSettingSpotlight} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Spotlight
+ actions = { [
+ {
+ onClick: this._next,
+ text: 'Next'
+ }
+ ] }
+ dialogPlacement = 'left top'
+ target = { 'email-setting' } >
+ The email you enter here will be part of your user profile and
+ it will be used to display your stored avatar in gravatar.com .
+ </Spotlight>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component.
+ *
+ * @returns {void}
+ */
+ _next() {
+ this.props.dispatch(continueOnboarding());
+ }
+}
+
+export default connect()(EmailSettingSpotlight);
+
diff --git a/app/features/onboarding/components/NameSettingSpotlight.js b/app/features/onboarding/components/NameSettingSpotlight.js
new file mode 100644
index 0000000..69abec0
--- /dev/null
+++ b/app/features/onboarding/components/NameSettingSpotlight.js
@@ -0,0 +1,69 @@
+// @flow
+
+import { Spotlight } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Name Setting Spotlight Component.
+ */
+class NameSettingSpotlight extends Component<Props, *> {
+ /**
+ * Initializes a new {@code NameSettingSpotlight} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Spotlight
+ actions = { [
+ {
+ onClick: this._next,
+ text: 'Next'
+ }
+ ] }
+ dialogPlacement = 'left top'
+ target = { 'name-setting' } >
+ This will be your display name, others will see you with this
+ name.
+ </Spotlight>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component.
+ *
+ * @returns {void}
+ */
+ _next() {
+ this.props.dispatch(continueOnboarding());
+ }
+}
+
+export default connect()(NameSettingSpotlight);
+
diff --git a/app/features/onboarding/components/Onboarding.js b/app/features/onboarding/components/Onboarding.js
new file mode 100644
index 0000000..31ea1d4
--- /dev/null
+++ b/app/features/onboarding/components/Onboarding.js
@@ -0,0 +1,66 @@
+// @flow
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { onboardingComponents, onboardingSteps } from '../../onboarding';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+
+ /**
+ * Onboarding Section.
+ */
+ section: string;
+
+ /**
+ * Active Onboarding.
+ */
+ _activeOnboarding: string;
+};
+
+/**
+ * Onboarding Component Entry Point.
+ */
+class Onboarding extends Component<Props, *> {
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ const { section, _activeOnboarding } = this.props;
+ const steps = onboardingSteps[section];
+
+ if (_activeOnboarding && steps.includes(_activeOnboarding)) {
+ const ActiveOnboarding = onboardingComponents[_activeOnboarding];
+
+ return <ActiveOnboarding />;
+ }
+
+ return null;
+ }
+}
+
+/**
+ * Maps (parts of) the redux state to the React props.
+ *
+ * @param {Object} state - The redux state.
+ * @returns {{
+ * _activeOnboarding: string
+ * }}
+ */
+function _mapStateToProps(state: Object) {
+ return {
+ _activeOnboarding: state.onboarding.activeOnboarding
+ };
+}
+
+export default connect(_mapStateToProps)(Onboarding);
+
+
diff --git a/app/features/onboarding/components/OnboardingModal.js b/app/features/onboarding/components/OnboardingModal.js
new file mode 100644
index 0000000..fe38c2d
--- /dev/null
+++ b/app/features/onboarding/components/OnboardingModal.js
@@ -0,0 +1,89 @@
+// @flow
+
+import { Modal } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import OnboardingModalImage from '../../../images/onboarding.png';
+
+import config from '../../config';
+
+import { skipOnboarding, continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Onboarding Modal Component.
+ */
+class OnboardingModal extends Component<Props, *> {
+ /**
+ * Initializes a new {@code OnboardingModal} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ // Bind event handlers.
+ this._skip = this._skip.bind(this);
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Modal
+ actions = { [
+ {
+ onClick: this._next,
+ text: 'Start Tour'
+ },
+ {
+ onClick: this._skip,
+ text: 'Skip'
+ }
+ ] }
+ heading = { `Welcome to ${config.appName}` }
+ image = { OnboardingModalImage } >
+ <p> Let us show you around!</p>
+ </Modal>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component.
+ *
+ * @returns {void}
+ */
+ _next() {
+ this.props.dispatch(continueOnboarding());
+ }
+
+ _skip: (*) => void;
+
+ /**
+ * Skips all the onboardings.
+ *
+ * @returns {void}
+ */
+ _skip() {
+ this.props.dispatch(skipOnboarding());
+ }
+
+}
+
+export default connect()(OnboardingModal);
diff --git a/app/features/onboarding/components/ServerSettingSpotlight.js b/app/features/onboarding/components/ServerSettingSpotlight.js
new file mode 100644
index 0000000..dbd6eda
--- /dev/null
+++ b/app/features/onboarding/components/ServerSettingSpotlight.js
@@ -0,0 +1,69 @@
+// @flow
+
+import { Spotlight } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Server Setting Spotlight Component.
+ */
+class ServerSettingSpotlight extends Component<Props, *> {
+ /**
+ * Initializes a new {@code ServerSettingSpotlight} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Spotlight
+ actions = { [
+ {
+ onClick: this._next,
+ text: 'Next'
+ }
+ ] }
+ dialogPlacement = 'left top'
+ target = { 'server-setting' } >
+ This will be the server where your conferences will take place.
+ You can use your own, but you don't need to!
+ </Spotlight>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component.
+ *
+ * @returns {void}
+ */
+ _next() {
+ this.props.dispatch(continueOnboarding());
+ }
+}
+
+export default connect()(ServerSettingSpotlight);
+
diff --git a/app/features/onboarding/components/SettingsDrawerSpotlight.js b/app/features/onboarding/components/SettingsDrawerSpotlight.js
new file mode 100644
index 0000000..15ad601
--- /dev/null
+++ b/app/features/onboarding/components/SettingsDrawerSpotlight.js
@@ -0,0 +1,68 @@
+// @flow
+
+import { Spotlight } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { openDrawer } from '../../navbar';
+import { SettingsDrawer } from '../../settings';
+
+import { continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Settings Drawer Spotlight Component.
+ */
+class SettingsDrawerSpotlight extends Component<Props, *> {
+ /**
+ * Initializes a new {@code SettingsDrawerSpotlight} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Spotlight
+ dialogPlacement = 'right top'
+ target = { 'settings-drawer-button' }
+ targetOnClick = { this._next }>
+ Click here to open the settings drawer.
+ </Spotlight>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component and opens Settings Drawer and shows
+ * onboarding.
+ *
+ * @returns {void}
+ */
+ _next() {
+ this.props.dispatch(openDrawer(SettingsDrawer));
+ this.props.dispatch(continueOnboarding());
+ }
+}
+
+export default connect()(SettingsDrawerSpotlight);
+
diff --git a/app/features/onboarding/components/StartMutedTogglesSpotlight.js b/app/features/onboarding/components/StartMutedTogglesSpotlight.js
new file mode 100644
index 0000000..aad9af5
--- /dev/null
+++ b/app/features/onboarding/components/StartMutedTogglesSpotlight.js
@@ -0,0 +1,78 @@
+// @flow
+
+import { Spotlight } from '@atlaskit/onboarding';
+
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import type { Dispatch } from 'redux';
+
+import { closeDrawer } from '../../navbar';
+
+import { continueOnboarding } from '../actions';
+
+type Props = {
+
+ /**
+ * Redux dispatch.
+ */
+ dispatch: Dispatch<*>;
+};
+
+/**
+ * Start Muted Toggles Spotlight Component.
+ */
+class StartMutedTogglesSpotlight extends Component<Props, *> {
+ /**
+ * Initializes a new {@code StartMutedTogglesSpotlight} instance.
+ *
+ * @inheritdoc
+ */
+ constructor(props: Props) {
+ super(props);
+
+ this._next = this._next.bind(this);
+ }
+
+ /**
+ * Render function of component.
+ *
+ * @returns {ReactElement}
+ */
+ render() {
+ return (
+ <Spotlight
+ actions = { [
+ {
+ onClick: this._next,
+ text: 'Next'
+ }
+ ] }
+ dialogPlacement = 'left top'
+ target = { 'start-muted-toggles' } >
+ You can toggle if you want to start with your audio or video
+ muted here. This will be applied to all conferences.
+ </Spotlight>
+ );
+ }
+
+ _next: (*) => void;
+
+ /**
+ * Close the spotlight component.
+ *
+ * @returns {void}
+ */
+ _next() {
+ const { dispatch } = this.props;
+
+ dispatch(continueOnboarding());
+
+ // FIXME: find a better way to do this.
+ setTimeout(() => {
+ dispatch(closeDrawer());
+ }, 300);
+ }
+}
+
+export default connect()(StartMutedTogglesSpotlight);
+
diff --git a/app/features/onboarding/components/index.js b/app/features/onboarding/components/index.js
new file mode 100644
index 0000000..8b3dded
--- /dev/null
+++ b/app/features/onboarding/components/index.js
@@ -0,0 +1,10 @@
+export { default as ConferenceURLSpotlight } from './ConferenceURLSpotlight';
+export { default as EmailSettingSpotlight } from './EmailSettingSpotlight';
+export { default as NameSettingSpotlight } from './NameSettingSpotlight';
+export { default as Onboarding } from './Onboarding';
+export { default as OnboardingModal } from './OnboardingModal';
+export { default as ServerSettingSpotlight } from './ServerSettingSpotlight';
+export { default as SettingsDrawerSpotlight } from './SettingsDrawerSpotlight';
+export {
+ default as StartMutedTogglesSpotlight
+} from './StartMutedTogglesSpotlight';
diff --git a/app/features/onboarding/constants.js b/app/features/onboarding/constants.js
new file mode 100644
index 0000000..34cb1ed
--- /dev/null
+++ b/app/features/onboarding/constants.js
@@ -0,0 +1,35 @@
+// @flow
+
+import {
+ OnboardingModal,
+ ConferenceURLSpotlight,
+ SettingsDrawerSpotlight,
+ NameSettingSpotlight,
+ EmailSettingSpotlight,
+ ServerSettingSpotlight,
+ StartMutedTogglesSpotlight
+} from './components';
+
+export const onboardingSteps = {
+ 'welcome-page': [
+ 'onboarding-modal',
+ 'conference-url',
+ 'settings-drawer-button'
+ ],
+ 'settings-drawer': [
+ 'name-setting',
+ 'email-setting',
+ 'server-setting',
+ 'start-muted-toggles'
+ ]
+};
+
+export const onboardingComponents = {
+ 'onboarding-modal': OnboardingModal,
+ 'conference-url': ConferenceURLSpotlight,
+ 'settings-drawer-button': SettingsDrawerSpotlight,
+ 'name-setting': NameSettingSpotlight,
+ 'email-setting': EmailSettingSpotlight,
+ 'server-setting': ServerSettingSpotlight,
+ 'start-muted-toggles': StartMutedTogglesSpotlight
+};
diff --git a/app/features/onboarding/index.js b/app/features/onboarding/index.js
new file mode 100644
index 0000000..8446bcd
--- /dev/null
+++ b/app/features/onboarding/index.js
@@ -0,0 +1,7 @@
+export * from './actions';
+export * from './actionTypes';
+export * from './components';
+export * from './constants';
+
+export { default as middleware } from './middleware';
+export { default as reducer } from './reducer';
diff --git a/app/features/onboarding/middleware.js b/app/features/onboarding/middleware.js
new file mode 100644
index 0000000..36ee2a2
--- /dev/null
+++ b/app/features/onboarding/middleware.js
@@ -0,0 +1,37 @@
+// @flow
+
+import { setActiveOnboarding } from './actions';
+import { CONTINUE_ONBOARDING, START_ONBOARDING } from './actionTypes';
+import { onboardingSteps } from './constants';
+
+export default (store: Object) => (next: Function) => (action: Object) => {
+ const result = next(action);
+ const state = store.getState();
+
+ switch (action.type) {
+ case CONTINUE_ONBOARDING: {
+ const section = state.onboarding.activeOnboardingSection;
+
+ const nextStep = onboardingSteps[section].find(
+ step => !state.onboarding.onboardingShown.includes(step)
+ );
+
+ store.dispatch(setActiveOnboarding(nextStep, nextStep && section));
+ break;
+ }
+
+ case START_ONBOARDING: {
+ const { section } = action;
+ const nextStep = onboardingSteps[section].find(
+ step => !state.onboarding.onboardingShown.includes(step)
+ );
+
+ if (nextStep) {
+ store.dispatch(setActiveOnboarding(nextStep, section));
+ }
+ break;
+ }
+ }
+
+ return result;
+};
diff --git a/app/features/onboarding/reducer.js b/app/features/onboarding/reducer.js
new file mode 100644
index 0000000..59904ce
--- /dev/null
+++ b/app/features/onboarding/reducer.js
@@ -0,0 +1,66 @@
+// @flow
+
+import {
+ CONTINUE_ONBOARDING,
+ SET_ACTIVE_ONBOARDING,
+ SKIP_ONBOARDING
+} from './actionTypes';
+import { onboardingSteps } from './constants';
+
+type State = {
+ activeOnboarding: ?string;
+ activeOnboardingSection: ?string;
+ onboardingShown: Array<string>;
+};
+
+const DEFAULT_STATE = {
+ activeOnboarding: undefined,
+ activeOnboardingSection: undefined,
+ onboardingShown: []
+};
+
+/**
+ * Reduces redux actions for features/onboarding.
+ *
+ * @param {State} state - Current reduced redux state.
+ * @param {Object} action - Action which was dispatched.
+ * @returns {State} - Updated reduced redux state.
+ */
+export default (state: State = DEFAULT_STATE, action: Object) => {
+ switch (action.type) {
+ case CONTINUE_ONBOARDING:
+ return {
+ ...state,
+ activeOnboarding: undefined,
+ onboardingShown:
+
+ // $FlowFixMe
+ state.onboardingShown.concat(state.activeOnboarding)
+ };
+
+ case SET_ACTIVE_ONBOARDING:
+ return {
+ ...state,
+ activeOnboarding: action.name,
+ activeOnboardingSection: action.section
+ };
+
+ case SKIP_ONBOARDING: {
+ // $FlowFixMe
+ const allSteps = [].concat(...Object.values(onboardingSteps));
+
+ return {
+ ...state,
+ activeOnboarding: undefined,
+ activeOnboardingSection: undefined,
+ onboardingShown:
+
+ // $FlowFixMe
+ state.onboardingShown.concat(allSteps)
+ };
+ }
+
+ default:
+ return state;
+ }
+};
diff --git a/app/features/redux/middleware.js b/app/features/redux/middleware.js
index 9938da5..86d7acc 100644
--- a/app/features/redux/middleware.js
+++ b/app/features/redux/middleware.js
@@ -3,10 +3,12 @@
import { applyMiddleware } from 'redux';
import { createLogger } from 'redux-logger';
+import { middleware as onboardingMiddleware } from '../onboarding';
import { middleware as routerMiddleware } from '../router';
import { middleware as settingsMiddleware } from '../settings';
export default applyMiddleware(
+ onboardingMiddleware,
routerMiddleware,
settingsMiddleware,
createLogger()
diff --git a/app/features/redux/reducers.js b/app/features/redux/reducers.js
index 2f532e6..d72eb54 100644
--- a/app/features/redux/reducers.js
+++ b/app/features/redux/reducers.js
@@ -3,12 +3,14 @@
import { combineReducers } from 'redux';
import { reducer as navbarReducer } from '../navbar';
+import { reducer as onboardingReducer } from '../onboarding';
import { reducer as recentListReducer } from '../recent-list';
import { reducer as routerReducer } from '../router';
import { reducer as settingsReducer } from '../settings';
export default combineReducers({
navbar: navbarReducer,
+ onboarding: onboardingReducer,
recentList: recentListReducer,
router: routerReducer,
settings: settingsReducer
diff --git a/app/features/redux/store.js b/app/features/redux/store.js
index 472cdbd..b9469b9 100644
--- a/app/features/redux/store.js
+++ b/app/features/redux/store.js
@@ -11,6 +11,7 @@ const persistConfig = {
key: 'root',
storage: createElectronStorage(),
whitelist: [
+ 'onboarding',
'recentList',
'settings'
]
diff --git a/app/features/settings/components/SettingsButton.js b/app/features/settings/components/SettingsButton.js
index bb0115c..bd80f92 100644
--- a/app/features/settings/components/SettingsButton.js
+++ b/app/features/settings/components/SettingsButton.js
@@ -1,6 +1,7 @@
// @flow
import SettingsIcon from '@atlaskit/icon/glyph/settings';
+import { SpotlightTarget } from '@atlaskit/onboarding';
import * as Mousetrap from 'mousetrap';
import 'mousetrap/plugins/global-bind/mousetrap-global-bind';
@@ -66,8 +67,11 @@ class SettingsButton extends Component<Props, *> {
*/
render() {
return (
- <SettingsIcon
- onClick = { this._onIconClick } />
+ <SpotlightTarget
+ name = 'settings-drawer-button'>
+ <SettingsIcon
+ onClick = { this._onIconClick } />
+ </SpotlightTarget>
);
}
diff --git a/app/features/settings/components/SettingsDrawer.js b/app/features/settings/components/SettingsDrawer.js
index 7990e65..6ecf7eb 100644
--- a/app/features/settings/components/SettingsDrawer.js
+++ b/app/features/settings/components/SettingsDrawer.js
@@ -4,12 +4,14 @@ import Avatar from '@atlaskit/avatar';
import FieldText from '@atlaskit/field-text';
import ArrowLeft from '@atlaskit/icon/glyph/arrow-left';
import { AkCustomDrawer } from '@atlaskit/navigation';
+import { SpotlightTarget } from '@atlaskit/onboarding';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { closeDrawer, DrawerContainer, Logo } from '../../navbar';
+import { Onboarding, startOnboarding } from '../../onboarding';
import { AvatarContainer, SettingsContainer } from '../styled';
import { setEmail, setName } from '../actions';
@@ -64,6 +66,25 @@ class SettingsDrawer extends Component<Props, *> {
}
/**
+ * Start Onboarding once component is mounted.
+ *
+ * NOTE: It automatically checks if the onboarding is shown or not.
+ *
+ * @param {Props} prevProps - Props before component updated.
+ * @returns {void}
+ */
+ componentDidUpdate(prevProps: Props) {
+ if (!prevProps.isOpen && this.props.isOpen) {
+
+ // TODO - Find a better way for this.
+ // Delay for 300ms to let drawer open.
+ setTimeout(() => {
+ this.props.dispatch(startOnboarding('settings-drawer'));
+ }, 300);
+ }
+ }
+
+ /**
* Render function of component.
*
* @returns {ReactElement}
@@ -82,24 +103,37 @@ class SettingsDrawer extends Component<Props, *> {
size = 'xlarge'
src = { this.props._avatarURL } />
</AvatarContainer>
- <form onSubmit = { this._onNameFormSubmit }>
- <FieldText
- label = 'Name'
- onBlur = { this._onNameBlur }
- shouldFitContainer = { true }
- type = 'text'
- value = { this.props._name } />
- </form>
- <form onSubmit = { this._onEmailFormSubmit }>
- <FieldText
- label = 'Email'
- onBlur = { this._onEmailBlur }
- shouldFitContainer = { true }
- type = 'text'
- value = { this.props._email } />
- </form>
- <ServerURLField />
- <StartMutedToggles />
+ <SpotlightTarget
+ name = 'name-setting'>
+ <form onSubmit = { this._onNameFormSubmit }>
+ <FieldText
+ label = 'Name'
+ onBlur = { this._onNameBlur }
+ shouldFitContainer = { true }
+ type = 'text'
+ value = { this.props._name } />
+ </form>
+ </SpotlightTarget>
+ <SpotlightTarget
+ name = 'email-setting'>
+ <form onSubmit = { this._onEmailFormSubmit }>
+ <FieldText
+ label = 'Email'
+ onBlur = { this._onEmailBlur }
+ shouldFitContainer = { true }
+ type = 'text'
+ value = { this.props._email } />
+ </form>
+ </SpotlightTarget>
+ <SpotlightTarget
+ name = 'server-setting'>
+ <ServerURLField />
+ </SpotlightTarget>
+ <SpotlightTarget
+ name = 'start-muted-toggles'>
+ <StartMutedToggles />
+ </SpotlightTarget>
+ <Onboarding section = 'settings-drawer' />
</SettingsContainer>
</DrawerContainer>
</AkCustomDrawer>
diff --git a/app/features/welcome/components/Welcome.js b/app/features/welcome/components/Welcome.js
index 519016d..c9a35f8 100644
--- a/app/features/welcome/components/Welcome.js
+++ b/app/features/welcome/components/Welcome.js
@@ -2,6 +2,7 @@
import Button from '@atlaskit/button';
import { FieldTextStateless } from '@atlaskit/field-text';
+import { SpotlightTarget } from '@atlaskit/onboarding';
import Page from '@atlaskit/page';
import { AtlasKitThemeProvider } from '@atlaskit/theme';
@@ -11,6 +12,7 @@ import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { Navbar } from '../../navbar';
+import { Onboarding, startOnboarding } from '../../onboarding';
import { RecentList } from '../../recent-list';
import { normalizeServerURL } from '../../utils';
@@ -72,6 +74,17 @@ class Welcome extends Component<Props, State> {
}
/**
+ * Start Onboarding once component is mounted.
+ *
+ * NOTE: It autonatically checks if the onboarding is shown or not.
+ *
+ * @returns {void}
+ */
+ componentDidMount() {
+ this.props.dispatch(startOnboarding('welcome-page'));
+ }
+
+ /**
* Render function of component.
*
* @returns {ReactElement}
@@ -84,16 +97,18 @@ class Welcome extends Component<Props, State> {
<AtlasKitThemeProvider mode = 'light'>
<Wrapper>
<Header>
- <Form onSubmit = { this._onFormSubmit }>
- <FieldTextStateless
- autoFocus = { true }
- isInvalid = { state && state.error }
- isLabelHidden = { true }
- onChange = { this._onURLChange }
- shouldFitContainer = { true }
- type = 'text'
- value = { this.state.url } />
- </Form>
+ <SpotlightTarget name = 'conference-url'>
+ <Form onSubmit = { this._onFormSubmit }>
+ <FieldTextStateless
+ autoFocus = { true }
+ isInvalid = { state && state.error }
+ isLabelHidden = { true }
+ onChange = { this._onURLChange }
+ shouldFitContainer = { true }
+ type = 'text'
+ value = { this.state.url } />
+ </Form>
+ </SpotlightTarget>
<Button
appearance = 'primary'
onClick = { this._onJoin }
@@ -104,6 +119,7 @@ class Welcome extends Component<Props, State> {
<Body>
<RecentList />
</Body>
+ <Onboarding section = 'welcome-page' />
</Wrapper>
</AtlasKitThemeProvider>
</Page>
diff --git a/app/images/onboarding.png b/app/images/onboarding.png
new file mode 100644
index 0000000..604cd04
--- /dev/null
+++ b/app/images/onboarding.png
Binary files differ
diff --git a/app/index.js b/app/index.js
index 215fca2..d4dad57 100644
--- a/app/index.js
+++ b/app/index.js
@@ -5,6 +5,8 @@
*/
import '@atlaskit/css-reset';
+import { SpotlightManager } from '@atlaskit/onboarding';
+
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
@@ -28,7 +30,9 @@ class Root extends Component<*> {
<PersistGate
loading = { null }
persistor = { persistor }>
- <App />
+ <SpotlightManager>
+ <App />
+ </SpotlightManager>
</PersistGate>
</Provider>
);
diff --git a/package-lock.json b/package-lock.json
index 22b7b53..bbcc89d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -182,6 +182,23 @@
"react-transition-group": "^2.2.1"
}
},
+ "@atlaskit/modal-dialog": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@atlaskit/modal-dialog/-/modal-dialog-6.0.3.tgz",
+ "integrity": "sha512-qyM4l4jr+uYrb0qlme6g5AQwBJL7qBbR6lNe6ZiFOU7sxiC45DNwO36sUIpbJy4De8Ld5bwahKx6kBdu2fQVLw==",
+ "requires": {
+ "@atlaskit/analytics-next": "^3.0.1",
+ "@atlaskit/blanket": "^7.0.1",
+ "@atlaskit/button": "^9.0.2",
+ "@atlaskit/icon": "^13.0.0",
+ "@atlaskit/layer-manager": "^5.0.0",
+ "@atlaskit/theme": "^5.0.0",
+ "prop-types": "^15.5.10",
+ "raf-schd": "^2.1.0",
+ "react-scrolllock": "^3.0.1",
+ "react-transition-group": "^2.2.1"
+ }
+ },
"@atlaskit/navigation": {
"version": "33.0.2",
"resolved": "https://registry.npmjs.org/@atlaskit/navigation/-/navigation-33.0.2.tgz",
@@ -212,6 +229,26 @@
"react-transition-group": "^2.2.1"
}
},
+ "@atlaskit/onboarding": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/@atlaskit/onboarding/-/onboarding-5.1.1.tgz",
+ "integrity": "sha512-kMgQMeQJROFM3N0A2Uenn6yl6UYaCrSDekzM/ImnPkIEUnNVw8KD9Ok26ioi//FKmTjG6GOLYK6FKIbxtg2RvA==",
+ "requires": {
+ "@atlaskit/analytics-next": "^3.0.1",
+ "@atlaskit/button": "^9.0.2",
+ "@atlaskit/icon": "^13.0.0",
+ "@atlaskit/layer": "^5.0.0",
+ "@atlaskit/layer-manager": "^5.0.0",
+ "@atlaskit/modal-dialog": "^6.0.1",
+ "@atlaskit/theme": "^5.0.0",
+ "prop-types": "^15.5.10",
+ "react-lorem-component": "^0.12.2",
+ "react-node-resolver": "^1.0.1",
+ "react-scrolllock": "^3.0.1",
+ "react-transition-group": "^2.2.1",
+ "unstated": "^1.2.0"
+ }
+ },
"@atlaskit/page": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/@atlaskit/page/-/page-8.0.0.tgz",
@@ -3142,6 +3179,21 @@
"sha.js": "^2.4.8"
}
},
+ "create-react-class": {
+ "version": "15.6.3",
+ "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz",
+ "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==",
+ "requires": {
+ "fbjs": "^0.8.9",
+ "loose-envify": "^1.3.1",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "create-react-context": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.1.6.tgz",
+ "integrity": "sha512-eCnYYEUEc5i32LHwpE/W7NlddOB9oHwsPaWtWzYtflNkkwa3IfindIcoXdVWs12zCbwaMCavKNu84EXogVIWHw=="
+ },
"cross-spawn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@@ -4765,6 +4817,16 @@
"object-assign": "^4.0.1"
}
},
+ "file-loader": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz",
+ "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.0.2",
+ "schema-utils": "^0.4.5"
+ }
+ },
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@@ -4994,14 +5056,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5016,20 +5076,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5146,8 +5203,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"ini": {
"version": "1.3.5",
@@ -5159,7 +5215,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5174,7 +5229,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5182,14 +5236,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -5208,7 +5260,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5289,8 +5340,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
- "dev": true,
- "optional": true
+ "dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -5302,7 +5352,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -5424,7 +5473,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -6848,6 +6896,14 @@
"js-tokens": "^3.0.0"
}
},
+ "lorem-ipsum": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/lorem-ipsum/-/lorem-ipsum-1.0.5.tgz",
+ "integrity": "sha512-GW1N2mqtS1qRf4WHmQ/g/asGXXd2dciLUDEsz5hZQfVJNWHUZX0zsJDLfV581de4OOCrBRiTifyoSuSSUwIEdg==",
+ "requires": {
+ "minimist": "~1.2.0"
+ }
+ },
"loud-rejection": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
@@ -8143,6 +8199,22 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
+ "react-lorem-component": {
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/react-lorem-component/-/react-lorem-component-0.12.2.tgz",
+ "integrity": "sha1-oR/SmLBEos8GKQXFOYiA5Gv+zCU=",
+ "requires": {
+ "create-react-class": "^15.5.3",
+ "lorem-ipsum": "^1.0.3",
+ "object-assign": "^4.1.0",
+ "seedable-random": "0.0.1"
+ }
+ },
+ "react-node-resolver": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/react-node-resolver/-/react-node-resolver-1.0.1.tgz",
+ "integrity": "sha1-F5inKcDiGL8vDo3fecVQ1K9h2Do="
+ },
"react-redux": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz",
@@ -8775,6 +8847,11 @@
}
}
},
+ "seedable-random": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/seedable-random/-/seedable-random-0.0.1.tgz",
+ "integrity": "sha1-CzDOp55DmWiMWgZ1A6Bmt8QegxY="
+ },
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@@ -9950,6 +10027,14 @@
}
}
},
+ "unstated": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/unstated/-/unstated-1.2.0.tgz",
+ "integrity": "sha512-nmI65VVuMRFm1UBxF1BEWTt8XoRldX1gEwcyBhcFJSsLycHuHFa8qjYnTv8wMISGs7e+HKWeXAtTi1DvEsg00w==",
+ "requires": {
+ "create-react-context": "^0.1.5"
+ }
+ },
"unzip-response": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz",
diff --git a/package.json b/package.json
index ed1ba66..37812f0 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
"@atlaskit/field-text": "7.0.1",
"@atlaskit/icon": "13.2.0",
"@atlaskit/navigation": "33.0.2",
+ "@atlaskit/onboarding": "5.1.1",
"@atlaskit/page": "8.0.0",
"@atlaskit/spinner": "9.0.2",
"@atlaskit/theme": "5.1.0",
@@ -124,6 +125,7 @@
"eslint-plugin-import": "2.13.0",
"eslint-plugin-jsdoc": "3.7.1",
"eslint-plugin-react": "7.10.0",
+ "file-loader": "1.1.11",
"flow-bin": "0.76.0",
"html-webpack-plugin": "3.2.0",
"precommit-hook": "3.0.0",
diff --git a/webpack.config.js b/webpack.config.js
index 954cbb0..56e5140 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -45,6 +45,10 @@ const commonConfig = {
test: /\.css$/
},
{
+ use: 'file-loader',
+ test: /\.png$/
+ },
+ {
loader: 'svg-inline-loader',
test: /\.svg$/
}