diff --git a/static/app-strings.json b/static/app-strings.json
index 600ca762d..9a9fbc5a1 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -2319,5 +2319,53 @@
":WHAT:": ":WHAT:",
":WOODOO_DOLL:": ":WOODOO_DOLL:",
"Global Emotes": "Global Emotes",
+ ":CAT:": ":CAT:",
+ ":FAIL:": ":FAIL:",
+ ":HYPE:": ":HYPE:",
+ ":PANTS_1:": ":PANTS_1:",
+ ":PANTS_2:": ":PANTS_2:",
+ ":PISS:": ":PISS:",
+ ":PREGNANT_MAN_ASIA:": ":PREGNANT_MAN_ASIA:",
+ ":PREGNANT_MAN_BLACK_HAIR:": ":PREGNANT_MAN_BLACK_HAIR:",
+ ":PREGNANT_MAN_BLACK_SKIN:": ":PREGNANT_MAN_BLACK_SKIN:",
+ ":PREGNANT_MAN_BLONDE:": ":PREGNANT_MAN_BLONDE:",
+ ":PREGNANT_MAN_RED_HAIR:": ":PREGNANT_MAN_RED_HAIR:",
+ ":PREGNANT_WOMAN_BLACK_HAIR_GREEN_SHIRT:": ":PREGNANT_WOMAN_BLACK_HAIR_GREEN_SHIRT:",
+ ":PREGNANT_WOMAN_BLACK_HAIR:": ":PREGNANT_WOMAN_BLACK_HAIR:",
+ ":PREGNANT_WOMAN_BLACK_SKIN:": ":PREGNANT_WOMAN_BLACK_SKIN:",
+ ":PREGNANT_WOMAN_BLONDE:": ":PREGNANT_WOMAN_BLONDE:",
+ ":PREGNANT_WOMAN_BROWN_HAIR:": ":PREGNANT_WOMAN_BROWN_HAIR:",
+ ":PREGNANT_WOMAN_RED_HAIR:": ":PREGNANT_WOMAN_RED_HAIR:",
+ ":ROCKET_SPACEMAN:": ":ROCKET_SPACEMAN:",
+ ":SICK_FLAME:": ":SICK_FLAME:",
+ ":SICK_SKULL:": ":SICK_SKULL:",
+ ":SLIME:": ":SLIME:",
+ ":SPHAGETTI_BATH:": ":SPHAGETTI_BATH:",
+ ":THUG_LIFE:": ":THUG_LIFE:",
+ ":WHUUT:": ":WHUUT:",
+ ":TIP_HAND_FLIP:": ":TIP_HAND_FLIP:",
+ ":TIP_HAND_FLIP_COIN:": ":TIP_HAND_FLIP_COIN:",
+ ":TIP_HAND_FLIP_LBC:": ":TIP_HAND_FLIP_LBC:",
+ ":COMET_TIP:": ":COMET_TIP:",
+ ":LBC_COMET_TIP:": ":LBC_COMET_TIP:",
+ ":SMALL_TIP:": ":SMALL_TIP:",
+ ":SILVER_ODYSEE_COIN:": ":SILVER_ODYSEE_COIN:",
+ ":SMALL_LBC_TIP:": ":SMALL_LBC_TIP:",
+ ":BITE_TIP:": ":BITE_TIP:",
+ ":BITE_TIP_CLOSEUP:": ":BITE_TIP_CLOSEUP:",
+ ":BITE_LBC_CLOSEUP:": ":BITE_LBC_CLOSEUP:",
+ ":MEDIUM_TIP:": ":MEDIUM_TIP:",
+ ":MEDIUM_LBC_TIP:": ":MEDIUM_LBC_TIP:",
+ ":LARGE_TIP:": ":LARGE_TIP:",
+ ":LARGE_LBC_TIP:": ":LARGE_LBC_TIP:",
+ ":BIG_TIP:": ":BIG_TIP:",
+ ":BIG_LBC_TIP:": ":BIG_LBC_TIP:",
+ ":FORTUNE_CHEST:": ":FORTUNE_CHEST:",
+ ":FORTUNE_CHEST_LBC:": ":FORTUNE_CHEST_LBC:",
+ "Stickers": "Stickers",
+ "Different Sticker": "Different Sticker",
+ "LBC": "LBC",
+ "Add a Card": "Add a Card",
+ " To Tip Creators": " To Tip Creators",
"--end--": "--end--"
}
diff --git a/ui/component/comment/view.jsx b/ui/component/comment/view.jsx
index 13a997128..46410a77e 100644
--- a/ui/component/comment/view.jsx
+++ b/ui/component/comment/view.jsx
@@ -26,6 +26,8 @@ import CommentCreate from 'component/commentCreate';
import CommentMenuList from 'component/commentMenuList';
import UriIndicator from 'component/uriIndicator';
import CreditAmount from 'component/common/credit-amount';
+import OptimizedImage from 'component/optimizedImage';
+import { parseSticker } from 'util/comments';
const AUTO_EXPAND_ALL_REPLIES = false;
@@ -130,6 +132,7 @@ function Comment(props: Props) {
const totalLikesAndDislikes = likesCount + dislikesCount;
const slimedToDeath = totalLikesAndDislikes >= 5 && dislikesCount / totalLikesAndDislikes > 0.8;
const commentByOwnerOfContent = claim && claim.signing_channel && claim.signing_channel.permanent_url === authorUri;
+ const stickerFromMessage = parseSticker(message);
let channelOwnerOfContent;
try {
@@ -324,6 +327,10 @@ function Comment(props: Props) {
setDisplayDeadComment(true)} className="comment__dead">
{__('This comment was slimed to death.')}
+ ) : stickerFromMessage ? (
+
+
+
) : editedMessage.length >= LENGTH_TO_COLLAPSE ? (
{
const claim = selectClaimForUri(state, props.uri);
@@ -28,8 +28,8 @@ const select = (state, props) => {
};
const perform = (dispatch, ownProps) => ({
- createComment: (comment, claimId, parentId, txid, payment_intent_id, environment) =>
- dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, txid, payment_intent_id, environment)),
+ createComment: (comment, claimId, parentId, txid, payment_intent_id, environment, sticker) =>
+ dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri, txid, payment_intent_id, environment, sticker)),
sendTip: (params, callback, errorCallback) => dispatch(doSendTip(params, false, callback, errorCallback, false)),
doToast: (options) => dispatch(doToast(options)),
doFetchCreatorSettings: (channelClaimId) => dispatch(doFetchCreatorSettings(channelClaimId)),
diff --git a/ui/component/commentCreate/sticker-selector.jsx b/ui/component/commentCreate/sticker-selector.jsx
new file mode 100644
index 000000000..85fda7d82
--- /dev/null
+++ b/ui/component/commentCreate/sticker-selector.jsx
@@ -0,0 +1,94 @@
+// @flow
+import 'scss/component/_sticker-selector.scss';
+import { FREE_GLOBAL_STICKERS, PAID_GLOBAL_STICKERS } from 'constants/stickers';
+import * as ICONS from 'constants/icons';
+import Button from 'component/button';
+import CreditAmount from 'component/common/credit-amount';
+import OptimizedImage from 'component/optimizedImage';
+import React from 'react';
+
+const buildStickerSideLink = (section: string, icon: string) => ({ section, icon });
+
+const STICKER_SIDE_LINKS = [
+ buildStickerSideLink(__('Free'), ICONS.TAG),
+ buildStickerSideLink(__('Tips'), ICONS.FINANCE),
+ // Future work may include Channel, Subscriptions, ...
+];
+
+type Props = { claimIsMine: boolean, onSelect: (any) => void };
+
+export default function StickerSelector(props: Props) {
+ const { claimIsMine, onSelect } = props;
+
+ function scrollToStickerSection(section: string) {
+ const listBodyEl = document.querySelector('.stickerSelector__listBody');
+ const sectionToScroll = document.getElementById(section);
+
+ if (listBodyEl && sectionToScroll) {
+ // $FlowFixMe
+ listBodyEl.scrollTo({
+ top: sectionToScroll.offsetTop - sectionToScroll.getBoundingClientRect().height * 2,
+ behavior: 'smooth',
+ });
+ }
+ }
+
+ const getListRow = (rowTitle: string, rowStickers: any) => (
+
+
+ {rowTitle}
+
+
+ {rowStickers.map((sticker) => (
+
+ ))}
+
+
+ );
+
+ return (
+
+
+
+
+
+ {getListRow(__('Free'), FREE_GLOBAL_STICKERS)}
+ {!claimIsMine && getListRow(__('Tips'), PAID_GLOBAL_STICKERS)}
+
+
+
+
+ {STICKER_SIDE_LINKS.map(
+ (linkProps) =>
+ ((claimIsMine && linkProps.section !== 'Tips') || !claimIsMine) && (
+ -
+
+ )
+ )}
+
+
+
+
+ );
+}
diff --git a/ui/component/commentCreate/view.jsx b/ui/component/commentCreate/view.jsx
index 216a15d79..44b683332 100644
--- a/ui/component/commentCreate/view.jsx
+++ b/ui/component/commentCreate/view.jsx
@@ -1,9 +1,9 @@
// @flow
-import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import 'scss/component/_comment-create.scss';
+import { buildValidSticker } from 'util/comments';
+import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { FormField, Form } from 'component/common/form';
import { getChannelIdFromClaim } from 'util/claim';
-import { Lbryio } from 'lbryinc';
import { useHistory } from 'react-router';
import * as ICONS from 'constants/icons';
import * as KEYCODES from 'constants/keycodes';
@@ -15,96 +15,108 @@ import classnames from 'classnames';
import CreditAmount from 'component/common/credit-amount';
import EmoteSelector from './emote-selector';
import Empty from 'component/common/empty';
+import FilePrice from 'component/filePrice';
import I18nMessage from 'component/i18nMessage';
import Icon from 'component/common/icon';
+import OptimizedImage from 'component/optimizedImage';
import React from 'react';
import SelectChannel from 'component/selectChannel';
+import StickerSelector from './sticker-selector';
import type { ElementRef } from 'react';
import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'effects/use-persisted-state';
import WalletTipAmountSelector from 'component/walletTipAmountSelector';
import { getStripeEnvironment } from 'util/stripe';
-let stripeEnvironment = getStripeEnvironment();
+const stripeEnvironment = getStripeEnvironment();
const TAB_FIAT = 'TabFiat';
const TAB_LBC = 'TabLBC';
const MENTION_DEBOUNCE_MS = 100;
+// for sendCashTip REMOVE
+// type TipParams = { tipAmount: number, tipChannelName: string, channelClaimId: string };
+// type UserParams = { activeChannelName: ?string, activeChannelId: ?string };
+
type Props = {
- uri: string,
- claim: StreamClaim,
- hasChannels: boolean,
- isNested: boolean,
- isFetchingChannels: boolean,
- parentId: string,
- isReply: boolean,
activeChannel: string,
activeChannelClaim: ?ChannelClaim,
bottom: boolean,
- embed?: boolean,
+ hasChannels: boolean, //
+ claim: StreamClaim,
claimIsMine: boolean,
- supportDisabled: boolean,
+ isFetchingChannels: boolean,
+ isNested: boolean,
+ isReply: boolean,
+ parentId: string,
settingsByChannelId: { [channelId: string]: PerChannelSettings },
shouldFetchComment: boolean,
- doToast: ({ message: string }) => void,
- createComment: (string, string, string, ?string, ?string, ?string) => Promise,
- onDoneReplying?: () => void,
- onCancelReplying?: () => void,
- toast: (string) => void,
- sendTip: ({}, (any) => void, (any) => void) => void,
+ supportDisabled: boolean,
+ uri: string,
+ createComment: (string, string, string, ?string, ?string, ?string, ?boolean) => Promise,
doFetchCreatorSettings: (channelId: string) => Promise,
- setQuickReply: (any) => void,
+ doToast: ({ message: string }) => void,
fetchComment: (commentId: string) => Promise,
+ onCancelReplying?: () => void,
+ onDoneReplying?: () => void,
+ sendTip: ({}, (any) => void, (any) => void) => void,
+ setQuickReply: (any) => void,
+ toast: (string) => void,
};
export function CommentCreate(props: Props) {
const {
- uri,
- claim,
- hasChannels,
- isNested,
- isFetchingChannels,
- isReply,
- parentId,
activeChannelClaim,
bottom,
+ hasChannels,
+ claim,
claimIsMine,
+ isFetchingChannels,
+ isNested,
+ isReply,
+ parentId,
settingsByChannelId,
- supportDisabled,
shouldFetchComment,
- doToast,
+ supportDisabled,
+ uri,
createComment,
- onDoneReplying,
- onCancelReplying,
- sendTip,
doFetchCreatorSettings,
- setQuickReply,
+ doToast,
fetchComment,
+ onCancelReplying,
+ onDoneReplying,
+ sendTip,
+ setQuickReply,
} = props;
+
const formFieldRef: ElementRef = React.useRef();
const formFieldInputRef = formFieldRef && formFieldRef.current && formFieldRef.current.input;
const selectionIndex = formFieldInputRef && formFieldInputRef.current && formFieldInputRef.current.selectionStart;
const buttonRef: ElementRef = React.useRef();
+
const {
push,
location: { pathname },
} = useHistory();
- const [isSubmitting, setIsSubmitting] = React.useState(false);
+ const [isSubmitting, setSubmitting] = React.useState(false);
const [commentFailure, setCommentFailure] = React.useState(false);
const [successTip, setSuccessTip] = React.useState({ txid: undefined, tipAmount: undefined });
const [isSupportComment, setIsSupportComment] = React.useState();
- const [isReviewingSupportComment, setIsReviewingSupportComment] = React.useState();
+ const [isReviewingSupportComment, setReviewingSupportComment] = React.useState();
+ const [isReviewingStickerComment, setReviewingStickerComment] = React.useState();
+ const [selectedSticker, setSelectedSticker] = React.useState();
const [tipAmount, setTipAmount] = React.useState(1);
+ const [convertedAmount, setConvertedAmount] = React.useState();
const [commentValue, setCommentValue] = React.useState('');
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
+ const [stickerSelector, setStickerSelector] = React.useState();
const [activeTab, setActiveTab] = React.useState('');
const [tipError, setTipError] = React.useState();
const [deletedComment, setDeletedComment] = React.useState(false);
const [pauseQuickSend, setPauseQuickSend] = React.useState(false);
- const [shouldDisableReviewButton, setShouldDisableReviewButton] = React.useState();
const [showEmotes, setShowEmotes] = React.useState(false);
+ const [disableReviewButton, setDisableReviewButton] = React.useState();
const selectedMentionIndex =
commentValue.indexOf('@', selectionIndex) === selectionIndex
@@ -124,8 +136,7 @@ export function CommentCreate(props: Props) {
: '';
const claimId = claim && claim.claim_id;
- const signingChannel = (claim && claim.signing_channel) || claim;
- const channelUri = signingChannel && signingChannel.permanent_url;
+ const channelUri = claim && (claim.signing_channel ? claim.signing_channel.permanent_url : claim.permanent_url);
const charCount = commentValue ? commentValue.length : 0;
const disabled = deletedComment || isSubmitting || isFetchingChannels || !commentValue.length || pauseQuickSend;
const channelId = getChannelIdFromClaim(claim);
@@ -138,31 +149,23 @@ export function CommentCreate(props: Props) {
const minAmountRef = React.useRef(minAmount);
minAmountRef.current = minAmount;
- const MinAmountNotice = minAmount ? (
-
- }}>
- {minTip ? 'Comment min: %lbc%' : minSuper ? 'HyperChat min: %lbc%' : ''}
-
-
-
- ) : null;
-
// **************************************************************************
// Functions
// **************************************************************************
+ function handleSelectSticker(sticker: any) {
+ // $FlowFixMe
+ setSelectedSticker(sticker);
+ setReviewingStickerComment(true);
+ setTipAmount(sticker.price || 0);
+ setStickerSelector(false);
+
+ if (sticker.price && sticker.price > 0) {
+ setActiveTab(TAB_FIAT);
+ setIsSupportComment(true);
+ }
+ }
+
function handleCommentChange(event) {
let commentValue;
if (isReply) {
@@ -203,9 +206,7 @@ export function CommentCreate(props: Props) {
}
function handleSupportComment() {
- if (!activeChannelClaim) {
- return;
- }
+ if (!activeChannelClaim) return;
if (!channelId) {
doToast({
@@ -233,7 +234,7 @@ export function CommentCreate(props: Props) {
message: __('The creator just updated the minimum setting. Please revise or double-check your tip amount.'),
isError: true,
});
- setIsReviewingSupportComment(false);
+ setReviewingSupportComment(false);
return;
}
@@ -242,33 +243,18 @@ export function CommentCreate(props: Props) {
}
function doSubmitTip() {
- if (!activeChannelClaim) {
- return;
- }
+ if (!activeChannelClaim) return;
- const params = {
- amount: tipAmount,
- claim_id: claimId,
- channel_id: activeChannelClaim.claim_id,
- };
-
- const activeChannelName = activeChannelClaim && activeChannelClaim.name;
- const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
+ const params = { amount: tipAmount, claim_id: claimId, channel_id: activeChannelClaim.claim_id };
+ // FIAT ONLY - REMOVE
+ // const activeChannelName = activeChannelClaim && activeChannelClaim.name;
+ // const activeChannelId = activeChannelClaim && activeChannelClaim.claim_id;
// setup variables for tip API
- let channelClaimId, tipChannelName;
- // if there is a signing channel it's on a file
- if (claim.signing_channel) {
- channelClaimId = claim.signing_channel.claim_id;
- tipChannelName = claim.signing_channel.name;
+ // const channelClaimId = claim.signing_channel ? claim.signing_channel.claim_id : claim.claim_id;
+ const tipChannelName = claim.signing_channel ? claim.signing_channel.name : claim.name;
- // otherwise it's on the channel page
- } else {
- channelClaimId = claim.claim_id;
- tipChannelName = claim.name;
- }
-
- setIsSubmitting(true);
+ setSubmitting(true);
if (activeTab === TAB_LBC) {
// call sendTip and then run the callback from the response
@@ -296,59 +282,24 @@ export function CommentCreate(props: Props) {
},
() => {
// reset the frontend so people can send a new comment
- setIsSubmitting(false);
+ setSubmitting(false);
}
);
} else {
- const sourceClaimId = claim.claim_id;
- const roundedAmount = Math.round(tipAmount * 100) / 100;
-
- Lbryio.call(
- 'customer',
- 'tip',
- {
- // round to deal with floating point precision
- amount: Math.round(100 * roundedAmount), // convert from dollars to cents
- creator_channel_name: tipChannelName, // creator_channel_name
- creator_channel_claim_id: channelClaimId,
- tipper_channel_name: activeChannelName,
- tipper_channel_claim_id: activeChannelId,
- currency: 'USD',
- anonymous: false,
- source_claim_id: sourceClaimId,
- environment: stripeEnvironment,
- },
- 'post'
- )
- .then((customerTipResponse) => {
- const paymentIntendId = customerTipResponse.payment_intent_id;
-
- handleCreateComment(null, paymentIntendId, stripeEnvironment);
-
- setCommentValue('');
- setIsReviewingSupportComment(false);
- setIsSupportComment(false);
- setCommentFailure(false);
- setIsSubmitting(false);
-
- doToast({
- message: __("You sent $%formattedAmount% as a tip to %tipChannelName%, I'm sure they appreciate it!", {
- formattedAmount: roundedAmount.toFixed(2), // force show decimal places
- tipChannelName,
- }),
- });
-
- // handleCreateComment(null);
- })
- .catch((error) => {
- doToast({
- message:
- error.message !== 'payment intent failed to confirm'
- ? error.message
- : 'Sorry, there was an error in processing your payment!',
- isError: true,
- });
- });
+ // No cash tips
+ // const tipParams: TipParams = { tipAmount: Math.round(tipAmount * 100) / 100, tipChannelName, channelClaimId };
+ // const userParams: UserParams = { activeChannelName, activeChannelId };
+ // sendCashTip(tipParams, userParams, claim.claim_id, stripeEnvironment, (customerTipResponse) => {
+ // const { payment_intent_id } = customerTipResponse;
+ //
+ // handleCreateComment(null, payment_intent_id, stripeEnvironment);
+ //
+ // setCommentValue('');
+ // setReviewingSupportComment(false);
+ // setIsSupportComment(false);
+ // setCommentFailure(false);
+ // setSubmitting(false);
+ // });
}
}
@@ -360,16 +311,17 @@ export function CommentCreate(props: Props) {
*/
function handleCreateComment(txid, payment_intent_id, environment) {
setShowEmotes(false);
- setIsSubmitting(true);
+ setSubmitting(true);
+ const stickerValue = selectedSticker && buildValidSticker(selectedSticker.name);
- createComment(commentValue, claimId, parentId, txid, payment_intent_id, environment)
+ createComment(stickerValue || commentValue, claimId, parentId, txid, payment_intent_id, environment, !!stickerValue)
.then((res) => {
- setIsSubmitting(false);
+ setSubmitting(false);
if (setQuickReply) setQuickReply(res);
if (res && res.signature) {
setCommentValue('');
- setIsReviewingSupportComment(false);
+ setReviewingSupportComment(false);
setIsSupportComment(false);
setCommentFailure(false);
@@ -379,7 +331,7 @@ export function CommentCreate(props: Props) {
}
})
.catch(() => {
- setIsSubmitting(false);
+ setSubmitting(false);
setCommentFailure(true);
if (channelId) {
@@ -426,6 +378,10 @@ export function CommentCreate(props: Props) {
// Render
// **************************************************************************
+ const getActionButton = (title: string, icon: string, handleClick: () => void) => (
+
+ );
+
if (channelSettings && !channelSettings.comments_enabled) {
return ;
}
@@ -447,24 +403,106 @@ export function CommentCreate(props: Props) {
);
}
- if (isReviewingSupportComment && activeChannelClaim) {
- return (
-