From d5fa7455e5d905aff4126d7fa6b2902ee6fd547e Mon Sep 17 00:00:00 2001 From: okbel Date: Tue, 1 May 2018 17:55:01 -0300 Subject: [PATCH] Adding InputField --- .../client/components/ErrorMessage.css | 8 + .../client/components/ErrorMessage.js | 17 ++ .../client/components/InputField.css | 80 +++++++ .../client/components/InputField.js | 94 ++++++++ .../components/ChangePassword.css | 87 +++++++ .../components/ChangePassword.js | 223 ++++++++++++++++++ .../components/ChangeUsername.css | 122 ++++++++++ .../components/ChangeUsername.js | 188 +++++++++++++++ .../components/ChangeUsernameDialog.css | 84 +++++++ .../components/ChangeUsernameDialog.js | 117 +++++++++ .../components/DeleteMyAccountDialog.js | 15 +- .../components/DeleteMyAccountStep3.js | 48 +++- .../components/ErrorMessage.css | 8 + .../components/ErrorMessage.js | 17 ++ .../components/InputField.css | 80 +++++++ .../profile-settings/components/InputField.js | 94 ++++++++ .../containers/ChangePassword.js | 12 + .../containers/ChangeUsername.js | 12 + 18 files changed, 1302 insertions(+), 4 deletions(-) create mode 100644 plugins/talk-plugin-auth/client/components/ErrorMessage.css create mode 100644 plugins/talk-plugin-auth/client/components/ErrorMessage.js create mode 100644 plugins/talk-plugin-auth/client/components/InputField.css create mode 100644 plugins/talk-plugin-auth/client/components/InputField.js 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/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/components/ChangeUsernameDialog.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js 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/InputField.css create mode 100644 plugins/talk-plugin-auth/client/profile-settings/components/InputField.js create mode 100644 plugins/talk-plugin-auth/client/profile-settings/containers/ChangePassword.js create mode 100644 plugins/talk-plugin-auth/client/profile-settings/containers/ChangeUsername.js diff --git a/plugins/talk-plugin-auth/client/components/ErrorMessage.css b/plugins/talk-plugin-auth/client/components/ErrorMessage.css new file mode 100644 index 000000000..039ffae30 --- /dev/null +++ b/plugins/talk-plugin-auth/client/components/ErrorMessage.css @@ -0,0 +1,8 @@ +.errorMsg { + color: #FA4643; + font-size: 0.9em; +} + +.warningIcon { + color: #FA4643; +} diff --git a/plugins/talk-plugin-auth/client/components/ErrorMessage.js b/plugins/talk-plugin-auth/client/components/ErrorMessage.js new file mode 100644 index 000000000..f39a8fc08 --- /dev/null +++ b/plugins/talk-plugin-auth/client/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/components/InputField.css b/plugins/talk-plugin-auth/client/components/InputField.css new file mode 100644 index 000000000..3442befde --- /dev/null +++ b/plugins/talk-plugin-auth/client/components/InputField.css @@ -0,0 +1,80 @@ + +.detailItem { + margin-bottom: 12px; +} + +.detailItemContainer { + display: flex; +} + +.columnDisplay { + flex-direction: column; + + .detailItemMessage { + padding: 4px 0 0; + } +} + +.detailItemContent { + border: solid 1px #787D80; + border-radius: 2px; + background-color: white; + height: 30px; + display: inline-block; + width: 230px; + display: flex; + box-sizing: border-box; + + > .detailIcon { + font-size: 1.2em; + padding: 0 5px; + color: #787D80; + line-height: 30px; + } + + &.error { + border: solid 2px #FA4643; + } + + &.disabled { + background-color: #E0E0E0; + } +} + +.detailLabel { + color: #4C4C4D; + font-size: 1em; + display: block; + margin-bottom: 4px; +} + +.detailValue { + background: transparent; + border: none; + font-size: 1em; + color: #000; + outline: none; + flex: 1; + height: 100%; + box-sizing: border-box; +} + +.detailItemMessage { + flex-grow: 1; + display: flex; + align-items: center; + padding-left: 6px; + padding-top: 16px; + + .warningIcon, .checkIcon { + font-size: 17px; + } +} + +.checkIcon { + color: #00CD73; +} + +.warningIcon { + color: #FA4643; +} diff --git a/plugins/talk-plugin-auth/client/components/InputField.js b/plugins/talk-plugin-auth/client/components/InputField.js new file mode 100644 index 000000000..26937d5b9 --- /dev/null +++ b/plugins/talk-plugin-auth/client/components/InputField.js @@ -0,0 +1,94 @@ +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'; + +const InputField = ({ + id = '', + label = '', + type = 'text', + name = '', + onChange = () => {}, + showError = true, + hasError = false, + errorMsg = '', + children, + columnDisplay = false, + showSuccess = false, + validationType = '', + icon = '', + value = '', + defaultValue = '', + disabled = false, +}) => { + const inputValue = { + ...(value ? { value } : {}), + ...(defaultValue ? { defaultValue } : {}), + }; + + return ( +
+
+ {label && ( + + )} +
+ {icon && } + +
+
+ {!hasError && + showSuccess && + value && } + {hasError && showError && {errorMsg}} +
+
+ {children} +
+ ); +}; + +InputField.propTypes = { + id: PropTypes.string, + disabled: PropTypes.bool, + 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, + children: PropTypes.node, + columnDisplay: PropTypes.bool, + showSuccess: PropTypes.bool, + validationType: PropTypes.string, +}; + +export default InputField; 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..6c7f9ae41 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.css @@ -0,0 +1,87 @@ +.container { + position: relative; + color: #202020; + padding: 10px; + border-radius: 2px; + border: solid 1px transparent; + box-sizing: border-box; + justify-content: space-between; + + &.editing { + border-color: #979797; + background-color: #EDEDED; + } +} + +.actions { + position: absolute; + top: 10px; + right: 10px; + display: flex; + flex-direction: column; + align-items: center; +} + +.title { + color: #202020; + margin: 0 0 20px; +} + +.detailBottomBox { + display: block; + padding-top: 4px; + text-align: right; + width: 280px; +} + +.detailLink { + color: #00538A; + text-decoration: none; + font-size: 0.9em; + &:hover { + cursor: pointer; + } +} + +.button { + border: 1px solid #787d80; + background-color: transparent; + height: 30px; + font-size: 0.9em; + line-height: normal; +} + +.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; + } +} 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..90940728d --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangePassword.js @@ -0,0 +1,223 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cn from 'classnames'; +import styles from './ChangePassword.css'; +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 InputField from './InputField'; +import { getErrorMessages } from 'coral-framework/utils'; + +const initialState = { + editing: false, + showErrors: true, + errors: {}, + formData: {}, +}; + +class ChangePassword extends React.Component { + state = initialState; + validKeys = ['oldPassword', 'newPassword', 'confirmNewPassword']; + + onChange = e => { + const { name, value, type } = e.target; + this.setState( + state => ({ + formData: { + ...state.formData, + [name]: value, + }, + }), + () => { + this.fieldValidation(value, type, name); + + // Perform equality validation if password fields have changed + if (name === 'newPassword' || name === 'confirmNewPassword') { + this.equalityValidation('newPassword', 'confirmNewPassword'); + } + } + ); + }; + + equalityValidation = (field, field2) => { + const cond = this.state.formData[field] === this.state.formData[field2]; + if (!cond) { + this.addError({ + [field2]: t('talk-plugin-auth.change_password.passwords_dont_match'), + }); + } else { + this.removeError(field2); + } + return cond; + }; + + 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 }, + })); + }; + + removeError = errKey => { + this.setState(state => { + const { [errKey]: _, ...errors } = state.errors; + return { + errors, + }; + }); + }; + + enableEditing = () => { + this.setState({ + editing: true, + }); + }; + + isSubmitBlocked = () => { + const formHasErrors = !!Object.keys(this.state.errors).length; + const formIncomplete = !isEqual( + Object.keys(this.state.formData), + this.validKeys + ); + return formHasErrors || formIncomplete; + }; + + clearForm = () => { + this.setState(initialState); + }; + + onSave = async () => { + const { oldPassword, newPassword } = this.state.formData; + + 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(); + }; + + disableEditing = () => { + this.setState({ + editing: false, + }); + }; + + cancel = () => { + this.clearForm(); + this.disableEditing(); + }; + + render() { + const { editing, errors } = this.state; + + return ( +
+

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

+ {editing && ( +
+ + + + {t('talk-plugin-auth.change_password.forgot_password')} + + + + + + + )} + {editing ? ( +
+ + + {t('talk-plugin-auth.change_password.cancel')} + +
+ ) : ( +
+ +
+ )} +
+ ); + } +} + +ChangePassword.propTypes = { + changePassword: PropTypes.func.isRequired, + notify: PropTypes.func.isRequired, +}; + +export default 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..5b4631ecf --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.css @@ -0,0 +1,122 @@ +.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 { + margin-bottom: 4px; +} + +.button { + border: 1px solid #787d80; + background-color: transparent; + height: 30px; + font-size: 0.9em; + line-height: normal; +} + +.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; +} + +.detailList { + list-style: none; + margin: 0; + padding: 0; +} + +.detailItem { + 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 new file mode 100644 index 000000000..8188bdfbe --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsername.js @@ -0,0 +1,188 @@ +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 ChangeUsernameDialog from './ChangeUsernameDialog'; +import { t } from 'plugin-api/beta/client/services'; +import InputField from './InputField'; +import { getErrorMessages } from 'coral-framework/utils'; +import { canUsernameBeUpdated } from 'coral-framework/utils/user'; + +const initialState = { + editing: false, + showDialog: false, + formData: {}, +}; + +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(); + }; + + saveChanges = async () => { + const { newUsername } = this.state.formData; + const { changeUsername } = this.props; + + try { + await changeUsername(newUsername); + this.props.notify( + 'success', + t('talk-plugin-auth.change_username.changed_username_success_msg') + ); + } catch (err) { + this.props.notify('error', getErrorMessages(err)); + } + + this.clearForm(); + this.disableEditing(); + }; + + onChange = e => { + const { name, value } = e.target; + + this.setState(state => ({ + formData: { + ...state.formData, + [name]: value, + }, + })); + }; + + closeDialog = () => { + this.setState({ + showDialog: false, + }); + }; + + render() { + const { + username, + emailAddress, + root: { me: { state: { status } } }, + notify, + } = this.props; + const { editing, formData, showDialog } = this.state; + + return ( +
+ + + {editing ? ( +
+
+ + + {t('talk-plugin-auth.change_username.change_username_note')} + + + +
+
+ ) : ( +
+

{username}

+ {emailAddress ? ( +

{emailAddress}

+ ) : null} +
+ )} + {editing ? ( +
+ + + {t('talk-plugin-auth.change_username.cancel')} + +
+ ) : ( +
+ +
+ )} +
+ ); + } +} + +ChangeUsername.propTypes = { + root: PropTypes.object.isRequired, + changeUsername: PropTypes.func.isRequired, + notify: PropTypes.func.isRequired, + username: PropTypes.string, + emailAddress: PropTypes.string, +}; + +export default ChangeUsername; 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..af681d596 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.css @@ -0,0 +1,84 @@ +.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; + padding-top: 10px; + display: block; +} + +.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..321b36926 --- /dev/null +++ b/plugins/talk-plugin-auth/client/profile-settings/components/ChangeUsernameDialog.js @@ -0,0 +1,117 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import cn from 'classnames'; +import styles from './ChangeUsernameDialog.css'; +import InputField from './InputField'; +import { Button, Dialog } from 'plugin-api/beta/client/components/ui'; +import { t } from 'plugin-api/beta/client/services'; + +class ChangeUsernameDialog extends React.Component { + state = { + showError: false, + }; + + showError = () => { + this.setState({ + showError: true, + }); + }; + + confirmChanges = async () => { + if (this.formHasError()) { + this.showError(); + return; + } + + if (!this.props.canUsernameBeUpdated) { + this.props.notify( + 'error', + t('talk-plugin-auth.change_username.change_username_attempt') + ); + return; + } + + await this.props.saveChanges(); + this.props.closeDialog(); + }; + + formHasError = () => + this.props.formData.confirmNewUsername !== this.props.formData.newUsername; + + render() { + return ( + + + × + +

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

+
+

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

+
+ + {t('talk-plugin-auth.change_username.old_username')}:{' '} + {this.props.username} + + + {t('talk-plugin-auth.change_username.new_username')}:{' '} + {this.props.formData.newUsername} + +
+
+ + + {t('talk-plugin-auth.change_username.bottom_note')} + + +
+
+ + +
+
+
+ ); + } +} + +ChangeUsernameDialog.propTypes = { + saveChanges: PropTypes.func, + closeDialog: PropTypes.func, + showDialog: PropTypes.bool, + onChange: PropTypes.func, + username: PropTypes.string, + formData: PropTypes.object, + canUsernameBeUpdated: PropTypes.bool.isRequired, + notify: PropTypes.func.isRequired, +}; + +export default ChangeUsernameDialog; diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountDialog.js b/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountDialog.js index b9a1bfc9d..35069384c 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountDialog.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountDialog.js @@ -9,7 +9,7 @@ import DeleteMyAccountStep2 from './DeleteMyAccountStep2'; import DeleteMyAccountStep3 from './DeleteMyAccountStep3'; import DeleteMyAccountFinalStep from './DeleteMyAccountFinalStep'; -const initialState = { step: 0 }; +const initialState = { step: 0, formData: {} }; class DeleteMyAccountDialog extends React.Component { state = initialState; @@ -29,6 +29,17 @@ class DeleteMyAccountDialog extends React.Component { this.props.closeDialog(); }; + onChange = e => { + const { name, value } = e.target; + + this.setState(state => ({ + formData: { + ...state.formData, + [name]: value, + }, + })); + }; + render() { const { step } = this.state; return ( @@ -58,9 +69,11 @@ class DeleteMyAccountDialog extends React.Component { )} {step === 3 && ( )} {step === 4 && } diff --git a/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountStep3.js b/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountStep3.js index 63a00243b..45dca8f97 100644 --- a/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountStep3.js +++ b/plugins/talk-plugin-auth/client/profile-settings/components/DeleteMyAccountStep3.js @@ -3,13 +3,41 @@ import PropTypes from 'prop-types'; import cn from 'classnames'; import { Button } from 'plugin-api/beta/client/components/ui'; import styles from './DeleteMyAccountStep.css'; +import InputField from '../../components/InputField'; + +const initialState = { + showError: false, +}; class DeleteMyAccountStep3 extends React.Component { - deleteAndContinue = () => { - this.props.requestAccountDeletion(); + state = initialState; + phrase = 'delete'; + + showError = () => { + this.setState({ + showError: true, + }); + }; + + clear = () => { + this.setState(initialState); + }; + + deleteAndContinue = async () => { + if (this.formHasError()) { + this.showError(); + return; + } + + await this.props.requestAccountDeletion(); + this.clear(); this.props.goToNextStep(); }; + formHasError = () => + !this.props.formData.confirmPhrase || + this.props.formData.confirmPhrase !== this.phrase; + render() { return (
@@ -24,7 +52,19 @@ class DeleteMyAccountStep3 extends React.Component { className={styles.textBox} disabled={true} readOnly={true} - value="delete" + value={this.phrase} + /> +