import React from 'react';
import { string, arrayOf, bool, func, number } from 'prop-types';
import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
import { dropWhile } from 'lodash';
import classNames from 'classnames';
import { Avatar, InlineTextButton, ReviewRating } from '../../components';
import { formatDate } from '../../util/dates';
import { ensureTransaction, ensureUser, ensureListing, userDisplayName } from '../../util/data';
import * as propTypes from '../../util/propTypes';
import * as log from '../../util/log';
import css from './ActivityFeed.css';
const Message = props => {
const { message, intl } = props;
const todayString = intl.formatMessage({ id: 'ActivityFeed.today' });
return (
{message.attributes.content}
{formatDate(intl, todayString, message.attributes.at)}
);
};
Message.propTypes = {
message: propTypes.message.isRequired,
intl: intlShape.isRequired,
};
const OwnMessage = props => {
const { message, intl } = props;
const todayString = intl.formatMessage({ id: 'ActivityFeed.today' });
return (
{message.attributes.content}
{formatDate(intl, todayString, message.attributes.at)}
);
};
OwnMessage.propTypes = {
message: propTypes.message.isRequired,
intl: intlShape.isRequired,
};
const Review = props => {
const { content, rating } = props;
return (
);
};
Review.propTypes = {
content: string.isRequired,
rating: number.isRequired,
};
// Check if a transition is the kind that
// should be rendered in he ActivityFeed
const shouldRenderTransition = transition => {
return [
propTypes.TX_TRANSITION_PREAUTHORIZE,
propTypes.TX_TRANSITION_PREAUTHORIZE_ENQUIRY,
propTypes.TX_TRANSITION_ACCEPT,
propTypes.TX_TRANSITION_DECLINE,
propTypes.TX_TRANSITION_AUTO_DECLINE,
propTypes.TX_TRANSITION_CANCEL,
propTypes.TX_TRANSITION_MARK_DELIVERED,
propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_FIRST,
propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_SECOND,
propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_FIRST,
propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_SECOND,
].includes(transition);
};
// Check if a user giving a review is related to
// given tx transition.
const isReviewTransition = transition => {
return [
propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_FIRST,
propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_FIRST,
propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_SECOND,
propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_SECOND,
].includes(transition);
};
const hasUserLeftAReviewFirst = (userRole, lastTransition) => {
return (
(lastTransition === propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_FIRST &&
userRole === propTypes.TX_TRANSITION_ACTOR_CUSTOMER) ||
(lastTransition === propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_FIRST &&
userRole === propTypes.TX_TRANSITION_ACTOR_PROVIDER) ||
propTypes.areReviewsCompleted(lastTransition)
);
};
const resolveTransitionMessage = (
transition,
lastTransition,
listingTitle,
ownRole,
otherUsersName,
intl,
onOpenReviewModal
) => {
const isOwnTransition = transition.by === ownRole;
const currentTransition = transition.transition;
const displayName = otherUsersName;
const deliveredState = lastTransition === propTypes.TX_TRANSITION_MARK_DELIVERED;
switch (currentTransition) {
case propTypes.TX_TRANSITION_PREAUTHORIZE:
case propTypes.TX_TRANSITION_PREAUTHORIZE_ENQUIRY:
return isOwnTransition ? (
) : (
);
case propTypes.TX_TRANSITION_ACCEPT:
return isOwnTransition ? (
) : (
);
case propTypes.TX_TRANSITION_DECLINE:
return isOwnTransition ? (
) : (
);
case propTypes.TX_TRANSITION_AUTO_DECLINE:
return ownRole === propTypes.TX_TRANSITION_ACTOR_PROVIDER ? (
) : (
);
case propTypes.TX_TRANSITION_CANCEL:
return ;
case propTypes.TX_TRANSITION_MARK_DELIVERED:
// Show the leave a review link if the state is delivered or
// if current user is not the first to leave a review
const reviewLink =
deliveredState || !hasUserLeftAReviewFirst(ownRole, lastTransition) ? (
) : null;
return ;
case propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_FIRST:
case propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_FIRST:
if (isOwnTransition) {
return ;
} else {
// show the leave a review link if current user is not the first
// one to leave a review
const reviewLink = !hasUserLeftAReviewFirst(ownRole, lastTransition) ? (
) : null;
return (
);
}
case propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_SECOND:
case propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_SECOND:
if (isOwnTransition) {
return ;
} else {
return (
);
}
default:
log.error(new Error('Unknown transaction transition type'), 'unknown-transition-type', {
transitionType: currentTransition,
});
return '';
}
};
const reviewByAuthorId = (transaction, userId) => {
return transaction.reviews.filter(r => r.author.id.uuid === userId.uuid)[0];
};
const Transition = props => {
const { transition, transaction, currentUser, intl, onOpenReviewModal } = props;
const currentTransaction = ensureTransaction(transaction);
const customer = currentTransaction.customer;
const provider = currentTransaction.provider;
const deletedListing = intl.formatMessage({
id: 'ActivityFeed.deletedListing',
});
const listingTitle = currentTransaction.listing.attributes.deleted
? deletedListing
: currentTransaction.listing.attributes.title;
const lastTransition = currentTransaction.attributes.lastTransition;
const ownRole =
currentUser.id.uuid === customer.id.uuid
? propTypes.TX_TRANSITION_ACTOR_CUSTOMER
: propTypes.TX_TRANSITION_ACTOR_PROVIDER;
const bannedUserDisplayName = intl.formatMessage({
id: 'ActivityFeed.bannedUserDisplayName',
});
const otherUsersName =
ownRole === propTypes.TX_TRANSITION_ACTOR_CUSTOMER
? userDisplayName(provider, bannedUserDisplayName)
: userDisplayName(customer, bannedUserDisplayName);
const transitionMessage = resolveTransitionMessage(
transition,
lastTransition,
listingTitle,
ownRole,
otherUsersName,
intl,
onOpenReviewModal
);
const currentTransition = transition.transition;
let reviewComponent = null;
if (isReviewTransition(currentTransition) && propTypes.areReviewsCompleted(lastTransition)) {
const customerReview =
currentTransition === propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_FIRST ||
currentTransition === propTypes.TX_TRANSITION_REVIEW_BY_CUSTOMER_SECOND;
const providerReview =
currentTransition === propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_FIRST ||
currentTransition === propTypes.TX_TRANSITION_REVIEW_BY_PROVIDER_SECOND;
if (customerReview) {
const review = reviewByAuthorId(currentTransaction, customer.id);
reviewComponent = (
);
} else if (providerReview) {
const review = reviewByAuthorId(currentTransaction, provider.id);
reviewComponent = (
);
}
}
const todayString = intl.formatMessage({ id: 'ActivityFeed.today' });
return (
{transitionMessage}
{formatDate(intl, todayString, transition.at)}
{reviewComponent}
);
};
Transition.propTypes = {
transition: propTypes.txTransition.isRequired,
transaction: propTypes.transaction.isRequired,
currentUser: propTypes.currentUser.isRequired,
intl: intlShape.isRequired,
onOpenReviewModal: func.isRequired,
};
const EmptyTransition = () => {
return (
);
};
const isMessage = item => item && item.type === 'message';
// Compare function for sorting an array containint messages and transitions
const compareItems = (a, b) => {
const itemDate = item => (isMessage(item) ? item.attributes.at : item.at);
return itemDate(a) - itemDate(b);
};
const organizedItems = (messages, transitions, hideOldTransitions) => {
const items = messages.concat(transitions).sort(compareItems);
if (hideOldTransitions) {
// Hide transitions that happened before the oldest message. Since
// we have older items (messages) that we are not showing, seeing
// old transitions would be confusing.
return dropWhile(items, i => !isMessage(i));
} else {
return items;
}
};
export const ActivityFeedComponent = props => {
const {
rootClassName,
className,
messages,
transaction,
currentUser,
hasOlderMessages,
onOpenReviewModal,
onShowOlderMessages,
fetchMessagesInProgress,
intl,
} = props;
const classes = classNames(rootClassName || css.root, className);
const currentTransaction = ensureTransaction(transaction);
const transitions = currentTransaction.attributes.transitions
? currentTransaction.attributes.transitions
: [];
const currentCustomer = ensureUser(currentTransaction.customer);
const currentProvider = ensureUser(currentTransaction.provider);
const currentListing = ensureListing(currentTransaction.listing);
const transitionsAvailable = !!(
currentUser &&
currentUser.id &&
currentCustomer.id &&
currentProvider.id &&
currentListing.id
);
// combine messages and transaction transitions
const items = organizedItems(messages, transitions, hasOlderMessages || fetchMessagesInProgress);
const transitionComponent = transition => {
if (transitionsAvailable) {
return (
);
} else {
return ;
}
};
const messageComponent = message => {
const isOwnMessage =
message.sender &&
message.sender.id &&
currentUser &&
currentUser.id &&
message.sender.id.uuid === currentUser.id.uuid;
if (isOwnMessage) {
return ;
}
return ;
};
const messageListItem = message => {
return (
{messageComponent(message)}
);
};
const transitionListItem = transition => {
if (shouldRenderTransition(transition.transition)) {
return (
{transitionComponent(transition)}
);
} else {
return null;
}
};
return (
{hasOlderMessages ? (
-
) : null}
{items.map(item => {
if (isMessage(item)) {
return messageListItem(item);
} else {
return transitionListItem(item);
}
})}
);
};
ActivityFeedComponent.defaultProps = {
rootClassName: null,
className: null,
};
ActivityFeedComponent.propTypes = {
rootClassName: string,
className: string,
currentUser: propTypes.currentUser,
transaction: propTypes.transaction,
messages: arrayOf(propTypes.message),
hasOlderMessages: bool.isRequired,
onOpenReviewModal: func.isRequired,
onShowOlderMessages: func.isRequired,
fetchMessagesInProgress: bool.isRequired,
// from injectIntl
intl: intlShape.isRequired,
};
const ActivityFeed = injectIntl(ActivityFeedComponent);
export default ActivityFeed;