mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 07:38:07 -05:00
move login/registration functions and view state to session reducer, pass to gui (#2078)
* move login/registration functions and view state to session reducer, pass to gui * navigation reducer handles login; gui passed renderLogin function * put back in join class to make smoke tests keep working
This commit is contained in:
parent
c047612d68
commit
935eb0b15f
18 changed files with 549 additions and 349 deletions
|
@ -33,8 +33,8 @@
|
|||
}
|
||||
|
||||
input {
|
||||
// 100% minus border and padding
|
||||
margin-bottom: 12px;
|
||||
// 100% minus border and padding
|
||||
width: calc(100% - 30px);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ const connect = require('react-redux').connect;
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const sessionActions = require('../../redux/session.js');
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
|
||||
const IframeModal = require('../modal/iframe/modal.jsx');
|
||||
const Registration = require('../registration/registration.jsx');
|
||||
|
@ -15,10 +15,7 @@ class Intro extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'handleShowVideo',
|
||||
'handleCloseVideo',
|
||||
'handleJoinClick',
|
||||
'handleCloseRegistration',
|
||||
'handleCompleteRegistration'
|
||||
'handleCloseVideo'
|
||||
]);
|
||||
this.state = {
|
||||
videoOpen: false
|
||||
|
@ -30,17 +27,6 @@ class Intro extends React.Component {
|
|||
handleCloseVideo () {
|
||||
this.setState({videoOpen: false});
|
||||
}
|
||||
handleJoinClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({registrationOpen: true});
|
||||
}
|
||||
handleCloseRegistration () {
|
||||
this.setState({registrationOpen: false});
|
||||
}
|
||||
handleCompleteRegistration () {
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
this.closeRegistration();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div className="intro">
|
||||
|
@ -92,7 +78,7 @@ class Intro extends React.Component {
|
|||
<a
|
||||
className="sprite sprite-3"
|
||||
href="#"
|
||||
onClick={this.handleJoinClick}
|
||||
onClick={this.props.handleOpenRegistration}
|
||||
>
|
||||
<img
|
||||
alt="Gobo"
|
||||
|
@ -111,10 +97,7 @@ class Intro extends React.Component {
|
|||
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
|
||||
</a>
|
||||
<Registration
|
||||
isOpen={this.state.registrationOpen}
|
||||
key="registration"
|
||||
onRegistrationDone={this.handleCompleteRegistration}
|
||||
onRequestClose={this.handleCloseRegistration}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -160,7 +143,7 @@ class Intro extends React.Component {
|
|||
}
|
||||
|
||||
Intro.propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
handleOpenRegistration: PropTypes.func,
|
||||
messages: PropTypes.shape({
|
||||
'intro.aboutScratch': PropTypes.string,
|
||||
'intro.forEducators': PropTypes.string,
|
||||
|
@ -194,6 +177,17 @@ const mapStateToProps = state => ({
|
|||
session: state.session
|
||||
});
|
||||
|
||||
const ConnectedIntro = connect(mapStateToProps)(Intro);
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.handleOpenRegistration());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const ConnectedIntro = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Intro);
|
||||
|
||||
module.exports = ConnectedIntro;
|
||||
|
|
60
src/components/login/canceled-deletion-modal.jsx
Normal file
60
src/components/login/canceled-deletion-modal.jsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
const React = require('react');
|
||||
const connect = require('react-redux').connect;
|
||||
const FormattedMessage = require('react-intl').FormattedMessage;
|
||||
const PropTypes = require('prop-types');
|
||||
const injectIntl = require('react-intl').injectIntl;
|
||||
const intlShape = require('react-intl').intlShape;
|
||||
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
const Modal = require('../modal/base/modal.jsx');
|
||||
|
||||
const CanceledDeletionModal = ({
|
||||
canceledDeletionOpen,
|
||||
handleCloseCanceledDeletion,
|
||||
intl
|
||||
}) => (
|
||||
<Modal
|
||||
isOpen={canceledDeletionOpen}
|
||||
style={{
|
||||
content: {
|
||||
padding: 15
|
||||
}
|
||||
}}
|
||||
onRequestClose={handleCloseCanceledDeletion}
|
||||
>
|
||||
<h4><FormattedMessage id="general.noDeletionTitle" /></h4>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="general.noDeletionDescription"
|
||||
values={{
|
||||
resetLink: <a href="/accounts/password_reset/">
|
||||
{intl.formatMessage({id: 'general.noDeletionLink'})}
|
||||
</a>
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
CanceledDeletionModal.propTypes = {
|
||||
canceledDeletionOpen: PropTypes.bool,
|
||||
handleCloseCanceledDeletion: PropTypes.func,
|
||||
intl: intlShape
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
canceledDeletionOpen: state.navigation && state.navigation.canceledDeletionOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleCloseCanceledDeletion: () => {
|
||||
dispatch(navigationActions.setCanceledDeletionOpen(false));
|
||||
}
|
||||
});
|
||||
|
||||
const ConnectedCanceledDeletionModal = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CanceledDeletionModal);
|
||||
|
||||
module.exports = injectIntl(ConnectedCanceledDeletionModal);
|
34
src/components/login/connected-login.jsx
Normal file
34
src/components/login/connected-login.jsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const connect = require('react-redux').connect;
|
||||
|
||||
const Login = require('./login.jsx');
|
||||
|
||||
require('./login-dropdown.scss');
|
||||
|
||||
const ConnectedLogin = ({
|
||||
error,
|
||||
onLogIn
|
||||
}) => (
|
||||
<Login
|
||||
error={error}
|
||||
key="login-dropdown-presentation"
|
||||
onLogIn={onLogIn}
|
||||
/>
|
||||
);
|
||||
|
||||
ConnectedLogin.propTypes = {
|
||||
error: PropTypes.string,
|
||||
onLogIn: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
error: state.navigation && state.navigation.loginError
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ConnectedLogin);
|
50
src/components/login/login-dropdown.jsx
Normal file
50
src/components/login/login-dropdown.jsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const connect = require('react-redux').connect;
|
||||
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
const Dropdown = require('../dropdown/dropdown.jsx');
|
||||
const ConnectedLogin = require('./connected-login.jsx');
|
||||
|
||||
require('./login-dropdown.scss');
|
||||
|
||||
const LoginDropdown = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onLogIn
|
||||
}) => (
|
||||
<Dropdown
|
||||
className={'with-arrow'}
|
||||
isOpen={isOpen}
|
||||
key="login-dropdown"
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<ConnectedLogin
|
||||
onLogIn={onLogIn}
|
||||
/>
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
LoginDropdown.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
onClose: PropTypes.func,
|
||||
onLogIn: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isOpen: state.navigation && state.navigation.loginOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onClose: () => {
|
||||
dispatch(navigationActions.setLoginOpen(false));
|
||||
},
|
||||
onLogIn: (formData, callback) => {
|
||||
dispatch(navigationActions.handleLogIn(formData, callback));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(LoginDropdown);
|
0
src/components/login/login-dropdown.scss
Normal file
0
src/components/login/login-dropdown.scss
Normal file
|
@ -3,8 +3,6 @@ const FormattedMessage = require('react-intl').FormattedMessage;
|
|||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
|
||||
const log = require('../../lib/log.js');
|
||||
|
||||
const Form = require('../forms/form.jsx');
|
||||
const Input = require('../forms/input.jsx');
|
||||
const Button = require('../forms/button.jsx');
|
||||
|
@ -24,8 +22,7 @@ class Login extends React.Component {
|
|||
}
|
||||
handleSubmit (formData) {
|
||||
this.setState({waiting: true});
|
||||
this.props.onLogIn(formData, err => {
|
||||
if (err) log.error(err);
|
||||
this.props.onLogIn(formData, () => {
|
||||
this.setState({waiting: false});
|
||||
});
|
||||
}
|
||||
|
@ -48,9 +45,6 @@ class Login extends React.Component {
|
|||
key="usernameInput"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
ref={input => {
|
||||
this.username = input;
|
||||
}}
|
||||
type="text"
|
||||
/>
|
||||
<label
|
||||
|
@ -63,9 +57,6 @@ class Login extends React.Component {
|
|||
required
|
||||
key="passwordInput"
|
||||
name="password"
|
||||
ref={input => {
|
||||
this.password = input;
|
||||
}}
|
||||
type="password"
|
||||
/>
|
||||
{this.state.waiting ? [
|
||||
|
|
|
@ -2,6 +2,26 @@
|
|||
|
||||
.login {
|
||||
padding: 10px;
|
||||
width: 200px;
|
||||
line-height: 1.5rem;
|
||||
white-space: normal; // override any parent, such as in gui, who sets nowrap
|
||||
color: $type-white;
|
||||
font-size: .8125rem;
|
||||
|
||||
.button {
|
||||
padding: .75em;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.input {
|
||||
margin-bottom: 12px;
|
||||
// 100% minus border and padding
|
||||
width: calc(100% - 30px);
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
label {
|
||||
padding-top: 5px;
|
||||
|
@ -24,13 +44,19 @@
|
|||
|
||||
a {
|
||||
margin-top: 15px;
|
||||
color: $ui-white;
|
||||
|
||||
&:link,
|
||||
&:visited,
|
||||
&:active {
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.error {
|
||||
border: 1px solid $active-dark-gray;
|
||||
border-radius: 5px;
|
||||
|
|
|
@ -7,7 +7,7 @@ const ReactModal = require('react-modal');
|
|||
|
||||
require('./modal.scss');
|
||||
|
||||
ReactModal.setAppElement(document.getElementById('view'));
|
||||
ReactModal.setAppElement(document.getElementById('app'));
|
||||
|
||||
/**
|
||||
* Container for pop up windows (See: registration window)
|
||||
|
@ -25,7 +25,7 @@ class Modal extends React.Component {
|
|||
render () {
|
||||
return (
|
||||
<ReactModal
|
||||
appElement={document.getElementById('view')}
|
||||
appElement={document.getElementById('app')}
|
||||
className={{
|
||||
base: classNames('modal-content', this.props.className),
|
||||
afterOpen: classNames('modal-content', this.props.className),
|
||||
|
|
|
@ -23,10 +23,11 @@ const AccountNav = ({
|
|||
}) => (
|
||||
<div className="account-nav">
|
||||
<a
|
||||
className={classNames({
|
||||
'user-info': true,
|
||||
'open': isOpen
|
||||
})}
|
||||
className={classNames([
|
||||
'ignore-react-onclickoutside',
|
||||
'user-info',
|
||||
{open: isOpen}
|
||||
])}
|
||||
href="#"
|
||||
onClick={onClick}
|
||||
>
|
||||
|
|
|
@ -8,16 +8,14 @@ const PropTypes = require('prop-types');
|
|||
const React = require('react');
|
||||
|
||||
const messageCountActions = require('../../../redux/message-count.js');
|
||||
const navigationActions = require('../../../redux/navigation.js');
|
||||
const sessionActions = require('../../../redux/session.js');
|
||||
|
||||
const api = require('../../../lib/api');
|
||||
const Button = require('../../forms/button.jsx');
|
||||
const Dropdown = require('../../dropdown/dropdown.jsx');
|
||||
const Form = require('../../forms/form.jsx');
|
||||
const Input = require('../../forms/input.jsx');
|
||||
const log = require('../../../lib/log.js');
|
||||
const Login = require('../../login/login.jsx');
|
||||
const Modal = require('../../modal/base/modal.jsx');
|
||||
const LoginDropdown = require('../../login/login-dropdown.jsx');
|
||||
const CanceledDeletionModal = require('../../login/canceled-deletion-modal.jsx');
|
||||
const NavigationBox = require('../base/navigation.jsx');
|
||||
const Registration = require('../../registration/registration.jsx');
|
||||
const AccountNav = require('./accountnav.jsx');
|
||||
|
@ -29,32 +27,16 @@ class Navigation extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'getProfileUrl',
|
||||
'handleJoinClick',
|
||||
'handleLoginClick',
|
||||
'handleCloseLogin',
|
||||
'handleLogIn',
|
||||
'handleLogOut',
|
||||
'handleAccountNavClick',
|
||||
'handleCloseAccountNav',
|
||||
'showCanceledDeletion',
|
||||
'handleCloseCanceledDeletion',
|
||||
'handleCloseRegistration',
|
||||
'handleCompleteRegistration',
|
||||
'handleSearchSubmit'
|
||||
]);
|
||||
this.state = {
|
||||
accountNavOpen: false,
|
||||
canceledDeletionOpen: false,
|
||||
loginOpen: false,
|
||||
loginError: null,
|
||||
registrationOpen: false,
|
||||
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.props.session.session.user) {
|
||||
if (this.props.user) {
|
||||
const intervalId = setInterval(() => {
|
||||
this.props.getMessageCount(this.props.session.session.user.username);
|
||||
this.props.getMessageCount(this.props.user.username);
|
||||
}, 120000); // check for new messages every 2 mins.
|
||||
this.setState({ // eslint-disable-line react/no-did-mount-set-state
|
||||
messageCountIntervalId: intervalId
|
||||
|
@ -62,14 +44,11 @@ class Navigation extends React.Component {
|
|||
}
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.session.session.user !== this.props.session.session.user) {
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
loginOpen: false,
|
||||
accountNavOpen: false
|
||||
});
|
||||
if (this.props.session.session.user) {
|
||||
if (prevProps.user !== this.props.user) {
|
||||
this.props.closeAccountMenus();
|
||||
if (this.props.user) {
|
||||
const intervalId = setInterval(() => {
|
||||
this.props.getMessageCount(this.props.session.session.user.username);
|
||||
this.props.getMessageCount(this.props.user.username);
|
||||
}, 120000); // check for new messages every 2 mins.
|
||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||
messageCountIntervalId: intervalId
|
||||
|
@ -88,104 +67,25 @@ class Navigation extends React.Component {
|
|||
// clear message interval if it exists
|
||||
if (this.state.messageCountIntervalId !== -1) {
|
||||
clearInterval(this.state.messageCountIntervalId);
|
||||
this.props.dispatch(messageCountActions.setCount(0));
|
||||
this.props.setMessageCount(0);
|
||||
this.setState({
|
||||
messageCountIntervalId: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
getProfileUrl () {
|
||||
if (!this.props.session.session.user) return;
|
||||
return `/users/${this.props.session.session.user.username}/`;
|
||||
}
|
||||
handleJoinClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({registrationOpen: true});
|
||||
}
|
||||
handleLoginClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({loginOpen: !this.state.loginOpen});
|
||||
}
|
||||
handleCloseLogin () {
|
||||
this.setState({loginOpen: false});
|
||||
}
|
||||
// NOTE: TODO: continue here. Should move these two functions up to a redux level,
|
||||
// maybe into session...
|
||||
handleLogIn (formData, callback) {
|
||||
this.setState({loginError: null});
|
||||
formData.useMessages = true;
|
||||
api({
|
||||
method: 'post',
|
||||
host: '',
|
||||
uri: '/accounts/login/',
|
||||
json: formData,
|
||||
useCsrf: true
|
||||
}, (err, body) => {
|
||||
if (err) this.setState({loginError: err.message});
|
||||
if (body) {
|
||||
body = body[0];
|
||||
if (body.success) {
|
||||
this.handleCloseLogin();
|
||||
body.messages.map(message => { // eslint-disable-line array-callback-return
|
||||
if (message.message === 'canceled-deletion') {
|
||||
this.showCanceledDeletion();
|
||||
}
|
||||
});
|
||||
this.props.refreshSession();
|
||||
} else {
|
||||
if (body.redirect) {
|
||||
window.location = body.redirect;
|
||||
}
|
||||
// Update login error message to a friendlier one if it exists
|
||||
this.setState({loginError: body.msg});
|
||||
}
|
||||
}
|
||||
// JS error already logged by api mixin
|
||||
callback();
|
||||
});
|
||||
}
|
||||
handleLogOut (e) {
|
||||
e.preventDefault();
|
||||
api({
|
||||
host: '',
|
||||
method: 'post',
|
||||
uri: '/accounts/logout/',
|
||||
useCsrf: true
|
||||
}, err => {
|
||||
if (err) log.error(err);
|
||||
this.handleCloseLogin();
|
||||
window.location = '/';
|
||||
});
|
||||
}
|
||||
handleAccountNavClick (e) {
|
||||
e.preventDefault();
|
||||
this.setState({accountNavOpen: true});
|
||||
}
|
||||
handleCloseAccountNav () {
|
||||
this.setState({accountNavOpen: false});
|
||||
}
|
||||
showCanceledDeletion () {
|
||||
this.setState({canceledDeletionOpen: true});
|
||||
}
|
||||
handleCloseCanceledDeletion () {
|
||||
this.setState({canceledDeletionOpen: false});
|
||||
}
|
||||
handleCloseRegistration () {
|
||||
this.setState({registrationOpen: false});
|
||||
}
|
||||
handleCompleteRegistration () {
|
||||
this.props.dispatch(sessionActions.refreshSession());
|
||||
this.handleCloseRegistration();
|
||||
if (!this.props.user) return;
|
||||
return `/users/${this.props.user.username}/`;
|
||||
}
|
||||
handleSearchSubmit (formData) {
|
||||
window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
|
||||
}
|
||||
render () {
|
||||
const createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
|
||||
const createLink = this.props.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
|
||||
return (
|
||||
<NavigationBox
|
||||
className={classNames({
|
||||
'logged-in': this.props.session.session.user
|
||||
'logged-in': this.props.user
|
||||
})}
|
||||
>
|
||||
<ul>
|
||||
|
@ -233,7 +133,7 @@ class Navigation extends React.Component {
|
|||
</Form>
|
||||
</li>
|
||||
{this.props.session.status === sessionActions.Status.FETCHED ? (
|
||||
this.props.session.session.user ? [
|
||||
this.props.user ? [
|
||||
<li
|
||||
className="link right messages"
|
||||
key="messages"
|
||||
|
@ -267,16 +167,16 @@ class Navigation extends React.Component {
|
|||
key="account-nav"
|
||||
>
|
||||
<AccountNav
|
||||
classroomId={this.props.session.session.user.classroomId}
|
||||
classroomId={this.props.user.classroomId}
|
||||
isEducator={this.props.permissions.educator}
|
||||
isOpen={this.state.accountNavOpen}
|
||||
isOpen={this.props.accountNavOpen}
|
||||
isStudent={this.props.permissions.student}
|
||||
profileUrl={this.getProfileUrl()}
|
||||
thumbnailUrl={this.props.session.session.user.thumbnailUrl}
|
||||
username={this.props.session.session.user.username}
|
||||
onClick={this.handleAccountNavClick}
|
||||
onClickLogout={this.handleLogOut}
|
||||
onClose={this.handleCloseAccountNav}
|
||||
thumbnailUrl={this.props.user.thumbnailUrl}
|
||||
username={this.props.user.username}
|
||||
onClick={this.props.handleToggleAccountNav}
|
||||
onClickLogout={this.props.handleLogOut}
|
||||
onClose={this.props.handleCloseAccountNav}
|
||||
/>
|
||||
</li>
|
||||
] : [
|
||||
|
@ -286,16 +186,13 @@ class Navigation extends React.Component {
|
|||
>
|
||||
<a
|
||||
href="#"
|
||||
onClick={this.handleJoinClick}
|
||||
onClick={this.props.handleOpenRegistration}
|
||||
>
|
||||
<FormattedMessage id="general.joinScratch" />
|
||||
</a>
|
||||
</li>,
|
||||
<Registration
|
||||
isOpen={this.state.registrationOpen}
|
||||
key="registration"
|
||||
onRegistrationDone={this.handleCompleteRegistration}
|
||||
onRequestClose={this.handleCloseRegistration}
|
||||
/>,
|
||||
<li
|
||||
className="link right login-item"
|
||||
|
@ -305,54 +202,31 @@ class Navigation extends React.Component {
|
|||
className="ignore-react-onclickoutside"
|
||||
href="#"
|
||||
key="login-link"
|
||||
onClick={this.handleLoginClick}
|
||||
onClick={this.props.handleToggleLoginOpen}
|
||||
>
|
||||
<FormattedMessage id="general.signIn" />
|
||||
</a>
|
||||
<Dropdown
|
||||
className="login-dropdown with-arrow"
|
||||
isOpen={this.state.loginOpen}
|
||||
<LoginDropdown
|
||||
key="login-dropdown"
|
||||
onRequestClose={this.handleCloseLogin}
|
||||
>
|
||||
<Login
|
||||
error={this.state.loginError}
|
||||
onLogIn={this.handleLogIn}
|
||||
/>
|
||||
</Dropdown>
|
||||
</li>
|
||||
]) : []}
|
||||
</ul>
|
||||
<Modal
|
||||
isOpen={this.state.canceledDeletionOpen}
|
||||
style={{
|
||||
content: {
|
||||
padding: 15
|
||||
}
|
||||
}}
|
||||
onRequestClose={this.handleCloseCanceledDeletion}
|
||||
>
|
||||
<h4>Your Account Will Not Be Deleted</h4>
|
||||
<h4><FormattedMessage id="general.noDeletionTitle" /></h4>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="general.noDeletionDescription"
|
||||
values={{
|
||||
resetLink: <a href="/accounts/password_reset/">
|
||||
{this.props.intl.formatMessage({id: 'general.noDeletionLink'})}
|
||||
</a>
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</Modal>
|
||||
<CanceledDeletionModal />
|
||||
</NavigationBox>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Navigation.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
accountNavOpen: PropTypes.bool,
|
||||
closeAccountMenus: PropTypes.func,
|
||||
getMessageCount: PropTypes.func,
|
||||
handleCloseAccountNav: PropTypes.func,
|
||||
handleLogOut: PropTypes.func,
|
||||
handleOpenRegistration: PropTypes.func,
|
||||
handleToggleAccountNav: PropTypes.func,
|
||||
handleToggleLoginOpen: PropTypes.func,
|
||||
intl: intlShape,
|
||||
permissions: PropTypes.shape({
|
||||
admin: PropTypes.bool,
|
||||
|
@ -361,20 +235,17 @@ Navigation.propTypes = {
|
|||
educator_invitee: PropTypes.bool,
|
||||
student: PropTypes.bool
|
||||
}),
|
||||
refreshSession: PropTypes.func,
|
||||
searchTerm: PropTypes.string,
|
||||
session: PropTypes.shape({
|
||||
session: PropTypes.shape({
|
||||
status: PropTypes.string
|
||||
}),
|
||||
setMessageCount: PropTypes.func,
|
||||
unreadMessageCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
user: PropTypes.shape({
|
||||
classroomId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
thumbnailUrl: PropTypes.string,
|
||||
username: PropTypes.string
|
||||
})
|
||||
}),
|
||||
status: PropTypes.string
|
||||
}),
|
||||
setMessageCount: PropTypes.func,
|
||||
unreadMessageCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
};
|
||||
|
||||
Navigation.defaultProps = {
|
||||
|
@ -384,18 +255,39 @@ Navigation.defaultProps = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
accountNavOpen: state.navigation && state.navigation.accountNavOpen,
|
||||
session: state.session,
|
||||
permissions: state.permissions,
|
||||
searchTerm: state.navigation.searchTerm,
|
||||
unreadMessageCount: state.messageCount.messageCount,
|
||||
searchTerm: state.navigation
|
||||
user: state.session && state.session.session && state.session.session.user
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
closeAccountMenus: () => {
|
||||
dispatch(navigationActions.closeAccountMenus());
|
||||
},
|
||||
getMessageCount: username => {
|
||||
dispatch(messageCountActions.getCount(username));
|
||||
},
|
||||
refreshSession: () => {
|
||||
dispatch(sessionActions.refreshSession());
|
||||
handleToggleAccountNav: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.handleToggleAccountNav());
|
||||
},
|
||||
handleCloseAccountNav: () => {
|
||||
dispatch(navigationActions.setAccountNavOpen(false));
|
||||
},
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.setRegistrationOpen(true));
|
||||
},
|
||||
handleLogOut: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.handleLogOut());
|
||||
},
|
||||
handleToggleLoginOpen: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.toggleLoginOpen());
|
||||
},
|
||||
setMessageCount: newCount => {
|
||||
dispatch(messageCountActions.setCount(newCount));
|
||||
|
|
|
@ -163,25 +163,6 @@
|
|||
background-image: url("/images/mystuff.png");
|
||||
}
|
||||
}
|
||||
|
||||
.login-dropdown {
|
||||
width: 200px;
|
||||
|
||||
.button {
|
||||
padding: .75em;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
.row {
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
height: 2.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//4 columns
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
const bindAll = require('lodash.bindall');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('react');
|
||||
const connect = require('react-redux').connect;
|
||||
|
||||
const IframeModal = require('../modal/iframe/modal.jsx');
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
|
||||
require('./registration.scss');
|
||||
|
||||
|
@ -26,7 +28,7 @@ class Registration extends React.Component {
|
|||
handleMessage (e) {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
if (e.source !== this.registrationIframe.contentWindow) return;
|
||||
if (e.data === 'registration-done') this.props.onRegistrationDone();
|
||||
if (e.data === 'registration-done') this.props.handleCompleteRegistration();
|
||||
if (e.data === 'registration-relaunch') {
|
||||
this.registrationIframe.contentWindow.location.reload();
|
||||
}
|
||||
|
@ -47,16 +49,32 @@ class Registration extends React.Component {
|
|||
}}
|
||||
isOpen={this.props.isOpen}
|
||||
src="/accounts/standalone-registration/"
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
onRequestClose={this.props.handleCloseRegistration}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Registration.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
onRegistrationDone: PropTypes.func,
|
||||
onRequestClose: PropTypes.func
|
||||
handleCloseRegistration: PropTypes.func,
|
||||
handleCompleteRegistration: PropTypes.func,
|
||||
isOpen: PropTypes.bool
|
||||
};
|
||||
|
||||
module.exports = Registration;
|
||||
const mapStateToProps = state => ({
|
||||
isOpen: state.navigation.registrationOpen
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleCloseRegistration: () => {
|
||||
dispatch(navigationActions.setRegistrationOpen(false));
|
||||
},
|
||||
handleCompleteRegistration: () => {
|
||||
dispatch(navigationActions.handleCompleteRegistration());
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Registration);
|
||||
|
|
|
@ -1,22 +1,153 @@
|
|||
const keyMirror = require('keymirror');
|
||||
const defaults = require('lodash.defaults');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log.js');
|
||||
const sessionActions = require('./session.js');
|
||||
|
||||
const Types = keyMirror({
|
||||
SET_SEARCH_TERM: null
|
||||
SET_SEARCH_TERM: null,
|
||||
SET_ACCOUNT_NAV_OPEN: null,
|
||||
TOGGLE_ACCOUNT_NAV_OPEN: null,
|
||||
SET_LOGIN_ERROR: null,
|
||||
SET_LOGIN_OPEN: null,
|
||||
TOGGLE_LOGIN_OPEN: null,
|
||||
SET_CANCELED_DELETION_OPEN: null,
|
||||
SET_REGISTRATION_OPEN: null
|
||||
});
|
||||
|
||||
module.exports.getInitialState = () => ({
|
||||
accountNavOpen: false,
|
||||
canceledDeletionOpen: false,
|
||||
loginError: null,
|
||||
loginOpen: false,
|
||||
registrationOpen: false,
|
||||
searchTerm: ''
|
||||
});
|
||||
|
||||
|
||||
module.exports.navigationReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = '';
|
||||
state = module.exports.getInitialState();
|
||||
}
|
||||
switch (action.type) {
|
||||
case Types.SET_SEARCH_TERM:
|
||||
return action.searchTerm;
|
||||
return defaults({searchTerm: action.searchTerm}, state);
|
||||
case Types.SET_ACCOUNT_NAV_OPEN:
|
||||
return defaults({accountNavOpen: action.isOpen}, state);
|
||||
case Types.TOGGLE_ACCOUNT_NAV_OPEN:
|
||||
return defaults({accountNavOpen: !state.accountNavOpen}, state);
|
||||
case Types.SET_LOGIN_ERROR:
|
||||
return defaults({loginError: action.loginError}, state);
|
||||
case Types.SET_LOGIN_OPEN:
|
||||
return defaults({loginOpen: action.isOpen}, state);
|
||||
case Types.TOGGLE_LOGIN_OPEN:
|
||||
return defaults({loginOpen: !state.loginOpen}, state);
|
||||
case Types.SET_CANCELED_DELETION_OPEN:
|
||||
return defaults({canceledDeletionOpen: action.isOpen}, state);
|
||||
case Types.SET_REGISTRATION_OPEN:
|
||||
return defaults({registrationOpen: action.isOpen}, state);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.setAccountNavOpen = isOpen => ({
|
||||
type: Types.SET_ACCOUNT_NAV_OPEN,
|
||||
isOpen: isOpen
|
||||
});
|
||||
|
||||
module.exports.handleToggleAccountNav = () => ({
|
||||
type: Types.TOGGLE_ACCOUNT_NAV_OPEN
|
||||
});
|
||||
|
||||
module.exports.setCanceledDeletionOpen = isOpen => ({
|
||||
type: Types.SET_CANCELED_DELETION_OPEN,
|
||||
isOpen: isOpen
|
||||
});
|
||||
|
||||
module.exports.setLoginError = loginError => ({
|
||||
type: Types.SET_LOGIN_ERROR,
|
||||
loginError: loginError
|
||||
});
|
||||
|
||||
module.exports.setLoginOpen = isOpen => ({
|
||||
type: Types.SET_LOGIN_OPEN,
|
||||
isOpen: isOpen
|
||||
});
|
||||
|
||||
module.exports.toggleLoginOpen = () => ({
|
||||
type: Types.TOGGLE_LOGIN_OPEN
|
||||
});
|
||||
|
||||
module.exports.setRegistrationOpen = isOpen => ({
|
||||
type: Types.SET_REGISTRATION_OPEN,
|
||||
isOpen: isOpen
|
||||
});
|
||||
|
||||
module.exports.setSearchTerm = searchTerm => ({
|
||||
type: Types.SET_SEARCH_TERM,
|
||||
searchTerm: searchTerm
|
||||
});
|
||||
|
||||
module.exports.handleCompleteRegistration = () => (dispatch => {
|
||||
dispatch(sessionActions.refreshSession());
|
||||
dispatch(module.exports.setRegistrationOpen(false));
|
||||
});
|
||||
|
||||
module.exports.closeAccountMenus = () => (dispatch => {
|
||||
dispatch(module.exports.setAccountNavOpen(false));
|
||||
dispatch(module.exports.setRegistrationOpen(false));
|
||||
});
|
||||
|
||||
module.exports.handleLogIn = (formData, callback) => (dispatch => {
|
||||
dispatch(module.exports.setLoginError(null));
|
||||
formData.useMessages = true; // NOTE: this may or may not be being used anywhere else
|
||||
api({
|
||||
method: 'post',
|
||||
host: '',
|
||||
uri: '/accounts/login/',
|
||||
json: formData,
|
||||
useCsrf: true
|
||||
}, (err, body) => {
|
||||
if (err) dispatch(module.exports.setLoginError(err.message));
|
||||
if (body) {
|
||||
body = body[0];
|
||||
if (body.success) {
|
||||
dispatch(module.exports.setLoginOpen(false));
|
||||
body.messages.forEach(message => {
|
||||
if (message.message === 'canceled-deletion') {
|
||||
dispatch(module.exports.setCanceledDeletionOpen(true));
|
||||
}
|
||||
});
|
||||
dispatch(sessionActions.refreshSession());
|
||||
callback({success: true});
|
||||
} else {
|
||||
if (body.redirect) {
|
||||
window.location = body.redirect;
|
||||
}
|
||||
// Update login error message to a friendlier one if it exists
|
||||
dispatch(module.exports.setLoginError(body.msg));
|
||||
// JS error already logged by api mixin
|
||||
callback({success: false});
|
||||
}
|
||||
} else {
|
||||
// JS error already logged by api mixin
|
||||
callback({success: false});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.handleLogOut = () => (dispatch => {
|
||||
api({
|
||||
host: '',
|
||||
method: 'post',
|
||||
uri: '/accounts/logout/',
|
||||
useCsrf: true
|
||||
}, err => {
|
||||
if (err) log.error(err);
|
||||
dispatch(module.exports.setLoginOpen(false));
|
||||
dispatch(module.exports.setAccountNavOpen(false));
|
||||
window.location = '/';
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ const defaults = require('lodash.defaults');
|
|||
const messageCountReducer = require('./message-count.js').messageCountReducer;
|
||||
const permissionsReducer = require('./permissions.js').permissionsReducer;
|
||||
const sessionReducer = require('./session.js').sessionReducer;
|
||||
const navigationReducer = require('./navigation.js').navigationReducer;
|
||||
|
||||
/**
|
||||
* Returns a combined reducer to be used for a page in `render.jsx`.
|
||||
|
@ -18,8 +19,9 @@ const sessionReducer = require('./session.js').sessionReducer;
|
|||
module.exports = opts => {
|
||||
opts = opts || {};
|
||||
return combineReducers(defaults(opts, {
|
||||
session: sessionReducer,
|
||||
messageCount: messageCountReducer,
|
||||
navigation: navigationReducer,
|
||||
permissions: permissionsReducer,
|
||||
messageCount: messageCountReducer
|
||||
session: sessionReducer
|
||||
}));
|
||||
};
|
||||
|
|
|
@ -8,7 +8,6 @@ const connect = require('react-redux').connect;
|
|||
const injectIntl = require('react-intl').injectIntl;
|
||||
const parser = require('scratch-parser');
|
||||
const Page = require('../../components/page/www/page.jsx');
|
||||
const api = require('../../lib/api');
|
||||
const render = require('../../lib/render.jsx');
|
||||
const storage = require('../../lib/storage.js').default;
|
||||
const log = require('../../lib/log');
|
||||
|
@ -16,8 +15,12 @@ const EXTENSION_INFO = require('../../lib/extensions.js').default;
|
|||
|
||||
const PreviewPresentation = require('./presentation.jsx');
|
||||
const projectShape = require('./projectshape.jsx').projectShape;
|
||||
const Registration = require('../../components/registration/registration.jsx');
|
||||
const ConnectedLogin = require('../../components/login/connected-login.jsx');
|
||||
const CanceledDeletionModal = require('../../components/login/canceled-deletion-modal.jsx');
|
||||
|
||||
const sessionActions = require('../../redux/session.js');
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
const previewActions = require('../../redux/preview.js');
|
||||
|
||||
const GUI = require('scratch-gui');
|
||||
|
@ -31,10 +34,7 @@ class Preview extends React.Component {
|
|||
'handleToggleStudio',
|
||||
'handleFavoriteToggle',
|
||||
'handleLoadMore',
|
||||
// temporary, to pass to GUI. Remove when nav bar components are shared between www and gui.
|
||||
'handleLogout',
|
||||
'handleLoveToggle',
|
||||
'handlePermissions',
|
||||
'handlePopState',
|
||||
'handleReportClick',
|
||||
'handleReportClose',
|
||||
|
@ -45,9 +45,8 @@ class Preview extends React.Component {
|
|||
'handleUpdateProjectTitle',
|
||||
'handleUpdate',
|
||||
'initCounts',
|
||||
'isShared',
|
||||
'pushHistory',
|
||||
'userOwnsProject'
|
||||
'renderLogin'
|
||||
]);
|
||||
const pathname = window.location.pathname.toLowerCase();
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
|
@ -55,7 +54,6 @@ class Preview extends React.Component {
|
|||
// parts[1]: either :id or 'editor'
|
||||
// parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen'
|
||||
this.state = {
|
||||
editable: false,
|
||||
extensions: [],
|
||||
favoriteCount: 0,
|
||||
loveCount: 0,
|
||||
|
@ -90,7 +88,6 @@ class Preview extends React.Component {
|
|||
if (this.props.projectInfo.id !== prevProps.projectInfo.id) {
|
||||
this.getExtensions(this.state.projectId);
|
||||
this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves);
|
||||
this.handlePermissions();
|
||||
if (this.props.projectInfo.remix.parent !== null) {
|
||||
this.props.getParentInfo(this.props.projectInfo.remix.parent);
|
||||
}
|
||||
|
@ -145,21 +142,6 @@ 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 () {
|
||||
this.setState({reportOpen: true});
|
||||
}
|
||||
|
@ -261,12 +243,6 @@ class Preview extends React.Component {
|
|||
}));
|
||||
}
|
||||
}
|
||||
handlePermissions () {
|
||||
// TODO: handle admins and mods
|
||||
if (this.props.projectInfo.author.username === this.props.user.username) {
|
||||
this.setState({editable: true});
|
||||
}
|
||||
}
|
||||
handleSeeInside () {
|
||||
this.props.setPlayer(false);
|
||||
}
|
||||
|
@ -289,26 +265,21 @@ class Preview extends React.Component {
|
|||
loveCount: loves
|
||||
});
|
||||
}
|
||||
isShared () {
|
||||
renderLogin ({onClose}) {
|
||||
return (
|
||||
// if we don't have projectInfo assume shared until we know otherwise
|
||||
Object.keys(this.props.projectInfo).length === 0 || (
|
||||
this.props.projectInfo.history &&
|
||||
this.props.projectInfo.history.shared.length > 0
|
||||
)
|
||||
);
|
||||
<ConnectedLogin
|
||||
key="login-dropdown-presentation"
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
onLogIn={(formData, callback) => {
|
||||
this.props.handleLogIn(formData, result => {
|
||||
if (result.success === true) {
|
||||
onClose();
|
||||
}
|
||||
isLoggedIn () {
|
||||
return (
|
||||
this.props.sessionStatus === sessionActions.Status.FETCHED &&
|
||||
Object.keys(this.props.user).length > 0
|
||||
);
|
||||
}
|
||||
userOwnsProject () {
|
||||
return (
|
||||
this.isLoggedIn() &&
|
||||
Object.keys(this.props.projectInfo).length > 0 &&
|
||||
this.props.user.id === this.props.projectInfo.author.id
|
||||
callback(result);
|
||||
});
|
||||
}}
|
||||
/* eslint-ensable react/jsx-no-bind */
|
||||
/>
|
||||
);
|
||||
}
|
||||
render () {
|
||||
|
@ -320,13 +291,13 @@ class Preview extends React.Component {
|
|||
assetHost={this.props.assetHost}
|
||||
backpackOptions={this.props.backpackOptions}
|
||||
comments={this.props.comments}
|
||||
editable={this.state.editable}
|
||||
editable={this.props.isEditable}
|
||||
extensions={this.state.extensions}
|
||||
faved={this.props.faved}
|
||||
favoriteCount={this.state.favoriteCount}
|
||||
isFullScreen={this.state.isFullScreen}
|
||||
isLoggedIn={this.isLoggedIn()}
|
||||
isShared={this.isShared()}
|
||||
isLoggedIn={this.props.isLoggedIn}
|
||||
isShared={this.props.isShared}
|
||||
loveCount={this.state.loveCount}
|
||||
loved={this.props.loved}
|
||||
originalInfo={this.props.original}
|
||||
|
@ -339,7 +310,7 @@ class Preview extends React.Component {
|
|||
replies={this.props.replies}
|
||||
reportOpen={this.state.reportOpen}
|
||||
studios={this.props.studios}
|
||||
userOwnsProject={this.userOwnsProject()}
|
||||
userOwnsProject={this.props.userOwnsProject}
|
||||
onAddToStudioClicked={this.handleAddToStudioClick}
|
||||
onAddToStudioClosed={this.handleAddToStudioClose}
|
||||
onFavoriteClicked={this.handleFavoriteToggle}
|
||||
|
@ -353,6 +324,7 @@ class Preview extends React.Component {
|
|||
onUpdate={this.handleUpdate}
|
||||
/>
|
||||
</Page> :
|
||||
<React.Fragment>
|
||||
<IntlGUI
|
||||
enableCommunity
|
||||
hideIntro
|
||||
|
@ -363,9 +335,16 @@ class Preview extends React.Component {
|
|||
projectHost={this.props.projectHost}
|
||||
projectId={this.state.projectId}
|
||||
projectTitle={this.props.projectInfo.title}
|
||||
onClickLogout={this.handleLogout}
|
||||
renderLogin={this.renderLogin}
|
||||
onLogOut={this.props.handleLogOut}
|
||||
onOpenRegistration={this.props.handleOpenRegistration}
|
||||
onToggleLoginOpen={this.props.handleToggleLoginOpen}
|
||||
onUpdateProjectTitle={this.handleUpdateProjectTitle}
|
||||
/>
|
||||
<Registration />
|
||||
<CanceledDeletionModal />
|
||||
</React.Fragment>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -388,6 +367,13 @@ Preview.propTypes = {
|
|||
getProjectStudios: PropTypes.func.isRequired,
|
||||
getRemixes: PropTypes.func.isRequired,
|
||||
getTopLevelComments: PropTypes.func.isRequired,
|
||||
handleLogIn: PropTypes.func,
|
||||
handleLogOut: PropTypes.func,
|
||||
handleOpenRegistration: PropTypes.func,
|
||||
handleToggleLoginOpen: PropTypes.func,
|
||||
isEditable: PropTypes.bool,
|
||||
isLoggedIn: PropTypes.bool,
|
||||
isShared: PropTypes.bool,
|
||||
loved: PropTypes.bool,
|
||||
original: projectShape,
|
||||
parent: projectShape,
|
||||
|
@ -415,7 +401,8 @@ Preview.propTypes = {
|
|||
dateJoined: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
classroomId: PropTypes.string
|
||||
})
|
||||
}),
|
||||
userOwnsProject: PropTypes.bool
|
||||
};
|
||||
|
||||
Preview.defaultProps = {
|
||||
|
@ -463,26 +450,64 @@ const consolidateStudiosInfo = (curatedStudios, projectStudios, currentStudioIds
|
|||
return consolidatedStudios;
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
const mapStateToProps = state => {
|
||||
const projectInfoPresent = Object.keys(state.preview.projectInfo).length > 0;
|
||||
const userPresent = state.session.session.user &&
|
||||
Object.keys(state.session.session.user).length > 0;
|
||||
const isLoggedIn = state.session.status === sessionActions.Status.FETCHED &&
|
||||
userPresent;
|
||||
const authorPresent = projectInfoPresent && state.preview.projectInfo.author &&
|
||||
Object.keys(state.preview.projectInfo.author).length > 0;
|
||||
|
||||
return {
|
||||
comments: state.preview.comments,
|
||||
faved: state.preview.faved,
|
||||
fullScreen: state.scratchGui.mode.isFullScreen,
|
||||
// project is editable iff logged in user is the author of the project, or
|
||||
// logged in user is an admin.
|
||||
isEditable: isLoggedIn &&
|
||||
((authorPresent && state.preview.projectInfo.author.username === state.session.session.user.username) ||
|
||||
state.permissions.admin === true),
|
||||
isLoggedIn: isLoggedIn,
|
||||
// if we don't have projectInfo, assume it's shared until we know otherwise
|
||||
isShared: !projectInfoPresent || (
|
||||
state.preview.projectInfo.history &&
|
||||
state.preview.projectInfo.history.shared &&
|
||||
state.preview.projectInfo.history.shared.length > 0),
|
||||
loved: state.preview.loved,
|
||||
original: state.preview.original,
|
||||
parent: state.preview.parent,
|
||||
remixes: state.preview.remixes,
|
||||
replies: state.preview.replies,
|
||||
sessionStatus: state.session.status,
|
||||
playerMode: state.scratchGui.mode.isPlayerOnly,
|
||||
projectInfo: state.preview.projectInfo,
|
||||
projectStudios: state.preview.projectStudios,
|
||||
remixes: state.preview.remixes,
|
||||
replies: state.preview.replies,
|
||||
sessionStatus: state.session.status, // check if used
|
||||
studios: consolidateStudiosInfo(state.preview.curatedStudios,
|
||||
state.preview.projectStudios, state.preview.currentStudioIds,
|
||||
state.preview.status.studioRequests),
|
||||
user: state.session.session.user,
|
||||
playerMode: state.scratchGui.mode.isPlayerOnly,
|
||||
fullScreen: state.scratchGui.mode.isFullScreen
|
||||
});
|
||||
userOwnsProject: isLoggedIn && authorPresent &&
|
||||
state.session.session.user.id === state.preview.projectInfo.author.id
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.setRegistrationOpen(true));
|
||||
},
|
||||
handleLogIn: (formData, callback) => {
|
||||
dispatch(navigationActions.handleLogIn(formData, callback));
|
||||
},
|
||||
handleLogOut: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.handleLogOut());
|
||||
},
|
||||
handleToggleLoginOpen: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.toggleLoginOpen());
|
||||
},
|
||||
getOriginalInfo: id => {
|
||||
dispatch(previewActions.getOriginalInfo(id));
|
||||
},
|
||||
|
@ -523,9 +548,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
setLovedStatus: (loved, id, username, token) => {
|
||||
dispatch(previewActions.setLovedStatus(loved, id, username, token));
|
||||
},
|
||||
refreshSession: () => {
|
||||
dispatch(sessionActions.refreshSession());
|
||||
},
|
||||
reportProject: (id, formData) => {
|
||||
dispatch(previewActions.reportProject(id, formData));
|
||||
},
|
||||
|
|
|
@ -232,7 +232,7 @@ Search.propTypes = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
searchTerm: state.navigation
|
||||
searchTerm: state.navigation.searchTerm
|
||||
});
|
||||
|
||||
const WrappedSearch = injectIntl(Search);
|
||||
|
|
|
@ -7,8 +7,8 @@ const React = require('react');
|
|||
const api = require('../../lib/api');
|
||||
const injectIntl = require('../../lib/intl.jsx').injectIntl;
|
||||
const intlShape = require('../../lib/intl.jsx').intlShape;
|
||||
const log = require('../../lib/log.js');
|
||||
const sessionStatus = require('../../redux/session').Status;
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
|
||||
const Deck = require('../../components/deck/deck.jsx');
|
||||
const Progression = require('../../components/progression/progression.jsx');
|
||||
|
@ -24,7 +24,6 @@ class StudentCompleteRegistration extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'handleAdvanceStep',
|
||||
'handleLogOut',
|
||||
'handleRegister',
|
||||
'handleGoToClass'
|
||||
]);
|
||||
|
@ -61,18 +60,6 @@ class StudentCompleteRegistration extends React.Component {
|
|||
formData: defaults({}, formData, this.state.formData)
|
||||
});
|
||||
}
|
||||
handleLogOut (e) {
|
||||
e.preventDefault();
|
||||
api({
|
||||
host: '',
|
||||
method: 'post',
|
||||
uri: '/accounts/logout/',
|
||||
useCsrf: true
|
||||
}, err => {
|
||||
if (err) return log.error(err);
|
||||
window.location = '/';
|
||||
});
|
||||
}
|
||||
handleRegister (formData) {
|
||||
this.setState({waiting: true});
|
||||
|
||||
|
@ -147,7 +134,7 @@ class StudentCompleteRegistration extends React.Component {
|
|||
classroom={this.state.classroom}
|
||||
studentUsername={this.props.studentUsername}
|
||||
waiting={this.state.waiting}
|
||||
onHandleLogOut={this.handleLogOut}
|
||||
onHandleLogOut={this.props.handleLogOut}
|
||||
onNextStep={this.handleAdvanceStep}
|
||||
/>
|
||||
{this.props.must_reset_password ?
|
||||
|
@ -178,6 +165,7 @@ class StudentCompleteRegistration extends React.Component {
|
|||
|
||||
StudentCompleteRegistration.propTypes = {
|
||||
classroomId: PropTypes.number.isRequired,
|
||||
handleLogOut: PropTypes.func,
|
||||
intl: intlShape,
|
||||
must_reset_password: PropTypes.bool.isRequired,
|
||||
newStudent: PropTypes.bool.isRequired,
|
||||
|
@ -199,6 +187,16 @@ const mapStateToProps = state => ({
|
|||
studentUsername: state.session.session.user && state.session.session.user.username
|
||||
});
|
||||
|
||||
const ConnectedStudentCompleteRegistration = connect(mapStateToProps)(IntlStudentCompleteRegistration);
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleLogOut: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.handleLogOut());
|
||||
}
|
||||
});
|
||||
|
||||
const ConnectedStudentCompleteRegistration = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(IntlStudentCompleteRegistration);
|
||||
|
||||
render(<ConnectedStudentCompleteRegistration />, document.getElementById('app'));
|
||||
|
|
Loading…
Reference in a new issue