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 && (
+
+ )}
+ {editing ? (
+
+ ) : (
+
+
+
+ )}
+
+ );
+ }
+}
+
+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 ? (
+
+ ) : (
+
+
+
+ )}
+
+ );
+ }
+}
+
+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 (
+
+ );
+ }
+}
+
+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}
+ />
+