Adding Coral-Plugin-Like

This commit is contained in:
Belen Curcio
2017-05-01 10:55:50 -03:00
parent ffcb1b464f
commit ef40174be9
10 changed files with 412 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"presets": [
"es2015"
],
"plugins": [
"add-module-exports",
"transform-class-properties",
"transform-decorators-legacy",
"transform-object-assign",
"transform-object-rest-spread",
"transform-async-to-generator",
"transform-react-jsx"
]
}
@@ -0,0 +1,23 @@
{
"env": {
"browser": true,
"es6": true,
"mocha": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}
@@ -0,0 +1,6 @@
import React from 'react';
import cn from 'classnames';
export default ({className}) => (
<i className={cn('fa', 'fa-handshake-o', className)} aria-hidden="true"/>
);
@@ -0,0 +1,69 @@
import React, {Component} from 'react';
import styles from './style.css';
import Icon from './Icon';
import {I18n} from 'coral-framework';
import cn from 'classnames';
import translations from '../translations.json';
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
const lang = new I18n(translations);
class RespectButton extends Component {
handleClick = () => {
const {postRespect, showSignInDialog, deleteAction} = this.props;
const {root: {me}, comment} = this.props;
const myRespectActionSummary = getMyActionSummary('RespectActionSummary', comment);
// If the current user does not exist, trigger sign in dialog.
if (!me) {
showSignInDialog();
return;
}
// If the current user is banned, do nothing.
if (me.status === 'BANNED') {
return;
}
if (myRespectActionSummary) {
deleteAction(myRespectActionSummary.current_user.id, comment.id);
} else {
postRespect({
item_id: comment.id,
item_type: 'COMMENTS'
});
}
}
render() {
const {comment} = this.props;
if (!comment) {
return null;
}
const myRespect = getMyActionSummary('RespectActionSummary', comment);
let count = getTotalActionCount('RespectActionSummary', comment);
return (
<div className={styles.respect}>
<button
className={cn(styles.button, {[styles.respected]: myRespect})}
onClick={this.handleClick} >
<span>{lang.t(myRespect ? 'respected' : 'respect')}</span>
<Icon className={cn(styles.icon, {[styles.respected]: myRespect})} />
{count > 0 && count}
</button>
</div>
);
}
}
RespectButton.propTypes = {
data: React.PropTypes.object.isRequired
};
export default RespectButton;
@@ -0,0 +1,30 @@
.respect {
display: inline-block;
}
.button {
color: #2a2a2a;
margin: 5px 10px 5px 0px;
background: none;
padding: 0px;
border: none;
font-size: inherit;
&:hover {
color: #767676;
cursor: pointer;
}
&.respected {
color: #c98211;
&:hover {
color: #e59614;
cursor: pointer;
}
}
}
.icon {
padding: 0 5px;
}
@@ -0,0 +1,163 @@
import {compose, gql, graphql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import get from 'lodash/get';
import withFragments from 'coral-framework/hocs/withFragments';
import {showSignInDialog} from 'coral-framework/actions/auth';
import RespectButton from '../components/LikeButton';
const isRespectAction = (a) => a.__typename === 'RespectActionSummary';
const COMMENT_FRAGMENT = gql`
fragment RespectButton_updateFragment on Comment {
action_summaries {
... on RespectActionSummary {
count
current_user {
id
}
}
}
}
`;
const withDeleteAction = graphql(gql`
mutation deleteAction($id: ID!) {
deleteAction(id:$id) {
errors {
translation_key
}
}
}
`, {
props: ({mutate}) => ({
deleteAction: (id, commentId) => {
return mutate({
variables: {id},
optimisticResponse: {
deleteAction: {
__typename: 'DeleteActionResponse',
errors: null,
}
},
update: (proxy) => {
const fragmentId = `Comment_${commentId}`;
// Read the data from our cache for this query.
const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId});
// Check whether we respected this comment.
const idx = data.action_summaries.findIndex(isRespectAction);
if (idx < 0 || get(data.action_summaries[idx], 'current_user.id') !== id) {
return;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count - 1,
current_user: null,
};
// Write our data back to the cache.
proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data});
},
});
},
}),
});
const withPostRespect = graphql(gql`
mutation createRespect($respect: CreateRespectInput!) {
createRespect(respect: $respect) {
respect {
id
}
errors {
translation_key
}
}
}
`, {
props: ({mutate}) => ({
postRespect: (respect) => {
return mutate({
variables: {respect},
optimisticResponse: {
createRespect: {
__typename: 'CreateRespectResponse',
errors: null,
respect: {
__typename: 'RespectAction',
id: 'pending',
},
}
},
update: (proxy, mutationResult) => {
const fragmentId = `Comment_${respect.item_id}`;
// Read the data from our cache for this query.
const data = proxy.readFragment({fragment: COMMENT_FRAGMENT, id: fragmentId});
// Add our comment from the mutation to the end.
let idx = data.action_summaries.findIndex(isRespectAction);
// Check whether we already respected this comment.
if (idx >= 0 && data.action_summaries[idx].current_user) {
return;
}
if (idx < 0) {
// Add initial action when it doesn't exist.
data.action_summaries.push({
__typename: 'RespectActionSummary',
count: 0,
current_user: null,
});
idx = data.action_summaries.length - 1;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count + 1,
current_user: mutationResult.data.createRespect.respect,
};
// Write our data back to the cache.
proxy.writeFragment({fragment: COMMENT_FRAGMENT, id: fragmentId, data});
},
});
},
}),
});
const mapDispatchToProps = dispatch =>
bindActionCreators({showSignInDialog}, dispatch);
const enhance = compose(
withFragments({
root: gql`
fragment RespectButton_root on RootQuery {
me {
status
}
}
`,
comment: gql`
fragment RespectButton_comment on Comment {
action_summaries {
... on RespectActionSummary {
count
current_user {
id
}
}
}
}`,
}),
connect(null, mapDispatchToProps),
withDeleteAction,
withPostRespect,
);
export default enhance(RespectButton);
@@ -0,0 +1,7 @@
import LikeButton from './containers/LikeButton';
export default {
slots: {
commentDetail: [LikeButton],
}
};
@@ -0,0 +1,10 @@
{
"en": {
"respect": "Respect",
"respected": "Respected"
},
"es": {
"respect": "Respeto",
"respected": "Respetado"
}
}
+36
View File
@@ -0,0 +1,36 @@
const {readFileSync} = require('fs');
const path = require('path');
const wrapResponse = require('../../graph/helpers/response');
module.exports = {
typeDefs: readFileSync(path.join(__dirname, 'server/typeDefs.graphql'), 'utf8'),
resolvers: {
RootMutation: {
createRespect(_, {like: {item_id, item_type}}, {mutators: {Action}}) {
return wrapResponse('like')(Action.create({item_id, item_type, action_type: 'LIKE'}));
}
}
},
hooks: {
Action: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'LIKE':
return 'LikeAction';
}
}
}
},
ActionSummary: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'LIKE':
return 'LikeActionSummary';
}
}
}
}
}
};
@@ -0,0 +1,54 @@
enum ACTION_TYPE {
# Represents a Like.
LIKE
}
input CreateLikeInput {
# The item's id for which we are to create a like.
item_id: ID!
# The type of the item for which we are to create the like.
item_type: ACTION_ITEM_TYPE!
}
# LikeAction is used by users who "like" a specific entity.
type LikeAction implements Action {
# The ID of the action.
id: ID!
# The author of the action.
user: User
# The time when the Action was updated.
updated_at: Date
# The time when the Action was created.
created_at: Date
}
type LikeActionSummary implements ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: LikeAction
}
type CreateLikeResponse implements Response {
# The like that was created.
like: LikeAction
# An array of errors relating to the mutation that occurred.
errors: [UserError]
}
type RootMutation {
# Creates a like on an entity.
createLike(like: CreateLikeInput!): CreateLikeResponse
}