Stickers/emojis fall out / improvements (#220)
* Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons
This commit is contained in:
parent
7cae754867
commit
fc2e2d2cfc
21 changed files with 389 additions and 275 deletions
|
@ -343,7 +343,7 @@ function Comment(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
) : stickerFromMessage ? (
|
) : stickerFromMessage ? (
|
||||||
<div className="sticker__comment">
|
<div className="sticker__comment">
|
||||||
<OptimizedImage src={stickerFromMessage.url} waitLoad />
|
<OptimizedImage src={stickerFromMessage.url} waitLoad loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
) : editedMessage.length >= LENGTH_TO_COLLAPSE ? (
|
) : editedMessage.length >= LENGTH_TO_COLLAPSE ? (
|
||||||
<Expandable>
|
<Expandable>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import 'scss/component/_emote-selector.scss';
|
import 'scss/component/_emote-selector.scss';
|
||||||
import { EMOTES_24px as EMOTES } from 'constants/emotes';
|
import { EMOTES_48px as EMOTES } from 'constants/emotes';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import EMOJIS from 'emoji-dictionary';
|
import EMOJIS from 'emoji-dictionary';
|
||||||
|
@ -32,8 +32,6 @@ export default function EmoteSelector(props: Props) {
|
||||||
|
|
||||||
<div className="emoteSelector__list">
|
<div className="emoteSelector__list">
|
||||||
<div className="emoteSelector__listRow">
|
<div className="emoteSelector__listRow">
|
||||||
<div className="emoteSelector__listRowTitle">{__('Global Emotes')}</div>
|
|
||||||
|
|
||||||
<div className="emoteSelector__listRowItems">
|
<div className="emoteSelector__listRowItems">
|
||||||
{OLD_QUICK_EMOJIS.map((emoji) => (
|
{OLD_QUICK_EMOJIS.map((emoji) => (
|
||||||
<Button
|
<Button
|
||||||
|
@ -50,7 +48,7 @@ export default function EmoteSelector(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={String(emote)}
|
key={emoteName}
|
||||||
title={emoteName}
|
title={emoteName}
|
||||||
button="alt"
|
button="alt"
|
||||||
className="button--file-action"
|
className="button--file-action"
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function StickerSelector(props: Props) {
|
||||||
className="button--file-action"
|
className="button--file-action"
|
||||||
onClick={() => onSelect(sticker)}
|
onClick={() => onSelect(sticker)}
|
||||||
>
|
>
|
||||||
<OptimizedImage src={sticker.url} waitLoad />
|
<OptimizedImage src={sticker.url} waitLoad loading="lazy" />
|
||||||
{sticker.price && sticker.price > 0 && (
|
{sticker.price && sticker.price > 0 && (
|
||||||
<CreditAmount superChatLight amount={sticker.price} size={2} isFiat />
|
<CreditAmount superChatLight amount={sticker.price} size={2} isFiat />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { buildValidSticker } from 'util/comments';
|
||||||
import { FF_MAX_CHARS_IN_COMMENT, FF_MAX_CHARS_IN_LIVESTREAM_COMMENT } from 'constants/form-field';
|
import { FF_MAX_CHARS_IN_COMMENT, FF_MAX_CHARS_IN_LIVESTREAM_COMMENT } from 'constants/form-field';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
import { getChannelIdFromClaim } from 'util/claim';
|
import { getChannelIdFromClaim } from 'util/claim';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
import { SIMPLE_SITE } from 'config';
|
import { SIMPLE_SITE } from 'config';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
@ -123,6 +124,8 @@ export function CommentCreate(props: Props) {
|
||||||
const [pauseQuickSend, setPauseQuickSend] = React.useState(false);
|
const [pauseQuickSend, setPauseQuickSend] = React.useState(false);
|
||||||
const [showEmotes, setShowEmotes] = React.useState(false);
|
const [showEmotes, setShowEmotes] = React.useState(false);
|
||||||
const [disableReviewButton, setDisableReviewButton] = React.useState();
|
const [disableReviewButton, setDisableReviewButton] = React.useState();
|
||||||
|
const [exchangeRate, setExchangeRate] = React.useState();
|
||||||
|
const [canReceiveFiatTip, setCanReceiveFiatTip] = React.useState(undefined);
|
||||||
|
|
||||||
const selectedMentionIndex =
|
const selectedMentionIndex =
|
||||||
commentValue.indexOf('@', selectionIndex) === selectionIndex
|
commentValue.indexOf('@', selectionIndex) === selectionIndex
|
||||||
|
@ -152,6 +155,7 @@ export function CommentCreate(props: Props) {
|
||||||
const minTip = (channelSettings && channelSettings.min_tip_amount_comment) || 0;
|
const minTip = (channelSettings && channelSettings.min_tip_amount_comment) || 0;
|
||||||
const minAmount = minTip || minSuper || 0;
|
const minAmount = minTip || minSuper || 0;
|
||||||
const minAmountMet = minAmount === 0 || tipAmount >= minAmount;
|
const minAmountMet = minAmount === 0 || tipAmount >= minAmount;
|
||||||
|
const stickerPrice = selectedSticker && selectedSticker.price;
|
||||||
|
|
||||||
const minAmountRef = React.useRef(minAmount);
|
const minAmountRef = React.useRef(minAmount);
|
||||||
minAmountRef.current = minAmount;
|
minAmountRef.current = minAmount;
|
||||||
|
@ -168,7 +172,7 @@ export function CommentCreate(props: Props) {
|
||||||
setStickerSelector(false);
|
setStickerSelector(false);
|
||||||
|
|
||||||
if (sticker.price && sticker.price > 0) {
|
if (sticker.price && sticker.price > 0) {
|
||||||
setActiveTab(TAB_FIAT);
|
setActiveTab(canReceiveFiatTip ? TAB_FIAT : TAB_LBC);
|
||||||
setIsSupportComment(true);
|
setIsSupportComment(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +320,7 @@ export function CommentCreate(props: Props) {
|
||||||
if (setQuickReply) setQuickReply(res);
|
if (setQuickReply) setQuickReply(res);
|
||||||
|
|
||||||
if (res && res.signature) {
|
if (res && res.signature) {
|
||||||
setCommentValue('');
|
if (!stickerValue) setCommentValue('');
|
||||||
setReviewingSupportComment(false);
|
setReviewingSupportComment(false);
|
||||||
setIsSupportComment(false);
|
setIsSupportComment(false);
|
||||||
setCommentFailure(false);
|
setCommentFailure(false);
|
||||||
|
@ -370,12 +374,45 @@ export function CommentCreate(props: Props) {
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [pauseQuickSend]);
|
}, [pauseQuickSend]);
|
||||||
|
|
||||||
|
// Stickers: Get LBC-USD exchange rate if hasn't yet and selected a paid sticker
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (stickerPrice && !exchangeRate) Lbryio.getExchangeRates().then(({ LBC_USD }) => setExchangeRate(LBC_USD));
|
||||||
|
}, [exchangeRate, stickerPrice]);
|
||||||
|
|
||||||
|
// Stickers: Check if creator has a tip account saved (on selector so that if a paid sticker is selected,
|
||||||
|
// it defaults to LBC tip instead of USD)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!stripeEnvironment || !stickerSelector || canReceiveFiatTip !== undefined) return;
|
||||||
|
|
||||||
|
const channelClaimId = claim.signing_channel ? claim.signing_channel.claim_id : claim.claim_id;
|
||||||
|
const tipChannelName = claim.signing_channel ? claim.signing_channel.name : claim.name;
|
||||||
|
|
||||||
|
Lbryio.call(
|
||||||
|
'account',
|
||||||
|
'check',
|
||||||
|
{
|
||||||
|
channel_claim_id: channelClaimId,
|
||||||
|
channel_name: tipChannelName,
|
||||||
|
environment: stripeEnvironment,
|
||||||
|
},
|
||||||
|
'post'
|
||||||
|
)
|
||||||
|
.then((accountCheckResponse) => {
|
||||||
|
if (accountCheckResponse === true && canReceiveFiatTip !== true) {
|
||||||
|
setCanReceiveFiatTip(true);
|
||||||
|
} else {
|
||||||
|
setCanReceiveFiatTip(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}, [canReceiveFiatTip, claim.claim_id, claim.name, claim.signing_channel, stickerSelector]);
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// Render
|
// Render
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
const getActionButton = (title: string, icon: string, handleClick: () => void) => (
|
const getActionButton = (title: string, label?: string, icon: string, handleClick: () => void) => (
|
||||||
<Button title={title} button="alt" icon={icon} onClick={handleClick} />
|
<Button title={title} label={label} button="alt" icon={icon} onClick={handleClick} />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (channelSettings && !channelSettings.comments_enabled) {
|
if (channelSettings && !channelSettings.comments_enabled) {
|
||||||
|
@ -410,6 +447,7 @@ export function CommentCreate(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
|
onSubmit={() => {}}
|
||||||
className={classnames('commentCreate', {
|
className={classnames('commentCreate', {
|
||||||
'commentCreate--reply': isReply,
|
'commentCreate--reply': isReply,
|
||||||
'commentCreate--nestedReply': isNested,
|
'commentCreate--nestedReply': isNested,
|
||||||
|
@ -427,10 +465,15 @@ export function CommentCreate(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="commentCreate__stickerPreviewImage">
|
<div className="commentCreate__stickerPreviewImage">
|
||||||
<OptimizedImage src={selectedSticker && selectedSticker.url} waitLoad />
|
<OptimizedImage src={selectedSticker && selectedSticker.url} waitLoad loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedSticker.price && <FilePrice customPrice={selectedSticker.price} isFiat />}
|
{selectedSticker.price && exchangeRate && (
|
||||||
|
<FilePrice
|
||||||
|
customPrices={{ priceFiat: selectedSticker.price, priceLBC: selectedSticker.price / exchangeRate }}
|
||||||
|
isFiat
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : isReviewingSupportComment && activeChannelClaim ? (
|
) : isReviewingSupportComment && activeChannelClaim ? (
|
||||||
<div className="commentCreate__supportCommentPreview">
|
<div className="commentCreate__supportCommentPreview">
|
||||||
|
@ -499,13 +542,14 @@ export function CommentCreate(props: Props) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(isSupportComment || (isReviewingStickerComment && selectedSticker && selectedSticker.price)) && (
|
{(isSupportComment || (isReviewingStickerComment && stickerPrice)) && (
|
||||||
<WalletTipAmountSelector
|
<WalletTipAmountSelector
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
amount={tipAmount}
|
amount={tipAmount}
|
||||||
claim={claim}
|
claim={claim}
|
||||||
convertedAmount={convertedAmount}
|
convertedAmount={convertedAmount}
|
||||||
customTipAmount={selectedSticker && selectedSticker.price}
|
customTipAmount={stickerPrice}
|
||||||
|
exchangeRate={exchangeRate}
|
||||||
fiatConversion={selectedSticker && !!selectedSticker.price}
|
fiatConversion={selectedSticker && !!selectedSticker.price}
|
||||||
onChange={(amount) => setTipAmount(amount)}
|
onChange={(amount) => setTipAmount(amount)}
|
||||||
setConvertedAmount={setConvertedAmount}
|
setConvertedAmount={setConvertedAmount}
|
||||||
|
@ -536,14 +580,7 @@ export function CommentCreate(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
label={__('Send')}
|
label={__('Send')}
|
||||||
disabled={
|
disabled={isSupportComment && (tipError || disableReviewButton)}
|
||||||
(isSupportComment && (tipError || disableReviewButton)) ||
|
|
||||||
(selectedSticker &&
|
|
||||||
selectedSticker.price &&
|
|
||||||
(activeTab === TAB_FIAT
|
|
||||||
? tipAmount < selectedSticker.price
|
|
||||||
: convertedAmount && convertedAmount < selectedSticker.price))
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isSupportComment) {
|
if (isSupportComment) {
|
||||||
handleSupportComment();
|
handleSupportComment();
|
||||||
|
@ -591,33 +628,38 @@ export function CommentCreate(props: Props) {
|
||||||
{/** Stickers/Support Buttons **/}
|
{/** Stickers/Support Buttons **/}
|
||||||
{!supportDisabled && !stickerSelector && (
|
{!supportDisabled && !stickerSelector && (
|
||||||
<>
|
<>
|
||||||
{isReviewingStickerComment ? (
|
{getActionButton(
|
||||||
<Button
|
__('Stickers'),
|
||||||
button="alt"
|
isReviewingStickerComment ? __('Different Sticker') : undefined,
|
||||||
label={__('Different Sticker')}
|
ICONS.STICKER,
|
||||||
onClick={() => {
|
() => {
|
||||||
setReviewingStickerComment(false);
|
if (isReviewingStickerComment) setReviewingStickerComment(false);
|
||||||
setIsSupportComment(false);
|
|
||||||
setStickerSelector(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
getActionButton(__('Stickers'), ICONS.TAG, () => {
|
|
||||||
setIsSupportComment(false);
|
setIsSupportComment(false);
|
||||||
setStickerSelector(true);
|
setStickerSelector(true);
|
||||||
})
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!claimIsMine && (
|
||||||
|
<>
|
||||||
|
{(!isSupportComment || activeTab !== TAB_LBC) &&
|
||||||
|
getActionButton(__('LBC'), isSupportComment ? __('Switch to LBC') : undefined, ICONS.LBC, () => {
|
||||||
|
setIsSupportComment(true);
|
||||||
|
setActiveTab(TAB_LBC);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{stripeEnvironment &&
|
||||||
|
(!isSupportComment || activeTab !== TAB_FIAT) &&
|
||||||
|
getActionButton(
|
||||||
|
__('Cash'),
|
||||||
|
isSupportComment ? __('Switch to Cash') : undefined,
|
||||||
|
ICONS.FINANCE,
|
||||||
|
() => {
|
||||||
|
setIsSupportComment(true);
|
||||||
|
setActiveTab(TAB_FIAT);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{!claimIsMine &&
|
|
||||||
getActionButton(__('LBC'), ICONS.LBC, () => {
|
|
||||||
setIsSupportComment(true);
|
|
||||||
setActiveTab(TAB_LBC);
|
|
||||||
})}
|
|
||||||
{!claimIsMine &&
|
|
||||||
stripeEnvironment &&
|
|
||||||
getActionButton(__('Cash'), ICONS.FINANCE, () => {
|
|
||||||
setIsSupportComment(true);
|
|
||||||
setActiveTab(TAB_FIAT);
|
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -635,7 +677,7 @@ export function CommentCreate(props: Props) {
|
||||||
if (isSupportComment || isReviewingSupportComment) {
|
if (isSupportComment || isReviewingSupportComment) {
|
||||||
if (!isReviewingSupportComment) setIsSupportComment(false);
|
if (!isReviewingSupportComment) setIsSupportComment(false);
|
||||||
setReviewingSupportComment(false);
|
setReviewingSupportComment(false);
|
||||||
if (selectedSticker && selectedSticker.price) {
|
if (stickerPrice) {
|
||||||
setReviewingStickerComment(false);
|
setReviewingStickerComment(false);
|
||||||
setStickerSelector(false);
|
setStickerSelector(false);
|
||||||
setSelectedSticker(null);
|
setSelectedSticker(null);
|
||||||
|
|
|
@ -1,102 +1,112 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import { formatCredits, formatFullPrice } from 'util/format-credits';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import LbcSymbol from 'component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
import { formatCredits, formatFullPrice } from 'util/format-credits';
|
import React from 'react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
amount: number,
|
amount?: number,
|
||||||
|
className?: string,
|
||||||
|
customAmounts?: { amountFiat: number, amountLBC: number },
|
||||||
|
fee?: boolean,
|
||||||
|
isEstimate?: boolean,
|
||||||
|
isFiat?: boolean,
|
||||||
|
noFormat?: boolean,
|
||||||
precision: number,
|
precision: number,
|
||||||
showFree: boolean,
|
showFree: boolean,
|
||||||
showFullPrice: boolean,
|
showFullPrice: boolean,
|
||||||
showPlus: boolean,
|
|
||||||
isEstimate?: boolean,
|
|
||||||
showLBC?: boolean,
|
showLBC?: boolean,
|
||||||
fee?: boolean,
|
showPlus: boolean,
|
||||||
className?: string,
|
|
||||||
noFormat?: boolean,
|
|
||||||
size?: number,
|
size?: number,
|
||||||
superChat?: boolean,
|
superChat?: boolean,
|
||||||
superChatLight?: boolean,
|
superChatLight?: boolean,
|
||||||
isFiat?: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class CreditAmount extends React.PureComponent<Props> {
|
class CreditAmount extends React.PureComponent<Props> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
noFormat: false,
|
||||||
precision: 2,
|
precision: 2,
|
||||||
showFree: false,
|
showFree: false,
|
||||||
showFullPrice: false,
|
showFullPrice: false,
|
||||||
showPlus: false,
|
|
||||||
showLBC: true,
|
showLBC: true,
|
||||||
noFormat: false,
|
showPlus: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
amount,
|
amount,
|
||||||
precision,
|
|
||||||
showFullPrice,
|
|
||||||
showFree,
|
|
||||||
showPlus,
|
|
||||||
isEstimate,
|
|
||||||
fee,
|
|
||||||
showLBC,
|
|
||||||
className,
|
className,
|
||||||
|
customAmounts,
|
||||||
|
fee,
|
||||||
|
isEstimate,
|
||||||
|
isFiat,
|
||||||
noFormat,
|
noFormat,
|
||||||
|
precision,
|
||||||
|
showFree,
|
||||||
|
showFullPrice,
|
||||||
|
showLBC,
|
||||||
|
showPlus,
|
||||||
size,
|
size,
|
||||||
superChat,
|
superChat,
|
||||||
superChatLight,
|
superChatLight,
|
||||||
isFiat,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const minimumRenderableAmount = 10 ** (-1 * precision);
|
const minimumRenderableAmount = 10 ** (-1 * precision);
|
||||||
|
|
||||||
// return null, otherwise it will try and convert undefined to a string
|
// return null, otherwise it will try and convert undefined to a string
|
||||||
if (amount === undefined) {
|
if (amount === undefined && customAmounts === undefined) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const fullPrice = formatFullPrice(amount, 2);
|
|
||||||
const isFree = parseFloat(amount) === 0;
|
|
||||||
|
|
||||||
let formattedAmount;
|
function getAmountText(amount: number, isFiat?: boolean) {
|
||||||
if (showFullPrice) {
|
const fullPrice = formatFullPrice(amount, 2);
|
||||||
formattedAmount = fullPrice;
|
const isFree = parseFloat(amount) === 0;
|
||||||
} else {
|
let formattedAmount;
|
||||||
formattedAmount =
|
|
||||||
amount > 0 && amount < minimumRenderableAmount
|
|
||||||
? `<${minimumRenderableAmount}`
|
|
||||||
: formatCredits(amount, precision, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let amountText;
|
if (showFullPrice) {
|
||||||
if (showFree && isFree) {
|
formattedAmount = fullPrice;
|
||||||
amountText = __('Free');
|
} else {
|
||||||
} else {
|
formattedAmount =
|
||||||
amountText = noFormat ? amount : formattedAmount;
|
amount > 0 && amount < minimumRenderableAmount
|
||||||
|
? `<${minimumRenderableAmount}`
|
||||||
if (showPlus && amount > 0) {
|
: formatCredits(amount, precision, true);
|
||||||
amountText = `+${amountText}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showLBC && !isFiat) {
|
if (showFree && isFree) {
|
||||||
amountText = <LbcSymbol postfix={amountText} size={size} />;
|
return __('Free');
|
||||||
} else if (showLBC && isFiat) {
|
} else {
|
||||||
amountText = <p style={{ display: 'inline' }}> ${(Math.round(Number(amountText) * 100) / 100).toFixed(2)}</p>;
|
let amountText = noFormat ? amount : formattedAmount;
|
||||||
}
|
|
||||||
|
|
||||||
if (fee) {
|
if (showPlus && amount > 0) {
|
||||||
amountText = __('%amount% fee', { amount: amountText });
|
amountText = `+${amountText}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLBC && !isFiat) {
|
||||||
|
amountText = <LbcSymbol postfix={amountText} size={size} />;
|
||||||
|
} else if (showLBC && isFiat) {
|
||||||
|
amountText = <p style={{ display: 'inline' }}> ${(Math.round(Number(amountText) * 100) / 100).toFixed(2)}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fee) {
|
||||||
|
amountText = __('%amount% fee', { amount: amountText });
|
||||||
|
}
|
||||||
|
|
||||||
|
return amountText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
title={fullPrice}
|
title={amount ? formatFullPrice(amount, 2) : ''}
|
||||||
className={classnames(className, {
|
className={classnames(className, {
|
||||||
'super-chat': superChat,
|
'super-chat': superChat,
|
||||||
'super-chat--light': superChatLight,
|
'super-chat--light': superChatLight,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span className="credit-amount">{amountText}</span>
|
{customAmounts
|
||||||
|
? Object.values(customAmounts).map((amount, index) => (
|
||||||
|
<span key={String(amount)} className="credit-amount">
|
||||||
|
{getAmountText(Number(amount), !index)}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
: amount && <span className="credit-amount">{getAmountText(amount, isFiat)}</span>}
|
||||||
|
|
||||||
{isEstimate ? (
|
{isEstimate ? (
|
||||||
<span className="credit-amount__estimate" title={__('This is an estimate and does not include data fees')}>
|
<span className="credit-amount__estimate" title={__('This is an estimate and does not include data fees')}>
|
||||||
|
|
|
@ -239,7 +239,7 @@ export class FormField extends React.PureComponent<Props> {
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
/>
|
/>
|
||||||
<div className="form-field__textarea-info">
|
<div className="form-field__textarea-info">
|
||||||
{!noEmojis && (
|
{!noEmojis && openEmoteMenu && (
|
||||||
<Button
|
<Button
|
||||||
type="alt"
|
type="alt"
|
||||||
className="button--file-action"
|
className="button--file-action"
|
||||||
|
|
|
@ -2534,4 +2534,14 @@ export const icons = {
|
||||||
<line x1="15" y1="9" x2="15.01" y2="9" />
|
<line x1="15" y1="9" x2="15.01" y2="9" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.STICKER]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<path d="M7.13,9a.38.38,0,1,1-.38.38A.38.38,0,0,1,7.13,9" />
|
||||||
|
<path d="M5.51,15.42A7.34,7.34,0,0,0,12,19.34a7.83,7.83,0,0,0,.92-.06" />
|
||||||
|
<path d="M23.24,11.52A11.25,11.25,0,1,0,12,23.25h.5" />
|
||||||
|
<path d="M14.45,9.66a2.31,2.31,0,0,1,3.91,0" />
|
||||||
|
<line x1="23.24" y1="11.52" x2="12.5" y2="23.24" />
|
||||||
|
<path d="M12.5,23.24v-1A10.74,10.74,0,0,1,23.24,11.52" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,10 @@ import OptimizedImage from 'component/optimizedImage';
|
||||||
|
|
||||||
const RE_EMOTE = /:\+1:|:-1:|:[\w-]+:/;
|
const RE_EMOTE = /:\+1:|:-1:|:[\w-]+:/;
|
||||||
|
|
||||||
|
function isEmote(title, src) {
|
||||||
|
return title && RE_EMOTE.test(title) && src.includes('static.odycdn.com/emoticons');
|
||||||
|
}
|
||||||
|
|
||||||
type SimpleTextProps = {
|
type SimpleTextProps = {
|
||||||
children?: React.Node,
|
children?: React.Node,
|
||||||
};
|
};
|
||||||
|
@ -103,8 +107,8 @@ const SimpleImageLink = (props: ImageLinkProps) => {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (title && RE_EMOTE.test(title) && src.includes('static.odycdn.com/emoticons')) {
|
if (isEmote(title, src)) {
|
||||||
return <OptimizedImage title={title} src={src} />;
|
return <OptimizedImage src={src} title={title} className="emote" waitLoad loading="lazy" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -194,18 +198,19 @@ const MarkdownPreview = (props: MarkdownProps) => {
|
||||||
),
|
),
|
||||||
// Workaraund of remarkOptions.Fragment
|
// Workaraund of remarkOptions.Fragment
|
||||||
div: React.Fragment,
|
div: React.Fragment,
|
||||||
img: isStakeEnoughForPreview(stakedLevel)
|
img: (imgProps) =>
|
||||||
? ZoomableImage
|
isStakeEnoughForPreview(stakedLevel) && !isEmote(imgProps.title, imgProps.src) ? (
|
||||||
: (imgProps) => (
|
ZoomableImage
|
||||||
<SimpleImageLink
|
) : (
|
||||||
src={imgProps.src}
|
<SimpleImageLink
|
||||||
alt={imgProps.alt}
|
src={imgProps.src}
|
||||||
title={imgProps.title}
|
alt={imgProps.alt}
|
||||||
helpText={
|
title={imgProps.title}
|
||||||
SIMPLE_SITE ? __("This channel isn't staking enough LBRY Credits for inline image previews.") : ''
|
helpText={
|
||||||
}
|
SIMPLE_SITE ? __("This channel isn't staking enough LBRY Credits for inline image previews.") : ''
|
||||||
/>
|
}
|
||||||
),
|
/>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ type Props = {
|
||||||
type?: string,
|
type?: string,
|
||||||
uri: string,
|
uri: string,
|
||||||
// below props are just passed to <CreditAmount />
|
// below props are just passed to <CreditAmount />
|
||||||
customPrice: number,
|
customPrices?: { priceFiat: number, priceLBC: number },
|
||||||
hideFree?: boolean, // hide the file price if it's free
|
hideFree?: boolean, // hide the file price if it's free
|
||||||
isFiat?: boolean,
|
isFiat?: boolean,
|
||||||
showLBC?: boolean,
|
showLBC?: boolean,
|
||||||
|
@ -50,10 +50,10 @@ class FilePrice extends React.PureComponent<Props> {
|
||||||
claimWasPurchased,
|
claimWasPurchased,
|
||||||
type,
|
type,
|
||||||
claimIsMine,
|
claimIsMine,
|
||||||
customPrice,
|
customPrices,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!customPrice && (claimIsMine || !costInfo || !costInfo.cost || (!costInfo.cost && hideFree))) return null;
|
if (!customPrices && (claimIsMine || !costInfo || !costInfo.cost || (!costInfo.cost && hideFree))) return null;
|
||||||
|
|
||||||
const className = classnames(claimWasPurchased ? 'filePrice__key' : 'filePrice', {
|
const className = classnames(claimWasPurchased ? 'filePrice__key' : 'filePrice', {
|
||||||
'filePrice--filepage': type === 'filepage',
|
'filePrice--filepage': type === 'filepage',
|
||||||
|
@ -66,7 +66,10 @@ class FilePrice extends React.PureComponent<Props> {
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<CreditAmount
|
<CreditAmount
|
||||||
amount={costInfo ? costInfo.cost : customPrice}
|
amount={costInfo ? costInfo.cost : undefined}
|
||||||
|
customAmounts={
|
||||||
|
customPrices ? { amountFiat: customPrices.priceFiat, amountLBC: customPrices.priceLBC } : undefined
|
||||||
|
}
|
||||||
className={className}
|
className={className}
|
||||||
isEstimate={!!costInfo && !costInfo.includesData}
|
isEstimate={!!costInfo && !costInfo.includesData}
|
||||||
isFiat={isFiat}
|
isFiat={isFiat}
|
||||||
|
|
|
@ -64,7 +64,7 @@ function LivestreamComment(props: Props) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="livestream-comment__body">
|
<div className="livestream-comment__body">
|
||||||
{(supportAmount > 0 || Boolean(stickerFromMessage)) && <ChannelThumbnail uri={authorUri} xsmall />}
|
{supportAmount > 0 && <ChannelThumbnail uri={authorUri} xsmall />}
|
||||||
<div
|
<div
|
||||||
className={classnames('livestream-comment__info', {
|
className={classnames('livestream-comment__info', {
|
||||||
'livestream-comment__info--sticker': Boolean(stickerFromMessage),
|
'livestream-comment__info--sticker': Boolean(stickerFromMessage),
|
||||||
|
@ -113,7 +113,7 @@ function LivestreamComment(props: Props) {
|
||||||
|
|
||||||
{stickerFromMessage ? (
|
{stickerFromMessage ? (
|
||||||
<div className="sticker__comment">
|
<div className="sticker__comment">
|
||||||
<OptimizedImage src={stickerFromMessage.url} waitLoad />
|
<OptimizedImage src={stickerFromMessage.url} waitLoad loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="livestream-comment__text">
|
<div className="livestream-comment__text">
|
||||||
|
|
|
@ -284,7 +284,7 @@ export default function LivestreamComments(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
{stickerSuperChats.includes(superChat) && getStickerUrl(superChat.comment) && (
|
{stickerSuperChats.includes(superChat) && getStickerUrl(superChat.comment) && (
|
||||||
<div className="livestream-superchat__info--image">
|
<div className="livestream-superchat__info--image">
|
||||||
<OptimizedImage src={getStickerUrl(superChat.comment)} waitLoad />
|
<OptimizedImage src={getStickerUrl(superChat.comment)} waitLoad loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,9 @@ import { connect } from 'react-redux';
|
||||||
import { doReadNotifications, doDeleteNotification } from 'redux/actions/notifications';
|
import { doReadNotifications, doDeleteNotification } from 'redux/actions/notifications';
|
||||||
import Notification from './view';
|
import Notification from './view';
|
||||||
|
|
||||||
export default connect(null, {
|
const perform = (dispatch, ownProps) => ({
|
||||||
doReadNotifications,
|
readNotification: () => dispatch(doReadNotifications([ownProps.notification.id])),
|
||||||
doDeleteNotification,
|
deleteNotification: () => dispatch(doDeleteNotification(ownProps.notification.id)),
|
||||||
})(Notification);
|
});
|
||||||
|
|
||||||
|
export default connect(null, perform)(Notification);
|
||||||
|
|
|
@ -1,39 +1,42 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import * as ICONS from 'constants/icons';
|
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
|
import { PAGE_VIEW_QUERY, DISCUSSION_PAGE } from 'page/channel/view';
|
||||||
|
import { parseSticker } from 'util/comments';
|
||||||
|
import { parseURI } from 'util/lbryURI';
|
||||||
import { RULE } from 'constants/notifications';
|
import { RULE } from 'constants/notifications';
|
||||||
import React from 'react';
|
import { useHistory } from 'react-router';
|
||||||
import classnames from 'classnames';
|
import * as ICONS from 'constants/icons';
|
||||||
import Icon from 'component/common/icon';
|
import * as PAGES from 'constants/pages';
|
||||||
import DateTime from 'component/dateTime';
|
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import classnames from 'classnames';
|
||||||
import { useHistory } from 'react-router';
|
|
||||||
import { parseURI } from 'util/lbryURI';
|
|
||||||
import { PAGE_VIEW_QUERY, DISCUSSION_PAGE } from 'page/channel/view';
|
|
||||||
import FileThumbnail from 'component/fileThumbnail';
|
|
||||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
|
||||||
import NotificationContentChannelMenu from 'component/notificationContentChannelMenu';
|
|
||||||
import LbcMessage from 'component/common/lbc-message';
|
|
||||||
import UriIndicator from 'component/uriIndicator';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
import CommentReactions from 'component/commentReactions';
|
|
||||||
import CommentCreate from 'component/commentCreate';
|
import CommentCreate from 'component/commentCreate';
|
||||||
|
import CommentReactions from 'component/commentReactions';
|
||||||
import CommentsReplies from 'component/commentsReplies';
|
import CommentsReplies from 'component/commentsReplies';
|
||||||
|
import DateTime from 'component/dateTime';
|
||||||
|
import FileThumbnail from 'component/fileThumbnail';
|
||||||
|
import Icon from 'component/common/icon';
|
||||||
|
import LbcMessage from 'component/common/lbc-message';
|
||||||
|
import NotificationContentChannelMenu from 'component/notificationContentChannelMenu';
|
||||||
|
import OptimizedImage from 'component/optimizedImage';
|
||||||
|
import React from 'react';
|
||||||
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: WebNotification,
|
|
||||||
menuButton: boolean,
|
menuButton: boolean,
|
||||||
children: any,
|
notification: WebNotification,
|
||||||
doReadNotifications: ([number]) => void,
|
deleteNotification: () => void,
|
||||||
doDeleteNotification: (number) => void,
|
readNotification: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
export default function Notification(props: Props) {
|
||||||
const { notification, menuButton = false, doReadNotifications, doDeleteNotification } = props;
|
const { menuButton = false, notification, readNotification, deleteNotification } = props;
|
||||||
|
|
||||||
|
const { notification_rule, notification_parameters, is_read } = notification;
|
||||||
|
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
const { notification_rule, notification_parameters, is_read, id } = notification;
|
|
||||||
const [isReplying, setReplying] = React.useState(false);
|
const [isReplying, setReplying] = React.useState(false);
|
||||||
const [quickReply, setQuickReply] = React.useState();
|
const [quickReply, setQuickReply] = React.useState();
|
||||||
|
|
||||||
|
@ -42,28 +45,14 @@ export default function Notification(props: Props) {
|
||||||
notification_rule === RULE.COMMENT_REPLY ||
|
notification_rule === RULE.COMMENT_REPLY ||
|
||||||
notification_rule === RULE.CREATOR_COMMENT;
|
notification_rule === RULE.CREATOR_COMMENT;
|
||||||
const commentText = isCommentNotification && notification_parameters.dynamic.comment;
|
const commentText = isCommentNotification && notification_parameters.dynamic.comment;
|
||||||
|
const stickerFromComment = isCommentNotification && commentText && parseSticker(commentText);
|
||||||
|
const notificationTarget = getNotificationTarget();
|
||||||
|
|
||||||
let notificationTarget;
|
const creatorIcon = (channelUrl) => (
|
||||||
switch (notification_rule) {
|
<UriIndicator uri={channelUrl} link>
|
||||||
case RULE.DAILY_WATCH_AVAILABLE:
|
<ChannelThumbnail small uri={channelUrl} />
|
||||||
case RULE.DAILY_WATCH_REMIND:
|
</UriIndicator>
|
||||||
notificationTarget = `/$/${PAGES.CHANNELS_FOLLOWING}`;
|
);
|
||||||
break;
|
|
||||||
case RULE.MISSED_OUT:
|
|
||||||
case RULE.REWARDS_APPROVAL_PROMPT:
|
|
||||||
notificationTarget = `/$/${PAGES.REWARDS_VERIFY}?redirect=/$/${PAGES.REWARDS}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
notificationTarget = notification_parameters.device.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
const creatorIcon = (channelUrl) => {
|
|
||||||
return (
|
|
||||||
<UriIndicator uri={channelUrl} link>
|
|
||||||
<ChannelThumbnail small uri={channelUrl} />
|
|
||||||
</UriIndicator>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
let channelUrl;
|
let channelUrl;
|
||||||
let icon;
|
let icon;
|
||||||
switch (notification_rule) {
|
switch (notification_rule) {
|
||||||
|
@ -109,8 +98,7 @@ export default function Notification(props: Props) {
|
||||||
let channelName;
|
let channelName;
|
||||||
if (channelUrl) {
|
if (channelUrl) {
|
||||||
try {
|
try {
|
||||||
const { claimName } = parseURI(channelUrl);
|
({ claimName: channelName } = parseURI(channelUrl));
|
||||||
channelName = claimName;
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,25 +126,28 @@ export default function Notification(props: Props) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { isChannel } = parseURI(notificationTarget);
|
const { isChannel } = parseURI(notificationTarget);
|
||||||
if (isChannel) {
|
if (isChannel) urlParams.append(PAGE_VIEW_QUERY, DISCUSSION_PAGE);
|
||||||
urlParams.append(PAGE_VIEW_QUERY, DISCUSSION_PAGE);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
notificationLink += `?${urlParams.toString()}`;
|
notificationLink += `?${urlParams.toString()}`;
|
||||||
const navLinkProps = {
|
const navLinkProps = { to: notificationLink, onClick: (e) => e.stopPropagation() };
|
||||||
to: notificationLink,
|
|
||||||
onClick: (e) => e.stopPropagation(),
|
function getNotificationTarget() {
|
||||||
};
|
switch (notification_rule) {
|
||||||
|
case RULE.DAILY_WATCH_AVAILABLE:
|
||||||
|
case RULE.DAILY_WATCH_REMIND:
|
||||||
|
return `/$/${PAGES.CHANNELS_FOLLOWING}`;
|
||||||
|
case RULE.MISSED_OUT:
|
||||||
|
case RULE.REWARDS_APPROVAL_PROMPT:
|
||||||
|
return `/$/${PAGES.REWARDS_VERIFY}?redirect=/$/${PAGES.REWARDS}`;
|
||||||
|
default:
|
||||||
|
return notification_parameters.device.target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleNotificationClick() {
|
function handleNotificationClick() {
|
||||||
if (!is_read) {
|
if (!is_read) readNotification();
|
||||||
doReadNotifications([id]);
|
if (menuButton && notificationLink) push(notificationLink);
|
||||||
}
|
|
||||||
|
|
||||||
if (menuButton && notificationLink) {
|
|
||||||
push(notificationLink);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Wrapper = menuButton
|
const Wrapper = menuButton
|
||||||
|
@ -181,45 +172,40 @@ export default function Notification(props: Props) {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={classnames('notification__wrapper', { 'notification__wrapper--unread': !is_read })}>
|
||||||
className={classnames('notification__wrapper', {
|
|
||||||
'notification__wrapper--unread': !is_read,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<div className="notification__icon">{icon}</div>
|
<div className="notification__icon">{icon}</div>
|
||||||
|
|
||||||
<div className="notification__content-wrapper">
|
<div className="notificationContent__wrapper">
|
||||||
<div className="notification__content">
|
<div className="notification__content">
|
||||||
<div className="notification__text-wrapper">
|
<div className="notificationText__wrapper">
|
||||||
{!isCommentNotification && <div className="notification__title">{title}</div>}
|
<div className="notification__title">{title}</div>
|
||||||
|
|
||||||
{isCommentNotification && commentText ? (
|
{!commentText ? (
|
||||||
<>
|
<div
|
||||||
<div className="notification__title">{title}</div>
|
title={notification_parameters.device.text.replace(/\sLBC/g, ' Credits')}
|
||||||
<div title={commentText} className="notification__text">
|
className="notification__text"
|
||||||
{commentText}
|
>
|
||||||
</div>
|
<LbcMessage>{notification_parameters.device.text}</LbcMessage>
|
||||||
</>
|
</div>
|
||||||
|
) : stickerFromComment ? (
|
||||||
|
<div className="sticker__comment">
|
||||||
|
<OptimizedImage src={stickerFromComment.url} waitLoad loading="lazy" />
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<div title={commentText} className="notification__text">
|
||||||
<div
|
{commentText}
|
||||||
title={notification_parameters.device.text.replace(/\sLBC/g, ' Credits')}
|
</div>
|
||||||
className="notification__text"
|
|
||||||
>
|
|
||||||
<LbcMessage>{notification_parameters.device.text}</LbcMessage>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{notification_rule === RULE.NEW_CONTENT && (
|
{notification_rule === RULE.NEW_CONTENT && (
|
||||||
<FileThumbnail uri={notification_parameters.device.target} className="notification__content-thumbnail" />
|
<FileThumbnail uri={notification_parameters.device.target} className="notificationContent__thumbnail" />
|
||||||
)}
|
)}
|
||||||
{notification_rule === RULE.NEW_LIVESTREAM && (
|
{notification_rule === RULE.NEW_LIVESTREAM && (
|
||||||
<FileThumbnail
|
<FileThumbnail
|
||||||
thumbnail={notification_parameters.device.image_url}
|
thumbnail={notification_parameters.device.image_url}
|
||||||
className="notification__content-thumbnail"
|
className="notificationContent__thumbnail"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -227,10 +213,10 @@ export default function Notification(props: Props) {
|
||||||
<div className="notification__extra">
|
<div className="notification__extra">
|
||||||
{!is_read && (
|
{!is_read && (
|
||||||
<Button
|
<Button
|
||||||
className="notification__mark-seen"
|
className="notification__markSeen"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
doReadNotifications([id]);
|
readNotification();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -243,7 +229,7 @@ export default function Notification(props: Props) {
|
||||||
<div className="notification__menu">
|
<div className="notification__menu">
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className={'menu__button notification__menu-button'}
|
className="menu__button notification__menuButton"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -252,7 +238,7 @@ export default function Notification(props: Props) {
|
||||||
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
|
<Icon size={18} icon={ICONS.MORE_VERTICAL} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list">
|
<MenuList className="menu__list">
|
||||||
<MenuItem className="menu__link" onSelect={() => doDeleteNotification(id)}>
|
<MenuItem className="menu__link" onSelect={() => deleteNotification()}>
|
||||||
<Icon aria-hidden icon={ICONS.DELETE} />
|
<Icon aria-hidden icon={ICONS.DELETE} />
|
||||||
{__('Delete')}
|
{__('Delete')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
@ -26,6 +26,7 @@ type Props = {
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
convertedAmount?: number,
|
convertedAmount?: number,
|
||||||
customTipAmount?: number,
|
customTipAmount?: number,
|
||||||
|
exchangeRate?: any,
|
||||||
fiatConversion?: boolean,
|
fiatConversion?: boolean,
|
||||||
tipError: boolean,
|
tipError: boolean,
|
||||||
tipError: string,
|
tipError: string,
|
||||||
|
@ -44,6 +45,7 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
claim,
|
claim,
|
||||||
convertedAmount,
|
convertedAmount,
|
||||||
customTipAmount,
|
customTipAmount,
|
||||||
|
exchangeRate,
|
||||||
fiatConversion,
|
fiatConversion,
|
||||||
tipError,
|
tipError,
|
||||||
onChange,
|
onChange,
|
||||||
|
@ -56,10 +58,16 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
const [useCustomTip, setUseCustomTip] = usePersistedState('comment-support:useCustomTip', true);
|
const [useCustomTip, setUseCustomTip] = usePersistedState('comment-support:useCustomTip', true);
|
||||||
const [hasCardSaved, setHasSavedCard] = usePersistedState('comment-support:hasCardSaved', false);
|
const [hasCardSaved, setHasSavedCard] = usePersistedState('comment-support:hasCardSaved', false);
|
||||||
const [canReceiveFiatTip, setCanReceiveFiatTip] = React.useState(); // dont persist because it needs to be calc'd per creator
|
const [canReceiveFiatTip, setCanReceiveFiatTip] = React.useState(); // dont persist because it needs to be calc'd per creator
|
||||||
const [exchangeRate, setExchangeRate] = React.useState();
|
|
||||||
|
const convertToTwoDecimalsOrMore = (number: number, decimals: number = 2) =>
|
||||||
|
Number((Math.round(number * 10 ** decimals) / 10 ** decimals).toFixed(decimals));
|
||||||
|
|
||||||
const tipAmountsToDisplay =
|
const tipAmountsToDisplay =
|
||||||
customTipAmount && fiatConversion && activeTab === TAB_FIAT ? [customTipAmount] : DEFAULT_TIP_AMOUNTS;
|
customTipAmount && fiatConversion && activeTab === TAB_FIAT
|
||||||
|
? [customTipAmount]
|
||||||
|
: customTipAmount && exchangeRate
|
||||||
|
? [convertToTwoDecimalsOrMore(customTipAmount / exchangeRate)]
|
||||||
|
: DEFAULT_TIP_AMOUNTS;
|
||||||
|
|
||||||
// if it's fiat but there's no card saved OR the creator can't receive fiat tips
|
// if it's fiat but there's no card saved OR the creator can't receive fiat tips
|
||||||
const shouldDisableFiatSelectors = activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip);
|
const shouldDisableFiatSelectors = activeTab === TAB_FIAT && (!hasCardSaved || !canReceiveFiatTip);
|
||||||
|
@ -81,7 +89,7 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
((amount > balance || balance === 0) && activeTab !== TAB_FIAT) ||
|
((amount > balance || balance === 0) && activeTab !== TAB_FIAT) ||
|
||||||
shouldDisableFiatSelectors ||
|
shouldDisableFiatSelectors ||
|
||||||
(customTipAmount && fiatConversion && activeTab !== TAB_FIAT && exchangeRate
|
(customTipAmount && fiatConversion && activeTab !== TAB_FIAT && exchangeRate
|
||||||
? amount * exchangeRate < customTipAmount
|
? convertToTwoDecimalsOrMore(amount * exchangeRate) < customTipAmount
|
||||||
: customTipAmount && amount < customTipAmount)
|
: customTipAmount && amount < customTipAmount)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -95,19 +103,13 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToTwoDecimals(number: number) {
|
|
||||||
return (Math.round(number * 100) / 100).toFixed(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!exchangeRate) {
|
if (setConvertedAmount && exchangeRate && (!convertedAmount || convertedAmount !== amount * exchangeRate)) {
|
||||||
Lbryio.getExchangeRates().then(({ LBC_USD }) => setExchangeRate(LBC_USD));
|
|
||||||
} else if ((!convertedAmount || convertedAmount !== amount * exchangeRate) && setConvertedAmount) {
|
|
||||||
setConvertedAmount(amount * exchangeRate);
|
setConvertedAmount(amount * exchangeRate);
|
||||||
}
|
}
|
||||||
}, [amount, convertedAmount, exchangeRate, setConvertedAmount]);
|
}, [amount, convertedAmount, exchangeRate, setConvertedAmount]);
|
||||||
|
|
||||||
// check if creator has a payment method saved
|
// check if user has a payment method saved
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!stripeEnvironment) return;
|
if (!stripeEnvironment) return;
|
||||||
|
|
||||||
|
@ -129,6 +131,7 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
});
|
});
|
||||||
}, [setHasSavedCard]);
|
}, [setHasSavedCard]);
|
||||||
|
|
||||||
|
// check if creator has a tip account saved
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!stripeEnvironment) return;
|
if (!stripeEnvironment) return;
|
||||||
|
|
||||||
|
@ -171,6 +174,25 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
setTipError(__('Not enough Credits'));
|
setTipError(__('Not enough Credits'));
|
||||||
} else if (amount < MINIMUM_PUBLISH_BID) {
|
} else if (amount < MINIMUM_PUBLISH_BID) {
|
||||||
setTipError(__('Amount must be higher'));
|
setTipError(__('Amount must be higher'));
|
||||||
|
} else if (
|
||||||
|
convertedAmount &&
|
||||||
|
exchangeRate &&
|
||||||
|
customTipAmount &&
|
||||||
|
amount < convertToTwoDecimalsOrMore(customTipAmount / exchangeRate)
|
||||||
|
) {
|
||||||
|
regexp = RegExp(/^(\d*([.]\d{0,2})?)$/);
|
||||||
|
const validCustomTipInput = regexp.test(String(amount));
|
||||||
|
|
||||||
|
if (validCustomTipInput) {
|
||||||
|
setTipError(
|
||||||
|
__('Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%', {
|
||||||
|
input_amount: convertToTwoDecimalsOrMore(convertedAmount, 4),
|
||||||
|
price_amount: convertToTwoDecimalsOrMore(customTipAmount),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setTipError(__('Amount must have no more than 2 decimal places'));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setTipError(false);
|
setTipError(false);
|
||||||
}
|
}
|
||||||
|
@ -185,35 +207,43 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
setTipError(__('Amount must be at least one dollar'));
|
setTipError(__('Amount must be at least one dollar'));
|
||||||
} else if (amount > 1000) {
|
} else if (amount > 1000) {
|
||||||
setTipError(__('Amount cannot be over 1000 dollars'));
|
setTipError(__('Amount cannot be over 1000 dollars'));
|
||||||
|
} else if (customTipAmount && amount < customTipAmount) {
|
||||||
|
setTipError(
|
||||||
|
__('Amount is lower than price of $%price_amount%', {
|
||||||
|
price_amount: convertToTwoDecimalsOrMore(customTipAmount),
|
||||||
|
})
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setTipError(false);
|
setTipError(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [activeTab, amount, balance, setTipError]);
|
}, [activeTab, amount, balance, convertedAmount, customTipAmount, exchangeRate, setTipError]);
|
||||||
|
|
||||||
const getHelpMessage = (helpMessage: any) => <div className="help">{helpMessage}</div>;
|
const getHelpMessage = (helpMessage: any) => <div className="help">{helpMessage}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="section">
|
<div className="section">
|
||||||
{tipAmountsToDisplay.map((defaultAmount) => (
|
{tipAmountsToDisplay &&
|
||||||
<Button
|
tipAmountsToDisplay.map((defaultAmount) => (
|
||||||
key={defaultAmount}
|
<Button
|
||||||
disabled={shouldDisableAmountSelector(defaultAmount)}
|
key={defaultAmount}
|
||||||
button="alt"
|
disabled={shouldDisableAmountSelector(defaultAmount)}
|
||||||
className={classnames('button-toggle button-toggle--expandformobile', {
|
button="alt"
|
||||||
'button-toggle--active': defaultAmount === amount && !useCustomTip,
|
className={classnames('button-toggle button-toggle--expandformobile', {
|
||||||
'button-toggle--disabled': amount > balance,
|
'button-toggle--active':
|
||||||
})}
|
convertToTwoDecimalsOrMore(defaultAmount) === convertToTwoDecimalsOrMore(amount) && !useCustomTip,
|
||||||
label={defaultAmount}
|
'button-toggle--disabled': amount > balance,
|
||||||
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
})}
|
||||||
onClick={() => {
|
label={defaultAmount}
|
||||||
handleCustomPriceChange(defaultAmount);
|
icon={activeTab === TAB_LBC ? ICONS.LBC : ICONS.FINANCE}
|
||||||
setUseCustomTip(false);
|
onClick={() => {
|
||||||
}}
|
handleCustomPriceChange(defaultAmount);
|
||||||
/>
|
setUseCustomTip(false);
|
||||||
))}
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
|
@ -240,13 +270,13 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
fiatConversion &&
|
fiatConversion &&
|
||||||
activeTab !== TAB_FIAT &&
|
activeTab !== TAB_FIAT &&
|
||||||
getHelpMessage(
|
getHelpMessage(
|
||||||
__(
|
__('This support is priced in $USD.') +
|
||||||
`This support is priced in $USD. ${
|
(convertedAmount
|
||||||
convertedAmount
|
? ' ' +
|
||||||
? __(`The current exchange rate for the submitted amount is: $${convertToTwoDecimals(convertedAmount)}`)
|
__('The current exchange rate for the submitted LBC amount is ~ $%exchange_amount%.', {
|
||||||
: ''
|
exchange_amount: convertToTwoDecimalsOrMore(convertedAmount),
|
||||||
}`
|
})
|
||||||
)
|
: '')
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* custom number input form */}
|
{/* custom number input form */}
|
||||||
|
@ -274,7 +304,7 @@ function WalletTipAmountSelector(props: Props) {
|
||||||
? getHelpMessage(
|
? getHelpMessage(
|
||||||
<>
|
<>
|
||||||
<Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add a Card')} button="link" />
|
<Button navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`} label={__('Add a Card')} button="link" />
|
||||||
{__(' To Tip Creators')}
|
{' ' + __('To Tip Creators')}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
: !canReceiveFiatTip
|
: !canReceiveFiatTip
|
||||||
|
|
|
@ -9,24 +9,23 @@ const buildEmote = (name: string, path: string) => ({
|
||||||
|
|
||||||
const getEmotes = (px: string, multiplier: string) => [
|
const getEmotes = (px: string, multiplier: string) => [
|
||||||
buildEmote('ALIEN', `${px}/Alien${multiplier}.png`),
|
buildEmote('ALIEN', `${px}/Alien${multiplier}.png`),
|
||||||
|
buildEmote('ANGRY_1', `${px}/angry${multiplier}.png`),
|
||||||
buildEmote('ANGRY_2', `${px}/angry%202${multiplier}.png`),
|
buildEmote('ANGRY_2', `${px}/angry%202${multiplier}.png`),
|
||||||
buildEmote('ANGRY_3', `${px}/angry%203${multiplier}.png`),
|
buildEmote('ANGRY_3', `${px}/angry%203${multiplier}.png`),
|
||||||
buildEmote('ANGRY_4', `${px}/angry%204${multiplier}.png`),
|
buildEmote('ANGRY_4', `${px}/angry%204${multiplier}.png`),
|
||||||
buildEmote('ANGRY_1', `${px}/angry${multiplier}.png`),
|
|
||||||
buildEmote('BLIND', `${px}/blind${multiplier}.png`),
|
buildEmote('BLIND', `${px}/blind${multiplier}.png`),
|
||||||
buildEmote('BLOCK', `${px}/block${multiplier}.png`),
|
buildEmote('BLOCK', `${px}/block${multiplier}.png`),
|
||||||
buildEmote('BOMB', `${px}/bomb${multiplier}.png`),
|
buildEmote('BOMB', `${px}/bomb${multiplier}.png`),
|
||||||
buildEmote('BRAIN_CHIP', `${px}/Brain%20chip${multiplier}.png`),
|
buildEmote('BRAIN_CHIP', `${px}/Brain%20chip${multiplier}.png`),
|
||||||
buildEmote('CONFIRM', `${px}/CONFIRM${multiplier}.png`),
|
buildEmote('CONFIRM', `${px}/CONFIRM${multiplier}.png`),
|
||||||
buildEmote('CONFUSED_1', `${px}/confused-1${multiplier}.png`),
|
buildEmote('CONFUSED_1', `${px}/confused${multiplier}-1.png`),
|
||||||
buildEmote('CONFUSED_2', `${px}/confused${multiplier}.png`),
|
buildEmote('CONFUSED_2', `${px}/confused${multiplier}.png`),
|
||||||
buildEmote('COOKING_SOMETHING_NICE', `${px}/cooking%20something%20nice${multiplier}.png`),
|
buildEmote('COOKING_SOMETHING_NICE', `${px}/cooking%20something%20nice${multiplier}.png`),
|
||||||
|
buildEmote('CRY_1', `${px}/cry${multiplier}.png`),
|
||||||
buildEmote('CRY_2', `${px}/cry%202${multiplier}.png`),
|
buildEmote('CRY_2', `${px}/cry%202${multiplier}.png`),
|
||||||
buildEmote('CRY_3', `${px}/cry%203${multiplier}.png`),
|
buildEmote('CRY_3', `${px}/cry%203${multiplier}.png`),
|
||||||
buildEmote('CRY_4', `${px}/cry%204${multiplier}.png`),
|
buildEmote('CRY_4', `${px}/cry%204${multiplier}.png`),
|
||||||
buildEmote('CRY_5', `${px}/cry%205${multiplier}.png`),
|
buildEmote('CRY_5', `${px}/cry%205${multiplier}.png`),
|
||||||
buildEmote('CRY_1', `${px}/cry${multiplier}.png`),
|
|
||||||
buildEmote('SPACE_DOGE', `${px}/doge${multiplier}.png`),
|
|
||||||
buildEmote('DONUT', `${px}/donut${multiplier}.png`),
|
buildEmote('DONUT', `${px}/donut${multiplier}.png`),
|
||||||
buildEmote('EGGPLANT_WITH_CONDOM', `${px}/eggplant%20with%20condom${multiplier}.png`),
|
buildEmote('EGGPLANT_WITH_CONDOM', `${px}/eggplant%20with%20condom${multiplier}.png`),
|
||||||
buildEmote('EGGPLANT', `${px}/eggplant${multiplier}.png`),
|
buildEmote('EGGPLANT', `${px}/eggplant${multiplier}.png`),
|
||||||
|
@ -37,27 +36,26 @@ const getEmotes = (px: string, multiplier: string) => [
|
||||||
buildEmote('HYPER_TROLL', `${px}/HyperTroll${multiplier}.png`),
|
buildEmote('HYPER_TROLL', `${px}/HyperTroll${multiplier}.png`),
|
||||||
buildEmote('ICE_CREAM', `${px}/ice%20cream${multiplier}.png`),
|
buildEmote('ICE_CREAM', `${px}/ice%20cream${multiplier}.png`),
|
||||||
buildEmote('IDK', `${px}/IDK${multiplier}.png`),
|
buildEmote('IDK', `${px}/IDK${multiplier}.png`),
|
||||||
buildEmote('ILLUMINATI_1', `${px}/Illuminati-1${multiplier}.png`),
|
buildEmote('ILLUMINATI_1', `${px}/Illuminati${multiplier}-1.png`),
|
||||||
buildEmote('ILLUMINATI_2', `${px}/Illuminati${multiplier}.png`),
|
buildEmote('ILLUMINATI_2', `${px}/Illuminati${multiplier}.png`),
|
||||||
buildEmote('KISS_2', `${px}/kiss%202${multiplier}.png`),
|
|
||||||
buildEmote('KISS_1', `${px}/kiss${multiplier}.png`),
|
buildEmote('KISS_1', `${px}/kiss${multiplier}.png`),
|
||||||
|
buildEmote('KISS_2', `${px}/kiss%202${multiplier}.png`),
|
||||||
buildEmote('LASER_GUN', `${px}/laser%20gun${multiplier}.png`),
|
buildEmote('LASER_GUN', `${px}/laser%20gun${multiplier}.png`),
|
||||||
buildEmote('LAUGHING_2', `${px}/Laughing 2${multiplier}.png`),
|
|
||||||
buildEmote('LAUGHING_1', `${px}/Laughing${multiplier}.png`),
|
buildEmote('LAUGHING_1', `${px}/Laughing${multiplier}.png`),
|
||||||
|
buildEmote('LAUGHING_2', `${px}/Laughing 2${multiplier}.png`),
|
||||||
buildEmote('LOLLIPOP', `${px}/Lollipop${multiplier}.png`),
|
buildEmote('LOLLIPOP', `${px}/Lollipop${multiplier}.png`),
|
||||||
buildEmote('LOVE_2', `${px}/Love%202${multiplier}.png`),
|
|
||||||
buildEmote('LOVE_1', `${px}/Love${multiplier}.png`),
|
buildEmote('LOVE_1', `${px}/Love${multiplier}.png`),
|
||||||
|
buildEmote('LOVE_2', `${px}/Love%202${multiplier}.png`),
|
||||||
buildEmote('MONSTER', `${px}/Monster${multiplier}.png`),
|
buildEmote('MONSTER', `${px}/Monster${multiplier}.png`),
|
||||||
buildEmote('MUSHROOM', `${px}/mushroom${multiplier}.png`),
|
buildEmote('MUSHROOM', `${px}/mushroom${multiplier}.png`),
|
||||||
buildEmote('NAIL_IT', `${px}/Nail%20It${multiplier}.png`),
|
buildEmote('NAIL_IT', `${px}/Nail%20It${multiplier}.png`),
|
||||||
buildEmote('NO', `${px}/NO${multiplier}.png`),
|
buildEmote('NO', `${px}/NO${multiplier}.png`),
|
||||||
buildEmote('OUCH', `${px}/ouch${multiplier}.png`),
|
buildEmote('OUCH', `${px}/ouch${multiplier}.png`),
|
||||||
buildEmote('PREACE', `${px}/peace${multiplier}.png`),
|
|
||||||
buildEmote('PIZZA', `${px}/pizza${multiplier}.png`),
|
buildEmote('PIZZA', `${px}/pizza${multiplier}.png`),
|
||||||
|
buildEmote('PREACE', `${px}/peace${multiplier}.png`),
|
||||||
buildEmote('RABBIT_HOLE', `${px}/rabbit%20hole${multiplier}.png`),
|
buildEmote('RABBIT_HOLE', `${px}/rabbit%20hole${multiplier}.png`),
|
||||||
buildEmote('RAINBOW_PUKE_1', `${px}/rainbow%20puke-1${multiplier}.png`),
|
buildEmote('RAINBOW_PUKE_1', `${px}/rainbow%20puke${multiplier}-1.png`),
|
||||||
buildEmote('RAINBOW_PUKE_2', `${px}/rainbow%20puke${multiplier}.png`),
|
buildEmote('RAINBOW_PUKE_2', `${px}/rainbow%20puke${multiplier}.png`),
|
||||||
buildEmote('SPACE_RESITAS', `${px}/resitas${multiplier}.png`),
|
|
||||||
buildEmote('ROCK', `${px}/ROCK${multiplier}.png`),
|
buildEmote('ROCK', `${px}/ROCK${multiplier}.png`),
|
||||||
buildEmote('SAD', `${px}/sad${multiplier}.png`),
|
buildEmote('SAD', `${px}/sad${multiplier}.png`),
|
||||||
buildEmote('SALTY', `${px}/salty${multiplier}.png`),
|
buildEmote('SALTY', `${px}/salty${multiplier}.png`),
|
||||||
|
@ -65,22 +63,24 @@ const getEmotes = (px: string, multiplier: string) => [
|
||||||
buildEmote('SLEEP', `${px}/Sleep${multiplier}.png`),
|
buildEmote('SLEEP', `${px}/Sleep${multiplier}.png`),
|
||||||
buildEmote('SLIME_DOWN', `${px}/slime%20down${multiplier}.png`),
|
buildEmote('SLIME_DOWN', `${px}/slime%20down${multiplier}.png`),
|
||||||
buildEmote('SMELLY_SOCKS', `${px}/smelly%20socks${multiplier}.png`),
|
buildEmote('SMELLY_SOCKS', `${px}/smelly%20socks${multiplier}.png`),
|
||||||
buildEmote('SMILE_2', `${px}/smile%202${multiplier}.png`),
|
|
||||||
buildEmote('SMILE_1', `${px}/smile${multiplier}.png`),
|
buildEmote('SMILE_1', `${px}/smile${multiplier}.png`),
|
||||||
|
buildEmote('SMILE_2', `${px}/smile%202${multiplier}.png`),
|
||||||
buildEmote('SPACE_CHAD', `${px}/space%20chad${multiplier}.png`),
|
buildEmote('SPACE_CHAD', `${px}/space%20chad${multiplier}.png`),
|
||||||
|
buildEmote('SPACE_DOGE', `${px}/doge${multiplier}.png`),
|
||||||
|
buildEmote('SPACE_GREEN_WOJAK', `${px}/space%20wojak${multiplier}-1.png`),
|
||||||
buildEmote('SPACE_JULIAN', `${px}/Space%20Julian${multiplier}.png`),
|
buildEmote('SPACE_JULIAN', `${px}/Space%20Julian${multiplier}.png`),
|
||||||
buildEmote('SPACE_TOM', `${px}/space%20Tom${multiplier}.png`),
|
|
||||||
buildEmote('SPACE_GREEN_WOJAK', `${px}/space%20wojak-1${multiplier}.png`),
|
|
||||||
buildEmote('SPACE_RED_WOJAK', `${px}/space%20wojak${multiplier}.png`),
|
buildEmote('SPACE_RED_WOJAK', `${px}/space%20wojak${multiplier}.png`),
|
||||||
|
buildEmote('SPACE_RESITAS', `${px}/resitas${multiplier}.png`),
|
||||||
|
buildEmote('SPACE_TOM', `${px}/space%20Tom${multiplier}.png`),
|
||||||
buildEmote('SPOCK', `${px}/SPOCK${multiplier}.png`),
|
buildEmote('SPOCK', `${px}/SPOCK${multiplier}.png`),
|
||||||
buildEmote('STAR', `${px}/Star${multiplier}.png`),
|
buildEmote('STAR', `${px}/Star${multiplier}.png`),
|
||||||
buildEmote('SUNNY_DAY', `${px}/sunny%20day${multiplier}.png`),
|
buildEmote('SUNNY_DAY', `${px}/sunny%20day${multiplier}.png`),
|
||||||
buildEmote('SUPRISED', `${px}/surprised${multiplier}.png`),
|
buildEmote('SUPRISED', `${px}/surprised${multiplier}.png`),
|
||||||
buildEmote('SWEET', `${px}/sweet${multiplier}.png`),
|
buildEmote('SWEET', `${px}/sweet${multiplier}.png`),
|
||||||
buildEmote('THINKING_1', `${px}/thinking-1${multiplier}.png`),
|
buildEmote('THINKING_1', `${px}/thinking${multiplier}-1.png`),
|
||||||
buildEmote('THINKING_2', `${px}/thinking${multiplier}.png`),
|
buildEmote('THINKING_2', `${px}/thinking${multiplier}.png`),
|
||||||
buildEmote('THUMB_DOWN', `${px}/thumb%20down${multiplier}.png`),
|
buildEmote('THUMB_DOWN', `${px}/thumb%20down${multiplier}.png`),
|
||||||
buildEmote('THUMB_UP_1', `${px}/thumb%20up-1${multiplier}.png`),
|
buildEmote('THUMB_UP_1', `${px}/thumb%20up${multiplier}-1.png`),
|
||||||
buildEmote('THUMB_UP_2', `${px}/thumb%20up${multiplier}.png`),
|
buildEmote('THUMB_UP_2', `${px}/thumb%20up${multiplier}.png`),
|
||||||
buildEmote('TINFOIL_HAT', `${px}/tin%20hat${multiplier}.png`),
|
buildEmote('TINFOIL_HAT', `${px}/tin%20hat${multiplier}.png`),
|
||||||
buildEmote('TROLL_KING', `${px}/Troll%20king${multiplier}.png`),
|
buildEmote('TROLL_KING', `${px}/Troll%20king${multiplier}.png`),
|
||||||
|
@ -91,6 +91,6 @@ const getEmotes = (px: string, multiplier: string) => [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const EMOTES_24px = getEmotes('24%20px', '');
|
export const EMOTES_24px = getEmotes('24%20px', '');
|
||||||
export const EMOTES_36px = getEmotes('36px', '@1.5x');
|
export const EMOTES_36px = getEmotes('36px', '%401.5x');
|
||||||
export const EMOTES_48px = getEmotes('48%20px', '@2x');
|
export const EMOTES_48px = getEmotes('48%20px', '%402x');
|
||||||
export const EMOTES_72px = getEmotes('72%20px', '@3x');
|
export const EMOTES_72px = getEmotes('72%20px', '%403x');
|
||||||
|
|
|
@ -180,3 +180,4 @@ export const ARTISTS = 'Artists';
|
||||||
export const MYSTERIES = 'Mysteries';
|
export const MYSTERIES = 'Mysteries';
|
||||||
export const TECHNOLOGY = 'Technology';
|
export const TECHNOLOGY = 'Technology';
|
||||||
export const EMOJI = 'Emoji';
|
export const EMOJI = 'Emoji';
|
||||||
|
export const STICKER = 'Sticker';
|
||||||
|
|
|
@ -106,5 +106,16 @@ $thumbnailWidthSmall: 1rem;
|
||||||
.filePrice {
|
.filePrice {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
|
|
||||||
|
.credit-amount:not(:last-child) {
|
||||||
|
&::after {
|
||||||
|
margin-left: var(--spacing-xxs);
|
||||||
|
content: '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit-amount:not(:first-child) {
|
||||||
|
margin-left: var(--spacing-xxs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -489,3 +489,8 @@ $thumbnailWidthSmall: 1rem;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emote {
|
||||||
|
max-width: 1.5rem;
|
||||||
|
max-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
img:not(.channel-thumbnail__custom) {
|
img:not(.channel-thumbnail__custom):not(.emote) {
|
||||||
margin-bottom: var(--spacing-m);
|
margin-bottom: var(--spacing-m);
|
||||||
padding-top: var(--spacing-m);
|
padding-top: var(--spacing-m);
|
||||||
max-height: var(--inline-player-max-height);
|
max-height: var(--inline-player-max-height);
|
||||||
|
|
|
@ -13,6 +13,12 @@ $contentMaxWidth: 60rem;
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-card-background-highlighted);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.commentCreate,
|
.commentCreate,
|
||||||
|
@ -25,7 +31,7 @@ $contentMaxWidth: 60rem;
|
||||||
.notification__icon {
|
.notification__icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin: auto;
|
margin-top: var(--spacing-xxs);
|
||||||
|
|
||||||
.icon__wrapper {
|
.icon__wrapper {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
|
@ -94,7 +100,7 @@ $contentMaxWidth: 60rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__content-wrapper {
|
.notificationContent__wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -121,7 +127,7 @@ $contentMaxWidth: 60rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__content-thumbnail {
|
.notificationContent__thumbnail {
|
||||||
@include thumbnail;
|
@include thumbnail;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
@ -139,8 +145,13 @@ $contentMaxWidth: 60rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__text-wrapper {
|
.notificationText__wrapper {
|
||||||
max-width: calc(#{$contentMaxWidth} - (#{$thumbnailWidth} * 16 / 9) - var(--spacing-m));
|
max-width: calc(#{$contentMaxWidth} - (#{$thumbnailWidth} * 16 / 9) - var(--spacing-m));
|
||||||
|
|
||||||
|
.sticker__comment {
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 4.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__title {
|
.notification__title {
|
||||||
|
@ -247,7 +258,7 @@ $contentMaxWidth: 60rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification__mark-seen {
|
.notification__markSeen {
|
||||||
height: 12px;
|
height: 12px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { EMOTES_24px as EMOTES } from 'constants/emotes';
|
import { EMOTES_48px as EMOTES } from 'constants/emotes';
|
||||||
import visit from 'unist-util-visit';
|
import visit from 'unist-util-visit';
|
||||||
|
|
||||||
const EMOTE_NODE_TYPE = 'emote';
|
const EMOTE_NODE_TYPE = 'emote';
|
||||||
|
|
Loading…
Add table
Reference in a new issue