Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parent Letter: generate custom letter for single student #34034

Merged
merged 10 commits into from Apr 4, 2020
257 changes: 161 additions & 96 deletions apps/src/lib/ui/ParentLetter.jsx
@@ -1,5 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import _ from 'lodash';
import {studio, pegasus} from '../util/urlHelpers';
import {SectionLoginType} from '../../util/sharedConstants';
import color from '../../util/color';
Expand All @@ -13,7 +15,7 @@ const ENGAGEMENT_URL =
const LOGIN_TYPE_NAMES = {
[SectionLoginType.clever]: 'Clever accounts',
[SectionLoginType.google_classroom]: 'Google Classroom accounts',
[SectionLoginType.picture]: 'picture passwosrds',
[SectionLoginType.picture]: 'picture passwords',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😂 Thank you for catching this!

[SectionLoginType.word]: 'secret words',
[SectionLoginType.email]: 'personal logins'
};
Expand All @@ -32,101 +34,161 @@ const LOGIN_TYPE_NAMES = {
* secretPicturePath
* secretWords
*/
export default function ParentLetter({
loginType,
secretPicturePath,
secretWords,
sectionCode,
studentName,
teacherName
}) {
return (
<div>
<Header />
<article>
<p>Hello!</p>
<p>
In my class, your child {studentName} is learning computer science on{' '}
<a href={pegasus('/')}>Code.org</a>, a fun, creative platform for
learning computer science and basic coding. Your interest in what your
child is learning is critical, and Code.org makes it easy to stay
involved.
</p>
<h1>Step 1 - Encourage your child, show interest</h1>
<p>
One of the best ways to show your interest is to ask your child to
explain what they’re learning and show you a project they are proud of
(<a href={ENGAGEMENT_URL}>details on how to engage your child</a>
).
</p>
<h1>Step 2 - Get your child set up to use Code.org at home</h1>
<SignInInstructions
loginType={loginType}
secretPicturePath={secretPicturePath}
secretWords={secretWords}
sectionCode={sectionCode}
studentName={studentName}
/>
<p>
At the top of their homepage, {studentName || 'your student'} can
continue the course they are doing with their classroom at school.
They can also create their own{' '}
<a href={studio('/projects/public')}>
games or artwork in the Project Gallery
</a>{' '}
or check out <a href={pegasus('/athome')}>code.org/athome</a> for
ideas for things to work on at home.
</p>
<h1>Step 3 - Connect your email to your student’s account</h1>
<p>
Keep up to date with what your student is working on and receive
updates from Code.org.
</p>
<ol>
<li>Have your child sign in to Code.org</li>
<li>
Click on the User Menu in the top right corner of the site, then
click on Account Settings.
</li>
<li>
Scroll down to the section “For Parents and Guardians” and add your
email address.
</li>
</ol>
<h1>Step 4 - Review Code.org’s privacy policy</h1>
<p>
Code.org assigns utmost importance to student safety and security.
Code.org has signed the{' '}
<a href={PRIVACY_PLEDGE_URL}>Student Privacy Pledge</a> and their
privacy practices have received{' '}
<a href={COMMON_SENSE_ARTICLE_URL}>
one of the highest overall scores from Common Sense Media
</a>
. You can find further details by viewing Code.org’s{' '}
<a href={pegasus('/privacy')}>Privacy Policy</a>.
</p>
<p>
Computer science teaches students critical thinking, problem solving,
and digital citizenship, and benefits all students in today’s world,
no matter what opportunities they pursue in the future.
</p>
<p>
Please let me know if you have any questions and thank you for your
continued support of your child and of our classroom!
</p>
<p>{teacherName}</p>
</article>
</div>
);
class ParentLetter extends React.Component {
static propTypes = {
studentId: PropTypes.string,
autoPrint: PropTypes.bool,
// Provided by Redux
section: PropTypes.shape({
id: PropTypes.number.isRequired,
loginType: PropTypes.oneOf(Object.values(SectionLoginType)).isRequired,
code: PropTypes.string.isRequired
}).isRequired,
students: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
secret_picture_path: PropTypes.string,
secret_words: PropTypes.string
})
),
teacherName: PropTypes.string.isRequired
};

static defaultProps = {
students: []
};

componentDidMount() {
if (this.props.autoPrint) {
this.printParentLetter();
}
}

printParentLetter = () => {
const printArea = document.getElementById('printArea').outerHTML;
// Adding a unique ID to the window name allows for multiple instances of this window
// to be open at once without affecting each other.
const windowName = `printWindow-${_.uniqueId()}`;
let printWindow = window.open('', windowName, '');

printWindow.document.open();
printWindow.addEventListener('load', event => {
printWindow.print();
});

printWindow.document.write(
`<html><head><link rel="stylesheet" type="text/css" href="/shared/css/standards-report-print.css"></head>`
);
printWindow.document.write('<body onafterprint="self.close()">');
printWindow.document.write(printArea);
printWindow.document.write('</body></html>');
printWindow.document.close();
};

render() {
const {students, teacherName, section, studentId} = this.props;
const sectionCode = section.code;
const loginType = section.loginType;
const student =
students.length !== 0
? students
.filter(student => student.id.toString() === studentId)
.shift()
: null;
const studentName = student ? student.name : null;
const secretPicturePath = student ? student.secret_picture_path : null;
const secretWords = student ? student.secret_words : null;
return (
<div id="printArea">
<Header />
<article>
<p>Hello!</p>
<p>
In my class, your child {studentName} is learning computer science
on <a href={pegasus('/')}>Code.org</a>, a fun, creative platform for
learning computer science and basic coding. Your interest in what
your child is learning is critical, and Code.org makes it easy to
stay involved.
</p>
<h1>Step 1 - Encourage your child, show interest</h1>
<p>
One of the best ways to show your interest is to ask your child to
explain what they’re learning and show you a project they are proud
of (<a href={ENGAGEMENT_URL}>details on how to engage your child</a>
).
</p>
<h1>Step 2 - Get your child set up to use Code.org at home</h1>
<SignInInstructions
loginType={loginType}
secretPicturePath={secretPicturePath}
secretWords={secretWords}
sectionCode={sectionCode}
studentName={studentName}
/>
<p>
At the top of their homepage, {studentName || 'your student'} can
continue the course they are doing with their classroom at school.
They can also create their own{' '}
<a href={studio('/projects/public')}>
games or artwork in the Project Gallery
</a>{' '}
or check out <a href={pegasus('/athome')}>code.org/athome</a> for
ideas for things to work on at home.
</p>
<h1>Step 3 - Connect your email to your student’s account</h1>
<p>
Keep up to date with what your student is working on and receive
updates from Code.org.
</p>
<ol>
<li>Have your child sign in to Code.org</li>
<li>
Click on the User Menu in the top right corner of the site, then
click on Account Settings.
</li>
<li>
Scroll down to the section “For Parents and Guardians” and add
your email address.
</li>
</ol>
<h1>Step 4 - Review Code.org’s privacy policy</h1>
<p>
Code.org assigns utmost importance to student safety and security.
Code.org has signed the{' '}
<a href={PRIVACY_PLEDGE_URL}>Student Privacy Pledge</a> and their
privacy practices have received{' '}
<a href={COMMON_SENSE_ARTICLE_URL}>
one of the highest overall scores from Common Sense Media
</a>
. You can find further details by viewing Code.org’s{' '}
<a href={pegasus('/privacy')}>Privacy Policy</a>.
</p>
<p>
Computer science teaches students critical thinking, problem
solving, and digital citizenship, and benefits all students in
today’s world, no matter what opportunities they pursue in the
future.
</p>
<p>
Please let me know if you have any questions and thank you for your
continued support of your child and of our classroom!
</p>
<p>{teacherName}</p>
</article>
</div>
);
}
}
ParentLetter.propTypes = {
loginType: PropTypes.oneOf(Object.values(SectionLoginType)).isRequired,
secretPicturePath: PropTypes.string,
secretWords: PropTypes.string,
sectionCode: PropTypes.string, // TODO: Conditionally-required
studentName: PropTypes.string,
teacherName: PropTypes.string.isRequired
};

export const UnconnectedParentLetter = ParentLetter;

export default connect(state => ({
section:
state.teacherSections.sections[state.teacherSections.selectedSectionId],
students: state.sectionData.section.students,
teacherName: state.currentUser.userName
}))(ParentLetter);

const Header = () => {
return (
Expand Down Expand Up @@ -188,7 +250,10 @@ const SignInInstructions = ({
{secretPicturePath && (
<span>
<br />
<img src={secretPicturePath} style={{width: 60, margin: 10}} />
<img
src={pegasus(`/images/${secretPicturePath}`)}
style={{width: 60, margin: 10}}
/>
</span>
)}
</li>
Expand Down
39 changes: 31 additions & 8 deletions apps/src/lib/ui/ParentLetter.story.jsx
@@ -1,20 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import ParentLetter from './ParentLetter';
import {UnconnectedParentLetter as ParentLetter} from './ParentLetter';
import {SectionLoginType} from '../../util/sharedConstants';
import wizardPng from '../../../static/skins/studio/wizard_thumb.png';

export default storybook => {
storybook = storybook.storiesOf('ParentLetter', module);

const sampleSection = {
id: 7,
code: 'ABCDEF'
};

const sampleStudents = [
{
id: 100,
name: 'Neville',
secret_picture_path: wizardPng,
secret_words: 'wizarding world'
},
{
id: 101,
name: 'Hermione',
secret_picture_path: wizardPng,
secret_words: 'wizarding world'
}
];

// Make stories for generic letters and personalized letters
// Make a story for every login type
Object.values(SectionLoginType).forEach(loginType => {
storybook = storybook.add(`Generic / ${loginType}`, () => (
<Page>
<ParentLetter
loginType={loginType}
sectionCode="ABCDEF"
section={{
...sampleSection,
loginType: loginType
}}
teacherName="Minerva McGonagall"
/>
</Page>
Expand All @@ -23,12 +45,13 @@ export default storybook => {
storybook = storybook.add(`Personalized / ${loginType}`, () => (
<Page>
<ParentLetter
loginType={loginType}
secretPicturePath={wizardPng}
secretWords="wizarding world"
sectionCode="ABCDEF"
section={{
...sampleSection,
loginType: loginType
}}
teacherName="Minerva McGonagall"
studentName="Neville"
students={sampleStudents}
studentId={'101'}
/>
</Page>
));
Expand Down
Expand Up @@ -195,6 +195,9 @@ class ManageStudentActionsCell extends Component {

onDownloadParentLetter = () => {
const {id, sectionId} = this.props;
const url =
teacherDashboardUrl(sectionId, '/parent_letter') + `?studentId=${id}`;
window.open(url, '_blank');
firehoseClient.putRecord(
{
study: 'teacher-dashboard',
Expand Down
16 changes: 14 additions & 2 deletions apps/src/templates/teacherDashboard/TeacherDashboard.jsx
Expand Up @@ -16,6 +16,8 @@ import EmptySection from './EmptySection';
import _ from 'lodash';
import firehoseClient from '../../lib/util/firehose';
import StandardsReport from '../sectionProgress/standards/StandardsReport';
import ParentLetter from '@cdo/apps/lib/ui/ParentLetter';
import {queryParams} from '@cdo/apps/code-studio/utils';

class TeacherDashboard extends Component {
static propTypes = {
Expand Down Expand Up @@ -70,10 +72,11 @@ class TeacherDashboard extends Component {
location.pathname = TeacherDashboardPath.progress;
}

// Include header components unless we are on the /login_info or /standards_report page.
// Include header components unless we are on the /login_info, /standards_report, or /parent_letter page.
const includeHeader =
location.pathname !== TeacherDashboardPath.loginInfo &&
location.pathname !== TeacherDashboardPath.standardsReport;
location.pathname !== TeacherDashboardPath.standardsReport &&
location.pathname !== TeacherDashboardPath.parentLetter;

return (
<div>
Expand Down Expand Up @@ -104,6 +107,15 @@ class TeacherDashboard extends Component {
path={TeacherDashboardPath.standardsReport}
component={props => <StandardsReport />}
/>
<Route
path={TeacherDashboardPath.parentLetter}
component={props => (
<ParentLetter
studentId={queryParams('studentId')}
autoPrint={true}
/>
)}
/>
{/* Break out of Switch if we have 0 students. Display EmptySection component instead. */}
{studentCount === 0 && (
<Route
Expand Down