From 0fcb0be3604649300582e7dfc84d5f4f8a2cbcc1 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 16 Apr 2018 12:29:37 -0600 Subject: [PATCH 01/12] Password Change Graph Support --- graph/mutators/user.js | 38 ++++++++++++++++++++++++++++++++ graph/typeDefs.graphql | 19 ++++++++++++++++ locales/en.yml | 3 +++ perms/constants/mutation.js | 1 + perms/reducers/mutation.js | 9 ++++++++ routes/api/v1/account.js | 15 +++---------- services/users.js | 43 +++++++++++++++++++++++++++++-------- 7 files changed, 107 insertions(+), 21 deletions(-) diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 6c673111b..4bdc746ba 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -9,6 +9,7 @@ const { SET_USER_SUSPENSION_STATUS, UPDATE_USER_ROLES, DELETE_USER, + CHANGE_PASSWORD, } = require('../../perms/constants'); const setUserUsernameStatus = async (ctx, id, status) => { @@ -143,6 +144,38 @@ const delUser = async (ctx, id) => { await user.remove(); }; +const changeUserPassword = async (ctx, oldPassword, newPassword) => { + const { + user, + loaders: { Settings }, + connectors: { services: { I18n } }, + } = ctx; + + // Verify the old password. + const validPassword = await user.verifyPassword(oldPassword); + if (!validPassword) { + throw new ErrNotAuthorized(); + } + + // Change the users password now. + await Users.changePassword(user.id, newPassword); + + // Get some context for the email to be sent. + const { organizationName, organizationContactEmail } = await Settings.load( + 'organizationName', + 'organizationContactEmail' + ); + + // Send the password change email. + await Users.sendEmail(user, { + template: 'plain', + locals: { + body: I18n.t('email.password_change.body', organizationName), + }, + subject: I18n.t('email.password_change.subject', organizationContactEmail), + }); +}; + module.exports = ctx => { let mutators = { User: { @@ -194,6 +227,11 @@ module.exports = ctx => { if (ctx.user.can(DELETE_USER)) { mutators.User.del = id => delUser(ctx, id); } + + if (ctx.user.can(CHANGE_PASSWORD)) { + mutators.User.changePassword = ({ oldPassword, newPassword }) => + changeUserPassword(ctx, oldPassword, newPassword); + } } return mutators; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index f94322344..2400f4e0b 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -1436,6 +1436,21 @@ type DelUserResponse implements Response { errors: [UserError!] } +input ChangePasswordInput { + # oldPassword is the previous password set on the account. An incorrect + # password here will result in an unauthorized error being thrown. + oldPassword: String! + + # newPassword is the password we're changing it to. + newPassword: String! +} + +type ChangePasswordResponse implements Response { + + # An array of errors relating to the mutation that occurred. + errors: [UserError!] +} + # All mutations for the application are defined on this object. type RootMutation { @@ -1536,6 +1551,10 @@ type RootMutation { # delUser will delete the user with the specified id. delUser(id: ID!): DelUserResponse + + # changePassword allows the current user to change their password that have an + # associated local user account. + changePassword(input: ChangePasswordInput!): ChangePasswordResponse } type UsernameChangedPayload { diff --git a/locales/en.yml b/locales/en.yml index 50845b96f..5524095d0 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -210,6 +210,9 @@ en: we_received_a_request: "We received a request to reset your password. If you did not request this change, you can ignore this email." if_you_did: "If you did," please_click: "please click here to reset password" + password_change: + subject: "{0} password change" + body: "Password was changed on your account.\n\nIf you did not request this change, please contact us at {0}." embedlink: copy: "Copy to Clipboard" error: diff --git a/perms/constants/mutation.js b/perms/constants/mutation.js index d2ebe73f1..57aa254e7 100644 --- a/perms/constants/mutation.js +++ b/perms/constants/mutation.js @@ -19,4 +19,5 @@ module.exports = { UPDATE_ASSET_STATUS: 'UPDATE_ASSET_STATUS', UPDATE_SETTINGS: 'UPDATE_SETTINGS', DELETE_USER: 'DELETE_USER', + CHANGE_PASSWORD: 'CHANGE_PASSWORD', }; diff --git a/perms/reducers/mutation.js b/perms/reducers/mutation.js index 73ee7ef28..d0841ef99 100644 --- a/perms/reducers/mutation.js +++ b/perms/reducers/mutation.js @@ -1,8 +1,17 @@ +const { isString } = require('lodash'); const { check } = require('../utils'); const types = require('../constants'); module.exports = (user, perm) => { switch (perm) { + case types.CHANGE_PASSWORD: + // Only users with a local account where they have a password set can + // actually change their password. + return ( + user.profiles.some(({ provider }) => provider === 'local') && + isString(user.password) && + user.password.length > 0 + ); case types.CHANGE_USERNAME: return user.status.username.status === 'REJECTED'; diff --git a/routes/api/v1/account.js b/routes/api/v1/account.js index 3909d5dce..bc642f93f 100644 --- a/routes/api/v1/account.js +++ b/routes/api/v1/account.js @@ -109,20 +109,11 @@ router.put( async (req, res, next) => { const { token, password } = req.body; - if (!password || password.length < 8) { - return next(errors.ErrPasswordTooShort); - } - try { - let [user, redirect] = await UsersService.verifyPasswordResetToken(token); - - // Change the users' password. - await UsersService.changePassword(user.id, password); - + const { redirect } = await UsersService.resetPassword(token, password); res.json({ redirect }); - } catch (e) { - console.error(e); - return next(errors.ErrNotAuthorized); + } catch (err) { + return next(err); } } ); diff --git a/services/users.js b/services/users.js index 93d6fd753..657be737a 100644 --- a/services/users.js +++ b/services/users.js @@ -132,7 +132,7 @@ class Users { locals: { body: message, }, - subject: 'Your account has been suspended', + subject: 'Your account has been suspended', // TODO: replace with translation }); } @@ -490,6 +490,10 @@ class Users { } static async changePassword(id, password) { + if (!password || password.length < 8) { + throw new ErrPasswordTooShort(); + } + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); return User.update( @@ -725,18 +729,13 @@ class Users { }); } - /** - * Verifies a jwt and returns the associated user. Throws an error when the - * token isn't valid. - * - * @param {String} token the JSON Web Token to verify - */ + // TODO: update doc static async verifyPasswordResetToken(token) { if (!token) { throw new Error('cannot verify an empty token'); } - const { userId, loc, version } = await Users.verifyToken(token, { + const { userId, loc: redirect, version } = await Users.verifyToken(token, { subject: PASSWORD_RESET_JWT_SUBJECT, }); @@ -746,7 +745,33 @@ class Users { throw new Error('password reset token has expired'); } - return [user, loc]; + return { user, redirect, version }; + } + + // TODO: update doc + static async resetPassword(token, password) { + const { user, redirect, version } = await this.verifyPasswordResetToken( + token + ); + + if (!password || password.length < 8) { + throw new ErrPasswordTooShort(); + } + + const hashedPassword = await bcrypt.hash(password, SALT_ROUNDS); + + // Update the user's password. + await User.update( + { id: user.id, __v: version }, + { + $inc: { __v: 1 }, + $set: { + password: hashedPassword, + }, + } + ); + + return { user, redirect }; } /** From ce291448f1d50cf573fe1c8508db34c9b6d76fb9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 16 Apr 2018 12:45:17 -0600 Subject: [PATCH 02/12] Added some change to mutators --- graph/mutators/user.js | 11 ++++++----- graph/resolvers/root_mutation.js | 3 +++ locales/en.yml | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 4bdc746ba..04fdcd366 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -161,18 +161,18 @@ const changeUserPassword = async (ctx, oldPassword, newPassword) => { await Users.changePassword(user.id, newPassword); // Get some context for the email to be sent. - const { organizationName, organizationContactEmail } = await Settings.load( + const { organizationName, organizationContactEmail } = await Settings.load([ 'organizationName', - 'organizationContactEmail' - ); + 'organizationContactEmail', + ]); // Send the password change email. await Users.sendEmail(user, { template: 'plain', locals: { - body: I18n.t('email.password_change.body', organizationName), + body: I18n.t('email.password_change.body', organizationContactEmail), }, - subject: I18n.t('email.password_change.subject', organizationContactEmail), + subject: I18n.t('email.password_change.subject', organizationName), }); }; @@ -188,6 +188,7 @@ module.exports = ctx => { setUsername: () => Promise.reject(new ErrNotAuthorized()), stopIgnoringUser: () => Promise.reject(new ErrNotAuthorized()), del: () => Promise.reject(new ErrNotAuthorized()), + changePassword: () => Promise.reject(new ErrNotAuthorized()), }, }; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index 2838f0f99..d2c2965e0 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -139,6 +139,9 @@ const RootMutation = { delUser: async (_, { id }, { mutators: { User } }) => { await User.del(id); }, + changePassword: async (_, { input }, { mutators: { User } }) => { + await User.changePassword(input); + }, }; module.exports = RootMutation; diff --git a/locales/en.yml b/locales/en.yml index ed48d20f9..11d55571d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -220,7 +220,7 @@ en: please_click: "please click here to reset password" password_change: subject: "{0} password change" - body: "Password was changed on your account.\n\nIf you did not request this change, please contact us at {0}." + body: "The password on your account has been changed.\n\nIf you did not request this change, please contact us at {0}." embedlink: copy: "Copy to Clipboard" error: From bc71366fd507f169b92ac6da6ca50715bfd0c789 Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 20 Apr 2018 18:23:46 -0300 Subject: [PATCH 03/12] Frontend for Change Password --- plugins/talk-plugin-auth/client/index.js | 2 + .../components/ChangePassword.css | 132 ++++++++++++++++++ .../components/ChangePassword.js | 118 ++++++++++++++++ .../containers/ChangePassword.js | 0 4 files changed, 252 insertions(+) create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js create mode 100644 plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js diff --git a/plugins/talk-plugin-auth/client/index.js b/plugins/talk-plugin-auth/client/index.js index 13abce1d9..9851e82c5 100644 --- a/plugins/talk-plugin-auth/client/index.js +++ b/plugins/talk-plugin-auth/client/index.js @@ -4,6 +4,7 @@ import SetUsernameDialog from './stream/containers/SetUsernameDialog'; import translations from './translations.yml'; import Login from './login/containers/Main'; import reducer from './login/reducer'; +import ChangePassword from './profile-settings/components/ChangePassword'; export default { reducer, @@ -11,5 +12,6 @@ export default { slots: { stream: [UserBox, SignInButton, SetUsernameDialog], login: [Login], + profileSettings: [ChangePassword], }, }; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css new file mode 100644 index 000000000..94a938e40 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -0,0 +1,132 @@ +.container { + position: relative; + color: #202020; + padding: 10px; + border-radius: 2px; + border: solid 1px transparent; + box-sizing: border-box; + justify-content: space-between; + + &.editable { + border-color: #979797; + background-color: #EDEDED; + } +} + +.detailItemContent { + min-width: 280px; +} + +.detailItemMessage { + flex-grow: 1; + display: flex; + align-items: center; + padding-left: 2px; + padding-top: 16px; +} + +.actions { + position: absolute; + top: 10px; + right: 10px; + display: flex; + flex-direction: column; + align-items: center; +} + +.title { + color: #202020; +} + +.detailList { + padding: 0; + margin: 0; + list-style: none; +} + +.detailLabel { + color: #4C4C4D; + font-size: 1em; + display: block; + margin-bottom: 4px; +} + +.detailValue { + padding: 6px 0; + border: solid 1px #979797; + display: block; + font-size: 1.1em; + border-radius: 2px; + background-color: #ffffff; + color: #979797; + box-sizing: border-box; + width: 100%; +} + +.detailItem { + margin-bottom: 12px; +} + +.detailItemContainer { + display: flex; +} + +.detailBottomBox { + display: block; + padding-top: 4px; + text-align: right; + width: 280px; +} + +.detailLink { + color: #00538A; + text-decoration: none; + &:hover { + cursor: pointer; + } +} + +.checkIcon { + color: #00CD73; + & > i { + font-size: 16px; + } +} + +.warningIcon { + color: #FA4643; +} + +.errorMsg { + color: #FA4643; + padding-left: 4px; + font-size: 0.9em; +} + +.button { + border: solid 1px #787D80; + background-color: transparent; + height: 30px; + font-size: 0.9em; +} + +.saveButton { + background-color: #3498DB; + border-color: #3498DB; + color: white; + + &:hover { + background-color: #399ee2; + color: white; + } +} + +.cancelButton { + color:#787D80; + margin-top: 6px; + font-size: 0.9em; + + &:hover { + cursor: pointer; + } +} diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js new file mode 100644 index 000000000..1c2162a4a --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -0,0 +1,118 @@ +import React from 'react'; +import cn from 'classnames'; +import styles from './ChangePassword.css'; +import { Button, Icon } from 'plugin-api/beta/client/components/ui'; + +class ChangePassword extends React.Component { + state = { editing: false, errors: [], oldPassword: '' }; + + addError = err => { + if (this.state.errors.indexOf(err) === -1) { + this.setState(({ errors }) => ({ + errors: errors.concat(err), + })); + } + }; + + removeError = err => { + this.setState(({ errors }) => ({ + errors: errors.filter(i => i !== err), + })); + }; + + toggleEditing = () => { + this.setState(({ editing }) => ({ + editing: !editing, + })); + }; + + disableEditing = () => { + this.setState(() => ({ + editing: false, + })); + }; + + render() { + return ( +
+

Change Password

+ {this.state.editing && ( +
    +
  • +
    +
    + + +
    +
    + + {/* Incorrect password. Please try again */} +
    +
    + + Forgot your password? + +
  • +
  • +
    +
    + + +
    +
    + Passwords don’t match +
    +
    +
  • +
  • +
    +
    + + +
    +
    + Passwords don’t match +
    +
    +
  • +
+ )} + {this.state.editing ? ( +
+ + + Cancel + +
+ ) : ( +
+ +
+ )} +
+ ); + } +} + +const ErrorMessage = ({ children }) => ( +
+ + {children} +
+); + +export default ChangePassword; diff --git a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js new file mode 100644 index 000000000..e69de29bb From b5dd63aec1d18b55da6d02bbd2cda7f7a551dca6 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 10:32:56 -0300 Subject: [PATCH 04/12] Adding withChangePassword to mutations and plugins API, styles --- client/coral-framework/graphql/fragments.js | 3 ++- client/coral-framework/graphql/mutations.js | 21 +++++++++++++++++++ plugin-api/beta/client/hocs/index.js | 1 + .../components/ChangePassword.css | 18 +++++++++++----- .../containers/ChangePassword.js | 5 +++++ 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/client/coral-framework/graphql/fragments.js b/client/coral-framework/graphql/fragments.js index a62fd6d92..c5704f719 100644 --- a/client/coral-framework/graphql/fragments.js +++ b/client/coral-framework/graphql/fragments.js @@ -25,6 +25,7 @@ export default { 'UnsuspendUserResponse', 'UpdateAssetSettingsResponse', 'UpdateAssetStatusResponse', - 'UpdateSettingsResponse' + 'UpdateSettingsResponse', + 'ChangePasswordResponse' ), }; diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index bd31be630..228911d4a 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -623,6 +623,27 @@ export const withUpdateSettings = withMutation( } ); +export const withChangePassword = withMutation( + gql` + mutation ChangePassword($input: ChangePasswordInput!) { + changePassword(input: $input) { + ...ChangePasswordResponse + } + } + `, + { + props: ({ mutate }) => ({ + changePassword: input => { + return mutate({ + variables: { + input, + }, + }); + }, + }), + } +); + export const withUpdateAssetSettings = withMutation( gql` mutation UpdateAssetSettings($id: ID!, $input: AssetSettingsInput!) { diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index 215641f32..41ede8445 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -25,5 +25,6 @@ export { withUnbanUser, withStopIgnoringUser, withSetCommentStatus, + withChangePassword, } from 'coral-framework/graphql/mutations'; export { compose } from 'recompose'; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css index 94a938e40..6a99de609 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -23,6 +23,10 @@ align-items: center; padding-left: 2px; padding-top: 16px; + + .warningIcon, .checkIcon { + font-size: 17px; + } } .actions { @@ -36,6 +40,7 @@ .title { color: #202020; + margin: 0 0 20px; } .detailList { @@ -81,6 +86,7 @@ .detailLink { color: #00538A; text-decoration: none; + font-size: 0.9em; &:hover { cursor: pointer; } @@ -88,9 +94,6 @@ .checkIcon { color: #00CD73; - & > i { - font-size: 16px; - } } .warningIcon { @@ -104,16 +107,21 @@ } .button { - border: solid 1px #787D80; + border: 1px solid #787d80; background-color: transparent; height: 30px; - font-size: 0.9em; + font-size: 1em; + line-height: normal; } .saveButton { background-color: #3498DB; border-color: #3498DB; color: white; + + > i { + font-size: 17px; + } &:hover { background-color: #399ee2; diff --git a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js index e69de29bb..af3a293f9 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js @@ -0,0 +1,5 @@ +import { compose } from 'recompose'; +import { withChangePassword } from 'plugin-api/beta/client/hocs'; +import ChangePassword from '../components/ChangePassword'; + +export default compose(withChangePassword)(ChangePassword); From 21f942a219b74af75ef2ccf0fdd71bf1646e66a0 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 11:43:40 -0300 Subject: [PATCH 05/12] validations --- plugins/talk-plugin-auth/client/index.js | 2 +- .../components/ChangePassword.css | 11 +- .../components/ChangePassword.js | 153 ++++++++++++++++-- .../containers/ChangePassword.js | 3 +- 4 files changed, 148 insertions(+), 21 deletions(-) diff --git a/plugins/talk-plugin-auth/client/index.js b/plugins/talk-plugin-auth/client/index.js index 9851e82c5..039f0f936 100644 --- a/plugins/talk-plugin-auth/client/index.js +++ b/plugins/talk-plugin-auth/client/index.js @@ -4,7 +4,7 @@ import SetUsernameDialog from './stream/containers/SetUsernameDialog'; import translations from './translations.yml'; import Login from './login/containers/Main'; import reducer from './login/reducer'; -import ChangePassword from './profile-settings/components/ChangePassword'; +import ChangePassword from './profile-settings/containers/ChangePassword'; export default { reducer, diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css index 6a99de609..809349bc6 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -7,7 +7,7 @@ box-sizing: border-box; justify-content: space-between; - &.editable { + &.editing { border-color: #979797; background-color: #EDEDED; } @@ -27,6 +27,13 @@ .warningIcon, .checkIcon { font-size: 17px; } + + .errorMsg { + display: none; + &:nth-child(1) { + display: block; + } + } } .actions { @@ -57,7 +64,7 @@ } .detailValue { - padding: 6px 0; + padding: 6px 2px; border: solid 1px #979797; display: block; font-size: 1.1em; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index 1c2162a4a..6a278b332 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -1,10 +1,75 @@ import React from 'react'; +import PropTypes from 'prop-types'; import cn from 'classnames'; import styles from './ChangePassword.css'; import { Button, Icon } from 'plugin-api/beta/client/components/ui'; +import validate from 'coral-framework/helpers/validate'; +import errorMsj from 'coral-framework/helpers/error'; class ChangePassword extends React.Component { - state = { editing: false, errors: [], oldPassword: '' }; + state = { + editing: false, + showErrors: true, + errors: [], + formData: { + oldPassword: '', + newPassword: '', + confirmNewPassword: '', + }, + }; + + onChange = e => { + const { name, value, type } = e.target; + this.setState( + state => ({ + formData: { + ...state.formData, + [name]: value, + }, + }), + () => { + this.fieldValidation(value, type, name); + this.equalityValidation('newPassword', 'confirmNewPassword'); + } + ); + }; + + equalityValidation = (field, field2) => { + const cond = this.state.formData[field] === this.state.formData[field2]; + if (!cond) { + this.addError('matchPasswords'); + } else { + this.removeError('matchPasswords'); + } + return cond; + }; + + fieldValidation = (value, type, name) => { + if (!validate[type](value)) { + this.addError(name); + } else { + this.removeError(name); + } + }; + + formValidation = () => { + // const { formData } = this.state; + // const validKeys = Object.keys(formData); + // // Required Validation + // const empty = validKeys.filter(name => { + // const cond = !formData[name].length; + // if (cond) { + // this.addError('empty'); + // } else { + // this.removeError() + // } + // return cond; + // }); + }; + + hasError = err => { + return this.state.errors.indexOf(err) !== -1; + }; addError = err => { if (this.state.errors.indexOf(err) === -1) { @@ -33,24 +98,37 @@ class ChangePassword extends React.Component { }; render() { + const { formData, showErrors, editing } = this.state; + return ( -

Change Password

- {this.state.editing && ( + {editing && (
  • - +
    - - {/* Incorrect password. Please try again */} + {!this.hasError('oldPassword') && + formData.oldPassword.length ? ( + + ) : null} + {showErrors && + this.hasError('oldPassword') && ( + {errorMsj['password']} + )}
    @@ -61,10 +139,27 @@ class ChangePassword extends React.Component {
    - +
    - Passwords don’t match + {!this.hasError('newPassword') && + !this.hasError('matchPasswords') && + formData.newPassword.length ? ( + + ) : null} + {showErrors && + this.hasError('newPassword') && ( + {errorMsj['password']} + )} + {showErrors && + this.hasError('matchPasswords') && ( + Passwords don`t match amigo + )}
  • @@ -74,21 +169,39 @@ class ChangePassword extends React.Component { - +
- Passwords don’t match + {!this.hasError('confirmNewPassword') && + !this.hasError('matchPasswords') && + formData.confirmNewPassword.length ? ( + + ) : null} + {showErrors && + this.hasError('confirmNewPassword') && ( + {errorMsj['password']} + )} + {showErrors && + this.hasError('matchPasswords') && ( + Passwords don`t match amigo + )}
)} - {this.state.editing ? ( + {editing ? (
@@ -103,16 +216,24 @@ class ChangePassword extends React.Component {
)} - + ); } } +ChangePassword.propTypes = { + changePassword: PropTypes.func, +}; + const ErrorMessage = ({ children }) => ( -
+
- {children} + {children}
); +ErrorMessage.propTypes = { + children: PropTypes.node, +}; + export default ChangePassword; diff --git a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js index af3a293f9..b4805c166 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js @@ -1,5 +1,4 @@ -import { compose } from 'recompose'; import { withChangePassword } from 'plugin-api/beta/client/hocs'; import ChangePassword from '../components/ChangePassword'; -export default compose(withChangePassword)(ChangePassword); +export default withChangePassword(ChangePassword); From 9bb3a9f7cb951c5dff6bf97527f0a8c82307fc95 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 15:22:50 -0300 Subject: [PATCH 06/12] changes --- .../components/ChangePassword.css | 7 -- .../components/ChangePassword.js | 99 ++++++++++--------- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css index 809349bc6..6b34d8112 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -27,13 +27,6 @@ .warningIcon, .checkIcon { font-size: 17px; } - - .errorMsg { - display: none; - &:nth-child(1) { - display: block; - } - } } .actions { diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index 6a278b332..fc190626d 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -10,12 +10,8 @@ class ChangePassword extends React.Component { state = { editing: false, showErrors: true, - errors: [], - formData: { - oldPassword: '', - newPassword: '', - confirmNewPassword: '', - }, + errors: {}, + formData: {}, }; onChange = e => { @@ -37,54 +33,64 @@ class ChangePassword extends React.Component { equalityValidation = (field, field2) => { const cond = this.state.formData[field] === this.state.formData[field2]; if (!cond) { - this.addError('matchPasswords'); + this.addError({ [field2]: 'Passwords don`t match' }); } else { - this.removeError('matchPasswords'); + this.removeError(field2); } return cond; }; fieldValidation = (value, type, name) => { if (!validate[type](value)) { - this.addError(name); + this.addError({ [name]: errorMsj[type] }); } else { this.removeError(name); } }; - formValidation = () => { - // const { formData } = this.state; - // const validKeys = Object.keys(formData); - // // Required Validation - // const empty = validKeys.filter(name => { - // const cond = !formData[name].length; - // if (cond) { - // this.addError('empty'); - // } else { - // this.removeError() - // } - // return cond; - // }); - }; + onSave = () => { + console.log(this.state.errors); + const { formData } = this.state; + const validKeys = Object.keys(formData); - hasError = err => { - return this.state.errors.indexOf(err) !== -1; - }; + // Required Validation + const validation = validKeys.filter(name => { + const cond = !formData[name].length; + if (cond) { + this.addError({ + [name]: 'This Field is required', + }); + } else { + this.removeError(name); + } + return cond; + }); - addError = err => { - if (this.state.errors.indexOf(err) === -1) { - this.setState(({ errors }) => ({ - errors: errors.concat(err), - })); + if (validation.length) { + //error + return; } }; - removeError = err => { + hasError = err => { + return Object.keys(this.state.errors).indexOf(err) !== -1; + }; + + addError = err => { this.setState(({ errors }) => ({ - errors: errors.filter(i => i !== err), + errors: { ...errors, ...err }, })); }; + removeError = errKey => { + this.setState(state => { + const { [errKey]: _, ...errors } = state.errors; + return { + errors, + }; + }); + }; + toggleEditing = () => { this.setState(({ editing }) => ({ editing: !editing, @@ -98,7 +104,7 @@ class ChangePassword extends React.Component { }; render() { - const { formData, showErrors, editing } = this.state; + const { formData, showErrors, editing, errors } = this.state; return (
{errorMsj['password']} + {errors['oldPassword']} )}
@@ -154,11 +160,7 @@ class ChangePassword extends React.Component { ) : null} {showErrors && this.hasError('newPassword') && ( - {errorMsj['password']} - )} - {showErrors && - this.hasError('matchPasswords') && ( - Passwords don`t match amigo + {errors['newPassword']} )} @@ -178,17 +180,15 @@ class ChangePassword extends React.Component {
{!this.hasError('confirmNewPassword') && - !this.hasError('matchPasswords') && + !this.hasError('confirmNewPassword') && formData.confirmNewPassword.length ? ( ) : null} {showErrors && this.hasError('confirmNewPassword') && ( - {errorMsj['password']} - )} - {showErrors && - this.hasError('matchPasswords') && ( - Passwords don`t match amigo + + {errors['confirmNewPassword']} + )}
@@ -200,8 +200,11 @@ class ChangePassword extends React.Component { From 76ede5c541c65337235659b7ff357dbd75662eef Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 15:32:21 -0300 Subject: [PATCH 07/12] fix for large icons, reorder of styles --- .../server/views/unsubscribe-notifications.ejs | 2 +- views/admin.ejs | 2 +- views/embed/stream.ejs | 2 +- views/login.ejs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs index a34a45dbb..129c3115c 100644 --- a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs +++ b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs @@ -3,9 +3,9 @@ <%= t('talk-plugin-notifications.unsubscribe_page.unsubscribe') %> + <%- include(root + '/partials/head') %> - <%- include(root + '/partials/head') %>
diff --git a/views/admin.ejs b/views/admin.ejs index 979e2ec41..b8f775d79 100644 --- a/views/admin.ejs +++ b/views/admin.ejs @@ -11,8 +11,8 @@ - <%- include partials/head %> +
diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs index 50faaf5c2..2027f6069 100644 --- a/views/embed/stream.ejs +++ b/views/embed/stream.ejs @@ -2,9 +2,9 @@ + <%- include ../partials/head %> - <%- include ../partials/head %>
diff --git a/views/login.ejs b/views/login.ejs index 7af820a9c..c5a86c2fb 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -4,8 +4,8 @@ - <%- include partials/head %> +
From 493c4689c2d54eab43e98065f7d1ee3b64672b64 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 23:34:16 -0300 Subject: [PATCH 08/12] as InputField --- .../components/ChangePassword.js | 168 +++++++++--------- 1 file changed, 85 insertions(+), 83 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index fc190626d..ff6549a26 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -25,7 +25,11 @@ class ChangePassword extends React.Component { }), () => { this.fieldValidation(value, type, name); - this.equalityValidation('newPassword', 'confirmNewPassword'); + + // Perform equality validation if password fields have changed + if (name === 'newPassword' || name === 'confirmNewPassword') { + this.equalityValidation('newPassword', 'confirmNewPassword'); + } } ); }; @@ -49,7 +53,8 @@ class ChangePassword extends React.Component { }; onSave = () => { - console.log(this.state.errors); + // errors is empty + const { formData } = this.state; const validKeys = Object.keys(formData); @@ -104,8 +109,11 @@ class ChangePassword extends React.Component { }; render() { - const { formData, showErrors, editing, errors } = this.state; - + const { editing, errors } = this.state; + console.log( + Object.keys(this.state.formData).length, + Object.keys(this.state.errors).length + ); return (
Change Password {editing && (
    -
  • -
    -
    - - -
    -
    - {!this.hasError('oldPassword') && - formData.oldPassword.length ? ( - - ) : null} - {showErrors && - this.hasError('oldPassword') && ( - {errors['oldPassword']} - )} -
    -
    + Forgot your password? -
  • -
  • -
    -
    - - -
    -
    - {!this.hasError('newPassword') && - !this.hasError('matchPasswords') && - formData.newPassword.length ? ( - - ) : null} - {showErrors && - this.hasError('newPassword') && ( - {errors['newPassword']} - )} -
    -
    -
  • -
  • -
    -
    - - -
    -
    - {!this.hasError('confirmNewPassword') && - !this.hasError('confirmNewPassword') && - formData.confirmNewPassword.length ? ( - - ) : null} - {showErrors && - this.hasError('confirmNewPassword') && ( - - {errors['confirmNewPassword']} - - )} -
    -
    -
  • + + +
)} {editing ? ( @@ -201,10 +168,6 @@ class ChangePassword extends React.Component { className={cn(styles.button, styles.saveButton)} icon="save" onClick={this.onSave} - disabled={ - Object.keys(this.state.formData).length && - Object.keys(this.state.errors).length - } > Save @@ -228,6 +191,45 @@ ChangePassword.propTypes = { changePassword: PropTypes.func, }; +const InputField = ({ + id = '', + label = '', + type = 'text', + name = '', + onChange = () => {}, + value = '', + showError = true, + hasError = false, + errorMsg = '', + children, +}) => { + return ( +
  • +
    +
    + + +
    +
    + {!hasError && + value && } + {hasError && showError && {errorMsg}} +
    +
    + {children} +
  • + ); +}; + const ErrorMessage = ({ children }) => (
    From 1d2be0cef0932c07537f396e8bf4f43b34f3676e Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 23:56:54 -0300 Subject: [PATCH 09/12] Validation finished --- .../components/ChangePassword.js | 51 +++++++------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index ff6549a26..dbb210d33 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -5,6 +5,7 @@ import styles from './ChangePassword.css'; import { Button, Icon } from 'plugin-api/beta/client/components/ui'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; +import isEqual from 'lodash/isEqual'; class ChangePassword extends React.Component { state = { @@ -14,6 +15,8 @@ class ChangePassword extends React.Component { formData: {}, }; + validKeys = ['oldPassword', 'newPassword', 'confirmNewPassword']; + onChange = e => { const { name, value, type } = e.target; this.setState( @@ -45,38 +48,15 @@ class ChangePassword extends React.Component { }; fieldValidation = (value, type, name) => { - if (!validate[type](value)) { + if (!value.length) { + this.addError({ [name]: 'This field is required' }); + } else if (!validate[type](value)) { this.addError({ [name]: errorMsj[type] }); } else { this.removeError(name); } }; - onSave = () => { - // errors is empty - - const { formData } = this.state; - const validKeys = Object.keys(formData); - - // Required Validation - const validation = validKeys.filter(name => { - const cond = !formData[name].length; - if (cond) { - this.addError({ - [name]: 'This Field is required', - }); - } else { - this.removeError(name); - } - return cond; - }); - - if (validation.length) { - //error - return; - } - }; - hasError = err => { return Object.keys(this.state.errors).indexOf(err) !== -1; }; @@ -102,18 +82,20 @@ class ChangePassword extends React.Component { })); }; - disableEditing = () => { - this.setState(() => ({ - editing: false, - })); + isSubmitBlocked = () => { + const formHasErrors = !!Object.keys(this.state.errors).length; + const formIncomplete = !isEqual( + Object.keys(this.state.formData), + this.validKeys + ); + return formHasErrors || formIncomplete; }; + onSave = () => {}; + render() { const { editing, errors } = this.state; - console.log( - Object.keys(this.state.formData).length, - Object.keys(this.state.errors).length - ); + return (
    Save From 5e3fc2b22ad5bedc3c3571ad198afbb442aa3633 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 24 Apr 2018 00:20:10 -0300 Subject: [PATCH 10/12] Adding translations, en, es, more --- .../components/ChangePassword.js | 90 ++++++++++++++----- .../talk-plugin-auth/client/translations.yml | 16 ++++ 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index dbb210d33..e6c69a11b 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -6,15 +6,17 @@ import { Button, Icon } from 'plugin-api/beta/client/components/ui'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; import isEqual from 'lodash/isEqual'; +import { t } from 'plugin-api/beta/client/services'; + +const initialState = { + editing: false, + showErrors: true, + errors: {}, + formData: {}, +}; class ChangePassword extends React.Component { - state = { - editing: false, - showErrors: true, - errors: {}, - formData: {}, - }; - + state = initialState; validKeys = ['oldPassword', 'newPassword', 'confirmNewPassword']; onChange = e => { @@ -40,7 +42,9 @@ class ChangePassword extends React.Component { equalityValidation = (field, field2) => { const cond = this.state.formData[field] === this.state.formData[field2]; if (!cond) { - this.addError({ [field2]: 'Passwords don`t match' }); + this.addError({ + [field2]: t('talk-plugin-auth.change_password.passwords_dont_match'), + }); } else { this.removeError(field2); } @@ -49,7 +53,9 @@ class ChangePassword extends React.Component { fieldValidation = (value, type, name) => { if (!value.length) { - this.addError({ [name]: 'This field is required' }); + this.addError({ + [name]: t('talk-plugin-auth.change_password.required_field'), + }); } else if (!validate[type](value)) { this.addError({ [name]: errorMsj[type] }); } else { @@ -76,10 +82,10 @@ class ChangePassword extends React.Component { }); }; - toggleEditing = () => { - this.setState(({ editing }) => ({ - editing: !editing, - })); + enableEditing = () => { + this.setState({ + editing: true, + }); }; isSubmitBlocked = () => { @@ -91,7 +97,32 @@ class ChangePassword extends React.Component { return formHasErrors || formIncomplete; }; - onSave = () => {}; + clearForm() { + this.setState(initialState); + } + + onSave = async () => { + const { oldPassword, newPassword } = this.state.formData; + + await this.props.changePassword({ + oldPassword, + newPassword, + }); + + this.clearForm(); + this.disableEditing(); + }; + + disableEditing = () => { + this.setState({ + editing: false, + }); + }; + + cancel() { + this.clearForm(); + this.disableEditing(); + } render() { const { editing, errors } = this.state; @@ -102,7 +133,9 @@ class ChangePassword extends React.Component { [styles.editing]: editing, })} > -

    Change Password

    +

    + {t('talk-plugin-auth.change_password.change_password')} +

    {editing && (
    ) : (
    -
    )} @@ -213,6 +248,19 @@ const InputField = ({ ); }; +InputField.propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + onChange: PropTypes.func, + value: PropTypes.string, + showError: PropTypes.bool, + hasError: PropTypes.bool, + errorMsg: PropTypes.string, + children: PropTypes.node, +}; + const ErrorMessage = ({ children }) => (
    diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index 6d6f8a6d6..d08162066 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -131,6 +131,14 @@ en: username: Username write_your_username: "Edit your username" your_username: "Your username appears on every comment you post." + change_password: + change_password: "Change Password" + passwords_dont_match: "Passwords don`t match" + required_field: "This field is required" + forgot_password: "Forgot your password?" + save: "Save" + cancel: "Cancel" + edit: "Edit" de: talk-plugin-auth: login: @@ -222,6 +230,14 @@ es: username: Nombre write_your_username: "Edita tu nombre" your_username: "Tu nombre aparece en cada comentario que publiques." + change_password: + change_password: "Cambiar Contraseña" + passwords_dont_match: "Las contraseñas no coinciden" + required_field: "Este campo es requerido" + forgot_password: "Olvidaste tu contraseña?" + save: "Guardar" + cancel: "Cancelar" + edit: "Editar" fr: talk-plugin-auth: login: From eaf0df95732183dd693fe0c28f3833a8d9f739ef Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 24 Apr 2018 00:45:51 -0300 Subject: [PATCH 11/12] final refactor, done. --- .../components/ChangePassword.css | 73 +++-------------- .../components/ChangePassword.js | 79 +++---------------- .../components/ErrorMessage.css | 9 +++ .../components/ErrorMessage.js | 17 ++++ .../profile-settings/components/Form.css | 5 ++ .../profile-settings/components/Form.js | 16 ++++ .../components/InputField.css | 51 ++++++++++++ .../profile-settings/components/InputField.js | 60 ++++++++++++++ 8 files changed, 177 insertions(+), 133 deletions(-) create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.js create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/Form.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/Form.js create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/InputField.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/InputField.js diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css index 6b34d8112..e94247ac1 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -13,22 +13,6 @@ } } -.detailItemContent { - min-width: 280px; -} - -.detailItemMessage { - flex-grow: 1; - display: flex; - align-items: center; - padding-left: 2px; - padding-top: 16px; - - .warningIcon, .checkIcon { - font-size: 17px; - } -} - .actions { position: absolute; top: 10px; @@ -42,39 +26,6 @@ color: #202020; margin: 0 0 20px; } - -.detailList { - padding: 0; - margin: 0; - list-style: none; -} - -.detailLabel { - color: #4C4C4D; - font-size: 1em; - display: block; - margin-bottom: 4px; -} - -.detailValue { - padding: 6px 2px; - border: solid 1px #979797; - display: block; - font-size: 1.1em; - border-radius: 2px; - background-color: #ffffff; - color: #979797; - box-sizing: border-box; - width: 100%; -} - -.detailItem { - margin-bottom: 12px; -} - -.detailItemContainer { - display: flex; -} .detailBottomBox { display: block; @@ -92,20 +43,6 @@ } } -.checkIcon { - color: #00CD73; -} - -.warningIcon { - color: #FA4643; -} - -.errorMsg { - color: #FA4643; - padding-left: 4px; - font-size: 0.9em; -} - .button { border: 1px solid #787d80; background-color: transparent; @@ -127,6 +64,16 @@ background-color: #399ee2; color: white; } + + &:disabled { + border-color: #e0e0e0; + + &:hover { + background-color: #e0e0e0; + color: #4f5c67; + cursor: default; + } + } } .cancelButton { diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index e6c69a11b..0e4e39641 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -2,11 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import cn from 'classnames'; import styles from './ChangePassword.css'; -import { Button, Icon } from 'plugin-api/beta/client/components/ui'; +import { Button } from 'plugin-api/beta/client/components/ui'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; import isEqual from 'lodash/isEqual'; import { t } from 'plugin-api/beta/client/services'; +import Form from './Form'; +import InputField from './InputField'; const initialState = { editing: false, @@ -97,9 +99,9 @@ class ChangePassword extends React.Component { return formHasErrors || formIncomplete; }; - clearForm() { + clearForm = () => { this.setState(initialState); - } + }; onSave = async () => { const { oldPassword, newPassword } = this.state.formData; @@ -119,10 +121,10 @@ class ChangePassword extends React.Component { }); }; - cancel() { + cancel = () => { this.clearForm(); this.disableEditing(); - } + }; render() { const { editing, errors } = this.state; @@ -137,7 +139,7 @@ class ChangePassword extends React.Component { {t('talk-plugin-auth.change_password.change_password')} {editing && ( -
      +
      -
    + )} {editing ? (
    @@ -209,67 +211,4 @@ ChangePassword.propTypes = { changePassword: PropTypes.func, }; -const InputField = ({ - id = '', - label = '', - type = 'text', - name = '', - onChange = () => {}, - value = '', - showError = true, - hasError = false, - errorMsg = '', - children, -}) => { - return ( -
  • -
    -
    - - -
    -
    - {!hasError && - value && } - {hasError && showError && {errorMsg}} -
    -
    - {children} -
  • - ); -}; - -InputField.propTypes = { - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - onChange: PropTypes.func, - value: PropTypes.string, - showError: PropTypes.bool, - hasError: PropTypes.bool, - errorMsg: PropTypes.string, - children: PropTypes.node, -}; - -const ErrorMessage = ({ children }) => ( -
    - - {children} -
    -); - -ErrorMessage.propTypes = { - children: PropTypes.node, -}; - export default ChangePassword; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css b/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css new file mode 100644 index 000000000..d7a1b6d45 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css @@ -0,0 +1,9 @@ +.errorMsg { + color: #FA4643; + padding-left: 4px; + font-size: 0.9em; +} + +.warningIcon { + color: #FA4643; +} \ No newline at end of file diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.js b/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.js new file mode 100644 index 000000000..f39a8fc08 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.js @@ -0,0 +1,17 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './ErrorMessage.css'; +import { Icon } from 'plugin-api/beta/client/components/ui'; + +const ErrorMessage = ({ children }) => ( +
    + + {children} +
    +); + +ErrorMessage.propTypes = { + children: PropTypes.node, +}; + +export default ErrorMessage; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/Form.css b/plugins/talk-plugin-auth/client/profile-settings/components/Form.css new file mode 100644 index 000000000..b7a2a21ee --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/Form.css @@ -0,0 +1,5 @@ +.detailList { + padding: 0; + margin: 0; + list-style: none; +} \ No newline at end of file diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/Form.js b/plugins/talk-plugin-auth/client/profile-settings/components/Form.js new file mode 100644 index 000000000..8a455e808 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/Form.js @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './Form.css'; +import PropTypes from 'prop-types'; + +const Form = ({ children, className = '' }) => ( +
    +
      {children}
    +
    +); + +Form.propTypes = { + className: PropTypes.string, + children: PropTypes.node, +}; + +export default Form; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css new file mode 100644 index 000000000..be25a44b9 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css @@ -0,0 +1,51 @@ + +.detailItem { + margin-bottom: 12px; +} + +.detailItemContainer { + display: flex; +} + +.detailItemContent { + min-width: 280px; +} + +.detailLabel { + color: #4C4C4D; + font-size: 1em; + display: block; + margin-bottom: 4px; +} + +.detailValue { + padding: 6px 2px; + border: solid 1px #979797; + display: block; + font-size: 1.1em; + border-radius: 2px; + background-color: #ffffff; + color: #979797; + box-sizing: border-box; + width: 100%; +} + +.detailItemMessage { + flex-grow: 1; + display: flex; + align-items: center; + padding-left: 2px; + padding-top: 16px; + + .warningIcon, .checkIcon { + font-size: 17px; + } +} + +.checkIcon { + color: #00CD73; +} + +.warningIcon { + color: #FA4643; +} diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js new file mode 100644 index 000000000..ed9c394ee --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './InputField.css'; +import ErrorMessage from './ErrorMessage'; +import { Icon } from 'plugin-api/beta/client/components/ui'; + +const InputField = ({ + id = '', + label = '', + type = 'text', + name = '', + onChange = () => {}, + value = '', + showError = true, + hasError = false, + errorMsg = '', + children, +}) => { + return ( +
  • +
    +
    + + +
    +
    + {!hasError && + value && } + {hasError && showError && {errorMsg}} +
    +
    + {children} +
  • + ); +}; + +InputField.propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + onChange: PropTypes.func, + value: PropTypes.string, + showError: PropTypes.bool, + hasError: PropTypes.bool, + errorMsg: PropTypes.string, + children: PropTypes.node, +}; + +export default InputField; From 37919a11ab36dcb991c5ec00f4ced014fe90d905 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 24 Apr 2018 13:39:13 -0300 Subject: [PATCH 12/12] Adding a notification after saving --- .../components/ChangePassword.js | 20 ++++++++++++++----- .../containers/ChangePassword.js | 12 +++++++++-- .../talk-plugin-auth/client/translations.yml | 2 ++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js index 0e4e39641..3479b5dd9 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -9,6 +9,7 @@ import isEqual from 'lodash/isEqual'; import { t } from 'plugin-api/beta/client/services'; import Form from './Form'; import InputField from './InputField'; +import { getErrorMessages } from 'coral-framework/utils'; const initialState = { editing: false, @@ -106,10 +107,18 @@ class ChangePassword extends React.Component { onSave = async () => { const { oldPassword, newPassword } = this.state.formData; - await this.props.changePassword({ - oldPassword, - newPassword, - }); + try { + await this.props.changePassword({ + oldPassword, + newPassword, + }); + this.props.notify( + 'success', + t('talk-plugin-auth.change_password.changed_password_msg') + ); + } catch (err) { + this.props.notify('error', getErrorMessages(err)); + } this.clearForm(); this.disableEditing(); @@ -208,7 +217,8 @@ class ChangePassword extends React.Component { } ChangePassword.propTypes = { - changePassword: PropTypes.func, + changePassword: PropTypes.func.isRequired, + notify: PropTypes.func.isRequired, }; export default ChangePassword; diff --git a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js index b4805c166..1322a2940 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js @@ -1,4 +1,12 @@ -import { withChangePassword } from 'plugin-api/beta/client/hocs'; +import { compose } from 'react-apollo'; +import { bindActionCreators } from 'redux'; +import { connect } from 'plugin-api/beta/client/hocs'; import ChangePassword from '../components/ChangePassword'; +import { notify } from 'coral-framework/actions/notification'; +import { withChangePassword } from 'plugin-api/beta/client/hocs'; -export default withChangePassword(ChangePassword); +const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch); + +export default compose(connect(null, mapDispatchToProps), withChangePassword)( + ChangePassword +); diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index d08162066..915aee1c7 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -139,6 +139,7 @@ en: save: "Save" cancel: "Cancel" edit: "Edit" + changed_password_msg: "Changed Password - Your password has been successfully changed" de: talk-plugin-auth: login: @@ -238,6 +239,7 @@ es: save: "Guardar" cancel: "Cancelar" edit: "Editar" + changed_password_msg: "Contraseña Actualizada - Tu contraseña ha sido exitosamente actualizada" fr: talk-plugin-auth: login: