Split up content of EmailListTermsConditionsForm within onboarding (#7294) [deploy]
* Move Terms & Conditions, CoC into Intro slide of onboarding Closes https://github.com/thepracticaldev/dev.to/issues/6545. * Rename ClosingSlide in Onboarding tests * Rename EmailListTermsConditionsForm to EmailPreferencesForm * Proper lint and prepend `v2:` to some onboarding components * Move email preferences to last slide Closes https://github.com/thepracticaldev/dev.to/issues/6550. * Use crayons variables for padding * Remove backToSlide function, rename buttonIsDisabled function
This commit is contained in:
parent
d27471aac5
commit
bbe0bf970f
11 changed files with 414 additions and 368 deletions
|
|
@ -109,11 +109,19 @@
|
|||
width: 100%;
|
||||
border-bottom: 2px solid var(--box-darker);
|
||||
|
||||
&.intro-slide {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.navigation-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: $su-4;
|
||||
|
||||
&.intro-slide {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
@extend .crayons-btn;
|
||||
}
|
||||
|
|
@ -337,6 +345,10 @@
|
|||
padding: $su-7;
|
||||
justify-content: center;
|
||||
|
||||
&.intro-slide {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
@extend .crayons-btn;
|
||||
}
|
||||
|
|
@ -586,6 +598,14 @@ $onboarding-user-selected-hover: rgba(71, 85, 235, 0.2);
|
|||
}
|
||||
}
|
||||
|
||||
.checkbox-form-wrapper {
|
||||
padding: $su-7;
|
||||
|
||||
.checkbox-form {
|
||||
padding-bottom: $su-6;
|
||||
}
|
||||
}
|
||||
|
||||
// Checkboxes
|
||||
.checkbox-item {
|
||||
label {
|
||||
|
|
@ -625,5 +645,5 @@ $onboarding-user-selected-hover: rgba(71, 85, 235, 0.2);
|
|||
.onboarding-form-input--last {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'preact/devtools';
|
|||
import { h, Component } from 'preact';
|
||||
|
||||
import IntroSlide from './components/IntroSlide';
|
||||
import EmailListTermsConditionsForm from './components/EmailListTermsConditionsForm';
|
||||
import EmailPreferencesForm from './components/EmailPreferencesForm';
|
||||
import ClosingSlide from './components/ClosingSlide';
|
||||
import FollowTags from './components/FollowTags';
|
||||
import FollowUsers from './components/FollowUsers';
|
||||
|
|
@ -20,10 +20,10 @@ export default class Onboarding extends Component {
|
|||
|
||||
const slides = [
|
||||
IntroSlide,
|
||||
EmailListTermsConditionsForm,
|
||||
ProfileForm,
|
||||
FollowTags,
|
||||
FollowUsers,
|
||||
EmailPreferencesForm,
|
||||
ClosingSlide,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { axe, toHaveNoViolations } from 'jest-axe';
|
|||
|
||||
import Onboarding from '../Onboarding';
|
||||
import ProfileForm from '../components/ProfileForm';
|
||||
import EmailTermsConditionsForm from '../components/EmailListTermsConditionsForm';
|
||||
import FollowTags from '../components/FollowTags';
|
||||
import FollowUsers from '../components/FollowUsers';
|
||||
|
||||
|
|
@ -82,29 +81,6 @@ describe('<Onboarding />', () => {
|
|||
|
||||
describe('IntroSlide', () => {
|
||||
let onboardingSlides;
|
||||
|
||||
beforeEach(() => {
|
||||
onboardingSlides = initializeSlides(0, getUserData());
|
||||
});
|
||||
|
||||
test('renders properly', () => {
|
||||
expect(onboardingSlides).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should not have basic a11y violations', async () => {
|
||||
const results = await axe(onboardingSlides.toString());
|
||||
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
|
||||
test('should advance', () => {
|
||||
onboardingSlides.find('.next-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmailTermsConditionsForm', () => {
|
||||
let onboardingSlides;
|
||||
const codeOfConductCheckEvent = {
|
||||
target: {
|
||||
value: 'checked_code_of_conduct',
|
||||
|
|
@ -117,6 +93,7 @@ describe('<Onboarding />', () => {
|
|||
name: 'checked_terms_and_conditions',
|
||||
},
|
||||
};
|
||||
|
||||
const updateCodeOfConduct = () => {
|
||||
onboardingSlides
|
||||
.find('#checked_code_of_conduct')
|
||||
|
|
@ -129,59 +106,29 @@ describe('<Onboarding />', () => {
|
|||
};
|
||||
|
||||
beforeEach(() => {
|
||||
onboardingSlides = initializeSlides(1, getUserData());
|
||||
onboardingSlides = initializeSlides(0, getUserData());
|
||||
});
|
||||
|
||||
test('renders properly', () => {
|
||||
expect(onboardingSlides).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// Arguably this test is actually just testing the Preact framework
|
||||
// but for the sake of detecting a regression I am refactoring it instead
|
||||
// of removing it (@jacobherrington)
|
||||
test('should track state changes', () => {
|
||||
const emailTerms = onboardingSlides.find(<EmailTermsConditionsForm />);
|
||||
|
||||
expect(emailTerms.state('checked_code_of_conduct')).toBe(false);
|
||||
expect(emailTerms.state('checked_terms_and_conditions')).toBe(false);
|
||||
|
||||
updateCodeOfConduct();
|
||||
updateTermsAndConditions();
|
||||
|
||||
expect(emailTerms.state('checked_code_of_conduct')).toBe(true);
|
||||
expect(emailTerms.state('checked_terms_and_conditions')).toBe(true);
|
||||
});
|
||||
|
||||
test('should not advance if required boxes are not checked', () => {
|
||||
// When none of the boxes are checked
|
||||
onboardingSlides.find('.next-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
|
||||
// When only the code of conduct is checked
|
||||
updateCodeOfConduct();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
|
||||
// When only the terms and conditions are checked
|
||||
updateCodeOfConduct();
|
||||
updateTermsAndConditions();
|
||||
onboardingSlides.find('.next-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
});
|
||||
|
||||
test('should advance if required boxes are checked', async () => {
|
||||
fetch.once({});
|
||||
expect(onboardingSlides.state().currentSlide).toBe(0);
|
||||
|
||||
updateCodeOfConduct();
|
||||
updateTermsAndConditions();
|
||||
|
||||
onboardingSlides.find('.next-button').simulate('click');
|
||||
await flushPromises();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(2);
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
});
|
||||
|
||||
it('should step backward', () => {
|
||||
onboardingSlides.find('.back-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(0);
|
||||
test('should not have basic a11y violations', async () => {
|
||||
const results = await axe(onboardingSlides.toString());
|
||||
|
||||
expect(results).toHaveNoViolations();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -193,7 +140,7 @@ describe('<Onboarding />', () => {
|
|||
document.body.appendChild(meta);
|
||||
|
||||
beforeEach(() => {
|
||||
onboardingSlides = initializeSlides(2, getUserData());
|
||||
onboardingSlides = initializeSlides(1, getUserData());
|
||||
});
|
||||
|
||||
test('renders properly', () => {
|
||||
|
|
@ -231,12 +178,12 @@ describe('<Onboarding />', () => {
|
|||
profileForm.find('.next-button').simulate('click');
|
||||
fetch.once(fakeTagsResponse);
|
||||
await flushPromises();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(3);
|
||||
expect(onboardingSlides.state().currentSlide).toBe(2);
|
||||
});
|
||||
|
||||
it('should step backward', () => {
|
||||
onboardingSlides.find('.back-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
expect(onboardingSlides.state().currentSlide).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -244,7 +191,7 @@ describe('<Onboarding />', () => {
|
|||
let onboardingSlides;
|
||||
|
||||
beforeEach(async () => {
|
||||
onboardingSlides = initializeSlides(3, getUserData(), fakeTagsResponse);
|
||||
onboardingSlides = initializeSlides(2, getUserData(), fakeTagsResponse);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
|
|
@ -256,7 +203,7 @@ describe('<Onboarding />', () => {
|
|||
expect(onboardingSlides.find('.onboarding-tags__item').length).toBe(3);
|
||||
});
|
||||
|
||||
test('should allow a user to add a tag', async () => {
|
||||
test('should allow a user to add a tag and advance', async () => {
|
||||
fetch.once({});
|
||||
const followTags = onboardingSlides.find(<FollowTags />);
|
||||
const firstButton = onboardingSlides
|
||||
|
|
@ -269,12 +216,12 @@ describe('<Onboarding />', () => {
|
|||
onboardingSlides.find('.next-button').simulate('click');
|
||||
fetch.once(fakeUsersResponse);
|
||||
await flushPromises();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(4);
|
||||
expect(onboardingSlides.state().currentSlide).toBe(3);
|
||||
});
|
||||
|
||||
it('should step backward', () => {
|
||||
onboardingSlides.find('.back-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(2);
|
||||
expect(onboardingSlides.state().currentSlide).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -282,7 +229,7 @@ describe('<Onboarding />', () => {
|
|||
let onboardingSlides;
|
||||
|
||||
beforeEach(async () => {
|
||||
onboardingSlides = initializeSlides(4, getUserData(), fakeUsersResponse);
|
||||
onboardingSlides = initializeSlides(3, getUserData(), fakeUsersResponse);
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
|
|
@ -309,7 +256,7 @@ describe('<Onboarding />', () => {
|
|||
expect(followUsers.state('selectedUsers').length).toBe(2);
|
||||
onboardingSlides.find('.next-button').simulate('click');
|
||||
await flushPromises();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(5);
|
||||
expect(onboardingSlides.state().currentSlide).toBe(4);
|
||||
});
|
||||
|
||||
test('should have a functioning select-all toggle', async () => {
|
||||
|
|
@ -330,11 +277,36 @@ describe('<Onboarding />', () => {
|
|||
fetch.once(fakeTagsResponse);
|
||||
onboardingSlides.find('.back-button').simulate('click');
|
||||
await flushPromises();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('EmailPreferencesForm', () => {
|
||||
let onboardingSlides;
|
||||
|
||||
beforeEach(() => {
|
||||
onboardingSlides = initializeSlides(4, getUserData());
|
||||
});
|
||||
|
||||
test('renders properly', () => {
|
||||
expect(onboardingSlides).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should allow user to advance', async () => {
|
||||
fetch.once({});
|
||||
|
||||
onboardingSlides.find('.next-button').simulate('click');
|
||||
await flushPromises();
|
||||
expect(onboardingSlides.state().currentSlide).toBe(5);
|
||||
});
|
||||
|
||||
it('should step backward', () => {
|
||||
onboardingSlides.find('.back-button').simulate('click');
|
||||
expect(onboardingSlides.state().currentSlide).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CloseSlide', () => {
|
||||
describe('ClosingSlide', () => {
|
||||
let onboardingSlides;
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Onboarding /> CloseSlide renders properly 1`] = `
|
||||
exports[`<Onboarding /> ClosingSlide renders properly 1`] = `
|
||||
preact-render-spy (1 nodes)
|
||||
-------
|
||||
<main class="onboarding-body">
|
||||
|
|
@ -77,7 +77,7 @@ preact-render-spy (1 nodes)
|
|||
|
||||
`;
|
||||
|
||||
exports[`<Onboarding /> EmailTermsConditionsForm renders properly 1`] = `
|
||||
exports[`<Onboarding /> EmailPreferencesForm renders properly 1`] = `
|
||||
preact-render-spy (1 nodes)
|
||||
-------
|
||||
<main class="onboarding-body">
|
||||
|
|
@ -102,53 +102,12 @@ preact-render-spy (1 nodes)
|
|||
</nav>
|
||||
<div class="onboarding-content terms-and-conditions-wrapper">
|
||||
<header class="onboarding-content-header">
|
||||
<h1 class="title">Getting started</h1>
|
||||
<h2 class="subtitle">Let's review a few things first</h2>
|
||||
<h1 class="title">Almost there!</h1>
|
||||
<h2 class="subtitle">
|
||||
Review your email preferences before we continue.
|
||||
</h2>
|
||||
</header>
|
||||
<form>
|
||||
<fieldset>
|
||||
<legend>Some things to check off</legend>
|
||||
<ul>
|
||||
<li class="checkbox-item">
|
||||
<label htmlFor="checked_code_of_conduct">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_code_of_conduct"
|
||||
name="checked_code_of_conduct"
|
||||
checked={false}
|
||||
onChange={[Function bound handleChange]}
|
||||
/>
|
||||
I agree to uphold the
|
||||
<a
|
||||
href="/code-of-conduct"
|
||||
data-no-instant={true}
|
||||
onClick={[Function onClick]}
|
||||
>
|
||||
Code of Conduct
|
||||
</a>
|
||||
</label>
|
||||
</li>
|
||||
<li class="checkbox-item">
|
||||
<label htmlFor="checked_terms_and_conditions">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_terms_and_conditions"
|
||||
name="checked_terms_and_conditions"
|
||||
checked={false}
|
||||
onChange={[Function bound handleChange]}
|
||||
/>
|
||||
I agree to our
|
||||
<a
|
||||
href="/terms"
|
||||
data-no-instant={true}
|
||||
onClick={[Function onClick]}
|
||||
>
|
||||
Terms and Conditions
|
||||
</a>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Email preferences</legend>
|
||||
<ul>
|
||||
|
|
@ -173,7 +132,7 @@ preact-render-spy (1 nodes)
|
|||
checked={true}
|
||||
onChange={[Function bound handleChange]}
|
||||
/>
|
||||
I want to receive a periodic digest with some of the top posts from your tags.
|
||||
I want to receive a periodic digest of top posts from my tags.
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
@ -413,17 +372,66 @@ preact-render-spy (1 nodes)
|
|||
DEV is where programmers share ideas and help each other grow.
|
||||
</h2>
|
||||
</div>
|
||||
<nav class="onboarding-navigation">
|
||||
<div class="navigation-content">
|
||||
<button
|
||||
onClick={[Function bound onSubmit]}
|
||||
class="next-button"
|
||||
type="button"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="checkbox-form-wrapper">
|
||||
<form class="checkbox-form">
|
||||
<fieldset>
|
||||
<ul>
|
||||
<li class="checkbox-item">
|
||||
<label htmlFor="checked_code_of_conduct">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_code_of_conduct"
|
||||
name="checked_code_of_conduct"
|
||||
checked={false}
|
||||
onChange={[Function bound handleChange]}
|
||||
/>
|
||||
You agree to uphold our
|
||||
<a
|
||||
href="/code-of-conduct"
|
||||
data-no-instant={true}
|
||||
onClick={[Function onClick]}
|
||||
>
|
||||
Code of Conduct
|
||||
</a>
|
||||
.
|
||||
</label>
|
||||
</li>
|
||||
<li class="checkbox-item">
|
||||
<label htmlFor="checked_terms_and_conditions">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_terms_and_conditions"
|
||||
name="checked_terms_and_conditions"
|
||||
checked={false}
|
||||
onChange={[Function bound handleChange]}
|
||||
/>
|
||||
You agree to our
|
||||
<a
|
||||
href="/terms"
|
||||
data-no-instant={true}
|
||||
onClick={[Function onClick]}
|
||||
>
|
||||
Terms and Conditions
|
||||
</a>
|
||||
.
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</form>
|
||||
<nav class="onboarding-navigation intro-slide">
|
||||
<div class="navigation-content intro-slide">
|
||||
<button
|
||||
disabled={true}
|
||||
onClick={[Function bound onSubmit]}
|
||||
class="next-button"
|
||||
type="button"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
/* eslint camelcase: "off" */
|
||||
|
||||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Navigation from './Navigation';
|
||||
import { getContentOfToken, updateOnboarding } from '../utilities';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
class EmailTermsConditionsForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.checkRequirements = this.checkRequirements.bind(this);
|
||||
|
||||
this.state = {
|
||||
checked_code_of_conduct: false,
|
||||
checked_terms_and_conditions: false,
|
||||
email_newsletter: true,
|
||||
email_digest_periodic: true,
|
||||
message: '',
|
||||
textShowing: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
updateOnboarding('emails, COC and T&C form');
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (!this.checkRequirements()) return;
|
||||
const csrfToken = getContentOfToken('csrf-token');
|
||||
|
||||
fetch('/onboarding_checkbox_update', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ user: this.state }),
|
||||
credentials: 'same-origin',
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
localStorage.setItem('shouldRedirectToOnboarding', false);
|
||||
const { next } = this.props;
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkRequirements() {
|
||||
const {
|
||||
checked_code_of_conduct,
|
||||
checked_terms_and_conditions,
|
||||
} = this.state;
|
||||
if (!checked_code_of_conduct) {
|
||||
this.setState({
|
||||
message: 'You must agree to our Code of Conduct before continuing.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!checked_terms_and_conditions) {
|
||||
this.setState({
|
||||
message:
|
||||
'You must agree to our Terms and Conditions before continuing.',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
const { name } = event.target;
|
||||
this.setState((currentState) => ({
|
||||
[name]: !currentState[name],
|
||||
}));
|
||||
}
|
||||
|
||||
handleShowText(event, id) {
|
||||
event.preventDefault();
|
||||
this.setState({ textShowing: document.getElementById(id).innerHTML });
|
||||
}
|
||||
|
||||
backToSlide() {
|
||||
this.setState({ textShowing: null });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
message,
|
||||
checked_code_of_conduct,
|
||||
checked_terms_and_conditions,
|
||||
email_newsletter,
|
||||
email_digest_periodic,
|
||||
textShowing,
|
||||
} = this.state;
|
||||
const { prev } = this.props;
|
||||
if (textShowing) {
|
||||
return (
|
||||
<div className="onboarding-main">
|
||||
<div className="onboarding-content terms-and-conditions-wrapper">
|
||||
<button type="button" onClick={() => this.backToSlide()}>
|
||||
Back
|
||||
</button>
|
||||
<div
|
||||
className="terms-and-conditions-content"
|
||||
/* eslint-disable react/no-danger */
|
||||
dangerouslySetInnerHTML={{ __html: textShowing }}
|
||||
/* eslint-enable react/no-danger */
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="onboarding-main">
|
||||
<Navigation prev={prev} next={this.onSubmit} />
|
||||
<div className="onboarding-content terms-and-conditions-wrapper">
|
||||
<header className="onboarding-content-header">
|
||||
<h1 className="title">Getting started</h1>
|
||||
<h2 className="subtitle">Let's review a few things first</h2>
|
||||
</header>
|
||||
|
||||
{message && (
|
||||
<span className="crayons-notice crayons-notice--danger">
|
||||
{message}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<form>
|
||||
<fieldset>
|
||||
<legend>Some things to check off</legend>
|
||||
<ul>
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="checked_code_of_conduct">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_code_of_conduct"
|
||||
name="checked_code_of_conduct"
|
||||
checked={checked_code_of_conduct}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
I agree to uphold the
|
||||
{' '}
|
||||
<a
|
||||
href="/code-of-conduct"
|
||||
data-no-instant
|
||||
onClick={(e) => this.handleShowText(e, 'coc')}
|
||||
>
|
||||
Code of Conduct
|
||||
</a>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="checked_terms_and_conditions">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_terms_and_conditions"
|
||||
name="checked_terms_and_conditions"
|
||||
checked={checked_terms_and_conditions}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
I agree to our
|
||||
{' '}
|
||||
<a
|
||||
href="/terms"
|
||||
data-no-instant
|
||||
onClick={(e) => this.handleShowText(e, 'terms')}
|
||||
>
|
||||
Terms and Conditions
|
||||
</a>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Email preferences</legend>
|
||||
<ul>
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="email_newsletter">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="email_newsletter"
|
||||
name="email_newsletter"
|
||||
checked={email_newsletter}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
I want to receive weekly newsletter emails.
|
||||
</label>
|
||||
</li>
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="email_digest_periodic">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="email_digest_periodic"
|
||||
name="email_digest_periodic"
|
||||
checked={email_digest_periodic}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
I want to receive a periodic digest with some of the top
|
||||
posts from your tags.
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EmailTermsConditionsForm.propTypes = {
|
||||
prev: PropTypes.func.isRequired,
|
||||
next: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default EmailTermsConditionsForm;
|
||||
111
app/javascript/onboarding/components/EmailPreferencesForm.jsx
Normal file
111
app/javascript/onboarding/components/EmailPreferencesForm.jsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { h, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Navigation from './Navigation';
|
||||
import { getContentOfToken, updateOnboarding } from '../utilities';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
class EmailPreferencesForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
|
||||
this.state = {
|
||||
email_newsletter: true,
|
||||
email_digest_periodic: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
updateOnboarding('v2: email preferences form');
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const csrfToken = getContentOfToken('csrf-token');
|
||||
|
||||
fetch('/onboarding_checkbox_update', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ user: this.state }),
|
||||
credentials: 'same-origin',
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
localStorage.setItem('shouldRedirectToOnboarding', false);
|
||||
const { next } = this.props;
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
const { name } = event.target;
|
||||
this.setState((currentState) => ({
|
||||
[name]: !currentState[name],
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { email_newsletter, email_digest_periodic } = this.state;
|
||||
const { prev } = this.props;
|
||||
return (
|
||||
<div className="onboarding-main">
|
||||
<Navigation prev={prev} next={this.onSubmit} />
|
||||
<div className="onboarding-content terms-and-conditions-wrapper">
|
||||
<header className="onboarding-content-header">
|
||||
<h1 className="title">Almost there!</h1>
|
||||
<h2 className="subtitle">
|
||||
Review your email preferences before we continue.
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<form>
|
||||
<fieldset>
|
||||
<legend>Email preferences</legend>
|
||||
<ul>
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="email_newsletter">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="email_newsletter"
|
||||
name="email_newsletter"
|
||||
checked={email_newsletter}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
I want to receive weekly newsletter emails.
|
||||
</label>
|
||||
</li>
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="email_digest_periodic">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="email_digest_periodic"
|
||||
name="email_digest_periodic"
|
||||
checked={email_digest_periodic}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
I want to receive a periodic digest of top posts from my
|
||||
tags.
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EmailPreferencesForm.propTypes = {
|
||||
prev: PropTypes.func.isRequired,
|
||||
next: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default EmailPreferencesForm;
|
||||
|
||||
/* eslint-enable camelcase */
|
||||
|
|
@ -32,7 +32,7 @@ class FollowTags extends Component {
|
|||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user: { last_onboarding_page: 'follow tags page' },
|
||||
user: { last_onboarding_page: 'v2: follow tags page' },
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class FollowUsers extends Component {
|
|||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user: { last_onboarding_page: 'follow users page' },
|
||||
user: { last_onboarding_page: 'v2: follow users page' },
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,27 +2,94 @@ import { h, Component } from 'preact';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import Navigation from './Navigation';
|
||||
import { userData, updateOnboarding } from '../utilities';
|
||||
import { getContentOfToken, userData, updateOnboarding } from '../utilities';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
class IntroSlide extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.user = userData();
|
||||
|
||||
this.state = {
|
||||
checked_code_of_conduct: false,
|
||||
checked_terms_and_conditions: false,
|
||||
text: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
updateOnboarding('intro slide');
|
||||
updateOnboarding('v2: intro, code of conduct, terms & conditions');
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const { next } = this.props;
|
||||
next();
|
||||
const csrfToken = getContentOfToken('csrf-token');
|
||||
|
||||
fetch('/onboarding_checkbox_update', {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ user: this.state }),
|
||||
credentials: 'same-origin',
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
localStorage.setItem('shouldRedirectToOnboarding', false);
|
||||
next();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
const { name } = event.target;
|
||||
this.setState((currentState) => ({
|
||||
[name]: !currentState[name],
|
||||
}));
|
||||
}
|
||||
|
||||
handleShowText(event, id) {
|
||||
event.preventDefault();
|
||||
this.setState({ text: document.getElementById(id).innerHTML });
|
||||
}
|
||||
|
||||
isButtonDisabled() {
|
||||
const {
|
||||
checked_code_of_conduct,
|
||||
checked_terms_and_conditions,
|
||||
} = this.state;
|
||||
|
||||
return !checked_code_of_conduct || !checked_terms_and_conditions;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { prev } = this.props;
|
||||
const {
|
||||
checked_code_of_conduct,
|
||||
checked_terms_and_conditions,
|
||||
text,
|
||||
} = this.state;
|
||||
|
||||
if (text) {
|
||||
return (
|
||||
<div className="onboarding-main">
|
||||
<div className="onboarding-content terms-and-conditions-wrapper">
|
||||
<button type="button" onClick={() => this.setState({ text: null })}>
|
||||
Back
|
||||
</button>
|
||||
<div
|
||||
className="terms-and-conditions-content"
|
||||
/* eslint-disable react/no-danger */
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
/* eslint-enable react/no-danger */
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="onboarding-main introduction">
|
||||
|
|
@ -43,7 +110,65 @@ class IntroSlide extends Component {
|
|||
DEV is where programmers share ideas and help each other grow.
|
||||
</h2>
|
||||
</div>
|
||||
<Navigation prev={prev} next={this.onSubmit} hidePrev />
|
||||
|
||||
<div className="checkbox-form-wrapper">
|
||||
<form className="checkbox-form">
|
||||
<fieldset>
|
||||
<ul>
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="checked_code_of_conduct">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_code_of_conduct"
|
||||
name="checked_code_of_conduct"
|
||||
checked={checked_code_of_conduct}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
You agree to uphold our
|
||||
{' '}
|
||||
<a
|
||||
href="/code-of-conduct"
|
||||
data-no-instant
|
||||
onClick={(e) => this.handleShowText(e, 'coc')}
|
||||
>
|
||||
Code of Conduct
|
||||
</a>
|
||||
.
|
||||
</label>
|
||||
</li>
|
||||
|
||||
<li className="checkbox-item">
|
||||
<label htmlFor="checked_terms_and_conditions">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="checked_terms_and_conditions"
|
||||
name="checked_terms_and_conditions"
|
||||
checked={checked_terms_and_conditions}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
You agree to our
|
||||
{' '}
|
||||
<a
|
||||
href="/terms"
|
||||
data-no-instant
|
||||
onClick={(e) => this.handleShowText(e, 'terms')}
|
||||
>
|
||||
Terms and Conditions
|
||||
</a>
|
||||
.
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</fieldset>
|
||||
</form>
|
||||
<Navigation
|
||||
disabled={this.isButtonDisabled()}
|
||||
className="intro-slide"
|
||||
prev={prev}
|
||||
next={this.onSubmit}
|
||||
hidePrev
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -55,3 +180,5 @@ IntroSlide.propTypes = {
|
|||
};
|
||||
|
||||
export default IntroSlide;
|
||||
|
||||
/* eslint-enable camelcase */
|
||||
|
|
|
|||
|
|
@ -1,16 +1,36 @@
|
|||
import { h } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Navigation = ({ next, prev, hideNext, hidePrev }) => (
|
||||
<nav className="onboarding-navigation">
|
||||
<div className="navigation-content">
|
||||
const Navigation = ({
|
||||
next,
|
||||
prev,
|
||||
hideNext,
|
||||
hidePrev,
|
||||
disabled,
|
||||
className,
|
||||
}) => (
|
||||
<nav
|
||||
className={`onboarding-navigation${
|
||||
className && className.length > 0 ? ` ${className}` : ''
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`navigation-content${
|
||||
className && className.length > 0 ? ` ${className}` : ''
|
||||
}`}
|
||||
>
|
||||
{!hidePrev && (
|
||||
<button onClick={prev} className="back-button" type="button">
|
||||
Back
|
||||
</button>
|
||||
)}
|
||||
{!hideNext && (
|
||||
<button onClick={next} className="next-button" type="button">
|
||||
<button
|
||||
disabled={disabled}
|
||||
onClick={next}
|
||||
className="next-button"
|
||||
type="button"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
)}
|
||||
|
|
@ -19,6 +39,8 @@ const Navigation = ({ next, prev, hideNext, hidePrev }) => (
|
|||
);
|
||||
|
||||
Navigation.propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
prev: PropTypes.func.isRequired,
|
||||
next: PropTypes.string.isRequired,
|
||||
hideNext: PropTypes.bool,
|
||||
|
|
@ -26,8 +48,10 @@ Navigation.propTypes = {
|
|||
};
|
||||
|
||||
Navigation.defaultProps = {
|
||||
disabled: false,
|
||||
hideNext: false,
|
||||
hidePrev: false,
|
||||
className: '',
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|||
import Navigation from './Navigation';
|
||||
import { userData, getContentOfToken, updateOnboarding } from '../utilities';
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
class ProfileForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -40,7 +41,7 @@ class ProfileForm extends Component {
|
|||
const { next } = this.props;
|
||||
next();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
|
|
@ -68,7 +69,11 @@ class ProfileForm extends Component {
|
|||
</header>
|
||||
<div className="current-user-info">
|
||||
<figure className="current-user-avatar-container">
|
||||
<img className="current-user-avatar" alt="profile" src={profile_image_90} />
|
||||
<img
|
||||
className="current-user-avatar"
|
||||
alt="profile"
|
||||
src={profile_image_90}
|
||||
/>
|
||||
</figure>
|
||||
<h3>{name}</h3>
|
||||
<p>{username}</p>
|
||||
|
|
@ -131,3 +136,5 @@ ProfileForm.propTypes = {
|
|||
};
|
||||
|
||||
export default ProfileForm;
|
||||
|
||||
/* eslint-enable camelcase */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue