diff --git a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js deleted file mode 100644 index f888789f3..000000000 --- a/plugins/talk-plugin-notifications-category-featured/client/containers/Toggle.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose, gql } from 'react-apollo'; -import Toggle from 'talk-plugin-notifications/client/components/Toggle'; -import { t } from 'plugin-api/beta/client/services'; -import { withFragments } from 'plugin-api/beta/client/hocs'; - -class ToggleContainer extends React.Component { - constructor(props) { - super(props); - props.setTurnOffInputFragment({ onFeatured: false }); - - if (this.getOnFeaturedSetting()) { - props.indicateOn(); - } - } - - componentWillReceiveProps(nextProps) { - const prevSetting = this.getOnFeaturedSetting(this.props); - const nextSetting = this.getOnFeaturedSetting(nextProps); - if (prevSetting && !nextSetting) { - nextProps.indicateOff(); - } else if (!prevSetting && nextSetting) { - nextProps.indicateOn(); - } - } - - getOnFeaturedSetting = (props = this.props) => - props.root.me.notificationSettings.onFeatured; - - toggle = () => { - this.props.updateNotificationSettings({ - onFeatured: !this.getOnFeaturedSetting(), - }); - }; - - render() { - return ( - - {t('talk-plugin-notifications-category-featured.toggle_description')} - - ); - } -} - -ToggleContainer.propTypes = { - data: PropTypes.object, - root: PropTypes.object, - indicateOn: PropTypes.func.isRequired, - indicateOff: PropTypes.func.isRequired, - setTurnOffInputFragment: PropTypes.func.isRequired, - updateNotificationSettings: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, -}; - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkNotificationsCategoryFeatured_Toggle_root on RootQuery { - me { - notificationSettings { - onFeatured - } - } - } - `, - }) -); - -export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-featured/client/graphql.js b/plugins/talk-plugin-notifications-category-featured/client/graphql.js deleted file mode 100644 index 440a6f7fa..000000000 --- a/plugins/talk-plugin-notifications-category-featured/client/graphql.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'react-apollo'; - -export default { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onFeatured === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryFeatured_User_Fragment on User { - notificationSettings { - onFeatured - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onFeatured: input.onFeatured, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; diff --git a/plugins/talk-plugin-notifications-category-featured/client/index.js b/plugins/talk-plugin-notifications-category-featured/client/index.js index 1e22c3a93..74c0c2aa5 100644 --- a/plugins/talk-plugin-notifications-category-featured/client/index.js +++ b/plugins/talk-plugin-notifications-category-featured/client/index.js @@ -1,11 +1,14 @@ -import Toggle from './containers/Toggle'; import translations from './translations.yml'; -import graphql from './graphql'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onFeatured', () => + t('talk-plugin-notifications-category-featured.toggle_description') +); export default { slots: { - notificationSettings: [Toggle], + notificationSettings: [SettingsToggle], }, translations, - ...graphql, }; diff --git a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js deleted file mode 100644 index c55be5f90..000000000 --- a/plugins/talk-plugin-notifications-category-reply/client/containers/Toggle.js +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose, gql } from 'react-apollo'; -import Toggle from 'talk-plugin-notifications/client/components/Toggle'; -import { t } from 'plugin-api/beta/client/services'; -import { - withFragments, - withGraphQLExtension, -} from 'plugin-api/beta/client/hocs'; - -class ToggleContainer extends React.Component { - constructor(props) { - super(props); - props.setTurnOffInputFragment({ onReply: false }); - - if (this.getOnReplySetting()) { - props.indicateOn(); - } - } - - componentWillReceiveProps(nextProps) { - const prevSetting = this.getOnReplySetting(this.props); - const nextSetting = this.getOnReplySetting(nextProps); - if (prevSetting && !nextSetting) { - nextProps.indicateOff(); - } else if (!prevSetting && nextSetting) { - nextProps.indicateOn(); - } - } - - getOnReplySetting = (props = this.props) => - props.root.me.notificationSettings.onReply; - - toggle = () => { - this.props.updateNotificationSettings({ - onReply: !this.getOnReplySetting(), - }); - }; - - render() { - return ( - - {t('talk-plugin-notifications-category-reply.toggle_description')} - - ); - } -} - -ToggleContainer.propTypes = { - data: PropTypes.object, - root: PropTypes.object, - indicateOn: PropTypes.func.isRequired, - indicateOff: PropTypes.func.isRequired, - setTurnOffInputFragment: PropTypes.func.isRequired, - updateNotificationSettings: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, -}; - -const extension = { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onReply === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryReply_User_Fragment on User { - notificationSettings { - onReply - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onReply: input.onReply, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkNotificationsCategoryReply_Toggle_root on RootQuery { - me { - notificationSettings { - onReply - } - } - } - `, - }), - withGraphQLExtension(extension) -); - -export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-reply/client/graphql.js b/plugins/talk-plugin-notifications-category-reply/client/graphql.js deleted file mode 100644 index 623e72366..000000000 --- a/plugins/talk-plugin-notifications-category-reply/client/graphql.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'react-apollo'; - -export default { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onReply === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryReply_User_Fragment on User { - notificationSettings { - onReply - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onReply: input.onReply, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; diff --git a/plugins/talk-plugin-notifications-category-reply/client/index.js b/plugins/talk-plugin-notifications-category-reply/client/index.js index fa10c15e2..098a1397e 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/index.js +++ b/plugins/talk-plugin-notifications-category-reply/client/index.js @@ -1,8 +1,14 @@ -import Toggle from './containers/Toggle'; import translations from './translations.yml'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onReply', () => + t('talk-plugin-notifications-category-reply.toggle_description') +); + export default { slots: { - notificationSettings: [Toggle], + notificationSettings: [SettingsToggle], }, translations, }; diff --git a/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js b/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js deleted file mode 100644 index 62a4cbbce..000000000 --- a/plugins/talk-plugin-notifications-category-staff/client/containers/Toggle.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { compose, gql } from 'react-apollo'; -import Toggle from 'talk-plugin-notifications/client/components/Toggle'; -import { t } from 'plugin-api/beta/client/services'; -import { withFragments } from 'plugin-api/beta/client/hocs'; - -class ToggleContainer extends React.Component { - constructor(props) { - super(props); - props.setTurnOffInputFragment({ onStaffReply: false }); - - if (this.getOnReplySetting()) { - props.indicateOn(); - } - } - - componentWillReceiveProps(nextProps) { - const prevSetting = this.getOnReplySetting(this.props); - const nextSetting = this.getOnReplySetting(nextProps); - if (prevSetting && !nextSetting) { - nextProps.indicateOff(); - } else if (!prevSetting && nextSetting) { - nextProps.indicateOn(); - } - } - - getOnReplySetting = (props = this.props) => - props.root.me.notificationSettings.onStaffReply; - - toggle = () => { - this.props.updateNotificationSettings({ - onStaffReply: !this.getOnReplySetting(), - }); - }; - - render() { - return ( - - {t('talk-plugin-notifications-category-staff.toggle_description')} - - ); - } -} - -ToggleContainer.propTypes = { - data: PropTypes.object, - root: PropTypes.object, - indicateOn: PropTypes.func.isRequired, - indicateOff: PropTypes.func.isRequired, - setTurnOffInputFragment: PropTypes.func.isRequired, - updateNotificationSettings: PropTypes.func.isRequired, - disabled: PropTypes.bool.isRequired, -}; - -const enhance = compose( - withFragments({ - root: gql` - fragment TalkNotificationsCategoryStaffReply_User_Fragment on RootQuery { - me { - notificationSettings { - onStaffReply - } - } - } - `, - }) -); - -export default enhance(ToggleContainer); diff --git a/plugins/talk-plugin-notifications-category-staff/client/graphql.js b/plugins/talk-plugin-notifications-category-staff/client/graphql.js deleted file mode 100644 index 2e8007d4f..000000000 --- a/plugins/talk-plugin-notifications-category-staff/client/graphql.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from 'react-apollo'; - -export default { - mutations: { - UpdateNotificationSettings: ({ - variables: { input }, - state: { auth: { user: { id } } }, - }) => ({ - update: proxy => { - if (input.onStaffReply === undefined) { - return; - } - - const fragment = gql` - fragment TalkNotificationsCategoryStaffReply_User_Fragment on User { - notificationSettings { - onStaffReply - } - } - `; - const fragmentId = `User_${id}`; - const data = { - __typename: 'User', - notificationSettings: { - __typename: 'NotificationSettings', - onStaffReply: input.onStaffReply, - }, - }; - proxy.writeFragment({ fragment, id: fragmentId, data }); - }, - }), - }, -}; diff --git a/plugins/talk-plugin-notifications-category-staff/client/index.js b/plugins/talk-plugin-notifications-category-staff/client/index.js index 1e22c3a93..e273fceaa 100644 --- a/plugins/talk-plugin-notifications-category-staff/client/index.js +++ b/plugins/talk-plugin-notifications-category-staff/client/index.js @@ -1,11 +1,14 @@ -import Toggle from './containers/Toggle'; import translations from './translations.yml'; -import graphql from './graphql'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onStaffReply', () => + t('talk-plugin-notifications-category-staff.toggle_description') +); export default { slots: { - notificationSettings: [Toggle], + notificationSettings: [SettingsToggle], }, translations, - ...graphql, }; diff --git a/plugins/talk-plugin-notifications/client/api/components/Toggle.css b/plugins/talk-plugin-notifications/client/api/components/Toggle.css new file mode 100644 index 000000000..3f5b02705 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/components/Toggle.css @@ -0,0 +1,20 @@ +.title { + display: inline-block; + width: 100%; + cursor: pointer; + user-select: none; + + &.disabled { + color: #e5e5e5; + cursor: default; + } +} + +.toggle { + display: flex; + align-items: center; +} + +.checkBox { + text-align: right; +} diff --git a/plugins/talk-plugin-notifications/client/api/components/Toggle.js b/plugins/talk-plugin-notifications/client/api/components/Toggle.js new file mode 100644 index 000000000..40e7da4e6 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/components/Toggle.js @@ -0,0 +1,41 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Checkbox } from 'plugin-api/beta/client/components/ui'; +import styles from './Toggle.css'; +import uuid from 'uuid/v4'; +import cn from 'classnames'; + +class Toggle extends React.Component { + id = uuid(); + + render() { + const { checked, onChange, children, disabled } = this.props; + return ( +
+ +
+ +
+
+ ); + } +} + +Toggle.propTypes = { + disabled: PropTypes.bool, + checked: PropTypes.bool, + onChange: PropTypes.func, + children: PropTypes.node, +}; + +export default Toggle; diff --git a/plugins/talk-plugin-notifications/client/api/components/index.js b/plugins/talk-plugin-notifications/client/api/components/index.js new file mode 100644 index 000000000..81e73b58b --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/components/index.js @@ -0,0 +1 @@ +export { default as Toggle } from './Toggle'; diff --git a/plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js b/plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js new file mode 100644 index 000000000..95b7789cb --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/factories/createSettingsToggle.js @@ -0,0 +1,22 @@ +import React from 'react'; +import Toggle from '../components/Toggle'; +import withSettingsToggle from '../hocs/withSettingsToggle'; + +/** + * createSettingsToggle will add a boolean setting with the + * name `settingsName` to notification settings and return + * a full Toggle Component. + * + * You must provide a `label` either as a string or as a callback. + * E.g. to provide translations you could do: + * + * `const SettingsToggle = createSettingsToggle('onReply', () => t('translate'));` + */ +const createSettingsToggle = (settingsName, label) => { + const SettingsToggle = props => ( + {typeof label === 'function' ? label() : label} + ); + return withSettingsToggle(settingsName)(SettingsToggle); +}; + +export default createSettingsToggle; diff --git a/plugins/talk-plugin-notifications/client/api/factories/index.js b/plugins/talk-plugin-notifications/client/api/factories/index.js new file mode 100644 index 000000000..b18ad7679 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/factories/index.js @@ -0,0 +1 @@ +export { default as createSettingsToggle } from './createSettingsToggle'; diff --git a/plugins/talk-plugin-notifications/client/api/hocs/index.js b/plugins/talk-plugin-notifications/client/api/hocs/index.js new file mode 100644 index 000000000..c55d371bc --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/hocs/index.js @@ -0,0 +1 @@ +export { default as withSettingsToggle } from './withSettingsToggle'; diff --git a/plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js b/plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js new file mode 100644 index 000000000..fc7b1bac6 --- /dev/null +++ b/plugins/talk-plugin-notifications/client/api/hocs/withSettingsToggle.js @@ -0,0 +1,121 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { compose, gql } from 'react-apollo'; +import { + withFragments, + withGraphQLExtension, +} from 'plugin-api/beta/client/hocs'; + +const createHOC = settingsName => WrappedComponent => { + class WithSettingsToggle extends React.Component { + constructor(props) { + super(props); + props.setTurnOffInputFragment({ [settingsName]: false }); + + if (this.isChecked()) { + props.indicateOn(); + } + } + + componentWillReceiveProps(nextProps) { + const prevSetting = this.isChecked(this.props); + const nextSetting = this.isChecked(nextProps); + if (prevSetting && !nextSetting) { + nextProps.indicateOff(); + } else if (!prevSetting && nextSetting) { + nextProps.indicateOn(); + } + } + + isChecked = (props = this.props) => + props.root.me.notificationSettings[settingsName]; + + toggle = () => { + this.props.updateNotificationSettings({ + [settingsName]: !this.isChecked(), + }); + }; + + render() { + return ( + + ); + } + } + + WithSettingsToggle.propTypes = { + root: PropTypes.object, + indicateOn: PropTypes.func.isRequired, + indicateOff: PropTypes.func.isRequired, + setTurnOffInputFragment: PropTypes.func.isRequired, + updateNotificationSettings: PropTypes.func.isRequired, + disabled: PropTypes.bool.isRequired, + }; + + return WithSettingsToggle; +}; + +/** + * withSettingsToggle will add a boolean setting with the + * name `settingsName` to notification settings and provide + * the folliwng props: + * + * `checked: boolean` Whether setting is on or off + * `onChange: () => void` Calling this will toggle the setting + * `disabled: boolean` Whether setting is disabled + */ +const withSettingsToggle = settingsName => { + const extension = { + mutations: { + UpdateNotificationSettings: ({ + variables: { input }, + state: { auth: { user: { id } } }, + }) => ({ + update: proxy => { + if (input[settingsName] === undefined) { + return; + } + + const fragment = gql` + fragment TalkNotifications_Toggle_${settingsName}_Fragment on User { + notificationSettings { + ${settingsName} + } + } + `; + const fragmentId = `User_${id}`; + const data = { + __typename: 'User', + notificationSettings: { + __typename: 'NotificationSettings', + [settingsName]: input[settingsName], + }, + }; + proxy.writeFragment({ fragment, id: fragmentId, data }); + }, + }), + }, + }; + + return compose( + withFragments({ + root: gql` + fragment TalkNotifications_Toggle_${settingsName}_root on RootQuery { + me { + notificationSettings { + ${settingsName} + } + } + } + `, + }), + withGraphQLExtension(extension), + createHOC(settingsName) + ); +}; + +export default withSettingsToggle;