Skip to content

Commit

Permalink
Merge pull request #38953 from code-dot-org/hbergam/signin_callout_ui
Browse files Browse the repository at this point in the history
Hbergam/signin callout UI
  • Loading branch information
hannahbergam committed Feb 11, 2021
2 parents 29796f2 + 3a25711 commit 7e972ac
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
2 changes: 2 additions & 0 deletions apps/i18n/common/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,8 @@
"notApplicable": "N/A",
"notCompleted": "Not completed",
"notSaved": "Not saved",
"notSignedInHeader": "You are not signed in",
"notSignedInBody": "You don't need an account to work on this lesson, but if you want to save your work, remember to sign in or create an account before you get started.",
"notStarted": "Not started",
"nPoints": "{numPoints, plural, one {1 point} other {# points}}",
"numMatchCorrect": "# match correct",
Expand Down
114 changes: 114 additions & 0 deletions apps/src/code-studio/components/header/SignInCallout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react';
import i18n from '@cdo/locale';
import PropTypes from 'prop-types';

const CALLOUT_COLOR = '#454545';
const TRIANGLE_BASE = 30;
const TRIANGLE_HEIGHT = 15;
const CALLOUT_Z_INDEX = 1040;
const CALLOUT_TOP = 30;

const styles = {
container: {
// The outermost div is relatively positioned so it can be used as a positional
// anchor for its children (which will be absolutely positioned to avoid affecting
// layout). This element must be 0-sized to avoid affecting layout.
position: 'relative',
height: 0,
width: 0
},
content: {
position: 'absolute',
top: CALLOUT_TOP,
right: -90,
zIndex: CALLOUT_Z_INDEX,
backgroundColor: CALLOUT_COLOR,
borderRadius: 3
},
modalBackdrop: {
// Most backdrop attributes come from the 'modal-backdrop' class defined by bootstrap
// but we need to override the opacity as the default opacity of 0.8 is too dark.
// Note that bootstrap defaults the z-index of the backdrop to 1040.
opacity: 0.5
},
upTriangle: {
position: 'absolute',
top: CALLOUT_TOP - TRIANGLE_HEIGHT,
left: -(TRIANGLE_HEIGHT / 2.0),
width: 0,
height: 0,
borderStyle: 'solid',
borderTopWidth: 0,
borderRightWidth: TRIANGLE_BASE,
borderBottomWidth: TRIANGLE_HEIGHT,
borderLeftWidth: TRIANGLE_BASE,
borderTopColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: CALLOUT_COLOR,
borderLeftColor: 'transparent',
zIndex: CALLOUT_Z_INDEX
},
contentContainer: {
display: 'flex',
padding: 20
},
imageContainer: {
width: 100,
marginRight: 20
},
textContainer: {
width: 400,
textAlign: 'left',
whiteSpace: 'normal'
},
textHeader: {
marginTop: 0
}
};

/*
* This is a callout attached to the sign-in button that's used on CSF level
* pages to remind the user to sign-in. Note that the sign-in button is
* defined in shared/haml/user_header.haml and is not a React component.
* This component is injected into the page by src/code-studio/header.js.
*/
export default class SignInCallout extends React.Component {
static propTypes = {
handleClose: PropTypes.func.isRequired
};

constructor(props) {
super(props);

this.renderContent = this.renderContent.bind(this);
}

renderContent() {
return (
<div style={styles.contentContainer}>
<img
style={styles.imageContainer}
src="/shared/images/user-not-signed-in.png"
/>
<div style={styles.textContainer}>
<h2 style={styles.textHeader}>{i18n.notSignedInHeader()}</h2>
<p> {i18n.notSignedInBody()}</p>
</div>
</div>
);
}

render() {
return (
<div style={styles.container}>
<div
className="modal-backdrop"
style={styles.modalBackdrop}
onClick={this.props.handleClose}
/>
<div style={styles.upTriangle} />
<div style={styles.content}>{this.renderContent()}</div>
</div>
);
}
}
48 changes: 48 additions & 0 deletions apps/src/code-studio/components/header/SignInCalloutWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import cookies from 'js-cookie';
import SignInCallout from './SignInCallout';

const HideSignInCallout = 'hide_signin_callout';

// This class hold the display logic for the sign in callout, which prompts
// students to log in in order to save their progress. The callout's freqency
// is regulated here by the use of both session storage and cookies.
export default class SignInCalloutWrapper extends React.Component {
constructor(props) {
super(props);
this.closeCallout = this.closeCallout.bind(this);
// Before the cookies are set, searching for them will return false, so the
// desired flag should logically read 'true' when set: resulting in the
// 'hide' name. Though this leads to a bit of a double negative in the
// display logic (if not hide: display), it is the clearest option for now.
this.state = {
hideCallout:
// The use of both session storage and cookies is to check for 1 day
// and 1 session, and display the callout again once BOTH have passed.
cookies.get(HideSignInCallout) === 'true' ||
sessionStorage.getItem(HideSignInCallout) === 'true'
};
}

closeCallout(event) {
this.setState({hideCallout: true});
cookies.set(HideSignInCallout, 'true', {expires: 1, path: '/'});
sessionStorage.setItem(HideSignInCallout, 'true');
event.preventDefault();
}

// For readibility: returning an empty div here explicitly if the callout is
// not supposed to be displayed. This avoids using a render statement that
// often returns *nothing.
render() {
if (this.state.hideCallout) {
return null;
} else {
return (
<div className="uitest-signincallout">
<SignInCallout handleClose={this.closeCallout} />
</div>
);
}
}
}
22 changes: 22 additions & 0 deletions apps/test/unit/code-studio/components/header/SignInCalloutTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import SignInCalloutWrapper from '@cdo/apps/code-studio/components/header/SignInCalloutWrapper';
import {shallow} from 'enzyme';
import {expect} from '../../../../util/reconfiguredChai';
import i18n from '@cdo/locale';

const wrapper = shallow(<SignInCalloutWrapper />);

describe('ViewPopup', () => {
it('displays the correct background darkness', () => {
wrapper.setState({hideCallout: false});
expect(wrapper.html().includes('opacity:0.5')).to.be.true;
});

it('shows the correct header', () => {
expect(wrapper.html().includes(i18n.notSignedInHeader())).to.be.true;
});

it('shows the correct image', () => {
expect(wrapper.html().includes('user-not-signed-in.png')).to.be.true;
});
});

0 comments on commit 7e972ac

Please sign in to comment.