Feat(privacy): Add granular privacy controls of public profile (#17178)
* feat(privacy): Add granular privacy controls of public profile * feat(certs): Hide certs if showCerts is false
This commit is contained in:
committed by
Quincy Larson
parent
a1f2fc7c5c
commit
bb4bcbfb45
@ -20,6 +20,7 @@ export const types = createTypes([
|
||||
'optoUpdatePortfolio',
|
||||
'regresPortfolio',
|
||||
'resetFullBlocks',
|
||||
'updateLocalProfileUI',
|
||||
'updateMultipleUserFlags',
|
||||
'updateTheme',
|
||||
'updateUserFlag',
|
||||
@ -56,6 +57,8 @@ export const updateUserLang = createAction(
|
||||
(username, lang) => ({ username, languageTag: lang })
|
||||
);
|
||||
|
||||
export const updateLocalProfileUI = createAction(types.updateLocalProfileUI);
|
||||
|
||||
export const resetFullBlocks = createAction(types.resetFullBlocks);
|
||||
|
||||
export const updateUserCurrentChallenge = createAction(
|
||||
@ -294,6 +297,23 @@ export default composeReducers(
|
||||
languageTag
|
||||
}
|
||||
}
|
||||
}),
|
||||
[types.updateLocalProfileUI]:
|
||||
(
|
||||
state,
|
||||
{ payload: { username, profileUI } }
|
||||
) => ({
|
||||
...state,
|
||||
user: {
|
||||
...state.user,
|
||||
[username]: {
|
||||
...state.user[username],
|
||||
profileUI: {
|
||||
...state.user[username].profileUI,
|
||||
...profileUI
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
defaultState
|
||||
|
@ -38,7 +38,20 @@ const mapStateToProps = createSelector(
|
||||
userFoundSelector,
|
||||
(
|
||||
isSignedIn,
|
||||
{ isLocked, username: requestedUsername },
|
||||
{
|
||||
username: requestedUsername,
|
||||
profileUI: {
|
||||
isLocked,
|
||||
showAbout,
|
||||
showCerts,
|
||||
showHeatMap,
|
||||
showLocation,
|
||||
showName,
|
||||
showPoints,
|
||||
showPortfolio,
|
||||
showTimeLine
|
||||
} = {}
|
||||
},
|
||||
{ username: paramsUsername },
|
||||
currentUsername,
|
||||
showLoading,
|
||||
@ -47,12 +60,20 @@ const mapStateToProps = createSelector(
|
||||
isSignedIn,
|
||||
currentUsername,
|
||||
isCurrentUserProfile: paramsUsername === currentUsername,
|
||||
isLocked,
|
||||
isUserFound,
|
||||
fetchOtherUserCompleted: typeof isUserFound === 'boolean',
|
||||
paramsUsername,
|
||||
requestedUsername,
|
||||
showLoading
|
||||
isLocked,
|
||||
showLoading,
|
||||
showAbout,
|
||||
showCerts,
|
||||
showHeatMap,
|
||||
showLocation,
|
||||
showName,
|
||||
showPoints,
|
||||
showPortfolio,
|
||||
showTimeLine
|
||||
})
|
||||
);
|
||||
|
||||
@ -71,7 +92,15 @@ const propTypes = {
|
||||
isUserFound: PropTypes.bool,
|
||||
paramsUsername: PropTypes.string,
|
||||
requestedUsername: PropTypes.string,
|
||||
showAbout: PropTypes.bool,
|
||||
showCerts: PropTypes.bool,
|
||||
showHeatMap: PropTypes.bool,
|
||||
showLoading: PropTypes.bool,
|
||||
showLocation: PropTypes.bool,
|
||||
showName: PropTypes.bool,
|
||||
showPoints: PropTypes.bool,
|
||||
showPortfolio: PropTypes.bool,
|
||||
showTimeLine: PropTypes.bool,
|
||||
updateTitle: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@ -93,7 +122,15 @@ class Profile extends Component {
|
||||
isLocked,
|
||||
isUserFound,
|
||||
isCurrentUserProfile,
|
||||
paramsUsername
|
||||
paramsUsername,
|
||||
showAbout,
|
||||
showLocation,
|
||||
showName,
|
||||
showPoints,
|
||||
showHeatMap,
|
||||
showCerts,
|
||||
showPortfolio,
|
||||
showTimeLine
|
||||
} = this.props;
|
||||
const takeMeToChallenges = (
|
||||
<a href='/challenges/current-challenge'>
|
||||
@ -113,8 +150,8 @@ class Profile extends Component {
|
||||
<Alert bsStyle='info'>
|
||||
<p>
|
||||
{
|
||||
'In order to view their progress through the freeCodeCamp ' +
|
||||
'curriculum, they need to make all of thie solutions public'
|
||||
'In order to view their freeCodeCamp certiciations, ' +
|
||||
'they need to make their profile public'
|
||||
}
|
||||
</p>
|
||||
</Alert>
|
||||
@ -136,11 +173,16 @@ class Profile extends Component {
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<CamperHOC />
|
||||
<HeatMap />
|
||||
<Certificates />
|
||||
<Portfolio />
|
||||
<Timeline className='timelime-container' />
|
||||
<CamperHOC
|
||||
showAbout={ showAbout }
|
||||
showLocation={ showLocation }
|
||||
showName={ showName }
|
||||
showPoints={ showPoints }
|
||||
/>
|
||||
{ showHeatMap ? <HeatMap /> : null }
|
||||
{ showCerts ? <Certificates /> : null }
|
||||
{ showPortfolio ? <Portfolio /> : null }
|
||||
{ showTimeLine ? <Timeline className='timelime-container' /> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -31,6 +31,10 @@ const propTypes = {
|
||||
name: PropTypes.string,
|
||||
picture: PropTypes.string,
|
||||
points: PropTypes.number,
|
||||
showAbout: PropTypes.bool,
|
||||
showLocation: PropTypes.bool,
|
||||
showName: PropTypes.bool,
|
||||
showPoints: PropTypes.bool,
|
||||
username: PropTypes.string
|
||||
};
|
||||
|
||||
@ -40,17 +44,21 @@ function CamperHOC({
|
||||
location,
|
||||
points,
|
||||
picture,
|
||||
about
|
||||
about,
|
||||
showAbout,
|
||||
showLocation,
|
||||
showName,
|
||||
showPoints
|
||||
}) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Camper
|
||||
about={ about }
|
||||
location={ location }
|
||||
name={ name }
|
||||
about={ showAbout && about }
|
||||
location={ showLocation && location }
|
||||
name={ showName && name }
|
||||
picture={ picture }
|
||||
points={ points }
|
||||
points={ showPoints && points }
|
||||
username={ username }
|
||||
/>
|
||||
<hr />
|
||||
|
@ -15,6 +15,7 @@ import EmailSettings from './components/Email-Settings.jsx';
|
||||
import DangerZone from './components/DangerZone.jsx';
|
||||
import CertificationSettings from './components/Cert-Settings.jsx';
|
||||
import PortfolioSettings from './components/Portfolio-Settings.jsx';
|
||||
import PrivacySettings from './components/Privacy-Settings.jsx';
|
||||
import Honesty from './components/Honesty.jsx';
|
||||
|
||||
import {
|
||||
@ -101,6 +102,8 @@ export class Settings extends React.Component {
|
||||
<h1 className='text-center'>{ `Account Settings for ${username}` }</h1>
|
||||
<AboutSettings />
|
||||
<Spacer />
|
||||
<PrivacySettings />
|
||||
<Spacer />
|
||||
<EmailSettings />
|
||||
<Spacer />
|
||||
<InternetSettings />
|
||||
|
@ -9,15 +9,12 @@ import {
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import { FullWidthRow, Spacer } from '../../../helperComponents';
|
||||
import LockedSettings from './Locked-Settings.jsx';
|
||||
import ThemeSettings from './ThemeSettings.jsx';
|
||||
import Camper from './Camper.jsx';
|
||||
import UsernameSettings from './UsernameSettings.jsx';
|
||||
import SectionHeader from './SectionHeader.jsx';
|
||||
import { userSelector, toggleNightMode } from '../../../redux';
|
||||
import {
|
||||
updateUserBackend
|
||||
} from '../redux';
|
||||
import { updateUserBackend } from '../redux';
|
||||
import {
|
||||
BlockSaveButton,
|
||||
BlockSaveWrapper,
|
||||
@ -33,7 +30,6 @@ const mapStateToProps = createSelector(
|
||||
(
|
||||
{
|
||||
about,
|
||||
isLocked,
|
||||
location,
|
||||
name,
|
||||
picture,
|
||||
@ -45,7 +41,6 @@ const mapStateToProps = createSelector(
|
||||
about,
|
||||
currentTheme: theme,
|
||||
initialValues: { name, location, about, picture },
|
||||
isLocked,
|
||||
location,
|
||||
name,
|
||||
picture,
|
||||
@ -79,7 +74,6 @@ const propTypes = {
|
||||
currentTheme: PropTypes.string,
|
||||
fields: PropTypes.object,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
isLocked: PropTypes.bool,
|
||||
location: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
picture: PropTypes.string,
|
||||
@ -163,14 +157,11 @@ class AboutSettings extends PureComponent {
|
||||
currentTheme,
|
||||
fields: { _meta: { allPristine } },
|
||||
handleSubmit,
|
||||
isLocked,
|
||||
toggleNightMode,
|
||||
updateUserBackend,
|
||||
username
|
||||
} = this.props;
|
||||
const { view } = this.state;
|
||||
|
||||
const toggleIsLocked = () => updateUserBackend({ isLocked: !isLocked });
|
||||
const toggleTheme = () => toggleNightMode(username, currentTheme);
|
||||
return (
|
||||
<div className='about-settings'>
|
||||
@ -205,12 +196,6 @@ class AboutSettings extends PureComponent {
|
||||
</form>
|
||||
</FullWidthRow>
|
||||
<Spacer />
|
||||
<FullWidthRow>
|
||||
<LockedSettings
|
||||
isLocked={ isLocked }
|
||||
toggleIsLocked={ toggleIsLocked }
|
||||
/>
|
||||
</FullWidthRow>
|
||||
<FullWidthRow>
|
||||
<ThemeSettings
|
||||
currentTheme={ currentTheme }
|
||||
|
@ -42,9 +42,13 @@ function Camper({
|
||||
{ name && <p className='text-center name'>{ name }</p> }
|
||||
{ location && <p className='text-center location'>{ location }</p> }
|
||||
{ about && <p className='bio text-center'>{ about }</p> }
|
||||
<p className='text-center points'>
|
||||
{ `${points} ${pluralise('point', points > 1)}` }
|
||||
</p>
|
||||
{
|
||||
typeof points === 'number' ? (
|
||||
<p className='text-center points'>
|
||||
{ `${points} ${pluralise('point', points > 1)}` }
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
<br/>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,42 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
ControlLabel
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import TB from '../Toggle-Button';
|
||||
|
||||
const propTypes = {
|
||||
isLocked: PropTypes.bool,
|
||||
toggleIsLocked: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default function LockSettings({ isLocked, toggleIsLocked }) {
|
||||
return (
|
||||
<Row className='inline-form'>
|
||||
<Col sm={ 8 } xs={ 12 }>
|
||||
<ControlLabel htmlFor='isLocked'>
|
||||
<p>
|
||||
<strong>
|
||||
Make all of my solutions private
|
||||
<br />
|
||||
<em>(this disables your certificates)</em>
|
||||
</strong>
|
||||
</p>
|
||||
</ControlLabel>
|
||||
</Col>
|
||||
<Col sm={ 4 } xs={ 12 }>
|
||||
<TB
|
||||
name='isLocked'
|
||||
onChange={ toggleIsLocked }
|
||||
value={ isLocked }
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
LockSettings.displayName = 'LockSettings';
|
||||
LockSettings.propTypes = propTypes;
|
126
common/app/routes/Settings/components/Privacy-Settings.jsx
Normal file
126
common/app/routes/Settings/components/Privacy-Settings.jsx
Normal file
@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { updateMyProfileUI } from '../redux';
|
||||
import { userSelector } from '../../../redux';
|
||||
|
||||
import { FullWidthRow } from '../../../helperComponents';
|
||||
import SectionHeader from './SectionHeader.jsx';
|
||||
import ToggleSetting from './ToggleSetting.jsx';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
userSelector,
|
||||
({
|
||||
profileUI = {}
|
||||
}) => ({
|
||||
...profileUI
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ updateMyProfileUI }, dispatch);
|
||||
|
||||
const propTypes = {
|
||||
isLocked: PropTypes.bool,
|
||||
showAbout: PropTypes.bool,
|
||||
showCerts: PropTypes.bool,
|
||||
showHeatMap: PropTypes.bool,
|
||||
showLocation: PropTypes.bool,
|
||||
showName: PropTypes.bool,
|
||||
showPoints: PropTypes.bool,
|
||||
showPortfolio: PropTypes.bool,
|
||||
showTimeLine: PropTypes.bool,
|
||||
updateMyProfileUI: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function PrivacySettings(props) {
|
||||
const {
|
||||
isLocked,
|
||||
showAbout,
|
||||
showCerts,
|
||||
showHeatMap,
|
||||
showLocation,
|
||||
showName,
|
||||
showPoints,
|
||||
showPortfolio,
|
||||
showTimeLine,
|
||||
updateMyProfileUI
|
||||
} = props;
|
||||
const toggleFlag = flag =>
|
||||
() => updateMyProfileUI({ profileUI: { [flag]: !props[flag] } });
|
||||
return (
|
||||
<div className='privacy-settings'>
|
||||
<SectionHeader>Privacy Settings</SectionHeader>
|
||||
<FullWidthRow>
|
||||
<p>
|
||||
The settings in this section enable you to control what is show on{' '}
|
||||
your freeCodeCamp public profile.
|
||||
</p>
|
||||
<ToggleSetting
|
||||
action='Make my profile completely private'
|
||||
explain='Your certifications will be disabled'
|
||||
flag={ isLocked }
|
||||
flagName='isLocked'
|
||||
toggleFlag={ toggleFlag('isLocked') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my name completely private'
|
||||
flag={ !showName }
|
||||
flagName='name'
|
||||
toggleFlag={ toggleFlag('showName') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my location completely private'
|
||||
flag={ !showLocation }
|
||||
flagName='showLocation'
|
||||
toggleFlag={ toggleFlag('showLocation') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my "about me" completely private'
|
||||
flag={ !showAbout }
|
||||
flagName='showAbout'
|
||||
toggleFlag={ toggleFlag('showAbout') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my points completely private'
|
||||
flag={ !showPoints }
|
||||
flagName='showPoints'
|
||||
toggleFlag={ toggleFlag('showPoints') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my heat map completely private'
|
||||
flag={ !showHeatMap }
|
||||
flagName='showHeatMap'
|
||||
toggleFlag={ toggleFlag('showHeatMap') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my certifications completely private'
|
||||
explain='Your certifications will be disabled'
|
||||
flag={ !showCerts }
|
||||
flagName='showCerts'
|
||||
toggleFlag={ toggleFlag('showCerts') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my portfolio completely private'
|
||||
flag={ !showPortfolio }
|
||||
flagName='showPortfolio'
|
||||
toggleFlag={ toggleFlag('showPortfolio') }
|
||||
/>
|
||||
<ToggleSetting
|
||||
action='Make my time line completely private'
|
||||
flag={ !showTimeLine }
|
||||
flagName='showTimeLine'
|
||||
toggleFlag={ toggleFlag('showTimeLine') }
|
||||
/>
|
||||
</FullWidthRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PrivacySettings.displayName = 'PrivacySettings';
|
||||
PrivacySettings.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PrivacySettings);
|
53
common/app/routes/Settings/components/ToggleSetting.jsx
Normal file
53
common/app/routes/Settings/components/ToggleSetting.jsx
Normal file
@ -0,0 +1,53 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
ControlLabel
|
||||
} from 'react-bootstrap';
|
||||
|
||||
import TB from '../Toggle-Button';
|
||||
|
||||
const propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
explain: PropTypes.string,
|
||||
flag: PropTypes.bool.isRequired,
|
||||
flagName: PropTypes.string.isRequired,
|
||||
toggleFlag: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default function ToggleSetting({
|
||||
action,
|
||||
explain,
|
||||
flag,
|
||||
flagName,
|
||||
toggleFlag
|
||||
}) {
|
||||
return (
|
||||
<Row className='inline-form'>
|
||||
<Col sm={ 8 } xs={ 12 }>
|
||||
<ControlLabel htmlFor={ flagName }>
|
||||
<p>
|
||||
<strong>
|
||||
{ action }
|
||||
</strong>
|
||||
<br />
|
||||
{
|
||||
explain ? <em>{explain}</em> : null
|
||||
}
|
||||
</p>
|
||||
</ControlLabel>
|
||||
</Col>
|
||||
<Col sm={ 4 } xs={ 12 }>
|
||||
<TB
|
||||
name={ flagName }
|
||||
onChange={ toggleFlag }
|
||||
value={ flag }
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
ToggleSetting.displayName = 'ToggleSetting';
|
||||
ToggleSetting.propTypes = propTypes;
|
@ -34,6 +34,7 @@ export const types = createTypes([
|
||||
createAsyncTypes('updateUserBackend'),
|
||||
createAsyncTypes('deletePortfolio'),
|
||||
createAsyncTypes('updateMyPortfolio'),
|
||||
createAsyncTypes('updateMyProfileUI'),
|
||||
'updateNewUsernameValidity',
|
||||
createAsyncTypes('validateUsername'),
|
||||
createAsyncTypes('refetchCompletedChallenges'),
|
||||
@ -85,6 +86,14 @@ export const updateMyPortfolioError = createAction(
|
||||
export const deletePortfolio = createAction(types.deletePortfolio.start);
|
||||
export const deletePortfolioError = createAction(types.deletePortfolio.error);
|
||||
|
||||
export const updateMyProfileUI = createAction(types.updateMyProfileUI.start);
|
||||
export const updateMyProfileUIComplete = createAction(
|
||||
types.updateMyProfileUI.complete
|
||||
);
|
||||
export const updateMyProfileUIError = createAction(
|
||||
types.updateMyProfileUI.error
|
||||
);
|
||||
|
||||
export const resetProgress = createAction(types.resetProgress.start);
|
||||
export const resetProgressComplete = createAction(types.resetProgress.complete);
|
||||
export const resetProgressError = createAction(
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
types,
|
||||
refetchCompletedChallenges,
|
||||
updateUserBackendComplete,
|
||||
updateMyPortfolioComplete
|
||||
updateMyPortfolioComplete,
|
||||
updateMyProfileUIComplete
|
||||
} from './';
|
||||
import { makeToast } from '../../../Toasts/redux';
|
||||
import {
|
||||
@ -18,7 +19,8 @@ import {
|
||||
updateUserEmail,
|
||||
updateMultipleUserFlags,
|
||||
regresPortfolio,
|
||||
optoUpdatePortfolio
|
||||
optoUpdatePortfolio,
|
||||
updateLocalProfileUI
|
||||
} from '../../../entities';
|
||||
|
||||
import { postJSON$ } from '../../../../utils/ajax-stream';
|
||||
@ -188,9 +190,41 @@ function updateUserEmailEpic(actions, { getState }) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateMyProfileUIEpic(action$, { getState }) {
|
||||
const toggle = action$::ofType(types.updateMyProfileUI.start);
|
||||
|
||||
const server = toggle.flatMap(({payload: { profileUI }}) => {
|
||||
const state = getState();
|
||||
const { csrfToken: _csrf } = state.app;
|
||||
const username = usernameSelector(state);
|
||||
const oldUI = { ...userSelector(state).profileUI };
|
||||
return postJSON$('/update-my-profile-ui', { _csrf, profileUI })
|
||||
.map(updateMyProfileUIComplete)
|
||||
.catch(
|
||||
doActionOnError(
|
||||
() => Observable.of(
|
||||
makeToast({
|
||||
message:
|
||||
'Something went wrong saving your privacy settings, ' +
|
||||
'please try again.'
|
||||
}),
|
||||
updateLocalProfileUI({username, profileUI: oldUI })
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
const optimistic = toggle.flatMap(({payload: { profileUI }}) => {
|
||||
const username = usernameSelector(getState());
|
||||
return Observable.of(updateLocalProfileUI({username, profileUI}));
|
||||
});
|
||||
|
||||
return Observable.merge(server, optimistic);
|
||||
}
|
||||
|
||||
export default combineEpics(
|
||||
backendUserUpdateEpic,
|
||||
refetchCompletedChallengesEpic,
|
||||
updateMyPortfolioEpic,
|
||||
updateUserEmailEpic
|
||||
updateUserEmailEpic,
|
||||
updateMyProfileUIEpic
|
||||
);
|
||||
|
@ -63,6 +63,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-settings {
|
||||
|
||||
.inline-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.btn-group > label {
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
|
||||
em {
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{ns}-email-container {
|
||||
.below(sm, {
|
||||
text-align: center;
|
||||
|
@ -747,6 +747,22 @@ module.exports = function(User) {
|
||||
`);
|
||||
};
|
||||
|
||||
User.prototype.updateMyProfileUI = function updateMyProfileUI(profileUI) {
|
||||
const oldUI = { ...this.profileUI };
|
||||
const update = {
|
||||
profileUI: {
|
||||
...oldUI,
|
||||
...profileUI
|
||||
}
|
||||
};
|
||||
|
||||
return this.update$(update)
|
||||
.do(() => Object.assign(this, update))
|
||||
.map(() => dedent`
|
||||
Your privacy settings have been updated.
|
||||
`);
|
||||
};
|
||||
|
||||
User.prototype.updateMyUsername = function updateMyUsername(newUsername) {
|
||||
return Observable.defer(
|
||||
() => {
|
||||
|
@ -74,10 +74,6 @@
|
||||
},
|
||||
"require": true
|
||||
},
|
||||
"bio": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"about": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
@ -107,11 +103,6 @@
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"isLocked": {
|
||||
"type": "boolean",
|
||||
"description": "Campers profile does not show challenges/certificates to the public",
|
||||
"default": false
|
||||
},
|
||||
"currentChallengeId": {
|
||||
"type": "string",
|
||||
"description": "The challenge last visited by the user",
|
||||
@ -210,6 +201,55 @@
|
||||
"type": "string",
|
||||
"default": "default"
|
||||
},
|
||||
"profileUI": {
|
||||
"type": {
|
||||
"isLocked": {
|
||||
"type": "boolean",
|
||||
"description": "Campers profile shows only their username and avatar to the public",
|
||||
"default": true
|
||||
},
|
||||
"showAbout": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showCerts": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showHeatMap": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showLocation": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showName": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showPoints": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showPortfolio": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
},
|
||||
"showTimeLine": {
|
||||
"type": "boolean",
|
||||
"description": "For granular control of what is shown to the public",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"badges": {
|
||||
"type": {
|
||||
"coreTeam": {
|
||||
|
@ -308,7 +308,6 @@ export default function certificate(app) {
|
||||
username,
|
||||
{
|
||||
isCheater: true,
|
||||
isLocked: true,
|
||||
isFrontEndCert: true,
|
||||
isBackEndCert: true,
|
||||
isFullStackCert: true,
|
||||
@ -322,11 +321,13 @@ export default function certificate(app) {
|
||||
isHonest: true,
|
||||
username: true,
|
||||
name: true,
|
||||
completedChallenges: true
|
||||
completedChallenges: true,
|
||||
profileUI: true
|
||||
}
|
||||
)
|
||||
.subscribe(
|
||||
user => {
|
||||
const { isLocked, showCerts } = user.profileUI;
|
||||
const profile = `/portfolio/${user.username}`;
|
||||
if (!user) {
|
||||
req.flash(
|
||||
@ -341,7 +342,7 @@ export default function certificate(app) {
|
||||
'danger',
|
||||
dedent`
|
||||
This user needs to add their name to their account
|
||||
in order for others to be able to view their certificate.
|
||||
in order for others to be able to view their certification.
|
||||
`
|
||||
);
|
||||
return res.redirect(profile);
|
||||
@ -351,13 +352,25 @@ export default function certificate(app) {
|
||||
return res.redirect(profile);
|
||||
}
|
||||
|
||||
if (user.isLocked) {
|
||||
if (isLocked) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
${username} has chosen to make their profile
|
||||
private. They will need to make their profile public
|
||||
in order for others to be able to view their certificate.
|
||||
in order for others to be able to view their certification.
|
||||
`
|
||||
);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!showCerts) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
${username} has chosen to make their certifications
|
||||
private. They will need to make their certifications public
|
||||
in order for others to be able to view them.
|
||||
`
|
||||
);
|
||||
return res.redirect('/');
|
||||
|
@ -114,6 +114,18 @@ export default function settingsController(app) {
|
||||
);
|
||||
}
|
||||
|
||||
function updateMyProfileUI(req, res, next) {
|
||||
const {
|
||||
user,
|
||||
body: { profileUI }
|
||||
} = req;
|
||||
return user.updateMyProfileUI(profileUI)
|
||||
.subscribe(
|
||||
message => res.json({ message }),
|
||||
next
|
||||
);
|
||||
}
|
||||
|
||||
function updateMyProjects(req, res, next) {
|
||||
const {
|
||||
user,
|
||||
@ -164,6 +176,11 @@ export default function settingsController(app) {
|
||||
ifNoUser401,
|
||||
updateMyPortfolio
|
||||
);
|
||||
api.post(
|
||||
'/update-my-profile-ui',
|
||||
ifNoUser401,
|
||||
updateMyProfileUI
|
||||
);
|
||||
api.post(
|
||||
'/update-my-projects',
|
||||
ifNoUser401,
|
||||
|
@ -23,13 +23,13 @@ export const publicUserProps = [
|
||||
'isHonest',
|
||||
'isInfosecQaCert',
|
||||
'isJsAlgoDataStructCert',
|
||||
'isLocked',
|
||||
'isRespWebDesignCert',
|
||||
'linkedin',
|
||||
'location',
|
||||
'name',
|
||||
'points',
|
||||
'portfolio',
|
||||
'profileUI',
|
||||
'projects',
|
||||
'streak',
|
||||
'twitter',
|
||||
|
Reference in New Issue
Block a user