Merge pull request #2022 from benjiwheeler/account-nav-component

split account nav into its own component, pass logout callback to GUI
This commit is contained in:
Benjamin Wheeler 2018-09-05 13:32:45 -04:00 committed by GitHub
commit 3c6530fe85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 239 additions and 141 deletions

View file

@ -0,0 +1,101 @@
const classNames = require('classnames');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const PropTypes = require('prop-types');
const React = require('react');
const Avatar = require('../../avatar/avatar.jsx');
const Dropdown = require('../../dropdown/dropdown.jsx');
require('./accountnav.scss');
const AccountNav = ({
classroomId,
isEducator,
isOpen,
isStudent,
profileUrl,
thumbnailUrl,
username,
onClick,
onClickLogout,
onClose
}) => (
<div className="account-nav">
<a
className={classNames({
'user-info': true,
'open': isOpen
})}
href="#"
onClick={onClick}
>
<Avatar
alt=""
src={thumbnailUrl}
/>
<span className="profile-name">
{username}
</span>
</a>
<Dropdown
as="ul"
className={process.env.SCRATCH_ENV}
isOpen={isOpen}
onRequestClose={onClose}
>
<li>
<a href={profileUrl}>
<FormattedMessage id="general.profile" />
</a>
</li>
<li>
<a href="/mystuff/">
<FormattedMessage id="general.myStuff" />
</a>
</li>
{isEducator ? [
<li key="my-classes-li">
<a href="/educators/classes/">
<FormattedMessage id="general.myClasses" />
</a>
</li>
] : []}
{isStudent ? [
<li key="my-class-li">
<a href={`/classes/${classroomId}/`}>
<FormattedMessage id="general.myClass" />
</a>
</li>
] : []}
<li>
<a href="/accounts/settings/">
<FormattedMessage id="general.accountSettings" />
</a>
</li>
<li className="divider">
<a
href="#"
onClick={onClickLogout}
>
<FormattedMessage id="navigation.signOut" />
</a>
</li>
</Dropdown>
</div>
);
AccountNav.propTypes = {
classroomId: PropTypes.string,
isEducator: PropTypes.bool,
isOpen: PropTypes.bool,
isStudent: PropTypes.bool,
onClick: PropTypes.func,
onClickLogout: PropTypes.func,
onClose: PropTypes.func,
profileUrl: PropTypes.string,
thumbnailUrl: PropTypes.string,
username: PropTypes.string
};
module.exports = injectIntl(AccountNav);

View file

@ -0,0 +1,98 @@
@import "../../../colors";
@import "../../../frameless";
.account-nav {
.user-info {
display: inline-block;
padding: 14px 15px 4px 15px;
max-width: 260px;
height: 33px;
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
color: $type-white;
font-size: .8125rem;
font-weight: normal;
.avatar {
margin-right: 10px;
border-radius: 3px;
width: 24px;
height: 24px;
vertical-align: middle;
}
&:hover {
background-color: $active-gray;
}
&.open {
background-color: $active-gray;
}
&:after {
display: inline-block;
margin-left: 8px;
background-image: url("/images/dropdown.png");
background-repeat: no-repeat;
background-position: center center;
background-size: 50%;
width: 20px;
height: 20px;
vertical-align: middle;
content: " ";
}
}
.dropdown {
top: 50px;
padding: 0;
padding-top: 5px;
width: 100%;
box-sizing: border-box;
}
}
//4 columns
@media only screen and (max-width: $mobile - 1) {
.account-nav {
margin-left: 0;
.user-info {
.avatar {
margin-right: 0;
}
&:after {
display: none;
}
}
}
}
//6 columns
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.account-nav {
margin-left: 0;
.user-info {
.avatar {
margin-right: 0;
}
&:after {
display: none;
}
}
}
}
//8 columns
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.account-nav {
margin-left: 0;
}
}

View file

@ -11,7 +11,6 @@ const messageCountActions = require('../../../redux/message-count.js');
const sessionActions = require('../../../redux/session.js'); const sessionActions = require('../../../redux/session.js');
const api = require('../../../lib/api'); const api = require('../../../lib/api');
const Avatar = require('../../avatar/avatar.jsx');
const Button = require('../../forms/button.jsx'); const Button = require('../../forms/button.jsx');
const Dropdown = require('../../dropdown/dropdown.jsx'); const Dropdown = require('../../dropdown/dropdown.jsx');
const Form = require('../../forms/form.jsx'); const Form = require('../../forms/form.jsx');
@ -21,6 +20,7 @@ const Login = require('../../login/login.jsx');
const Modal = require('../../modal/base/modal.jsx'); const Modal = require('../../modal/base/modal.jsx');
const NavigationBox = require('../base/navigation.jsx'); const NavigationBox = require('../base/navigation.jsx');
const Registration = require('../../registration/registration.jsx'); const Registration = require('../../registration/registration.jsx');
const AccountNav = require('./accountnav.jsx');
require('./navigation.scss'); require('./navigation.scss');
@ -113,6 +113,8 @@ class Navigation extends React.Component {
handleCloseLogin () { handleCloseLogin () {
this.setState({loginOpen: false}); this.setState({loginOpen: false});
} }
// NOTE: TODO: continue here. Should move these two functions up to a redux level,
// maybe into session...
handleLogIn (formData, callback) { handleLogIn (formData, callback) {
this.setState({loginError: null}); this.setState({loginError: null});
formData.useMessages = true; formData.useMessages = true;
@ -268,66 +270,18 @@ class Navigation extends React.Component {
className="link right account-nav" className="link right account-nav"
key="account-nav" key="account-nav"
> >
<a <AccountNav
className={classNames({ classroomId={this.props.session.session.user.classroomId}
'user-info': true, isEducator={this.props.permissions.educator}
'open': this.state.accountNavOpen
})}
href="#"
onClick={this.handleAccountNavClick}
>
<Avatar
alt=""
src={this.props.session.session.user.thumbnailUrl}
/>
<span className="profile-name">
{this.props.session.session.user.username}
</span>
</a>
<Dropdown
as="ul"
className={process.env.SCRATCH_ENV}
isOpen={this.state.accountNavOpen} isOpen={this.state.accountNavOpen}
onRequestClose={this.handleCloseAccountNav} isStudent={this.props.permissions.student}
> profileUrl={this.getProfileUrl()}
<li> thumbnailUrl={this.props.session.session.user.thumbnailUrl}
<a href={this.getProfileUrl()}> username={this.props.session.session.user.username}
<FormattedMessage id="general.profile" /> onClick={this.handleAccountNavClick}
</a> onClickLogout={this.handleLogOut}
</li> onClose={this.handleCloseAccountNav}
<li> />
<a href="/mystuff/">
<FormattedMessage id="general.myStuff" />
</a>
</li>
{this.props.permissions.educator ? [
<li key="my-classes-li">
<a href="/educators/classes/">
<FormattedMessage id="general.myClasses" />
</a>
</li>
] : []}
{this.props.permissions.student ? [
<li key="my-class-li">
<a href={`/classes/${this.props.session.session.user.classroomId}/`}>
<FormattedMessage id="general.myClass" />
</a>
</li>
] : []}
<li>
<a href="/accounts/settings/">
<FormattedMessage id="general.accountSettings" />
</a>
</li>
<li className="divider">
<a
href="#"
onClick={this.handleLogOut}
>
<FormattedMessage id="navigation.signOut" />
</a>
</li>
</Dropdown>
</li> </li>
] : [ ] : [
<li <li
@ -437,6 +391,11 @@ const mapStateToProps = state => ({
searchTerm: state.navigation searchTerm: state.navigation
}); });
const ConnectedNavigation = connect(mapStateToProps)(Navigation); const mapDispatchToProps = () => ({});
const ConnectedNavigation = connect(
mapStateToProps,
mapDispatchToProps
)(Navigation);
module.exports = injectIntl(ConnectedNavigation); module.exports = injectIntl(ConnectedNavigation);

View file

@ -182,55 +182,6 @@
} }
} }
} }
.account-nav {
.user-info {
padding-top: 14px;
max-width: 260px;
}
> a {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
font-size: .8125rem;
font-weight: normal;
.avatar {
margin-right: 10px;
border-radius: 3px;
width: 24px;
height: 24px;
vertical-align: middle;
}
&.open {
background-color: $active-gray;
}
&:after {
display: inline-block;
margin-left: 8px;
background-image: url("/images/dropdown.png");
background-repeat: no-repeat;
background-position: center center;
background-size: 50%;
width: 20px;
height: 20px;
vertical-align: middle;
content: " ";
}
}
.dropdown {
top: 50px;
padding: 0;
padding-top: 5px;
width: 100%;
box-sizing: border-box;
}
}
} }
//4 columns //4 columns
@ -242,20 +193,6 @@
&.login-item { &.login-item {
margin-left: 0; margin-left: 0;
} }
&.account-nav {
margin-left: 0;
> a {
.avatar {
margin-right: 0;
}
&:after {
display: none;
}
}
}
} }
.create, .create,
@ -280,20 +217,6 @@
&.login-item { &.login-item {
margin-left: 0; margin-left: 0;
} }
&.account-nav {
margin-left: 0;
> a {
.avatar {
margin-right: 0;
}
&:after {
display: none;
}
}
}
} }
.discuss, .discuss,
@ -313,8 +236,7 @@
width: $cols8; width: $cols8;
> ul > li { > ul > li {
&.login-item, &.login-item {
&.account-nav {
margin-left: 0; margin-left: 0;
} }
} }

View file

@ -8,6 +8,7 @@ const connect = require('react-redux').connect;
const injectIntl = require('react-intl').injectIntl; const injectIntl = require('react-intl').injectIntl;
const parser = require('scratch-parser'); const parser = require('scratch-parser');
const Page = require('../../components/page/www/page.jsx'); const Page = require('../../components/page/www/page.jsx');
const api = require('../../lib/api');
const render = require('../../lib/render.jsx'); const render = require('../../lib/render.jsx');
const storage = require('../../lib/storage.js').default; const storage = require('../../lib/storage.js').default;
const log = require('../../lib/log'); const log = require('../../lib/log');
@ -30,6 +31,8 @@ class Preview extends React.Component {
'handleToggleStudio', 'handleToggleStudio',
'handleFavoriteToggle', 'handleFavoriteToggle',
'handleLoadMore', 'handleLoadMore',
// temporary, to pass to GUI. Remove when nav bar components are shared between www and gui.
'handleLogout',
'handleLoveToggle', 'handleLoveToggle',
'handlePermissions', 'handlePermissions',
'handlePopState', 'handlePopState',
@ -141,6 +144,21 @@ class Preview extends React.Component {
}); });
}); });
} }
// Temporarily duplicated this function from navigation.jsx here.
// Should move handling of login/logout into session.js, and handle them
// from here as well as navigation.jsx.
handleLogout (e) {
e.preventDefault();
api({
host: '',
method: 'post',
uri: '/accounts/logout/',
useCsrf: true
}, err => {
if (err) log.error(err);
window.location = '/';
});
}
handleReportClick () { handleReportClick () {
this.setState({reportOpen: true}); this.setState({reportOpen: true});
} }
@ -315,7 +333,6 @@ class Preview extends React.Component {
replies={this.props.replies} replies={this.props.replies}
reportOpen={this.state.reportOpen} reportOpen={this.state.reportOpen}
studios={this.props.studios} studios={this.props.studios}
user={this.props.user}
userOwnsProject={this.userOwnsProject()} userOwnsProject={this.userOwnsProject()}
onAddToStudioClicked={this.handleAddToStudioClick} onAddToStudioClicked={this.handleAddToStudioClick}
onAddToStudioClosed={this.handleAddToStudioClose} onAddToStudioClosed={this.handleAddToStudioClose}
@ -339,6 +356,7 @@ class Preview extends React.Component {
className="gui" className="gui"
projectHost={this.props.projectHost} projectHost={this.props.projectHost}
projectId={this.state.projectId} projectId={this.state.projectId}
onClickLogout={this.handleLogout}
/> />
); );
} }