Add Save Articles Page to Onboarding (#182)
* Add MVP of onboarding articles * Lint and fix unfollow all button bug * Update onboarding tests * Fix typo and adjust when saveAll request gets sent * Set default to always save all articles * Fix when request is sent for follows/saves * Check for article length when for initial request * Add a loading indicator for follows page * Prevent multiple follow/save requests * Update tests * Add some hover and click effects * Adjust onboarding test and suggested articles
This commit is contained in:
parent
1bf80860ed
commit
deb63ab643
16 changed files with 415 additions and 47 deletions
|
|
@ -42,6 +42,9 @@
|
|||
.yellow{
|
||||
background: $yellow;
|
||||
}
|
||||
.purple{
|
||||
background: $purple;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
height: 10%;
|
||||
|
|
@ -194,15 +197,56 @@
|
|||
color: $black;
|
||||
margin: 20px auto 25px;
|
||||
}
|
||||
.onboarding-article-container{
|
||||
color: $black;
|
||||
.onboarding-article-header{
|
||||
padding: 5px 10px 5px 5px;
|
||||
background: $purple;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
border: 1px solid darken($purple, 20%);
|
||||
box-shadow: 4px 5px 0px darken($purple, 20%);
|
||||
}
|
||||
.onboarding-article-header-checkbox{
|
||||
width: 80%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
.onboarding-article-save-all-btn{
|
||||
border: 2px solid transparent;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
float: right;
|
||||
font-size: 0.75em;
|
||||
background: inherit;
|
||||
color: $black;
|
||||
&:hover {
|
||||
background: darken($purple, 10%);
|
||||
}
|
||||
&.saved {
|
||||
background: darken($purple, 26%);
|
||||
color: $white;
|
||||
&:hover {
|
||||
background: darken($purple, 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onboarding-user-list{
|
||||
color: $black;
|
||||
font-size: 0.7em;
|
||||
overflow: scroll;
|
||||
border: 1px solid darken($purple, 20%);
|
||||
box-shadow: 4px 5px 0px darken($purple, 20%);
|
||||
.onboarding-user-list-body{
|
||||
height: calc(39vh - 20px);
|
||||
overflow: scroll;
|
||||
overflow-y: scroll;
|
||||
.onboarding-user-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 15%;
|
||||
color: $medium-gray;
|
||||
}
|
||||
}
|
||||
.onboarding-user-list-row{
|
||||
padding: 10px 0px;
|
||||
|
|
@ -214,6 +258,21 @@
|
|||
vertical-align: -0.5em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.onboarding-article-engagement {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: $helvetica-condensed;
|
||||
color: $medium-gray;
|
||||
img {
|
||||
height: 20px;
|
||||
min-width: 27px;
|
||||
width: initial;
|
||||
vertical-align: -5px;
|
||||
margin-right: 3px;
|
||||
border-radius: initial;
|
||||
padding-left: 7px;
|
||||
}
|
||||
}
|
||||
&:nth-child(odd){
|
||||
background: $light-gray;
|
||||
}
|
||||
|
|
@ -226,6 +285,24 @@
|
|||
width: calc(100% - 80px);
|
||||
display: inline-block;
|
||||
padding-left: 10px;
|
||||
h3 {
|
||||
-webkit-margin-before: 0.2em;
|
||||
-webkit-margin-after: 0.3em;
|
||||
font-size: 30px;
|
||||
}
|
||||
&.article {
|
||||
width: calc(100% - 100px);
|
||||
span {
|
||||
// article author's name
|
||||
color: $medium-gray;
|
||||
}
|
||||
p {
|
||||
// article description
|
||||
-webkit-margin-before: 0.5em;
|
||||
-webkit-margin-start: 10px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.onboarding-user-list-checkbox{
|
||||
width: 70px;
|
||||
|
|
@ -233,11 +310,55 @@
|
|||
text-align: center;
|
||||
button{
|
||||
border: 0px;
|
||||
border-radius: 3px;
|
||||
background: transparent;
|
||||
font-size:1.1em;
|
||||
&.checked{
|
||||
background: $green;
|
||||
color: white;
|
||||
&:hover {
|
||||
background: darken($green, 10%);
|
||||
}
|
||||
&:active {
|
||||
background: darken($green, 20%);
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: darken($purple, 10%);
|
||||
}
|
||||
&:active {
|
||||
background: darken($purple, 20%);
|
||||
}
|
||||
&.article{
|
||||
&:hover{
|
||||
background: darken($purple, 10%);
|
||||
}
|
||||
&:active {
|
||||
background: darken($purple, 20%);
|
||||
}
|
||||
&.save-single{
|
||||
background: darken($purple, 2%);
|
||||
font-size: 0.8em;
|
||||
font-family: $helvetica-condensed;
|
||||
width: inherit;
|
||||
height: 35px;
|
||||
&:hover {
|
||||
background: darken($purple, 10%);
|
||||
}
|
||||
&:active {
|
||||
background: darken($purple, 20%);
|
||||
}
|
||||
}
|
||||
&.saved {
|
||||
background: darken($purple, 26%);
|
||||
color: white;
|
||||
&:hover {
|
||||
background: darken($purple, 35%);
|
||||
}
|
||||
&:active {
|
||||
background: darken($purple, 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -274,6 +395,12 @@
|
|||
a {
|
||||
color: black;
|
||||
}
|
||||
&:hover {
|
||||
background: darken($green, 20%);
|
||||
}
|
||||
&:active {
|
||||
background: darken($green, 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,20 @@ module Api
|
|||
@article = Article.includes(:user).find(params[:id]).decorate
|
||||
not_found unless @article.published
|
||||
end
|
||||
|
||||
def onboarding
|
||||
tag_list = params[:tag_list].split(",")
|
||||
@articles = []
|
||||
4.times do
|
||||
@articles << ClassicArticle.new.get(tag_list)
|
||||
end
|
||||
Article.tagged_with(tag_list, any: true).
|
||||
order("published_at DESC").
|
||||
where("positive_reactions_count > ? OR comments_count > ?", 10, 3).limit(15).each do |article|
|
||||
@articles << article
|
||||
end
|
||||
@articles = @articles.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,6 +18,19 @@ module Api
|
|||
render json: { reaction: @reaction.to_json }
|
||||
end
|
||||
|
||||
def onboarding
|
||||
verify_authenticity_token
|
||||
reactable_ids = JSON.parse(params[:articles]).map { |article| article["id"] }
|
||||
reactable_ids.each do |article_id|
|
||||
Reaction.delay.create(
|
||||
user_id: current_user.id,
|
||||
reactable_id: article_id,
|
||||
reactable_type: "Article",
|
||||
category: "readinglist",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_user
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import OnboardingFollowTags from './components/OnboardingFollowTags';
|
|||
import OnboardingFollowUsers from './components/OnboardingFollowUsers';
|
||||
import OnboardingWelcomeThread from './components/OnboardingWelcomeThread';
|
||||
import cancelSvg from '../../assets/images/cancel.svg';
|
||||
import OnboardingArticles from './components/OnboardingArticles';
|
||||
|
||||
class Onboarding extends Component {
|
||||
constructor() {
|
||||
|
|
@ -17,6 +18,8 @@ class Onboarding extends Component {
|
|||
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.getUsersToFollow = this.getUsersToFollow.bind(this);
|
||||
this.state = {
|
||||
pageNumber: 1,
|
||||
|
|
@ -25,6 +28,10 @@ class Onboarding extends Component {
|
|||
allTags: [],
|
||||
users: [],
|
||||
checkedUsers: [],
|
||||
followRequestSent: false,
|
||||
articles: [],
|
||||
savedArticles: [],
|
||||
saveRequestSent: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -76,8 +83,29 @@ class Onboarding extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
getSuggestedArticles() {
|
||||
const followedTags = [];
|
||||
for (let i = 0; i < this.state.allTags.length; i += 1) {
|
||||
if (this.state.allTags[i].following) {
|
||||
followedTags.push(this.state.allTags[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
fetch(`/api/articles/onboarding?tag_list=${followedTags.join(',')}`, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then((json) => {
|
||||
this.setState({ articles: json, savedArticles: json });
|
||||
});
|
||||
}
|
||||
|
||||
handleBulkFollowUsers(users) {
|
||||
if (this.state.checkedUsers.length > 0) {
|
||||
if (this.state.checkedUsers.length > 0 && !this.state.followRequestSent) {
|
||||
const csrfToken = document.querySelector("meta[name='csrf-token']").content;
|
||||
|
||||
const formData = new FormData();
|
||||
|
|
@ -90,7 +118,32 @@ class Onboarding extends Component {
|
|||
},
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
}).then(() => {
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
this.setState({ followRequestSent: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleBulkSaveArticles(articles) {
|
||||
if (this.state.savedArticles.length > 0 && !this.state.saveRequestSent) {
|
||||
const csrfToken = document.querySelector("meta[name='csrf-token']").content;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('articles', JSON.stringify(articles));
|
||||
|
||||
fetch('/api/reactions/onboarding', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrfToken,
|
||||
},
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
this.setState({ saveRequestSent: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -122,8 +175,6 @@ class Onboarding extends Component {
|
|||
credentials: 'same-origin',
|
||||
})
|
||||
.then((response) => {
|
||||
// change allTags state
|
||||
// this.setState({ allTags: [] });
|
||||
return response.json().then((json) => {
|
||||
this.setState({
|
||||
allTags: this.state.allTags.map((currentTag) => {
|
||||
|
|
@ -144,7 +195,7 @@ class Onboarding extends Component {
|
|||
|
||||
|
||||
handleCheckAllUsers() {
|
||||
if (this.state.checkedUsers.length < 50) {
|
||||
if (this.state.checkedUsers.length < this.state.users.length) {
|
||||
this.setState({ checkedUsers: this.state.users.slice() });
|
||||
} else {
|
||||
this.setState({ checkedUsers: [] });
|
||||
|
|
@ -162,13 +213,47 @@ class Onboarding extends Component {
|
|||
this.setState({ checkedUsers: newCheckedUsers });
|
||||
}
|
||||
|
||||
handleNextButton() {
|
||||
handleSaveAllArticles() {
|
||||
if (this.state.savedArticles.length < this.state.articles.length) {
|
||||
this.setState({ savedArticles: this.state.articles.slice() });
|
||||
} else {
|
||||
this.setState({ savedArticles: [] });
|
||||
}
|
||||
}
|
||||
|
||||
handleSaveArticle(article) {
|
||||
const newSavedArticles = this.state.savedArticles.slice();
|
||||
if (this.state.savedArticles.indexOf(article) > -1) {
|
||||
const index = newSavedArticles.indexOf(article);
|
||||
newSavedArticles.splice(index, 1);
|
||||
} else {
|
||||
newSavedArticles.push(article);
|
||||
}
|
||||
this.setState({ savedArticles: newSavedArticles });
|
||||
}
|
||||
|
||||
handleNextHover() {
|
||||
if (this.state.pageNumber === 2 && this.state.users.length === 0) {
|
||||
this.getUsersToFollow();
|
||||
this.getSuggestedArticles();
|
||||
}
|
||||
if (this.state.pageNumber < 4) {
|
||||
}
|
||||
|
||||
handleNextButton() {
|
||||
if (this.state.pageNumber === 2 &&
|
||||
this.state.users.length === 0 &&
|
||||
this.state.articles.length === 0) {
|
||||
this.getUsersToFollow();
|
||||
this.getSuggestedArticles();
|
||||
}
|
||||
if (this.state.pageNumber < 5) {
|
||||
this.setState({ pageNumber: this.state.pageNumber + 1 });
|
||||
} else if (this.state.pageNumber === 4) {
|
||||
if (this.state.pageNumber === 4 && this.state.checkedUsers.length > 0) {
|
||||
this.handleBulkFollowUsers(this.state.checkedUsers);
|
||||
} else if (this.state.pageNumber === 5 && this.state.savedArticles.length > 0) {
|
||||
this.handleBulkSaveArticles(this.state.savedArticles);
|
||||
}
|
||||
} else if (this.state.pageNumber === 5) {
|
||||
this.closeOnboarding();
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +266,6 @@ class Onboarding extends Component {
|
|||
|
||||
closeOnboarding() {
|
||||
document.getElementsByTagName('body')[0].classList.remove('modal-open');
|
||||
this.handleBulkFollowUsers(this.state.checkedUsers);
|
||||
const csrfToken = document.querySelector("meta[name='csrf-token']").content;
|
||||
const formData = new FormData();
|
||||
formData.append('saw_onboarding', true);
|
||||
|
|
@ -227,6 +311,15 @@ class Onboarding extends Component {
|
|||
/>
|
||||
);
|
||||
} else if (this.state.pageNumber === 4) {
|
||||
return (
|
||||
<OnboardingArticles
|
||||
articles={this.state.articles}
|
||||
savedArticles={this.state.savedArticles}
|
||||
handleSaveAllArticles={this.handleSaveAllArticles}
|
||||
handleSaveArticle={this.handleSaveArticle}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.pageNumber === 5) {
|
||||
return (
|
||||
<OnboardingWelcomeThread />
|
||||
);
|
||||
|
|
@ -241,17 +334,15 @@ class Onboarding extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleNextHover() {
|
||||
if (this.state.pageNumber === 2 && this.state.users.length === 0) {
|
||||
this.getUsersToFollow();
|
||||
}
|
||||
}
|
||||
|
||||
renderNextButton() {
|
||||
const onclick = this.handleNextButton;
|
||||
return (
|
||||
<button className="button cta" onClick={this.handleNextButton} onMouseOver={this.handleNextHover}>
|
||||
{this.state.pageNumber < 4 ? 'NEXT' : "LET'S GO"}
|
||||
<button
|
||||
className="button cta"
|
||||
onClick={this.handleNextButton}
|
||||
onMouseOver={this.handleNextHover}
|
||||
onFocus={this.handleNextHover}
|
||||
>
|
||||
{this.state.pageNumber < 5 ? 'NEXT' : "LET'S GO"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -261,7 +352,8 @@ class Onboarding extends Component {
|
|||
1: 'WELCOME!',
|
||||
2: 'FOLLOW TAGS!',
|
||||
3: 'FOLLOW SOME DEVS!',
|
||||
4: 'GET INVOLVED!',
|
||||
4: 'SAVE SOME POSTS!',
|
||||
5: 'GET INVOLVED!',
|
||||
};
|
||||
return messages[this.state.pageNumber];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,14 +117,17 @@ describe('<Onboarding />', () => {
|
|||
// going to page four
|
||||
context.find('.button').at(1).simulate('click');
|
||||
expect(context.state('pageNumber')).toEqual(4);
|
||||
fetch.mockResponse(JSON.stringify({ outcome: 'onboarding closed' }));
|
||||
// going back to page three
|
||||
context.find('.button').at(0).simulate('click');
|
||||
expect(context.state('pageNumber')).toEqual(3);
|
||||
// going back to page four
|
||||
// going to page five
|
||||
context.find('.button').at(1).simulate('click');
|
||||
expect(context.state('pageNumber')).toEqual(5);
|
||||
fetch.mockResponse(JSON.stringify({ outcome: 'onboarding closed' }));
|
||||
// going back to page four
|
||||
context.find('.button').at(0).simulate('click');
|
||||
expect(context.state('pageNumber')).toEqual(4);
|
||||
// evaluting the button text on page 4
|
||||
// going back to page five
|
||||
context.find('.button').at(1).simulate('click');
|
||||
expect(context.state('pageNumber')).toEqual(5);
|
||||
// evaluting the button text on page five
|
||||
expect(context.find('[onClick]').at(2).text()).toEqual("LET'S GO");
|
||||
context.find('.button').at(1).simulate('click');
|
||||
// clicking the final button
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ FindWrapper {
|
|||
"attributes": Object {
|
||||
"className": "button cta",
|
||||
"onClick": [Function],
|
||||
"onFocus": [Function],
|
||||
"onMouseOver": [Function],
|
||||
},
|
||||
"children": Array [
|
||||
|
|
@ -51,8 +52,12 @@ FindWrapper {
|
|||
exports[`<Onboarding /> display if there is current user 1`] = `
|
||||
Object {
|
||||
"allTags": Array [],
|
||||
"articles": Array [],
|
||||
"checkedUsers": Array [],
|
||||
"followRequestSent": false,
|
||||
"pageNumber": 1,
|
||||
"saveRequestSent": false,
|
||||
"savedArticles": Array [],
|
||||
"showOnboarding": true,
|
||||
"userData": Object {
|
||||
"followed_tag_names": Array [
|
||||
|
|
|
|||
81
app/javascript/src/components/OnboardingArticles.jsx
Normal file
81
app/javascript/src/components/OnboardingArticles.jsx
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { h, render, Component } from 'preact';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class OnboardingArticles extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleAllClick = this.handleAllClick.bind(this);
|
||||
}
|
||||
|
||||
|
||||
handleAllClick() {
|
||||
this.props.handleSaveAllArticles();
|
||||
}
|
||||
|
||||
render() {
|
||||
const articles = this.props.articles.map((article) => {
|
||||
return (
|
||||
<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 < 75 ? article.description : `${article.description.substring(0, 75)}...`}</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={this.props.handleSaveArticle.bind(this, article)}
|
||||
className={`article save-single ${this.props.savedArticles.indexOf(article) > -1 ? 'saved' : ''}`}
|
||||
>
|
||||
{this.props.savedArticles.indexOf(article) > -1 ? 'SAVED' : 'SAVE'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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 ${this.props.savedArticles.length === this.props.articles.length ? 'saved' : ''}`}
|
||||
>
|
||||
{this.props.savedArticles.length === this.props.articles.length ? '✓' : '+'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="onboarding-user-list-body">{articles}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
OnboardingArticles.propTypes = {
|
||||
handleSaveArticle: PropTypes.func.isRequired,
|
||||
handleSaveAllArticles: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default OnboardingArticles;
|
||||
|
|
@ -30,11 +30,21 @@ class OnboardingUsers extends Component {
|
|||
</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 folks from the community you might want to follow <span class="yellow">based on your interests</span>
|
||||
Here are some folks from the community you might want to follow <strong className="yellow">based on your interests.</strong>
|
||||
</div>
|
||||
<div className="onboarding-user-list">
|
||||
<div className="onboarding-user-list-header onboarding-user-list-row">
|
||||
|
|
@ -47,7 +57,9 @@ class OnboardingUsers extends Component {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="onboarding-user-list-body">{followList}</div>
|
||||
<div className="onboarding-user-list-body">
|
||||
{renderLoadingOrList()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const OnboardingWelcome = () => {
|
|||
return (
|
||||
<div>
|
||||
{messages.map(item => (<p>{item}</p>))}
|
||||
<p><strong className="yellow">{specialMessage}</strong>.</p>
|
||||
<p><strong className="yellow">{specialMessage}.</strong></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ exports[`<OnboardingFollowUsers /> renders properly when given users 1`] = `
|
|||
class="onboarding-user-cta"
|
||||
>
|
||||
Here are some folks from the community you might want to follow
|
||||
<span
|
||||
<strong
|
||||
class="yellow"
|
||||
>
|
||||
based on your interests
|
||||
</span>
|
||||
based on your interests.
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="onboarding-user-list"
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ exports[`<OnboardingUsers /> when given users to follow renders correctly 1`] =
|
|||
class="onboarding-user-cta"
|
||||
>
|
||||
Here are some folks from the community you might want to follow
|
||||
<span
|
||||
<strong
|
||||
class="yellow"
|
||||
>
|
||||
based on your interests
|
||||
</span>
|
||||
based on your interests.
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="onboarding-user-list"
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ exports[`<OnboardingWelcome /> renders correctly 1`] = `
|
|||
<strong
|
||||
class="yellow"
|
||||
>
|
||||
Let's get started
|
||||
Let's get started.
|
||||
</strong>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ class ClassicArticle
|
|||
@not_ids = options[:not_ids]
|
||||
end
|
||||
|
||||
def get
|
||||
if rand(5) == 1
|
||||
def get(tag_names = random_supported_tag_names)
|
||||
if rand(8) == 1
|
||||
random_high_quality_article
|
||||
else
|
||||
qualifying_articles(random_supported_tag_names).where.not(id: not_ids).compact.sample ||
|
||||
qualifying_articles(tag_names).where.not(id: not_ids).compact.sample ||
|
||||
random_high_quality_article
|
||||
end
|
||||
end
|
||||
|
|
|
|||
14
app/views/api/v0/articles/onboarding.json.jbuilder
Normal file
14
app/views/api/v0/articles/onboarding.json.jbuilder
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
json.array! @articles do |article|
|
||||
json.id article.id
|
||||
json.title article.title
|
||||
json.description article.description
|
||||
json.published_at article.published_at
|
||||
json.tag_list article.cached_tag_list
|
||||
json.comments_count article.comments_count
|
||||
json.positive_reactions_count article.positive_reactions_count
|
||||
|
||||
json.user do
|
||||
json.name article.user.name
|
||||
json.profile_image_url ProfileImage.new(article.user).get(90)
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
json.array! @users.each do |user|
|
||||
json.id user.id
|
||||
json.name user.name
|
||||
json.username user.username
|
||||
json.profile_image_url ProfileImage.new(user).get(90)
|
||||
json.id user.id
|
||||
json.name user.name
|
||||
json.username user.username
|
||||
json.profile_image_url ProfileImage.new(user).get(90)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,10 +38,18 @@ Rails.application.routes.draw do
|
|||
|
||||
namespace :api, defaults: {format: 'json'} do
|
||||
scope module: :v0, constraints: ApiConstraints.new(version: 0, default: true) do
|
||||
resources :articles
|
||||
resources :articles, only: [:index, :show] do
|
||||
collection do
|
||||
get "/onboarding", to: "articles#onboarding"
|
||||
end
|
||||
end
|
||||
resources :comments
|
||||
resources :podcast_episodes
|
||||
resources :reactions, only: [:create]
|
||||
resources :reactions, only: [:create] do
|
||||
collection do
|
||||
post "/onboarding", to: "reactions#onboarding"
|
||||
end
|
||||
end
|
||||
resources :users, only: [:index]
|
||||
resources :tags, only: [:index] do
|
||||
collection do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue