diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index ae3abd7d9..e5319ac88 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -813,6 +813,7 @@ function buildAction( let type: ApiAction['type'] = 'other'; let photo: ApiPhoto | undefined; let score: number | undefined; + let months: number | undefined; const targetUserIds = 'users' in action ? action.users && action.users.map((id) => buildApiPeerId(id, 'user')) @@ -944,6 +945,19 @@ function buildAction( } else if (action instanceof GramJs.MessageActionWebViewDataSent) { text = 'Notification.WebAppSentData'; translationValues.push(action.text); + } else if (action instanceof GramJs.MessageActionGiftPremium) { + text = isOutgoing ? 'ActionGiftOutbound' : 'ActionGiftInbound'; + if (isOutgoing) { + translationValues.push('%gift_payment_amount%'); + } else { + translationValues.push('%action_origin%', '%gift_payment_amount%'); + } + if (targetPeerId) { + targetUserIds.push(targetPeerId); + } + currency = action.currency; + amount = action.amount.toJSNumber(); + months = action.months; } else { text = 'ChatList.UnsupportedMessage'; } @@ -965,6 +979,7 @@ function buildAction( call, phoneCall, score, + months, }; } diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index c1bab2ee6..6b3249733 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -41,7 +41,7 @@ export { fetchStickerSets, fetchRecentStickers, fetchFavoriteStickers, fetchFeaturedStickers, faveSticker, fetchStickers, fetchSavedGifs, saveGif, searchStickers, installStickerSet, uninstallStickerSet, searchGifs, fetchAnimatedEmojis, fetchStickersForEmoji, fetchEmojiKeywords, fetchAnimatedEmojiEffects, - removeRecentSticker, clearRecentStickers, + removeRecentSticker, clearRecentStickers, fetchPremiumGifts, } from './symbols'; export { diff --git a/src/api/gramjs/methods/symbols.ts b/src/api/gramjs/methods/symbols.ts index d8d7fa04c..27320b2f0 100644 --- a/src/api/gramjs/methods/symbols.ts +++ b/src/api/gramjs/methods/symbols.ts @@ -163,6 +163,21 @@ export async function fetchAnimatedEmojiEffects() { }; } +export async function fetchPremiumGifts() { + const result = await invokeRequest(new GramJs.messages.GetStickerSet({ + stickerset: new GramJs.InputStickerSetPremiumGifts(), + })); + + if (!(result instanceof GramJs.messages.StickerSet)) { + return undefined; + } + + return { + set: buildStickerSet(result.set), + stickers: processStickerResult(result.documents), + }; +} + export async function searchStickers({ query, hash = '0' }: { query: string; hash?: string }) { const result = await invokeRequest(new GramJs.messages.SearchStickerSets({ q: query, diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 4e043b3a7..34a325efc 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -235,6 +235,7 @@ export interface ApiAction { call?: Partial; phoneCall?: PhoneCallAction; score?: number; + months?: number; } export interface ApiWebPage { diff --git a/src/assets/fonts/icomoon.woff b/src/assets/fonts/icomoon.woff index c4d15655a..aa7251477 100644 Binary files a/src/assets/fonts/icomoon.woff and b/src/assets/fonts/icomoon.woff differ diff --git a/src/assets/fonts/icomoon.woff2 b/src/assets/fonts/icomoon.woff2 index 0d62eb289..7b75d5898 100644 Binary files a/src/assets/fonts/icomoon.woff2 and b/src/assets/fonts/icomoon.woff2 differ diff --git a/src/assets/mir.svg b/src/assets/mir.svg new file mode 100644 index 000000000..e0ce80883 --- /dev/null +++ b/src/assets/mir.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/bundles/extra.ts b/src/bundles/extra.ts index f4c34825f..e2dcb09f9 100644 --- a/src/bundles/extra.ts +++ b/src/bundles/extra.ts @@ -13,6 +13,7 @@ export { default as BotTrustModal } from '../components/main/BotTrustModal'; export { default as BotAttachModal } from '../components/main/BotAttachModal'; export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDialog'; export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal'; +export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal'; export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal'; export { default as AboutAdsModal } from '../components/common/AboutAdsModal'; diff --git a/src/components/common/helpers/detectCardType.ts b/src/components/common/helpers/detectCardType.ts index 7002e93fd..34f6df6ef 100644 --- a/src/components/common/helpers/detectCardType.ts +++ b/src/components/common/helpers/detectCardType.ts @@ -1,17 +1,20 @@ const VISA = /^4[0-9]{12}(?:[0-9]{1,3})?$/; const MASTERCARD1 = /^5[1-5][0-9]{11,14}$/; const MASTERCARD2 = /^2[2-7][0-9]{11,14}$/; +const MIR = /^220[0-4]/; export enum CardType { Default, Visa, Mastercard, + Mir, } const cards: Record = { [CardType.Default]: '', [CardType.Visa]: 'visa', [CardType.Mastercard]: 'mastercard', + [CardType.Mir]: 'mir', }; export function detectCardType(cardNumber: string): number { @@ -19,6 +22,9 @@ export function detectCardType(cardNumber: string): number { if (VISA.test(cardNumber)) { return CardType.Visa; } + if (MIR.test(cardNumber)) { + return CardType.Mir; + } if (MASTERCARD1.test(cardNumber) || MASTERCARD2.test(cardNumber)) { return CardType.Mastercard; } diff --git a/src/components/common/helpers/renderActionMessageText.tsx b/src/components/common/helpers/renderActionMessageText.tsx index 90b04b88c..2f9a5e992 100644 --- a/src/components/common/helpers/renderActionMessageText.tsx +++ b/src/components/common/helpers/renderActionMessageText.tsx @@ -55,6 +55,15 @@ export function renderActionMessageText( if (translationKey.includes('ScoredInGame')) { // Translation hack for games unprocessed = unprocessed.replace('un1', '%action_origin%').replace('un2', '%message%'); } + if (translationKey === 'ActionGiftOutbound') { // Translation hack for Premium Gift + unprocessed = unprocessed.replace('un2', '%gift_payment_amount%').replace(/\*\*/g, ''); + } + if (translationKey === 'ActionGiftInbound') { // Translation hack for Premium Gift + unprocessed = unprocessed + .replace('un1', '%action_origin%') + .replace('un2', '%gift_payment_amount%') + .replace(/\*\*/g, ''); + } let processed: TextPart[]; if (unprocessed.includes('%payment_amount%')) { @@ -80,6 +89,16 @@ export function renderActionMessageText( unprocessed = processed.pop() as string; content.push(...processed); + if (unprocessed.includes('%gift_payment_amount%')) { + processed = processPlaceholder( + unprocessed, + '%gift_payment_amount%', + formatCurrency(amount!, currency!, lang.code), + ); + unprocessed = processed.pop() as string; + content.push(...processed); + } + if (unprocessed.includes('%score%')) { processed = processPlaceholder( unprocessed, diff --git a/src/components/main/Main.tsx b/src/components/main/Main.tsx index 772bd2ed8..d93d9ee9d 100644 --- a/src/components/main/Main.tsx +++ b/src/components/main/Main.tsx @@ -169,6 +169,7 @@ const Main: FC = ({ loadCountryList, loadAvailableReactions, loadStickerSets, + loadPremiumGifts, loadAddedStickers, loadFavoriteStickers, ensureTimeFormat, @@ -206,10 +207,12 @@ const Main: FC = ({ loadEmojiKeywords({ language: BASE_EMOJI_KEYWORD_LANG }); loadAttachMenuBots(); loadContactList(); + loadPremiumGifts(); } }, [ lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings, loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachMenuBots, loadContactList, + loadPremiumGifts, ]); // Language-based API calls diff --git a/src/components/main/premium/GiftOption.module.scss b/src/components/main/premium/GiftOption.module.scss new file mode 100644 index 000000000..e29731157 --- /dev/null +++ b/src/components/main/premium/GiftOption.module.scss @@ -0,0 +1,110 @@ +.wrapper { + position: relative; + display: block; + padding-inline: 4.5rem 1rem; + margin-bottom: 1.5rem; + border-radius: var(--border-radius-default); + background: var(--color-background); + box-shadow: 0 0 0 0.0625rem var(--color-borders-input); + + cursor: pointer; + + line-height: 1.5rem; +} + +.active { + box-shadow: 0 0 0 0.125rem var(--color-primary); +} + +.input { + position: absolute; + z-index: var(--z-below); + opacity: 0; + + &:checked ~ .content { + &::before { + border-color: var(--color-primary); + } + + &::after { + opacity: 1; + } + } +} + +.content { + display: grid; + grid-template-areas: "left_top right" "left_bottom right"; + grid-template-columns: 1fr auto; + justify-content: start; + padding: 0.5rem 0; + gap: 0.25rem; + + &::before, + &::after { + content: ""; + display: block; + position: absolute; + inset-inline-start: 1.0625rem; + top: 50%; + width: 1.25rem; + height: 1.25rem; + transform: translateY(-50%); + } + + &::before { + border: 2px solid var(--color-borders-input); + border-radius: 50%; + background-color: var(--color-background); + opacity: 1; + transition: border-color 0.1s ease, opacity 0.1s ease; + } + + &::after { + inset-inline-start: 1.375rem; + width: 0.625rem; + height: 0.625rem; + border-radius: 50%; + background: var(--color-primary); + opacity: 0; + transition: opacity 0.1s ease; + } +} + +.month { + grid-area: left_top; + white-space: nowrap; +} + +.perMonth { + grid-area: left_bottom; + align-self: end; + display: flex; + flex-direction: row-reverse; + margin-inline-end: auto; + + font-size: 0.875rem; + color: var(--color-text-secondary); + + @media (max-width: 450px) { + flex-direction: column-reverse; + } +} + +.amount { + grid-area: right; + align-self: center; + justify-self: end; + padding-inline-start: 1.5rem; + color: var(--color-text-secondary); +} + +.discount { + color: var(--color-white); + background: var(--color-primary); + border-radius: var(--border-radius-default-small); + padding: 0 0.5rem; + unicode-bidi: plaintext; + margin-inline-end: 0.5rem; + align-self: baseline; +} diff --git a/src/components/main/premium/GiftOption.tsx b/src/components/main/premium/GiftOption.tsx new file mode 100644 index 000000000..a2dc12afe --- /dev/null +++ b/src/components/main/premium/GiftOption.tsx @@ -0,0 +1,64 @@ +import type { ChangeEvent } from 'react'; +import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; + +import type { FC } from '../../../lib/teact/teact'; +import type { ApiPremiumGiftOption } from '../../../api/types'; + +import { formatCurrency } from '../../../util/formatCurrency'; +import buildClassName from '../../../util/buildClassName'; +import useLang from '../../../hooks/useLang'; + +import styles from './GiftOption.module.scss'; + +type OwnProps = { + option: ApiPremiumGiftOption; + checked?: boolean; + fullMonthlyAmount?: number; + onChange: (month: number) => void; +}; + +const GiftOption: FC = ({ + option, checked, fullMonthlyAmount, onChange, +}) => { + const lang = useLang(); + + const { months, amount, currency } = option; + const perMonth = Math.floor(amount / months); + + const discount = useMemo(() => { + return fullMonthlyAmount && fullMonthlyAmount > perMonth + ? Math.ceil(100 - perMonth / (fullMonthlyAmount / 100)) + : undefined; + }, [fullMonthlyAmount, perMonth]); + + const handleChange = useCallback((e: ChangeEvent) => { + if (e.target.checked) { + onChange(months); + } + }, [months, onChange]); + + return ( + + ); +}; + +export default memo(GiftOption); diff --git a/src/components/main/premium/GiftPremiumModal.async.tsx b/src/components/main/premium/GiftPremiumModal.async.tsx new file mode 100644 index 000000000..1c5e0abd8 --- /dev/null +++ b/src/components/main/premium/GiftPremiumModal.async.tsx @@ -0,0 +1,17 @@ +import type { FC } from '../../../lib/teact/teact'; +import React, { memo } from '../../../lib/teact/teact'; +import { Bundles } from '../../../util/moduleLoader'; + +import type { OwnProps } from './GiftPremiumModal'; + +import useModuleLoader from '../../../hooks/useModuleLoader'; + +const GiftPremiumModalAsync: FC = (props) => { + const { isOpen } = props; + const GiftPremiumModal = useModuleLoader(Bundles.Extra, 'GiftPremiumModal', !isOpen); + + // eslint-disable-next-line react/jsx-props-no-spreading + return GiftPremiumModal ? : undefined; +}; + +export default memo(GiftPremiumModalAsync); diff --git a/src/components/main/premium/GiftPremiumModal.module.scss b/src/components/main/premium/GiftPremiumModal.module.scss new file mode 100644 index 000000000..bfe8e82bb --- /dev/null +++ b/src/components/main/premium/GiftPremiumModal.module.scss @@ -0,0 +1,44 @@ +@media (min-width: 451px) { + .modalDialog :global(.modal-dialog) { + max-width: 32rem !important; + } +} + +.closeButton { + position: absolute; + top: 0.5rem; + left: 0.5rem; +} + +.avatar { + margin: 0 auto 1.5rem; +} + +.headerText { + font-size: 1.5rem; + font-weight: 500; + text-align: center; +} + +.description, +.premiumFeatures { + text-align: center; + margin: 0 auto 2rem; + max-width: 25rem; +} + +.premiumFeatures { + font-size: 0.9375rem; + color: var(--color-text-secondary); +} + +.options { + margin-bottom: 2.5rem; +} + +.button { + height: 3rem; + background: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%); + font-size: 1rem; + font-weight: 600; +} diff --git a/src/components/main/premium/GiftPremiumModal.tsx b/src/components/main/premium/GiftPremiumModal.tsx new file mode 100644 index 000000000..d4f96c777 --- /dev/null +++ b/src/components/main/premium/GiftPremiumModal.tsx @@ -0,0 +1,166 @@ +import React, { + memo, useCallback, useEffect, useMemo, useState, +} from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; + +import type { FC } from '../../../lib/teact/teact'; +import type { ApiPremiumGiftOption, ApiUser } from '../../../api/types'; + +import { formatCurrency } from '../../../util/formatCurrency'; +import renderText from '../../common/helpers/renderText'; +import { getUserFirstOrLastName } from '../../../global/helpers'; +import { selectUser } from '../../../global/selectors'; + +import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; +import useLang from '../../../hooks/useLang'; + +import Modal from '../../ui/Modal'; +import Button from '../../ui/Button'; +import Link from '../../ui/Link'; +import Avatar from '../../common/Avatar'; +import GiftOption from './GiftOption'; + +import styles from './GiftPremiumModal.module.scss'; + +export type OwnProps = { + isOpen?: boolean; +}; + +type StateProps = { + user?: ApiUser; + gifts?: ApiPremiumGiftOption[]; + monthlyCurrency?: string; + monthlyAmount?: number; +}; + +const GiftPremiumModal: FC = ({ + isOpen, user, gifts, monthlyCurrency, monthlyAmount, +}) => { + const { openPremiumModal, closeGiftPremiumModal, openUrl } = getActions(); + + const lang = useLang(); + const renderedUser = useCurrentOrPrev(user, true); + const renderedGifts = useCurrentOrPrev(gifts, true); + const [selectedOption, setSelectedOption] = useState(); + const firstGift = renderedGifts?.[0]; + const fullMonthlyAmount = useMemo(() => { + if (!renderedGifts || renderedGifts.length === 0 || !firstGift) { + return undefined; + } + + const cheaperGift = renderedGifts.reduce((acc, gift) => { + return gift.amount < firstGift?.amount ? gift : firstGift; + }, firstGift); + + return cheaperGift.currency === monthlyCurrency && monthlyAmount + ? monthlyAmount + : Math.floor(cheaperGift.amount / cheaperGift.months); + }, [firstGift, renderedGifts, monthlyAmount, monthlyCurrency]); + + useEffect(() => { + if (isOpen) { + setSelectedOption(firstGift?.months); + } + }, [firstGift?.months, isOpen]); + + const selectedGift = useMemo(() => { + return renderedGifts?.find((gift) => gift.months === selectedOption); + }, [renderedGifts, selectedOption]); + + const handleSubmit = useCallback(() => { + if (!selectedGift) { + return; + } + + closeGiftPremiumModal(); + openUrl({ url: selectedGift.botUrl }); + }, [closeGiftPremiumModal, openUrl, selectedGift]); + + const handlePremiumClick = useCallback(() => { + openPremiumModal(); + }, [openPremiumModal]); + + function renderPremiumFeaturesLink() { + const info = lang('GiftPremiumListFeaturesAndTerms'); + // Translation hack for rendering component inside string + const parts = info.match(/([^*]*)\*([^*]+)\*(.*)/); + + if (!parts || parts.length < 4) { + return undefined; + } + + return ( +

+ {parts[1]} + {parts[2]} + {parts[3]} +

+ ); + } + + return ( + +
+ + +

+ {lang('GiftTelegramPremiumTitle')} +

+

+ {renderText( + lang('GiftTelegramPremiumDescription', getUserFirstOrLastName(renderedUser)), + ['emoji', 'simple_markdown'], + )} +

+ +
+ {renderedGifts?.map((gift) => ( + + ))} +
+ + {renderPremiumFeaturesLink()} +
+ + +
+ ); +}; + +export default memo(withGlobal((global): StateProps => { + const { forUserId, monthlyCurrency, monthlyAmount } = global.giftPremiumModal || {}; + const user = forUserId ? selectUser(global, forUserId) : undefined; + const gifts = user ? user.fullInfo?.premiumGifts : undefined; + + return { + user, + gifts, + monthlyCurrency, + monthlyAmount: monthlyAmount ? Number(monthlyAmount) : undefined, + }; +})(GiftPremiumModal)); diff --git a/src/components/main/premium/PremiumFeatureItem.module.scss b/src/components/main/premium/PremiumFeatureItem.module.scss index 4ab359ab5..42ed3e106 100644 --- a/src/components/main/premium/PremiumFeatureItem.module.scss +++ b/src/components/main/premium/PremiumFeatureItem.module.scss @@ -14,11 +14,11 @@ } .description { - font-size: 14px; + font-size: 0.875rem; color: var(--color-text-secondary); white-space: pre-wrap; - line-height: 20px; - min-height: 40px; + line-height: 1.25rem; + min-height: 2.5rem; } .icon { diff --git a/src/components/main/premium/PremiumFeatureModal.module.scss b/src/components/main/premium/PremiumFeatureModal.module.scss index 40b70af58..2dda6c4c0 100644 --- a/src/components/main/premium/PremiumFeatureModal.module.scss +++ b/src/components/main/premium/PremiumFeatureModal.module.scss @@ -4,8 +4,8 @@ .button { font-weight: 600; - font-size: 16px; - height: 48px; + font-size: 1rem; + height: 3rem; } .button-premium { @@ -63,17 +63,17 @@ .limits-content { overflow: auto; padding: 1rem; - margin-top: 59px; + margin-top: 3.6875rem; height: calc(var(--vh) * 55 + 41px); } .header { padding-left: 4rem; - font-size: 20px; + font-size: 1.25rem; font-weight: 500; padding-top: 0.875rem; padding-bottom: 0.875rem; - border-bottom: 1px solid var(--color-borders); + border-bottom: 0.0625rem solid var(--color-borders); position: absolute; background: var(--color-background); width: 100%; @@ -101,7 +101,7 @@ } .title { - font-size: 20px; + font-size: 1.25rem; font-weight: 500; text-align: center; color: var(--color-text); @@ -109,16 +109,16 @@ } .description { - font-size: 16px; + font-size: 1rem; font-weight: 400; - line-height: 22px; + line-height: 1.375rem; text-align: center; color: var(--color-text-secondary); padding: 0 5%; } .footer { - border-top: 1px solid var(--color-borders); + border-top: 0.0625rem solid var(--color-borders); position: absolute; bottom: 0; left: 0; diff --git a/src/components/main/premium/PremiumMainModal.module.scss b/src/components/main/premium/PremiumMainModal.module.scss index 223d80c42..3cda07669 100644 --- a/src/components/main/premium/PremiumMainModal.module.scss +++ b/src/components/main/premium/PremiumMainModal.module.scss @@ -26,8 +26,8 @@ .button { font-weight: 600; background: var(--premium-gradient); - font-size: 16px; - height: 48px; + font-size: 1rem; + height: 3rem; } .main { @@ -46,7 +46,7 @@ } .header-text { - font-size: 24px; + font-size: 1.5rem; font-weight: 500; text-align: center; } @@ -57,7 +57,7 @@ } .list { - margin-bottom: 80px; + margin-bottom: 5rem; width: 100%; } @@ -69,12 +69,12 @@ z-index: 2; display: flex; align-items: center; - border-bottom: 1px solid var(--color-borders); + border-bottom: 0.0625rem solid var(--color-borders); position: absolute; width: 100%; left: 0; top: 0; - height: 56px; + height: 3.5rem; padding: 0.5rem; background: var(--color-background); transition: 0.25s ease-out transform; @@ -92,7 +92,7 @@ } .premium-header-text { - font-size: 20px; + font-size: 1.25rem; font-weight: 500; margin: 0 0 0 3rem; } @@ -111,7 +111,7 @@ position: absolute; width: 100%; background: var(--color-background); - border-top: 1px solid var(--color-borders); + border-top: 0.0625rem solid var(--color-borders); left: 0; bottom: 0; padding: 1rem; diff --git a/src/components/main/premium/PremiumMainModal.tsx b/src/components/main/premium/PremiumMainModal.tsx index ddbed16f2..a8568f6aa 100644 --- a/src/components/main/premium/PremiumMainModal.tsx +++ b/src/components/main/premium/PremiumMainModal.tsx @@ -65,12 +65,16 @@ export type OwnProps = { }; type StateProps = { + currentUserId?: string; promo?: ApiPremiumPromo; isClosing?: boolean; fromUser?: ApiUser; + toUser?: ApiUser; initialSection?: string; isPremium?: boolean; isSuccess?: boolean; + isGift?: boolean; + monthsAmount?: number; limitChannels: number; limitPins: number; limitLinks: number; @@ -83,6 +87,7 @@ type StateProps = { const PremiumMainModal: FC = ({ isOpen, + currentUserId, fromUser, promo, initialSection, @@ -96,6 +101,9 @@ const PremiumMainModal: FC = ({ premiumBotUsername, isClosing, isSuccess, + isGift, + toUser, + monthsAmount, premiumPromoOrder, }) => { // eslint-disable-next-line no-null/no-null @@ -170,6 +178,42 @@ const PremiumMainModal: FC = ({ if (!promo) return undefined; + function getHeaderText() { + if (isGift) { + return fromUser?.id === currentUserId + ? lang('TelegramPremiumUserGiftedPremiumOutboundDialogTitle', [getUserFullName(toUser), monthsAmount]) + : lang('TelegramPremiumUserGiftedPremiumDialogTitle', [getUserFullName(fromUser), monthsAmount]); + } + + return fromUser + ? lang('TelegramPremiumUserDialogTitle', getUserFullName(fromUser)) + : lang(isPremium ? 'TelegramPremiumSubscribedTitle' : 'TelegramPremium'); + } + + function getHeaderDescription() { + if (isGift) { + return fromUser?.id === currentUserId + ? lang('TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle', getUserFullName(toUser)) + : lang('TelegramPremiumUserGiftedPremiumDialogSubtitle'); + } + + return fromUser + ? lang('TelegramPremiumUserDialogSubtitle') + : lang(isPremium ? 'TelegramPremiumSubscribedSubtitle' : 'TelegramPremiumSubtitle'); + } + + function renderFooterText() { + if (!promo || (isGift && fromUser?.id === currentUserId)) { + return undefined; + } + + return ( +
+ {renderTextWithEntities(promo.statusText, promo.statusEntities)} +
+ ); + } + return ( = ({

- {renderText( - fromUser - ? lang('TelegramPremiumUserDialogTitle', getUserFullName(fromUser)) - : lang(isPremium ? 'TelegramPremiumSubscribedTitle' : 'TelegramPremium'), - ['simple_markdown', 'emoji'], - )} + {renderText(getHeaderText(), ['simple_markdown', 'emoji'])}

- {renderText( - lang(fromUser ? 'TelegramPremiumUserDialogSubtitle' - : (isPremium ? 'TelegramPremiumSubscribedSubtitle' : 'TelegramPremiumSubtitle')), - ['simple_markdown'], - )} + {renderText(getHeaderDescription(), ['simple_markdown', 'emoji'])}

@@ -240,18 +275,7 @@ const PremiumMainModal: FC = ({ {renderText(lang('AboutPremiumDescription2'), ['simple_markdown'])}

-
- {renderTextWithEntities( - promo.statusText, - promo.statusEntities, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - )} -
+ {renderFooterText()} {!isPremium && (
@@ -280,10 +304,14 @@ const PremiumMainModal: FC = ({ export default memo(withGlobal((global): StateProps => { return { + currentUserId: global.currentUserId, promo: global.premiumModal?.promo, isClosing: global.premiumModal?.isClosing, isSuccess: global.premiumModal?.isSuccess, + isGift: global.premiumModal?.isGift, + monthsAmount: global.premiumModal?.monthsAmount, fromUser: global.premiumModal?.fromUserId ? selectUser(global, global.premiumModal.fromUserId) : undefined, + toUser: global.premiumModal?.toUserId ? selectUser(global, global.premiumModal.toUserId) : undefined, initialSection: global.premiumModal?.initialSection, isPremium: selectIsCurrentUserPremium(global), limitChannels: selectPremiumLimit(global, 'channels'), diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx index 88df0e00e..cd08ac709 100644 --- a/src/components/middle/ActionMessage.tsx +++ b/src/components/middle/ActionMessage.tsx @@ -2,9 +2,11 @@ import type { FC } from '../../lib/teact/teact'; import React, { memo, useEffect, useMemo, useRef, } from '../../lib/teact/teact'; -import { withGlobal } from '../../global'; +import { getActions, withGlobal } from '../../global'; -import type { ApiUser, ApiMessage, ApiChat } from '../../api/types'; +import type { + ApiUser, ApiMessage, ApiChat, ApiSticker, +} from '../../api/types'; import type { FocusDirection } from '../../types'; import { @@ -16,24 +18,27 @@ import { import { getMessageHtmlId, isChatChannel } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; import { renderActionMessageText } from '../common/helpers/renderActionMessageText'; +import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; import useEnsureMessage from '../../hooks/useEnsureMessage'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import type { ObserveFn } from '../../hooks/useIntersectionObserver'; -import { useOnIntersect } from '../../hooks/useIntersectionObserver'; +import { useIsIntersecting, useOnIntersect } from '../../hooks/useIntersectionObserver'; import useFocusMessage from './message/hooks/useFocusMessage'; import useLang from '../../hooks/useLang'; - -import ContextMenuContainer from './message/ContextMenuContainer.async'; import useFlag from '../../hooks/useFlag'; import useShowTransition from '../../hooks/useShowTransition'; -import { preventMessageInputBlur } from './helpers/preventMessageInputBlur'; + +import ContextMenuContainer from './message/ContextMenuContainer.async'; +import AnimatedIconFromSticker from '../common/AnimatedIconFromSticker'; type OwnProps = { message: ApiMessage; observeIntersection?: ObserveFn; + observeIntersectionForAnimation?: ObserveFn; isEmbedded?: boolean; appearanceOrder?: number; isLastInList?: boolean; + memoFirstUnreadIdRef?: { current: number | undefined }; }; type StateProps = { @@ -46,6 +51,7 @@ type StateProps = { isFocused: boolean; focusDirection?: FocusDirection; noFocusHighlight?: boolean; + premiumGiftSticker?: ApiSticker; }; const APPEARANCE_DELAY = 10; @@ -53,6 +59,7 @@ const APPEARANCE_DELAY = 10; const ActionMessage: FC = ({ message, observeIntersection, + observeIntersectionForAnimation, isEmbedded, appearanceOrder = 0, isLastInList, @@ -65,7 +72,13 @@ const ActionMessage: FC = ({ isFocused, focusDirection, noFocusHighlight, + premiumGiftSticker, + memoFirstUnreadIdRef, }) => { + const { openPremiumModal, requestConfetti } = getActions(); + + const lang = useLang(); + // eslint-disable-next-line no-null/no-null const ref = useRef(null); @@ -73,10 +86,10 @@ const ActionMessage: FC = ({ useEnsureMessage(message.chatId, message.replyToMessageId, targetMessage); useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight); - const lang = useLang(); - const noAppearanceAnimation = appearanceOrder <= 0; const [isShown, markShown] = useFlag(noAppearanceAnimation); + const isGift = Boolean(message.content.action?.text.startsWith('ActionGift')); + useEffect(() => { if (noAppearanceAnimation) { return; @@ -84,6 +97,21 @@ const ActionMessage: FC = ({ setTimeout(markShown, appearanceOrder * APPEARANCE_DELAY); }, [appearanceOrder, markShown, noAppearanceAnimation]); + + const isVisible = useIsIntersecting(ref, observeIntersectionForAnimation); + + const shouldShowConfettiRef = useRef((() => { + const isUnread = memoFirstUnreadIdRef?.current && message.id >= memoFirstUnreadIdRef.current; + return isGift && !message.isOutgoing && isUnread; + })()); + + useEffect(() => { + if (isVisible && shouldShowConfettiRef.current) { + shouldShowConfettiRef.current = false; + requestConfetti(); + } + }, [isVisible, requestConfetti]); + const { transitionClassNames } = useShowTransition(isShown, undefined, noAppearanceAnimation, false); const targetUsers = useMemo(() => { @@ -114,13 +142,41 @@ const ActionMessage: FC = ({ handleBeforeContextMenu(e); }; + const handlePremiumGiftClick = () => { + openPremiumModal({ + isGift: true, + fromUserId: senderUser?.id, + toUserId: targetUserIds?.[0], + monthsAmount: message.content.action?.months || 0, + }); + }; + if (isEmbedded) { return {content}; } + function renderGift() { + return ( + + + {lang('ActionGiftPremiumTitle')} + {lang('ActionGiftPremiumSubtitle', lang('Months', message.content.action?.months, 'i'))} + + {lang('ActionGiftPremiumView')} + + ); + } + const className = buildClassName( 'ActionMessage message-list-item', isFocused && !noFocusHighlight && 'focused', + isGift && 'premium-gift', isContextMenuShown && 'has-menu-open', isLastInList && 'last-in-list', transitionClassNames, @@ -136,6 +192,7 @@ const ActionMessage: FC = ({ onContextMenu={handleContextMenu} > {content} + {isGift && renderGift()} {contextMenuPosition && ( ( const isChat = chat && (isChatChannel(chat) || userId === message.chatId); const senderUser = !isChat && userId ? selectUser(global, userId) : undefined; const senderChat = isChat ? chat : undefined; + const premiumGiftSticker = global.premiumGifts?.stickers?.[0]; return { usersById, @@ -176,6 +234,7 @@ export default memo(withGlobal( targetUserIds, targetMessage, isFocused, + premiumGiftSticker, ...(isFocused && { focusDirection, noFocusHighlight }), }; }, diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index a367fb0d1..acdcd475b 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -11,7 +11,7 @@ import { REPLIES_USER_ID } from '../../config'; import { IS_SINGLE_COLUMN_LAYOUT } from '../../util/environment'; import { disableScrolling, enableScrolling } from '../../util/scrollLock'; import { - selectChat, selectNotifySettings, selectNotifyExceptions, selectUser, selectChatBot, + selectChat, selectNotifySettings, selectNotifyExceptions, selectUser, selectChatBot, selectIsPremiumPurchaseBlocked, } from '../../global/selectors'; import { isUserId, getCanDeleteChat, selectIsChatMuted, getCanAddContact, isChatChannel, isChatGroup, @@ -73,6 +73,7 @@ type StateProps = { canAddContact?: boolean; canReportChat?: boolean; canDeleteChat?: boolean; + canGiftPremium?: boolean; hasLinkedChat?: boolean; }; @@ -98,6 +99,7 @@ const HeaderMenuContainer: FC = ({ isMuted, canReportChat, canDeleteChat, + canGiftPremium, hasLinkedChat, canAddContact, onSubscribeChannel, @@ -116,6 +118,7 @@ const HeaderMenuContainer: FC = ({ openAddContactDialog, requestCall, toggleStatistics, + openGiftPremiumModal, } = getActions(); const [isMenuOpen, setIsMenuOpen] = useState(true); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -181,6 +184,11 @@ const HeaderMenuContainer: FC = ({ closeMenu(); }, [chatId, closeMenu, openLinkedChat]); + const handleGiftPremiumClick = useCallback(() => { + openGiftPremiumModal({ forUserId: chatId }); + closeMenu(); + }, [openGiftPremiumModal, chatId, closeMenu]); + const handleAddContactClick = useCallback(() => { openAddContactDialog({ userId: chatId }); closeMenu(); @@ -358,6 +366,14 @@ const HeaderMenuContainer: FC = ({ )} {botButtons} + {canGiftPremium && ( + + {lang('GiftPremium')} + + )} {canLeave && ( ( const canReportChat = isChatChannel(chat) || isChatGroup(chat) || (user && !user.isSelf); const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined; + const canGiftPremium = Boolean( + global.lastSyncTime + && user?.fullInfo?.premiumGifts?.length + && !selectIsPremiumPurchaseBlocked(global), + ); return { chat, @@ -410,6 +431,7 @@ export default memo(withGlobal( canAddContact, canReportChat, canDeleteChat: getCanDeleteChat(chat), + canGiftPremium, hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId), botCommands: chatBot?.fullInfo?.botInfo?.commands, }; diff --git a/src/components/middle/MessageList.scss b/src/components/middle/MessageList.scss index 15625b7db..4c425105e 100644 --- a/src/components/middle/MessageList.scss +++ b/src/components/middle/MessageList.scss @@ -259,6 +259,31 @@ } } + .ActionMessage.premium-gift { + display: flex; + flex-direction: column; + align-items: center; + } + + .action-message-gift { + display: flex !important; + flex-direction: column; + align-items: center; + line-height: 1rem !important; + padding-bottom: 0.75rem !important; + margin-top: 0.5rem; + cursor: pointer; + outline: none; + } + + .action-message-button { + display: inline-block; + border-radius: var(--border-radius-default); + padding: 0.5rem 0.75rem; + margin-top: 0.5rem; + background-color: var(--pattern-color); + } + .sticky-date { margin-top: 1rem; margin-bottom: 1rem; diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx index 3f148fe60..317f36b50 100644 --- a/src/components/middle/MessageListContent.tsx +++ b/src/components/middle/MessageListContent.tsx @@ -144,6 +144,8 @@ const MessageListContent: FC = ({ key={message.id} message={message} observeIntersection={observeIntersectionForReading} + observeIntersectionForAnimation={observeIntersectionForAnimatedStickers} + memoFirstUnreadIdRef={memoFirstUnreadIdRef} appearanceOrder={messageCountToAnimate - ++appearanceIndex} isLastInList={isLastInList} />, diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 44e5eb364..bb8002e3f 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -71,6 +71,7 @@ import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async'; import SeenByModal from '../common/SeenByModal.async'; import EmojiInteractionAnimation from './EmojiInteractionAnimation.async'; import ReactorListModal from './ReactorListModal.async'; +import GiftPremiumModal from '../main/premium/GiftPremiumModal.async'; import './MiddleColumn.scss'; import styles from './MiddleColumn.module.scss'; @@ -98,6 +99,7 @@ type StateProps = { isSelectModeActive?: boolean; isSeenByModalOpen: boolean; isReactorListModalOpen: boolean; + isGiftPremiumModalOpen?: boolean; animationLevel?: number; shouldSkipHistoryAnimations?: boolean; currentTransitionKey: number; @@ -140,6 +142,7 @@ const MiddleColumn: FC = ({ isSelectModeActive, isSeenByModalOpen, isReactorListModalOpen, + isGiftPremiumModalOpen, animationLevel, shouldSkipHistoryAnimations, currentTransitionKey, @@ -551,6 +554,7 @@ const MiddleColumn: FC = ({ /> ))}
+ ); }; @@ -580,6 +584,7 @@ export default memo(withGlobal( isSelectModeActive: selectIsInSelectMode(global), isSeenByModalOpen: Boolean(global.seenByModal), isReactorListModalOpen: Boolean(global.reactorModal), + isGiftPremiumModalOpen: global.giftPremiumModal?.isOpen, animationLevel: global.settings.byKey.animationLevel, currentTransitionKey: Math.max(0, messageLists.length - 1), activeEmojiInteractions, diff --git a/src/components/payment/CardInput.tsx b/src/components/payment/CardInput.tsx index 635e15dea..0e9d21b55 100644 --- a/src/components/payment/CardInput.tsx +++ b/src/components/payment/CardInput.tsx @@ -14,6 +14,7 @@ import './CardInput.scss'; import mastercardIconPath from '../../assets/mastercard.svg'; import visaIconPath from '../../assets/visa.svg'; +import mirIconPath from '../../assets/mir.svg'; const CARD_NUMBER_MAX_LENGTH = 23; @@ -73,6 +74,8 @@ function getCardIcon(cardType: CardType) { return ; case CardType.Visa: return ; + case CardType.Mir: + return ; default: return undefined; } diff --git a/src/components/ui/Link.module.scss b/src/components/ui/Link.module.scss new file mode 100644 index 000000000..ad93cba37 --- /dev/null +++ b/src/components/ui/Link.module.scss @@ -0,0 +1,19 @@ +.link { + color: inherit; + + &:hover { + color: inherit; + + &:global(.GroupCallLink) { + text-decoration: none; + } + } +} + +.isPrimary { + color: var(--color-primary); + + &:hover { + color: var(--color-primary); + } +} diff --git a/src/components/ui/Link.scss b/src/components/ui/Link.scss deleted file mode 100644 index c68289c5d..000000000 --- a/src/components/ui/Link.scss +++ /dev/null @@ -1,11 +0,0 @@ -.Link { - color: inherit; - - &:hover { - color: inherit; - - &.GroupCallLink { - text-decoration: none; - } - } -} diff --git a/src/components/ui/Link.tsx b/src/components/ui/Link.tsx index bc488d8cc..23993fbde 100644 --- a/src/components/ui/Link.tsx +++ b/src/components/ui/Link.tsx @@ -3,17 +3,18 @@ import React, { useCallback } from '../../lib/teact/teact'; import buildClassName from '../../util/buildClassName'; -import './Link.scss'; +import styles from './Link.module.scss'; type OwnProps = { children: React.ReactNode; className?: string; isRtl?: boolean; + isPrimary?: boolean; onClick?: (e: React.MouseEvent) => void; }; const Link: FC = ({ - children, className, isRtl, onClick, + children, isPrimary, className, isRtl, onClick, }) => { const handleClick = useCallback((e: React.MouseEvent) => { e.preventDefault(); @@ -23,7 +24,7 @@ const Link: FC = ({ return ( diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index d14b57a4c..0cc9c990c 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -642,6 +642,10 @@ addActionHandler('openTelegramLink', (global, actions, payload) => { chatId, messageId, }); + } else if (part1.startsWith('$')) { + actions.openInvoice({ + slug: part1.substring(1), + }); } else if (part1 === 'invoice') { actions.openInvoice({ slug: part2, diff --git a/src/global/actions/api/payments.ts b/src/global/actions/api/payments.ts index b428af158..f9aa3a7b7 100644 --- a/src/global/actions/api/payments.ts +++ b/src/global/actions/api/payments.ts @@ -376,7 +376,9 @@ addActionHandler('closePremiumModal', (global, actions, payload) => { }); addActionHandler('openPremiumModal', async (global, actions, payload) => { - const { initialSection, fromUserId, isSuccess } = payload || {}; + const { + initialSection, fromUserId, isSuccess, isGift, monthsAmount, toUserId, + } = payload || {}; actions.loadPremiumStickers(); @@ -393,7 +395,36 @@ addActionHandler('openPremiumModal', async (global, actions, payload) => { initialSection, isOpen: true, fromUserId, + toUserId, + isGift, + monthsAmount, isSuccess, }, }); }); + +addActionHandler('openGiftPremiumModal', async (global, actions, payload) => { + const { forUserId } = payload || {}; + const result = await callApi('fetchPremiumPromo'); + if (!result) return; + + global = getGlobal(); + global = addUsers(global, buildCollectionByKey(result.users, 'id')); + + setGlobal({ + ...global, + giftPremiumModal: { + isOpen: true, + forUserId, + monthlyCurrency: result.promo.currency, + monthlyAmount: result.promo.monthlyAmount, + }, + }); +}); + +addActionHandler('closeGiftPremiumModal', (global) => { + setGlobal({ + ...global, + giftPremiumModal: { isOpen: false }, + }); +}); diff --git a/src/global/actions/api/symbols.ts b/src/global/actions/api/symbols.ts index f50223210..28ef0aa14 100644 --- a/src/global/actions/api/symbols.ts +++ b/src/global/actions/api/symbols.ts @@ -109,6 +109,20 @@ addActionHandler('loadFeaturedStickers', (global) => { void loadFeaturedStickers(hash); }); +addActionHandler('loadPremiumGifts', async () => { + const stickerSet = await callApi('fetchPremiumGifts'); + if (!stickerSet) { + return; + } + + const { set, stickers } = stickerSet; + + setGlobal({ + ...getGlobal(), + premiumGifts: { ...set, stickers }, + }); +}); + addActionHandler('loadStickers', (global, actions, payload) => { const { stickerSetId, stickerSetShortName } = payload!; let { stickerSetAccessHash } = payload!; diff --git a/src/global/actions/apiUpdaters/payments.ts b/src/global/actions/apiUpdaters/payments.ts index 6cfc40de8..cf0e29c1c 100644 --- a/src/global/actions/apiUpdaters/payments.ts +++ b/src/global/actions/apiUpdaters/payments.ts @@ -7,16 +7,16 @@ addActionHandler('apiUpdate', (global, actions, update) => { switch (update['@type']) { case 'updatePaymentStateCompleted': { const { inputInvoice } = global.payment; - if (update.slug && inputInvoice && 'slug' in inputInvoice && inputInvoice.slug !== update.slug) { - return undefined; - } - // On the production host, the payment frame receives a message with the payment event, // after which the payment form closes. In other cases, the payment form must be closed manually. if (!IS_PRODUCTION_HOST) { global = clearPayment(global); } + if (update.slug && inputInvoice && 'slug' in inputInvoice && inputInvoice.slug !== update.slug) { + return !IS_PRODUCTION_HOST ? global : undefined; + } + return { ...global, payment: { diff --git a/src/global/types.ts b/src/global/types.ts index 06e3fcedd..1c0170766 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -314,6 +314,7 @@ export type GlobalState = { animatedEmojis?: ApiStickerSet; animatedEmojiEffects?: ApiStickerSet; + premiumGifts?: ApiStickerSet; emojiKeywords: Partial>; gifs: { @@ -638,9 +639,19 @@ export type GlobalState = { promo: ApiPremiumPromo; initialSection?: string; fromUserId?: string; + toUserId?: string; + isGift?: boolean; + monthsAmount?: number; isSuccess?: boolean; }; + giftPremiumModal?: { + isOpen?: boolean; + forUserId?: string; + monthlyCurrency?: string; + monthlyAmount?: string; + }; + transcriptions: Record; limitReachedModal?: { @@ -1062,7 +1073,10 @@ export interface ActionPayloads { openPremiumModal: { initialSection?: string; fromUserId?: string; + toUserId?: string; isSuccess?: boolean; + isGift?: boolean; + monthsAmount?: number; }; closePremiumModal: never | { isClosed?: boolean; @@ -1073,10 +1087,17 @@ export interface ActionPayloads { messageId: number; }; + loadPremiumGifts: never; loadPremiumStickers: { hash?: string; }; + openGiftPremiumModal: { + forUserId?: string; + }; + + closeGiftPremiumModal: never; + // Invoice openInvoice: ApiInputInvoice; } diff --git a/src/styles/Telegram T.json b/src/styles/Telegram T.json index be51033b8..ba27c9300 100644 --- a/src/styles/Telegram T.json +++ b/src/styles/Telegram T.json @@ -2,7 +2,7 @@ "metadata": { "name": "Telegram T", "lastOpened": 0, - "created": 1659146815066 + "created": 1660758480766 }, "iconSets": [ { @@ -157,13 +157,21 @@ }, { "selection": [ + { + "order": 737, + "id": 77, + "name": "gift", + "prevSize": 32, + "code": 59821, + "tempChar": "" + }, { "order": 734, "id": 76, "name": "sort", "prevSize": 32, "code": 59820, - "tempChar": "" + "tempChar": "" }, { "order": 732, @@ -171,7 +179,7 @@ "name": "web", "prevSize": 32, "code": 59819, - "tempChar": "" + "tempChar": "" }, { "order": 731, @@ -179,7 +187,7 @@ "name": "transcribe", "prevSize": 32, "code": 59818, - "tempChar": "" + "tempChar": "" }, { "order": 719, @@ -187,7 +195,7 @@ "name": "add-one-badge", "prevSize": 32, "code": 59803, - "tempChar": "" + "tempChar": "" }, { "order": 720, @@ -195,7 +203,7 @@ "name": "chat-badge", "prevSize": 32, "code": 59808, - "tempChar": "" + "tempChar": "" }, { "order": 721, @@ -203,7 +211,7 @@ "name": "chats-badge", "prevSize": 32, "code": 59809, - "tempChar": "" + "tempChar": "" }, { "order": 722, @@ -211,7 +219,7 @@ "name": "double-badge", "prevSize": 32, "code": 59810, - "tempChar": "" + "tempChar": "" }, { "order": 723, @@ -219,7 +227,7 @@ "name": "file-badge", "prevSize": 32, "code": 59811, - "tempChar": "" + "tempChar": "" }, { "order": 724, @@ -227,7 +235,7 @@ "name": "folder-badge", "prevSize": 32, "code": 59812, - "tempChar": "" + "tempChar": "" }, { "order": 726, @@ -235,7 +243,7 @@ "name": "link-badge", "prevSize": 32, "code": 59813, - "tempChar": "" + "tempChar": "" }, { "order": 725, @@ -243,7 +251,7 @@ "name": "pin-badge", "prevSize": 32, "code": 59814, - "tempChar": "" + "tempChar": "" }, { "order": 727, @@ -251,7 +259,7 @@ "name": "premium", "prevSize": 32, "code": 59815, - "tempChar": "" + "tempChar": "" }, { "order": 728, @@ -259,7 +267,7 @@ "name": "unlock-badge", "prevSize": 32, "code": 59816, - "tempChar": "" + "tempChar": "" }, { "order": 729, @@ -267,7 +275,7 @@ "name": "lock-badge", "prevSize": 32, "code": 59817, - "tempChar": "" + "tempChar": "" }, { "order": 715, @@ -275,7 +283,7 @@ "name": "key", "prevSize": 32, "code": 59802, - "tempChar": "" + "tempChar": "" }, { "order": 714, @@ -283,7 +291,7 @@ "name": "heart-outline", "prevSize": 32, "code": 59806, - "tempChar": "" + "tempChar": "" }, { "order": 713, @@ -291,7 +299,7 @@ "name": "heart", "prevSize": 32, "code": 59807, - "tempChar": "" + "tempChar": "" }, { "order": 712, @@ -299,7 +307,7 @@ "name": "word-wrap", "prevSize": 32, "code": 59805, - "tempChar": "" + "tempChar": "" }, { "order": 708, @@ -307,7 +315,7 @@ "name": "webapp", "prevSize": 32, "code": 59795, - "tempChar": "" + "tempChar": "" }, { "order": 707, @@ -315,7 +323,7 @@ "name": "reload", "prevSize": 32, "code": 59796, - "tempChar": "" + "tempChar": "" }, { "order": 706, @@ -323,7 +331,7 @@ "name": "install", "prevSize": 32, "code": 59801, - "tempChar": "" + "tempChar": "" }, { "order": 705, @@ -331,7 +339,7 @@ "name": "favorite-filled", "prevSize": 32, "code": 59800, - "tempChar": "" + "tempChar": "" }, { "order": 702, @@ -339,7 +347,7 @@ "name": "share-screen", "prevSize": 32, "code": 59770, - "tempChar": "" + "tempChar": "" }, { "order": 701, @@ -347,7 +355,7 @@ "name": "video-outlined", "prevSize": 32, "code": 59799, - "tempChar": "" + "tempChar": "" }, { "order": 700, @@ -355,7 +363,7 @@ "name": "stats", "prevSize": 32, "code": 59798, - "tempChar": "" + "tempChar": "" }, { "order": 699, @@ -363,7 +371,7 @@ "name": "copy-media", "prevSize": 32, "code": 59797, - "tempChar": "" + "tempChar": "" }, { "order": 704, @@ -371,7 +379,7 @@ "name": "sidebar", "prevSize": 32, "code": 59794, - "tempChar": "" + "tempChar": "" }, { "order": 690, @@ -379,7 +387,7 @@ "name": "video-stop", "prevSize": 32, "code": 59787, - "tempChar": "" + "tempChar": "" }, { "order": 678, @@ -387,7 +395,7 @@ "name": "speaker", "prevSize": 32, "code": 59777, - "tempChar": "" + "tempChar": "" }, { "order": 679, @@ -395,7 +403,7 @@ "name": "speaker-outline", "prevSize": 32, "code": 59778, - "tempChar": "" + "tempChar": "" }, { "order": 680, @@ -403,7 +411,7 @@ "name": "phone-discard-outline", "prevSize": 32, "code": 59779, - "tempChar": "" + "tempChar": "" }, { "order": 681, @@ -411,7 +419,7 @@ "name": "allow-speak", "prevSize": 32, "code": 59780, - "tempChar": "" + "tempChar": "" }, { "order": 682, @@ -419,7 +427,7 @@ "name": "stop-raising-hand", "prevSize": 32, "code": 59781, - "tempChar": "" + "tempChar": "" }, { "order": 683, @@ -427,7 +435,7 @@ "name": "share-screen-outlined", "prevSize": 32, "code": 59782, - "tempChar": "" + "tempChar": "" }, { "order": 684, @@ -435,7 +443,7 @@ "name": "voice-chat", "prevSize": 32, "code": 59783, - "tempChar": "" + "tempChar": "" }, { "order": 689, @@ -443,7 +451,7 @@ "name": "video", "prevSize": 32, "code": 59784, - "tempChar": "" + "tempChar": "" }, { "order": 686, @@ -451,7 +459,7 @@ "name": "noise-suppression", "prevSize": 32, "code": 59785, - "tempChar": "" + "tempChar": "" }, { "order": 703, @@ -459,7 +467,7 @@ "name": "phone-discard", "prevSize": 32, "code": 59786, - "tempChar": "" + "tempChar": "" }, { "order": 667, @@ -467,7 +475,7 @@ "name": "bot-commands-filled", "prevSize": 32, "code": 59775, - "tempChar": "" + "tempChar": "" }, { "order": 664, @@ -475,7 +483,7 @@ "name": "reply-filled", "prevSize": 32, "code": 59776, - "tempChar": "" + "tempChar": "" }, { "order": 656, @@ -483,7 +491,7 @@ "name": "bug", "prevSize": 32, "code": 59774, - "tempChar": "" + "tempChar": "" }, { "order": 619, @@ -491,7 +499,7 @@ "name": "data", "prevSize": 32, "code": 59773, - "tempChar": "" + "tempChar": "" }, { "order": 622, @@ -499,7 +507,7 @@ "name": "darkmode", "prevSize": 32, "code": 59769, - "tempChar": "" + "tempChar": "" }, { "order": 711, @@ -507,7 +515,7 @@ "name": "animations", "prevSize": 32, "code": 59804, - "tempChar": "" + "tempChar": "" }, { "order": 626, @@ -515,7 +523,7 @@ "name": "enter", "prevSize": 32, "code": 59771, - "tempChar": "" + "tempChar": "" }, { "order": 627, @@ -523,7 +531,7 @@ "name": "fontsize", "prevSize": 32, "code": 59772, - "tempChar": "" + "tempChar": "" }, { "order": 630, @@ -531,7 +539,7 @@ "name": "permissions", "prevSize": 32, "code": 59766, - "tempChar": "" + "tempChar": "" }, { "order": 631, @@ -539,7 +547,7 @@ "name": "card", "prevSize": 32, "code": 59767, - "tempChar": "" + "tempChar": "" }, { "order": 634, @@ -547,7 +555,7 @@ "name": "truck", "prevSize": 32, "code": 59768, - "tempChar": "" + "tempChar": "" }, { "order": 663, @@ -555,7 +563,7 @@ "name": "share-filled", "prevSize": 32, "code": 59738, - "tempChar": "" + "tempChar": "" }, { "order": 638, @@ -563,7 +571,7 @@ "name": "bold", "prevSize": 32, "code": 59745, - "tempChar": "" + "tempChar": "" }, { "order": 639, @@ -571,7 +579,7 @@ "name": "bot-command", "prevSize": 32, "code": 59746, - "tempChar": "" + "tempChar": "" }, { "order": 642, @@ -579,7 +587,7 @@ "name": "calendar-filter", "prevSize": 32, "code": 59747, - "tempChar": "" + "tempChar": "" }, { "order": 643, @@ -587,7 +595,7 @@ "name": "comments", "prevSize": 32, "code": 59748, - "tempChar": "" + "tempChar": "" }, { "order": 645, @@ -595,7 +603,7 @@ "name": "comments-sticker", "prevSize": 32, "code": 59749, - "tempChar": "" + "tempChar": "" }, { "order": 646, @@ -603,7 +611,7 @@ "name": "arrow-down", "prevSize": 32, "code": 59750, - "tempChar": "" + "tempChar": "" }, { "order": 668, @@ -611,7 +619,7 @@ "name": "email", "prevSize": 32, "code": 59751, - "tempChar": "" + "tempChar": "" }, { "order": 648, @@ -619,7 +627,7 @@ "name": "italic", "prevSize": 32, "code": 59752, - "tempChar": "" + "tempChar": "" }, { "order": 620, @@ -627,7 +635,7 @@ "name": "link", "prevSize": 32, "code": 59753, - "tempChar": "" + "tempChar": "" }, { "order": 621, @@ -635,7 +643,7 @@ "name": "mention", "prevSize": 32, "code": 59754, - "tempChar": "" + "tempChar": "" }, { "order": 624, @@ -643,7 +651,7 @@ "name": "monospace", "prevSize": 32, "code": 59755, - "tempChar": "" + "tempChar": "" }, { "order": 625, @@ -651,7 +659,7 @@ "name": "next", "prevSize": 32, "code": 59756, - "tempChar": "" + "tempChar": "" }, { "order": 628, @@ -659,7 +667,7 @@ "name": "password-off", "prevSize": 32, "code": 59757, - "tempChar": "" + "tempChar": "" }, { "order": 629, @@ -667,7 +675,7 @@ "name": "pin-list", "prevSize": 32, "code": 59758, - "tempChar": "" + "tempChar": "" }, { "order": 632, @@ -675,7 +683,7 @@ "name": "previous", "prevSize": 32, "code": 59759, - "tempChar": "" + "tempChar": "" }, { "order": 633, @@ -683,7 +691,7 @@ "name": "replace", "prevSize": 32, "code": 59760, - "tempChar": "" + "tempChar": "" }, { "order": 636, @@ -691,7 +699,7 @@ "name": "schedule", "prevSize": 32, "code": 59761, - "tempChar": "" + "tempChar": "" }, { "order": 691, @@ -699,7 +707,7 @@ "name": "strikethrough", "prevSize": 32, "code": 59762, - "tempChar": "" + "tempChar": "" }, { "order": 692, @@ -707,7 +715,7 @@ "name": "underlined", "prevSize": 32, "code": 59763, - "tempChar": "" + "tempChar": "" }, { "order": 641, @@ -715,7 +723,7 @@ "name": "zoom-in", "prevSize": 32, "code": 59764, - "tempChar": "" + "tempChar": "" }, { "order": 649, @@ -723,27 +731,41 @@ "name": "zoom-out", "prevSize": 32, "code": 59765, - "tempChar": "" + "tempChar": "" } ], "id": 2, "metadata": { "name": "Untitled Set", "importSize": { - "width": 489, - "height": 489 + "width": 768, + "height": 768 } }, "height": 1024, "prevSize": 32, "icons": [ { - "id": 76, + "id": 77, "paths": [ - "M543.839 668.139c-15.919-14.001-41.812-11.891-55.815 4.028l-147.687 166.483v-665.357c0-21.865-18.029-39.894-39.894-39.894s-39.894 18.029-39.894 39.894v665.357l-147.687-166.483c-15.919-15.919-39.894-18.029-55.815-4.028-15.919 15.919-18.029 39.894-4.028 55.815l217.503 243.396c16.303 20.138 45.074 17.070 57.925 0l219.421-243.396c14.001-15.728 12.084-41.812-4.028-55.815z", - "M970.979 301.033l-215.584-243.396c-19.18-19.371-43.155-20.523-59.841 0l-219.421 243.396c-14.001 15.919-11.891 41.812 4.028 55.815 24.551 19.565 49.293 6.138 55.815-4.028l147.687-166.483v663.439c0 21.865 18.029 39.894 39.894 39.894s39.894-15.919 39.894-37.976v-665.166l147.687 166.483c15.919 15.919 39.894 18.029 55.815 4.028 15.919-16.112 18.029-40.087 4.028-56.006z" + "M226.933 596.267c0.4 0 0.667 0 1.067 0 7.2 0 14.4 0 21.467 0 3.6 0 7.333 0 10.933 0 23.6 0 42.667-19.067 42.667-42.667s-19.067-42.667-42.667-42.667c-3.6 0-7.2 0-10.8 0-7.333 0-14.8 0-22.133 0-0.267 0-0.4 0-0.667 0-28.267 0-48.8-10.8-61.333-32.267-14.4-24.533-14.533-58.933-0.4-83.6 12.4-21.733 33.067-32.8 61.467-32.933h287.733c23.6 0 42.667-19.067 42.667-42.667s-19.067-42.667-42.667-42.667h-287.733c-0.133 0-0.133 0-0.267 0-47.6 0.267-89.467 18.667-118.133 51.867-25.2 29.2-38.933 67.733-38.667 108.8s14.4 79.467 40 108.4c28.667 32.533 70.4 50.4 117.467 50.4z", + "M514.4 951.6c23.6 0 42.667-19.067 42.667-42.667v-112.667c0-23.6-19.067-42.667-42.667-42.667s-42.667 19.067-42.667 42.667v112.667c0 23.467 19.067 42.667 42.667 42.667z", + "M247.733 951.6c0 0 0.133 0 0 0h266.533c23.6 0 42.667-19.067 42.667-42.667s-19.067-42.667-42.667-42.667h-266.4c0 0 0 0 0 0-24.667 0-30.933-9.067-34-29.2v-293.067c0-23.6-19.067-42.667-42.667-42.667s-42.667 19.067-42.667 42.667v296c0 1.867 0.133 3.867 0.4 5.733 8.933 67.2 52.267 105.867 118.8 105.867z", + "M486.8 362.133c4 0 8-0.533 12.133-1.733 10.133-2.933 43.067-16.8 40.667-69.733-1.6-36.4-19.067-82.8-45.6-121.2-24.133-34.8-66.4-79.067-131.2-92.133-74.933-15.2-111.867 7.6-129.733 29.333-23.067 28.133-25.867 69.2-7.6 112.533 27.2 64.667 101.6 134.4 191.067 142.267 14.667 1.333 28.133-4.933 36.8-15.6 8 10.133 20.4 16.267 33.467 16.267zM454.267 291.867c-0.133 0.133-0.267 0.4-0.533 0.533-7.067-8.933-17.6-14.933-29.733-16-49.867-4.4-93.733-43.333-113.467-77.6-12.667-22-12.133-35.2-11.2-38 3.067-2 16.8-5.733 46.667 0.267 42.267 8.533 70.133 44.133 82.933 64.667 17.867 28.667 24.133 54.133 25.333 66.133zM474.8 278.533c0 0 0 0 0 0s0 0 0 0z", + "M799.067 596.267c-0.4 0-0.667 0-1.067 0-7.2 0-14.4 0-21.467 0-3.6 0-7.333 0-10.933 0-23.6 0-42.667-19.067-42.667-42.667s19.067-42.667 42.667-42.667c3.6 0 7.2 0 10.8 0 7.333 0 14.8 0 22.133 0 0.267 0 0.4 0 0.667 0 28.267 0 48.8-10.8 61.333-32.267 14.4-24.533 14.533-58.933 0.4-83.6-12.4-21.733-33.067-32.8-61.467-32.933h-287.733c-23.6 0-42.667-19.067-42.667-42.667s19.067-42.667 42.667-42.667h287.867c0.133 0 0.133 0 0.267 0 47.6 0.267 89.467 18.667 118.133 51.867 25.2 29.2 38.933 67.733 38.667 108.8s-14.4 79.467-40 108.4c-28.667 32.533-70.533 50.4-117.6 50.4z", + "M511.733 951.6c-23.6 0-42.667-19.067-42.667-42.667v-112.667c0-23.6 19.067-42.667 42.667-42.667s42.667 19.067 42.667 42.667v112.667c0 23.467-19.2 42.667-42.667 42.667z", + "M778.267 951.6c0 0 0 0 0 0h-266.533c-23.6 0-42.667-19.067-42.667-42.667s19.067-42.667 42.667-42.667h266.533c0 0 0 0 0 0 24.667 0 30.933-9.067 34-29.2v-293.067c0-23.6 19.067-42.667 42.667-42.667s42.667 19.067 42.667 42.667v296c0 1.867-0.133 3.867-0.4 5.733-9.067 67.2-52.4 105.867-118.933 105.867z", + "M539.2 362.133c-4 0-8-0.533-12.133-1.733-10.133-2.933-43.067-16.8-40.667-69.733 1.6-36.4 19.067-82.8 45.6-121.2 24.133-34.8 66.4-79.067 131.2-92.133 74.933-15.2 111.867 7.6 129.733 29.333 23.067 28.133 25.867 69.2 7.6 112.533-27.2 64.667-101.6 134.4-191.067 142.267-14.667 1.333-28.133-4.933-36.8-15.6-8 10.133-20.4 16.267-33.467 16.267zM571.867 291.867c0.133 0.133 0.267 0.4 0.533 0.533 7.067-8.933 17.6-14.933 29.733-16 49.867-4.4 93.733-43.333 113.467-77.6 12.667-22 12.133-35.2 11.2-38-3.067-2-16.8-5.733-46.667 0.267-42.267 8.533-70.133 44.133-82.933 64.667-18 28.667-24.267 54.133-25.333 66.133zM551.2 278.533c0.133 0 0.133 0 0 0 0.133 0 0.133 0 0 0z", + "M514.267 697.2l-85.733 52.533c-8.933 5.467-20.533 2.667-26-6.267-2.667-4.4-3.467-9.6-2.267-14.533l13.333-52.267c4.8-18.8 17.733-34.667 35.2-43.067l93.467-44.933c4.4-2.133 6.133-7.333 4.133-11.733-1.733-3.467-5.467-5.467-9.333-4.8l-104.133 18c-21.2 3.6-42.8-2.133-59.333-16l-32.933-27.467c-8-6.667-9.067-18.667-2.4-26.667 3.2-3.867 8-6.267 13.067-6.667l100.4-7.867c7.067-0.533 13.333-5.067 16-11.6l38.8-93.6c4-9.6 15.067-14.267 24.667-10.267 4.667 1.867 8.267 5.6 10.267 10.267l38.8 93.6c2.667 6.533 8.933 11.067 16 11.6l101.067 7.867c10.4 0.8 18.267 9.867 17.333 20.4-0.4 5.067-2.8 9.6-6.533 12.933l-77.067 65.733c-5.467 4.667-7.733 11.867-6.133 18.8l23.733 98.4c2.4 10.133-3.867 20.4-14 22.8-4.933 1.2-10 0.4-14.267-2.267l-86.4-52.933c-6.133-3.733-13.733-3.733-19.733 0v0z" ], "attrs": [ + {}, + {}, + {}, + {}, + {}, + {}, + {}, {}, {} ], @@ -751,9 +773,24 @@ "isMulticolor2": false, "grid": 24, "tags": [ - "sort" + "gift" ] }, + { + "id": 76, + "paths": [ + "M128 341.867h768c25.6 0 42.667-17.067 42.667-42.667s-17.067-42.667-42.667-42.667h-768c-25.6 0-42.667 17.067-42.667 42.667s17.067 42.667 42.667 42.667zM130.667 554.667h768c25.6 0 42.667-17.067 42.667-42.667s-17.067-42.667-42.667-42.667h-768c-25.6 0-42.667 17.067-42.667 42.667s17.067 42.667 42.667 42.667zM130.667 768h768c25.6 0 42.667-17.067 42.667-42.667s-17.067-42.667-42.667-42.667h-768c-25.6 0-42.667 17.067-42.667 42.667s17.067 42.667 42.667 42.667z" + ], + "attrs": [ + {} + ], + "grid": 24, + "tags": [ + "sort" + ], + "isMulticolor": false, + "isMulticolor2": false + }, { "id": 75, "paths": [ @@ -3416,7 +3453,7 @@ "name": "select", "prevSize": 32, "code": 59744, - "tempChar": "" + "tempChar": "" }, { "order": 480, @@ -3424,7 +3461,7 @@ "name": "folder", "prevSize": 32, "code": 59667, - "tempChar": "" + "tempChar": "" }, { "order": 481, @@ -3432,7 +3469,7 @@ "name": "bots", "prevSize": 32, "code": 59669, - "tempChar": "" + "tempChar": "" }, { "order": 482, @@ -3440,7 +3477,7 @@ "name": "calendar", "prevSize": 32, "code": 59670, - "tempChar": "" + "tempChar": "" }, { "order": 483, @@ -3448,7 +3485,7 @@ "name": "cloud-download", "prevSize": 32, "code": 59671, - "tempChar": "" + "tempChar": "" }, { "order": 484, @@ -3456,7 +3493,7 @@ "name": "colorize", "prevSize": 32, "code": 59672, - "tempChar": "" + "tempChar": "" }, { "order": 651, @@ -3464,7 +3501,7 @@ "name": "forward", "prevSize": 32, "code": 59687, - "tempChar": "" + "tempChar": "" }, { "order": 650, @@ -3472,7 +3509,7 @@ "name": "reply", "prevSize": 32, "code": 59719, - "tempChar": "" + "tempChar": "" }, { "order": 487, @@ -3480,7 +3517,7 @@ "name": "help", "prevSize": 32, "code": 59690, - "tempChar": "" + "tempChar": "" }, { "order": 488, @@ -3488,7 +3525,7 @@ "name": "info", "prevSize": 32, "code": 59691, - "tempChar": "" + "tempChar": "" }, { "order": 489, @@ -3496,7 +3533,7 @@ "name": "info-filled", "prevSize": 32, "code": 59675, - "tempChar": "" + "tempChar": "" }, { "order": 490, @@ -3504,7 +3541,7 @@ "name": "delete-filled", "prevSize": 32, "code": 59676, - "tempChar": "" + "tempChar": "" }, { "order": 491, @@ -3512,7 +3549,7 @@ "name": "delete", "prevSize": 32, "code": 59677, - "tempChar": "" + "tempChar": "" }, { "order": 492, @@ -3520,7 +3557,7 @@ "name": "edit", "prevSize": 32, "code": 59683, - "tempChar": "" + "tempChar": "" }, { "order": 493, @@ -3528,7 +3565,7 @@ "name": "new-chat-filled", "prevSize": 32, "code": 59705, - "tempChar": "" + "tempChar": "" }, { "order": 494, @@ -3536,7 +3573,7 @@ "name": "send", "prevSize": 32, "code": 59722, - "tempChar": "" + "tempChar": "" }, { "order": 495, @@ -3544,7 +3581,7 @@ "name": "send-outline", "prevSize": 32, "code": 59723, - "tempChar": "" + "tempChar": "" }, { "order": 496, @@ -3552,7 +3589,7 @@ "name": "add-user-filled", "prevSize": 32, "code": 59652, - "tempChar": "" + "tempChar": "" }, { "order": 497, @@ -3560,7 +3597,7 @@ "name": "add-user", "prevSize": 32, "code": 59653, - "tempChar": "" + "tempChar": "" }, { "order": 498, @@ -3568,7 +3605,7 @@ "name": "delete-user", "prevSize": 32, "code": 59678, - "tempChar": "" + "tempChar": "" }, { "order": 499, @@ -3576,7 +3613,7 @@ "name": "microphone", "prevSize": 32, "code": 59701, - "tempChar": "" + "tempChar": "" }, { "order": 500, @@ -3584,7 +3621,7 @@ "name": "microphone-alt", "prevSize": 32, "code": 59707, - "tempChar": "" + "tempChar": "" }, { "order": 501, @@ -3592,7 +3629,7 @@ "name": "poll", "prevSize": 32, "code": 59704, - "tempChar": "" + "tempChar": "" }, { "order": 502, @@ -3600,7 +3637,7 @@ "name": "revote", "prevSize": 32, "code": 59706, - "tempChar": "" + "tempChar": "" }, { "order": 503, @@ -3608,7 +3645,7 @@ "name": "photo", "prevSize": 32, "code": 59712, - "tempChar": "" + "tempChar": "" }, { "order": 504, @@ -3616,7 +3653,7 @@ "name": "document", "prevSize": 32, "code": 59679, - "tempChar": "" + "tempChar": "" }, { "order": 505, @@ -3624,7 +3661,7 @@ "name": "camera", "prevSize": 32, "code": 59662, - "tempChar": "" + "tempChar": "" }, { "order": 506, @@ -3632,7 +3669,7 @@ "name": "camera-add", "prevSize": 32, "code": 59663, - "tempChar": "" + "tempChar": "" }, { "order": 507, @@ -3640,7 +3677,7 @@ "name": "logout", "prevSize": 32, "code": 59698, - "tempChar": "" + "tempChar": "" }, { "order": 508, @@ -3648,7 +3685,7 @@ "name": "saved-messages", "prevSize": 32, "code": 59720, - "tempChar": "" + "tempChar": "" }, { "order": 509, @@ -3656,7 +3693,7 @@ "name": "settings", "prevSize": 32, "code": 59726, - "tempChar": "" + "tempChar": "" }, { "order": 652, @@ -3664,7 +3701,7 @@ "name": "phone", "prevSize": 32, "code": 59711, - "tempChar": "" + "tempChar": "" }, { "order": 653, @@ -3672,7 +3709,7 @@ "name": "attach", "prevSize": 32, "code": 59657, - "tempChar": "" + "tempChar": "" }, { "order": 512, @@ -3680,7 +3717,7 @@ "name": "copy", "prevSize": 32, "code": 59674, - "tempChar": "" + "tempChar": "" }, { "order": 513, @@ -3688,7 +3725,7 @@ "name": "channel", "prevSize": 32, "code": 59665, - "tempChar": "" + "tempChar": "" }, { "order": 514, @@ -3696,7 +3733,7 @@ "name": "group", "prevSize": 32, "code": 59689, - "tempChar": "" + "tempChar": "" }, { "order": 515, @@ -3704,7 +3741,7 @@ "name": "user", "prevSize": 32, "code": 59737, - "tempChar": "" + "tempChar": "" }, { "order": 516, @@ -3712,7 +3749,7 @@ "name": "non-contacts", "prevSize": 32, "code": 59688, - "tempChar": "" + "tempChar": "" }, { "order": 517, @@ -3720,7 +3757,7 @@ "name": "active-sessions", "prevSize": 32, "code": 59650, - "tempChar": "" + "tempChar": "" }, { "order": 518, @@ -3728,7 +3765,7 @@ "name": "admin", "prevSize": 32, "code": 59654, - "tempChar": "" + "tempChar": "" }, { "order": 519, @@ -3736,7 +3773,7 @@ "name": "download", "prevSize": 32, "code": 59681, - "tempChar": "" + "tempChar": "" }, { "order": 520, @@ -3744,7 +3781,7 @@ "name": "location", "prevSize": 32, "code": 59696, - "tempChar": "" + "tempChar": "" }, { "order": 521, @@ -3752,7 +3789,7 @@ "name": "stop", "prevSize": 32, "code": 59730, - "tempChar": "" + "tempChar": "" }, { "order": 523, @@ -3760,7 +3797,7 @@ "name": "archive", "prevSize": 32, "code": 59656, - "tempChar": "" + "tempChar": "" }, { "order": 524, @@ -3768,7 +3805,7 @@ "name": "unarchive", "prevSize": 32, "code": 59731, - "tempChar": "" + "tempChar": "" }, { "order": 525, @@ -3776,7 +3813,7 @@ "name": "readchats", "prevSize": 32, "code": 59699, - "tempChar": "" + "tempChar": "" }, { "order": 526, @@ -3784,7 +3821,7 @@ "name": "unread", "prevSize": 32, "code": 59735, - "tempChar": "" + "tempChar": "" }, { "order": 654, @@ -3792,7 +3829,7 @@ "name": "message", "prevSize": 32, "code": 59700, - "tempChar": "" + "tempChar": "" }, { "order": 659, @@ -3800,7 +3837,7 @@ "name": "lock", "prevSize": 32, "code": 59697, - "tempChar": "" + "tempChar": "" }, { "order": 529, @@ -3808,7 +3845,7 @@ "name": "unlock", "prevSize": 32, "code": 59732, - "tempChar": "" + "tempChar": "" }, { "order": 530, @@ -3816,7 +3853,7 @@ "name": "mute", "prevSize": 32, "code": 59703, - "tempChar": "" + "tempChar": "" }, { "order": 531, @@ -3824,7 +3861,7 @@ "name": "unmute", "prevSize": 32, "code": 59733, - "tempChar": "" + "tempChar": "" }, { "order": 532, @@ -3832,7 +3869,7 @@ "name": "pin", "prevSize": 32, "code": 59713, - "tempChar": "" + "tempChar": "" }, { "order": 533, @@ -3840,7 +3877,7 @@ "name": "unpin", "prevSize": 32, "code": 59734, - "tempChar": "" + "tempChar": "" }, { "order": 534, @@ -3848,7 +3885,7 @@ "name": "smallscreen", "prevSize": 32, "code": 59742, - "tempChar": "" + "tempChar": "" }, { "order": 535, @@ -3856,7 +3893,7 @@ "name": "fullscreen", "prevSize": 32, "code": 59743, - "tempChar": "" + "tempChar": "" }, { "order": 536, @@ -3864,7 +3901,7 @@ "name": "large-pause", "prevSize": 32, "code": 59694, - "tempChar": "" + "tempChar": "" }, { "order": 537, @@ -3872,7 +3909,7 @@ "name": "large-play", "prevSize": 32, "code": 59695, - "tempChar": "" + "tempChar": "" }, { "order": 538, @@ -3880,7 +3917,7 @@ "name": "pause", "prevSize": 32, "code": 59709, - "tempChar": "" + "tempChar": "" }, { "order": 539, @@ -3888,7 +3925,7 @@ "name": "play", "prevSize": 32, "code": 59715, - "tempChar": "" + "tempChar": "" }, { "order": 540, @@ -3896,7 +3933,7 @@ "name": "channelviews", "prevSize": 32, "code": 59666, - "tempChar": "" + "tempChar": "" }, { "order": 541, @@ -3904,7 +3941,7 @@ "name": "message-succeeded", "prevSize": 32, "code": 59648, - "tempChar": "" + "tempChar": "" }, { "order": 657, @@ -3912,7 +3949,7 @@ "name": "message-read", "prevSize": 32, "code": 59649, - "tempChar": "" + "tempChar": "" }, { "order": 543, @@ -3920,7 +3957,7 @@ "name": "message-pending", "prevSize": 32, "code": 59724, - "tempChar": "" + "tempChar": "" }, { "order": 544, @@ -3928,7 +3965,7 @@ "name": "message-failed", "prevSize": 32, "code": 59725, - "tempChar": "" + "tempChar": "" }, { "order": 545, @@ -3936,7 +3973,7 @@ "name": "favorite", "prevSize": 32, "code": 59710, - "tempChar": "" + "tempChar": "" }, { "order": 546, @@ -3944,7 +3981,7 @@ "name": "keyboard", "prevSize": 32, "code": 59716, - "tempChar": "" + "tempChar": "" }, { "order": 547, @@ -3952,7 +3989,7 @@ "name": "delete-left", "prevSize": 32, "code": 59717, - "tempChar": "" + "tempChar": "" }, { "order": 548, @@ -3960,7 +3997,7 @@ "name": "recent", "prevSize": 32, "code": 59718, - "tempChar": "" + "tempChar": "" }, { "order": 549, @@ -3968,7 +4005,7 @@ "name": "gifs", "prevSize": 32, "code": 59727, - "tempChar": "" + "tempChar": "" }, { "order": 550, @@ -3976,7 +4013,7 @@ "name": "stickers", "prevSize": 32, "code": 59739, - "tempChar": "" + "tempChar": "" }, { "order": 551, @@ -3984,7 +4021,7 @@ "name": "smile", "prevSize": 32, "code": 59728, - "tempChar": "" + "tempChar": "" }, { "order": 552, @@ -3992,7 +4029,7 @@ "name": "animals", "prevSize": 32, "code": 59655, - "tempChar": "" + "tempChar": "" }, { "order": 553, @@ -4000,7 +4037,7 @@ "name": "eats", "prevSize": 32, "code": 59682, - "tempChar": "" + "tempChar": "" }, { "order": 554, @@ -4008,7 +4045,7 @@ "name": "sport", "prevSize": 32, "code": 59729, - "tempChar": "" + "tempChar": "" }, { "order": 555, @@ -4016,7 +4053,7 @@ "name": "car", "prevSize": 32, "code": 59664, - "tempChar": "" + "tempChar": "" }, { "order": 556, @@ -4024,7 +4061,7 @@ "name": "lamp", "prevSize": 32, "code": 59692, - "tempChar": "" + "tempChar": "" }, { "order": 557, @@ -4032,7 +4069,7 @@ "name": "language", "prevSize": 32, "code": 59693, - "tempChar": "" + "tempChar": "" }, { "order": 558, @@ -4040,7 +4077,7 @@ "name": "flag", "prevSize": 32, "code": 59686, - "tempChar": "" + "tempChar": "" }, { "order": 559, @@ -4048,7 +4085,7 @@ "name": "more", "prevSize": 32, "code": 59702, - "tempChar": "" + "tempChar": "" }, { "order": 560, @@ -4056,7 +4093,7 @@ "name": "search", "prevSize": 32, "code": 59721, - "tempChar": "" + "tempChar": "" }, { "order": 561, @@ -4064,7 +4101,7 @@ "name": "remove", "prevSize": 32, "code": 59740, - "tempChar": "" + "tempChar": "" }, { "order": 562, @@ -4072,7 +4109,7 @@ "name": "add", "prevSize": 32, "code": 59651, - "tempChar": "" + "tempChar": "" }, { "order": 563, @@ -4080,7 +4117,7 @@ "name": "check", "prevSize": 32, "code": 59668, - "tempChar": "" + "tempChar": "" }, { "order": 564, @@ -4088,7 +4125,7 @@ "name": "close", "prevSize": 32, "code": 59673, - "tempChar": "" + "tempChar": "" }, { "order": 610, @@ -4096,7 +4133,7 @@ "name": "arrow-left", "prevSize": 32, "code": 59661, - "tempChar": "" + "tempChar": "" }, { "order": 566, @@ -4104,7 +4141,7 @@ "name": "arrow-right", "prevSize": 32, "code": 59708, - "tempChar": "" + "tempChar": "" }, { "order": 730, @@ -4112,7 +4149,7 @@ "name": "down", "prevSize": 32, "code": 59680, - "tempChar": "" + "tempChar": "" }, { "order": 568, @@ -4120,7 +4157,7 @@ "name": "up", "prevSize": 32, "code": 59736, - "tempChar": "" + "tempChar": "" }, { "order": 569, @@ -4128,7 +4165,7 @@ "name": "eye-closed", "prevSize": 32, "code": 59685, - "tempChar": "" + "tempChar": "" }, { "order": 570, @@ -4136,7 +4173,7 @@ "name": "eye", "prevSize": 32, "code": 59684, - "tempChar": "" + "tempChar": "" }, { "order": 571, @@ -4144,7 +4181,7 @@ "name": "muted", "prevSize": 32, "code": 59741, - "tempChar": "" + "tempChar": "" }, { "order": 572, @@ -4152,7 +4189,7 @@ "name": "avatar-archived-chats", "prevSize": 32, "code": 59658, - "tempChar": "" + "tempChar": "" }, { "order": 573, @@ -4160,7 +4197,7 @@ "name": "avatar-deleted-account", "prevSize": 32, "code": 59659, - "tempChar": "" + "tempChar": "" }, { "order": 574, @@ -4168,7 +4205,7 @@ "name": "avatar-saved-messages", "prevSize": 32, "code": 59660, - "tempChar": "" + "tempChar": "" }, { "order": 575, @@ -4176,7 +4213,7 @@ "name": "pinned-chat", "prevSize": 32, "code": 59714, - "tempChar": "" + "tempChar": "" } ], "prevSize": 32, diff --git a/src/styles/icons.scss b/src/styles/icons.scss index befb49ead..ff5bd44ef 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -51,6 +51,9 @@ .icon-volume-3:before { content: "\e991"; } +.icon-gift:before { + content: "\e9ad"; +} .icon-sort:before { content: "\e9ac"; } diff --git a/src/util/langProvider.ts b/src/util/langProvider.ts index 488dce6dd..c1b9da425 100644 --- a/src/util/langProvider.ts +++ b/src/util/langProvider.ts @@ -204,14 +204,14 @@ function processTemplate(template: string, value: any) { const initialValue = translationSlices.shift(); return translationSlices.reduce((result, str, index) => { - return `${result}${String(value[index] || '')}${str}`; + return `${result}${String(value[index] ?? '')}${str}`; }, initialValue || ''); } function processTranslation(langString: ApiLangString | undefined, key: string, value?: any, format?: 'i') { - const preferedPluralOption = typeof value === 'number' ? getPluralOption(value) : 'value'; + const preferredPluralOption = typeof value === 'number' ? getPluralOption(value) : 'value'; const template = langString ? ( - langString[preferedPluralOption] || langString.otherValue || langString.value + langString[preferredPluralOption] || langString.otherValue || langString.value ) : undefined; if (!template || !template.trim()) { const parts = key.split('.');