* set up refactored onboarding * create onboarding page * add in first slide and change slide functionality * fix test suite * profile refactor * profile refactor * refactor to api * add checkbox fields * add checkbox fields * remove puts * add basic css * add styling * add redirect * hide back and next at first and last slides * test refactored onboarding * test refactored onboarding * remove article edits * Fix schema * Add deleted file back in * Add default value for checked_t&c column * Adjust HTML structure to keep nav buttons in place * Fix ESLint issues on Onboarding.jsx file * Handling for undefined or empty followedTags on getUserTags * Fix codeclimate issues * Fix codeclimate issues * Fix more codeclimate issues * Fix more codeclimate issues * Update Onboarding snapshots * Uncheck the CoC and T&C checkboxes on render * Update snapshots * Return false instead of raising error * Update spec to use new onboarding * Redirect to onboarding if haven't seen it yet * Prevent redirect to onboarding from /signout_confirm * Use assign_attributes instead of saving twice * Move COC and T&C checkbox page to second slide * Add 'go back to original page' functionality * Reuse ready prototype logic * Keep track of the last visited onboarding page * Fix email subscription bug * Fix overflow issue for tags page * Remove height to prevent page container scrolling * Check for CoC and T&C for displaying onboarding * Add InstantClick redirect and preserve referrer in client * Fix async update + check by using localStorage * Turn off onboarding for tests * Finalize design for onboarding * Finalize design for onboarding * Make bulk follows during onboarding * Fix bulk follow test
200 lines
5.6 KiB
JavaScript
200 lines
5.6 KiB
JavaScript
import { h, Component } from 'preact';
|
|
import heartImage from 'images/emoji/emoji-one-heart.png';
|
|
import unicornImage from 'images/emoji/emoji-one-unicorn.png';
|
|
import bookmarkImage from 'images/emoji/emoji-one-bookmark.png';
|
|
import openLink from 'images/external-link-logo.svg';
|
|
|
|
export default class Article extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
reactionCounts: [],
|
|
userReactions: [],
|
|
optimisticUserReaction: null,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
fetch(`/reactions?article_id=${this.props.resource.id}`, {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
credentials: 'same-origin',
|
|
})
|
|
.then(response => response.json())
|
|
.then(this.displayReactions)
|
|
.catch(this.displayReactionsFailure);
|
|
}
|
|
|
|
displayReactions = response => {
|
|
this.setState({ userReactions: response.reactions });
|
|
};
|
|
|
|
displayReactionsFailure = response => {
|
|
console.log(response);
|
|
};
|
|
|
|
handleNewReactionResponse = response => {
|
|
let oldUserReactions = this.state.userReactions;
|
|
const foundReactions = oldUserReactions.filter(obj => {
|
|
return obj.category === response.category;
|
|
});
|
|
if (foundReactions.length === 0 && response.result === 'create') {
|
|
oldUserReactions.push({ category: response.category });
|
|
} else {
|
|
oldUserReactions = oldUserReactions.filter(obj => {
|
|
return obj.category != response.category;
|
|
});
|
|
}
|
|
this.setState({
|
|
userReactions: oldUserReactions,
|
|
optimisticUserReaction: null,
|
|
});
|
|
};
|
|
|
|
handleNewReactionFailure = response => {
|
|
console.log(response);
|
|
};
|
|
|
|
handleReactionClick = e => {
|
|
e.preventDefault();
|
|
const { target } = e;
|
|
this.setState({ optimisticUserReaction: target.dataset.category });
|
|
const article = this.props.resource;
|
|
fetch('/reactions', {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'X-CSRF-Token': window.csrfToken,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
reactable_type: 'Article',
|
|
reactable_id: article.id,
|
|
category: target.dataset.category,
|
|
}),
|
|
credentials: 'same-origin',
|
|
})
|
|
.then(response => response.json())
|
|
.then(this.handleNewReactionResponse)
|
|
.catch(this.handleNewReactionFailure);
|
|
};
|
|
|
|
actionButton = props => {
|
|
const types = {
|
|
heart: ['heart-reaction-button', 'like', heartImage],
|
|
unicorn: ['unicorn-reaction-button', 'unicorn', unicornImage],
|
|
readinglist: [
|
|
'readinglist-reaction-button',
|
|
'readinglist',
|
|
bookmarkImage,
|
|
],
|
|
};
|
|
|
|
const curType = types[props.reaction];
|
|
|
|
return (
|
|
<button
|
|
className={`${curType[0]} ${props.reactedClass}`}
|
|
onClick={this.handleReactionClick}
|
|
data-category={curType[1]}
|
|
>
|
|
<img
|
|
src={curType[2]}
|
|
data-category={curType[1]}
|
|
alt={`${curType[1]} reaction`}
|
|
/>
|
|
</button>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const article = this.props.resource;
|
|
let heartReactedClass = '';
|
|
let unicornReactedClass = '';
|
|
let bookmarkReactedClass = '';
|
|
const { state } = this;
|
|
state.userReactions.forEach(reaction => {
|
|
if (
|
|
reaction.category === 'like' ||
|
|
state.optimisticUserReaction === 'like'
|
|
) {
|
|
heartReactedClass = 'active';
|
|
}
|
|
if (
|
|
reaction.category === 'unicorn' ||
|
|
state.optimisticUserReaction === 'unicorn'
|
|
) {
|
|
unicornReactedClass = 'active';
|
|
}
|
|
if (
|
|
reaction.category === 'readinglist' ||
|
|
state.optimisticUserReaction === 'readinglist'
|
|
) {
|
|
bookmarkReactedClass = 'active';
|
|
}
|
|
});
|
|
let coverImage = '';
|
|
if (article.cover_image) {
|
|
coverImage = (
|
|
<section>
|
|
<div
|
|
className="image image-final"
|
|
style={{ backgroundImage: `url(${article.cover_image}` }}
|
|
/>
|
|
</section>
|
|
);
|
|
}
|
|
return (
|
|
<div className="activechatchannel__activeArticle">
|
|
<div className="activechatchannel__activeArticleDetails">
|
|
<a href={article.path} target="_blank" rel="noopener noreferrer">
|
|
<span className="activechatchannel__activeArticleDetailsPath">
|
|
{article.path}
|
|
</span>
|
|
</a>
|
|
</div>
|
|
<div className="container">
|
|
{coverImage}
|
|
<div className="title">
|
|
<h1>{article.title}</h1>
|
|
<h3>
|
|
<a
|
|
href={`/${article.user.username}`}
|
|
className="author"
|
|
data-content={`/users/${article.user.id}`}
|
|
>
|
|
<img
|
|
className="profile-pic"
|
|
src={article.user.profile_image_90}
|
|
alt={article.user.username}
|
|
/>
|
|
<span>{article.user.name} </span>
|
|
<span className="published-at">
|
|
{' '}
|
|
| {article.readable_publish_date}
|
|
</span>
|
|
</a>
|
|
</h3>
|
|
</div>
|
|
<div className="body">
|
|
<div dangerouslySetInnerHTML={{ __html: article.body_html }} />
|
|
</div>
|
|
</div>
|
|
<div className="activechatchannel__activeArticleActions">
|
|
<this.actionButton
|
|
reaction="heart"
|
|
reactedClass={heartReactedClass}
|
|
/>
|
|
<this.actionButton
|
|
reaction="unicorn"
|
|
reactedClass={unicornReactedClass}
|
|
/>
|
|
<this.actionButton
|
|
reaction="readinglist"
|
|
reactedClass={bookmarkReactedClass}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|