Compare commits

...

3 Commits

Author SHA1 Message Date
Alexander Sheehan
63bbf8d2a3 WIP: iterative steps towards demoable (non-real world functioning) product 2021-08-12 12:08:33 -04:00
manny-m
39e046f09d feat: added PartnetWelcome componenet 2021-08-11 16:52:38 -04:00
Alexander Sheehan
14c1f5b944 feat: beginnings of the recruit me some money exploits 2021-08-11 10:48:16 -04:00
9 changed files with 353 additions and 123 deletions

View File

@@ -12,7 +12,7 @@ import {
import messages from './messages';
const AuthExtraLargeLayout = (props) => {
const { intl, username, variant } = props;
const { intl, username, variant, toggleWelcomeText } = props;
return (
<div className="container row p-0 m-0 large-screen-container">
@@ -48,21 +48,23 @@ const AuthExtraLargeLayout = (props) => {
messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username },
)}
</div>
<div
className={classNames(
'text-primary',
{
'display-1': variant === 'xl',
'display-2': variant === 'xxl',
},
)}
>
{intl.formatMessage(messages['complete.your.profile.1'])}
<span className="text-accent-a">
<br />
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</div>
{ !toggleWelcomeText ? (
<div
className={classNames(
'text-primary',
{
'display-1': variant === 'xl',
'display-2': variant === 'xxl',
},
)}
>
{intl.formatMessage(messages['complete.your.profile.1'])}
<span className="text-accent-a">
<br />
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</div>
) : null}
</Col>
</Row>
</div>
@@ -86,12 +88,14 @@ const AuthExtraLargeLayout = (props) => {
AuthExtraLargeLayout.defaultProps = {
variant: 'xl',
toggleWelcomeText: false,
};
AuthExtraLargeLayout.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
variant: PropTypes.oneOf(['xl', 'xxl']),
toggleWelcomeText: PropTypes.bool,
};
export default injectIntl(AuthExtraLargeLayout);

View File

@@ -11,7 +11,7 @@ import {
import messages from './messages';
const AuthMediumLayout = (props) => {
const { intl, username } = props;
const { intl, username, toggleWelcomeText } = props;
return (
<div className="container row p-0 mb-3 medium-container">
@@ -33,13 +33,15 @@ const AuthMediumLayout = (props) => {
messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username },
)}
</h3>
<div className="display-1 text-primary">
{intl.formatMessage(messages['complete.your.profile.1'])}
<span className="text-accent-a">
<br />
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</div>
{ !toggleWelcomeText ? (
<div className="display-1 text-primary">
{intl.formatMessage(messages['complete.your.profile.1'])}
<span className="text-accent-a">
<br />
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</div>
) : null}
</Col>
</Row>
</div>
@@ -61,9 +63,15 @@ const AuthMediumLayout = (props) => {
);
};
AuthMediumLayout.defaultProps = {
toggleWelcomeText: false,
};
AuthMediumLayout.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
toggleWelcomeText: PropTypes.bool,
};
export default injectIntl(AuthMediumLayout);

View File

@@ -12,7 +12,7 @@ import {
import messages from './messages';
const AuthSmallLayout = (props) => {
const { intl, username, variant } = props;
const { intl, username, variant, toggleWelcomeText } = props;
return (
<div className="small-screen-header-light">
@@ -40,13 +40,15 @@ const AuthSmallLayout = (props) => {
messages['welcome.to.platform'], { siteName: getConfig().SITE_NAME, username },
)}
</h5>
<h1>
{intl.formatMessage(messages['complete.your.profile.1'])}
<br />
<span className="text-accent-a">
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</h1>
{ !toggleWelcomeText ? (
<h1>
{intl.formatMessage(messages['complete.your.profile.1'])}
<br />
<span className="text-accent-a">
{intl.formatMessage(messages['complete.your.profile.2'])}
</span>
</h1>
) : null}
</Col>
</Row>
</div>
@@ -57,12 +59,14 @@ const AuthSmallLayout = (props) => {
AuthSmallLayout.defaultProps = {
variant: 'sm',
toggleWelcomeText: false,
};
AuthSmallLayout.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
variant: PropTypes.oneOf(['sm', 'xs']),
toggleWelcomeText: PropTypes.bool,
};
export default injectIntl(AuthSmallLayout);

View File

@@ -17,7 +17,7 @@ import AuthExtraLargeLayout from './AuthExtraLargeLayout';
import AuthMediumLayout from './AuthMediumLayout';
import AuthSmallLayout from './AuthSmallLayout';
const BaseComponent = ({ children }) => {
const BaseComponent = ({ children, toggleWelcomeText }) => {
const authenticatedUser = getAuthenticatedUser();
return (
@@ -33,25 +33,25 @@ const BaseComponent = ({ children }) => {
<div className={classNames('layout', { authenticated: authenticatedUser })}>
<ExtraSmall>
<div className="col-md-12 small-screen-top-stripe" />
{authenticatedUser ? <AuthSmallLayout variant="xs" username={authenticatedUser.username} /> : <SmallLayout />}
{authenticatedUser ? <AuthSmallLayout toggleWelcomeText={toggleWelcomeText} variant="xs" username={authenticatedUser.username} /> : <SmallLayout />}
</ExtraSmall>
<Small>
<div className="col-md-12 small-screen-top-stripe" />
{authenticatedUser ? <AuthSmallLayout username={authenticatedUser.username} /> : <SmallLayout />}
{authenticatedUser ? <AuthSmallLayout toggleWelcomeText={toggleWelcomeText} username={authenticatedUser.username} /> : <SmallLayout />}
</Small>
<Medium>
<div className="w-100 medium-screen-top-stripe" />
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : <MediumLayout />}
{authenticatedUser ? <AuthMediumLayout toggleWelcomeText={toggleWelcomeText} username={authenticatedUser.username} /> : <MediumLayout />}
</Medium>
<Large>
<div className="w-100 large-screen-top-stripe" />
{authenticatedUser ? <AuthMediumLayout username={authenticatedUser.username} /> : <MediumLayout />}
{authenticatedUser ? <AuthMediumLayout toggleWelcomeText={toggleWelcomeText} username={authenticatedUser.username} /> : <MediumLayout />}
</Large>
<ExtraLarge>
{authenticatedUser ? <AuthExtraLargeLayout username={authenticatedUser.username} /> : <LargeLayout />}
{authenticatedUser ? <AuthExtraLargeLayout toggleWelcomeText={toggleWelcomeText} username={authenticatedUser.username} /> : <LargeLayout />}
</ExtraLarge>
<ExtraExtraLarge>
{authenticatedUser ? <AuthExtraLargeLayout variant="xxl" username={authenticatedUser.username} /> : <LargeLayout />}
{authenticatedUser ? <AuthExtraLargeLayout toggleWelcomeText={toggleWelcomeText} variant="xxl" username={authenticatedUser.username} /> : <LargeLayout />}
</ExtraExtraLarge>
<div className={classNames('content', { 'align-items-center mt-0': authenticatedUser })}>
@@ -64,6 +64,11 @@ const BaseComponent = ({ children }) => {
BaseComponent.propTypes = {
children: PropTypes.node.isRequired,
toggleWelcomeText: PropTypes.bool,
};
BaseComponent.defaultProps = {
toggleWelcomeText: false,
}
export default BaseComponent;

View File

@@ -38,7 +38,7 @@ initialize({
DISABLE_ENTERPRISE_LOGIN: process.env.DISABLE_ENTERPRISE_LOGIN || '',
INFO_EMAIL: process.env.INFO_EMAIL || '',
REGISTER_CONVERSION_COOKIE_NAME: process.env.REGISTER_CONVERSION_COOKIE_NAME || null,
ENABLE_PROGRESSIVE_PROFILING: process.env.ENABLE_PROGRESSIVE_PROFILING || false,
ENABLE_PROGRESSIVE_PROFILING: true,
});
},
},

View File

@@ -29,6 +29,8 @@ export const EDUCATION_LEVELS = [
'other',
];
export const REGISTRATION_REASONS = ['', 'job', 'learn', 'boss'];
export const GENDER_OPTIONS = ['', 'f', 'm', 'o'];
// Other constants

View File

@@ -0,0 +1,152 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
Form,
Button,
StatefulButton,
Hyperlink,
Icon,
Collapsible,
Image,
} from '@edx/paragon';
import { Pin, Tag, Email } from '@edx/paragon/icons';
import { getConfig } from '@edx/frontend-platform';
const logoStyle = { height: '4rem' };
const PartnerWelcome = ({ onComplete }) => {
const [formValues, setFormValues] = useState({ email: '', location: '', search: '' });
const onChangeHandler = (e) => {
setFormValues({ ...formValues, [e.target.name]: e.target.value });
};
const submitToPartnerButton = (href) => (
<Button
target="_blank"
rel="noopener noreferrer"
as="a"
href={href}
size="sm"
>
Submit
</Button>
);
return (
<>
<Form>
<Collapsible
styling="card-lg"
title={<Image style={logoStyle} src="https://www.ziprecruiter.com/zrs/starterview/f810ae13/img/logos/ziprecruiter-blacktext.svg" fluid />}
>
<Form.Group controlId="email">
<Form.Control
name="email"
value={formValues.email}
onChange={(e) => onChangeHandler(e)}
floatingLabel="Email"
trailingElement={<Icon src={Email} />}
/>
</Form.Group>
<Form.Group controlId="location">
<Form.Control
name="location"
value={formValues.location}
onChange={(e) => onChangeHandler(e)}
floatingLabel="Location"
trailingElement={<Icon src={Pin} />}
/>
</Form.Group>
<Form.Group controlId="search">
<Form.Control
name="search"
value={formValues.search}
onChange={(e) => onChangeHandler(e)}
floatingLabel="Desired Title"
trailingElement={<Icon src={Tag} />}
/>
</Form.Group>
<Form.Group>
{submitToPartnerButton('https://www.ziprecruiter.com/')}
</Form.Group>
</Collapsible>
<Collapsible
styling="card-lg"
title={<Image style={logoStyle} src="https://content.linkedin.com/content/dam/me/business/en-us/amp/brand-site/v2/bg/LI-Logo.svg.original.svg" fluid />}
>
<Form.Group controlId="email">
<Form.Control
name="email"
value={formValues.email}
onChange={(e) => onChangeHandler(e)}
floatingLabel="Email"
trailingElement={<Icon src={Email} />}
/>
</Form.Group>
<Form.Group controlId="location">
<Form.Control
name="location"
value={formValues.location}
onChange={(e) => onChangeHandler(e)}
floatingLabel="Location"
trailingElement={<Icon src={Pin} />}
/>
</Form.Group>
<Form.Group controlId="search">
<Form.Control
name="search"
value={formValues.search}
onChange={(e) => onChangeHandler(e)}
floatingLabel="Desired Title"
trailingElement={<Icon src={Tag} />}
/>
</Form.Group>
<Form.Group>
{submitToPartnerButton('https://www.linkedin.com/')}
</Form.Group>
</Collapsible>
<span className="progressive-profiling-support">
<Hyperlink
isInline
variant="muted"
destination={getConfig().WELCOME_PAGE_SUPPORT_LINK}
target="_blank"
showLaunchIcon={false}
>
Learn more about our partners
</Hyperlink>
</span>
<div className="d-flex mt-4">
<StatefulButton
type="submit"
variant="brand"
className="login-button-width"
state="active"
labels={{
default: 'Complete',
pending: '',
}}
onClick={onComplete}
onMouseDown={(e) => e.preventDefault()}
/>
</div>
</Form>
</>
);
};
PartnerWelcome.propTypes = {
onComplete: PropTypes.func.isRequired,
};
export default PartnerWelcome;

View File

@@ -30,8 +30,9 @@ import messages from './messages';
import { RedirectLogistration } from '../common-components';
import { DEFAULT_REDIRECT_URL, DEFAULT_STATE } from '../data/constants';
import { EDUCATION_LEVELS, GENDER_OPTIONS, YEAR_OF_BIRTH_OPTIONS } from '../register/data/constants';
import { EDUCATION_LEVELS, GENDER_OPTIONS, YEAR_OF_BIRTH_OPTIONS, REGISTRATION_REASONS } from '../register/data/constants';
import WelcomePageModal from './WelcomePageModal';
import PartnerWelcome from './PartnerWelcome';
import BaseComponent from '../base-component';
const WelcomePage = (props) => {
@@ -39,7 +40,8 @@ const WelcomePage = (props) => {
const [ready, setReady] = useState(false);
const [registrationResult, setRegistrationResult] = useState({ redirectUrl: '' });
const [values, setValues] = useState({ levelOfEducation: '', yearOfBirth: '', gender: '' });
const [values, setValues] = useState({ levelOfEducation: '', yearOfBirth: '', gender: '', reason: '' });
const [openCareerDialog, setOpenCareerDialog] = useState(false);
const [openDialog, setOpenDialog] = useState(false);
const DASHBOARD_URL = getConfig().LMS_BASE_URL.concat(DEFAULT_REDIRECT_URL);
@@ -83,6 +85,11 @@ const WelcomePage = (props) => {
{intl.formatMessage(messages[`gender.options.${key || 'label'}`])}
</option>
)),
reason: REGISTRATION_REASONS.map(key => (
<option className="data-hj-suppress" key={key} value={key}>
{intl.formatMessage(messages[`reason.options.${key || 'label'}`])}
</option>
)),
};
return options[fieldName];
@@ -99,11 +106,19 @@ const WelcomePage = (props) => {
}
});
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
window.optimizely.push({
type: 'event',
eventName: 'authn_welcome_page_submit_btn_clicked',
});
if (!openCareerDialog) {
if (values.reason === 'job') {
setOpenCareerDialog(true);
} else {
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
window.optimizely.push({
type: 'event',
eventName: 'authn_welcome_page_submit_btn_clicked',
});
}
} else {
props.saveUserProfile(authenticatedUser.username, snakeCaseObject(payload));
}
};
const handleSkip = (e) => {
@@ -122,7 +137,7 @@ const WelcomePage = (props) => {
return (
<>
<BaseComponent>
<BaseComponent toggleWelcomeText={openCareerDialog}>
<Helmet>
<title>{intl.formatMessage(messages['progressive.profiling.page.title'],
{ siteName: getConfig().SITE_NAME })}
@@ -137,7 +152,13 @@ const WelcomePage = (props) => {
) : null}
<div className="mw-xs welcome-page-content">
<div className="welcome-page-heading">
<h2 className="h3 text-primary">{intl.formatMessage(messages['progressive.profiling.page.heading'])}</h2>
{ !openCareerDialog ? (
<h2 className="h3 text-primary">
{intl.formatMessage(messages['progressive.profiling.page.heading'])}
</h2>
) : (
<h2 className="h3 text-primary">Advance your career by signing up with one of our partners. </h2>
) }
</div>
<hr className="border-light-700 mb-4" />
{showError ? (
@@ -146,80 +167,94 @@ const WelcomePage = (props) => {
<p>{intl.formatMessage(messages['welcome.page.error.message'])}</p>
</Alert>
) : null}
<Form>
<Form.Group controlId="levelOfEducation">
<Form.Control
as="select"
name="levelOfEducation"
value={values.levelOfEducation}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore} />}
floatingLabel={intl.formatMessage(messages['education.levels.label'])}
>
{getOptions('levelOfEducation')}
</Form.Control>
</Form.Group>
<Form.Group controlId="yearOfBirth">
<Form.Control
as="select"
name="yearOfBirth"
value={values.yearOfBirth}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore} />}
floatingLabel={intl.formatMessage(messages['year.of.birth.label'])}
>
<option value="">{intl.formatMessage(messages['year.of.birth.label'])}</option>
{getOptions('yearOfBirth')}
</Form.Control>
</Form.Group>
<Form.Group controlId="gender" className="mb-3">
<Form.Control
as="select"
name="gender"
value={values.gender}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore} />}
floatingLabel={intl.formatMessage(messages['gender.options.label'])}
>
{getOptions('gender')}
</Form.Control>
</Form.Group>
<span className="progressive-profiling-support">
<Hyperlink
isInline
variant="muted"
destination={getConfig().WELCOME_PAGE_SUPPORT_LINK}
target="_blank"
showLaunchIcon={false}
>
{intl.formatMessage(messages['optional.fields.information.link'])}
</Hyperlink>
</span>
<div className="d-flex mt-4">
<StatefulButton
type="submit"
variant="brand"
className="login-button-width"
state={submitState}
labels={{
default: intl.formatMessage(messages['optional.fields.submit.button']),
pending: '',
}}
onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
<StatefulButton
className="text-gray-700 font-weight-500"
type="submit"
variant="link"
labels={{
default: intl.formatMessage(messages['optional.fields.skip.button']),
}}
onClick={handleSkip}
onMouseDown={(e) => e.preventDefault()}
/>
</div>
</Form>
{ !openCareerDialog ? (
<Form>
<Form.Group controlId="levelOfEducation">
<Form.Control
as="select"
name="levelOfEducation"
value={values.levelOfEducation}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore}/>}
floatingLabel={intl.formatMessage(messages['education.levels.label'])}
>
{getOptions('levelOfEducation')}
</Form.Control>
</Form.Group>
<Form.Group controlId="yearOfBirth">
<Form.Control
as="select"
name="yearOfBirth"
value={values.yearOfBirth}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore}/>}
floatingLabel={intl.formatMessage(messages['year.of.birth.label'])}
>
<option value="">{intl.formatMessage(messages['year.of.birth.label'])}</option>
{getOptions('yearOfBirth')}
</Form.Control>
</Form.Group>
<Form.Group controlId="gender" className="mb-3">
<Form.Control
as="select"
name="gender"
value={values.gender}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore}/>}
floatingLabel={intl.formatMessage(messages['gender.options.label'])}
>
{getOptions('gender')}
</Form.Control>
</Form.Group>
<Form.Group controlId="purposeOfAccountCreation" className="mb-3">
<Form.Control
as="select"
name="reason"
value={values.reason}
onChange={(e) => onChangeHandler(e)}
trailingElement={<Icon src={ExpandMore}/>}
floatingLabel={intl.formatMessage(messages['reason.options.label'])}
>
{getOptions('reason')}
</Form.Control>
</Form.Group>
<span className="progressive-profiling-support">
<Hyperlink
isInline
variant="muted"
destination={getConfig().WELCOME_PAGE_SUPPORT_LINK}
target="_blank"
showLaunchIcon={false}
>
{intl.formatMessage(messages['optional.fields.information.link'])}
</Hyperlink>
</span>
<div className="d-flex mt-4">
<StatefulButton
type="submit"
variant="brand"
className="login-button-width"
state={submitState}
labels={{
default: intl.formatMessage(messages['optional.fields.submit.button']),
pending: '',
}}
onClick={handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
<StatefulButton
className="text-gray-700 font-weight-500"
type="submit"
variant="link"
labels={{
default: intl.formatMessage(messages['optional.fields.skip.button']),
}}
onClick={handleSkip}
onMouseDown={(e) => e.preventDefault()}
/>
</div>
</Form>
) : <PartnerWelcome onComplete={handleSubmit} /> }
</div>
</BaseComponent>
</>

View File

@@ -31,6 +31,26 @@ const messages = defineMessages({
defaultMessage: 'Other/Prefer not to say',
description: 'The label for catch-all gender option.',
},
'reason.options.label': {
id: 'reason.options.label',
defaultMessage: 'Purpose (optional)',
description: 'Placeholder for the reasons options dropdown',
},
'reason.options.job': {
id: 'reason.options.job',
defaultMessage: 'Finding a job',
description: 'edX grants unlimited opportunities to expand and grow one\' career',
},
'reason.options.learn': {
id: 'reason.options.learn',
defaultMessage: 'Learning tons of shit',
description: 'edX ever increasing catalog of a wide array of courses allows one to explore new ares of knowledge and challenge their perception of the world.',
},
'reason.options.boss': {
id: 'reason.options.boss',
defaultMessage: 'My boss made me',
description: 'edX allows businesses and institutions to empower their employees on a mass scale, offering opportunities to learn to countless learners.',
},
'education.levels.label': {
id: 'education.levels.label',
defaultMessage: 'Highest level of education completed (optional)',