Skip to content

Commit

Permalink
Added settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
amir-qayyum-khan committed Jun 28, 2016
1 parent 8b1e8e2 commit 75c0fd8
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 11 deletions.
26 changes: 26 additions & 0 deletions static/js/components/PrivacyForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @flow
/* global SETTINGS: false */
import React from 'react';

import ProfileFormFields from '../util/ProfileFormFields';
import type { Profile } from '../flow/profileTypes';
import type { UIState } from '../reducers/ui';

class PrivacyForm extends ProfileFormFields {
props: {
profile: Profile,
ui: UIState,
updateProfile: Function,
};

render() {
return (
<div>
<h4 className="privacy-form-heading">Who can see your profile?</h4>
{ this.boundRadioGroupField(['account_privacy'], '', this.privacyOptions) }
</div>
);
}
}

export default PrivacyForm;
6 changes: 3 additions & 3 deletions static/js/components/PrivacyTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from 'react';
import Grid, { Cell } from 'react-mdl/lib/Grid';

import PrivacyForm from './PrivacyForm';
import ProfileProgressControls from './ProfileProgressControls';
import ProfileFormFields from '../util/ProfileFormFields';
import {
Expand Down Expand Up @@ -31,10 +32,9 @@ class PrivacyTab extends ProfileFormFields {
We care about your privacy.
</Cell>
</Grid>
<Grid className="profile-tab-grid">
<Grid className="profile-tab-grid privacy-form">
<Cell col={12}>
<h4>Who can see your profile?</h4>
{ this.boundRadioGroupField(['account_privacy'], '', this.privacyOptions) } <br />
<PrivacyForm {...this.props} />
</Cell>
<Cell col={12}>
<ProfileProgressControls
Expand Down
2 changes: 1 addition & 1 deletion static/js/components/ProfileProgressControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class ProfileProgressControls extends React.Component {
prevUrl: React.PropTypes.string,
isLastTab: React.PropTypes.bool,
saveProfile: React.PropTypes.func.isRequired,
profile: React.PropTypes.object.isRequired,
profile: React.PropTypes.object,
ui: React.PropTypes.object.isRequired,
validator: React.PropTypes.func.isRequired
};
Expand Down
5 changes: 5 additions & 0 deletions static/js/containers/LoginButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class LoginButton extends React.Component {
title={title}
bsStyle="danger"
id="logout-button">
<LinkContainer to={{ pathname: '/settings' }} active={false}>
<MenuItem>
Settings
</MenuItem>
</LinkContainer>
<MenuItem
href="/logout"
eventKey="logout">
Expand Down
10 changes: 5 additions & 5 deletions static/js/containers/ProfileFormContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,16 @@ class ProfileFormContainer extends React.Component {
profiles: state.profiles,
ui: state.ui,
};
}
};

fetchProfile: Function = (): void => {
const { dispatch, profiles, params: { username } } = this.props;
fetchProfile: Function = (username: string): void => {
const { dispatch, profiles } = this.props;
if (profiles[username] === undefined || profiles[username].getStatus === undefined) {
dispatch(fetchUserProfile(username));
}
};
}

updateProfile(isEdit: boolean, profile: Profile) {
updateProfile: Function = (isEdit: boolean, profile: Profile): void => {
const { dispatch } = this.props;
const username = SETTINGS.username;

Expand Down
96 changes: 96 additions & 0 deletions static/js/containers/SettingsPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// @flow
/* global SETTINGS: false */
import React from 'react';
import { connect } from 'react-redux';
import Loader from 'react-loader';
import Grid, { Cell } from 'react-mdl/lib/Grid';

import Jumbotron from '../components/Jumbotron';
import {
startProfileEdit,
FETCH_PROCESSING,
} from '../actions/index';
import ProfileFormContainer from './ProfileFormContainer';
import PrivacyForm from '../components/PrivacyForm';
import ProfileProgressControls from '../components/ProfileProgressControls';
import {
combineValidators,
privacyValidation,
} from '../util/validation';
import { getPreferredName } from '../util/util';
import type { Profile } from '../flow/profileTypes';
import type { UIState } from '../reducers/ui';

class SettingsPage extends ProfileFormContainer {
props: {
profiles: {[key: string]: Profile},
dispatch: Function,
ui: UIState,
};

componentWillMount() {
this.startSettingsEdit();
}

startSettingsEdit() {
const { dispatch } = this.props;
dispatch(startProfileEdit(SETTINGS.username));
}

render() {
const { profiles, ui } = this.props;
let profile;
let errors;
let isEdit = true;
let loaded = false;
let username = SETTINGS.username;
let preferredName = SETTINGS.name;

if (profiles[username] !== undefined) {
let profileFromStore = profiles[username];
loaded = profileFromStore.getStatus !== FETCH_PROCESSING;
if (profileFromStore.edit !== undefined) {
errors = profileFromStore.edit.errors;
profile = profileFromStore.edit.profile;
} else {
profile = profileFromStore.profile;
errors = {};
isEdit = false;
}
preferredName = getPreferredName(profile);
}

return (
<Loader loaded={loaded}>
<Jumbotron profile={profile} text={preferredName}>
<div className="card-copy">
<Grid className="profile-tab-grid privacy-form">
<Cell col={12}>
<PrivacyForm
{...this.props}
errors={errors}
profile={profile}
updateProfile={this.updateProfile.bind(this, isEdit)}
/>
</Cell>
<Cell col={12}>
<ProfileProgressControls
nextUrl="/dashboard"
isLastTab={true}
saveProfile={this.saveProfile.bind(this, isEdit)}
profile={profile}
ui={ui}
validator={
combineValidators(privacyValidation)
}
/>
</Cell>
</Grid>
</div>
</Jumbotron>
</Loader>
);
}
}

export default connect(ProfileFormContainer.mapStateToProps)(SettingsPage);
96 changes: 96 additions & 0 deletions static/js/containers/SettingsPage_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* global SETTINGS: false */
import '../global_init';
import TestUtils from 'react-addons-test-utils';
import { assert } from 'chai';

import {
START_PROFILE_EDIT,
UPDATE_PROFILE_VALIDATION,
REQUEST_PATCH_USER_PROFILE,
RECEIVE_PATCH_USER_PROFILE_SUCCESS,
CLEAR_PROFILE_EDIT,

receiveGetUserProfileSuccess
} from '../actions';
import IntegrationTestHelper from '../util/integration_test_helper';
import * as api from '../util/api';
import { USER_PROFILE_RESPONSE } from '../constants';

describe("SettingsPage", function() {
this.timeout(5000);
let nextButtonSelector = '.progress-button.next';
let listenForActions, renderComponent, helper, patchUserProfileStub;
let userActions = [START_PROFILE_EDIT];

describe ("Settings page tests", () => {
beforeEach(() => {
helper = new IntegrationTestHelper();
listenForActions = helper.listenForActions.bind(helper);
renderComponent = helper.renderComponent.bind(helper);
patchUserProfileStub = helper.sandbox.stub(api, 'patchUserProfile');

helper.profileGetStub.
withArgs(SETTINGS.username).
returns(
Promise.resolve(Object.assign({}, USER_PROFILE_RESPONSE, {
username: SETTINGS.username
}))
);
});

afterEach(() => {
helper.cleanup();
});

let confirmSaveButtonBehavior = (updatedProfile, pageElements, validationFailure=false) => {
let { div, button } = pageElements;
button = button || div.querySelector(nextButtonSelector);
patchUserProfileStub.throws("Invalid arguments");
patchUserProfileStub.withArgs(SETTINGS.username, updatedProfile).returns(Promise.resolve(updatedProfile));

let actions = [];
if (!validationFailure) {
actions.push(
REQUEST_PATCH_USER_PROFILE,
RECEIVE_PATCH_USER_PROFILE_SUCCESS,
START_PROFILE_EDIT,
CLEAR_PROFILE_EDIT
);
}
actions.push(
UPDATE_PROFILE_VALIDATION
);
return listenForActions(actions, () => {
TestUtils.Simulate.click(button);
});
};

it('shows the privacy form', () => {
return renderComponent("/settings", userActions).then(([, div]) => {
let question = div.getElementsByClassName('privacy-form-heading')[0];
assert.equal(question.textContent, 'Who can see your profile?');
});
});

describe('save privacy form', () => {
it('save privacy changes', () => {
return renderComponent("/settings", userActions).then(([, div]) => {
let button = div.querySelector(nextButtonSelector);
let receivedProfile = Object.assign({}, USER_PROFILE_RESPONSE, {
account_privacy: 'public'
});

helper.store.dispatch(receiveGetUserProfileSuccess(SETTINGS.username, receivedProfile));

assert(button.innerHTML.includes("I'm Done!"));
let updatedProfile = Object.assign({}, receivedProfile, {
email_optin: true,
filled_out: true
});

return confirmSaveButtonBehavior(updatedProfile, {button: button});
});
});
});
});
});
6 changes: 4 additions & 2 deletions static/js/containers/UserPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import ProfileFormContainer from './ProfileFormContainer';

class UserPage extends ProfileFormContainer {
componentDidMount() {
this.fetchProfile();
const { params: { username } } = this.props;
this.fetchProfile(username);
}

componentDidUpdate() {
this.fetchProfile();
const { params: { username } } = this.props;
this.fetchProfile(username);
}

componentWillUnmount() {
Expand Down
2 changes: 2 additions & 0 deletions static/js/dashboard_routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import getMuiTheme from 'material-ui/styles/getMuiTheme';

import App from './containers/App';
import DashboardPage from './containers/DashboardPage';
import SettingsPage from './containers/SettingsPage';
import ProfilePage from './containers/ProfilePage';
import PersonalTab from './components/PersonalTab';
import EmploymentTab from './components/EmploymentTab';
Expand Down Expand Up @@ -40,6 +41,7 @@ export function makeDashboardRoutes(browserHistory: Object, store: Object, onRou
<Route path="privacy" component={PrivacyTab} />
</Route>
<Route path="/terms_of_service" component={TermsOfServicePage} />
<Route path="/settings" component={SettingsPage} />
<Route path="/users" component={UserPage} >
<IndexRedirect to={`${SETTINGS.username}`} />
<Route path=":username" component={User} />
Expand Down
1 change: 1 addition & 0 deletions ui/url_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
PROFILE_URL = '/profile/'
TERMS_OF_SERVICE_URL = '/terms_of_service/'
USERS_URL = '/users/'
SETTINGS_URL = "/settings/"
2 changes: 2 additions & 0 deletions ui/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
PROFILE_URL,
TERMS_OF_SERVICE_URL,
USERS_URL,
SETTINGS_URL,
)
from ui.views import (
dashboard,
Expand All @@ -22,6 +23,7 @@
PROFILE_URL,
TERMS_OF_SERVICE_URL,
USERS_URL,
SETTINGS_URL,
]
]

Expand Down

0 comments on commit 75c0fd8

Please sign in to comment.