Profile: Show collectible info (#4505)
This commit is contained in:
parent
37ad59d937
commit
1f834d42ed
@ -336,7 +336,7 @@ function buildAction(
|
|||||||
let currency: string | undefined;
|
let currency: string | undefined;
|
||||||
let giftCryptoInfo: {
|
let giftCryptoInfo: {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: number;
|
||||||
} | undefined;
|
} | undefined;
|
||||||
let text: string;
|
let text: string;
|
||||||
const translationValues: string[] = [];
|
const translationValues: string[] = [];
|
||||||
@ -499,10 +499,9 @@ function buildAction(
|
|||||||
}
|
}
|
||||||
currency = action.currency;
|
currency = action.currency;
|
||||||
if (action.cryptoCurrency) {
|
if (action.cryptoCurrency) {
|
||||||
const cryptoAmountWithDecimals = action.cryptoAmount!.divide(1e7).toJSNumber() / 100;
|
|
||||||
giftCryptoInfo = {
|
giftCryptoInfo = {
|
||||||
currency: action.cryptoCurrency,
|
currency: action.cryptoCurrency,
|
||||||
amount: cryptoAmountWithDecimals.toFixed(2),
|
amount: action.cryptoAmount!.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
amount = action.amount.toJSNumber();
|
amount = action.amount.toJSNumber();
|
||||||
@ -552,10 +551,9 @@ function buildAction(
|
|||||||
}
|
}
|
||||||
currency = action.currency;
|
currency = action.currency;
|
||||||
if (action.cryptoCurrency) {
|
if (action.cryptoCurrency) {
|
||||||
const cryptoAmountWithDecimals = action.cryptoAmount!.divide(1e7).toJSNumber() / 100;
|
|
||||||
giftCryptoInfo = {
|
giftCryptoInfo = {
|
||||||
currency: action.cryptoCurrency,
|
currency: action.cryptoCurrency,
|
||||||
amount: cryptoAmountWithDecimals.toFixed(2),
|
amount: action.cryptoAmount!.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (action.boostPeer) {
|
if (action.boostPeer) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
import type { ApiPrivacyKey } from '../../../types';
|
import type { ApiPrivacyKey } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiChatLink,
|
ApiChatLink,
|
||||||
|
ApiCollectionInfo,
|
||||||
ApiConfig, ApiCountry, ApiLangString,
|
ApiConfig, ApiCountry, ApiLangString,
|
||||||
ApiPeerColors,
|
ApiPeerColors,
|
||||||
ApiSession, ApiTimezone, ApiUrlAuthResult, ApiWallpaper, ApiWebSession,
|
ApiSession, ApiTimezone, ApiUrlAuthResult, ApiWallpaper, ApiWebSession,
|
||||||
@ -274,3 +275,23 @@ export function buildApiChatLink(data: GramJs.account.ResolvedBusinessChatLinks)
|
|||||||
text: buildMessageTextContent(data.message, data.entities),
|
text: buildMessageTextContent(data.message, data.entities),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiCollectibleInfo(info: GramJs.fragment.TypeCollectibleInfo): ApiCollectionInfo {
|
||||||
|
const {
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
cryptoAmount,
|
||||||
|
cryptoCurrency,
|
||||||
|
purchaseDate,
|
||||||
|
url,
|
||||||
|
} = info;
|
||||||
|
|
||||||
|
return {
|
||||||
|
amount: amount.toJSNumber(),
|
||||||
|
currency,
|
||||||
|
cryptoAmount: cryptoAmount.toJSNumber(),
|
||||||
|
cryptoCurrency,
|
||||||
|
purchaseDate,
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
26
src/api/gramjs/methods/fragment.ts
Normal file
26
src/api/gramjs/methods/fragment.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
|
import { buildApiCollectibleInfo } from '../apiBuilders/misc';
|
||||||
|
import { invokeRequest } from './client';
|
||||||
|
|
||||||
|
type InputCollectible = {
|
||||||
|
phone: string;
|
||||||
|
} | {
|
||||||
|
username: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function fetchCollectionInfo(collectible: InputCollectible) {
|
||||||
|
const inputCollectible = 'username' in collectible
|
||||||
|
? new GramJs.InputCollectibleUsername({ username: collectible.username })
|
||||||
|
: new GramJs.InputCollectiblePhone({ phone: collectible.phone });
|
||||||
|
|
||||||
|
const result = await invokeRequest(new GramJs.fragment.GetCollectibleInfo({
|
||||||
|
collectible: inputCollectible,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiCollectibleInfo(result);
|
||||||
|
}
|
||||||
@ -102,3 +102,5 @@ export {
|
|||||||
applyBoost, fetchBoostList, fetchBoostStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode,
|
applyBoost, fetchBoostList, fetchBoostStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode,
|
||||||
getPremiumGiftCodeOptions, launchPrepaidGiveaway,
|
getPremiumGiftCodeOptions, launchPrepaidGiveaway,
|
||||||
} from './payments';
|
} from './payments';
|
||||||
|
|
||||||
|
export * from './fragment';
|
||||||
|
|||||||
@ -351,7 +351,7 @@ export interface ApiAction {
|
|||||||
currency?: string;
|
currency?: string;
|
||||||
giftCryptoInfo?: {
|
giftCryptoInfo?: {
|
||||||
currency: string;
|
currency: string;
|
||||||
amount: string;
|
amount: number;
|
||||||
};
|
};
|
||||||
translationValues: string[];
|
translationValues: string[];
|
||||||
call?: Partial<ApiGroupCall>;
|
call?: Partial<ApiGroupCall>;
|
||||||
|
|||||||
@ -267,3 +267,12 @@ type ApiUrlAuthResultDefault = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUrlAuthResult = ApiUrlAuthResultRequest | ApiUrlAuthResultAccepted | ApiUrlAuthResultDefault;
|
export type ApiUrlAuthResult = ApiUrlAuthResultRequest | ApiUrlAuthResultAccepted | ApiUrlAuthResultDefault;
|
||||||
|
|
||||||
|
export interface ApiCollectionInfo {
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
cryptoAmount: number;
|
||||||
|
cryptoCurrency: string;
|
||||||
|
purchaseDate: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|||||||
BIN
src/assets/tgs/general/Fragment.tgs
Normal file
BIN
src/assets/tgs/general/Fragment.tgs
Normal file
Binary file not shown.
BIN
src/assets/tgs/general/Mention.tgs
Normal file
BIN
src/assets/tgs/general/Mention.tgs
Normal file
Binary file not shown.
@ -39,6 +39,7 @@ export { default as CountryPickerModal } from '../components/common/CountryPicke
|
|||||||
export { default as ReactorListModal } from '../components/middle/ReactorListModal';
|
export { default as ReactorListModal } from '../components/middle/ReactorListModal';
|
||||||
export { default as EmojiInteractionAnimation } from '../components/middle/EmojiInteractionAnimation';
|
export { default as EmojiInteractionAnimation } from '../components/middle/EmojiInteractionAnimation';
|
||||||
export { default as ChatLanguageModal } from '../components/middle/ChatLanguageModal';
|
export { default as ChatLanguageModal } from '../components/middle/ChatLanguageModal';
|
||||||
|
export { default as CollectibleInfoModal } from '../components/modals/collectible/CollectibleInfoModal';
|
||||||
|
|
||||||
export { default as LeftSearch } from '../components/left/search/LeftSearch';
|
export { default as LeftSearch } from '../components/left/search/LeftSearch';
|
||||||
export { default as Settings } from '../components/left/settings/Settings';
|
export { default as Settings } from '../components/left/settings/Settings';
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import VoiceMini from '../../../assets/tgs/calls/VoiceMini.tgs';
|
|||||||
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
import VoiceMuted from '../../../assets/tgs/calls/VoiceMuted.tgs';
|
||||||
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
import VoiceOutlined from '../../../assets/tgs/calls/VoiceOutlined.tgs';
|
||||||
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
import Flame from '../../../assets/tgs/general/Flame.tgs';
|
||||||
|
import Fragment from '../../../assets/tgs/general/Fragment.tgs';
|
||||||
|
import Mention from '../../../assets/tgs/general/Mention.tgs';
|
||||||
import PartyPopper from '../../../assets/tgs/general/PartyPopper.tgs';
|
import PartyPopper from '../../../assets/tgs/general/PartyPopper.tgs';
|
||||||
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
import Invite from '../../../assets/tgs/invites/Invite.tgs';
|
||||||
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
import JoinRequest from '../../../assets/tgs/invites/Requests.tgs';
|
||||||
@ -54,4 +56,6 @@ export const LOCAL_TGS_URLS = {
|
|||||||
ReadTime,
|
ReadTime,
|
||||||
Unlock,
|
Unlock,
|
||||||
LastSeen,
|
LastSeen,
|
||||||
|
Mention,
|
||||||
|
Fragment,
|
||||||
};
|
};
|
||||||
|
|||||||
5
src/components/common/helpers/formatUsername.ts
Normal file
5
src/components/common/helpers/formatUsername.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { TME_LINK_PREFIX } from '../../../config';
|
||||||
|
|
||||||
|
export default function formatUsername(username: string, asAbsoluteLink?: boolean) {
|
||||||
|
return asAbsoluteLink ? `${TME_LINK_PREFIX}${username}` : `@${username}`;
|
||||||
|
}
|
||||||
@ -138,7 +138,8 @@ export function renderActionMessageText(
|
|||||||
let priceText = price;
|
let priceText = price;
|
||||||
|
|
||||||
if (giftCryptoInfo) {
|
if (giftCryptoInfo) {
|
||||||
priceText = `${giftCryptoInfo.amount} ${giftCryptoInfo.currency} (~${price})`;
|
const cryptoPrice = formatCurrency(giftCryptoInfo.amount, giftCryptoInfo.currency, lang.code);
|
||||||
|
priceText = `${cryptoPrice} (${price})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
processed = processPlaceholder(
|
processed = processPlaceholder(
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import { TME_LINK_PREFIX } from '../../../config';
|
import { FRAGMENT_PHONE_CODE, FRAGMENT_PHONE_LENGTH } from '../../../config';
|
||||||
import {
|
import {
|
||||||
buildStaticMapHash,
|
buildStaticMapHash,
|
||||||
getChatLink,
|
getChatLink,
|
||||||
@ -33,6 +33,7 @@ import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
|||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import stopEvent from '../../../util/stopEvent';
|
import stopEvent from '../../../util/stopEvent';
|
||||||
import { ChatAnimationTypes } from '../../left/main/hooks';
|
import { ChatAnimationTypes } from '../../left/main/hooks';
|
||||||
|
import formatUsername from '../helpers/formatUsername';
|
||||||
import renderText from '../helpers/renderText';
|
import renderText from '../helpers/renderText';
|
||||||
|
|
||||||
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../../hooks/useEffectWithPrevDeps';
|
||||||
@ -102,6 +103,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
loadPeerStories,
|
loadPeerStories,
|
||||||
openSavedDialog,
|
openSavedDialog,
|
||||||
openMapModal,
|
openMapModal,
|
||||||
|
requestCollectibleInfo,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -206,10 +208,6 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
openSavedDialog({ chatId: chatOrUserId });
|
openSavedDialog({ chatId: chatOrUserId });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chat || chat.isRestricted || (isSelf && !isInSettings)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function copy(text: string, entity: string) {
|
function copy(text: string, entity: string) {
|
||||||
copyTextToClipboard(text);
|
copyTextToClipboard(text);
|
||||||
showNotification({ message: `${entity} was copied` });
|
showNotification({ message: `${entity} was copied` });
|
||||||
@ -217,6 +215,26 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const formattedNumber = phoneNumber && formatPhoneNumberWithCode(phoneCodeList, phoneNumber);
|
const formattedNumber = phoneNumber && formatPhoneNumberWithCode(phoneCodeList, phoneNumber);
|
||||||
|
|
||||||
|
const handlePhoneClick = useLastCallback(() => {
|
||||||
|
if (phoneNumber?.length === FRAGMENT_PHONE_LENGTH && phoneNumber.startsWith(FRAGMENT_PHONE_CODE)) {
|
||||||
|
requestCollectibleInfo({ collectible: phoneNumber, userId: userId!, type: 'phone' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
copy(formattedNumber!, lang('Phone'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleUsernameClick = useLastCallback((username: ApiUsername, isChat?: boolean) => {
|
||||||
|
if (!username.isEditable) {
|
||||||
|
requestCollectibleInfo({ collectible: username.username, userId: userId!, type: 'username' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
copy(formatUsername(username.username, isChat), lang(isChat ? 'Link' : 'Username'));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!chat || chat.isRestricted || (isSelf && !isInSettings)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function renderUsernames(usernameList: ApiUsername[], isChat?: boolean) {
|
function renderUsernames(usernameList: ApiUsername[], isChat?: boolean) {
|
||||||
const [mainUsername, ...otherUsernames] = usernameList;
|
const [mainUsername, ...otherUsernames] = usernameList;
|
||||||
|
|
||||||
@ -226,21 +244,21 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
.map((s) => {
|
.map((s) => {
|
||||||
return (s === 'USERNAMES' ? (
|
return (s === 'USERNAMES' ? (
|
||||||
<>
|
<>
|
||||||
{otherUsernames.map(({ username: nick }, idx) => {
|
{otherUsernames.map((username, idx) => {
|
||||||
const textToCopy = isChat ? `${TME_LINK_PREFIX}${nick}` : `@${nick}`;
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{idx > 0 ? ', ' : ''}
|
{idx > 0 ? ', ' : ''}
|
||||||
<a
|
<a
|
||||||
key={nick}
|
key={username.username}
|
||||||
href={`${TME_LINK_PREFIX}${nick}`}
|
href={formatUsername(username.username, true)}
|
||||||
|
onMouseDown={stopEvent}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
copy(textToCopy, lang(isChat ? 'Link' : 'Username'));
|
handleUsernameClick(username, isChat);
|
||||||
}}
|
}}
|
||||||
className="text-entity-link username-link"
|
className="text-entity-link username-link"
|
||||||
>
|
>
|
||||||
{`@${nick}`}
|
{formatUsername(username.username)}
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -250,9 +268,6 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const username = isChat ? `t.me/${mainUsername.username}` : mainUsername.username;
|
|
||||||
const textToCopy = isChat ? `${TME_LINK_PREFIX}${mainUsername.username}` : `@${mainUsername.username}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
icon={isChat ? 'link' : 'mention'}
|
icon={isChat ? 'link' : 'mention'}
|
||||||
@ -260,9 +275,11 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
narrow
|
narrow
|
||||||
ripple
|
ripple
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => copy(textToCopy, lang(isChat ? 'Link' : 'Username'))}
|
onClick={() => {
|
||||||
|
handleUsernameClick(mainUsername, isChat);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span className="title" dir="auto">{username}</span>
|
<span className="title" dir="auto">{formatUsername(mainUsername.username, isChat)}</span>
|
||||||
<span className="subtitle">
|
<span className="subtitle">
|
||||||
{usernameLinks && <span className="other-usernames">{usernameLinks}</span>}
|
{usernameLinks && <span className="other-usernames">{usernameLinks}</span>}
|
||||||
{lang(isChat ? 'Link' : 'Username')}
|
{lang(isChat ? 'Link' : 'Username')}
|
||||||
@ -289,9 +306,9 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{formattedNumber && Boolean(formattedNumber.length) && (
|
{Boolean(formattedNumber?.length) && (
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
<ListItem icon="phone" multiline narrow ripple onClick={() => copy(formattedNumber, lang('Phone'))}>
|
<ListItem icon="phone" multiline narrow ripple onClick={handlePhoneClick}>
|
||||||
<span className="title" dir="auto">{formattedNumber}</span>
|
<span className="title" dir="auto">{formattedNumber}</span>
|
||||||
<span className="subtitle">{lang('Phone')}</span>
|
<span className="subtitle">{lang('Phone')}</span>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { pick } from '../../util/iteratees';
|
|||||||
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
|
||||||
import BoostModal from './boost/BoostModal.async';
|
import BoostModal from './boost/BoostModal.async';
|
||||||
import ChatlistModal from './chatlist/ChatlistModal.async';
|
import ChatlistModal from './chatlist/ChatlistModal.async';
|
||||||
|
import CollectibleInfoModal from './collectible/CollectibleInfoModal.async';
|
||||||
import GiftCodeModal from './giftcode/GiftCodeModal.async';
|
import GiftCodeModal from './giftcode/GiftCodeModal.async';
|
||||||
import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async';
|
import InviteViaLinkModal from './inviteViaLink/InviteViaLinkModal.async';
|
||||||
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
||||||
@ -25,6 +26,7 @@ type ModalKey = keyof Pick<TabState,
|
|||||||
'oneTimeMediaModal' |
|
'oneTimeMediaModal' |
|
||||||
'inviteViaLinkModal' |
|
'inviteViaLinkModal' |
|
||||||
'requestedAttachBotInstall' |
|
'requestedAttachBotInstall' |
|
||||||
|
'collectibleInfoModal' |
|
||||||
'reportAdModal' |
|
'reportAdModal' |
|
||||||
'webApp'
|
'webApp'
|
||||||
>;
|
>;
|
||||||
@ -51,6 +53,7 @@ const MODALS: ModalRegistry = {
|
|||||||
requestedAttachBotInstall: AttachBotInstallModal,
|
requestedAttachBotInstall: AttachBotInstallModal,
|
||||||
reportAdModal: ReportAdModal,
|
reportAdModal: ReportAdModal,
|
||||||
webApp: WebAppModal,
|
webApp: WebAppModal,
|
||||||
|
collectibleInfoModal: CollectibleInfoModal,
|
||||||
};
|
};
|
||||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { OwnProps } from './CollectibleInfoModal';
|
||||||
|
|
||||||
|
import { Bundles } from '../../../util/moduleLoader';
|
||||||
|
|
||||||
|
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||||
|
|
||||||
|
const CollectibleInfoModalAsync: FC<OwnProps> = (props) => {
|
||||||
|
const { modal } = props;
|
||||||
|
const CollectibleInfoModal = useModuleLoader(Bundles.Extra, 'CollectibleInfoModal', !modal);
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
return CollectibleInfoModal ? <CollectibleInfoModal {...props} /> : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollectibleInfoModalAsync;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 5rem;
|
||||||
|
height: 5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title, .description {
|
||||||
|
text-align: center !important;
|
||||||
|
text-wrap: pretty;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
156
src/components/modals/collectible/CollectibleInfoModal.tsx
Normal file
156
src/components/modals/collectible/CollectibleInfoModal.tsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo,
|
||||||
|
useMemo,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiCountryCode } from '../../../api/types';
|
||||||
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
|
import { formatDateAtTime } from '../../../util/date/dateFormat';
|
||||||
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
|
import { formatPhoneNumberWithCode } from '../../../util/phoneNumber';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
|
import formatUsername from '../../common/helpers/formatUsername';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
|
import PickerSelectedItem from '../../common/PickerSelectedItem';
|
||||||
|
import Button from '../../ui/Button';
|
||||||
|
import Modal from '../../ui/Modal';
|
||||||
|
|
||||||
|
import styles from './CollectibleInfoModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
modal: TabState['collectibleInfoModal'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
phoneCodeList: ApiCountryCode[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOP_ICON_SIZE = 60;
|
||||||
|
|
||||||
|
const CollectibleInfoModal: FC<OwnProps & StateProps> = ({
|
||||||
|
modal,
|
||||||
|
phoneCodeList,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
closeCollectibleInfoModal,
|
||||||
|
openChat,
|
||||||
|
openUrl,
|
||||||
|
showNotification,
|
||||||
|
} = getActions();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isUsername = modal?.type === 'username';
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
closeCollectibleInfoModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOpenChat = useLastCallback(() => {
|
||||||
|
openChat({ id: modal!.userId });
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleOpenUrl = useLastCallback(() => {
|
||||||
|
openUrl({
|
||||||
|
url: modal!.url,
|
||||||
|
shouldSkipModal: true,
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCopy = useLastCallback(() => {
|
||||||
|
const text = isUsername ? formatUsername(modal!.collectible)
|
||||||
|
: formatPhoneNumberWithCode(phoneCodeList, modal!.collectible);
|
||||||
|
copyTextToClipboard(text);
|
||||||
|
showNotification({
|
||||||
|
message: lang(isUsername ? 'UsernameCopied' : 'PhoneCopied'),
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
});
|
||||||
|
|
||||||
|
const title = useMemo(() => {
|
||||||
|
if (!modal) return undefined;
|
||||||
|
const key = isUsername ? 'FragmentUsernameTitle' : 'FragmentPhoneTitle';
|
||||||
|
const formattedCollectible = isUsername
|
||||||
|
? formatUsername(modal.collectible)
|
||||||
|
: formatPhoneNumberWithCode(phoneCodeList, modal.collectible);
|
||||||
|
return lang(key, formattedCollectible);
|
||||||
|
}, [modal, isUsername, phoneCodeList, lang]);
|
||||||
|
|
||||||
|
const description = useMemo(() => {
|
||||||
|
if (!modal) return undefined;
|
||||||
|
const key = isUsername ? 'FragmentUsernameMessage' : 'FragmentPhoneMessage';
|
||||||
|
const date = formatDateAtTime(lang, modal.purchaseDate * 1000);
|
||||||
|
const currency = formatCurrency(modal.amount, modal.currency, lang.code);
|
||||||
|
const cryptoCurrency = formatCurrency(modal.cryptoAmount, modal.cryptoCurrency, lang.code);
|
||||||
|
const paid = `${cryptoCurrency} (${currency})`;
|
||||||
|
return lang(key, [date, paid]);
|
||||||
|
}, [modal, isUsername, lang]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={Boolean(modal)}
|
||||||
|
isSlim
|
||||||
|
contentClassName={styles.content}
|
||||||
|
onClose={closeCollectibleInfoModal}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
className={styles.closeButton}
|
||||||
|
ariaLabel={lang('Close')}
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
<Icon name="close" />
|
||||||
|
</Button>
|
||||||
|
<div className={styles.icon}>
|
||||||
|
<AnimatedIconWithPreview
|
||||||
|
tgsUrl={isUsername ? LOCAL_TGS_URLS.Mention : LOCAL_TGS_URLS.Fragment}
|
||||||
|
size={TOP_ICON_SIZE}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className={styles.title}>
|
||||||
|
{title && renderText(title, ['simple_markdown'])}
|
||||||
|
</h3>
|
||||||
|
<PickerSelectedItem
|
||||||
|
fluid
|
||||||
|
className={styles.chip}
|
||||||
|
peerId={modal?.userId}
|
||||||
|
forceShowSelf
|
||||||
|
onClick={handleOpenChat}
|
||||||
|
/>
|
||||||
|
<p className={styles.description}>
|
||||||
|
{description && renderText(description, ['simple_markdown'])}
|
||||||
|
</p>
|
||||||
|
<div className="dialog-buttons">
|
||||||
|
<Button className="confirm-dialog-button" onClick={handleOpenUrl}>
|
||||||
|
{lang('FragmentUsernameOpen')}
|
||||||
|
</Button>
|
||||||
|
<Button isText className="confirm-dialog-button" onClick={handleCopy}>
|
||||||
|
{lang(isUsername ? 'FragmentUsernameCopy' : 'FragmentPhoneCopy')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): StateProps => {
|
||||||
|
const { countryList } = global;
|
||||||
|
|
||||||
|
return {
|
||||||
|
phoneCodeList: countryList.phoneCodes,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(CollectibleInfoModal));
|
||||||
@ -319,6 +319,8 @@ export const GIVEAWAY_MAX_ADDITIONAL_CHANNELS = 10;
|
|||||||
export const GIVEAWAY_MAX_ADDITIONAL_USERS = 10;
|
export const GIVEAWAY_MAX_ADDITIONAL_USERS = 10;
|
||||||
export const GIVEAWAY_MAX_ADDITIONAL_COUNTRIES = 10;
|
export const GIVEAWAY_MAX_ADDITIONAL_COUNTRIES = 10;
|
||||||
export const BOOST_PER_SENT_GIFT = 3;
|
export const BOOST_PER_SENT_GIFT = 3;
|
||||||
|
export const FRAGMENT_PHONE_CODE = '888';
|
||||||
|
export const FRAGMENT_PHONE_LENGTH = 11;
|
||||||
|
|
||||||
export const LIGHT_THEME_BG_COLOR = '#99BA92';
|
export const LIGHT_THEME_BG_COLOR = '#99BA92';
|
||||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import {
|
|||||||
TOPICS_SLICE,
|
TOPICS_SLICE,
|
||||||
TOPICS_SLICE_SECOND_LOAD,
|
TOPICS_SLICE_SECOND_LOAD,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
||||||
import { isDeepLink } from '../../../util/deepLinkParser';
|
import { isDeepLink } from '../../../util/deepLinkParser';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -2673,6 +2674,38 @@ addActionHandler('resolveBusinessChatLink', async (global, actions, payload): Pr
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('requestCollectibleInfo', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
type, collectible, userId, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
let inputCollectible;
|
||||||
|
if (type === 'phone') {
|
||||||
|
inputCollectible = { phone: collectible };
|
||||||
|
}
|
||||||
|
if (type === 'username') {
|
||||||
|
inputCollectible = { username: collectible };
|
||||||
|
}
|
||||||
|
if (!inputCollectible) return;
|
||||||
|
|
||||||
|
const result = await callApi('fetchCollectionInfo', inputCollectible);
|
||||||
|
if (!result) {
|
||||||
|
copyTextToClipboard(collectible);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateTabState(global, {
|
||||||
|
collectibleInfoModal: {
|
||||||
|
...result,
|
||||||
|
type,
|
||||||
|
collectible,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
async function loadChats(
|
async function loadChats(
|
||||||
listType: ChatListType,
|
listType: ChatListType,
|
||||||
offsetId?: string,
|
offsetId?: string,
|
||||||
|
|||||||
@ -744,6 +744,13 @@ addActionHandler('closeInviteViaLinkModal', (global, actions, payload): ActionRe
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('closeCollectibleInfoModal', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload ?? {};
|
||||||
|
return updateTabState(global, {
|
||||||
|
collectibleInfoModal: undefined,
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('setShouldCloseRightColumn', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setShouldCloseRightColumn', (global, actions, payload): ActionReturnType => {
|
||||||
const { value, tabId = getCurrentTabId() } = payload;
|
const { value, tabId = getCurrentTabId() } = payload;
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import type {
|
|||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiChatType,
|
ApiChatType,
|
||||||
ApiCheckedGiftCode,
|
ApiCheckedGiftCode,
|
||||||
|
ApiCollectionInfo,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
ApiContact,
|
ApiContact,
|
||||||
ApiCountry,
|
ApiCountry,
|
||||||
@ -738,6 +739,12 @@ export type TabState = {
|
|||||||
oneTimeMediaModal?: {
|
oneTimeMediaModal?: {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
collectibleInfoModal?: ApiCollectionInfo & {
|
||||||
|
userId: string;
|
||||||
|
type: 'phone' | 'username';
|
||||||
|
collectible: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
@ -2867,6 +2874,13 @@ export interface ActionPayloads {
|
|||||||
openOneTimeMediaModal: TabState['oneTimeMediaModal'] & WithTabId;
|
openOneTimeMediaModal: TabState['oneTimeMediaModal'] & WithTabId;
|
||||||
closeOneTimeMediaModal: WithTabId | undefined;
|
closeOneTimeMediaModal: WithTabId | undefined;
|
||||||
|
|
||||||
|
requestCollectibleInfo: {
|
||||||
|
userId: string;
|
||||||
|
type : 'phone' | 'username';
|
||||||
|
collectible: string;
|
||||||
|
} & WithTabId;
|
||||||
|
closeCollectibleInfoModal: WithTabId | undefined;
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
joinGroupCall: {
|
joinGroupCall: {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
|
|||||||
@ -1637,4 +1637,5 @@ stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool;
|
|||||||
premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList;
|
premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList;
|
||||||
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
premium.getMyBoosts#be77b4a = premium.MyBoosts;
|
||||||
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector<int> peer:InputPeer = premium.MyBoosts;
|
||||||
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;`;
|
premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus;
|
||||||
|
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;`;
|
||||||
@ -349,5 +349,6 @@
|
|||||||
"payments.applyGiftCode",
|
"payments.applyGiftCode",
|
||||||
"payments.getGiveawayInfo",
|
"payments.getGiveawayInfo",
|
||||||
"payments.getPremiumGiftCodeOptions",
|
"payments.getPremiumGiftCodeOptions",
|
||||||
"payments.launchPrepaidGiveaway"
|
"payments.launchPrepaidGiveaway",
|
||||||
|
"fragment.getCollectibleInfo"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -24,6 +24,9 @@ export function formatCurrency(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCurrencyExp(currency: string) {
|
function getCurrencyExp(currency: string) {
|
||||||
|
if (currency === 'TON') {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
if (currency === 'CLF') {
|
if (currency === 'CLF') {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user