From 0fcb0be3604649300582e7dfc84d5f4f8a2cbcc1 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 16 Apr 2018 12:29:37 -0600 Subject: [PATCH 01/63] 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/63] 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 ba9f034bfbba573e75000ed723cfb805cb83bec0 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 16 Apr 2018 17:37:08 -0600 Subject: [PATCH 03/63] Allow a user to change their own username --- models/action.js | 4 +- perms/reducers/mutation.js | 21 ++- services/users.js | 133 ++++++++++++------ test/server/graph/mutations/changeUsername.js | 2 +- test/server/services/users.js | 58 ++++++++ 5 files changed, 172 insertions(+), 46 deletions(-) diff --git a/models/action.js b/models/action.js index f3414f3ea..773bccf7e 100644 --- a/models/action.js +++ b/models/action.js @@ -22,8 +22,8 @@ const ActionSchema = new Schema( item_id: String, user_id: String, - // The element that summaries will additionally group on in addtion to their action_type, item_type, and - // item_id. + // The element that summaries will additionally group on in addition to + // their action_type, item_type, and item_id. group_id: String, // Additional metadata stored on the field. diff --git a/perms/reducers/mutation.js b/perms/reducers/mutation.js index d0841ef99..f063fe1c2 100644 --- a/perms/reducers/mutation.js +++ b/perms/reducers/mutation.js @@ -1,4 +1,5 @@ -const { isString } = require('lodash'); +const { get, isString } = require('lodash'); +const moment = require('moment'); const { check } = require('../utils'); const types = require('../constants'); @@ -12,8 +13,22 @@ module.exports = (user, perm) => { isString(user.password) && user.password.length > 0 ); - case types.CHANGE_USERNAME: - return user.status.username.status === 'REJECTED'; + + case types.CHANGE_USERNAME: { + // Only users who have their usernames rejected or those users who + // not changed their usernames within 14 days can change their usernames. + const now = moment(); + return ( + user.status.username.status === 'REJECTED' || + get(user, 'status.username.history', []) + .filter(({ status }) => status === 'CHANGED') + .every(({ created_at }) => + moment(created_at) + .add(14, 'days') + .isAfter(now) + ) + ); + } case types.SET_USERNAME: return user.status.username.status === 'UNSET'; diff --git a/services/users.js b/services/users.js index 657be737a..b860acfeb 100644 --- a/services/users.js +++ b/services/users.js @@ -1,4 +1,5 @@ const uuid = require('uuid'); +const moment = require('moment'); const bcrypt = require('bcryptjs'); const { ErrMaxRateLimit, @@ -234,57 +235,64 @@ class Users { return user; } - static async _setUsername( - id, - username, - fromStatus, - toStatus, - assignedBy, - resetAllowed = false - ) { + static async setUsername(id, username, assignedBy) { try { + const oldestEditTime = moment().subtract(14, 'days'); + + // A username can be set if: + // + // - The previous status was 'UNSET' + // - The username has not been changed within the last 14 days. const query = { id, - 'status.username.status': fromStatus, - }; - if (!resetAllowed) { - query.username = { $ne: username }; - } - - let user = await User.findOneAndUpdate( - query, - { - $set: { - username, - lowercaseUsername: username.toLowerCase(), - 'status.username.status': toStatus, + $or: [ + { + 'status.username.status': 'UNSET', }, - $push: { - 'status.username.history': { - status: toStatus, - assigned_by: assignedBy, - created_at: Date.now(), + { + 'status.username.status': { $in: ['APPROVED', 'SET'] }, + 'status.username.history.created_at': { + $not: { + $gte: oldestEditTime, + }, }, }, + ], + }; + + const update = { + $set: { + username, + lowercaseUsername: username.toLowerCase(), + 'status.username.status': 'SET', }, - { - new: true, - } - ); + $push: { + 'status.username.history': { + status: 'SET', + assigned_by: assignedBy, + created_at: Date.now(), + }, + }, + }; + + let user = await User.findOneAndUpdate(query, update, { + new: true, + }); if (!user) { user = await Users.findById(id); if (user === null) { throw new ErrNotFound(); } - if (user.status.username.status !== fromStatus) { + if ( + !['UNSET', 'APPROVED', 'SET'].includes(user.status.username.status) || + !user.status.username.history.every(({ created_at }) => + oldestEditTime.isAfter(created_at) + ) + ) { throw new ErrPermissionUpdateUsername(); } - if (!resetAllowed && user.username === username) { - throw new ErrSameUsernameProvided(); - } - throw new Error('edit username failed for an unexpected reason'); } @@ -298,12 +306,57 @@ class Users { } } - static async setUsername(id, username, assignedBy) { - return Users._setUsername(id, username, 'UNSET', 'SET', assignedBy, true); - } - static async changeUsername(id, username, assignedBy) { - return Users._setUsername(id, username, 'REJECTED', 'CHANGED', assignedBy); + try { + const query = { + id, + username: { $ne: username }, + 'status.username.status': 'REJECTED', + }; + + const update = { + $set: { + username, + lowercaseUsername: username.toLowerCase(), + 'status.username.status': 'CHANGED', + }, + $push: { + 'status.username.history': { + status: 'CHANGED', + assigned_by: assignedBy, + created_at: Date.now(), + }, + }, + }; + + let user = await User.findOneAndUpdate(query, update, { + new: true, + }); + if (!user) { + user = await Users.findById(id); + if (user === null) { + throw new ErrNotFound(); + } + + if (user.status.username.status !== 'REJECTED') { + throw new ErrPermissionUpdateUsername(); + } + + if (user.username === username) { + throw new ErrSameUsernameProvided(); + } + + throw new Error('edit username failed for an unexpected reason'); + } + + return user; + } catch (err) { + if (err.code === 11000) { + throw new ErrUsernameTaken(); + } + + throw err; + } } /** diff --git a/test/server/graph/mutations/changeUsername.js b/test/server/graph/mutations/changeUsername.js index eaff85434..a5c3daed8 100644 --- a/test/server/graph/mutations/changeUsername.js +++ b/test/server/graph/mutations/changeUsername.js @@ -89,7 +89,7 @@ describe('graph.mutations.changeUsername', () => { expect(res.data.changeUsername.errors).to.have.length(1); expect(res.data.changeUsername.errors[0]).to.have.property( 'translation_key', - 'NOT_AUTHORIZED' + 'EDIT_USERNAME_NOT_AUTHORIZED' ); // Set the user to the desired status. diff --git a/test/server/services/users.js b/test/server/services/users.js index e8a0172f7..a7d7475e9 100644 --- a/test/server/services/users.js +++ b/test/server/services/users.js @@ -2,6 +2,8 @@ const UsersService = require('../../../services/users'); const SettingsService = require('../../../services/settings'); const mailer = require('../../../services/mailer'); const Context = require('../../../graph/context'); +const timekeeper = require('timekeeper'); +const moment = require('moment'); const chai = require('chai'); chai.use(require('chai-as-promised')); @@ -303,6 +305,62 @@ describe('services.UsersService', () => { } }); }); + + if (func === 'setUsername') { + it('should let a user set their username from UNSET', async () => { + const user = mockUsers[0]; + + // Set the user to the desired status. + await UsersService.setUsernameStatus(user.id, 'UNSET'); + await UsersService.setUsername(user.id, 'new_username', null); + }); + + describe('time based', () => { + afterEach(() => { + timekeeper.reset(); + }); + + ['SET', 'APPROVED'].forEach(status => { + it(`should not allow users to change their username if it was changed within 14 of today from ${status}`, async () => { + const user = mockUsers[0]; + + // Set the user to the desired status. + await UsersService.setUsernameStatus(user.id, status); + + timekeeper.travel( + moment() + .add(5, 'days') + .toDate() + ); + + try { + await UsersService.setUsername(user.id, 'new_username', null); + throw new Error('edit was processed successfully'); + } catch (err) { + expect(err).have.property( + 'translation_key', + 'EDIT_USERNAME_NOT_AUTHORIZED' + ); + } + }); + + it(`allows users to change their username if it was changed 14 days before today from ${status}`, async () => { + const user = mockUsers[0]; + + // Set the user to the desired status. + await UsersService.setUsernameStatus(user.id, status); + + timekeeper.travel( + moment() + .add(15, 'days') + .toDate() + ); + + await UsersService.setUsername(user.id, 'new_username', null); + }); + }); + }); + } }); describe('#isValidUsername', () => { From ca2070eada75d187bd65287ecae4fd02266a0be7 Mon Sep 17 00:00:00 2001 From: "D. Luijten" Date: Wed, 18 Apr 2018 14:28:42 +0200 Subject: [PATCH 04/63] Added NL translation for Moment. Also, sorted locale imports alphabetically. --- client/coral-framework/services/i18n.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js index e1c3ab99e..507e7f68e 100644 --- a/client/coral-framework/services/i18n.js +++ b/client/coral-framework/services/i18n.js @@ -9,6 +9,7 @@ import 'moment/locale/da'; import 'moment/locale/de'; import 'moment/locale/es'; import 'moment/locale/fr'; +import 'moment/locale/nl'; import 'moment/locale/pt-br'; import { createStorage } from 'coral-framework/services/storage'; @@ -18,10 +19,10 @@ import daTA from 'timeago.js/locales/da'; import deTA from 'timeago.js/locales/de'; import esTA from 'timeago.js/locales/es'; import frTA from 'timeago.js/locales/fr'; +import nl from 'timeago.js/locales/nl'; import pt_BRTA from 'timeago.js/locales/pt_BR'; import zh_CNTA from 'timeago.js/locales/zh_CN'; import zh_TWTA from 'timeago.js/locales/zh_TW'; -import nl from 'timeago.js/locales/nl'; import ar from '../../../locales/ar.yml'; import en from '../../../locales/en.yml'; @@ -29,10 +30,10 @@ import da from '../../../locales/da.yml'; import de from '../../../locales/de.yml'; import es from '../../../locales/es.yml'; import fr from '../../../locales/fr.yml'; +import nl_NL from '../../../locales/nl_NL.yml'; import pt_BR from '../../../locales/pt_BR.yml'; import zh_CN from '../../../locales/zh_CN.yml'; import zh_TW from '../../../locales/zh_TW.yml'; -import nl_NL from '../../../locales/nl_NL.yml'; const defaultLanguage = process.env.TALK_DEFAULT_LANG; const translations = { From b60e62f9769bf78f47d09d684d69180537bb624c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 19 Apr 2018 09:21:12 -0600 Subject: [PATCH 05/63] small fixes for db logic --- services/users.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/users.js b/services/users.js index b860acfeb..a3301be10 100644 --- a/services/users.js +++ b/services/users.js @@ -237,7 +237,9 @@ class Users { static async setUsername(id, username, assignedBy) { try { - const oldestEditTime = moment().subtract(14, 'days'); + const oldestEditTime = moment() + .subtract(14, 'days') + .toDate(); // A username can be set if: // @@ -252,9 +254,7 @@ class Users { { 'status.username.status': { $in: ['APPROVED', 'SET'] }, 'status.username.history.created_at': { - $not: { - $gte: oldestEditTime, - }, + $lte: oldestEditTime, }, }, ], From 67405937ed15b1f0e1312a7de6a027f65c3df601 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 19 Apr 2018 10:48:48 -0600 Subject: [PATCH 06/63] adjusted download to include all comments --- plugins/talk-plugin-profile-data/README.md | 3 +- .../talk-plugin-profile-data/server/router.js | 55 +++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/plugins/talk-plugin-profile-data/README.md b/plugins/talk-plugin-profile-data/README.md index 17f90c666..1d019a79b 100644 --- a/plugins/talk-plugin-profile-data/README.md +++ b/plugins/talk-plugin-profile-data/README.md @@ -20,4 +20,5 @@ their profile tab in the comment stream. Once clicked, an email will be sent that contains a download link. Only one link can be generated every 7 days, and the link will be valid for 24 hours. -The downloaded zip file will contain the users comments in a CSV format. +The downloaded zip file will contain all the users comments in a CSV format +including those that have been rejected, withheld, or still in premod. diff --git a/plugins/talk-plugin-profile-data/server/router.js b/plugins/talk-plugin-profile-data/server/router.js index 00061c173..2b0628753 100644 --- a/plugins/talk-plugin-profile-data/server/router.js +++ b/plugins/talk-plugin-profile-data/server/router.js @@ -20,14 +20,15 @@ async function verifyDownloadToken( // loadCommentsBatch will load a batch of the comments and write them to the // stream. -async function loadCommentsBatch(ctx, csv, variables = {}) { +async function loadCommentsBatch(ctx, csv, variables) { let result = await ctx.graphql( ` - query GetMyComments($cursor: Cursor) { - me { + query GetMyComments($userID: ID!, $cursor: Cursor) { + user(id: $userID) { comments(query: { limit: 100, - cursor: $cursor + cursor: $cursor, + statuses: null }) { hasNextPage endCursor @@ -50,7 +51,7 @@ async function loadCommentsBatch(ctx, csv, variables = {}) { throw result.errors; } - for (const comment of get(result, 'data.me.comments.nodes', [])) { + for (const comment of get(result, 'data.user.comments.nodes', [])) { csv.write([ comment.id, moment(comment.created_at).format('YYYY-MM-DD HH:mm:ss'), @@ -60,12 +61,12 @@ async function loadCommentsBatch(ctx, csv, variables = {}) { ]); } - return pick(result.data.me.comments, ['hasNextPage', 'endCursor']); + return pick(get(result, 'data.user.comments'), ['hasNextPage', 'endCursor']); } // loadComments will load batches of the comments and write them to the csv // stream. Once the comments have finished writing, it will close the stream. -async function loadComments(ctx, archive, latestContentDate) { +async function loadComments(ctx, userID, archive, latestContentDate) { // Create all the csv writers that'll write the data to the archive. const csv = stringify(); @@ -78,12 +79,14 @@ async function loadComments(ctx, archive, latestContentDate) { // from the token. let connection = await loadCommentsBatch(ctx, csv, { cursor: latestContentDate, + userID, }); // As long as there's more comments, keep paginating. while (connection.hasNextPage) { connection = await loadCommentsBatch(ctx, csv, { cursor: connection.endCursor, + userID, }); } @@ -120,7 +123,7 @@ module.exports = router => { return; } - const { connectors: { services: { Users } } } = req.context; + const { connectors: { graph: { Context }, errors } } = req.context; try { // Pull the userID and the date that the token was issued out of the @@ -130,25 +133,31 @@ module.exports = router => { token ); + // Create a system context used to get all comments for that user. + const ctx = Context.forSystem(); + + // Get the current user's username. We need it for the generated filenames. + const result = await ctx.graphql( + `query GetUser($userID: ID!) { + user(id: $userID) { username } + }`, + { userID } + ); + if (result.errors) { + throw result.errors; + } + + const user = get(result, 'data.user'); + if (!user) { + throw new errors.ErrNotFound(); + } + // Unpack the date that the token was issued, and use it as a source for the // earliest comment we should include in the download. const latestContentDate = new Date(iat * 1000); - // Grab the user that we're generating the export from. We'll use it to - // create a new context. - const user = await Users.findById(userID); - - // Base a new context off of the new user. - const ctx = req.context.masqueradeAs(user); - - // Get the current user's username. We need it for the generated filenames. - const result = await ctx.graphql('{ me { username } }'); - if (result.errors) { - throw result.errors; - } - const username = get(result, 'data.me.username'); - // Generate the filename of the file that the user will download. + const username = get(user, 'username'); const filename = `talk-${kebabCase(username)}-${kebabCase( moment(latestContentDate).format('YYYY-MM-DD HH:mm:ss') )}.zip`; @@ -167,7 +176,7 @@ module.exports = router => { archive.pipe(res); // Load the comments csv up with the user's comments. - await loadComments(ctx, archive, latestContentDate); + await loadComments(ctx, userID, archive, latestContentDate); // Mark the end of adding files, no more files can be added after this. Once // all the stream readers have finished writing, and have closed, the From 3129a6615f747f2af4ceeaf78873525fe455847d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Apr 2018 09:37:20 -0600 Subject: [PATCH 07/63] added direct download mutation --- graph/connectors.js | 4 ++ .../server/emails/download.html.ejs | 2 +- .../server/emails/download.txt.ejs | 2 +- .../server/mutators.js | 71 ++++++++++++++----- .../server/resolvers.js | 3 + .../talk-plugin-profile-data/server/router.js | 14 +++- .../server/typeDefs.graphql | 14 ++++ 7 files changed, 87 insertions(+), 23 deletions(-) diff --git a/graph/connectors.js b/graph/connectors.js index 3ba71fbfb..3ad3024ea 100644 --- a/graph/connectors.js +++ b/graph/connectors.js @@ -10,6 +10,9 @@ const secrets = require('../secrets'); // Errors. const errors = require('../errors'); +// URLs. +const url = require('../url'); + // Graph. const { getBroker } = require('./subscriptions/broker'); const { getPubsub } = require('./subscriptions/pubsub'); @@ -58,6 +61,7 @@ const defaultConnectors = { errors, config, secrets, + url, models: { Action, Asset, diff --git a/plugins/talk-plugin-profile-data/server/emails/download.html.ejs b/plugins/talk-plugin-profile-data/server/emails/download.html.ejs index 00b6ad372..974ea71f4 100644 --- a/plugins/talk-plugin-profile-data/server/emails/download.html.ejs +++ b/plugins/talk-plugin-profile-data/server/emails/download.html.ejs @@ -1 +1 @@ -

<%= t('email.download.download_link_ready', organizationName, now.toLocaleString()) %> <%= t('email.download.download_archive') %>

+

<%= t('email.download.download_link_ready', organizationName, now.toLocaleString()) %> <%= t('email.download.download_archive') %>

diff --git a/plugins/talk-plugin-profile-data/server/emails/download.txt.ejs b/plugins/talk-plugin-profile-data/server/emails/download.txt.ejs index 138ea1088..940d62bad 100644 --- a/plugins/talk-plugin-profile-data/server/emails/download.txt.ejs +++ b/plugins/talk-plugin-profile-data/server/emails/download.txt.ejs @@ -1,3 +1,3 @@ <%= t('email.download.download_link_ready', organizationName, now.toLocaleString()) %> - <%= BASE_URL %>account/download#<%= token %> + <%= downloadLandingURL %> diff --git a/plugins/talk-plugin-profile-data/server/mutators.js b/plugins/talk-plugin-profile-data/server/mutators.js index 5a897a2e1..09c9445ef 100644 --- a/plugins/talk-plugin-profile-data/server/mutators.js +++ b/plugins/talk-plugin-profile-data/server/mutators.js @@ -1,17 +1,41 @@ const moment = require('moment'); const uuid = require('uuid/v4'); const { DOWNLOAD_LINK_SUBJECT } = require('./constants'); +const { ErrNotAuthorized, ErrMaxRateLimit } = require('errors'); +const { URL } = require('url'); + +// generateDownloadLinks will generate a signed set of links for a given user to +// download an archive of their data. +async function generateDownloadLinks(ctx, userID) { + const { connectors: { url: { BASE_URL }, secrets } } = ctx; + + // Generate a token for the download link. + const token = await secrets.jwt.sign( + { user: userID }, + { jwtid: uuid.v4(), expiresIn: '1d', subject: DOWNLOAD_LINK_SUBJECT } + ); + + // Generate the url that a user can land on. + const downloadLandingURL = new URL('account/download', BASE_URL); + downloadLandingURL.hash = token; + + // Generate the url that the API calls to download the actual zip. + const downloadFileURL = new URL('api/v1/account/download', BASE_URL); + downloadFileURL.searchParams.set('token', token); + + return { + downloadLandingURL: downloadLandingURL.href, + downloadFileURL: downloadFileURL.href, + }; +} + +async function sendDownloadLink(ctx) { + const { + user, + loaders: { Settings }, + connectors: { services: { Users, I18n, Limit }, models: { User } }, + } = ctx; -async function sendDownloadLink({ - user, - loaders: { Settings }, - connectors: { - errors, - secrets, - services: { Users, I18n, Limit }, - models: { User }, - }, -}) { // downloadLinkLimiter can be used to limit downloads for the user's data to // once every 7 days. const downloadLinkLimiter = new Limit('profileDataDownloadLimiter', 1, '7d'); @@ -20,7 +44,7 @@ async function sendDownloadLink({ // 7 days. const attempts = await downloadLinkLimiter.get(user.id); if (attempts && attempts >= 1) { - throw errors.ErrMaxRateLimit; + throw new ErrMaxRateLimit(); } // Check if the lastAccountDownload time is within 7 days. @@ -30,7 +54,7 @@ async function sendDownloadLink({ .add(7, 'days') .isAfter(moment()) ) { - throw errors.ErrMaxRateLimit; + throw new ErrMaxRateLimit(); } // The account currently does not have a download link, let's record the @@ -38,21 +62,18 @@ async function sendDownloadLink({ // now. await downloadLinkLimiter.test(user.id); - // Generate a token for the download link. - const token = await secrets.jwt.sign( - { user: user.id }, - { jwtid: uuid.v4(), expiresIn: '1d', subject: DOWNLOAD_LINK_SUBJECT } - ); - const now = new Date(); + // Generate the download links. + const { downloadLandingURL } = await generateDownloadLinks(ctx, user.id); + const { organizationName } = await Settings.load('organizationName'); // Send the download link via the user's attached email account. await Users.sendEmail(user, { template: 'download', locals: { - token, + downloadLandingURL, organizationName, now, }, @@ -66,8 +87,20 @@ async function sendDownloadLink({ ); } +// downloadUser will return the download file url that can be used to directly +// download the archive. +async function downloadUser(ctx, userID) { + const { downloadFileURL } = await generateDownloadLinks(ctx, userID); + return downloadFileURL; +} + module.exports = ctx => ({ User: { requestDownloadLink: () => sendDownloadLink(ctx), + download: + // Only ADMIN users can execute an account download. + ctx.user && ctx.user.role === 'ADMIN' + ? userID => downloadUser(ctx, userID) + : () => Promise.reject(new ErrNotAuthorized()), }, }); diff --git a/plugins/talk-plugin-profile-data/server/resolvers.js b/plugins/talk-plugin-profile-data/server/resolvers.js index 691907f8c..7a261772f 100644 --- a/plugins/talk-plugin-profile-data/server/resolvers.js +++ b/plugins/talk-plugin-profile-data/server/resolvers.js @@ -5,6 +5,9 @@ module.exports = { requestDownloadLink: async (_, args, { mutators: { User } }) => { await User.requestDownloadLink(); }, + downloadUser: async (_, { id }, { mutators: { User } }) => ({ + archiveURL: await User.download(id), + }), }, User: { lastAccountDownload: (user, args, { user: currentUser }) => { diff --git a/plugins/talk-plugin-profile-data/server/router.js b/plugins/talk-plugin-profile-data/server/router.js index 2b0628753..2a2e0161a 100644 --- a/plugins/talk-plugin-profile-data/server/router.js +++ b/plugins/talk-plugin-profile-data/server/router.js @@ -101,11 +101,21 @@ module.exports = router => { // /api/v1/account/download will send back a zipped archive of the users // account. - router.post( + router.all( '/api/v1/account/download', express.urlencoded({ extended: false }), async (req, res, next) => { - const { token = null, check = false } = req.body; + let { token = null, check = false } = req.body; + + if (!token) { + // If the token wasn't found in the body, then we should check the query + // to see if it was passed that way. + token = req.query.token; + } + + if (!token) { + return res.status(400).end(); + } if (check) { // This request is checking to see if the token is valid. diff --git a/plugins/talk-plugin-profile-data/server/typeDefs.graphql b/plugins/talk-plugin-profile-data/server/typeDefs.graphql index 0029111c3..aa3d78adc 100644 --- a/plugins/talk-plugin-profile-data/server/typeDefs.graphql +++ b/plugins/talk-plugin-profile-data/server/typeDefs.graphql @@ -11,9 +11,23 @@ type RequestDownloadLinkResponse implements Response { errors: [UserError!] } +type DownloadUserResponse implements Response { + + # archiveURL is the link that can be used within the next 1 hour to download a + # users archive. + archiveURL: String + + # An array of errors relating to the mutation that occurred. + errors: [UserError!] +} + type RootMutation { # requestDownloadLink will request a download link be sent to the primary # users email address. requestDownloadLink: RequestDownloadLinkResponse + + # downloadUser will provide an account download for the indicated User. This + # mutation requires the ADMIN role. + downloadUser(id: ID!): DownloadUserResponse } From bc71366fd507f169b92ac6da6ca50715bfd0c789 Mon Sep 17 00:00:00 2001 From: okbel Date: Fri, 20 Apr 2018 18:23:46 -0300 Subject: [PATCH 08/63] 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 09/63] 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 10/63] 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 c103a0c481f3b1c40aaf42c60182e6a1233067cf Mon Sep 17 00:00:00 2001 From: "D. Luijten" Date: Mon, 23 Apr 2018 16:46:42 +0200 Subject: [PATCH 11/63] Renamed nl to nlTA. Sorted ta.register calls --- client/coral-framework/services/i18n.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js index 507e7f68e..03a0b4712 100644 --- a/client/coral-framework/services/i18n.js +++ b/client/coral-framework/services/i18n.js @@ -19,7 +19,7 @@ import daTA from 'timeago.js/locales/da'; import deTA from 'timeago.js/locales/de'; import esTA from 'timeago.js/locales/es'; import frTA from 'timeago.js/locales/fr'; -import nl from 'timeago.js/locales/nl'; +import nlTA from 'timeago.js/locales/nl'; import pt_BRTA from 'timeago.js/locales/pt_BR'; import zh_CNTA from 'timeago.js/locales/zh_CN'; import zh_TWTA from 'timeago.js/locales/zh_TW'; @@ -113,10 +113,10 @@ export function setupTranslations() { ta.register('da', daTA); ta.register('de', deTA); ta.register('fr', frTA); + ta.register('nl_NL', nlTA); ta.register('pt_BR', pt_BRTA); ta.register('zh_CN', zh_CNTA); ta.register('zh_TW', zh_TWTA); - ta.register('nl_NL', nl); timeagoInstance = ta(); } From c936a6ff2cb0bf39b9aba1ddfed2f22857360853 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Mon, 23 Apr 2018 10:59:23 -0400 Subject: [PATCH 12/63] Fix typo --- plugins/talk-plugin-profile-data/translations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-profile-data/translations.yml b/plugins/talk-plugin-profile-data/translations.yml index 6637c6958..d1059d470 100644 --- a/plugins/talk-plugin-profile-data/translations.yml +++ b/plugins/talk-plugin-profile-data/translations.yml @@ -1,7 +1,7 @@ en: download_landing: download_your_account: "Download Your Comment History" - download_details: "Your comment history will be downloaded into a .zip file. After your comment history is unzipped you will have a comma seperated value (or .csv) file that you can easily import into your favorite spreadsheet application." + download_details: "Your comment history will be downloaded into a .zip file. After your comment history is unzipped you will have a comma separated value (or .csv) file that you can easily import into your favorite spreadsheet application." all_information_included: "For each of your comments the following information is included:" information_included: date: "When you wrote the comment" From 9bb3a9f7cb951c5dff6bf97527f0a8c82307fc95 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 23 Apr 2018 15:22:50 -0300 Subject: [PATCH 13/63] 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 14/63] 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 15/63] 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 16/63] 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 17/63] 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 18/63] 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 a444d98733ec1edfd1112a9cfa7a7d5828550485 Mon Sep 17 00:00:00 2001 From: Clint Brown Date: Tue, 24 Apr 2018 17:17:48 +1000 Subject: [PATCH 19/63] Add missing ctx arg for CLI users create --- bin/cli-users | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/cli-users b/bin/cli-users index cfcbcd13a..18da341bb 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -287,8 +287,10 @@ async function createUser() { const { email, username, password, role } = answers; + const ctx = Context.forSystem(); + // Create the user. - const user = await UsersService.createLocalUser(email, password, username); + const user = await UsersService.createLocalUser(ctx, email, password, username); // Set the role. await UsersService.setRole(user.id, role); From f83d6246cb7cb355574b794b63e302105e06fcc3 Mon Sep 17 00:00:00 2001 From: Clint Brown Date: Tue, 24 Apr 2018 17:33:47 +1000 Subject: [PATCH 20/63] Fix lint --- bin/cli-users | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/cli-users b/bin/cli-users index 18da341bb..a01980a20 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -290,7 +290,12 @@ async function createUser() { const ctx = Context.forSystem(); // Create the user. - const user = await UsersService.createLocalUser(ctx, email, password, username); + const user = await UsersService.createLocalUser( + ctx, + email, + password, + username + ); // Set the role. await UsersService.setRole(user.id, role); From 37919a11ab36dcb991c5ec00f4ced014fe90d905 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 24 Apr 2018 13:39:13 -0300 Subject: [PATCH 21/63] 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: From 15817712c07af7d22d9082d98c81e37c3f4aa3c3 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 24 Apr 2018 13:58:28 -0300 Subject: [PATCH 22/63] Introducing profileHeader slot --- .../src/tabs/profile/components/Profile.js | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/profile/components/Profile.js b/client/coral-embed-stream/src/tabs/profile/components/Profile.js index 0bc90be0a..e73ebe455 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/components/Profile.js @@ -4,13 +4,31 @@ import Slot from 'coral-framework/components/Slot'; import styles from './Profile.css'; import TabPanel from '../containers/TabPanel'; +const DefaultProfileHeader = ({ username, emailAddress }) => ( +
    +

    {username}

    + {emailAddress ?

    {emailAddress}

    : null} +
    +); + +DefaultProfileHeader.propTypes = { + username: PropTypes.string, + emailAddress: PropTypes.string, +}; + const Profile = ({ username, emailAddress, root, slotPassthrough }) => { return (
    -
    -

    {username}

    - {emailAddress ?

    {emailAddress}

    : null} -
    +
    From 89114ecd13aad5fde9b19eae618bc6ec2daabe56 Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 24 Apr 2018 14:13:24 -0300 Subject: [PATCH 23/63] Adding withChangeUsername to plugin hocs, wip and more --- plugin-api/beta/client/hocs/index.js | 1 + plugins/talk-plugin-auth/client/index.js | 2 + .../components/ChangeUsername.css | 20 ++++++++ .../components/ChangeUsername.js | 51 +++++++++++++++++++ .../containers/ChangeUsername.js | 12 +++++ 5 files changed, 86 insertions(+) create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js create mode 100644 plugins/talk-plugin-auth/client/profile-settings/containers/ChangeUsername.js diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index 41ede8445..d7ca5fce9 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -26,5 +26,6 @@ export { withStopIgnoringUser, withSetCommentStatus, withChangePassword, + withChangeUsername, } from 'coral-framework/graphql/mutations'; export { compose } from 'recompose'; diff --git a/plugins/talk-plugin-auth/client/index.js b/plugins/talk-plugin-auth/client/index.js index 039f0f936..8fedb8033 100644 --- a/plugins/talk-plugin-auth/client/index.js +++ b/plugins/talk-plugin-auth/client/index.js @@ -5,6 +5,7 @@ import translations from './translations.yml'; import Login from './login/containers/Main'; import reducer from './login/reducer'; import ChangePassword from './profile-settings/containers/ChangePassword'; +import ChangeUsername from './profile-settings/containers/ChangeUsername'; export default { reducer, @@ -12,6 +13,7 @@ export default { slots: { stream: [UserBox, SignInButton, SetUsernameDialog], login: [Login], + profileHeader: [ChangeUsername], profileSettings: [ChangePassword], }, }; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css new file mode 100644 index 000000000..c28cc163c --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css @@ -0,0 +1,20 @@ +.container { + margin-bottom: 20px; +} + +.email { + margin: 0; +} + +.username { + margin-bottom: 4px; +} + +.actions { + position: absolute; + top: 10px; + right: 10px; + display: flex; + flex-direction: column; + align-items: center; +} \ No newline at end of file diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js new file mode 100644 index 000000000..59eadb1c4 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -0,0 +1,51 @@ +import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; +import styles from './ChangeUsername.css'; +import { Button } from 'plugin-api/beta/client/components/ui'; + +const initialState = { + editing: false, +}; + +class ChangeUsername extends React.Component { + state = initialState; + + render() { + const { username, emailAddress } = this.props; + return ( +
    +
    +

    {username}

    + {emailAddress ?

    {emailAddress}

    : null} +
    +
    + + + Cancel + +
    +
    + +
    +
    + ); + } +} + +ChangeUsername.propTypes = { + changeUsername: PropTypes.func.isRequired, + username: PropTypes.string, + emailAddress: PropTypes.string, +}; + +export default ChangeUsername; diff --git a/plugins/talk-plugin-auth/client/profile-settings/containers/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangeUsername.js new file mode 100644 index 000000000..87e1e18b5 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/containers/ChangeUsername.js @@ -0,0 +1,12 @@ +import { compose } from 'react-apollo'; +import { bindActionCreators } from 'redux'; +import { connect } from 'plugin-api/beta/client/hocs'; +import ChangeUsername from '../components/ChangeUsername'; +import { notify } from 'coral-framework/actions/notification'; +import { withChangeUsername } from 'plugin-api/beta/client/hocs'; + +const mapDispatchToProps = dispatch => bindActionCreators({ notify }, dispatch); + +export default compose(connect(null, mapDispatchToProps), withChangeUsername)( + ChangeUsername +); From 3bd2d0b3fcfd89e63c2f62cb0a2bcf91262d2ab2 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 24 Apr 2018 17:17:57 -0400 Subject: [PATCH 24/63] Small fix --- docs/source/03-03-product-guide-moderator-features.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/03-03-product-guide-moderator-features.md b/docs/source/03-03-product-guide-moderator-features.md index 9fbda6a18..abb2c454a 100644 --- a/docs/source/03-03-product-guide-moderator-features.md +++ b/docs/source/03-03-product-guide-moderator-features.md @@ -170,8 +170,9 @@ moderators. All your team and commenters show in the People sub-tab. From here, you can manage your team members’ roles (Admins, Moderators, Staff), as well as search -for commenters and take action on them (e.g. Ban/Un-ban, Suspend, etc.). ### -Configure +for commenters and take action on them (e.g. Ban/Un-ban, Suspend, etc.). + +### Configure See [Configuring Talk](/talk/configuring-talk/). From 2bc6687957214611a22fc85aaa1d74cf71eba710 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Wed, 25 Apr 2018 11:09:54 -0400 Subject: [PATCH 25/63] Use email vs e-mail --- locales/en.yml | 8 ++++---- plugins/talk-plugin-auth/client/translations.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index f8b9b9ce2..d7ad006cb 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -236,7 +236,7 @@ en: RATE_LIMIT_EXCEEDED: "Rate limit exceeded" USERNAME_IN_USE: "Username already in use" USERNAME_REQUIRED: "Must input a username" - EMAIL_NOT_VERIFIED: "E-mail address not verified" + EMAIL_NOT_VERIFIED: "Email address not verified" EDIT_WINDOW_ENDED: "You can no longer edit this comment. The time window to do so has expired." EDIT_USERNAME_NOT_AUTHORIZED: "You do not have permission to update your username." SAME_USERNAME_PROVIDED: "You must submit a different username." @@ -252,8 +252,8 @@ en: email: "Not a valid E-Mail" confirm_password: "Passwords don't match. Please check again" network_error: "Failed to connect to server. Check your internet connection and try again." - email_not_verified: "E-mail address {0} not verified." - email_password: "E-mail and/or password combination incorrect." + email_not_verified: "Email address {0} not verified." + email_password: "Email and/or password combination incorrect." organization_name: "Organization name must only contain letters or numbers." organization_contact_email: "Organization email is not valid." password: "Password must be at least 8 characters" @@ -441,7 +441,7 @@ en: title_reject: "We noticed you rejected a username" suspend_user: "Suspend User" yes_suspend: "Yes suspend" - email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns." + email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please email us if you have any questions or concerns." write_message: "Write a message" send: Send thank_you: "We value your safety and feedback. A moderator will review your report." diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index 915aee1c7..73c10b867 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -58,7 +58,7 @@ da: sign_in: "Sign in" sign_in_to_join: "Sign in to join the conversation" or: "Or" - email: "E-mail Address" + email: "Email Address" password: "Password" forgot_your_pass: "Forgot your password?" need_an_account: "Need an account?" @@ -101,7 +101,7 @@ en: sign_in: "Sign in" sign_in_to_join: "Sign in to join the conversation" or: "Or" - email: "E-mail Address" + email: "Email Address" password: "Password" forgot_your_pass: "Forgot your password?" need_an_account: "Need an account?" @@ -342,7 +342,7 @@ pt_BR: sign_in: "Sign in" sign_in_to_join: "Sign in to join the conversation" or: "Or" - email: "E-mail Address" + email: "Email Address" password: "Password" forgot_your_pass: "Forgot your password?" need_an_account: "Need an account?" From 754b5c5fbb6216e8344e744a7fb10ba49e9af1e7 Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 25 Apr 2018 12:20:51 -0300 Subject: [PATCH 26/63] Adding Dialog, styles and more --- .../components/ChangePassword.css | 2 +- .../components/ChangeUsername.css | 121 ++++++++++++- .../components/ChangeUsername.js | 170 +++++++++++++++--- .../components/ChangeUsernameDialog.css | 82 +++++++++ .../components/ChangeUsernameDialog.js | 57 ++++++ 5 files changed, 398 insertions(+), 34 deletions(-) create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.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 e94247ac1..6c7f9ae41 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -47,7 +47,7 @@ border: 1px solid #787d80; background-color: transparent; height: 30px; - font-size: 1em; + font-size: 0.9em; line-height: normal; } diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css index c28cc163c..8ab9eb5cd 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css @@ -1,20 +1,123 @@ .container { margin-bottom: 20px; + display: flex; + position: relative; + color: #202020; + padding: 10px; + border-radius: 2px; + box-sizing: border-box; + justify-content: space-between; + + &.editing { + background-color: #EDEDED; + } } +.content { + flex-grow: 1; +} + +.actions { + flex-grow: 0; + display: flex; + flex-direction: column; + align-items: center; +} + .email { margin: 0; } -.username { +.username { margin-bottom: 4px; +} + +.button { + border: 1px solid #787d80; + background-color: transparent; + height: 30px; + font-size: 0.9em; + line-height: normal; } -.actions { - position: absolute; - top: 10px; - right: 10px; - display: flex; - flex-direction: column; - align-items: center; -} \ No newline at end of file +.saveButton { + background-color: #3498DB; + border-color: #3498DB; + color: white; + + > i { + font-size: 17px; + } + + &:hover { + background-color: #399ee2; + color: white; + } + + &:disabled { + border-color: #e0e0e0; + + &:hover { + background-color: #e0e0e0; + color: #4f5c67; + cursor: default; + } + } +} + +.cancelButton { + color:#787D80; + margin-top: 6px; + font-size: 0.9em; + + &:hover { + cursor: pointer; + } +} + +.detailLabel { + border: solid 1px #787D80; + border-radius: 2px; + background-color: white; + height: 30px; + display: inline-block; + width: 230px; + display: flex; + + > .detailLabelIcon { + font-size: 1.2em; + padding: 0 5px; + color: #787D80; + line-height: 30px; + } + + &.disabled { + background-color: #E0E0E0; + } +} + +.detailValue { + background: transparent; + border: none; + font-size: 1em; + color: #000; + height: 30px; + outline: none; + flex: 1; +} + +.bottomText { + color: #474747; + font-size: 0.9em; + display: block; +} + +.detailList { + list-style: none; + margin: 0; + padding: 0; +} + +.detailItem { + margin-bottom: 8px; +} diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index 59eadb1c4..42349e3e9 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -2,41 +2,163 @@ import React from 'react'; import cn from 'classnames'; import PropTypes from 'prop-types'; import styles from './ChangeUsername.css'; -import { Button } from 'plugin-api/beta/client/components/ui'; +import { Icon, Button } from 'plugin-api/beta/client/components/ui'; +import ChangeUsernameDialog from './ChangeUsernameDialog'; const initialState = { editing: false, + formData: {}, + showDialog: false, }; class ChangeUsername extends React.Component { state = initialState; + clearForm = () => { + this.setState(initialState); + }; + + enableEditing = () => { + this.setState({ + editing: true, + }); + }; + + disableEditing = () => { + this.setState({ + editing: false, + }); + }; + + cancel = () => { + this.clearForm(); + this.disableEditing(); + }; + + showDialog = () => { + this.setState({ + showDialog: true, + }); + }; + + onSave = async () => { + this.showDialog(); + // this.clearForm(); + // this.disableEditing(); + }; + + onChange = e => { + const { name, value } = e.target; + this.setState( + state => ({ + formData: { + ...state.formData, + [name]: value, + }, + }), + () => { + console.log(this.state.formData); + // Validation + // this.fieldValidation(value, type, name); + // // Perform equality validation if password fields have changed + // if (name === 'newPassword' || name === 'confirmNewPassword') { + // this.equalityValidation('newPassword', 'confirmNewPassword'); + // } + } + ); + }; + + closeDialog = () => { + this.setState({ + showDialog: false, + }); + }; + render() { const { username, emailAddress } = this.props; + const { editing } = this.state; + + console.log('loading xxxx'); + return ( -
    -
    -

    {username}

    - {emailAddress ?

    {emailAddress}

    : null} -
    -
    - - - Cancel - -
    -
    - -
    +
    + + + {editing ? ( +
    +
      +
    • + + + Usernames can be changed every 14 days + +
    • +
    • + +
    • +
    +
    + ) : ( +
    +

    {username}

    + {emailAddress ? ( +

    {emailAddress}

    + ) : null} +
    + )} + + {editing ? ( +
    + + + Cancel + +
    + ) : ( +
    + +
    + )}
    ); } diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css new file mode 100644 index 000000000..b3c4524fe --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css @@ -0,0 +1,82 @@ +.dialog { + border: none; + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); + width: 320px; + top: 10px; + font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif; + font-size: 14px; + border-radius: 4px; + padding: 12px 20px; +} + +.close { + font-size: 20px; + line-height: 14px; + top: 10px; + right: 10px; + position: absolute; + display: block; + font-weight: bold; + color: #363636; + cursor: pointer; + + &:hover { + color: #6b6b6b; + } +} + +.title { + font-size: 1.3em; + margin-bottom: 8px; +} + +.description { + font-size: 1em; + line-height: 20px; + margin: 0; +} + +.item { + display: block; + color: #4C4C4D; + font-size: 1em; + margin-bottom: 2px; +} + +.bottomNote { + font-size: 0.9em; + line-height: 20px; +} + +.bottomActions { + text-align: right; +} + +.usernamesChange { + margin: 18px 0; +} + +.cancel { + border: 1px solid #787d80; + background-color: transparent; + height: 30px; + font-size: 0.9em; + line-height: normal; + + &:hover { + background-color: #eaeaea; + } +} + +.confirmChanges { + background-color: #3498DB; + border-color: #3498DB; + color: white; + height: 30px; + font-size: 0.9em; + + &:hover { + background-color: #3ba3ec; + color: white; + } +} \ No newline at end of file diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js new file mode 100644 index 000000000..e0c08ec3e --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js @@ -0,0 +1,57 @@ +import React from 'react'; +import cn from 'classnames'; +import styles from './ChangeUsernameDialog.css'; +import InputField from './InputField'; +import Form from './Form'; +import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; + +class ChangeUsernameDialog extends React.Component { + render() { + return ( + + + × + +

    Confirm Username Change

    +
    +

    + You are attempting to change your username. Your new username will + appear on all of your past and future comments. +

    +
    + + Old Username: {this.props.username} + + + New Username: {this.props.formData.newUsername} + +
    +
    + + + Note: You will not be able to change your username again for 14 + days + + +
    +
    + + +
    +
    +
    + ); + } +} + +export default ChangeUsernameDialog; From a79d71da7d78b634c8095303e4adfe03db8c967b Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 25 Apr 2018 12:53:03 -0300 Subject: [PATCH 27/63] Full functionality, needs translation --- .../components/ChangeUsername.css | 3 +- .../components/ChangeUsername.js | 11 +++--- .../components/ChangeUsernameDialog.css | 2 ++ .../components/ChangeUsernameDialog.js | 34 ++++++++++++++++++- .../components/ErrorMessage.css | 1 - .../components/InputField.css | 16 +++++++-- .../profile-settings/components/InputField.js | 14 ++++++-- 7 files changed, 68 insertions(+), 13 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css index 8ab9eb5cd..5b4631ecf 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css @@ -109,7 +109,6 @@ .bottomText { color: #474747; font-size: 0.9em; - display: block; } .detailList { @@ -119,5 +118,5 @@ } .detailItem { - margin-bottom: 8px; + margin-bottom: 12px; } diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index 42349e3e9..a146ea5f9 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -43,8 +43,10 @@ class ChangeUsername extends React.Component { onSave = async () => { this.showDialog(); - // this.clearForm(); - // this.disableEditing(); + }; + + saveChanges = async () => { + // savechanges }; onChange = e => { @@ -78,8 +80,6 @@ class ChangeUsername extends React.Component { const { username, emailAddress } = this.props; const { editing } = this.state; - console.log('loading xxxx'); - return (
    Save diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css index b3c4524fe..af681d596 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css @@ -46,6 +46,8 @@ .bottomNote { font-size: 0.9em; line-height: 20px; + padding-top: 10px; + display: block; } .bottomActions { diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js index e0c08ec3e..b337af007 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js @@ -6,6 +6,28 @@ import Form from './Form'; import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; class ChangeUsernameDialog extends React.Component { + state = { + showErrors: false, + }; + + showErrors = () => { + this.setState({ + showErrors: true, + }); + }; + + confirmChanges = async () => { + if (this.formHasError()) { + this.showErrors(); + } else { + // await this.props.saveChanges + this.props.closeDialog(); + } + }; + + formHasError = () => + this.props.formData.confirmNewUsername !== this.props.formData.newUsername; + render() { return ( Note: You will not be able to change your username again for 14 @@ -46,7 +73,12 @@ class ChangeUsernameDialog extends React.Component {
    - +
    diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css b/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css index d7a1b6d45..abcf692fe 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ErrorMessage.css @@ -1,6 +1,5 @@ .errorMsg { color: #FA4643; - padding-left: 4px; font-size: 0.9em; } diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css index be25a44b9..b2648e116 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css @@ -7,6 +7,14 @@ display: flex; } +.columnDisplay { + flex-direction: column; + + .detailItemMessage { + padding: 4px 0 0; + } +} + .detailItemContent { min-width: 280px; } @@ -28,13 +36,17 @@ color: #979797; box-sizing: border-box; width: 100%; + + &.error { + border-color: #FA4643; + } } .detailItemMessage { flex-grow: 1; display: flex; align-items: center; - padding-left: 2px; + padding-left: 6px; padding-top: 16px; .warningIcon, .checkIcon { @@ -48,4 +60,4 @@ .warningIcon { color: #FA4643; -} +} \ No newline at end of file diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js index ed9c394ee..5818bfd2c 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import cn from 'classnames'; import styles from './InputField.css'; import ErrorMessage from './ErrorMessage'; import { Icon } from 'plugin-api/beta/client/components/ui'; @@ -15,10 +16,16 @@ const InputField = ({ hasError = false, errorMsg = '', children, + columnDisplay = false, + showSuccess = true, }) => { return (
  • -
    +
    {!hasError && + showSuccess && value && } {hasError && showError && {errorMsg}}
    @@ -55,6 +63,8 @@ InputField.propTypes = { hasError: PropTypes.bool, errorMsg: PropTypes.string, children: PropTypes.node, + columnDisplay: PropTypes.bool, + showSuccess: PropTypes.bool, }; export default InputField; From aefac2056edb1037444a7f80b6f4a8b02cc85e95 Mon Sep 17 00:00:00 2001 From: okbel Date: Wed, 25 Apr 2018 13:36:00 -0300 Subject: [PATCH 28/63] wip more validations --- .../components/ChangeUsername.js | 61 ++++++++++++++++--- .../components/ChangeUsernameDialog.js | 1 + .../profile-settings/components/InputField.js | 5 +- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index a146ea5f9..d9f2fc15d 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -4,11 +4,15 @@ import PropTypes from 'prop-types'; import styles from './ChangeUsername.css'; import { Icon, Button } from 'plugin-api/beta/client/components/ui'; import ChangeUsernameDialog from './ChangeUsernameDialog'; +import validate from 'coral-framework/helpers/validate'; +import errorMsj from 'coral-framework/helpers/error'; +import { t } from 'plugin-api/beta/client/services'; const initialState = { editing: false, - formData: {}, showDialog: false, + errors: {}, + formData: {}, }; class ChangeUsername extends React.Component { @@ -49,8 +53,50 @@ class ChangeUsername extends React.Component { // savechanges }; + fieldValidation = (value, type, name) => { + if (!value.length) { + this.addError({ + [name]: t('talk-plugin-auth.change_password.required_field'), + }); + } else if (!validate[type](value)) { + this.addError({ [name]: errorMsj[type] }); + } else { + this.removeError(name); + } + }; + + hasError = err => { + return Object.keys(this.state.errors).indexOf(err) !== -1; + }; + + addError = err => { + this.setState( + ({ errors }) => ({ + errors: { ...errors, ...err }, + }), + () => { + console.log(this.state); + } + ); + }; + + removeError = errKey => { + this.setState( + state => { + const { [errKey]: _, ...errors } = state.errors; + return { + errors, + }; + }, + () => { + console.log(this.state); + } + ); + }; + onChange = e => { - const { name, value } = e.target; + const { name, value, type, dataset: { validationType } } = e.target; + this.setState( state => ({ formData: { @@ -59,13 +105,9 @@ class ChangeUsername extends React.Component { }, }), () => { - console.log(this.state.formData); - // Validation - // this.fieldValidation(value, type, name); - // // Perform equality validation if password fields have changed - // if (name === 'newPassword' || name === 'confirmNewPassword') { - // this.equalityValidation('newPassword', 'confirmNewPassword'); - // } + const fieldType = !validationType ? type : validationType; + this.fieldValidation(value, fieldType, name); + // the username cannot be the same } ); }; @@ -103,6 +145,7 @@ class ChangeUsername extends React.Component { Note: You will not be able to change your username again for 14 diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js index 5818bfd2c..511b89227 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js @@ -18,6 +18,7 @@ const InputField = ({ children, columnDisplay = false, showSuccess = true, + validationType = '', }) => { return (
  • @@ -34,10 +35,11 @@ const InputField = ({ id={id} type={type} name={name} - className={cn(styles.detailValue, styles.error)} + className={cn(styles.detailValue, { [styles.error]: hasError })} onChange={onChange} value={value} autoComplete="off" + data-validation-type={validationType} />
  • @@ -65,6 +67,7 @@ InputField.propTypes = { children: PropTypes.node, columnDisplay: PropTypes.bool, showSuccess: PropTypes.bool, + validationType: PropTypes.string, }; export default InputField; From dd9a9d5882577a8360f7e902d5aaba2034fe4224 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Wed, 25 Apr 2018 15:30:40 -0400 Subject: [PATCH 29/63] Remove refs to talk-plugin-profile-settings --- .gitignore | 1 - docs/source/api/slots.md | 3 +-- docs/source/integrating/configuring-comment-stream.md | 4 ---- docs/source/integrating/notifications.md | 4 ---- 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index e8cb92653..0cab02aeb 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ plugins/* !plugins/talk-plugin-notifications-digest-hourly !plugins/talk-plugin-offtopic !plugins/talk-plugin-permalink -!plugins/talk-plugin-profile-settings !plugins/talk-plugin-profile-data !plugins/talk-plugin-remember-sort !plugins/talk-plugin-respect diff --git a/docs/source/api/slots.md b/docs/source/api/slots.md index da57c0ad6..a2e7cc04d 100644 --- a/docs/source/api/slots.md +++ b/docs/source/api/slots.md @@ -33,8 +33,7 @@ By default, Talk has various plugins provided by default. We can see this in `pl "talk-plugin-sort-most-respected", "talk-plugin-sort-newest", "talk-plugin-sort-oldest", - "talk-plugin-viewing-options", - "talk-plugin-profile-settings" + "talk-plugin-viewing-options" ] } ``` diff --git a/docs/source/integrating/configuring-comment-stream.md b/docs/source/integrating/configuring-comment-stream.md index 110560de0..613535a13 100644 --- a/docs/source/integrating/configuring-comment-stream.md +++ b/docs/source/integrating/configuring-comment-stream.md @@ -65,10 +65,6 @@ First, you'll enable `talk-plugin-author-menu`, as this houses the Ignore button And then we will enable the Ignore User plugin: `talk-plugin-ignore-user`. -And finally, we will need to enable Profile Settings; this is the tab on My Profile > Settings where commenters can manage their Ignored Users list. - -`talk-plugin-profile-settings` - ### Featured Comments To enable the featuring of comments, you'll need to activate `talk-plugin-featured-comments`. If you would like the Featured Comments tab to be the default tab you land on for the stream, you will need to set the default tab ENV variable: diff --git a/docs/source/integrating/notifications.md b/docs/source/integrating/notifications.md index 470632907..a5f625083 100644 --- a/docs/source/integrating/notifications.md +++ b/docs/source/integrating/notifications.md @@ -36,10 +36,6 @@ Adding the `talk-plugin-notifications` plugin will also enable the `notification See https://github.com/coralproject/talk/blob/8b669a31c551a042f0f079d8cfc16825673eb8f0/plugins/talk-plugin-notifications-reply/index.js for an example. -### Commenter Notification Settings - -Note that notifications REQUIRE the `talk-plugin-profile-settings` plugin; this is where on My Profile commenters will enable and manage their notification settings. - ### Notification Categories Talk currently supports the following Notifications options out of the box: From 8f6691f7a4ea9448d55675fc74a5414f46ecfb0e Mon Sep 17 00:00:00 2001 From: okbel Date: Thu, 26 Apr 2018 11:15:18 -0300 Subject: [PATCH 30/63] InputField refactor --- .../components/ChangeUsername.js | 44 ++++++++----------- .../components/ChangeUsernameDialog.js | 12 ++--- .../components/InputField.css | 39 ++++++++++------ .../profile-settings/components/InputField.js | 41 ++++++++++++----- 4 files changed, 81 insertions(+), 55 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index d9f2fc15d..b5bd8916b 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -7,6 +7,7 @@ import ChangeUsernameDialog from './ChangeUsernameDialog'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; import { t } from 'plugin-api/beta/client/services'; +import InputField from './InputField'; const initialState = { editing: false, @@ -139,34 +140,27 @@ class ChangeUsername extends React.Component { {editing ? (
      -
    • - + Usernames can be changed every 14 days -
    • -
    • - -
    • + +
    ) : ( diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js index d5e50b4e8..22c74bce3 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js @@ -7,18 +7,18 @@ import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; class ChangeUsernameDialog extends React.Component { state = { - showErrors: false, + showError: false, }; - showErrors = () => { + showError = () => { this.setState({ - showErrors: true, + showError: true, }); }; confirmChanges = async () => { if (this.formHasError()) { - this.showErrors(); + this.showError(); } else { // await this.props.saveChanges this.props.closeDialog(); @@ -59,9 +59,9 @@ class ChangeUsernameDialog extends React.Component { type="text" onChange={this.props.onChange} value={this.props.formData.confirmNewUsername} - hasError={this.formHasError() && this.state.showErrors} + hasError={this.formHasError() && this.state.showError} errorMsg={'Username does not match'} - showErrors={this.state.showErrors} + showError={this.state.showError} columnDisplay showSuccess={false} validationType="username" diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css index b2648e116..f76970045 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css @@ -16,7 +16,24 @@ } .detailItemContent { - min-width: 280px; + border: solid 1px #787D80; + border-radius: 2px; + background-color: white; + height: 30px; + display: inline-block; + width: 230px; + display: flex; + + > .detailIcon { + font-size: 1.2em; + padding: 0 5px; + color: #787D80; + line-height: 30px; + } + + &.disabled { + background-color: #E0E0E0; + } } .detailLabel { @@ -27,19 +44,13 @@ } .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%; - - &.error { - border-color: #FA4643; - } + background: transparent; + border: none; + font-size: 1em; + color: #000; + height: 30px; + outline: none; + flex: 1; } .detailItemMessage { diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js index 511b89227..7fa9a368a 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js @@ -11,35 +11,53 @@ const InputField = ({ type = 'text', name = '', onChange = () => {}, - value = '', showError = true, hasError = false, errorMsg = '', children, columnDisplay = false, - showSuccess = true, + showSuccess = false, validationType = '', + icon = '', + value = '', + defaultValue = '', + disabled = false, }) => { + const inputValue = { + ...(value ? { value } : {}), + ...(defaultValue ? { defaultValue } : {}), + }; + return ( -
  • +
    -
    + {label && ( + )} +
    + {icon && }
    @@ -50,17 +68,20 @@ const InputField = ({
    {children} -
  • +
    ); }; InputField.propTypes = { - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, + id: PropTypes.string, + disabled: PropTypes.boolean, + label: PropTypes.string, + type: PropTypes.string, name: PropTypes.string.isRequired, onChange: PropTypes.func, value: PropTypes.string, + defaultValue: PropTypes.string, + icon: PropTypes.string, showError: PropTypes.bool, hasError: PropTypes.bool, errorMsg: PropTypes.string, From 2db9489646a9f87de1a68d6c809ef4d9d3b2c2cc Mon Sep 17 00:00:00 2001 From: okbel Date: Thu, 26 Apr 2018 11:15:35 -0300 Subject: [PATCH 31/63] Adding translations --- .../components/ChangePassword.js | 5 +- .../components/ChangeUsername.js | 79 ++++--------------- .../components/ChangeUsernameDialog.js | 14 +++- .../profile-settings/components/Form.css | 5 -- .../profile-settings/components/Form.js | 16 ---- .../talk-plugin-auth/client/translations.yml | 7 ++ 6 files changed, 34 insertions(+), 92 deletions(-) delete mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/Form.css delete mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/Form.js 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 3479b5dd9..90940728d 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -7,7 +7,6 @@ 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'; import { getErrorMessages } from 'coral-framework/utils'; @@ -148,7 +147,7 @@ class ChangePassword extends React.Component { {t('talk-plugin-auth.change_password.change_password')} {editing && ( -
    + - + )} {editing ? (
    diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index b5bd8916b..4fcca70fd 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -2,17 +2,14 @@ import React from 'react'; import cn from 'classnames'; import PropTypes from 'prop-types'; import styles from './ChangeUsername.css'; -import { Icon, Button } from 'plugin-api/beta/client/components/ui'; +import { Button } from 'plugin-api/beta/client/components/ui'; import ChangeUsernameDialog from './ChangeUsernameDialog'; -import validate from 'coral-framework/helpers/validate'; -import errorMsj from 'coral-framework/helpers/error'; import { t } from 'plugin-api/beta/client/services'; import InputField from './InputField'; const initialState = { editing: false, showDialog: false, - errors: {}, formData: {}, }; @@ -54,63 +51,15 @@ class ChangeUsername extends React.Component { // savechanges }; - fieldValidation = (value, type, name) => { - if (!value.length) { - this.addError({ - [name]: t('talk-plugin-auth.change_password.required_field'), - }); - } else if (!validate[type](value)) { - this.addError({ [name]: errorMsj[type] }); - } else { - this.removeError(name); - } - }; - - hasError = err => { - return Object.keys(this.state.errors).indexOf(err) !== -1; - }; - - addError = err => { - this.setState( - ({ errors }) => ({ - errors: { ...errors, ...err }, - }), - () => { - console.log(this.state); - } - ); - }; - - removeError = errKey => { - this.setState( - state => { - const { [errKey]: _, ...errors } = state.errors; - return { - errors, - }; - }, - () => { - console.log(this.state); - } - ); - }; - onChange = e => { - const { name, value, type, dataset: { validationType } } = e.target; + const { name, value } = e.target; - this.setState( - state => ({ - formData: { - ...state.formData, - [name]: value, - }, - }), - () => { - const fieldType = !validationType ? type : validationType; - this.fieldValidation(value, fieldType, name); - // the username cannot be the same - } - ); + this.setState(state => ({ + formData: { + ...state.formData, + [name]: value, + }, + })); }; closeDialog = () => { @@ -139,7 +88,7 @@ class ChangeUsername extends React.Component { {editing ? (
    -
      +
      - Usernames can be changed every 14 days + {t('talk-plugin-auth.change_username.change_username_note')} -
    +
    ) : (
    @@ -180,10 +129,10 @@ class ChangeUsername extends React.Component { onClick={this.onSave} disabled={!this.state.formData.newUsername} > - Save + {t('talk-plugin-auth.change_username.save')} - Cancel + {t('talk-plugin-auth.change_username.cancel')}
    ) : ( @@ -193,7 +142,7 @@ class ChangeUsername extends React.Component { icon="settings" onClick={this.enableEditing} > - Edit Profile + {t('talk-plugin-auth.change_username.edit_profile')}
    )} diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js index 22c74bce3..a561106c9 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js @@ -1,8 +1,8 @@ import React from 'react'; +import PropTypes from 'prop-types'; import cn from 'classnames'; import styles from './ChangeUsernameDialog.css'; import InputField from './InputField'; -import Form from './Form'; import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; class ChangeUsernameDialog extends React.Component { @@ -51,7 +51,7 @@ class ChangeUsernameDialog extends React.Component { New Username: {this.props.formData.newUsername} -
    + -
    +
    diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index 84dc4e320..0d05d45c5 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -145,6 +145,12 @@ en: save: "Save" edit_profile: "Edit Profile" cancel: "Cancel" + confirm_username_change: "Confirm Username Change" + description: "You are attempting to change your username. Your new username will appear on all of your past and future comments." + old_username: "Old Username" + new_username: "New Username" + bottom_note: "Note: You will not be able to change your username again for 14 days" + confirm_changes: "Confirm Changes" de: talk-plugin-auth: login: From ba7de6bf81f2cb96095a5a5061119451aee2fec8 Mon Sep 17 00:00:00 2001 From: okbel Date: Thu, 26 Apr 2018 11:25:40 -0300 Subject: [PATCH 33/63] Translations --- .../client/profile-settings/components/ChangeUsername.js | 5 ++++- .../profile-settings/components/ChangeUsernameDialog.js | 2 +- .../client/profile-settings/components/InputField.css | 4 ++++ .../client/profile-settings/components/InputField.js | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index 4fcca70fd..fbc8bfb1b 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -127,7 +127,10 @@ class ChangeUsername extends React.Component { className={cn(styles.button, styles.saveButton)} icon="save" onClick={this.onSave} - disabled={!this.state.formData.newUsername} + disabled={ + !this.state.formData.newUsername && + this.state.formData.newUsername !== username + } > {t('talk-plugin-auth.change_username.save')} diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js index 244dba00a..5bcc43241 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js @@ -92,7 +92,7 @@ class ChangeUsernameDialog extends React.Component { ChangeUsernameDialog.propTypes = { closeDialog: PropTypes.func, - showDialog: PropTypes.func, + showDialog: PropTypes.bool, onChange: PropTypes.func, username: PropTypes.string, formData: PropTypes.object, diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css index f76970045..ff0b47cf1 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.css @@ -31,6 +31,10 @@ line-height: 30px; } + &.error { + border: solid 2px #FA4643; + } + &.disabled { background-color: #E0E0E0; } diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js index 7fa9a368a..34c314c20 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/InputField.js @@ -74,7 +74,7 @@ const InputField = ({ InputField.propTypes = { id: PropTypes.string, - disabled: PropTypes.boolean, + disabled: PropTypes.bool, label: PropTypes.string, type: PropTypes.string, name: PropTypes.string.isRequired, From 8643b1fbf2b49dcc93b2307451c8e93a23b94375 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Thu, 26 Apr 2018 17:52:03 +0200 Subject: [PATCH 34/63] Decrease font-size and padding in Flag Comment popup a bit. --- client/coral-embed-stream/style/default.css | 4 ++-- client/coral-ui/components/PopupMenu.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 98416d68d..b9330ba62 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -177,8 +177,8 @@ button.comment__action-button[disabled], } .talk-plugin-flags-popup-header { - font-weight: bolder; - font-size: 1.33rem; + font-weight: bold; + font-size: 1rem; margin-bottom: 10px; } diff --git a/client/coral-ui/components/PopupMenu.css b/client/coral-ui/components/PopupMenu.css index 35544fbad..e6a084736 100644 --- a/client/coral-ui/components/PopupMenu.css +++ b/client/coral-ui/components/PopupMenu.css @@ -9,7 +9,7 @@ box-sizing: border-box; background: white; border-radius: 3px; - padding: 20px 10px; + padding: 10px 10px; z-index: 300; right: 1%; } From ab02b1b6ca5ecce778f2f7bb785a34120c1a3c47 Mon Sep 17 00:00:00 2001 From: okbel Date: Thu, 26 Apr 2018 17:00:08 -0300 Subject: [PATCH 35/63] No empty username, no same username --- .../client/profile-settings/components/ChangeUsername.js | 5 ++--- .../client/profile-settings/components/InputField.css | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js index fbc8bfb1b..8423ed403 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -120,7 +120,6 @@ class ChangeUsername extends React.Component { ) : null} )} - {editing ? (
    +