Merged in os-177-verification-view (pull request )

Os 177 verification view
This commit is contained in:
Ivan Diaz 2017-01-10 23:37:47 -03:00
commit 258f4003c0
31 changed files with 178 additions and 137 deletions

View File

@ -71,6 +71,13 @@ export default {
}
},
verify(value) {
return {
type: 'VERIFY',
payload: value
};
},
initSession() {
return {
type: 'CHECK_SESSION',

View File

@ -82,7 +82,7 @@ class LanguageSelector extends React.Component {
case 'supported':
return this.props.supportedLanguages;
case 'allowed':
return this.props.supportedLanguages;
return this.props.allowedLanguages;
case 'custom':
return this.props.customList;
}

View File

@ -60,11 +60,11 @@ class App extends React.Component {
loggedOutStaff: _.includes(props.location.pathname, '/admin/panel') && !props.session.logged
};
if(props.config['maintenance-mode'] && !_.includes(props.location.pathname, '/admin') && !_.includes(props.location.pathname, '/maintenance')) {
if(props.config['maintenance-mode'] === '1' && !_.includes(props.location.pathname, '/admin') && !_.includes(props.location.pathname, '/maintenance')) {
browserHistory.push('/maintenance');
}
if(!props.config['maintenance-mode'] && _.includes(props.location.pathname, '/maintenance')) {
if(props.config['maintenance-mode'] === '0' && _.includes(props.location.pathname, '/maintenance')) {
browserHistory.push('/');
}

View File

@ -10,6 +10,7 @@ import DemoPage from 'app/demo/components-demo-page';
import MainLayout from 'app/main/main-layout';
import MainHomePage from 'app/main/main-home/main-home-page';
import MainSignUpPage from 'app/main/main-signup/main-signup-page';
import MainVerifyTokenPage from 'app/main/main-verify-token-page';
import MainRecoverPasswordPage from 'app/main/main-recover-password/main-recover-password-page';
import MainMaintenancePage from 'app/main/main-maintenance-page';
@ -59,6 +60,7 @@ export default (
<Route path='/' component={MainLayout}>
<IndexRoute component={MainHomePage} />
<Route path='signup' component={MainSignUpPage}/>
<Route path='verify-token/:email/:token' component={MainVerifyTokenPage}/>
<Route path='recover-password' component={MainRecoverPasswordPage}/>
<Route path='maintenance' component={MainMaintenancePage}/>
<Route path='dashboard' component={DashboardLayout}>

View File

@ -1,90 +0,0 @@
import React from 'react';
import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n';
import ActivityRow from 'app-components/activity-row';
import SubmitButton from 'core-components/submit-button';
class ActivityList extends React.Component {
static propTypes = {
mode: React.PropTypes.oneOf(['staff', 'system'])
};
static childContextTypes = {
loading: React.PropTypes.bool
};
getChildContext() {
return {
loading: this.state.loading
};
}
state = {
activities: [],
page: 1,
limit: false,
loading: false
};
componentDidMount() {
this.retrieveNextPage();
}
render() {
if (this.props.mode === 'staff') {
return (
<div>
{this.state.activities.map(this.renderRow.bind(this))}
{(!this.state.limit) ? this.renderButton() : null}
</div>
);
}
else {
return (
<div>
{this.state.activities.map(this.renderRow.bind(this))}
{(!this.state.limit) ? this.renderButton() : null}
</div>
);
}
}
renderButton() {
return (
<SubmitButton type="secondary" onClick={this.retrieveNextPage.bind(this)}>
{i18n('LOAD_MORE')}
</SubmitButton>
);
}
renderRow(row) {
return (
<ActivityRow {...row} />
);
}
retrieveNextPage() {
this.setState({loading: true});
API.call({
path: '/staff/last-events',
data: {
page: this.state.page
}
}).then(this.onRetrieveSuccess.bind(this));
}
onRetrieveSuccess(result) {
this.setState({
activities: this.state.activities.concat(result.data),
page: this.state.page + 1,
limit: (result.data.length !== 10),
loading: false
});
}
}
export default ActivityList;

View File

@ -86,7 +86,7 @@ class AdminPanelActivity extends React.Component {
onMenuItemClick(index) {
this.setState({
page: 0,
page: 1,
mode: (index === 0) ? 'staff' : 'system',
activities: []
}, this.retrieveNextPage.bind(this));

View File

@ -1,6 +1,8 @@
import React from 'react';
import _ from 'lodash';
import store from 'app/store';
import ConfigActions from 'actions/config-actions';
import API from 'lib-app/api-call';
import i18n from 'lib-app/i18n';
import LanguageSelector from 'app-components/language-selector';
@ -225,6 +227,8 @@ class AdminPanelSystemPreferences extends React.Component {
'supportedLanguages': result.data.supportedLanguages.map(lang => (_.indexOf(languageKeys, lang)))
}
});
store.dispatch(ConfigActions.updateData());
}
onRecoverSettingsFail() {

View File

@ -11,12 +11,14 @@ import AreYouSure from 'app-components/are-you-sure';
import Header from 'core-components/header';
import Button from 'core-components/button';
import Message from 'core-components/message';
import InfoTooltip from 'core-components/info-tooltip';
class AdminPanelViewUser extends React.Component {
state = {
name: '',
email: '',
verified: true,
tickets: [],
invalid: false,
loading: true
@ -64,6 +66,7 @@ class AdminPanelViewUser extends React.Component {
{i18n('EMAIL')}
<div className="admin-panel-view-user__info-box">
{this.state.email}
{(!this.state.verified) ? this.renderNotVerified() : null}
</div>
</div>
<div className="admin-panel-view-user__delete-button">
@ -79,6 +82,12 @@ class AdminPanelViewUser extends React.Component {
);
}
renderNotVerified() {
return (
<InfoTooltip className="admin-panel-view-user__unverified" type="warning" text={i18n('UNVERIFIED_EMAIL')} />
);
}
getTicketListProps() {
return {
type: 'secondary',
@ -93,6 +102,7 @@ class AdminPanelViewUser extends React.Component {
this.setState({
name: result.data.name,
email: result.data.email,
verified: result.data.verified,
tickets: result.data.tickets,
loading: false
});

View File

@ -29,4 +29,8 @@
margin-bottom: 20px;
text-align: left;
}
&__unverified {
margin-left: 15px;
}
}

View File

@ -91,7 +91,8 @@ describe('Login/Recover Widget', function () {
renderComponent({
session: {
pending: false,
failed: true
failed: true,
failMessage: 'INVALID_CREDENTIALS'
}
});
expect(loginForm.props.errors).to.deep.equal({password: 'Invalid password'});

View File

@ -19,18 +19,14 @@ import Message from 'core-components/message';
class MainHomePageLoginWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
sideToShow: 'front',
loginFormErrors: {},
recoverFormErrors: {},
recoverSent: false,
loadingLogin: false,
loadingRecover: false
};
}
state = {
sideToShow: 'front',
loginFormErrors: {},
recoverFormErrors: {},
recoverSent: false,
loadingLogin: false,
loadingRecover: false
};
componentDidUpdate(prevProps) {
if (!prevProps.session.failed && this.props.session.failed) {
@ -126,7 +122,11 @@ class MainHomePageLoginWidget extends React.Component {
let errors = _.extend({}, this.state.loginFormErrors);
if (this.props.session.failed) {
errors.password = i18n('ERROR_PASSWORD');
if (this.props.session.failMessage === 'INVALID_CREDENTIALS') {
errors.password = i18n('ERROR_PASSWORD');
} else if (this.props.session.failMessage === 'UNVERIFIED_USER') {
errors.email = i18n('UNVERIFIED_EMAIL');
}
}
return errors;

View File

@ -1,13 +1,18 @@
import React from 'react';
import {connect} from 'react-redux'
import i18n from 'lib-app/i18n';
import MainHomePageLoginWidget from 'app/main/main-home/main-home-page-login-widget';
import MainHomePagePortal from 'app/main/main-home/main-home-page-portal';
import Message from 'core-components/message';
class MainHomePage extends React.Component {
render() {
return (
<div className="main-home-page">
{this.renderMessage()}
<div className="col-md-4">
<MainHomePageLoginWidget />
</div>
@ -17,6 +22,37 @@ class MainHomePage extends React.Component {
</div>
);
}
renderMessage() {
switch (this.props.session.verify) {
case 'success':
return this.renderSuccess();
case 'failed':
return this.renderFailed();
default:
return null;
}
}
renderSuccess() {
return (
<Message title={i18n('VERIFY_SUCCESS')} type="success" className="main-home-page__message">
{i18n('VERIFY_SUCCESS_DESCRIPTION')}
</Message>
);
}
renderFailed() {
return (
<Message title={i18n('VERIFY_FAILED')} type="error" className="main-home-page__message">
{i18n('VERIFY_FAILED_DESCRIPTION')}
</Message>
);
}
}
export default MainHomePage;
export default connect((store) => {
return {
session: store.session
};
})(MainHomePage);

View File

@ -1,3 +1,8 @@
.main-home-page {
&__message {
margin-bottom: 20px;
margin-left: 20px;
margin-right: 20px;
}
}

View File

@ -44,6 +44,7 @@ class MainLayoutHeader extends React.Component {
return {
className: 'main-layout-header__languages',
value: this.props.config.language,
type: 'allowed',
onChange: this.changeLanguage.bind(this)
};
}

View File

@ -0,0 +1,38 @@
import React from 'react';
import {connect} from 'react-redux'
import {browserHistory} from 'react-router';
import SessionActions from 'actions/session-actions'
import API from 'lib-app/api-call';
import Message from 'core-components/message';
class MainVerifyTokenPage extends React.Component {
componentDidMount() {
API.call({
path: '/user/verify',
data: {
token: this.props.params.token,
email: this.props.params.email
}
}).then(() => {
this.props.dispatch(SessionActions.verify(true));
browserHistory.push('/');
}).catch(() => {
this.props.dispatch(SessionActions.verify(false));
browserHistory.push('/');
});
}
render() {
return null;
}
}
export default connect((store) => {
return {
session: store.session
};
})(MainVerifyTokenPage);

View File

@ -149,7 +149,7 @@ class FormField extends React.Component {
onChange(nativeEvent) {
let event = nativeEvent;
if (this.props.field === 'checkbox') {
if (this.props.field === 'checkbox' && !this.props.decorator) {
event = {
target: {
value: event.target.checked
@ -157,7 +157,7 @@ class FormField extends React.Component {
};
}
if (this.props.field === 'select') {
if (this.props.field === 'select' && !this.props.decorator) {
event = {
target: {
value: event.index

View File

@ -21,7 +21,7 @@ module.exports = [
} else {
response = {
status: 'fail',
message: 'Invalid Credientals'
message: 'INVALID_CREDENTIALS'
};
}
@ -88,6 +88,16 @@ module.exports = [
}
}
},
{
path: '/user/verify-token',
time: 200,
response: function () {
return {
status: 'success',
data: {}
};
}
},
{
path: '/user/signup',
time: 1000,
@ -145,6 +155,7 @@ module.exports = [
data: {
name: 'Kurt Gödel',
email: 'kurt@currycurrylady.hs',
verified: false,
tickets: _.times(13).map(() => {
return {
ticketNumber: '118551',

View File

@ -150,6 +150,8 @@ export default {
'LOAD_MORE': 'Load More',
'MY_NOTIFICATIONS': 'My notifications',
'ALL_NOTIFICATIONS': 'All notifications',
'VERIFY_SUCCESS': 'User verified',
'VERIFY_FAILED': 'Could not verify',
//ACTIVITIES
'ACTIVITY_COMMENT': 'commented ticket',
@ -209,6 +211,8 @@ export default {
'MAINTENANCE_MODE_DESCRIPTION': 'The support system is in maintenance mode, thus unavailable at the moment. We will come back as soon as possible.',
'EMAIL_TEMPLATES_DESCRIPTION': 'Here you can edit the templates of the emails that will be sent to users. Remember that the double brackets curly braces indicate a variable value. For example, \'name\' represents the user\'s name.',
'SYSTEM_PREFERENCES_DESCRIPTION': 'Here you can edit the preferences of the system.',
'VERIFY_SUCCESS_DESCRIPTION': 'You user has been verified correctly. You can log in now.',
'VERIFY_FAILED_DESCRIPTION': 'The verification could not be done.',
//ERRORS
'EMAIL_OR_PASSWORD': 'Email or password invalid',
@ -232,6 +236,8 @@ export default {
'ERROR_RETRIEVING_ARTICLES': 'An error occurred while trying to retrieve articles.',
'ERROR_LIST': 'Select at least one',
'ERROR_URL': 'Invalid URL',
'UNVERIFIED_EMAIL': 'Email is not verified yet',
'ERROR_UPDATING_SETTINGS': 'An error occurred while trying to update settings',
//MESSAGES
'SIGNUP_SUCCESS': 'You have registered successfully in our support system.',

View File

@ -9,7 +9,8 @@ class SessionReducer extends Reducer {
initDone: false,
logged: false,
pending: false,
failed: false
failed: false,
verify: null
};
}
@ -19,6 +20,7 @@ class SessionReducer extends Reducer {
'LOGIN_FULFILLED': this.onLoginCompleted.bind(this),
'LOGIN_REJECTED': this.onLoginFailed,
'LOGOUT_FULFILLED': this.onLogout,
'VERIFY': this.onVerify,
'USER_DATA_FULFILLED': this.onUserDataRetrieved,
'CHECK_SESSION_REJECTED': (state) => { return _.extend({}, state, {initDone: true})},
'SESSION_CHECKED': this.onSessionChecked,
@ -46,8 +48,9 @@ class SessionReducer extends Reducer {
});
}
onLoginFailed(state) {
onLoginFailed(state, payload) {
return _.extend({}, state, {
failMessage: payload.message,
logged: false,
pending: false,
failed: true
@ -127,6 +130,12 @@ class SessionReducer extends Reducer {
userTickets: userData.tickets
});
}
onVerify(state, payload) {
return _.extend({}, state, {
verify: (payload) ? 'success' : 'failed'
});
}
}
export default SessionReducer.getInstance();

View File

@ -18,7 +18,7 @@ class GetLogsController extends Controller {
public function handler() {
$page = Controller::request('page');
$logList = Log::find('LIMIT ? OFFSET ?', [10, 10*($page-1)+1]);
$logList = Log::find('ORDER BY id desc LIMIT ? OFFSET ?', [10, 10*($page-1)+1]);
Response::respondSuccess($logList->toArray());
}

View File

@ -28,6 +28,7 @@ class GetUsersController extends Controller {
$userListArray[] = [
'id' => $user->id,
'name' => $user->name,
'verified' => !$user->verificationToken,
'tickets' => $user->tickets,
'email' => $user->email,
'signupDate' => $user->signupDate

View File

@ -29,6 +29,7 @@ class GetUserController extends Controller {
Response::respondSuccess([
'name' => $user->name,
'email' => $user->email,
'verified' => !$user->verificationToken,
'tickets' => $parsedTicketList
]);
}

View File

@ -1,5 +1,6 @@
<div>
Welcome, {{name}} to our support center,
your email is {{to}},
your token is {{verificationToken}}
you can verify your user using this link
http://dev3.opensupports.com/verify-token/{{to}}/{{verificationToken}}
</div>

View File

@ -1,5 +1,6 @@
<div>
Bienvenido, {{name}} a nuestro centro de soporte,
tu email es {{to}},
tu codigo de verificacion es {{verificationToken}}
podes verificar tu usuatio entrando en este link
http://dev3.opensupports.com/verify-token/{{to}}/{{verificationToken}}
</div>

View File

@ -6,14 +6,7 @@ use Respect\Validation\Rules\AbstractRule;
class ValidLanguage extends AbstractRule {
//TODO: Use a list from database instead
private $languages = [
'en',
'es',
'de'
];
public function validate($ticketNumber) {
return in_array($ticketNumber, $this->languages);
return in_array($ticketNumber, \Language::LANGUAGES);
}
}

View File

@ -8,7 +8,7 @@ class Language extends DataStore {
'es',
'de',
'fr',
'pr',
'pt',
'jp',
'ru',
'cn',

View File

@ -34,7 +34,8 @@ class User extends DataStore {
return [
'email' => $this->email,
'id' => $this->id,
'name' => $this->name
'name' => $this->name,
'verified' => !$this->verificationToken
];
}
}

View File

@ -6,19 +6,19 @@ describe'system/edit-settings' do
result= request('/system/edit-settings', {
"csrf_userid" => $csrf_userid,
"csrf_token" => $csrf_token,
"maintenance-mode" => 1,
"maintenance-mode" => false,
"time-zone" => -3,
"layout" => 'full-width',
"allow-attachments" => 1,
"max-size" => 2,
"language" => 'es',
"language" => 'en',
"no-reply-email" => 'testemail@hotmail.com'
})
(result['status']).should.equal('success')
row = $database.getRow('setting', 'maintenance-mode', 'name')
(row['value']).should.equal('1')
(row['value']).should.equal('0')
row = $database.getRow('setting', 'time-zone', 'name')
(row['value']).should.equal('-3')
@ -30,7 +30,7 @@ describe'system/edit-settings' do
(row['value']).should.equal('2')
row = $database.getRow('setting', 'language', 'name')
(row['value']).should.equal('es')
(row['value']).should.equal('en')
row = $database.getRow('setting', 'no-reply-email', 'name')
(row['value']).should.equal('testemail@hotmail.com')
@ -44,8 +44,8 @@ describe'system/edit-settings' do
result= request('/system/edit-settings', {
"csrf_userid" => $csrf_userid,
"csrf_token" => $csrf_token,
"supportedLanguages" => '["en", "pr", "jp", "ru"]',
"allowedLanguages" => '["en","pr", "jp", "ru", "de"]'
"supportedLanguages" => '["en", "pt", "jp", "ru"]',
"allowedLanguages" => '["en","pt", "jp", "ru", "de"]'
})
(result['status']).should.equal('success')
@ -53,7 +53,7 @@ describe'system/edit-settings' do
row = $database.getRow('language', 'en', 'code')
(row['supported']).should.equal('1')
row = $database.getRow('language', 'pr', 'code')
row = $database.getRow('language', 'pt', 'code')
(row['supported']).should.equal('1')
row = $database.getRow('language', 'jp', 'code')
@ -65,7 +65,7 @@ describe'system/edit-settings' do
row = $database.getRow('language', 'en', 'code')
(row['allowed']).should.equal('1')
row = $database.getRow('language', 'pr', 'code')
row = $database.getRow('language', 'pt', 'code')
(row['allowed']).should.equal('1')
row = $database.getRow('language', 'jp', 'code')

View File

@ -11,7 +11,7 @@ describe '/system/get-settings' do
(result['data']['allowedLanguages'][1]).should.equal('es')
(result['data']['allowedLanguages'][2]).should.equal('de')
(result['data']['allowedLanguages'][3]).should.equal('fr')
(result['data']['allowedLanguages'][4]).should.equal('pr')
(result['data']['allowedLanguages'][4]).should.equal('pt')
(result['data']['allowedLanguages'][5]).should.equal('jp')
(result['data']['allowedLanguages'][6]).should.equal('ru')
(result['data']['allowedLanguages'][7]).should.equal('cn')

View File

@ -8,7 +8,6 @@ describe '/user/get' do
content: 'A Lannister always pays his debts.',
departmentId: 1,
language: 'en',
language: 'en',
csrf_userid: $csrf_userid,
csrf_token: $csrf_token
})