From 890da0400f8657cfa55a3681aee00c96ad3cbbc9 Mon Sep 17 00:00:00 2001
From: Belen Curcio
Date: Tue, 31 Jan 2017 16:31:29 -0300
Subject: [PATCH 01/52] Steps
---
client/coral-admin/src/AppRouter.js | 2 ++
client/coral-admin/src/actions/install.js | 4 +++
client/coral-admin/src/constants/install.js | 2 ++
.../containers/Install/InstallContainer.js | 30 +++++++++++++++++++
.../src/containers/Install/index.js | 0
.../src/containers/Install/style.css | 0
client/coral-admin/src/reducers/index.js | 4 ++-
client/coral-admin/src/reducers/install.js | 21 +++++++++++++
8 files changed, 62 insertions(+), 1 deletion(-)
create mode 100644 client/coral-admin/src/actions/install.js
create mode 100644 client/coral-admin/src/constants/install.js
create mode 100644 client/coral-admin/src/containers/Install/InstallContainer.js
create mode 100644 client/coral-admin/src/containers/Install/index.js
create mode 100644 client/coral-admin/src/containers/Install/style.css
create mode 100644 client/coral-admin/src/reducers/install.js
diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js
index 9c0f271cc..ebb79b4e1 100644
--- a/client/coral-admin/src/AppRouter.js
+++ b/client/coral-admin/src/AppRouter.js
@@ -7,6 +7,7 @@ import Configure from 'containers/Configure/Configure';
import Streams from 'containers/Streams/Streams';
import CommunityContainer from 'containers/Community/CommunityContainer';
import LayoutContainer from 'containers/LayoutContainer';
+import InstallContainer from 'containers/Install/InstallContainer';
const routes = (
@@ -15,6 +16,7 @@ const routes = (
+
);
diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js
new file mode 100644
index 000000000..460a36e23
--- /dev/null
+++ b/client/coral-admin/src/actions/install.js
@@ -0,0 +1,4 @@
+import * as actions from '../constants/install';
+
+export const nextStep = () => ({type: actions.NEXT_STEP});
+export const previousStep = () => ({type: actions.PREVIOUS_STEP});
diff --git a/client/coral-admin/src/constants/install.js b/client/coral-admin/src/constants/install.js
new file mode 100644
index 000000000..f15a39ed4
--- /dev/null
+++ b/client/coral-admin/src/constants/install.js
@@ -0,0 +1,2 @@
+export const NEXT_STEP = 'NEXT_STEP';
+export const PREVIOUS_STEP = 'PREVIOUS_STEP';
diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js
new file mode 100644
index 000000000..de296acc0
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/InstallContainer.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import {connect} from 'react-redux';
+import styles from './style.css';
+import {nextStep, previousStep} from '../../actions/install'
+
+const InstallContainer = props => {
+ const {nextStep, previousStep, install} = props;
+
+ return (
+
+ Install:
+
+ {install.step}
+
+ Previous Step
+ Next Step
+
+ );
+};
+
+const mapStateToProps = state => ({
+ install: state.install.toJS()
+});
+
+const mapDispatchToProps = dispatch => ({
+ nextStep: () => dispatch(nextStep()),
+ previousStep: () => dispatch(previousStep())
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(InstallContainer);
diff --git a/client/coral-admin/src/containers/Install/index.js b/client/coral-admin/src/containers/Install/index.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/containers/Install/style.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js
index a19a5a087..1943e5976 100644
--- a/client/coral-admin/src/reducers/index.js
+++ b/client/coral-admin/src/reducers/index.js
@@ -6,6 +6,7 @@ import users from 'reducers/users';
import auth from 'reducers/auth';
import actions from 'reducers/actions';
import assets from 'reducers/assets';
+import install from 'reducers/install';
// Combine all reducers into a main one
export default combineReducers({
@@ -15,5 +16,6 @@ export default combineReducers({
auth,
actions,
assets,
- users
+ users,
+ install
});
diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js
new file mode 100644
index 000000000..1b9ae5b28
--- /dev/null
+++ b/client/coral-admin/src/reducers/install.js
@@ -0,0 +1,21 @@
+import {Map} from 'immutable';
+
+import * as actions from '../constants/install';
+
+const initialState = Map({
+ step: 0
+});
+
+export default function auth (state = initialState, action) {
+ switch (action.type) {
+ case actions.NEXT_STEP:
+ console.log(state)
+ return state
+ .set('step', state.get('step') + 1);
+ case actions.PREVIOUS_STEP:
+ return state
+ .set('step', state.get('step') - 1);
+ default :
+ return state;
+ }
+}
From 6a4315d60826fd0f2bfd4a884656cac0f1dd5fa3 Mon Sep 17 00:00:00 2001
From: Belen Curcio
Date: Wed, 1 Feb 2017 10:49:16 -0300
Subject: [PATCH 02/52] Working Install Wizard
---
client/coral-admin/src/actions/install.js | 1 +
client/coral-admin/src/constants/install.js | 1 +
.../containers/Install/InstallContainer.js | 29 +++++++++++++------
.../components/Steps/AddOrganizationName.js | 19 ++++++++++++
.../Install/components/Steps/InitialStep.js | 20 +++++++++++++
client/coral-admin/src/reducers/install.js | 3 ++
client/coral-ui/components/Wizard.js | 29 +++++++++++++++++++
client/coral-ui/index.js | 1 +
8 files changed, 94 insertions(+), 9 deletions(-)
create mode 100644 client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
create mode 100644 client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
create mode 100644 client/coral-ui/components/Wizard.js
diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js
index 460a36e23..abaaeceb7 100644
--- a/client/coral-admin/src/actions/install.js
+++ b/client/coral-admin/src/actions/install.js
@@ -2,3 +2,4 @@ import * as actions from '../constants/install';
export const nextStep = () => ({type: actions.NEXT_STEP});
export const previousStep = () => ({type: actions.PREVIOUS_STEP});
+export const goToStep = step => ({type: actions.GO_TO_STEP, step});
diff --git a/client/coral-admin/src/constants/install.js b/client/coral-admin/src/constants/install.js
index f15a39ed4..d33ec4657 100644
--- a/client/coral-admin/src/constants/install.js
+++ b/client/coral-admin/src/constants/install.js
@@ -1,2 +1,3 @@
export const NEXT_STEP = 'NEXT_STEP';
export const PREVIOUS_STEP = 'PREVIOUS_STEP';
+export const GO_TO_STEP = 'GO_TO_STEP';
diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js
index de296acc0..bda32bde2 100644
--- a/client/coral-admin/src/containers/Install/InstallContainer.js
+++ b/client/coral-admin/src/containers/Install/InstallContainer.js
@@ -1,19 +1,29 @@
import React from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
-import {nextStep, previousStep} from '../../actions/install'
+import {nextStep, previousStep, goToStep} from '../../actions/install';
+import {Button, Wizard} from 'coral-ui';
+
+import InitialStep from './components/Steps/InitialStep'
+import AddOrganizationName from './components/Steps/AddOrganizationName'
const InstallContainer = props => {
- const {nextStep, previousStep, install} = props;
+ const {nextStep, previousStep, goToStep, install} = props;
return (
- Install:
-
- {install.step}
-
-
Previous Step
-
Next Step
+
{install.step}
+
+
+ goToStep(0)}>Add Organization Name
+ goToStep(1)}>Create your account
+ goToStep(2)}>Invite team members
+
+
+
+
+
+
);
};
@@ -24,7 +34,8 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
nextStep: () => dispatch(nextStep()),
- previousStep: () => dispatch(previousStep())
+ previousStep: () => dispatch(previousStep()),
+ goToStep: step => dispatch(goToStep(step))
});
export default connect(mapStateToProps, mapDispatchToProps)(InstallContainer);
diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
new file mode 100644
index 000000000..1af4b9839
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
@@ -0,0 +1,19 @@
+import React from 'react';
+// import styles from './style.css';
+import {Button} from 'coral-ui';
+
+const InitialStep = props => {
+ const {nextStep, previousStep} = props;
+ return (
+
+
Welcome to the Coral Project
+
+ Please tell us the name of your organization. This will appear in emails when
+ inviting new team members
+
+
Go back
+
+ );
+};
+
+export default InitialStep;
diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
new file mode 100644
index 000000000..9efdf4ad0
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
@@ -0,0 +1,20 @@
+import React from 'react';
+// import styles from './style.css';
+import {Button} from 'coral-ui';
+
+const InitialStep = props => {
+ const {nextStep} = props;
+ return (
+
+
Welcome to the Coral Project
+
+ The remainder of the Talk installation will take about ten minutes.
+ Once you complete the following three steps, you will have a free
+ installation and provision of Mongo and Redis.
+
+
Get Started
+
+ );
+};
+
+export default InitialStep;
diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js
index 1b9ae5b28..95febb887 100644
--- a/client/coral-admin/src/reducers/install.js
+++ b/client/coral-admin/src/reducers/install.js
@@ -15,6 +15,9 @@ export default function auth (state = initialState, action) {
case actions.PREVIOUS_STEP:
return state
.set('step', state.get('step') - 1);
+ case actions.GO_TO_STEP:
+ return state
+ .set('step', action.step);
default :
return state;
}
diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js
new file mode 100644
index 000000000..1ba6e56a6
--- /dev/null
+++ b/client/coral-ui/components/Wizard.js
@@ -0,0 +1,29 @@
+import React, {PropTypes} from 'react';
+
+const Wizard = (props) => {
+ const {children, currentStep, nextStep, previousStep, goToStep, className = ''} = props;
+ return (
+
+ {React.Children.toArray(children)
+ .filter((child, i) => i === currentStep)
+ .map((child, i) =>
+ React.cloneElement(child, {
+ i,
+ currentStep,
+ nextStep,
+ previousStep,
+ goToStep
+ })
+ )}
+
+ );
+};
+
+Wizard.propTypes = {
+ currentStep: PropTypes.number.isRequired,
+ nextStep: PropTypes.func.isRequired,
+ previousStep: PropTypes.func.isRequired,
+ goToStep: PropTypes.func.isRequired
+}
+
+export default Wizard;
diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js
index 23d5d876a..b58c2a74a 100644
--- a/client/coral-ui/index.js
+++ b/client/coral-ui/index.js
@@ -14,3 +14,4 @@ export {default as List} from './components/List';
export {default as Item} from './components/Item';
export {default as Card} from './components/Card';
export {default as Pager} from './components/Pager';
+export {default as Wizard} from './components/Wizard';
From 12070309d6bd5db9fdf5e26e702c4fb90327b559 Mon Sep 17 00:00:00 2001
From: Belen Curcio
Date: Wed, 1 Feb 2017 12:11:15 -0300
Subject: [PATCH 03/52] Wizard Nav
---
.../containers/Install/InstallContainer.js | 29 +++--
.../components/Steps/AddOrganizationName.js | 13 ++-
.../Install/components/Steps/InitialStep.js | 13 +--
.../Install/components/Steps/style.css | 30 +++++
.../src/containers/Install/style.css | 12 ++
client/coral-admin/src/reducers/install.js | 1 -
client/coral-ui/components/Wizard.js | 4 +-
client/coral-ui/components/WizardNav.css | 109 ++++++++++++++++++
client/coral-ui/components/WizardNav.js | 29 +++++
client/coral-ui/index.js | 1 +
10 files changed, 215 insertions(+), 26 deletions(-)
create mode 100644 client/coral-admin/src/containers/Install/components/Steps/style.css
create mode 100644 client/coral-ui/components/WizardNav.css
create mode 100644 client/coral-ui/components/WizardNav.js
diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js
index bda32bde2..8ed0bbf69 100644
--- a/client/coral-admin/src/containers/Install/InstallContainer.js
+++ b/client/coral-admin/src/containers/Install/InstallContainer.js
@@ -2,24 +2,18 @@ import React from 'react';
import {connect} from 'react-redux';
import styles from './style.css';
import {nextStep, previousStep, goToStep} from '../../actions/install';
-import {Button, Wizard} from 'coral-ui';
+import {Wizard, WizardNav} from 'coral-ui';
-import InitialStep from './components/Steps/InitialStep'
-import AddOrganizationName from './components/Steps/AddOrganizationName'
+import InitialStep from './components/Steps/InitialStep';
+import AddOrganizationName from './components/Steps/AddOrganizationName';
const InstallContainer = props => {
const {nextStep, previousStep, goToStep, install} = props;
return (
-
{install.step}
-
-
- goToStep(0)}>Add Organization Name
- goToStep(1)}>Create your account
- goToStep(2)}>Invite team members
-
-
+
Welcome to the Coral Project
+ { install.step !== 0 ?
: null }
@@ -28,6 +22,19 @@ const InstallContainer = props => {
);
};
+const wizardNavitems = [{
+ text: '1. Add Organization Name',
+ step: 1
+},
+{
+ text: '2. Create your account',
+ step: 2
+},
+{
+ text: '3. Invite team members',
+ step: 3
+}];
+
const mapStateToProps = state => ({
install: state.install.toJS()
});
diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
index 1af4b9839..74f2c3016 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
@@ -1,17 +1,20 @@
import React from 'react';
-// import styles from './style.css';
+import styles from './style.css';
import {Button} from 'coral-ui';
const InitialStep = props => {
- const {nextStep, previousStep} = props;
+ const {nextStep} = props;
return (
-
-
Welcome to the Coral Project
+
Please tell us the name of your organization. This will appear in emails when
inviting new team members
-
Go back
+
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
index 9efdf4ad0..95043b515 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
@@ -1,18 +1,17 @@
import React from 'react';
-// import styles from './style.css';
+import styles from './style.css';
import {Button} from 'coral-ui';
const InitialStep = props => {
const {nextStep} = props;
return (
-
-
Welcome to the Coral Project
+
- The remainder of the Talk installation will take about ten minutes.
- Once you complete the following three steps, you will have a free
- installation and provision of Mongo and Redis.
+ The remainder of the Talk installation will take about ten minutes.
+ Once you complete the following three steps, you will have a free
+ installation and provision of Mongo and Redis.
-
Get Started
+
Get Started
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/containers/Install/components/Steps/style.css
new file mode 100644
index 000000000..5284b3470
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/components/Steps/style.css
@@ -0,0 +1,30 @@
+.step {
+
+ p {
+ max-width: 450px;
+ margin: 10px auto 30px;
+ }
+
+ > button {
+ min-width: 140px;
+ }
+
+ form {
+ max-width: 300px;
+ margin: 30px auto;
+
+ label {
+ text-align: left;
+ display: block;
+ }
+
+ input {
+ border: solid 1px rgba(0, 0, 0, 0.23);
+ padding: 10px 2px;
+ border-radius: 3px;
+ width: 100%;
+ box-sizing: border-box;
+ margin: 10px 0;
+ }
+ }
+}
diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/containers/Install/style.css
index e69de29bb..90c62c20e 100644
--- a/client/coral-admin/src/containers/Install/style.css
+++ b/client/coral-admin/src/containers/Install/style.css
@@ -0,0 +1,12 @@
+.Install {
+ max-width: 900px;
+ margin: 0 auto;
+ text-align: center;
+ padding: 40px 0;
+
+ h2 {
+ font-size: 2em;
+ font-weight: 500;
+ margin: 0;
+ }
+}
diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js
index 95febb887..d97c7f236 100644
--- a/client/coral-admin/src/reducers/install.js
+++ b/client/coral-admin/src/reducers/install.js
@@ -9,7 +9,6 @@ const initialState = Map({
export default function auth (state = initialState, action) {
switch (action.type) {
case actions.NEXT_STEP:
- console.log(state)
return state
.set('step', state.get('step') + 1);
case actions.PREVIOUS_STEP:
diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js
index 1ba6e56a6..e598b3c30 100644
--- a/client/coral-ui/components/Wizard.js
+++ b/client/coral-ui/components/Wizard.js
@@ -3,7 +3,7 @@ import React, {PropTypes} from 'react';
const Wizard = (props) => {
const {children, currentStep, nextStep, previousStep, goToStep, className = ''} = props;
return (
-
+
{React.Children.toArray(children)
.filter((child, i) => i === currentStep)
.map((child, i) =>
@@ -15,7 +15,7 @@ const Wizard = (props) => {
goToStep
})
)}
-
+
);
};
diff --git a/client/coral-ui/components/WizardNav.css b/client/coral-ui/components/WizardNav.css
new file mode 100644
index 000000000..12b9b3841
--- /dev/null
+++ b/client/coral-ui/components/WizardNav.css
@@ -0,0 +1,109 @@
+.WizardNav {
+ ul {
+ list-style: none;
+ }
+
+ li {
+ border: solid 1px #DFDFDF;
+ background-color: #F0F0F0;
+ display: inline-block;
+ padding: 10px;
+ margin-right: 10px;
+ color: #A7A7A7;
+ position: relative;
+ padding-left: 40px;
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ &.active {
+ background-color: #FFFFFF;
+ color: #636363;
+ font-weight: 500;
+
+ span {
+ &::after {
+ border-color: transparent transparent transparent #FFFFFF;
+ }
+ }
+ }
+
+ span {
+ padding: 10px 20px;
+ margin-right: 10px;
+ position: absolute;
+ top: -1px;
+ right: -51px;
+ z-index: 10;
+
+ &::before {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ border: 23px solid #DFDFDF;
+ border-color: transparent transparent transparent #DFDFDF;
+ top: 0;
+ left: 0;
+ }
+
+ &::after {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ border: 23px solid white;
+ border-color: transparent transparent transparent #f0f0f0;
+ top: 0;
+ left: -1px;
+ }
+ }
+
+ &::before {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ border: 23px solid #DFDFDF;
+ border-color: transparent transparent transparent #DFDFDF;
+ top: 0;
+ left: 0;
+ }
+
+ &::after {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ border: 23px solid white;
+ border-color: transparent transparent transparent #fafafa;
+ top: 0;
+ left: -1px;
+ }
+/*
+ &::before {
+ position: absolute;
+ top: 0;
+ left: -1px;
+ display: block;
+ border-left: 23px solid white;
+ border-top: 23px solid transparent;
+ border-bottom: 23px solid transparent;
+ width: 0;
+ height: 0;
+ content: " ";
+ }
+
+ &::after{
+ z-index: 1;
+ position: absolute;
+ top: 0;
+ right: -23px;
+ display: block;
+ border-left: 23px solid #f0f0f0;
+ border-top: 23px solid transparent;
+ border-bottom: 23px solid transparent;
+ width: 0;
+ height: 0;
+ content: " ";
+ }*/
+
+ }
+}
diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js
new file mode 100644
index 000000000..200adb216
--- /dev/null
+++ b/client/coral-ui/components/WizardNav.js
@@ -0,0 +1,29 @@
+import React, {PropTypes} from 'react';
+import styles from './WizardNav.css';
+
+const WizardNav = props => {
+ const {goToStep, currentStep, items} = props;
+ return (
+
+
+ {
+ items.map((item, i) => (
+ goToStep(item.step)}>
+ {item.text}
+
+ ))
+ }
+
+
+ );
+};
+
+WizardNav.propTypes = {
+ goToStep: PropTypes.func.isRequired,
+ currentStep: PropTypes.number.isRequired
+};
+
+export default WizardNav;
diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js
index b58c2a74a..69f63e397 100644
--- a/client/coral-ui/index.js
+++ b/client/coral-ui/index.js
@@ -15,3 +15,4 @@ export {default as Item} from './components/Item';
export {default as Card} from './components/Card';
export {default as Pager} from './components/Pager';
export {default as Wizard} from './components/Wizard';
+export {default as WizardNav} from './components/WizardNav';
From caa04de09df4c41b47c8e58301ce37889c5ab351 Mon Sep 17 00:00:00 2001
From: Belen Curcio
Date: Wed, 1 Feb 2017 12:12:28 -0300
Subject: [PATCH 04/52] linting and proptypes
---
client/coral-ui/components/Wizard.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js
index e598b3c30..429bce72c 100644
--- a/client/coral-ui/components/Wizard.js
+++ b/client/coral-ui/components/Wizard.js
@@ -1,7 +1,7 @@
import React, {PropTypes} from 'react';
const Wizard = (props) => {
- const {children, currentStep, nextStep, previousStep, goToStep, className = ''} = props;
+ const {children, currentStep, nextStep, previousStep, goToStep} = props;
return (
{React.Children.toArray(children)
@@ -24,6 +24,6 @@ Wizard.propTypes = {
nextStep: PropTypes.func.isRequired,
previousStep: PropTypes.func.isRequired,
goToStep: PropTypes.func.isRequired
-}
+};
export default Wizard;
From 6c01a37f6ab2c5ac20ef5c1caad66e934180dbc9 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 11:48:16 -0500
Subject: [PATCH 05/52] Removing user bio.
---
client/coral-embed-stream/src/Comment.js | 9 +--
client/coral-plugin-author-name/AuthorName.js | 27 +++++----
.../coral-settings/components/NotLoggedIn.js | 1 -
.../containers/SettingsContainer.js | 55 ++++++++++---------
4 files changed, 46 insertions(+), 46 deletions(-)
diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js
index 529420941..a2a4c4c6e 100644
--- a/client/coral-embed-stream/src/Comment.js
+++ b/client/coral-embed-stream/src/Comment.js
@@ -92,14 +92,7 @@ class Comment extends React.Component {
style={{marginLeft: depth * 30}}>
diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js
index f02badd80..4398ed5c6 100644
--- a/client/coral-settings/containers/SettingsContainer.js
+++ b/client/coral-settings/containers/SettingsContainer.js
@@ -4,12 +4,10 @@ import React, {Component} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import {myCommentHistory} from 'coral-framework/graphql/queries';
-import {saveBio} from 'coral-framework/actions/user';
-import BioContainer from './BioContainer';
import {link} from 'coral-framework/PymConnection';
import NotLoggedIn from '../components/NotLoggedIn';
-import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
+import {Spinner} from 'coral-ui';
import SettingsHeader from '../components/SettingsHeader';
import CommentHistory from 'coral-plugin-history/CommentHistory';
@@ -33,8 +31,7 @@ class SettingsContainer extends Component {
}
render() {
- const {loggedIn, userData, asset, showSignInDialog, data, user} = this.props;
- const {activeTab} = this.state;
+ const {loggedIn, asset, showSignInDialog, data} = this.props;
const {me} = this.props.data;
if (!loggedIn || !me) {
@@ -48,25 +45,30 @@ class SettingsContainer extends Component {
return (
-
- {lang.t('allComments')} ({user.myComments.length})
- {lang.t('profileSettings')}
-
-
- {
- me.comments.length ?
-
- :
- {lang.t('userNoComment')}
- }
-
-
-
-
+ {
+
+ // Hiding bio until moderation can get figured out
+ /*
+ {lang.t('allComments')} ({user.myComments.length})
+ {lang.t('profileSettings')}
+
+
*/
+ me.comments.length ?
+
+ :
+ {lang.t('userNoComment')}
+
+ // Hiding user bio pending effective moderation system.
+ /*
+
+
+ */
+ }
+
);
}
@@ -78,8 +80,9 @@ const mapStateToProps = state => ({
auth: state.auth.toJS()
});
-const mapDispatchToProps = dispatch => ({
- saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData))
+const mapDispatchToProps = () => ({
+
+ // saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData))
});
export default compose(
From 37975574ea0f214b1097134dc2962055b8f00671 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 12:26:01 -0500
Subject: [PATCH 06/52] Switching rejected usernames from suspended to banned.
---
.../src/containers/ModerationQueue/ModerationContainer.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
index eef855df6..aa754b4ac 100644
--- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
+++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
@@ -118,7 +118,7 @@ const mapDispatchToProps = dispatch => {
userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => {
dispatch(fetchModerationQueueComments());
}),
- suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId))
+ suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('BANNED', userId))
.then(() => dispatch(sendNotificationEmail(userId, subject, text)))
.then(() => dispatch(fetchModerationQueueComments()))
,
From 7866d1f8cdd79f6591edbad6cb25d96d4a90bd29 Mon Sep 17 00:00:00 2001
From: Belen Curcio
Date: Wed, 1 Feb 2017 15:17:15 -0300
Subject: [PATCH 07/52] Done Wizard mEnu
---
client/coral-admin/src/AppRouter.js | 18 ++++----
.../coral-admin/src/components/ui/Header.js | 35 ++++++++++-----
.../containers/Install/InstallContainer.js | 25 +++++++----
.../components/Steps/CreateYourAccount.js | 32 +++++++++++++
.../Install/components/Steps/FinalStep.js | 19 ++++++++
.../components/Steps/InviteTeamMembers.js | 35 +++++++++++++++
.../Install/components/Steps/style.css | 5 +++
client/coral-ui/components/WizardNav.css | 45 +++++++------------
client/coral-ui/components/WizardNav.js | 2 +-
9 files changed, 158 insertions(+), 58 deletions(-)
create mode 100644 client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
create mode 100644 client/coral-admin/src/containers/Install/components/Steps/FinalStep.js
create mode 100644 client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js
diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js
index ebb79b4e1..bebe6a19b 100644
--- a/client/coral-admin/src/AppRouter.js
+++ b/client/coral-admin/src/AppRouter.js
@@ -10,14 +10,16 @@ import LayoutContainer from 'containers/LayoutContainer';
import InstallContainer from 'containers/Install/InstallContainer';
const routes = (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
);
const AppRouter = () => ;
diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js
index 4e2b391c8..abfb0ff13 100644
--- a/client/coral-admin/src/components/ui/Header.js
+++ b/client/coral-admin/src/components/ui/Header.js
@@ -6,19 +6,32 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../translations.json';
import {Logo} from './Logo';
-export default ({handleLogout}) => (
+export default ({handleLogout, restricted = false}) => (
-
- {lang.t('configure.moderate')}
- {lang.t('configure.community')}
- {lang.t('configure.configure')}
- {lang.t('configure.streams')}
-
+ {
+ !restricted ?
+
+
+ {lang.t('configure.moderate')}
+
+
+ {lang.t('configure.community')}
+
+
+ {lang.t('configure.configure')}
+
+
+ {lang.t('configure.streams')}
+
+
+ :
+ null
+ }
diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js
index 8ed0bbf69..aac4654d3 100644
--- a/client/coral-admin/src/containers/Install/InstallContainer.js
+++ b/client/coral-admin/src/containers/Install/InstallContainer.js
@@ -3,22 +3,31 @@ import {connect} from 'react-redux';
import styles from './style.css';
import {nextStep, previousStep, goToStep} from '../../actions/install';
import {Wizard, WizardNav} from 'coral-ui';
+import {Layout} from '../../components/ui/Layout';
import InitialStep from './components/Steps/InitialStep';
import AddOrganizationName from './components/Steps/AddOrganizationName';
+import CreateYourAccount from './components/Steps/CreateYourAccount';
+import InviteTeamMembers from './components/Steps/InviteTeamMembers';
+import FinalStep from './components/Steps/FinalStep';
const InstallContainer = props => {
const {nextStep, previousStep, goToStep, install} = props;
return (
-
-
Welcome to the Coral Project
- { install.step !== 0 ?
: null }
-
-
-
-
-
+
+
+
Welcome to the Coral Project
+ { install.step !== 0 ?
: null }
+
+
+
+
+
+
+
+
+
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
new file mode 100644
index 000000000..cb29c3429
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import styles from './style.css';
+import {Button} from 'coral-ui';
+
+const InitialStep = props => {
+ const {nextStep} = props;
+ return (
+
+ );
+};
+
+export default InitialStep;
diff --git a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js
new file mode 100644
index 000000000..0b58508a0
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import styles from './style.css';
+import {Button} from 'coral-ui';
+
+const InitialStep = props => {
+ const {nextStep} = props;
+ return (
+
+
+ Thanks for installing Talk! We sent an email to your team member.
+ While they finish setting up their account, start engaging with your readers now.
+
+
Launch Talk
+
Close this Installer
+
+ );
+};
+
+export default InitialStep;
diff --git a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js
new file mode 100644
index 000000000..99ef84d19
--- /dev/null
+++ b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import styles from './style.css';
+import {Button} from 'coral-ui';
+
+const InviteTeamMembers = props => {
+ const {nextStep} = props;
+ return (
+
+
Invite Team Members
+
+ Once registered, new team members will receive an email to Create
+ their password.
+
+
+
+ );
+};
+
+export default InviteTeamMembers;
diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/containers/Install/components/Steps/style.css
index 5284b3470..c7d41aef0 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/style.css
+++ b/client/coral-admin/src/containers/Install/components/Steps/style.css
@@ -25,6 +25,11 @@
width: 100%;
box-sizing: border-box;
margin: 10px 0;
+ transition: border-color 0.2 ease;
+
+ &:focus {
+ border-color: black;
+ }
}
}
}
diff --git a/client/coral-ui/components/WizardNav.css b/client/coral-ui/components/WizardNav.css
index 12b9b3841..90d07af09 100644
--- a/client/coral-ui/components/WizardNav.css
+++ b/client/coral-ui/components/WizardNav.css
@@ -12,11 +12,23 @@
color: #A7A7A7;
position: relative;
padding-left: 40px;
-
+ border-radius: 1px;
+
&:hover {
cursor: pointer;
}
+ &.done {
+ border-color: #00796B;
+ background-color: #00796B;
+ color: #ffffff;
+ span {
+ &::after {
+ border-color: transparent transparent transparent #00796B;
+ }
+ }
+ }
+
&.active {
background-color: #FFFFFF;
color: #636363;
@@ -64,7 +76,7 @@
position: absolute;
border: 23px solid #DFDFDF;
border-color: transparent transparent transparent #DFDFDF;
- top: 0;
+ top: -1px;
left: 0;
}
@@ -74,36 +86,9 @@
position: absolute;
border: 23px solid white;
border-color: transparent transparent transparent #fafafa;
- top: 0;
+ top: -1px;
left: -1px;
}
-/*
- &::before {
- position: absolute;
- top: 0;
- left: -1px;
- display: block;
- border-left: 23px solid white;
- border-top: 23px solid transparent;
- border-bottom: 23px solid transparent;
- width: 0;
- height: 0;
- content: " ";
- }
-
- &::after{
- z-index: 1;
- position: absolute;
- top: 0;
- right: -23px;
- display: block;
- border-left: 23px solid #f0f0f0;
- border-top: 23px solid transparent;
- border-bottom: 23px solid transparent;
- width: 0;
- height: 0;
- content: " ";
- }*/
}
}
diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js
index 200adb216..b5a8db549 100644
--- a/client/coral-ui/components/WizardNav.js
+++ b/client/coral-ui/components/WizardNav.js
@@ -10,7 +10,7 @@ const WizardNav = props => {
items.map((item, i) => (
goToStep(item.step)}>
{item.text}
From b2430eaf60437ee498a153c934af0cf3928989d4 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 15:55:33 -0500
Subject: [PATCH 08/52] Adding displayname editing and appropriate toggle.
---
.../src/components/SuspendUserModal.js | 2 +-
graph/resolvers/root_mutation.js | 3 -
graph/typeDefs.graphql | 18 +----
models/user.js | 6 ++
routes/api/account/index.js | 13 +--
routes/api/users/index.js | 11 ++-
services/users.js | 31 ++++++++
test/routes/api/user/index.js | 79 +++++++++++++++++++
test/services/users.js | 52 ++++++++++++
9 files changed, 185 insertions(+), 30 deletions(-)
diff --git a/client/coral-admin/src/components/SuspendUserModal.js b/client/coral-admin/src/components/SuspendUserModal.js
index 5ad8c1616..5afb8ffee 100644
--- a/client/coral-admin/src/components/SuspendUserModal.js
+++ b/client/coral-admin/src/components/SuspendUserModal.js
@@ -36,7 +36,7 @@ class SuspendUserModal extends Component {
}
componentDidMount() {
- const about = this.props.actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username');
+ const about = lang.t('suspenduser.username');
this.setState({email: lang.t('suspenduser.email', about)});
}
diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js
index ddc8e4223..ef46b839a 100644
--- a/graph/resolvers/root_mutation.js
+++ b/graph/resolvers/root_mutation.js
@@ -8,9 +8,6 @@ const RootMutation = {
deleteAction(_, {id}, {mutators: {Action}}) {
return Action.delete({id});
},
- updateUserSettings(_, {settings}, {mutators: {User}}) {
- return User.updateSettings(settings);
- }
};
module.exports = RootMutation;
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index 744a53252..1c1470f58 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -10,11 +10,6 @@ enum SORT_ORDER {
# Date represented as an ISO8601 string.
scalar Date
-type UserSettings {
- # bio of the user.
- bio: String
-}
-
input CommentsQuery {
# current status of a comment.
statuses: [COMMENT_STATUS]
@@ -22,7 +17,7 @@ input CommentsQuery {
# asset that a comment is on.
asset_id: ID
- # the parent of the comment that we want to retrive.
+ # the parent of the comment that we want to retrieve.
parent_id: ID
# comments returned will only be ones which have at least one action of this
@@ -61,8 +56,8 @@ type User {
# the current roles of the user.
roles: [USER_ROLES]
- # settings for a user.
- settings: UserSettings
+ # determines whether the user can edit their username
+ canEditName: Boolean
# returns all comments based on a query.
comments(query: CommentsQuery): [Comment]
@@ -205,11 +200,6 @@ input CreateActionInput {
item_id: ID!
}
-input UpdateUserSettingsInput {
- # user bio
- bio: String!
-}
-
type RootMutation {
# creates a comment on the asset.
createComment(asset_id: ID!, parent_id: ID, body: String!): Comment
@@ -220,8 +210,6 @@ type RootMutation {
# delete an action based on the action id.
deleteAction(id: ID!): Boolean
- # updates a user's settings, it will return if the query was successful.
- updateUserSettings(settings: UpdateUserSettingsInput!): Boolean
}
schema {
diff --git a/models/user.js b/models/user.js
index 8b18ee983..7de99d211 100644
--- a/models/user.js
+++ b/models/user.js
@@ -96,6 +96,12 @@ const UserSchema = new mongoose.Schema({
default: 'ACTIVE'
},
+ // Determines whether the user can edit their username.
+ canEditName: {
+ type: Boolean,
+ default: false
+ },
+
// User's settings
settings: {
bio: {
diff --git a/routes/api/account/index.js b/routes/api/account/index.js
index 57647a89d..4dd6add7c 100644
--- a/routes/api/account/index.js
+++ b/routes/api/account/index.js
@@ -115,20 +115,13 @@ router.put('/password/reset', (req, res, next) => {
});
});
-router.put('/settings', authorization.needed(), (req, res, next) => {
-
- const {
- bio
- } = req.body;
-
+router.put('/displayname', authorization.needed(), (req, res, next) => {
UsersService
- .updateSettings(req.user.id, {bio})
+ .editUsername(req.user.id, req.body.displayName)
.then(() => {
res.status(204).end();
})
- .catch((err) => {
- next(err);
- });
+ .catch(next);
});
module.exports = router;
diff --git a/routes/api/users/index.js b/routes/api/users/index.js
index da47007ee..8b22adb36 100644
--- a/routes/api/users/index.js
+++ b/routes/api/users/index.js
@@ -57,7 +57,16 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next)
.catch(next);
});
-router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) => {
+router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => {
+ UsersService
+ .toggleUsernameEdit(req.params.user_id, true)
+ .then(() => {
+ res.status(204).end();
+ })
+ .catch(next);
+});
+
+router.post('/:user_id/email', authorization.needed('ADMIN'), (req, res, next) => {
UsersService.findById(req.params.user_id)
.then(user => {
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
diff --git a/services/users.js b/services/users.js
index 635e39e47..42278f1c4 100644
--- a/services/users.js
+++ b/services/users.js
@@ -613,4 +613,35 @@ module.exports = class UsersService {
return UserModel.find({status: 'PENDING'});
}
+ /**
+ * Gives the user the ability to edit their username.
+ * @param {String} id the id of the user to be toggled.
+ * @param {Boolean} canEditName sets whether the user can edit their name.
+ * @return {Promise}
+ */
+ static toggleUsernameEdit(id, canEditName) {
+ return UserModel.update({id}, {
+ $set: {canEditName}
+ });
+ }
+
+ /**
+ * Gives the user the ability to edit their username.
+ * @param {String} id the id of the user to be enabled.
+ * @param {String} displayName The new displayname for the user.
+ * @return {Promise}
+ */
+ static editUsername(id, displayName) {
+ return UserModel.findOne({id})
+ .then((user) => {
+ return user.canEditName ?
+ UserModel.update({id}, {
+ $set: {
+ displayName: displayName.toLowerCase(),
+ canEditName: false
+ }
+ })
+ : Promise.reject(new Error('Display name editing disabled for this account.'));
+ });
+ }
};
diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js
index d1771726d..2fdf937b5 100644
--- a/test/routes/api/user/index.js
+++ b/test/routes/api/user/index.js
@@ -83,3 +83,82 @@ describe('/api/v1/users/:user_id/actions', () => {
});
});
});
+
+describe('/api/v1/users/:user_id/username-enable', () => {
+ let mockUser;
+
+ beforeEach(() => SettingsService.init(settings).then(() => {
+ return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
+ })
+ .then((user) => {
+ mockUser = user;
+ }));
+
+ describe('#post', () => {
+ it('it should enable a user to edit their username', () => {
+ return chai.request(app)
+ .post(`/api/v1/users/${mockUser.id}/username-enable`)
+ .set(passport.inject({id: '456', roles: ['ADMIN']}))
+ .then((res) => {
+ expect(res).to.have.status(204);
+ });
+ });
+ });
+});
+
+describe('/api/v1/account/displayname', () => {
+ let mockUser;
+
+ beforeEach(() => SettingsService.init(settings).then(() => {
+ return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
+ })
+ .then((user) => {
+ mockUser = user;
+ }));
+
+ describe('#post', () => {
+ it('it should enable a user to edit their username if canEditName is enabled', () => {
+ return chai.request(app)
+ .post(`/api/v1/users/${mockUser.id}/username-enable`)
+ .set(passport.inject({id: '456', roles: ['ADMIN']}))
+ .then(() => chai.request(app)
+ .put('/api/v1/account/displayname')
+ .set(passport.inject({id: mockUser.id, roles: []}))
+ .send({displayName: 'MojoJojo'}))
+ .then((res) => {
+ expect(res).to.have.status(204);
+ });
+ });
+
+ it('it should return an error if the wrong user tries to edit a username', (done) => {
+ chai.request(app)
+ .post(`/api/v1/users/${mockUser.id}/username-enable`)
+ .set(passport.inject({id: '456', roles: ['ADMIN']}))
+ .then(() => chai.request(app)
+ .put('/api/v1/account/displayname')
+ .set(passport.inject({id: 'wrongid', roles: []}))
+ .send({displayName: 'MojoJojo'}))
+ .then(() => {
+ done(new Error('Exected Error'));
+ })
+ .catch((err) => {
+ expect(err).to.be.truthy;
+ done();
+ });
+ });
+
+ it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => {
+ chai.request(app)
+ .put('/api/v1/account/displayname')
+ .set(passport.inject({id: mockUser.id, roles: []}))
+ .send({username: 'MojoJojo'})
+ .then(() => {
+ done(new Error('Exected Error'));
+ })
+ .catch((err) => {
+ expect(err).to.be.truthy;
+ done();
+ });
+ });
+ });
+});
diff --git a/test/services/users.js b/test/services/users.js
index e2e5655e4..ef027a317 100644
--- a/test/services/users.js
+++ b/test/services/users.js
@@ -208,4 +208,56 @@ describe('services.UsersService', () => {
});
});
});
+
+ describe('#toggleUsernameEdit', () => {
+ it('should toggle the canEditName field', () => {
+ return UsersService
+ .toggleUsernameEdit(mockUsers[0].id, true)
+ .then(() => UsersService.findById(mockUsers[0].id))
+ .then((user) => {
+ expect(user).to.have.property('canEditName', true);
+ });
+ });
+ });
+
+ describe('#editUsername', () => {
+ it('should let the user edit their username if the proper toggle is set', () => {
+ return UsersService
+ .toggleUsernameEdit(mockUsers[0].id, true)
+ .then(() => UsersService.editUsername(mockUsers[0].id, 'Jojo'))
+ .then(() => UsersService.findById(mockUsers[0].id))
+ .then((user) => {
+ expect(user).to.have.property('displayName', 'jojo');
+ expect(user).to.have.property('canEditName', false);
+ });
+ });
+
+ it('should return an error if canEditName is false', (done) => {
+ UsersService
+ .editUsername(mockUsers[0].id, 'Jojo')
+ .then(() => UsersService.findById(mockUsers[0].id))
+ .then(() => {
+ done(new Error('Error expected'));
+ })
+ .catch((err) => {
+ expect(err).to.be.truthy;
+ done();
+ });
+ });
+
+ it('should return an error if the username is already taken', (done) => {
+ UsersService
+ .toggleUsernameEdit(mockUsers[0].id, true)
+ .then(() => UsersService.editUsername(mockUsers[0].id, 'Marvel'))
+ .then(() => UsersService.findById(mockUsers[0].id))
+ .then(() => {
+ done(new Error('Error expected'));
+ })
+ .catch((err) => {
+ expect(err).to.be.truthy;
+ done();
+ });
+ });
+ });
+
});
From 5dca1611bcbe182b09064fda3e9eb2992c61c631 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 15:58:07 -0500
Subject: [PATCH 09/52] Moving account endpoint tests to account.
---
test/routes/api/account/index.js | 71 ++++++++++++++++++++++++++++++++
test/routes/api/user/index.js | 57 -------------------------
2 files changed, 71 insertions(+), 57 deletions(-)
create mode 100644 test/routes/api/account/index.js
diff --git a/test/routes/api/account/index.js b/test/routes/api/account/index.js
new file mode 100644
index 000000000..5b9acfa9d
--- /dev/null
+++ b/test/routes/api/account/index.js
@@ -0,0 +1,71 @@
+const passport = require('../../../passport');
+
+const app = require('../../../../app');
+const chai = require('chai');
+const expect = chai.expect;
+
+const SettingsService = require('../../../../services/settings');
+const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
+
+// Setup chai.
+chai.should();
+chai.use(require('chai-http'));
+
+const UsersService = require('../../../../services/users');
+
+describe('/api/v1/account/displayname', () => {
+ let mockUser;
+
+ beforeEach(() => SettingsService.init(settings).then(() => {
+ return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
+ })
+ .then((user) => {
+ mockUser = user;
+ }));
+
+ describe('#put', () => {
+ it('it should enable a user to edit their username if canEditName is enabled', () => {
+ return chai.request(app)
+ .post(`/api/v1/users/${mockUser.id}/username-enable`)
+ .set(passport.inject({id: '456', roles: ['ADMIN']}))
+ .then(() => chai.request(app)
+ .put('/api/v1/account/displayname')
+ .set(passport.inject({id: mockUser.id, roles: []}))
+ .send({displayName: 'MojoJojo'}))
+ .then((res) => {
+ expect(res).to.have.status(204);
+ });
+ });
+
+ it('it should return an error if the wrong user tries to edit a username', (done) => {
+ chai.request(app)
+ .post(`/api/v1/users/${mockUser.id}/username-enable`)
+ .set(passport.inject({id: '456', roles: ['ADMIN']}))
+ .then(() => chai.request(app)
+ .put('/api/v1/account/displayname')
+ .set(passport.inject({id: 'wrongid', roles: []}))
+ .send({displayName: 'MojoJojo'}))
+ .then(() => {
+ done(new Error('Exected Error'));
+ })
+ .catch((err) => {
+ expect(err).to.be.truthy;
+ done();
+ });
+ });
+
+ it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => {
+ chai.request(app)
+ .put('/api/v1/account/displayname')
+ .set(passport.inject({id: mockUser.id, roles: []}))
+ .send({username: 'MojoJojo'})
+ .then(() => {
+ done(new Error('Exected Error'));
+ })
+ .catch((err) => {
+ expect(err).to.be.truthy;
+ done();
+ });
+ });
+ });
+});
diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js
index 2fdf937b5..4de799df6 100644
--- a/test/routes/api/user/index.js
+++ b/test/routes/api/user/index.js
@@ -105,60 +105,3 @@ describe('/api/v1/users/:user_id/username-enable', () => {
});
});
});
-
-describe('/api/v1/account/displayname', () => {
- let mockUser;
-
- beforeEach(() => SettingsService.init(settings).then(() => {
- return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
- })
- .then((user) => {
- mockUser = user;
- }));
-
- describe('#post', () => {
- it('it should enable a user to edit their username if canEditName is enabled', () => {
- return chai.request(app)
- .post(`/api/v1/users/${mockUser.id}/username-enable`)
- .set(passport.inject({id: '456', roles: ['ADMIN']}))
- .then(() => chai.request(app)
- .put('/api/v1/account/displayname')
- .set(passport.inject({id: mockUser.id, roles: []}))
- .send({displayName: 'MojoJojo'}))
- .then((res) => {
- expect(res).to.have.status(204);
- });
- });
-
- it('it should return an error if the wrong user tries to edit a username', (done) => {
- chai.request(app)
- .post(`/api/v1/users/${mockUser.id}/username-enable`)
- .set(passport.inject({id: '456', roles: ['ADMIN']}))
- .then(() => chai.request(app)
- .put('/api/v1/account/displayname')
- .set(passport.inject({id: 'wrongid', roles: []}))
- .send({displayName: 'MojoJojo'}))
- .then(() => {
- done(new Error('Exected Error'));
- })
- .catch((err) => {
- expect(err).to.be.truthy;
- done();
- });
- });
-
- it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => {
- chai.request(app)
- .put('/api/v1/account/displayname')
- .set(passport.inject({id: mockUser.id, roles: []}))
- .send({username: 'MojoJojo'})
- .then(() => {
- done(new Error('Exected Error'));
- })
- .catch((err) => {
- expect(err).to.be.truthy;
- done();
- });
- });
- });
-});
From 7e907e18f268bb910396001e8bf68321803781d6 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 16:19:42 -0500
Subject: [PATCH 10/52] Enabling canEditName when suspending user.
---
client/coral-admin/src/actions/users.js | 8 ++++++
.../src/components/ModerationList.js | 4 +--
.../src/components/{UserAction.js => User.js} | 25 ++-----------------
client/coral-admin/src/constants/users.js | 1 +
.../ModerationQueue/ModerationContainer.js | 3 ++-
.../graphql/fragments/commentView.graphql | 3 ---
6 files changed, 15 insertions(+), 29 deletions(-)
rename client/coral-admin/src/components/{UserAction.js => User.js} (75%)
diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js
index 44f0325d9..954d89820 100644
--- a/client/coral-admin/src/actions/users.js
+++ b/client/coral-admin/src/actions/users.js
@@ -21,3 +21,11 @@ export const sendNotificationEmail = (userId, subject, body) => {
.catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
};
};
+
+// let a user edit their username
+export const enableUsernameEdit = (userId) => {
+ return (dispatch) => {
+ return coralApi(`/users/${userId}/username-enable`, {method: 'POST'})
+ .catch(error => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error}));
+ };
+};
diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js
index c1df40d31..c2ecab9a2 100644
--- a/client/coral-admin/src/components/ModerationList.js
+++ b/client/coral-admin/src/components/ModerationList.js
@@ -3,7 +3,7 @@ import styles from './ModerationList.css';
import key from 'keymaster';
import Hammer from 'hammerjs';
import Comment from './Comment';
-import UserAction from './UserAction';
+import User from './User';
import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
@@ -177,7 +177,7 @@ export default class ModerationList extends React.Component {
// If the item is an action...
const user = users[item.item_id];
- modItem = user && {
+const User = props => {
const {action, user} = props;
let userStatus = user.status;
const links = user.settings.bio ? linkify.getMatches(user.settings.bio) : [];
@@ -48,32 +47,12 @@ const UserAction = props => {
{lang.t('comment.banned_user')} : null}
- {
- user.settings.bio &&
-
-
-
{lang.t('user.user_bio')}:
-
-
-
-
-
-
-
- }
{`${action.count} ${action.action_type === 'flag_bio' ? lang.t('user.bio_flags') : lang.t('user.username_flags')}`}
;
};
-export default UserAction;
-
-const linkStyles = {
- backgroundColor: 'rgb(255, 219, 135)',
- padding: '1px 2px'
-};
+export default User;
const lang = new I18n(translations);
diff --git a/client/coral-admin/src/constants/users.js b/client/coral-admin/src/constants/users.js
index 3cd5e5b4b..9cd7e2093 100644
--- a/client/coral-admin/src/constants/users.js
+++ b/client/coral-admin/src/constants/users.js
@@ -2,4 +2,5 @@ export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST';
export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS';
export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE';
export const USER_EMAIL_FAILURE = 'USER_EMAIL_FAILURE';
+export const USERNAME_ENABLE_FAILURE = 'USERNAME_ENABLE_FAILURE';
export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS';
diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
index aa754b4ac..4f56a6c69 100644
--- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
+++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js
@@ -11,7 +11,7 @@ import {
fetchFlaggedQueue,
fetchModerationQueueComments,
} from 'actions/comments';
-import {userStatusUpdate, sendNotificationEmail} from 'actions/users';
+import {userStatusUpdate, sendNotificationEmail, enableUsernameEdit} from 'actions/users';
import {fetchSettings} from 'actions/settings';
import ModerationQueue from './ModerationQueue';
@@ -119,6 +119,7 @@ const mapDispatchToProps = dispatch => {
dispatch(fetchModerationQueueComments());
}),
suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('BANNED', userId))
+ .then(() => dispatch(enableUsernameEdit(userId)))
.then(() => dispatch(sendNotificationEmail(userId, subject, text)))
.then(() => dispatch(fetchModerationQueueComments()))
,
diff --git a/client/coral-framework/graphql/fragments/commentView.graphql b/client/coral-framework/graphql/fragments/commentView.graphql
index fb8051155..29f9b3bfe 100644
--- a/client/coral-framework/graphql/fragments/commentView.graphql
+++ b/client/coral-framework/graphql/fragments/commentView.graphql
@@ -6,9 +6,6 @@ fragment commentView on Comment {
user {
id
name: displayName
- settings {
- bio
- }
}
actions {
type: action_type
From e3da45daeb59523db9f3a17c4270f2696e0007b8 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 16:38:01 -0500
Subject: [PATCH 11/52] Displaying appropriate suspension message.
---
client/coral-embed-stream/src/Embed.js | 8 +++++++-
client/coral-framework/components/SuspendedAccount.js | 8 ++++++--
client/coral-framework/translations.json | 6 ++++--
3 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js
index 2a7057cca..b57bc799f 100644
--- a/client/coral-embed-stream/src/Embed.js
+++ b/client/coral-embed-stream/src/Embed.js
@@ -96,6 +96,8 @@ class Embed extends Component {
const openStream = closedAt === null;
+ const banned = user && user.status === 'BANNED';
+
const expandForLogin = showSignInDialog ? {
minHeight: document.body.scrollHeight + 200
} : {};
@@ -118,7 +120,11 @@ class Embed extends Component {
content={asset.settings.infoBoxContent}
enable={asset.settings.infoBoxEnable}
/>
- }>
+
+ }>
{
user
? (
+export default ({canEditName}) => (
- {lang.t('suspendedAccountMsg')}
+ {
+ canEditName ?
+ lang.t('editNameMsg')
+ : lang.t('bannedAccountMsg')
+ }
);
diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json
index 5bf9b40a9..a61213aab 100644
--- a/client/coral-framework/translations.json
+++ b/client/coral-framework/translations.json
@@ -3,7 +3,8 @@
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
"successBioUpdate": "Your Bio has been updated",
"contentNotAvailable": "This content is not available",
- "suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
+ "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
+ "editNameMsg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.",
"error": {
"email": "Not a valid E-Mail",
"password": "Password must be at least 8 characters",
@@ -25,7 +26,8 @@
"successUpdateSettings": "La configuración de este articulo fue actualizada",
"successBioUpdate": "Tu bio fue actualizada",
"contentNotAvailable": "El contenido no se encuentra disponible",
- "suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information",
+ "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information",
+ "editNameMsg": "",
"error": {
"email": "No es un email válido",
"password": "La contraseña debe tener por lo menos 8 caracteres",
From d703cc21681cf7d0d7e55e4baf44d8520e3a738d Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 19:53:37 -0500
Subject: [PATCH 12/52] Prompting user to update name when banned.
---
client/coral-embed-stream/src/Embed.js | 3 +
client/coral-framework/actions/user.js | 16 +---
.../components/RestrictedContent.css | 10 +++
.../components/SuspendedAccount.js | 82 +++++++++++++++++--
client/coral-framework/constants/user.js | 6 +-
client/coral-framework/translations.json | 9 +-
graph/mutators/index.js | 2 -
graph/mutators/user.js | 31 -------
graph/typeDefs.graphql | 2 +-
models/user.js | 2 +-
routes/api/account/index.js | 10 ++-
routes/api/users/index.js | 2 +-
services/users.js | 7 +-
test/services/users.js | 16 ++--
14 files changed, 123 insertions(+), 75 deletions(-)
delete mode 100644 graph/mutators/user.js
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js
index b57bc799f..ceec3f1a9 100644
--- a/client/coral-embed-stream/src/Embed.js
+++ b/client/coral-embed-stream/src/Embed.js
@@ -11,6 +11,7 @@ const {fetchAssetSuccess} = assetActions;
import {queryStream} from 'coral-framework/graphql/queries';
import {postComment, postAction, deleteAction} from 'coral-framework/graphql/mutations';
+import {editName} from 'coral-framework/actions/user';
import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework';
import Stream from './Stream';
@@ -123,6 +124,7 @@ class Embed extends Component {
}>
{
@@ -200,6 +202,7 @@ const mapDispatchToProps = dispatch => ({
loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)),
addNotification: (type, text) => dispatch(addNotification(type, text)),
clearNotification: () => dispatch(clearNotification()),
+ editName: (displayName) => dispatch(editName(displayName)),
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
logout: () => dispatch(logout()),
dispatch: d => dispatch(d)
diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js
index 4620262d3..42d2e0398 100644
--- a/client/coral-framework/actions/user.js
+++ b/client/coral-framework/actions/user.js
@@ -1,4 +1,3 @@
-import * as actions from '../constants/user';
import {addNotification} from '../actions/notification';
import coralApi from '../helpers/response';
@@ -6,16 +5,9 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
-const saveBioRequest = () => ({type: actions.SAVE_BIO_REQUEST});
-const saveBioSuccess = settings => ({type: actions.SAVE_BIO_SUCCESS, settings});
-const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error});
-
-export const saveBio = (user_id, formData) => dispatch => {
- dispatch(saveBioRequest());
- coralApi('/account/settings', {method: 'PUT', body: formData})
+export const editName = (displayName) => (dispatch) => {
+ return coralApi('/account/displayname', {method: 'PUT', body: {displayName}})
.then(() => {
- dispatch(addNotification('success', lang.t('successBioUpdate')));
- dispatch(saveBioSuccess(formData));
- })
- .catch(error => dispatch(saveBioFailure(error)));
+ dispatch(addNotification('success', lang.t('successNameUpdate')));
+ });
};
diff --git a/client/coral-framework/components/RestrictedContent.css b/client/coral-framework/components/RestrictedContent.css
index 47ced7b0b..ca5a6517c 100644
--- a/client/coral-framework/components/RestrictedContent.css
+++ b/client/coral-framework/components/RestrictedContent.css
@@ -2,3 +2,13 @@
background: #D8D8D8;
padding: 25px;
}
+
+.editNameInput {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+.alert {
+ margin-top: 10px;
+ color: #B71C1C;
+}
diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js
index e6c53d038..0c6347fca 100644
--- a/client/coral-framework/components/SuspendedAccount.js
+++ b/client/coral-framework/components/SuspendedAccount.js
@@ -1,15 +1,79 @@
-import React from 'react';
+import React, {Component, PropTypes} from 'react';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-framework/translations.json';
const lang = new I18n(translations);
import styles from './RestrictedContent.css';
+import {Button} from 'coral-ui';
+import validate from '../helpers/validate';
-export default ({canEditName}) => (
-
-
{
+class SuspendedAccount extends Component {
+
+ static propTypes = {
+ canEditName: PropTypes.bool,
+ editName: PropTypes.func.isRequired
+ }
+
+ state = {
+ displayName: '',
+ alert: ''
+ }
+
+ onSubmitClick = (e) => {
+ const {editName} = this.props;
+ const {displayName} = this.state;
+ e.preventDefault();
+ if (validate.displayName(displayName)) {
+ editName(displayName)
+ .then(() => window.reload())
+ .catch((error) => {
+ this.setState({alert: lang.t(`error.${error.message}`)});
+ });
+ } else {
+ this.setState({alert: lang.t('editName.error')});
+ }
+
+ }
+
+ render () {
+ const {canEditName} = this.props;
+ const {displayName, alert} = this.state;
+
+ return
+ {
+ canEditName ?
+ lang.t('editName.msg')
+ : lang.t('bannedAccountMsg')
+ }
+ {
canEditName ?
- lang.t('editNameMsg')
- : lang.t('bannedAccountMsg')
- }
-
-);
+
+
+ {alert}
+
+
+ {lang.t('editName.label')}
+
+
this.setState({displayName: e.target.value})}
+ rows={3}/>
+
+ {
+ lang.t('editName.button')
+ }
+
+
: null
+ }
+ ;
+ }
+}
+
+export default SuspendedAccount;
diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js
index 9c6f508fe..a8e4585db 100644
--- a/client/coral-framework/constants/user.js
+++ b/client/coral-framework/constants/user.js
@@ -1,6 +1,6 @@
-export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST';
-export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS';
-export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE';
+export const EDIT_NAME_REQUEST = 'EDIT_NAME_REQUEST';
+export const EDIT_NAME_SUCCESS = 'EDIT_NAME_SUCCESS';
+export const EDIT_NAME_FAILURE = 'EDIT_NAME_FAILURE';
export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json
index a61213aab..80e865f8e 100644
--- a/client/coral-framework/translations.json
+++ b/client/coral-framework/translations.json
@@ -1,10 +1,15 @@
{
"en": {
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
- "successBioUpdate": "Your Bio has been updated",
+ "successNameUpdate": "Your display name has been updated",
"contentNotAvailable": "This content is not available",
"bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information",
- "editNameMsg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.",
+ "editName": {
+ "msg": "Your account is currently suspended because your display name has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.",
+ "label": "New Display Name",
+ "button": "Submit",
+ "error": "Display names can contain letters, numbers and _ only"
+ },
"error": {
"email": "Not a valid E-Mail",
"password": "Password must be at least 8 characters",
diff --git a/graph/mutators/index.js b/graph/mutators/index.js
index b799cf83d..58d0ed62c 100644
--- a/graph/mutators/index.js
+++ b/graph/mutators/index.js
@@ -2,7 +2,6 @@ const _ = require('lodash');
const Comment = require('./comment');
const Action = require('./action');
-const User = require('./user');
module.exports = (context) => {
@@ -10,7 +9,6 @@ module.exports = (context) => {
return _.merge(...[
Comment,
Action,
- User,
].map((mutators) => {
// Each set of mutators is a function which takes the context.
diff --git a/graph/mutators/user.js b/graph/mutators/user.js
deleted file mode 100644
index 96049bbab..000000000
--- a/graph/mutators/user.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const UsersService = require('../../services/users');
-
-/**
- * Updates a users settings.
- * @param {Object} user the user performing the request
- * @param {String} bio the new user bio
- * @return {Promise}
- */
-const updateUserSettings = ({user}, {bio}) => {
- return UsersService.updateSettings(user.id, {bio});
-};
-
-module.exports = (context) => {
-
- // TODO: refactor to something that'll return an error in the event an attempt
- // is made to mutate state while not logged in. There's got to be a better way
- // to do this.
- if (context.user && context.user.can('mutation:updateUserSettings')) {
- return {
- User: {
- updateSettings: (settings) => updateUserSettings(context, settings)
- }
- };
- }
-
- return {
- User: {
- updateSettings: () => {}
- }
- };
-};
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index 1c1470f58..b1f9b82f7 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -145,7 +145,7 @@ type Asset {
# The scraped title of the asset.
title: String
- # The URL that the asset is locaed on.
+ # The URL that the asset is located on.
url: String
# The top level comments that are attached to the asset.
diff --git a/models/user.js b/models/user.js
index 7de99d211..e5db70ea6 100644
--- a/models/user.js
+++ b/models/user.js
@@ -146,7 +146,7 @@ const USER_GRAPH_OPERATIONS = [
'mutation:createComment',
'mutation:createAction',
'mutation:deleteAction',
- 'mutation:updateUserSettings'
+ 'mutation:editName'
];
/**
diff --git a/routes/api/account/index.js b/routes/api/account/index.js
index 4dd6add7c..b08bdfc2e 100644
--- a/routes/api/account/index.js
+++ b/routes/api/account/index.js
@@ -117,11 +117,17 @@ router.put('/password/reset', (req, res, next) => {
router.put('/displayname', authorization.needed(), (req, res, next) => {
UsersService
- .editUsername(req.user.id, req.body.displayName)
+ .editName(req.user.id, req.body.displayName)
.then(() => {
res.status(204).end();
})
- .catch(next);
+ .catch(error => {
+ if (error.code === 11000) {
+ next(errors.ErrDisplayTaken);
+ } else {
+ next(error);
+ }
+ });
});
module.exports = router;
diff --git a/routes/api/users/index.js b/routes/api/users/index.js
index 8b22adb36..5d1783cfe 100644
--- a/routes/api/users/index.js
+++ b/routes/api/users/index.js
@@ -59,7 +59,7 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next)
router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => {
UsersService
- .toggleUsernameEdit(req.params.user_id, true)
+ .toggleNameEdit(req.params.user_id, true)
.then(() => {
res.status(204).end();
})
diff --git a/services/users.js b/services/users.js
index 42278f1c4..ec0967c3b 100644
--- a/services/users.js
+++ b/services/users.js
@@ -619,7 +619,7 @@ module.exports = class UsersService {
* @param {Boolean} canEditName sets whether the user can edit their name.
* @return {Promise}
*/
- static toggleUsernameEdit(id, canEditName) {
+ static toggleNameEdit(id, canEditName) {
return UserModel.update({id}, {
$set: {canEditName}
});
@@ -631,14 +631,15 @@ module.exports = class UsersService {
* @param {String} displayName The new displayname for the user.
* @return {Promise}
*/
- static editUsername(id, displayName) {
+ static editName(id, displayName) {
return UserModel.findOne({id})
.then((user) => {
return user.canEditName ?
UserModel.update({id}, {
$set: {
displayName: displayName.toLowerCase(),
- canEditName: false
+ canEditName: false,
+ status: 'PENDING'
}
})
: Promise.reject(new Error('Display name editing disabled for this account.'));
diff --git a/test/services/users.js b/test/services/users.js
index ef027a317..5774d45ae 100644
--- a/test/services/users.js
+++ b/test/services/users.js
@@ -209,10 +209,10 @@ describe('services.UsersService', () => {
});
});
- describe('#toggleUsernameEdit', () => {
+ describe('#toggleNameEdit', () => {
it('should toggle the canEditName field', () => {
return UsersService
- .toggleUsernameEdit(mockUsers[0].id, true)
+ .toggleNameEdit(mockUsers[0].id, true)
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('canEditName', true);
@@ -220,11 +220,11 @@ describe('services.UsersService', () => {
});
});
- describe('#editUsername', () => {
+ describe('#editName', () => {
it('should let the user edit their username if the proper toggle is set', () => {
return UsersService
- .toggleUsernameEdit(mockUsers[0].id, true)
- .then(() => UsersService.editUsername(mockUsers[0].id, 'Jojo'))
+ .toggleNameEdit(mockUsers[0].id, true)
+ .then(() => UsersService.editName(mockUsers[0].id, 'Jojo'))
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('displayName', 'jojo');
@@ -234,7 +234,7 @@ describe('services.UsersService', () => {
it('should return an error if canEditName is false', (done) => {
UsersService
- .editUsername(mockUsers[0].id, 'Jojo')
+ .editName(mockUsers[0].id, 'Jojo')
.then(() => UsersService.findById(mockUsers[0].id))
.then(() => {
done(new Error('Error expected'));
@@ -247,8 +247,8 @@ describe('services.UsersService', () => {
it('should return an error if the username is already taken', (done) => {
UsersService
- .toggleUsernameEdit(mockUsers[0].id, true)
- .then(() => UsersService.editUsername(mockUsers[0].id, 'Marvel'))
+ .toggleNameEdit(mockUsers[0].id, true)
+ .then(() => UsersService.editName(mockUsers[0].id, 'Marvel'))
.then(() => UsersService.findById(mockUsers[0].id))
.then(() => {
done(new Error('Error expected'));
From 97c3ebc3e039116933359562a86f83422430b0f2 Mon Sep 17 00:00:00 2001
From: David Jay
Date: Wed, 1 Feb 2017 20:55:45 -0500
Subject: [PATCH 13/52] Whitelisting usernames once they have been approved.
---
.../src/components/ModerationList.js | 2 +-
models/user.js | 2 +-
routes/api/users/index.js | 2 +-
services/users.js | 11 ++++++--
test/routes/api/user/index.js | 25 +++++++------------
5 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js
index c2ecab9a2..47e91e551 100644
--- a/client/coral-admin/src/components/ModerationList.js
+++ b/client/coral-admin/src/components/ModerationList.js
@@ -138,7 +138,7 @@ export default class ModerationList extends React.Component {
if (menuOption === 'REJECTED') {
this.setState({suspendUserModal: action});
} else if (menuOption === 'ACCEPTED') {
- this.props.userStatusUpdate('ACTIVE', action.item_id);
+ this.props.userStatusUpdate('APPROVED', action.item_id);
}
}
}
diff --git a/models/user.js b/models/user.js
index d483b1893..06dc3c7d1 100644
--- a/models/user.js
+++ b/models/user.js
@@ -12,7 +12,7 @@ const USER_STATUS = [
'ACTIVE',
'BANNED',
'PENDING',
- 'SUSPENDED',
+ 'APPROVED' // Indicates that the users' displayname has been approved
];
// ProfileSchema is the mongoose schema defined as the representation of a
diff --git a/routes/api/users/index.js b/routes/api/users/index.js
index f136fd776..0b7cd28aa 100644
--- a/routes/api/users/index.js
+++ b/routes/api/users/index.js
@@ -171,7 +171,7 @@ router.post('/:user_id/actions', authorization.needed(), (req, res, next) => {
// Set the user status to "pending" for review by moderators
if (action_type.slice(0, 4) === 'FLAG') {
- return UsersService.setStatus(req.user.id, 'PENDING')
+ return UsersService.setStatus(req.params.user_id, 'PENDING')
.then(() => action);
} else {
return action;
diff --git a/services/users.js b/services/users.js
index b9a64b288..157286ccf 100644
--- a/services/users.js
+++ b/services/users.js
@@ -349,8 +349,15 @@ module.exports = class UsersService {
// User status is not supported! Error out here.
return Promise.reject(new Error(`status ${status} is not supported`));
}
-
- return UserModel.update({id}, {$set: {status}});
+
+ return UserModel.findOne({id})
+ .then((user) => {
+ if (user.status === 'APPROVED' && status === 'PENDING') {
+ return Promise.resolve();
+ } else {
+ return UserModel.update({id}, {$set: {status}});
+ }
+ });
}
/**
diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js
index 4de799df6..f6e202243 100644
--- a/test/routes/api/user/index.js
+++ b/test/routes/api/user/index.js
@@ -52,33 +52,26 @@ describe('/api/v1/users/:user_id/email/confirm', () => {
describe('/api/v1/users/:user_id/actions', () => {
- const users = [{
- displayName: 'Ana',
- email: 'ana@gmail.com',
- password: '123456789'
- }, {
- displayName: 'Maria',
- email: 'maria@gmail.com',
- password: '123456789'
- }];
+ let mockUser;
- beforeEach(() => {
- return SettingsService.init(settings).then(() => {
- return UsersService.createLocalUsers(users);
- });
- });
+ beforeEach(() => SettingsService.init(settings).then(() => {
+ return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana');
+ })
+ .then((user) => {
+ mockUser = user;
+ }));
describe('#post', () => {
it('it should update actions', () => {
return chai.request(app)
- .post('/api/v1/users/abc/actions')
+ .post(`/api/v1/users/${mockUser.id}/actions`)
.set(passport.inject({id: '456', roles: ['ADMIN']}))
.send({'action_type': 'FLAG', metadata: {reason: 'Bio is too awesome.'}})
.then((res) => {
expect(res).to.have.status(201);
expect(res).to.have.body;
expect(res.body).to.have.property('action_type', 'FLAG');
- expect(res.body).to.have.property('item_id', 'abc');
+ expect(res.body).to.have.property('item_id', mockUser.id);
});
});
});
From 65aa5e7e92be6ff9d3f396e39796b05abf071306 Mon Sep 17 00:00:00 2001
From: Belen Curcio
Date: Thu, 2 Feb 2017 11:31:43 -0300
Subject: [PATCH 14/52] =?UTF-8?q?=C3=81dding=20Select,=20Options=20to=20Co?=
=?UTF-8?q?ral=20Ui=20and=20steps=20with=20formField?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../coral-admin/src/components/ui/Header.js | 74 ++++++++++---------
.../containers/Install/InstallContainer.js | 2 +-
.../components/Steps/AddOrganizationName.js | 17 +++--
.../components/Steps/CreateYourAccount.js | 50 ++++++++-----
.../Install/components/Steps/InitialStep.js | 2 +-
.../components/Steps/InviteTeamMembers.js | 45 ++++++-----
.../Install/components/Steps/style.css | 52 +++++++++----
.../src/containers/Install/style.css | 2 +-
client/coral-ui/components/Option.css | 3 +
client/coral-ui/components/Option.js | 14 ++++
client/coral-ui/components/Select.css | 33 +++++++++
client/coral-ui/components/Select.js | 14 ++++
client/coral-ui/components/WizardNav.css | 15 +++-
client/coral-ui/components/WizardNav.js | 7 +-
client/coral-ui/index.js | 2 +
15 files changed, 230 insertions(+), 102 deletions(-)
create mode 100644 client/coral-ui/components/Option.css
create mode 100644 client/coral-ui/components/Option.js
create mode 100644 client/coral-ui/components/Select.css
create mode 100644 client/coral-ui/components/Select.js
diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js
index abfb0ff13..3e6598d6e 100644
--- a/client/coral-admin/src/components/ui/Header.js
+++ b/client/coral-admin/src/components/ui/Header.js
@@ -11,42 +11,44 @@ export default ({handleLogout, restricted = false}) => (
{
!restricted ?
-
-
- {lang.t('configure.moderate')}
-
-
- {lang.t('configure.community')}
-
-
- {lang.t('configure.configure')}
-
-
- {lang.t('configure.streams')}
-
-
- :
- null
- }
-
-
-
-
-
-
- Sign Out
-
-
-
-
- {`v${process.env.VERSION}`}
-
-
-
+
+
+
+ {lang.t('configure.moderate')}
+
+
+ {lang.t('configure.community')}
+
+
+ {lang.t('configure.configure')}
+
+
+ {lang.t('configure.streams')}
+
+
+
+
+
+
+
+
+ Sign Out
+
+
+
+
+ {`v${process.env.VERSION}`}
+
+
+
+
+ :
+ null
+ }
);
diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js
index aac4654d3..350e60709 100644
--- a/client/coral-admin/src/containers/Install/InstallContainer.js
+++ b/client/coral-admin/src/containers/Install/InstallContainer.js
@@ -18,7 +18,7 @@ const InstallContainer = props => {
Welcome to the Coral Project
- { install.step !== 0 ?
: null }
+ { install.step !== 0 ? : null }
diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
index 74f2c3016..ff7125ab5 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js
@@ -1,6 +1,6 @@
import React from 'react';
import styles from './style.css';
-import {Button} from 'coral-ui';
+import {FormField, Button} from 'coral-ui';
const InitialStep = props => {
const {nextStep} = props;
@@ -10,11 +10,16 @@ const InitialStep = props => {
Please tell us the name of your organization. This will appear in emails when
inviting new team members
-
+
+
+
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
index cb29c3429..f245218d8 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js
@@ -1,30 +1,40 @@
import React from 'react';
import styles from './style.css';
-import {Button} from 'coral-ui';
+import {FormField, Button} from 'coral-ui';
const InitialStep = props => {
const {nextStep} = props;
return (
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
index 95043b515..51c40d44d 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js
@@ -11,7 +11,7 @@ const InitialStep = props => {
Once you complete the following three steps, you will have a free
installation and provision of Mongo and Redis.
- Get Started
+ Get Started
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js
index 99ef84d19..156916ab0 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js
+++ b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js
@@ -1,6 +1,6 @@
import React from 'react';
import styles from './style.css';
-import {Button} from 'coral-ui';
+import {Button, Select, Option, FormField} from 'coral-ui';
const InviteTeamMembers = props => {
const {nextStep} = props;
@@ -11,23 +11,32 @@ const InviteTeamMembers = props => {
Once registered, new team members will receive an email to Create
their password.
-
+
);
};
diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/containers/Install/components/Steps/style.css
index c7d41aef0..9dd95f90b 100644
--- a/client/coral-admin/src/containers/Install/components/Steps/style.css
+++ b/client/coral-admin/src/containers/Install/components/Steps/style.css
@@ -1,34 +1,54 @@
.step {
+ padding: 20px 0;
+
+ h3 {
+ max-width: 450px;
+ margin: 0 auto;
+ text-align: left;
+ font-size: 1.4em;
+ font-weight: bold;
+ }
p {
max-width: 450px;
- margin: 10px auto 30px;
+ margin: 0 auto 30px;
+ font-size: 1.1em;
+ text-align: left;
}
> button {
- min-width: 140px;
+ min-width: 145px;
}
- form {
+ .form {
max-width: 300px;
margin: 30px auto;
- label {
- text-align: left;
- display: block;
+ form > button {
+ margin: 30px 0;
}
- input {
- border: solid 1px rgba(0, 0, 0, 0.23);
- padding: 10px 2px;
- border-radius: 3px;
- width: 100%;
- box-sizing: border-box;
- margin: 10px 0;
- transition: border-color 0.2 ease;
+ .formField {
+ label {
+ text-align: left;
+ display: block;
+ margin: 10px 0;
+ font-weight: 400;
+ font-size: 1.08em;
+ }
- &:focus {
- border-color: black;
+ > input {
+ border: solid 1px rgba(0, 0, 0, 0.23);
+ padding: 10px;
+ border-radius: 3px;
+ width: 100%;
+ box-sizing: border-box;
+ transition: border-color 0.2s ease;
+ font-size: 1em;
+
+ &:focus {
+ border-color: black;
+ }
}
}
}
diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/containers/Install/style.css
index 90c62c20e..62f7ffe04 100644
--- a/client/coral-admin/src/containers/Install/style.css
+++ b/client/coral-admin/src/containers/Install/style.css
@@ -2,7 +2,7 @@
max-width: 900px;
margin: 0 auto;
text-align: center;
- padding: 40px 0;
+ padding: 50px 0;
h2 {
font-size: 2em;
diff --git a/client/coral-ui/components/Option.css b/client/coral-ui/components/Option.css
new file mode 100644
index 000000000..54784d8fc
--- /dev/null
+++ b/client/coral-ui/components/Option.css
@@ -0,0 +1,3 @@
+.Option {
+
+}
diff --git a/client/coral-ui/components/Option.js b/client/coral-ui/components/Option.js
new file mode 100644
index 000000000..d322ef0ef
--- /dev/null
+++ b/client/coral-ui/components/Option.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import {Option as OptionMDL} from 'react-mdl-selectfield';
+import styles from './Option.css';
+
+const Option = props => {
+ const {children, ...attrs} = props;
+ return (
+
+ {children}
+
+ );
+};
+
+export default Option;
diff --git a/client/coral-ui/components/Select.css b/client/coral-ui/components/Select.css
new file mode 100644
index 000000000..ac93a843c
--- /dev/null
+++ b/client/coral-ui/components/Select.css
@@ -0,0 +1,33 @@
+.Select {
+ position: relative;
+ width: 100%;
+ height: 40px;
+ background: #2c2c2c;
+ padding: 10px 15px;
+ box-sizing: border-box;
+ color: white;
+ border-radius: 2px;
+ box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12);
+
+ > div {
+ padding: 0;
+ }
+
+ i {
+ position: absolute;
+ top: 7px;
+ right: 7px;
+ }
+
+ input {
+ padding: 0;
+ font-size: 13px;
+ letter-spacing: 0.7px;
+ font-weight: 400;
+ }
+
+ &:hover {
+ cursor: pointer;
+ }
+
+}
diff --git a/client/coral-ui/components/Select.js b/client/coral-ui/components/Select.js
new file mode 100644
index 000000000..731c50ddc
--- /dev/null
+++ b/client/coral-ui/components/Select.js
@@ -0,0 +1,14 @@
+import React from 'react';
+import {SelectField} from 'react-mdl-selectfield';
+import styles from './Select.css';
+
+const Select = props => {
+ const {children, ...attrs} = props;
+ return (
+
+ {children}
+
+ );
+};
+
+export default Select;
diff --git a/client/coral-ui/components/WizardNav.css b/client/coral-ui/components/WizardNav.css
index 90d07af09..001ee8603 100644
--- a/client/coral-ui/components/WizardNav.css
+++ b/client/coral-ui/components/WizardNav.css
@@ -13,7 +13,7 @@
position: relative;
padding-left: 40px;
border-radius: 1px;
-
+
&:hover {
cursor: pointer;
}
@@ -27,6 +27,10 @@
border-color: transparent transparent transparent #00796B;
}
}
+
+ i {
+ opacity: 1;
+ }
}
&.active {
@@ -41,6 +45,15 @@
}
}
+ i {
+ transition: opacity 0.2s ease;
+ opacity: 0;
+ vertical-align: middle;
+ font-size: 16px;
+ margin-top: -5px;
+ margin-left: 8px;
+ }
+
span {
padding: 10px 20px;
margin-right: 10px;
diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js
index b5a8db549..9fd9a1f23 100644
--- a/client/coral-ui/components/WizardNav.js
+++ b/client/coral-ui/components/WizardNav.js
@@ -1,8 +1,9 @@
import React, {PropTypes} from 'react';
import styles from './WizardNav.css';
+import Icon from './Icon';
const WizardNav = props => {
- const {goToStep, currentStep, items} = props;
+ const {goToStep, currentStep, items, icon} = props;
return (