Remove unused onboarding files (#4254)

* Remove commented code

* Format onboarding SCSS

* Remove old onboarding files

* Remove tests for deleted React components

* Remove outdated onboarding stories
This commit is contained in:
Jacob Herrington 2019-10-14 08:35:37 -05:00 committed by Ben Halpern
parent 691faf78ac
commit 742d50f524
34 changed files with 52 additions and 3464 deletions

View file

@ -1,7 +1,9 @@
@import 'variables';
#onboarding-container {
min-height: calc(100vh - 350px);
}
.onboarding-container {
max-width: 800px;
margin: 0 auto;
@ -40,4 +42,4 @@
input[type='checkbox'] {
display: inline;
}
}
}

View file

@ -24,12 +24,15 @@
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
background-position: center; /* Center the image */
background-repeat: no-repeat; /* Do not repeat the image */
/* Center the image */
background-position: center;
/* Do not repeat the image */
background-repeat: no-repeat;
a {
@include themeable(color, theme-anchor-color, #2e3b9e);
}
.sticker-logo {
height: 80px;
width: 80px;
@ -39,6 +42,7 @@
top: -10px;
padding-top: 5px;
}
.onboarding-main {
display: flex;
flex-direction: column;
@ -61,6 +65,7 @@
width: 100%;
height: 440px;
margin-top: 5px;
h1 {
margin-top: 15px;
line-height: 1.1em;
@ -83,12 +88,15 @@
h1 {
text-align: center;
}
h2 {
font-size: 15px;
@media screen and (min-width: 600px) {
font-size: 26px;
}
}
p {
line-height: 1.25em;
}
@ -122,6 +130,7 @@
.about {
width: 100%;
label,
input,
textarea {
@ -161,6 +170,7 @@
margin: 8px;
cursor: pointer;
}
input[type='checkbox']:checked {
@include themeable(background, theme-anchor-color, #66e2d5);
}
@ -200,15 +210,19 @@
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol';
@media screen and (min-width: 600px) {
width: 255px;
}
&.tag-selected {
opacity: 1;
}
&:hover {
opacity: 1;
}
.onboarding-tag-follow-indicator {
position: absolute;
bottom: 22px;
@ -221,6 +235,7 @@
.select-all-button-wrapper {
width: 100%;
button {
width: 300px;
line-height: 1em;
@ -243,6 +258,7 @@
margin-right: 5px;
position: relative;
color: $black;
.onboarding-user-follow-status {
position: absolute;
color: #32a99c;
@ -250,17 +266,21 @@
top: 5px;
font-size: 0.88em;
}
span {
display: block;
width: 100%;
}
p {
font-weight: 400;
height: 55px;
}
@media screen and (min-width: 600px) {
width: 365px;
}
img {
height: 80px;
width: 80px;
@ -270,9 +290,11 @@
display: block;
}
}
.user:active {
background-color: red;
}
.badge-image {
position: absolute;
}
@ -282,9 +304,11 @@
flex-direction: column;
height: 250px;
overflow: auto;
@media screen and (min-width: 600px) {
flex-direction: row;
}
a {
background: #d9e7ff;
padding: 15px 4px 0px 12px;
@ -297,12 +321,14 @@
font-weight: bold;
position: relative;
text-align: center;
@media screen and (min-width: 600px) {
height: 220px;
margin: 4px 4px;
text-align: left;
padding: 20px 4px 0px 12px;
}
}
.whatnext-emoji {
display: none;
text-align: center;
@ -311,20 +337,24 @@
left: 0;
right: 0;
font-size: 50px;
@media screen and (min-width: 600px) {
display: block;
}
}
}
}
.onboarding-previous-location {
text-align: center;
margin-top: 10px;
font-weight: bold;
display: none;
@media screen and (min-width: 600px) {
display: block;
}
code {
font-size: 0.6em;
margin-top: -10px;
@ -332,14 +362,17 @@
font-weight: 400;
}
}
@media screen and (max-width: 800px) {
.onboarding-main {
width: 100%;
height: 100%;
background-image: url('/assets/mobile-onboarding-background.png');
background-size: cover;
background-position: center; /* Center the image */
background-repeat: no-repeat; /* Do not repeat the image */
/* Center the image */
background-position: center;
/* Do not repeat the image */
background-repeat: no-repeat;
border: none;
color: $black;
}
@ -347,7 +380,8 @@
.onboarding-content {
flex: 0 0 auto;
h1, h2 {
h1,
h2 {
margin-top: initial;
}
}
@ -356,7 +390,7 @@
display: block;
}
}
@media screen and (max-width: 650px) {
.onboarding-main {
font-size: 18px;
@ -394,4 +428,4 @@
font-size: 20px;
}
}
}
}

View file

@ -1,475 +0,0 @@
import { h, Component } from 'preact';
import OnboardingWelcome from './components/OnboardingWelcome';
import OnboardingFollowTags from './components/OnboardingFollowTags';
import OnboardingFollowUsers from './components/OnboardingFollowUsers';
import OnboardingWelcomeThread from './components/OnboardingWelcomeThread';
import cancelSvg from '../../assets/images/cancel.svg';
import OnboardingProfile from './components/OnboardingProfile';
const getContentOfToken = token =>
document.querySelector(`meta[name='${token}']`).content;
const getFormDataAndAppend = array => {
const form = new FormData();
array.forEach(item => form.append(item.key, item.value));
return form;
};
class Onboarding extends Component {
constructor() {
super();
this.handleNextButton = this.handleNextButton.bind(this);
this.handleBackButton = this.handleBackButton.bind(this);
this.closeOnboarding = this.closeOnboarding.bind(this);
this.handleFollowTag = this.handleFollowTag.bind(this);
this.handleNextHover = this.handleNextHover.bind(this);
this.updateUserData = this.updateUserData.bind(this);
this.getUserTags = this.getUserTags.bind(this);
this.handleCheckAllUsers = this.handleCheckAllUsers.bind(this);
this.handleCheckUser = this.handleCheckUser.bind(this);
this.handleSaveAllArticles = this.handleSaveAllArticles.bind(this);
this.handleSaveArticle = this.handleSaveArticle.bind(this);
this.handleProfileChange = this.handleProfileChange.bind(this);
this.getUsersToFollow = this.getUsersToFollow.bind(this);
this.state = {
pageNumber: 1,
showOnboarding: false,
userData: null,
allTags: [],
users: [],
checkedUsers: [],
followRequestSent: false,
articles: [],
savedArticles: [],
profileInfo: {},
};
}
componentDidMount() {
this.updateUserData();
this.getUserTags();
document.getElementsByTagName('body')[0].classList.add('modal-open');
}
getUserTags() {
fetch('/api/tags/onboarding')
.then(response => response.json())
.then(json => {
const followedTagNames = JSON.parse(
document.body.getAttribute('data-user'),
).followed_tag_names;
function checkFollowingStatus(followedTags, jsonTags) {
if (!followedTags || !followedTags.length) return jsonTags;
const newJSON = jsonTags;
jsonTags.map((tag, index) => {
if (followedTags.includes(tag.name)) {
newJSON[index].following = true;
} else {
newJSON[index].following = false;
}
return newJSON;
});
return newJSON;
}
const updatedJSON = checkFollowingStatus(followedTagNames, json);
this.setState({ allTags: updatedJSON });
})
.catch(error => {
console.log(error);
});
}
getUsersToFollow() {
const { users } = this.state;
fetch('/api/users?state=follow_suggestions', {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
credentials: 'same-origin',
})
.then(response => response.json())
.then(json => {
if (users.length === 0) {
this.setState({ users: json, checkedUsers: json });
}
})
.catch(error => {
console.log(error);
});
}
handleBulkFollowUsers(users) {
const { checkedUsers, followRequestSent } = this.state;
if (checkedUsers.length > 0 && !followRequestSent) {
const csrfToken = getContentOfToken('csrf-token');
const formData = getFormDataAndAppend([
{ key: 'users', value: JSON.stringify(users) },
]);
fetch('/api/follows', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
},
body: formData,
credentials: 'same-origin',
}).then(response => {
if (response.ok) {
this.setState({ followRequestSent: true });
}
});
}
}
handleUserProfileSave() {
const csrfToken = getContentOfToken('csrf-token');
const { profileInfo } = this.state;
const formData = getFormDataAndAppend([
{ key: 'user', value: JSON.stringify(profileInfo) },
]);
fetch('/onboarding_update', {
method: 'PATCH',
headers: {
'X-CSRF-Token': csrfToken,
},
body: formData,
credentials: 'same-origin',
});
}
updateUserData() {
const userData = JSON.parse(document.body.getAttribute('data-user'));
const { saw_onboarding: sawOnboarding } = userData;
this.setState({
userData,
showOnboarding: !sawOnboarding,
});
}
handleFollowTag(tag) {
const csrfToken = getContentOfToken('csrf-token');
const formData = getFormDataAndAppend([
{ key: 'followable_type', value: 'Tag' },
{ key: 'followable_id', value: tag.id },
{ key: 'verb', value: tag.following ? 'unfollow' : 'follow' },
]);
this.setState(prevState => ({
allTags: prevState.allTags.map(currentTag => {
const newTag = currentTag;
if (currentTag.name === tag.name) {
newTag.following = true;
}
return newTag;
}),
}));
fetch('/follows', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
},
body: formData,
credentials: 'same-origin',
})
.then(response =>
response.json().then(json => {
this.setState(prevState => ({
allTags: prevState.allTags.map(currentTag => {
const newTag = currentTag;
if (currentTag.name === tag.name) {
newTag.following = json.outcome === 'followed';
}
return newTag;
// add in optimistic rendering
}),
}));
}),
)
.catch(error => {
console.log(error);
});
}
handleCheckAllUsers() {
const { users, checkedUsers: prevCheckedUsers } = this.state;
let checkedUsers = [];
if (prevCheckedUsers.length < users.length) {
checkedUsers = users.slice();
}
this.setState({ checkedUsers });
}
handleProfileChange(event) {
const { name, value } = event.target;
this.setState(prevState => ({
profileInfo: {
...prevState.profileInfo,
[name]: value,
},
}));
}
handleCheckUser(user) {
const { checkedUsers } = this.state;
const newCheckedUsers = checkedUsers.slice();
const index = checkedUsers.indexOf(user);
if (index > -1) {
newCheckedUsers.splice(index, 1);
} else {
newCheckedUsers.push(user);
}
this.setState({ checkedUsers: newCheckedUsers });
}
handleSaveAllArticles() {
const { savedArticles: prevSavedArticles, articles } = this.state;
let savedArticles = [];
if (prevSavedArticles.length < articles.length) {
savedArticles = articles.slice();
}
this.setState({ savedArticles });
}
handleSaveArticle(article) {
const { savedArticles } = this.state;
const newSavedArticles = savedArticles.slice();
const index = newSavedArticles.indexOf(article);
if (index > -1) {
newSavedArticles.splice(index, 1);
} else {
newSavedArticles.push(article);
}
this.setState({ savedArticles: newSavedArticles });
}
handleNextHover() {
const { pageNumber, users } = this.state;
if (pageNumber === 2 && users.length === 0) {
this.getUsersToFollow();
}
}
handleNextButton() {
const { users, articles, checkedUsers, profileInfo } = this.state;
let { pageNumber } = this.state;
if (pageNumber === 2 && users.length === 0 && articles.length === 0) {
this.getUsersToFollow();
}
if (pageNumber < 5) {
pageNumber += 1;
this.setState({ pageNumber });
if (pageNumber === 4 && checkedUsers.length > 0) {
this.handleBulkFollowUsers(checkedUsers);
} else if (pageNumber === 5) {
this.handleUserProfileSave(profileInfo);
}
} else if (pageNumber === 5) {
this.closeOnboarding();
}
}
handleBackButton() {
const { pageNumber } = this.state;
if (pageNumber > 1) {
this.setState({ pageNumber: pageNumber - 1 });
}
}
closeOnboarding() {
const { pageNumber } = this.state;
document.getElementsByTagName('body')[0].classList.remove('modal-open');
const csrfToken = getContentOfToken('csrf-token');
const formData = getFormDataAndAppend([
{ key: 'saw_onboarding', value: true },
]);
if (window.ga && ga.create) {
ga('send', 'event', 'click', 'close onboarding slide', pageNumber, null);
}
fetch('/onboarding_update', {
method: 'PATCH',
headers: {
'X-CSRF-Token': csrfToken,
},
body: formData,
credentials: 'same-origin',
})
.then(response => response.json())
.then(json => {
this.setState({ showOnboarding: json.outcome === 'onboarding opened' });
// console.log('this is special')
// console.log(this.state)
})
.catch(error => {
console.log(error);
});
}
toggleOnboardingSlide() {
const ONBOARDING = {
WELCOME_SCREEN: 1,
FOLLOW_TAG_SCREEN: 2,
FOLLOW_USERS_SCREEN: 3,
PROFILE_SCREEN: 4,
WELCOME_THREAD: 5,
};
const {
pageNumber,
userData,
allTags,
followedTags,
users,
checkedUsers,
} = this.state;
switch (pageNumber) {
case ONBOARDING.WELCOME_SCREEN:
return <OnboardingWelcome />;
case ONBOARDING.FOLLOW_TAG_SCREEN:
return (
<OnboardingFollowTags
userData={userData}
allTags={allTags}
followedTags={followedTags}
handleFollowTag={this.handleFollowTag}
/>
);
case ONBOARDING.FOLLOW_USERS_SCREEN:
return (
<OnboardingFollowUsers
users={users}
checkedUsers={checkedUsers}
handleCheckUser={this.handleCheckUser}
handleCheckAllUsers={this.handleCheckAllUsers}
/>
);
case ONBOARDING.PROFILE_SCREEN:
return <OnboardingProfile onChange={this.handleProfileChange} />;
case ONBOARDING.WELCOME_THREAD:
return <OnboardingWelcomeThread />;
default:
return null;
}
}
renderCloseButton() {
const btnClassName = 'close-button';
return (
<button
className={btnClassName}
type="button"
onClick={this.closeOnboarding}
>
<img src={cancelSvg} alt="cancel button" />
</button>
);
}
renderBackButton() {
const { pageNumber } = this.state;
if (pageNumber > 1) {
return (
<button
className="button cta"
type="button"
onClick={this.handleBackButton}
>
BACK
</button>
);
}
return null;
}
renderNextButton() {
const { pageNumber } = this.state;
return (
<button
className="button cta"
onClick={this.handleNextButton}
onMouseOver={this.handleNextHover}
onFocus={this.handleNextHover}
type="button"
>
{pageNumber < 5 ? 'NEXT' : "LET'S GO"}
</button>
);
}
renderPageIndicators() {
const { pageNumber } = this.state;
const firstIndicatorClassName =
pageNumber === 2
? 'pageindicator pageindicator--active'
: 'pageindicator';
const secondIndicatorClassName =
pageNumber === 3
? 'pageindicator pageindicator--active'
: 'pageindicator';
const thirdIndicatorClassName =
pageNumber === 4
? 'pageindicator pageindicator--active'
: 'pageindicator';
return (
<div className="pageindicators">
<div className={firstIndicatorClassName} />
<div className={secondIndicatorClassName} />
<div className={thirdIndicatorClassName} />
</div>
);
}
renderSloanMessage() {
const messages = {
1: 'WELCOME!',
2: 'FOLLOW TAGS',
3: 'FOLLOW DEVS',
4: 'CREATE YOUR PROFILE',
5: 'GET INVOLVED',
};
const { pageNumber } = this.state;
return messages[pageNumber];
}
render() {
const { showOnboarding } = this.state;
if (!showOnboarding) return null;
return (
<div className="global-modal" style={{ display: 'none' }}>
<div className="global-modal-bg">{this.renderCloseButton()}</div>
<div className="global-modal-inner">
<div className="modal-header">
<div className="triangle-isosceles">
{this.renderSloanMessage()}
</div>
</div>
<div className="modal-body">
<div id="sloan-mascot-onboarding-area" className="sloan-bar wiggle">
<img
src="https://res.cloudinary.com/practicaldev/image/fetch/s--iiubRINO--/c_imagga_scale,f_auto,fl_progressive,q_auto,w_300/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/sloan.png"
className="sloan-img"
alt="Sloan, the sloth mascot"
/>
</div>
<div className="body-message">{this.toggleOnboardingSlide()}</div>
</div>
<div className="modal-footer">
<div className="modal-footer-left">{this.renderBackButton()}</div>
<div className="modal-footer-center">
{this.renderPageIndicators()}
</div>
<div className="modal-footer-right">{this.renderNextButton()}</div>
</div>
</div>
</div>
);
}
}
export default Onboarding;

View file

@ -1,318 +0,0 @@
import { h } from 'preact';
import { shallow, deep } from 'preact-render-spy';
import fetch from 'jest-fetch-mock';
import Onboarding from '../Onboarding';
global.fetch = fetch;
function flushPromises() {
return new Promise(resolve => setImmediate(resolve));
}
describe('<Onboarding />', () => {
beforeEach(() => {
document.body.setAttribute('data-user', null);
fetch.resetMocks();
});
const fakeTagResponse = JSON.stringify([
{
bg_color_hex: '#000000',
id: 715,
name: 'discuss',
text_color_hex: '#ffffff',
},
{
bg_color_hex: '#f7df1e',
id: 6,
name: 'javascript',
text_color_hex: '#000000',
},
{
bg_color_hex: '#2a2566',
id: 630,
name: 'career',
text_color_hex: '#ffffff',
},
]);
const dataUser = JSON.stringify({
followed_tag_names: ['javascript'],
});
const fakeUsersToFollowResponse = JSON.stringify([
{
id: 1,
name: 'Ben Halpern',
profile_image_url: 'ben.jpg',
},
{
id: 2,
name: 'Krusty the Clown',
profile_image_url: 'clown.jpg',
},
{
id: 3,
name: 'dev.to staff',
profile_image_url: 'dev.jpg',
},
]);
describe('when user is not logged in', () => {
beforeEach(() => {
fetch.once(fakeTagResponse);
document.body.setAttribute(
'data-user',
JSON.stringify({ saw_onboarding: true }),
);
});
test('showOnboarding is false', () => {
const context = shallow(<Onboarding />);
expect(context.state('showOnboarding')).toEqual(false);
});
test('nothing should be rendered', async () => {
// TODO: dataUser should be {};
const context = shallow(<Onboarding />);
await flushPromises();
expect(context).toMatchSnapshot();
});
});
describe('Welcome page', () => {
let context;
beforeEach(async () => {
fetch.once(fakeTagResponse);
document.body.setAttribute('data-user', dataUser);
context = deep(<Onboarding />);
await flushPromises();
});
test('renders properly', () => {
expect(context).toMatchSnapshot();
});
test('NEXT button should work', () => {
context.find('.button').simulate('click');
expect(context.state('pageNumber')).toEqual(2);
expect(context).toMatchSnapshot();
});
// TODO: test closing button
});
describe('Follow tag page', () => {
let context;
const meta = document.createElement('meta');
meta.setAttribute('name', 'csrf-token');
document.body.appendChild(meta);
beforeEach(async () => {
fetch.once(fakeTagResponse);
document.body.setAttribute('data-user', dataUser);
context = deep(<Onboarding />);
context.setState({ pageNumber: 2 });
await flushPromises();
});
test('renders properly', () => {
expect(context).toMatchSnapshot();
});
test('BACK button works', () => {
context
.find('.button')
.at(0)
.simulate('click');
expect(context.state('pageNumber')).toEqual(1);
expect(context).toMatchSnapshot();
});
test('NEXT button works', async () => {
fetch.once(fakeUsersToFollowResponse);
context
.find('.button')
.at(1)
.simulate('click');
await flushPromises();
expect(context.state('pageNumber')).toEqual(3);
expect(context).toMatchSnapshot();
});
test('each tag can be clicked', async () => {
fetch.mockResponse(JSON.stringify({ outcome: 'followed' }));
context.find('.onboarding-tag-link').map(tag => tag.simulate('click'));
await flushPromises();
expect(context.state('allTags').map(tag => tag.following)).toEqual([
true,
true,
true,
]);
});
test('each tag can be clicked (in weird combinations)', async () => {
fetch.mockResponse(JSON.stringify({ outcome: 'followed' }));
context
.find('.onboarding-tag-link')
.at(1)
.simulate('click');
await flushPromises();
expect(context.state('allTags').map(tag => tag.following)).toEqual([
false,
true,
false,
]);
});
});
describe('Follow dev members page', () => {
let context;
beforeEach(async () => {
fetch.once(fakeTagResponse);
document.body.setAttribute('data-user', dataUser);
context = deep(<Onboarding />);
fetch.once(fakeUsersToFollowResponse);
context.setState({ pageNumber: 2 });
context
.find('.button')
.at(1)
.simulate('click');
await flushPromises();
});
test('renders properly', () => {
expect(context).toMatchSnapshot();
});
test('BACK button works', () => {
context
.find('.button')
.at(0)
.simulate('click');
expect(context.state('pageNumber')).toEqual(2);
expect(context).toMatchSnapshot();
});
test('NEXT button works', async () => {
fetch.once(JSON.stringify({}));
context
.find('.button')
.at(1)
.simulate('click');
await flushPromises();
expect(context.state('pageNumber')).toEqual(4);
expect(context).toMatchSnapshot();
});
});
describe('User info page', () => {
let context;
beforeEach(async () => {
fetch.once(fakeTagResponse);
document.body.setAttribute('data-user', dataUser);
context = deep(<Onboarding />);
context.setState({ pageNumber: 4 });
await flushPromises();
});
test('renders properly', () => {
expect(context).toMatchSnapshot();
});
test('NEXT button works', async () => {
fetch.once({});
context
.find('.button')
.at(1)
.simulate('click');
await flushPromises();
expect(context.state('pageNumber')).toEqual(5);
});
test('BACK button works', () => {
context
.find('.button')
.at(0)
.simulate('click');
expect(context.state('pageNumber')).toEqual(3);
});
test('forms can be filled and submitted', async () => {
fetch.once({});
const targets = [
'summary',
'location',
'employment_title',
'employer_name',
'mostly_work_with',
'currently_learning',
];
const findAndUpdate = target => {
// TODO: we shouldn't have to add name. This might have to do with preact-render-spy
const event = { target: { value: 'TEST', name: target } };
context.find(<input name={target} />).simulate('change', event);
};
targets.forEach(findAndUpdate);
context
.find('.button')
.at(1)
.simulate('click');
await flushPromises();
expect(context.state('pageNumber')).toEqual(5);
const idealResult = {};
targets.forEach(attr => {
idealResult[attr] = 'TEST';
});
expect(context.state('profileInfo')).toEqual(idealResult);
});
});
describe('Final page', () => {
let context;
beforeEach(async () => {
fetch.once(fakeTagResponse);
document.body.setAttribute('data-user', dataUser);
const meta = document.createElement('meta');
meta.setAttribute('name', 'csrf-token');
document.body.appendChild(meta);
context = deep(<Onboarding />);
context.setState({ pageNumber: 5, profileInfo: { summary: 'hi there' } });
await flushPromises();
});
it('renders properly', () => {
expect(context).toMatchSnapshot();
});
it('special next button exists and should reroute user', async () => {
fetch.once(JSON.stringify({ outcome: 'onboarding closed' }));
const next = context.find('.button').at(1);
expect(next.text()).toEqual("LET'S GO");
next.simulate('click');
await flushPromises();
expect(context.state('pageNumber')).toEqual(5);
expect(context).toMatchSnapshot();
});
it('sends proper update on close', async () => {
fetch.once(JSON.stringify({ outcome: 'onboarding closed' }));
context
.find('.button')
.at(1)
.simulate('click');
await flushPromises();
expect(context.state('showOnboarding')).toEqual(false);
});
it('BACK button works', () => {
context
.find('.button')
.at(0)
.simulate('click');
expect(context.state('pageNumber')).toEqual(4);
expect(context).toMatchSnapshot();
});
});
});

View file

@ -10,7 +10,8 @@ export const ItemListTags = ({ availableTags, selectedTags, onClick }) => {
data-no-instant
onClick={e => onClick(e, tag)}
>
#{tag}
#
{tag}
</a>
));
return <div className="tags">{tagsHTML}</div>;

View file

@ -1,61 +0,0 @@
import { h } from 'preact';
import PropTypes from 'prop-types';
import { userPropTypes } from './common-prop-types';
const MAX_ARTICLE_DESCRIPTION_LENGTH = 75;
export const ARTICLE_PROP_TYPE = {
user: userPropTypes,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
positive_reactions_count: PropTypes.number.isRequired,
comments_count: PropTypes.number.isRequired,
};
const OnboardingArticle = ({ article, isSaved, onSaveArticle }) => (
<div className="onboarding-user-list-row" key={article.user.id}>
<div className="onboarding-user-list-key article">
<h3>{article.title}</h3>
<img src={article.user.profile_image_url} alt={article.user.name} />
<span>{article.user.name}</span>
<p>
{article.description.length < MAX_ARTICLE_DESCRIPTION_LENGTH
? article.description
: `${article.description.substring(
0,
MAX_ARTICLE_DESCRIPTION_LENGTH,
)}...`}
</p>
<div className="onboarding-article-engagement">
<img
src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/reactions-stack-4bb9c1e4b3e71b7aa135d6f9a5ef29a6494141da882edd4fa971a77abe13dbe7.png"
alt="Reactions"
/>
{' '}
{article.positive_reactions_count}
<img
src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/comments-bubble-7448082accd39cfe9db9b977f38fa6e8f8d26dc43e142c5d160400d6f952ee47.png"
alt="Comments"
/>
{' '}
{article.comments_count}
</div>
</div>
<div className="onboarding-user-list-checkbox article">
<button
onClick={onSaveArticle}
className={`article save-single ${isSaved ? 'saved' : ''}`}
>
{isSaved ? 'SAVED' : 'SAVE'}
</button>
</div>
</div>
);
OnboardingArticle.propTypes = {
article: PropTypes.shape(ARTICLE_PROP_TYPE).isRequired,
isSaved: PropTypes.bool.isRequired,
onSaveArticle: PropTypes.func.isRequired,
};
export default OnboardingArticle;

View file

@ -1,64 +0,0 @@
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import OnboardingArticle, { ARTICLE_PROP_TYPE } from './OnboardingArticle';
class OnboardingArticles extends Component {
constructor(props) {
super(props);
this.handleAllClick = this.handleAllClick.bind(this);
}
handleAllClick() {
this.props.handleSaveAllArticles();
}
render() {
const { articles, savedArticles, handleSaveArticle } = this.props;
const onboardingArticles = articles.map(article => (
<OnboardingArticle
article={article}
isSaved={savedArticles.indexOf(article) > -1}
// eslint-disable-next-line react/jsx-no-bind
onSaveArticle={handleSaveArticle.bind(this, article)}
/>
));
const areAllSaved = savedArticles.length === articles.length;
return (
<div className="onboarding-user-container">
<div className="onboarding-user-cta">
When you see an interesting post, you can
{' '}
<strong className="purple">SAVE</strong>
{' '}
it. To get started, here are
pre-selected suggestions.
</div>
<div className="onboarding-user-list">
<div className="onboarding-user-list-header onboarding-user-list-row">
<div className="onboarding-user-list-key">Save All</div>
<div className="onboarding-user-list-checkbox">
<button
onClick={this.handleAllClick}
className={`article save-all ${areAllSaved ? 'saved' : ''}`}
>
{areAllSaved ? '✓' : '+'}
</button>
</div>
</div>
<div className="onboarding-user-list-body">{onboardingArticles}</div>
</div>
</div>
);
}
}
OnboardingArticles.propTypes = {
articles: PropTypes.arrayOf(PropTypes.shape(ARTICLE_PROP_TYPE)).isRequired,
savedArticles: PropTypes.arrayOf(PropTypes.shape(ARTICLE_PROP_TYPE))
.isRequired,
handleSaveArticle: PropTypes.func.isRequired,
handleSaveAllArticles: PropTypes.func.isRequired,
};
export default OnboardingArticles;

View file

@ -1,14 +0,0 @@
import { h } from 'preact';
import OnboardingTags from './OnboardingTags';
// component of page 2
const OnboardingFollowTags = ({ userData, allTags, followedTags, handleFollowTag }) => (
<OnboardingTags
userData={userData}
allTags={allTags}
followedTags={followedTags}
handleFollowTag={handleFollowTag}
/>
);
export default OnboardingFollowTags;

View file

@ -1,13 +0,0 @@
import { h } from 'preact';
import OnboardingUsers from './OnboardingUsers';
const OnboardingFollowUsers = ({ users, checkedUsers, handleCheckUser, handleCheckAllUsers }) => (
<OnboardingUsers
users={users}
checkedUsers={checkedUsers}
handleCheckUser={handleCheckUser}
handleCheckAllUsers={handleCheckAllUsers}
/>
);
export default OnboardingFollowUsers;

View file

@ -1,60 +0,0 @@
import { h, render, Component } from 'preact';
// page 1
const OnboardingProfile = ({ onChange }) => {
return (
<div className="onboarding-profile-page">
<div className="onboarding-user-cta">
Tell the us a bit about yourself
</div>
<div className="onboarding-profile-question">What's your quick bio?</div>
<input
name="summary"
placeholder="e.g. I'm a passionate hacker wizard unicorn ninja"
maxLength="120"
onChange={onChange}
/>
<div className="onboarding-profile-question">Where are you located?</div>
<input
name="location"
placeholder="e.g. New York City"
maxLength="60"
onChange={onChange}
/>
<div className="onboarding-profile-question">What is your title?</div>
<input
name="employment_title"
placeholder="e.g. Frontend developer"
maxLength="60"
onChange={onChange}
/>
<div className="onboarding-profile-question">Where do you work?</div>
<input
name="employer_name"
placeholder="e.g. Google"
maxLength="60"
onChange={onChange}
/>
<div className="onboarding-profile-question">
What are your core skills/languages?
</div>
<input
name="mostly_work_with"
placeholder="e.g. JavaScript and MongoDB"
maxLength="200"
onChange={onChange}
/>
<div className="onboarding-profile-question">
What are you currently learning/playing with?
</div>
<input
name="currently_learning"
placeholder="e.g. Rust and Docker"
maxLength="200"
onChange={onChange}
/>
</div>
);
};
export default OnboardingProfile;

View file

@ -1,54 +0,0 @@
import { h, Component } from 'preact';
import PropTypes from 'prop-types';
class OnboardingSingleTag extends Component {
propTypes = {
onTagClick: PropTypes.func.isRequired,
tag: PropTypes.objectOf().isRequired,
};
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { onTagClick, tag } = this.props;
onTagClick(tag);
}
render() {
const { tag } = this.props;
const backgroundColor = tag.following ? tag.bg_color_hex : '';
const textroundColor = tag.following ? tag.text_color_hex : '';
return (
<div
className={`onboarding-tag-container${
tag.following ? ' followed-tag' : ''
}`}
id={`onboarding-tag-container-${tag.name}`}
style={`background: ${backgroundColor}`}
>
<a
className="onboarding-tag-link"
href="#0"
style={`color:${textroundColor}`}
onClick={this.onClick}
>
#{tag.name}
</a>
<a
className="onboarding-tag-link-follow"
href="#0"
id={`onboarding-tag-link-follow-${tag.name}`}
style={`color:${textroundColor}`}
onClick={this.onClick}
>
{tag.following ? '✓' : '+'}
</a>
</div>
);
}
}
export default OnboardingSingleTag;

View file

@ -1,30 +0,0 @@
import { h } from 'preact';
import PropTypes from 'prop-types';
import OnboardingSingleTag from './OnboardingSingleTag';
// page 2
function OnboardingTags({ allTags, handleFollowTag }) {
const tags = allTags.map(tag => {
return (
<OnboardingSingleTag
key={tag.id}
tag={tag}
onTagClick={handleFollowTag.bind(this, tag)} // eslint-disable-line react/jsx-no-bind
/>
);
});
return (
<div className="tags-slide">
<div className="onboarding-user-cta">Personalize your home feed</div>
<div className="tags-col-container">{tags}</div>
</div>
);
}
OnboardingTags.propTypes = {
handleFollowTag: PropTypes.func.isRequired,
allTags: PropTypes.objectOf().isRequired,
};
export default OnboardingTags;

View file

@ -1,83 +0,0 @@
import { h, render, Component } from 'preact';
import PropTypes from 'prop-types';
class OnboardingUsers extends Component {
constructor(props) {
super(props);
this.handleAllClick = this.handleAllClick.bind(this);
}
handleAllClick() {
this.props.handleCheckAllUsers();
}
render() {
const followList = this.props.users.map(user => {
return (
<div className="onboarding-user-list-row" key={user.id}>
<div className="onboarding-user-list-key">
<img src={user.profile_image_url} alt={user.name} />
<div>{user.name}</div>
<div className="onboarding-user-list-row__summary">
<em>{user.summary}</em>
</div>
</div>
<div className="onboarding-user-list-checkbox">
<button
onClick={this.props.handleCheckUser.bind(this, user)}
className={
this.props.checkedUsers.indexOf(user) > -1 ? 'checked' : ''
}
>
{this.props.checkedUsers.indexOf(user) > -1 ? '✓' : '+'}
</button>
</div>
</div>
);
});
const renderLoadingOrList = () => {
if (this.props.users.length === 0) {
return <div className="onboarding-user-loading">Loading...</div>;
}
return followList;
};
return (
<div className="onboarding-user-container">
<div className="onboarding-user-cta">
Here are some suggestions based on your interests
</div>
<div className="onboarding-user-list">
<div className="onboarding-user-list-header">
<div className="onboarding-user-list-key">Follow All</div>
<div className="onboarding-user-list-checkbox">
<button
id="onboarding-user-follow-all-btn"
onClick={this.handleAllClick}
className={
this.props.checkedUsers.length === this.props.users.length
? 'checked'
: ''
}
>
{this.props.checkedUsers.length === this.props.users.length
? '✓'
: '+'}
</button>
</div>
</div>
<div className="onboarding-user-list-body">
{renderLoadingOrList()}
</div>
</div>
</div>
);
}
}
OnboardingUsers.propTypes = {
handleCheckUser: PropTypes.func.isRequired,
handleCheckAllUsers: PropTypes.func.isRequired,
};
export default OnboardingUsers;

View file

@ -1,26 +0,0 @@
import { h, render, Component } from 'preact';
// page 1
const OnboardingWelcome = () => {
const messages = [
'Thank you for joining the DEV Community.',
'Keep up with the people and software trends you care about. ❤️',
];
const specialMessage = "Let's get started!";
return (
<div className="onboarding-initial-welcome">
{messages.map(item => (
<p>{item}</p>
))}
<p>
<em>
<strong className="green">{specialMessage}</strong>
</em>
</p>
</div>
);
};
export default OnboardingWelcome;

View file

@ -1,17 +0,0 @@
import { h } from 'preact';
const OnboardingWelcomeThread = () => {
const wrapInYellow = message => <strong className="yellow">{message}</strong>;
return (
<div className="onboarding-final-message">
<p>Software is driven by community.</p>
<p>Don't hesitate to find a discussion and jump right in.</p>
<p>
<em className="green">Everyone is welcome!</em>
</p>
</div>
);
};
export default OnboardingWelcomeThread;

View file

@ -1,144 +0,0 @@
import { h } from 'preact';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import faker from 'faker';
import GlobalModalWrapper from './GlobalModalWrapper';
import OnboardingArticle from '../OnboardingArticle';
import { defaultChildrenPropTypes } from '../common-prop-types';
const article = {
user: {
id: faker.internet.userName(),
name: faker.name.firstName(),
profile_image_url: './images/storm-trooper-33x33.png',
},
description: faker.random.words(5),
title: faker.random.words(2),
positive_reactions_count: 100,
comments_count: 8,
};
const commonProps = {
article,
isSaved: false,
onSaveArticle: action('Saving article'),
};
const ArticleWrapper = ({ children }) => (
<div className="onboarding-user-container">
<div className="onboarding-user-list">
<div className="onboarding-user-list-body">{children}</div>
</div>
</div>
);
ArticleWrapper.propTypes = {
// Diabling linting below because of https://github.com/yannickcr/eslint-plugin-react/issues/1389
// eslint-disable-next-line react/no-typos
children: defaultChildrenPropTypes.isRequired,
};
storiesOf('Onboarding/OnboardingArticle', module)
.addDecorator(storyFn => (
<GlobalModalWrapper>
<ArticleWrapper>{storyFn()}</ArticleWrapper>
</GlobalModalWrapper>
))
.add('Already saved article', () => (
<OnboardingArticle
{...commonProps}
onSaveArticle={action('Unsaving article')}
isSaved
/>
))
.add('Short title', () => <OnboardingArticle {...commonProps} />)
.add('Medium length title', () => (
<OnboardingArticle
{...commonProps}
article={{ ...article, title: faker.random.words(10) }}
/>
))
.add('Long title', () => (
<OnboardingArticle
{...commonProps}
article={{ ...article, title: faker.random.words(15) }}
/>
))
.add('Short description', () => (
<OnboardingArticle
{...commonProps}
article={{ ...article, description: faker.random.words(5) }}
/>
))
.add('Medium length description', () => (
<OnboardingArticle
{...commonProps}
article={{ ...article, description: faker.random.words(10) }}
/>
))
.add('Long description', () => (
<OnboardingArticle
{...commonProps}
article={{ ...article, description: faker.random.words(20) }}
/>
))
.add('No reactions/comments count', () => (
<OnboardingArticle
{...commonProps}
article={{
...article,
positive_reactions_count: 0,
comments_count: 0,
}}
/>
))
.add('10 reactions/comments', () => (
<OnboardingArticle
{...commonProps}
article={{
...article,
positive_reactions_count: 10,
comments_count: 10,
}}
/>
))
.add('999 reactions/comments', () => (
<OnboardingArticle
{...commonProps}
article={{
...article,
positive_reactions_count: 999,
comments_count: 999,
}}
/>
))
.add('9999 reactions/comments', () => (
<OnboardingArticle
{...commonProps}
article={{
...article,
positive_reactions_count: 9999,
comments_count: 9999,
}}
/>
))
.add('99999 reactions/comments', () => (
<OnboardingArticle
{...commonProps}
article={{
...article,
positive_reactions_count: 99999,
comments_count: 99999,
}}
/>
))
.add('999999 reactions/comments', () => (
<OnboardingArticle
{...commonProps}
article={{
...article,
positive_reactions_count: 999999,
comments_count: 999999,
}}
/>
));

View file

@ -1,86 +0,0 @@
import { h } from 'preact';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import faker from 'faker';
import { globalModalDecorator } from './story-decorators';
import OnboardingArticles from '../OnboardingArticles';
const getArticles = (numberOfArticles = 5) =>
new Array(numberOfArticles).fill('').map(() => ({
user: {
id: faker.internet.userName(),
name: faker.name.firstName(),
profile_image_url: './images/storm-trooper-33x33.png',
},
description: faker.random.words(5),
title: faker.random.words(2),
positive_reactions_count: faker.random.number(),
comments_count: faker.random.number(),
}));
const unsavedArticles = getArticles();
const commonProps = {
articles: unsavedArticles,
savedArticles: unsavedArticles.slice(),
handleSaveArticle: action('Handling save article'),
handleSaveAllArticles: action('Saving all articles'),
};
storiesOf('Onboarding/OnboardingArticles', module)
.addDecorator(globalModalDecorator)
.add('All articles saved', () => <OnboardingArticles {...commonProps} />)
.add('Some unsaved articles', () => {
const { articles } = commonProps;
const savedArticles = articles.slice(0, 1).concat(articles.slice(3));
return (
<OnboardingArticles {...commonProps} savedArticles={savedArticles} />
);
})
.add('20 articles articles', () => {
const articles = getArticles(20);
const savedArticles = articles.slice(0, 5).concat(articles.slice(10, 13));
return (
<OnboardingArticles
{...commonProps}
articles={articles}
savedArticles={savedArticles}
/>
);
})
.add('100 articles', () => {
const articles = getArticles(100);
const savedArticles = articles.slice(0, 5).concat(articles.slice(10, 13));
return (
<OnboardingArticles
{...commonProps}
articles={articles}
savedArticles={savedArticles}
/>
);
})
.add('500 articles', () => {
const articles = getArticles(500);
const savedArticles = articles.slice(0, 5).concat(articles.slice(10, 13));
return (
<OnboardingArticles
{...commonProps}
articles={articles}
savedArticles={savedArticles}
/>
);
})
.add('1000 articles', () => {
const articles = getArticles(1000);
const savedArticles = articles.slice(0, 5).concat(articles.slice(10, 13));
return (
<OnboardingArticles
{...commonProps}
articles={articles}
savedArticles={savedArticles}
/>
);
});

View file

@ -1,8 +0,0 @@
import { h } from 'preact';
import { storiesOf } from '@storybook/react';
import { globalModalDecorator } from './story-decorators';
import OnboardingWelcome from '../OnboardingWelcome';
storiesOf('OnboardingWelcome', module)
.addDecorator(globalModalDecorator)
.add('Default', () => <OnboardingWelcome />);

View file

@ -1,8 +0,0 @@
import { h } from 'preact';
import { storiesOf } from '@storybook/react';
import { globalModalDecorator } from './story-decorators';
import OnboardingWelcomeThread from '../OnboardingWelcomeThread';
storiesOf('OnboardingWelcomeThread', module)
.addDecorator(globalModalDecorator)
.add('Default', () => <OnboardingWelcomeThread />);

View file

@ -1,34 +0,0 @@
import { h } from 'preact';
import render from 'preact-render-to-json';
import OnboardingFollowTags from '../OnboardingFollowTags';
describe('<OnboardingFollowTags />', () => {
it('renders properly when given a tag', () => {
const userData = {};
const allTags = [{
bg_color_hex: '#000000',
id: 715,
name: 'discuss',
text_color_hex: '#ffffff',
}, {
bg_color_hex: '#f7df1e',
id: 6,
name: 'javascript',
text_color_hex: '#000000',
}, {
bg_color_hex: '#f7df1e',
id: 6,
name: 'javascript',
text_color_hex: '#000000',
}];
const followedTags = {};
const handleFollowTag = jest.fn();
const tree = render(<OnboardingFollowTags
userData={userData}
allTags={allTags}
followedTags={followedTags}
handleFollowTag={handleFollowTag}
/>);
expect(tree).toMatchSnapshot();
});
});

View file

@ -1,51 +0,0 @@
import { h } from 'preact';
import render from 'preact-render-to-json';
import OnboardingFollowUsers from '../OnboardingFollowUsers';
describe('<OnboardingFollowUsers />', () => {
it('renders properly when given users', () => {
const users = [
{
id: 1,
name: 'Ben Halpern',
profile_image_url: 'ben.jpg',
},
{
id: 2,
name: 'Krusty the Clown',
profile_image_url: 'clown.jpg',
},
{
id: 3,
name: 'dev.to staff',
profile_image_url: 'dev.jpg',
},
];
const checkedUsers = [
{
id: 1,
name: 'Ben Halpern',
profile_image_url: 'ben.jpg',
},
{
id: 2,
name: 'Krusty the Clown',
profile_image_url: 'clown.jpg',
},
{
id: 3,
name: 'dev.to staff',
profile_image_url: 'dev.jpg',
},
];
const handleCheckUser = jest.fn();
const handleCheckAllUsers = jest.fn();
const tree = render(<OnboardingFollowUsers
users={users}
checkedUsers={checkedUsers}
handleCheckUser={handleCheckUser}
handleCheckAllUsers={handleCheckAllUsers}
/>);
expect(tree).toMatchSnapshot();
});
});

View file

@ -1,43 +0,0 @@
import { h } from 'preact';
import { shallow } from 'preact-render-spy';
import { render } from 'preact-render-to-json';
import OnboardingSingleTag from '../OnboardingSingleTag';
describe('<OnboardingSingleTag />', () => {
const followingTag = { following: true };
const notFollowingTag = { following: false };
describe('when given a following tag', () => {
it('renders correctly', () => {
const context = render(<OnboardingSingleTag tag={followingTag} />);
expect(context).toMatchSnapshot();
});
it('responses to clicks', () => {
const onClick = jest.fn();
const context = shallow(<OnboardingSingleTag tag={followingTag} onTagClick={onClick} />);
expect(context.find('a').text()).toEqual('#✓');
context.find('.onboarding-tag-link-follow').simulate('click');
expect(onClick).toHaveBeenCalledTimes(1);
context.render(<OnboardingSingleTag tag={notFollowingTag} onTagClick={onClick} />);
expect(context.find('a').text()).toEqual('#+');
});
});
describe('when given a non-following tag', () => {
it('renders correctly', () => {
const context = render(<OnboardingSingleTag tag={notFollowingTag} />);
expect(context).toMatchSnapshot();
});
it('responses to clicks', () => {
const onClick = jest.fn();
const context = shallow(<OnboardingSingleTag tag={notFollowingTag} onTagClick={onClick} />);
expect(context.find('a').text()).toEqual('#+');
context.find('.onboarding-tag-link-follow').simulate('click');
expect(onClick).toHaveBeenCalledTimes(1);
context.render(<OnboardingSingleTag tag={followingTag} onTagClick={onClick} />);
expect(context.find('a').text()).toEqual('#✓');
});
});
});

View file

@ -1,67 +0,0 @@
import { h } from 'preact';
import { shallow } from 'preact-render-spy';
import { render } from 'preact-render-to-json';
import OnboardingUsers from '../OnboardingUsers';
describe('<OnboardingUsers />', () => {
const users = [
{
id: 1,
name: 'Ben Halpern',
profile_image_url: 'ben.jpg',
},
{
id: 2,
name: 'Krusty the Clown',
profile_image_url: 'clown.jpg',
},
{
id: 3,
name: 'dev.to staff',
profile_image_url: 'dev.jpg',
},
];
const checkedUsers = [
{
id: 1,
name: 'Ben Halpern',
profile_image_url: 'ben.jpg',
},
{
id: 2,
name: 'Krusty the Clown',
profile_image_url: 'clown.jpg',
},
{
id: 3,
name: 'dev.to staff',
profile_image_url: 'dev.jpg',
},
];
const handleCheckUser = jest.fn();
const handleCheckAllUsers = jest.fn();
describe('when given users to follow', () => {
it('renders correctly', () => {
const context = render(<OnboardingUsers
users={users}
checkedUsers={checkedUsers}
handleCheckUser={handleCheckUser}
handleCheckAllUsers={handleCheckAllUsers}
/>);
expect(context).toMatchSnapshot();
});
it('responds to clicking Follow All', () => {
const context = shallow(<OnboardingUsers
users={users}
checkedUsers={checkedUsers}
handleCheckUser={handleCheckUser}
handleCheckAllUsers={handleCheckAllUsers}
/>);
expect(context.find('#onboarding-user-follow-all-btn').text()).toEqual('✓');
context.find('#onboarding-user-follow-all-btn').simulate('click');
expect(handleCheckAllUsers).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -1,10 +0,0 @@
import { h } from 'preact';
import { render } from 'preact-render-to-json';
import OnboardingWelcome from '../OnboardingWelcome';
describe('<OnboardingWelcome />', () => {
it('renders correctly', () => {
const tree = render(<OnboardingWelcome />);
expect(tree).toMatchSnapshot();
});
});

View file

@ -1,10 +0,0 @@
import { h } from 'preact';
import { render } from 'preact-render-to-json';
import OnboardingWelcomeThread from '../OnboardingWelcomeThread';
describe('<OnboardingWelcomeThread />', () => {
it('renders correctly', () => {
const tree = render(<OnboardingWelcomeThread />);
expect(tree).toMatchSnapshot();
});
});

View file

@ -1,86 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OnboardingFollowTags /> renders properly when given a tag 1`] = `
<div
class="tags-slide"
>
<div
class="onboarding-user-cta"
>
Personalize your home feed
</div>
<div
class="tags-col-container"
>
<div
class="onboarding-tag-container"
id="onboarding-tag-container-discuss"
style="background: "
>
<a
class="onboarding-tag-link"
href="#0"
onClick={[Function]}
style="color:"
>
#discuss
</a>
<a
class="onboarding-tag-link-follow"
href="#0"
id="onboarding-tag-link-follow-discuss"
onClick={[Function]}
style="color:"
>
+
</a>
</div>
<div
class="onboarding-tag-container"
id="onboarding-tag-container-javascript"
style="background: "
>
<a
class="onboarding-tag-link"
href="#0"
onClick={[Function]}
style="color:"
>
#javascript
</a>
<a
class="onboarding-tag-link-follow"
href="#0"
id="onboarding-tag-link-follow-javascript"
onClick={[Function]}
style="color:"
>
+
</a>
</div>
<div
class="onboarding-tag-container"
id="onboarding-tag-container-javascript"
style="background: "
>
<a
class="onboarding-tag-link"
href="#0"
onClick={[Function]}
style="color:"
>
#javascript
</a>
<a
class="onboarding-tag-link-follow"
href="#0"
id="onboarding-tag-link-follow-javascript"
onClick={[Function]}
style="color:"
>
+
</a>
</div>
</div>
</div>
`;

View file

@ -1,128 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OnboardingFollowUsers /> renders properly when given users 1`] = `
<div
class="onboarding-user-container"
>
<div
class="onboarding-user-cta"
>
Here are some suggestions based on your interests
</div>
<div
class="onboarding-user-list"
>
<div
class="onboarding-user-list-header"
>
<div
class="onboarding-user-list-key"
>
Follow All
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
class="checked"
id="onboarding-user-follow-all-btn"
onClick={[Function]}
>
</button>
</div>
</div>
<div
class="onboarding-user-list-body"
>
<div
class="onboarding-user-list-row"
>
<div
class="onboarding-user-list-key"
>
<img
alt="Ben Halpern"
src="ben.jpg"
/>
<div>
Ben Halpern
</div>
<div
class="onboarding-user-list-row__summary"
>
<em />
</div>
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
onClick={[Function]}
>
+
</button>
</div>
</div>
<div
class="onboarding-user-list-row"
>
<div
class="onboarding-user-list-key"
>
<img
alt="Krusty the Clown"
src="clown.jpg"
/>
<div>
Krusty the Clown
</div>
<div
class="onboarding-user-list-row__summary"
>
<em />
</div>
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
onClick={[Function]}
>
+
</button>
</div>
</div>
<div
class="onboarding-user-list-row"
>
<div
class="onboarding-user-list-key"
>
<img
alt="dev.to staff"
src="dev.jpg"
/>
<div>
dev.to staff
</div>
<div
class="onboarding-user-list-row__summary"
>
<em />
</div>
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
onClick={[Function]}
>
+
</button>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -1,53 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OnboardingSingleTag /> when given a following tag renders correctly 1`] = `
<div
class="onboarding-tag-container followed-tag"
id="onboarding-tag-container-undefined"
style="background: undefined"
>
<a
class="onboarding-tag-link"
href="#0"
onClick={[Function]}
style="color:undefined"
>
#
</a>
<a
class="onboarding-tag-link-follow"
href="#0"
id="onboarding-tag-link-follow-undefined"
onClick={[Function]}
style="color:undefined"
>
</a>
</div>
`;
exports[`<OnboardingSingleTag /> when given a non-following tag renders correctly 1`] = `
<div
class="onboarding-tag-container"
id="onboarding-tag-container-undefined"
style="background: "
>
<a
class="onboarding-tag-link"
href="#0"
onClick={[Function]}
style="color:"
>
#
</a>
<a
class="onboarding-tag-link-follow"
href="#0"
id="onboarding-tag-link-follow-undefined"
onClick={[Function]}
style="color:"
>
+
</a>
</div>
`;

View file

@ -1,128 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OnboardingUsers /> when given users to follow renders correctly 1`] = `
<div
class="onboarding-user-container"
>
<div
class="onboarding-user-cta"
>
Here are some suggestions based on your interests
</div>
<div
class="onboarding-user-list"
>
<div
class="onboarding-user-list-header"
>
<div
class="onboarding-user-list-key"
>
Follow All
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
class="checked"
id="onboarding-user-follow-all-btn"
onClick={[Function]}
>
</button>
</div>
</div>
<div
class="onboarding-user-list-body"
>
<div
class="onboarding-user-list-row"
>
<div
class="onboarding-user-list-key"
>
<img
alt="Ben Halpern"
src="ben.jpg"
/>
<div>
Ben Halpern
</div>
<div
class="onboarding-user-list-row__summary"
>
<em />
</div>
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
onClick={[Function]}
>
+
</button>
</div>
</div>
<div
class="onboarding-user-list-row"
>
<div
class="onboarding-user-list-key"
>
<img
alt="Krusty the Clown"
src="clown.jpg"
/>
<div>
Krusty the Clown
</div>
<div
class="onboarding-user-list-row__summary"
>
<em />
</div>
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
onClick={[Function]}
>
+
</button>
</div>
</div>
<div
class="onboarding-user-list-row"
>
<div
class="onboarding-user-list-key"
>
<img
alt="dev.to staff"
src="dev.jpg"
/>
<div>
dev.to staff
</div>
<div
class="onboarding-user-list-row__summary"
>
<em />
</div>
</div>
<div
class="onboarding-user-list-checkbox"
>
<button
onClick={[Function]}
>
+
</button>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -1,23 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OnboardingWelcome /> renders correctly 1`] = `
<div
class="onboarding-initial-welcome"
>
<p>
Thank you for joining the DEV Community.
</p>
<p>
Keep up with the people and software trends you care about. ❤️
</p>
<p>
<em>
<strong
class="green"
>
Let's get started!
</strong>
</em>
</p>
</div>
`;

View file

@ -1,21 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<OnboardingWelcomeThread /> renders correctly 1`] = `
<div
class="onboarding-final-message"
>
<p>
Software is driven by community.
</p>
<p>
Don't hesitate to find a discussion and jump right in.
</p>
<p>
<em
class="green"
>
Everyone is welcome!
</em>
</p>
</div>
`;

View file

@ -118,7 +118,9 @@ class UnopenedChannelNotice extends Component {
padding: '19px 5px 14px',
}}
>
New Message from {channels}
New Message from
{' '}
{channels}
</a>
);
}

View file

@ -26,10 +26,10 @@ export default function setupPusher(key, callbackObjects) {
'client-initiatevideocall',
callbackObjects.videoCallInitiated,
);
channel.bind('client-endvideocall', callbackObjects.videoCallEnded);
// channel.bind('pusher:subscription_succeeded', callbackObjects.channelSubscribed);
channel.bind('client-endvideocall', callbackObjects.videoCallEnded);
channel.bind('pusher:subscription_error', callbackObjects.channelError);
return channel;
});
}