Stars: Format currency with icon (#4678)
This commit is contained in:
parent
41cc393a3c
commit
6f79159ec3
@ -698,7 +698,6 @@ function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: bool
|
|||||||
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
||||||
return {
|
return {
|
||||||
type: 'receipt',
|
type: 'receipt',
|
||||||
text: 'PaymentReceipt',
|
|
||||||
receiptMessageId: media.receiptMsgId,
|
receiptMessageId: media.receiptMsgId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,7 +726,6 @@ interface ApiKeyboardButtonSimple {
|
|||||||
|
|
||||||
interface ApiKeyboardButtonReceipt {
|
interface ApiKeyboardButtonReceipt {
|
||||||
type: 'receipt';
|
type: 'receipt';
|
||||||
text: string;
|
|
||||||
receiptMessageId: number;
|
receiptMessageId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
isExpiredMessage,
|
isExpiredMessage,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { getMessageSummaryText } from '../../../global/helpers/messageSummary';
|
import { getMessageSummaryText } from '../../../global/helpers/messageSummary';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||||
import trimText from '../../../util/trimText';
|
import trimText from '../../../util/trimText';
|
||||||
import renderText from './renderText';
|
import renderText from './renderText';
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ export function renderActionMessageText(
|
|||||||
processed = processPlaceholder(
|
processed = processPlaceholder(
|
||||||
unprocessed,
|
unprocessed,
|
||||||
'%payment_amount%',
|
'%payment_amount%',
|
||||||
formatCurrency(amount!, currency!, lang.code),
|
formatCurrencyAsString(amount!, currency!, lang.code),
|
||||||
);
|
);
|
||||||
unprocessed = processed.pop() as string;
|
unprocessed = processed.pop() as string;
|
||||||
content.push(...processed);
|
content.push(...processed);
|
||||||
@ -134,11 +134,11 @@ export function renderActionMessageText(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (unprocessed.includes('%gift_payment_amount%')) {
|
if (unprocessed.includes('%gift_payment_amount%')) {
|
||||||
const price = formatCurrency(amount!, currency!, lang.code);
|
const price = formatCurrencyAsString(amount!, currency!, lang.code);
|
||||||
let priceText = price;
|
let priceText = price;
|
||||||
|
|
||||||
if (giftCryptoInfo) {
|
if (giftCryptoInfo) {
|
||||||
const cryptoPrice = formatCurrency(giftCryptoInfo.amount, giftCryptoInfo.currency, lang.code);
|
const cryptoPrice = formatCurrencyAsString(giftCryptoInfo.amount, giftCryptoInfo.currency, lang.code);
|
||||||
priceText = `${cryptoPrice} (${price})`;
|
priceText = `${cryptoPrice} (${price})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,14 @@
|
|||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.adaptive {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
|
|
||||||
.svg {
|
.svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import styles from './StarIcon.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
type?: 'gold' | 'premium' | 'regular';
|
type?: 'gold' | 'premium' | 'regular';
|
||||||
size?: 'small' | 'middle' | 'big';
|
size?: 'small' | 'middle' | 'big' | 'adaptive';
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: VoidFunction;
|
onClick?: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import buildClassName from '../../util/buildClassName';
|
|||||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||||
import { getPictogramDimensions, REM } from '../common/helpers/mediaDimensions';
|
import { getPictogramDimensions, REM } from '../common/helpers/mediaDimensions';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
|
import renderKeyboardButtonText from './composer/helpers/renderKeyboardButtonText';
|
||||||
|
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
import { useFastClick } from '../../hooks/useFastClick';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -198,7 +199,7 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
|
|||||||
onMouseEnter={!IS_TOUCH_ENV ? markNoHoverColor : undefined}
|
onMouseEnter={!IS_TOUCH_ENV ? markNoHoverColor : undefined}
|
||||||
onMouseLeave={!IS_TOUCH_ENV ? unmarkNoHoverColor : undefined}
|
onMouseLeave={!IS_TOUCH_ENV ? unmarkNoHoverColor : undefined}
|
||||||
>
|
>
|
||||||
{renderText(inlineButton.text)}
|
{renderKeyboardButtonText(lang, inlineButton)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../../lib/teact/teact';
|
||||||
import React, { memo } from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage } from '../../../api/types';
|
import type { ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors';
|
import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
|
import renderKeyboardButtonText from './helpers/renderKeyboardButtonText';
|
||||||
|
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Menu from '../../ui/Menu';
|
import Menu from '../../ui/Menu';
|
||||||
@ -29,9 +31,20 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { clickBotInlineButton } = getActions();
|
const { clickBotInlineButton } = getActions();
|
||||||
|
|
||||||
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
||||||
const { isKeyboardSingleUse } = message || {};
|
const { isKeyboardSingleUse } = message || {};
|
||||||
|
|
||||||
|
const buttonTexts = useMemo(() => {
|
||||||
|
const texts: TeactNode[][] = [];
|
||||||
|
message?.keyboardButtons!.forEach((row) => {
|
||||||
|
texts.push(row.map((button) => renderKeyboardButtonText(lang, button)));
|
||||||
|
});
|
||||||
|
|
||||||
|
return texts;
|
||||||
|
}, [lang, message?.keyboardButtons]);
|
||||||
|
|
||||||
if (!message || !message.keyboardButtons) {
|
if (!message || !message.keyboardButtons) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -50,16 +63,16 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
|||||||
noCompact
|
noCompact
|
||||||
>
|
>
|
||||||
<div className="content custom-scroll">
|
<div className="content custom-scroll">
|
||||||
{message.keyboardButtons.map((row) => (
|
{message.keyboardButtons.map((row, i) => (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{row.map((button) => (
|
{row.map((button, j) => (
|
||||||
<Button
|
<Button
|
||||||
ripple
|
ripple
|
||||||
disabled={button.type === 'unsupported'}
|
disabled={button.type === 'unsupported'}
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => clickBotInlineButton({ messageId: message.id, button })}
|
onClick={() => clickBotInlineButton({ messageId: message.id, button })}
|
||||||
>
|
>
|
||||||
{button.text}
|
{buttonTexts?.[i][j]}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import React, { type TeactNode } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { ApiKeyboardButton } from '../../../../api/types';
|
||||||
|
|
||||||
|
import { replaceWithTeact } from '../../../../util/replaceWithTeact';
|
||||||
|
import renderText from '../../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import { type LangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Icon from '../../../common/icons/Icon';
|
||||||
|
|
||||||
|
export default function renderKeyboardButtonText(lang: LangFn, button: ApiKeyboardButton): TeactNode {
|
||||||
|
if (button.type === 'receipt') {
|
||||||
|
return lang('PaymentReceipt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button.type === 'buy') {
|
||||||
|
return replaceWithTeact(button.text, '⭐', <Icon className="star-currency-icon" name="star" />);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderText(button.text);
|
||||||
|
}
|
||||||
@ -37,7 +37,7 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.corner-icon {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.1875rem;
|
right: 0.1875rem;
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../../lib/teact/teact';
|
||||||
import React from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiKeyboardButton, ApiMessage } from '../../../api/types';
|
import type { ApiKeyboardButton, ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
import { RE_TME_LINK } from '../../../config';
|
import { RE_TME_LINK } from '../../../config';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderKeyboardButtonText from '../composer/helpers/renderKeyboardButtonText';
|
||||||
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
import './InlineButtons.scss';
|
import './InlineButtons.scss';
|
||||||
@ -25,29 +26,37 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case 'url': {
|
case 'url': {
|
||||||
if (!RE_TME_LINK.test(button.url)) {
|
if (!RE_TME_LINK.test(button.url)) {
|
||||||
return <i className="icon icon-arrow-right" />;
|
return <Icon className="corner-icon" name="arrow-right" />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'urlAuth':
|
case 'urlAuth':
|
||||||
return <i className="icon icon-arrow-right" />;
|
return <Icon className="corner-icon" name="arrow-right" />;
|
||||||
case 'buy':
|
case 'buy':
|
||||||
case 'receipt':
|
case 'receipt':
|
||||||
return <i className="icon icon-cart" />;
|
return <Icon className="corner-icon" name="card" />;
|
||||||
case 'switchBotInline':
|
case 'switchBotInline':
|
||||||
return <i className="icon icon-share-filled" />;
|
return <Icon className="corner-icon" name="share-filled" />;
|
||||||
case 'webView':
|
case 'webView':
|
||||||
case 'simpleWebView':
|
case 'simpleWebView':
|
||||||
return <i className="icon icon-webapp" />;
|
return <Icon className="corner-icon" name="webapp" />;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buttonTexts = useMemo(() => {
|
||||||
|
const texts: TeactNode[][] = [];
|
||||||
|
message.inlineButtons!.forEach((row) => {
|
||||||
|
texts.push(row.map((button) => renderKeyboardButtonText(lang, button)));
|
||||||
|
});
|
||||||
|
return texts;
|
||||||
|
}, [lang, message.inlineButtons]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="InlineButtons">
|
<div className="InlineButtons">
|
||||||
{message.inlineButtons!.map((row) => (
|
{message.inlineButtons!.map((row, i) => (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{row.map((button) => (
|
{row.map((button, j) => (
|
||||||
<Button
|
<Button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
ripple
|
ripple
|
||||||
@ -55,7 +64,9 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
|||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => onClick({ messageId: message.id, button })}
|
onClick={() => onClick({ messageId: message.id, button })}
|
||||||
>
|
>
|
||||||
<span className="inline-button-text">{renderText(lang(button.text))}</span>
|
<span className="inline-button-text">
|
||||||
|
{buttonTexts[i][j]}
|
||||||
|
</span>
|
||||||
{renderIcon(button)}
|
{renderIcon(button)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
@ -65,4 +76,4 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default InlineButtons;
|
export default memo(InlineButtons);
|
||||||
|
|||||||
@ -48,6 +48,11 @@
|
|||||||
.test-invoice {
|
.test-invoice {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.invoice-currency-icon {
|
||||||
|
margin-inline-end: 0.125rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.invoice-image-container {
|
.invoice-image-container {
|
||||||
|
|||||||
@ -119,7 +119,7 @@ const Invoice: FC<OwnProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="description-text">
|
<p className="description-text">
|
||||||
{formatCurrency(amount, currency, lang.code)}
|
{formatCurrency(amount, currency, lang.code, { iconClassName: 'invoice-currency-icon' })}
|
||||||
{isTest && <span className="test-invoice">{lang('PaymentTestInvoice')}</span>}
|
{isTest && <span className="test-invoice">{lang('PaymentTestInvoice')}</span>}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import type { ApiMessage } from '../../../api/types';
|
|||||||
import { getMessageInvoice } from '../../../global/helpers';
|
import { getMessageInvoice } from '../../../global/helpers';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatMediaDuration } from '../../../util/dates/dateFormat';
|
import { formatMediaDuration } from '../../../util/dates/dateFormat';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||||
|
|
||||||
import useInterval from '../../../hooks/schedulers/useInterval';
|
import useInterval from '../../../hooks/schedulers/useInterval';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
@ -74,7 +74,7 @@ const InvoiceMediaPreview: FC<OwnProps> = ({
|
|||||||
{Boolean(duration) && <div className={styles.duration}>{formatMediaDuration(duration)}</div>}
|
{Boolean(duration) && <div className={styles.duration}>{formatMediaDuration(duration)}</div>}
|
||||||
<div className={styles.buy}>
|
<div className={styles.buy}>
|
||||||
<i className={buildClassName('icon', 'icon-lock', styles.lock)} />
|
<i className={buildClassName('icon', 'icon-lock', styles.lock)} />
|
||||||
{lang('Checkout.PayPrice', formatCurrency(amount, currency))}
|
{lang('Checkout.PayPrice', formatCurrencyAsString(amount, currency))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type { TabState } from '../../../global/types';
|
|||||||
|
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import { formatDateAtTime } from '../../../util/dates/dateFormat';
|
import { formatDateAtTime } from '../../../util/dates/dateFormat';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||||
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||||
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
import formatUsername from '../../common/helpers/formatUsername';
|
import formatUsername from '../../common/helpers/formatUsername';
|
||||||
@ -91,8 +91,8 @@ const CollectibleInfoModal: FC<OwnProps & StateProps> = ({
|
|||||||
if (!modal) return undefined;
|
if (!modal) return undefined;
|
||||||
const key = isUsername ? 'FragmentUsernameMessage' : 'FragmentPhoneMessage';
|
const key = isUsername ? 'FragmentUsernameMessage' : 'FragmentPhoneMessage';
|
||||||
const date = formatDateAtTime(lang, modal.purchaseDate * 1000);
|
const date = formatDateAtTime(lang, modal.purchaseDate * 1000);
|
||||||
const currency = formatCurrency(modal.amount, modal.currency, lang.code);
|
const currency = formatCurrencyAsString(modal.amount, modal.currency, lang.code);
|
||||||
const cryptoCurrency = formatCurrency(modal.cryptoAmount, modal.cryptoCurrency, lang.code);
|
const cryptoCurrency = formatCurrencyAsString(modal.cryptoAmount, modal.cryptoCurrency, lang.code);
|
||||||
const paid = `${cryptoCurrency} (${currency})`;
|
const paid = `${cryptoCurrency} (${currency})`;
|
||||||
return lang(key, [date, paid]);
|
return lang(key, [date, paid]);
|
||||||
}, [modal, isUsername, lang]);
|
}, [modal, isUsername, lang]);
|
||||||
|
|||||||
@ -121,7 +121,7 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
className={buildClassName(styles.tipsItem, tip === tipAmount && styles.tipsItem_active)}
|
className={buildClassName(styles.tipsItem, tip === tipAmount && styles.tipsItem_active)}
|
||||||
onClick={dispatch ? () => handleTipsClick(tip === tipAmount ? 0 : tip) : undefined}
|
onClick={dispatch ? () => handleTipsClick(tip === tipAmount ? 0 : tip) : undefined}
|
||||||
>
|
>
|
||||||
{formatCurrency(tip, currency, lang.code, true)}
|
{formatCurrency(tip, currency, lang.code, { shouldOmitFractions: true })}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -199,7 +199,7 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
label: lang('PaymentCheckoutProvider'),
|
label: lang('PaymentCheckoutProvider'),
|
||||||
customIcon: buildClassName(styles.provider, styles[paymentProvider.toLowerCase()]),
|
customIcon: buildClassName(styles.provider, styles[paymentProvider.toLowerCase()]),
|
||||||
})}
|
})}
|
||||||
{(needAddress || !isInteractive) && renderCheckoutItem({
|
{(needAddress || (!isInteractive && shippingAddress)) && renderCheckoutItem({
|
||||||
title: shippingAddress,
|
title: shippingAddress,
|
||||||
label: lang('PaymentShippingAddress'),
|
label: lang('PaymentShippingAddress'),
|
||||||
icon: 'location',
|
icon: 'location',
|
||||||
@ -215,7 +215,7 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
label: lang('PaymentCheckoutPhoneNumber'),
|
label: lang('PaymentCheckoutPhoneNumber'),
|
||||||
icon: 'phone',
|
icon: 'phone',
|
||||||
})}
|
})}
|
||||||
{(hasShippingOptions || !isInteractive) && renderCheckoutItem({
|
{(hasShippingOptions || (!isInteractive && shippingMethod)) && renderCheckoutItem({
|
||||||
title: shippingMethod,
|
title: shippingMethod,
|
||||||
label: lang('PaymentCheckoutShippingMethod'),
|
label: lang('PaymentCheckoutShippingMethod'),
|
||||||
icon: 'truck',
|
icon: 'truck',
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { getUserFullName } from '../../global/helpers';
|
|||||||
import { selectChat, selectTabState, selectUser } from '../../global/selectors';
|
import { selectChat, selectTabState, selectUser } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||||
import { formatCurrency } from '../../util/formatCurrency';
|
import { formatCurrencyAsString } from '../../util/formatCurrency';
|
||||||
import { detectCardTypeText } from '../common/helpers/detectCardType';
|
import { detectCardTypeText } from '../common/helpers/detectCardType';
|
||||||
|
|
||||||
import usePaymentReducer from '../../hooks/reducers/usePaymentReducer';
|
import usePaymentReducer from '../../hooks/reducers/usePaymentReducer';
|
||||||
@ -517,7 +517,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
|||||||
}, [step, lang]);
|
}, [step, lang]);
|
||||||
|
|
||||||
const buttonText = step === PaymentStep.Checkout
|
const buttonText = step === PaymentStep.Checkout
|
||||||
? lang('Checkout.PayPrice', formatCurrency(totalPrice, currency!, lang.code))
|
? lang('Checkout.PayPrice', formatCurrencyAsString(totalPrice, currency!, lang.code))
|
||||||
: lang('Next');
|
: lang('Next');
|
||||||
|
|
||||||
function getIsSubmitDisabled() {
|
function getIsSubmitDisabled() {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ type OwnProps = {
|
|||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
label: TeactNode;
|
label: TeactNode;
|
||||||
subLabel?: string;
|
subLabel?: TeactNode;
|
||||||
value: string;
|
value: string;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import Radio from './Radio';
|
|||||||
|
|
||||||
export type IRadioOption<T = string> = {
|
export type IRadioOption<T = string> = {
|
||||||
label: TeactNode;
|
label: TeactNode;
|
||||||
subLabel?: string;
|
subLabel?: TeactNode;
|
||||||
value: T;
|
value: T;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -25,7 +25,7 @@ type OwnProps = {
|
|||||||
onClickAction?: (value: string) => void;
|
onClickAction?: (value: string) => void;
|
||||||
isLink?: boolean;
|
isLink?: boolean;
|
||||||
subLabelClassName?: string;
|
subLabelClassName?: string;
|
||||||
subLabel?: string | undefined;
|
subLabel?: TeactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RadioGroup: FC<OwnProps> = ({
|
const RadioGroup: FC<OwnProps> = ({
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
|
|
||||||
import { areDeepEqual } from '../../../util/areDeepEqual';
|
import { areDeepEqual } from '../../../util/areDeepEqual';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||||
import * as langProvider from '../../../util/oldLangProvider';
|
import * as langProvider from '../../../util/oldLangProvider';
|
||||||
import { addActionHandler, setGlobal } from '../../index';
|
import { addActionHandler, setGlobal } from '../../index';
|
||||||
import { closeInvoice, updateStarsBalance } from '../../reducers';
|
import { closeInvoice, updateStarsBalance } from '../../reducers';
|
||||||
@ -22,7 +22,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
actions.showNotification({
|
actions.showNotification({
|
||||||
tabId,
|
tabId,
|
||||||
message: langProvider.oldTranslate('PaymentInfoHint', [
|
message: langProvider.oldTranslate('PaymentInfoHint', [
|
||||||
formatCurrency(amount, currency, langProvider.getTranslationFn().code),
|
formatCurrencyAsString(amount, currency, langProvider.getTranslationFn().code),
|
||||||
title,
|
title,
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import * as langProvider from '../util/oldLangProvider';
|
import * as langProvider from '../util/oldLangProvider';
|
||||||
import useAsync from './useAsync';
|
import useAsync from './useAsync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Migrate to `useLang`, while using needed key inside initial fallback
|
||||||
|
*/
|
||||||
const useOldLangString = (
|
const useOldLangString = (
|
||||||
langCode: string | undefined,
|
langCode: string | undefined,
|
||||||
key: string,
|
key: string,
|
||||||
|
|||||||
@ -284,6 +284,11 @@ body:not(.is-ios) {
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.star-currency-icon {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
|
|
||||||
.shared-canvas-container {
|
.shared-canvas-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,40 @@
|
|||||||
|
import React, { type TeactNode } from '../lib/teact/teact';
|
||||||
|
|
||||||
import type { LangCode } from '../types';
|
import type { LangCode } from '../types';
|
||||||
|
|
||||||
|
import StarIcon from '../components/common/icons/StarIcon';
|
||||||
|
|
||||||
const STARS_CODE = 'XTR';
|
const STARS_CODE = 'XTR';
|
||||||
|
|
||||||
export function formatCurrency(
|
export function formatCurrency(
|
||||||
totalPrice: number,
|
totalPrice: number,
|
||||||
currency: string,
|
currency: string,
|
||||||
locale: LangCode = 'en',
|
locale: LangCode = 'en',
|
||||||
shouldOmitFractions = false,
|
options?: {
|
||||||
) {
|
shouldOmitFractions?: boolean;
|
||||||
|
iconClassName?: string;
|
||||||
|
},
|
||||||
|
): TeactNode {
|
||||||
const price = totalPrice / 10 ** getCurrencyExp(currency);
|
const price = totalPrice / 10 ** getCurrencyExp(currency);
|
||||||
|
|
||||||
if (currency === STARS_CODE) {
|
if (currency === STARS_CODE) {
|
||||||
return `⭐️${price}`;
|
return [<StarIcon className={options?.iconClassName} type="gold" size="adaptive" />, price];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldOmitFractions && price % 1 === 0) {
|
return formatCurrencyAsString(totalPrice, currency, locale, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatCurrencyAsString(
|
||||||
|
totalPrice: number,
|
||||||
|
currency: string,
|
||||||
|
locale: LangCode = 'en',
|
||||||
|
options?: {
|
||||||
|
shouldOmitFractions?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const price = totalPrice / 10 ** getCurrencyExp(currency);
|
||||||
|
|
||||||
|
if (options?.shouldOmitFractions && price % 1 === 0) {
|
||||||
return new Intl.NumberFormat(locale, {
|
return new Intl.NumberFormat(locale, {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency,
|
currency,
|
||||||
Loading…
x
Reference in New Issue
Block a user