Stars Gifting: Implement Stars Gifting for users (#4847)
This commit is contained in:
parent
7d1eaa55da
commit
6f292c9032
@ -352,6 +352,7 @@ function buildAction(
|
||||
let phoneCall: PhoneCallAction | undefined;
|
||||
let call: Partial<ApiGroupCall> | undefined;
|
||||
let amount: number | undefined;
|
||||
let stars: number | undefined;
|
||||
let currency: string | undefined;
|
||||
let giftCryptoInfo: {
|
||||
currency: string;
|
||||
@ -370,11 +371,11 @@ function buildAction(
|
||||
let isUnclaimed: boolean | undefined;
|
||||
let pluralValue: number | undefined;
|
||||
|
||||
const targetUserIds = 'users' in action
|
||||
let targetUserIds = 'users' in action
|
||||
? action.users && action.users.map((id) => buildApiPeerId(id, 'user'))
|
||||
: ('userId' in action && [buildApiPeerId(action.userId, 'user')]) || [];
|
||||
let targetChatId: string | undefined;
|
||||
|
||||
let targetChatId;
|
||||
if (action instanceof GramJs.MessageActionChatCreate) {
|
||||
text = 'Notification.CreatedChatWithTitle';
|
||||
translationValues.push('%action_origin%', action.title);
|
||||
@ -611,6 +612,27 @@ function buildAction(
|
||||
text = 'ActionRefunded';
|
||||
amount = Number(action.totalAmount);
|
||||
currency = action.currency;
|
||||
} else if (action instanceof GramJs.MessageActionRequestedPeer) {
|
||||
text = 'ActionRequestedPeer';
|
||||
if (action.peers) {
|
||||
targetUserIds = action.peers?.map((peer) => getApiChatIdFromMtpPeer(peer));
|
||||
}
|
||||
if (targetPeerId) {
|
||||
translationValues.unshift('%action_origin%');
|
||||
}
|
||||
} else if (action instanceof GramJs.MessageActionGiftStars) {
|
||||
text = isOutgoing ? 'ActionGiftOutbound' : 'BoostingReceivedGiftNoName';
|
||||
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();
|
||||
stars = action.stars.toJSNumber();
|
||||
} else {
|
||||
text = 'ChatList.UnsupportedMessage';
|
||||
}
|
||||
@ -628,6 +650,7 @@ function buildAction(
|
||||
targetChatId,
|
||||
photo, // TODO Only used internally now, will be used for the UI in future
|
||||
amount,
|
||||
stars,
|
||||
currency,
|
||||
giftCryptoInfo,
|
||||
isGiveaway,
|
||||
|
||||
@ -8,7 +8,7 @@ import type {
|
||||
ApiGiveawayInfo,
|
||||
ApiInvoice, ApiLabeledPrice, ApiMyBoost, ApiPaymentCredentials,
|
||||
ApiPaymentForm, ApiPaymentSavedInfo, ApiPremiumGiftCodeOption, ApiPremiumPromo, ApiPremiumSubscriptionOption,
|
||||
ApiReceipt,
|
||||
ApiReceipt, ApiStarsGiftOption,
|
||||
ApiStarsTransaction,
|
||||
ApiStarsTransactionPeer,
|
||||
ApiStarTopupOption,
|
||||
@ -383,6 +383,19 @@ export function buildApiPremiumGiftCodeOption(option: GramJs.PremiumGiftCodeOpti
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarsGiftOptions(option: GramJs.StarsGiftOption): ApiStarsGiftOption {
|
||||
const {
|
||||
extended, stars, amount, currency,
|
||||
} = option;
|
||||
|
||||
return {
|
||||
isExtended: extended,
|
||||
stars: stars.toJSNumber(),
|
||||
amount: amount.toJSNumber(),
|
||||
currency,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiStarsTransactionPeer(peer: GramJs.TypeStarsTransactionPeer): ApiStarsTransactionPeer {
|
||||
if (peer instanceof GramJs.StarsTransactionPeerAppStore) {
|
||||
return { type: 'appStore' };
|
||||
|
||||
@ -546,6 +546,15 @@ GramJs.TypeInputStorePaymentPurpose {
|
||||
});
|
||||
}
|
||||
|
||||
if (purpose.type === 'starsgift') {
|
||||
return new GramJs.InputStorePaymentStarsGift({
|
||||
userId: buildInputEntity(purpose.user.id, purpose.user.accessHash) as GramJs.InputUser,
|
||||
stars: BigInt(purpose.stars),
|
||||
currency: purpose.currency,
|
||||
amount: BigInt(purpose.amount),
|
||||
});
|
||||
}
|
||||
|
||||
if (purpose.type === 'giftcode') {
|
||||
return new GramJs.InputStorePaymentPremiumGiftCode({
|
||||
users: purpose.users.map((user) => buildInputEntity(user.id, user.accessHash) as GramJs.InputUser),
|
||||
|
||||
@ -101,7 +101,7 @@ export {
|
||||
validateRequestedInfo, sendPaymentForm, getPaymentForm, getReceipt, fetchPremiumPromo, fetchTemporaryPaymentPassword,
|
||||
applyBoost, fetchBoostList, fetchBoostStatus, fetchGiveawayInfo, fetchMyBoosts, applyGiftCode, checkGiftCode,
|
||||
getPremiumGiftCodeOptions, launchPrepaidGiveaway, fetchStarsStatus, fetchStarsTopupOptions, fetchStarsTransactions,
|
||||
sendStarPaymentForm,
|
||||
sendStarPaymentForm, getStarsGiftOptions,
|
||||
} from './payments';
|
||||
|
||||
export * from './fragment';
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
buildApiPaymentForm,
|
||||
buildApiPremiumGiftCodeOption,
|
||||
buildApiPremiumPromo,
|
||||
buildApiReceipt,
|
||||
buildApiReceipt, buildApiStarsGiftOptions,
|
||||
buildApiStarsTransaction,
|
||||
buildApiStarTopupOption,
|
||||
buildShippingOptions,
|
||||
@ -403,6 +403,22 @@ export async function getPremiumGiftCodeOptions({
|
||||
return result.map(buildApiPremiumGiftCodeOption);
|
||||
}
|
||||
|
||||
export async function getStarsGiftOptions({
|
||||
chat,
|
||||
}: {
|
||||
chat?: ApiChat;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.payments.GetStarsGiftOptions({
|
||||
userId: chat && buildInputPeer(chat.id, chat.accessHash),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return result.map(buildApiStarsGiftOptions);
|
||||
}
|
||||
|
||||
export function launchPrepaidGiveaway({
|
||||
chat,
|
||||
giveawayId,
|
||||
|
||||
@ -240,8 +240,16 @@ export type ApiInputInvoiceStars = {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type ApiInputInvoiceStarsGift = {
|
||||
type: 'starsgift';
|
||||
userId: string;
|
||||
stars: number;
|
||||
currency: string;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
|
||||
| ApiInputInvoiceGiftCode | ApiInputInvoiceStars;
|
||||
| ApiInputInvoiceGiftCode | ApiInputInvoiceStarsGift | ApiInputInvoiceStars;
|
||||
|
||||
/* Used for Invoice request */
|
||||
export type ApiRequestInputInvoiceMessage = {
|
||||
@ -392,6 +400,8 @@ export interface ApiAction {
|
||||
| 'other';
|
||||
photo?: ApiPhoto;
|
||||
amount?: number;
|
||||
stars?: number;
|
||||
transactionId?: string;
|
||||
currency?: string;
|
||||
giftCryptoInfo?: {
|
||||
currency: string;
|
||||
|
||||
@ -142,8 +142,16 @@ export type ApiInputStorePaymentStarsTopup = {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type ApiInputStorePaymentStarsGift = {
|
||||
type: 'starsgift';
|
||||
user: ApiUser;
|
||||
stars: number;
|
||||
currency: string;
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type ApiInputStorePaymentPurpose = ApiInputStorePaymentGiveaway | ApiInputStorePaymentGiftcode |
|
||||
ApiInputStorePaymentStarsTopup;
|
||||
ApiInputStorePaymentStarsTopup | ApiInputStorePaymentStarsGift;
|
||||
|
||||
export interface ApiPremiumGiftCodeOption {
|
||||
users: number;
|
||||
@ -152,6 +160,13 @@ export interface ApiPremiumGiftCodeOption {
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export interface ApiStarsGiftOption {
|
||||
isExtended?: true;
|
||||
stars: number;
|
||||
currency: string;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export type ApiBoostsStatus = {
|
||||
level: number;
|
||||
currentLevelBoosts: number;
|
||||
|
||||
@ -1273,3 +1273,6 @@
|
||||
"ReplyInPrivateMessage" = "Reply In Private Message";
|
||||
"AriaSearchOlderResult" = "Focus next result";
|
||||
"AriaSearchNewerResult" = "Focus previous result";
|
||||
"CreditsBoxHistoryEntryGiftOutAbout" = "With Stars, {user} will be able to unlock content and services on Telegram. {link}"
|
||||
"CreditsBoxOutAbout" = "Review the {link} for Stars."
|
||||
"GiftStarsOutgoing" = "With Stars, {user} will be able to unlock content and services on Telegram."
|
||||
|
||||
@ -16,13 +16,16 @@ export { default as BotTrustModal } from '../components/main/BotTrustModal';
|
||||
export { default as AttachBotInstallModal } from '../components/modals/attachBotInstall/AttachBotInstallModal';
|
||||
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 PremiumGiftModal } from '../components/main/premium/PremiumGiftModal';
|
||||
export { default as StarsGiftModal } from '../components/main/premium/StarsGiftModal';
|
||||
export { default as GiveawayModal } from '../components/main/premium/GiveawayModal';
|
||||
export { default as PremiumGiftingModal } from '../components/main/premium/PremiumGiftingModal';
|
||||
export { default as PremiumGiftingPickerModal } from '../components/main/premium/PremiumGiftingPickerModal';
|
||||
export { default as StarsGiftingPickerModal } from '../components/main/premium/StarsGiftingPickerModal';
|
||||
export { default as PremiumLimitReachedModal } from '../components/main/premium/common/PremiumLimitReachedModal';
|
||||
export { default as StatusPickerMenu } from '../components/left/main/StatusPickerMenu';
|
||||
export { default as BoostModal } from '../components/modals/boost/BoostModal';
|
||||
export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal';
|
||||
export { default as StarGiftInfoModal } from '../components/modals/stars/StarGiftInfoModal';
|
||||
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
|
||||
export { default as StarsBalanceModal } from '../components/modals/stars/StarsBalanceModal';
|
||||
export { default as StarPaymentModal } from '../components/modals/stars/StarsPaymentModal';
|
||||
|
||||
0
src/components/common/Picker.tsx
Normal file
0
src/components/common/Picker.tsx
Normal file
@ -84,8 +84,41 @@ export function renderActionMessageText(
|
||||
.replace('un1', '%action_origin%')
|
||||
.replace('%1$s', '%gift_payment_amount%');
|
||||
}
|
||||
if (translationKey === 'ActionRequestedPeer') {
|
||||
unprocessed = unprocessed
|
||||
.replace('un1', '%star_target_user%')
|
||||
.replace('un2', '%action_origin%')
|
||||
.replace(/\*\*/g, '');
|
||||
}
|
||||
let processed: TextPart[];
|
||||
|
||||
if (unprocessed.includes('%star_target_user%')) {
|
||||
processed = processPlaceholder(
|
||||
unprocessed,
|
||||
'%star_target_user%',
|
||||
targetUsers
|
||||
? targetUsers.map((user) => renderUserContent(user, noLinks)).filter(Boolean)
|
||||
: 'User',
|
||||
);
|
||||
|
||||
unprocessed = processed.pop() as string;
|
||||
content.push(...processed);
|
||||
}
|
||||
|
||||
processed = processPlaceholder(
|
||||
unprocessed,
|
||||
'%action_origin%',
|
||||
actionOriginChat ? (
|
||||
renderChatContent(lang, actionOriginChat, noLinks) || NBSP
|
||||
) : actionOriginUser ? (
|
||||
renderUserContent(actionOriginUser, noLinks) || NBSP
|
||||
) : 'User',
|
||||
'',
|
||||
);
|
||||
|
||||
unprocessed = processed.pop() as string;
|
||||
content.push(...processed);
|
||||
|
||||
if (unprocessed.includes('%payment_amount%')) {
|
||||
processed = processPlaceholder(
|
||||
unprocessed,
|
||||
@ -96,20 +129,6 @@ export function renderActionMessageText(
|
||||
content.push(...processed);
|
||||
}
|
||||
|
||||
processed = processPlaceholder(
|
||||
unprocessed,
|
||||
'%action_origin%',
|
||||
actionOriginUser ? (
|
||||
renderUserContent(actionOriginUser, noLinks) || NBSP
|
||||
) : actionOriginChat ? (
|
||||
renderChatContent(lang, actionOriginChat, noLinks) || NBSP
|
||||
) : 'User',
|
||||
'',
|
||||
);
|
||||
|
||||
unprocessed = processed.pop() as string;
|
||||
content.push(...processed);
|
||||
|
||||
if (unprocessed.includes('%action_topic%')) {
|
||||
const topicEmoji = topic?.iconEmojiId
|
||||
? <CustomEmoji documentId={topic.iconEmojiId} />
|
||||
|
||||
@ -14,6 +14,7 @@ type OwnProps = {
|
||||
isConfirmDisabled?: boolean;
|
||||
shouldAdaptToSearch?: boolean;
|
||||
withFixedHeight?: boolean;
|
||||
withPremiumGradient?: boolean;
|
||||
onConfirm?: NoneToVoidFunction;
|
||||
} & ModalProps;
|
||||
|
||||
@ -23,6 +24,7 @@ const PickerModal = ({
|
||||
shouldAdaptToSearch,
|
||||
withFixedHeight,
|
||||
onConfirm,
|
||||
withPremiumGradient,
|
||||
...modalProps
|
||||
}: OwnProps) => {
|
||||
const lang = useOldLang();
|
||||
@ -43,6 +45,7 @@ const PickerModal = ({
|
||||
{modalProps.children}
|
||||
<div className={styles.buttonWrapper}>
|
||||
<Button
|
||||
withPremiumGradient={withPremiumGradient}
|
||||
onClick={onConfirm || modalProps.onClose}
|
||||
color="primary"
|
||||
size="smaller"
|
||||
|
||||
@ -53,7 +53,7 @@ const UserBirthday = ({
|
||||
animatedEmojiEffects,
|
||||
isInSettings,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { openGiftPremiumModal, requestConfetti } = getActions();
|
||||
const { openPremiumGiftModal, requestConfetti } = getActions();
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const animationPlayedRef = useRef(false);
|
||||
@ -144,15 +144,15 @@ const UserBirthday = ({
|
||||
|
||||
const canGiftPremium = isToday && !user.isPremium && !user.isSelf && !isPremiumPurchaseBlocked;
|
||||
|
||||
const handleOpenGiftModal = useLastCallback(() => {
|
||||
openGiftPremiumModal({ forUserIds: [user.id] });
|
||||
const handleOpenPremiumGiftModal = useLastCallback(() => {
|
||||
openPremiumGiftModal({ forUserIds: [user.id] });
|
||||
});
|
||||
|
||||
const handleClick = useLastCallback(() => {
|
||||
if (!isToday) return;
|
||||
|
||||
if (canGiftPremium && animationPlayedRef.current) {
|
||||
handleOpenGiftModal();
|
||||
handleOpenPremiumGiftModal();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ const UserBirthday = ({
|
||||
ripple={!isStatic}
|
||||
onClick={handleClick}
|
||||
isStatic={isStatic}
|
||||
onSecondaryIconClick={handleOpenGiftModal}
|
||||
onSecondaryIconClick={handleOpenPremiumGiftModal}
|
||||
>
|
||||
<div className="title">{renderText(lang(valueKey, [formattedDate, age], undefined, age))}</div>
|
||||
<span className="subtitle">{lang(isToday ? 'ProfileBirthdayToday' : 'ProfileBirthday')}</span>
|
||||
|
||||
@ -59,7 +59,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const [isSupportDialogOpen, openSupportDialog, closeSupportDialog] = useFlag(false);
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUserId) {
|
||||
@ -99,7 +99,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.General)}
|
||||
>
|
||||
{lang('Telegram.GeneralSettingsViewController')}
|
||||
{oldLang('Telegram.GeneralSettingsViewController')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="animations"
|
||||
@ -107,7 +107,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.Performance)}
|
||||
>
|
||||
{lang('Animations and Performance')}
|
||||
{oldLang('Animations and Performance')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="unmute"
|
||||
@ -115,7 +115,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.Notifications)}
|
||||
>
|
||||
{lang('Notifications')}
|
||||
{oldLang('Notifications')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="data"
|
||||
@ -123,7 +123,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.DataStorage)}
|
||||
>
|
||||
{lang('DataSettings')}
|
||||
{oldLang('DataSettings')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="lock"
|
||||
@ -131,7 +131,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.Privacy)}
|
||||
>
|
||||
{lang('PrivacySettings')}
|
||||
{oldLang('PrivacySettings')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="folder"
|
||||
@ -139,7 +139,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.Folders)}
|
||||
>
|
||||
{lang('Filters')}
|
||||
{oldLang('Filters')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="active-sessions"
|
||||
@ -147,7 +147,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.ActiveSessions)}
|
||||
>
|
||||
{lang('SessionsTitle')}
|
||||
{oldLang('SessionsTitle')}
|
||||
{sessionCount > 0 && (<span className="settings-item__current-value">{sessionCount}</span>)}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
@ -156,8 +156,8 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.Language)}
|
||||
>
|
||||
{lang('Language')}
|
||||
<span className="settings-item__current-value">{lang.langName}</span>
|
||||
{oldLang('Language')}
|
||||
<span className="settings-item__current-value">{oldLang.langName}</span>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="stickers"
|
||||
@ -165,7 +165,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => onScreenSelect(SettingsScreens.Stickers)}
|
||||
>
|
||||
{lang('StickersName')}
|
||||
{oldLang('StickersName')}
|
||||
</ListItem>
|
||||
</div>
|
||||
<div className="settings-main-menu">
|
||||
@ -176,7 +176,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumModal()}
|
||||
>
|
||||
{lang('TelegramPremium')}
|
||||
{oldLang('TelegramPremium')}
|
||||
</ListItem>
|
||||
)}
|
||||
{shouldDisplayStars && (
|
||||
@ -186,7 +186,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openStarsBalanceModal({})}
|
||||
>
|
||||
{lang('MenuTelegramStars')}
|
||||
{oldLang('MenuTelegramStars')}
|
||||
{Boolean(starsBalance) && (
|
||||
<span className="settings-item__current-value">{formatInteger(starsBalance)}</span>
|
||||
)}
|
||||
@ -199,7 +199,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openPremiumGiftingModal()}
|
||||
>
|
||||
{lang('GiftPremiumGifting')}
|
||||
{oldLang('GiftPremiumGifting')}
|
||||
</ListItem>
|
||||
)}
|
||||
</div>
|
||||
@ -209,7 +209,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
narrow
|
||||
onClick={openSupportDialog}
|
||||
>
|
||||
{lang('AskAQuestion')}
|
||||
{oldLang('AskAQuestion')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="help"
|
||||
@ -217,7 +217,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openUrl({ url: FAQ_URL })}
|
||||
>
|
||||
{lang('TelegramFaq')}
|
||||
{oldLang('TelegramFaq')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="privacy-policy"
|
||||
@ -225,14 +225,14 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => openUrl({ url: PRIVACY_URL })}
|
||||
>
|
||||
{lang('PrivacyPolicy')}
|
||||
{oldLang('PrivacyPolicy')}
|
||||
</ListItem>
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
isOpen={isSupportDialogOpen}
|
||||
confirmLabel={lang('lng_settings_ask_ok')}
|
||||
title={lang('AskAQuestion')}
|
||||
text={lang('lng_settings_ask_sure')}
|
||||
confirmLabel={oldLang('lng_settings_ask_ok')}
|
||||
title={oldLang('AskAQuestion')}
|
||||
text={oldLang('lng_settings_ask_sure')}
|
||||
confirmHandler={handleOpenSupport}
|
||||
onClose={closeSupportDialog}
|
||||
/>
|
||||
|
||||
@ -74,6 +74,7 @@ import ReactionPicker from '../middle/message/reactions/ReactionPicker.async';
|
||||
import MessageListHistoryHandler from '../middle/MessageListHistoryHandler';
|
||||
import MiddleColumn from '../middle/MiddleColumn';
|
||||
import ModalContainer from '../modals/ModalContainer';
|
||||
import StarGiftInfoModal from '../modals/stars/StarGiftInfoModal';
|
||||
import PaymentModal from '../payment/PaymentModal.async';
|
||||
import ReceiptModal from '../payment/ReceiptModal.async';
|
||||
import RightColumn from '../right/RightColumn';
|
||||
@ -92,8 +93,9 @@ import NewContactModal from './NewContactModal.async';
|
||||
import Notifications from './Notifications.async';
|
||||
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
|
||||
import GiveawayModal from './premium/GiveawayModal.async';
|
||||
import PremiumGiftingModal from './premium/PremiumGiftingModal.async';
|
||||
import PremiumGiftingPickerModal from './premium/PremiumGiftingPickerModal.async';
|
||||
import PremiumMainModal from './premium/PremiumMainModal.async';
|
||||
import StarsGiftingPickerModal from './premium/StarsGiftingPickerModal.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
|
||||
import './Main.scss';
|
||||
@ -145,7 +147,9 @@ type StateProps = {
|
||||
isAppendModalOpen?: boolean;
|
||||
isGiveawayModalOpen?: boolean;
|
||||
isDeleteMessageModalOpen?: boolean;
|
||||
isPremiumGiftingModalOpen?: boolean;
|
||||
isPremiumGiftingPickerModal?: boolean;
|
||||
isStarsGiftingPickerModal?: boolean;
|
||||
isStarGiftInfoModal?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
noRightColumnAnimation?: boolean;
|
||||
withInterfaceAnimations?: boolean;
|
||||
@ -195,7 +199,9 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
isPremiumModalOpen,
|
||||
isGiveawayModalOpen,
|
||||
isDeleteMessageModalOpen,
|
||||
isPremiumGiftingModalOpen,
|
||||
isPremiumGiftingPickerModal,
|
||||
isStarsGiftingPickerModal,
|
||||
isStarGiftInfoModal,
|
||||
isPaymentModalOpen,
|
||||
isReceiptModalOpen,
|
||||
isReactionPickerOpen,
|
||||
@ -574,7 +580,9 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
<MessageListHistoryHandler />
|
||||
{isPremiumModalOpen && <PremiumMainModal isOpen={isPremiumModalOpen} />}
|
||||
{isGiveawayModalOpen && <GiveawayModal isOpen={isGiveawayModalOpen} />}
|
||||
{isPremiumGiftingModalOpen && <PremiumGiftingModal isOpen={isPremiumGiftingModalOpen} />}
|
||||
{isPremiumGiftingPickerModal && <PremiumGiftingPickerModal isOpen={isPremiumGiftingPickerModal} />}
|
||||
{isStarsGiftingPickerModal && <StarsGiftingPickerModal isOpen={isStarsGiftingPickerModal} />}
|
||||
{isStarGiftInfoModal && <StarGiftInfoModal isOpen={isStarGiftInfoModal} />}
|
||||
<PremiumLimitReachedModal limit={limitReached} />
|
||||
<PaymentModal isOpen={isPaymentModalOpen} onClose={closePaymentModal} />
|
||||
<ReceiptModal isOpen={isReceiptModalOpen} onClose={clearReceipt} />
|
||||
@ -616,6 +624,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
giveawayModal,
|
||||
deleteMessageModal,
|
||||
giftingModal,
|
||||
starsGiftingModal,
|
||||
starGiftInfoModal,
|
||||
isMasterTab,
|
||||
payment,
|
||||
limitReachedModal,
|
||||
@ -671,7 +681,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
isPremiumModalOpen: premiumModal?.isOpen,
|
||||
isGiveawayModalOpen: giveawayModal?.isOpen,
|
||||
isDeleteMessageModalOpen: Boolean(deleteMessageModal),
|
||||
isPremiumGiftingModalOpen: giftingModal?.isOpen,
|
||||
isPremiumGiftingPickerModal: giftingModal?.isOpen,
|
||||
isStarsGiftingPickerModal: starsGiftingModal?.isOpen,
|
||||
isStarGiftInfoModal: starGiftInfoModal?.isOpen,
|
||||
limitReached: limitReachedModal?.limit,
|
||||
isPaymentModalOpen: payment.isPaymentModalOpen,
|
||||
isReceiptModalOpen: Boolean(payment.receipt),
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
@media (min-width: 451px) {
|
||||
.modalDialog :global(.modal-dialog) {
|
||||
max-width: 32rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
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;
|
||||
}
|
||||
|
||||
.boostIcon {
|
||||
color: var(--color-primary);
|
||||
vertical-align: middle;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './GiftPremiumModal';
|
||||
import type { OwnProps } from './PremiumGiftModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const GiftPremiumModalAsync: FC<OwnProps> = (props) => {
|
||||
const PremiumGiftModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const GiftPremiumModal = useModuleLoader(Bundles.Extra, 'GiftPremiumModal', !isOpen);
|
||||
const PremiumGiftModal = useModuleLoader(Bundles.Extra, 'PremiumGiftModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return GiftPremiumModal ? <GiftPremiumModal {...props} /> : undefined;
|
||||
return PremiumGiftModal ? <PremiumGiftModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default GiftPremiumModalAsync;
|
||||
export default PremiumGiftModalAsync;
|
||||
123
src/components/main/premium/PremiumGiftModal.module.scss
Normal file
123
src/components/main/premium/PremiumGiftModal.module.scss
Normal file
@ -0,0 +1,123 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
@media (min-width: 451px) {
|
||||
.modalDialog :global(.modal-dialog) {
|
||||
max-width: 32rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
z-index: calc(var(--z-media-viewer) - 1);
|
||||
}
|
||||
|
||||
.root :global(.modal-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog) {
|
||||
height: min(calc(55vh + 41px + 193px), 90vh);
|
||||
max-width: 26.25rem;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog),
|
||||
.root :global(.modal-content),
|
||||
.transition {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.giftSection {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 3.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--color-background);
|
||||
transition: 0.25s ease-out transform;
|
||||
}
|
||||
|
||||
.starHeaderText {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin: 0 0 0 3rem;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.hiddenHeader {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
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);
|
||||
}
|
||||
|
||||
.boostIcon {
|
||||
color: var(--color-primary);
|
||||
vertical-align: middle;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.optionBottom {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.secondaryInfo {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin: 0 1.5rem;
|
||||
}
|
||||
@ -28,7 +28,7 @@ import Link from '../../ui/Link';
|
||||
import Modal from '../../ui/Modal';
|
||||
import PremiumSubscriptionOption from './PremiumSubscriptionOption';
|
||||
|
||||
import styles from './GiftPremiumModal.module.scss';
|
||||
import styles from './PremiumGiftModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
@ -41,7 +41,7 @@ type StateProps = {
|
||||
boostPerSentGift?: number;
|
||||
};
|
||||
|
||||
const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
const PremiumGiftModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
isCompleted,
|
||||
gifts,
|
||||
@ -52,10 +52,11 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
openPremiumModal, closeGiftPremiumModal, openInvoice, requestConfetti,
|
||||
openPremiumModal, closePremiumGiftModal, openInvoice, requestConfetti,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [selectedMonthOption, setSelectedMonthOption] = useState<number | undefined>();
|
||||
|
||||
const selectedUserQuantity = forUserIds && forUserIds.length * boostPerSentGift;
|
||||
@ -140,23 +141,23 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
|
||||
function renderGiftTitle() {
|
||||
if (isCompleted) {
|
||||
return renderText(lang('TelegramPremiumUserGiftedPremiumOutboundDialogTitle',
|
||||
return renderText(oldLang('TelegramPremiumUserGiftedPremiumOutboundDialogTitle',
|
||||
[userNameList, selectedGift?.months]), ['simple_markdown']);
|
||||
}
|
||||
|
||||
return lang('GiftTelegramPremiumTitle');
|
||||
return oldLang('GiftTelegramPremiumTitle');
|
||||
}
|
||||
|
||||
function renderGiftText() {
|
||||
if (isCompleted) {
|
||||
return renderText(lang('TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle', userNameList),
|
||||
return renderText(oldLang('TelegramPremiumUserGiftedPremiumOutboundDialogSubtitle', userNameList),
|
||||
['simple_markdown']);
|
||||
}
|
||||
return renderText(lang('GiftPremiumUsersGiveAccessManyZero', userNameList), ['simple_markdown']);
|
||||
return renderText(oldLang('GiftPremiumUsersGiveAccessManyZero', userNameList), ['simple_markdown']);
|
||||
}
|
||||
|
||||
function renderPremiumFeaturesLink() {
|
||||
const info = lang('GiftPremiumListFeaturesAndTerms');
|
||||
const info = oldLang('GiftPremiumListFeaturesAndTerms');
|
||||
// Translation hack for rendering component inside string
|
||||
const parts = info.match(/([^*]*)\*([^*]+)\*(.*)/);
|
||||
|
||||
@ -174,7 +175,8 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
function renderBoostsPluralText() {
|
||||
const giftParts = renderText(lang('GiftPremiumWillReceiveBoostsPlural', selectedUserQuantity), ['simple_markdown']);
|
||||
const giftParts = renderText(oldLang('GiftPremiumWillReceiveBoostsPlural',
|
||||
selectedUserQuantity), ['simple_markdown']);
|
||||
return giftParts.map((part) => {
|
||||
if (typeof part === 'string') {
|
||||
return part.split(/(⚡)/g).map((subpart) => {
|
||||
@ -210,19 +212,20 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
return (
|
||||
<Modal
|
||||
dialogRef={dialogRef}
|
||||
onClose={closeGiftPremiumModal}
|
||||
onClose={closePremiumGiftModal}
|
||||
isOpen={isOpen}
|
||||
className={styles.modalDialog}
|
||||
contentClassName={styles.content}
|
||||
className={buildClassName(styles.modalDialog, styles.root)}
|
||||
>
|
||||
<div className="custom-scroll">
|
||||
<div className={buildClassName(styles.main, 'custom-scroll')}>
|
||||
<Button
|
||||
round
|
||||
size="smaller"
|
||||
className={styles.closeButton}
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => closeGiftPremiumModal()}
|
||||
ariaLabel={lang('Close')}
|
||||
onClick={() => closePremiumGiftModal()}
|
||||
ariaLabel={oldLang('Close')}
|
||||
>
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
@ -244,7 +247,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
{renderText(renderBoostsPluralText(), ['simple_markdown', 'emoji'])}
|
||||
</p>
|
||||
|
||||
<div className={styles.options}>
|
||||
<div className={styles.giftSection}>
|
||||
{renderSubscriptionGiftOptions()}
|
||||
</div>
|
||||
</>
|
||||
@ -253,12 +256,14 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
|
||||
{!isCompleted && (
|
||||
<Button withPremiumGradient className={styles.button} isShiny disabled={!selectedGift} onClick={handleSubmit}>
|
||||
{lang(
|
||||
'GiftSubscriptionFor', selectedGift
|
||||
&& formatCurrency(selectedGift!.amount, selectedGift.currency, lang.code),
|
||||
)}
|
||||
</Button>
|
||||
<div className={styles.footer}>
|
||||
<Button withPremiumGradient isShiny disabled={!selectedGift} onClick={handleSubmit}>
|
||||
{oldLang(
|
||||
'GiftSubscriptionFor', selectedGift
|
||||
&& formatCurrency(selectedGift!.amount, selectedGift.currency, oldLang.code),
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
@ -267,7 +272,7 @@ const GiftPremiumModal: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const {
|
||||
gifts, forUserIds, isCompleted,
|
||||
} = selectTabState(global).giftPremiumModal || {};
|
||||
} = selectTabState(global).giftModal || {};
|
||||
|
||||
return {
|
||||
isCompleted,
|
||||
@ -275,4 +280,4 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
boostPerSentGift: global.appConfig?.boostsPerSentGift,
|
||||
forUserIds,
|
||||
};
|
||||
})(GiftPremiumModal));
|
||||
})(PremiumGiftModal));
|
||||
@ -1,18 +0,0 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './PremiumGiftingModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const PremiumGiftingModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const PremiumGiftingModal = useModuleLoader(Bundles.Extra, 'PremiumGiftingModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return PremiumGiftingModal ? <PremiumGiftingModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default PremiumGiftingModalAsync;
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './PremiumGiftingPickerModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const PremiumGiftingPickerModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const PremiumGiftingPickerModal = useModuleLoader(Bundles.Extra, 'PremiumGiftingPickerModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return PremiumGiftingPickerModal ? <PremiumGiftingPickerModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default PremiumGiftingPickerModalAsync;
|
||||
@ -8,19 +8,16 @@ import { GIVEAWAY_MAX_ADDITIONAL_CHANNELS } from '../../../config';
|
||||
import {
|
||||
filterUsersByName, isUserBot,
|
||||
} from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PeerPicker from '../../common/pickers/PeerPicker';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
import PickerModal from '../../common/pickers/PickerModal';
|
||||
|
||||
import styles from './PremiumGiftingModal.module.scss';
|
||||
import styles from './PremiumGiftingPickerModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
@ -32,15 +29,15 @@ interface StateProps {
|
||||
userIds?: string[];
|
||||
}
|
||||
|
||||
const PremiumGiftingModal: FC<OwnProps & StateProps> = ({
|
||||
const PremiumGiftingPickerModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
currentUserId,
|
||||
userSelectionLimit = GIVEAWAY_MAX_ADDITIONAL_CHANNELS,
|
||||
userIds,
|
||||
}) => {
|
||||
const { closePremiumGiftingModal, openGiftPremiumModal, showNotification } = getActions();
|
||||
const { closePremiumGiftingModal, openPremiumGiftModal, showNotification } = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
@ -61,8 +58,7 @@ const PremiumGiftingModal: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const handleSendIdList = useLastCallback(() => {
|
||||
if (selectedUserIds?.length) {
|
||||
openGiftPremiumModal({ forUserIds: selectedUserIds });
|
||||
|
||||
openPremiumGiftModal({ forUserIds: selectedUserIds });
|
||||
closePremiumGiftingModal();
|
||||
}
|
||||
});
|
||||
@ -70,65 +66,42 @@ const PremiumGiftingModal: FC<OwnProps & StateProps> = ({
|
||||
const handleSelectedUserIdsChange = useLastCallback((newSelectedIds: string[]) => {
|
||||
if (newSelectedIds.length > userSelectionLimit) {
|
||||
showNotification({
|
||||
message: lang('BoostingSelectUpToWarningUsers', userSelectionLimit),
|
||||
message: oldLang('BoostingSelectUpToWarningUsers', userSelectionLimit),
|
||||
});
|
||||
return;
|
||||
}
|
||||
setSelectedUserIds(newSelectedIds);
|
||||
});
|
||||
|
||||
function renderSearchField() {
|
||||
return (
|
||||
<div className={styles.filter} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
round
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => closePremiumGiftingModal()}
|
||||
ariaLabel={lang('Close')}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<h3 className={styles.title}>{lang('GiftTelegramPremiumTitle')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<PickerModal
|
||||
className={styles.root}
|
||||
isOpen={isOpen}
|
||||
onClose={closePremiumGiftingModal}
|
||||
title={oldLang('GiftTelegramPremiumTitle')}
|
||||
hasCloseButton
|
||||
shouldAdaptToSearch
|
||||
withFixedHeight
|
||||
confirmButtonText={oldLang('Continue')}
|
||||
onConfirm={handleSendIdList}
|
||||
onEnter={handleSendIdList}
|
||||
withPremiumGradient
|
||||
>
|
||||
<div className={styles.main}>
|
||||
{renderSearchField()}
|
||||
<div className={buildClassName(styles.main, 'custom-scroll')}>
|
||||
<PeerPicker
|
||||
className={styles.picker}
|
||||
itemIds={displayedUserIds}
|
||||
selectedIds={selectedUserIds}
|
||||
filterValue={searchQuery}
|
||||
filterPlaceholder={lang('Search')}
|
||||
searchInputId="users-picker-search"
|
||||
onSelectedIdsChange={handleSelectedUserIdsChange}
|
||||
onFilterChange={setSearchQuery}
|
||||
isSearchable
|
||||
withDefaultPadding
|
||||
withStatus
|
||||
allowMultiple
|
||||
itemInputType="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
<Button withPremiumGradient size="smaller" onClick={handleSendIdList} disabled={!selectedUserIds?.length}>
|
||||
{lang('Continue')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<PeerPicker
|
||||
className={styles.picker}
|
||||
itemIds={displayedUserIds}
|
||||
selectedIds={selectedUserIds}
|
||||
filterValue={searchQuery}
|
||||
filterPlaceholder={oldLang('Search')}
|
||||
onSelectedIdsChange={handleSelectedUserIdsChange}
|
||||
onFilterChange={setSearchQuery}
|
||||
isSearchable
|
||||
withDefaultPadding
|
||||
withStatus
|
||||
allowMultiple
|
||||
itemInputType="checkbox"
|
||||
/>
|
||||
</PickerModal>
|
||||
);
|
||||
};
|
||||
|
||||
@ -140,4 +113,4 @@ export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
userIds: global.contactList?.userIds,
|
||||
userSelectionLimit: global.appConfig?.giveawayAddPeersMax,
|
||||
};
|
||||
})(PremiumGiftingModal));
|
||||
})(PremiumGiftingPickerModal));
|
||||
18
src/components/main/premium/StarsGiftModal.async.tsx
Normal file
18
src/components/main/premium/StarsGiftModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './StarsGiftModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const StarsGiftModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const StarsGiftModal = useModuleLoader(Bundles.Extra, 'StarsGiftModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return StarsGiftModal ? <StarsGiftModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default StarsGiftModalAsync;
|
||||
107
src/components/main/premium/StarsGiftModal.module.scss
Normal file
107
src/components/main/premium/StarsGiftModal.module.scss
Normal file
@ -0,0 +1,107 @@
|
||||
@media (min-width: 451px) {
|
||||
.modalDialog :global(.modal-dialog) {
|
||||
max-width: 32rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
z-index: calc(var(--z-media-viewer) - 1);
|
||||
}
|
||||
|
||||
.root :global(.modal-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog) {
|
||||
height: min(calc(55vh + 41px + 193px), 90vh);
|
||||
max-width: 26.25rem;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog),
|
||||
.root :global(.modal-content),
|
||||
.transition {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 0.0625rem solid var(--color-borders);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 3.5rem;
|
||||
padding: 0.5rem;
|
||||
background: var(--color-background);
|
||||
transition: 0.25s ease-out transform;
|
||||
}
|
||||
|
||||
.starHeaderText {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin: 0 0 0 3.5rem;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
.hiddenHeader {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.boostIcon {
|
||||
color: var(--color-primary);
|
||||
vertical-align: middle;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.moreOptions {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.secondaryInfo {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
padding: 0.5rem 1rem;
|
||||
margin-top: auto;
|
||||
}
|
||||
197
src/components/main/premium/StarsGiftModal.tsx
Normal file
197
src/components/main/premium/StarsGiftModal.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useEffect, useMemo, useRef,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiStarsGiftOption, ApiStarTopupOption, ApiUser,
|
||||
} from '../../../api/types';
|
||||
|
||||
import {
|
||||
selectTabState, selectUser,
|
||||
} from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatCurrencyAsString } from '../../../util/formatCurrency';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Avatar from '../../common/Avatar';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import StarTopupOptionList from '../../modals/stars/StarTopupOptionList';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
import styles from './StarsGiftModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
isCompleted?: boolean;
|
||||
starsGiftOptions?: ApiStarsGiftOption[] | undefined;
|
||||
forUserId?: string;
|
||||
user?: ApiUser;
|
||||
};
|
||||
|
||||
const StarsGiftModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
isCompleted,
|
||||
starsGiftOptions,
|
||||
forUserId,
|
||||
user,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const {
|
||||
closeStarsGiftModal, openInvoice, requestConfetti,
|
||||
} = getActions();
|
||||
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState<ApiStarTopupOption | undefined>();
|
||||
const [isHeaderHidden, setHeaderHidden] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setHeaderHidden(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const showConfetti = useLastCallback(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return;
|
||||
if (isOpen) {
|
||||
const {
|
||||
top, left, width, height,
|
||||
} = dialog.querySelector('.modal-content')!.getBoundingClientRect();
|
||||
requestConfetti({
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height,
|
||||
withStars: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isCompleted) {
|
||||
showConfetti();
|
||||
}
|
||||
}, [isCompleted, showConfetti]);
|
||||
|
||||
const handleClick = useLastCallback((option: ApiStarTopupOption) => {
|
||||
setSelectedOption(option);
|
||||
openInvoice({
|
||||
type: 'starsgift',
|
||||
userId: forUserId!,
|
||||
stars: option.stars,
|
||||
currency: option.currency,
|
||||
amount: option.amount,
|
||||
});
|
||||
});
|
||||
|
||||
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
||||
const { scrollTop } = e.currentTarget;
|
||||
|
||||
setHeaderHidden(scrollTop <= 150);
|
||||
}
|
||||
|
||||
function renderGiftTitle() {
|
||||
if (isCompleted) {
|
||||
return renderText(oldLang('Notification.StarsGift.SentYou',
|
||||
formatCurrencyAsString(selectedOption!.amount, selectedOption!.currency, oldLang.code)), ['simple_markdown']);
|
||||
}
|
||||
|
||||
return oldLang('GiftStarsTitle');
|
||||
}
|
||||
|
||||
function renderStarOptionList() {
|
||||
return (
|
||||
<StarTopupOptionList
|
||||
options={starsGiftOptions}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const bottomText = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
const text = oldLang('lng_credits_summary_options_about');
|
||||
const parts = text.split('{link}');
|
||||
return [
|
||||
parts[0],
|
||||
<SafeLink url={oldLang('StarsTOSLink')} text={oldLang('lng_credits_summary_options_about_link')} />,
|
||||
parts[1],
|
||||
];
|
||||
}, [isOpen, oldLang]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dialogRef={dialogRef}
|
||||
onClose={closeStarsGiftModal}
|
||||
isOpen={isOpen}
|
||||
className={buildClassName(styles.modalDialog, styles.root)}
|
||||
>
|
||||
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
||||
<Button
|
||||
round
|
||||
size="smaller"
|
||||
className={styles.closeButton}
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => closeStarsGiftModal()}
|
||||
ariaLabel={oldLang('Close')}
|
||||
>
|
||||
<i className="icon icon-close" />
|
||||
</Button>
|
||||
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
||||
<h2 className={styles.starHeaderText}>
|
||||
{oldLang('GiftStarsTitle')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.avatars}>
|
||||
<Avatar
|
||||
size="large"
|
||||
peer={user}
|
||||
/>
|
||||
</div>
|
||||
<h2 className={buildClassName(styles.headerText, styles.center)}>
|
||||
{renderGiftTitle()}
|
||||
</h2>
|
||||
{!isCompleted && (
|
||||
<>
|
||||
<div className={buildClassName(styles.section, styles.options)}>
|
||||
{renderStarOptionList()}
|
||||
</div>
|
||||
<div className={styles.secondaryInfo}>
|
||||
{bottomText}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const {
|
||||
starsGiftOptions, forUserId, isCompleted,
|
||||
} = selectTabState(global).starsGiftModal || {};
|
||||
|
||||
const user = forUserId ? selectUser(getGlobal(), forUserId) : undefined;
|
||||
|
||||
return {
|
||||
isCompleted,
|
||||
starsGiftOptions,
|
||||
forUserId,
|
||||
user,
|
||||
};
|
||||
})(StarsGiftModal));
|
||||
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './StarsGiftingPickerModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const StarsGiftingPickerModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const StarsGiftingPickerModal = useModuleLoader(Bundles.Extra, 'StarsGiftingPickerModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return StarsGiftingPickerModal ? <StarsGiftingPickerModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default StarsGiftingPickerModalAsync;
|
||||
@ -0,0 +1,86 @@
|
||||
.root {
|
||||
z-index: calc(var(--z-media-viewer) - 1);
|
||||
}
|
||||
|
||||
.root :global(.modal-content) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog) {
|
||||
max-width: 55vh;
|
||||
}
|
||||
|
||||
.root :global(.modal-dialog),
|
||||
.root :global(.modal-content),
|
||||
.transition {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main {
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.filter {
|
||||
padding: 0.375rem 1rem 0.25rem 0.75rem;
|
||||
margin-bottom: 0.625rem;
|
||||
background-color: var(--color-background);
|
||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
||||
border-bottom: 0.625rem solid var(--color-background-secondary);
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
max-height: 20rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
width: 100%;
|
||||
background: var(--color-background);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.picker {
|
||||
height: 75vh;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
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;
|
||||
font-weight: 600;
|
||||
}
|
||||
141
src/components/main/premium/StarsGiftingPickerModal.tsx
Normal file
141
src/components/main/premium/StarsGiftingPickerModal.tsx
Normal file
@ -0,0 +1,141 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||
import {
|
||||
filterUsersByName, isDeletedUser, isUserBot,
|
||||
} from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { unique } from '../../../util/iteratees';
|
||||
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PeerPicker from '../../common/pickers/PeerPicker';
|
||||
import Button from '../../ui/Button';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
import styles from './StarsGiftingPickerModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
interface StateProps {
|
||||
currentUserId?: string;
|
||||
userIds?: string[];
|
||||
activeListIds?: string[];
|
||||
archivedListIds?: string[];
|
||||
}
|
||||
|
||||
const StarsGiftingPickerModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
currentUserId,
|
||||
activeListIds,
|
||||
archivedListIds,
|
||||
userIds,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const dialogRef = useRef<HTMLDivElement>(null);
|
||||
const { closeStarsGiftingModal, openStarsGiftModal } = getActions();
|
||||
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
|
||||
const displayedUserIds = useMemo(() => {
|
||||
const usersById = getGlobal().users.byId;
|
||||
const combinedIds = [
|
||||
...(userIds || []),
|
||||
...(activeListIds || []),
|
||||
...(archivedListIds || []),
|
||||
];
|
||||
|
||||
const filteredContactIds = filterUsersByName(combinedIds, usersById, searchQuery);
|
||||
|
||||
return sortChatIds(unique(filteredContactIds).filter((id) => {
|
||||
const user = usersById[id];
|
||||
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !user.isSupport
|
||||
&& !isUserBot(user) && !isDeletedUser(user)
|
||||
&& id !== currentUserId && id !== SERVICE_NOTIFICATIONS_USER_ID;
|
||||
}));
|
||||
}, [currentUserId, searchQuery, userIds, activeListIds, archivedListIds]);
|
||||
|
||||
const handleSelectedUserIdsChange = useLastCallback((newSelectedId?: string) => {
|
||||
if (newSelectedId?.length) {
|
||||
openStarsGiftModal({ forUserId: newSelectedId });
|
||||
}
|
||||
});
|
||||
|
||||
function renderHeaderText() {
|
||||
return (
|
||||
<div className={styles.filter} dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||
<Button
|
||||
round
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => closeStarsGiftingModal()}
|
||||
ariaLabel={oldLang('Close')}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<h3 className={buildClassName(styles.title, 'ml-2')}>{oldLang('GiftStarsTitle')}
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={styles.root}
|
||||
isOpen={isOpen}
|
||||
onClose={closeStarsGiftingModal}
|
||||
onEnter={handleSelectedUserIdsChange}
|
||||
dialogRef={dialogRef}
|
||||
>
|
||||
<div className={buildClassName(styles.main, 'custom-scroll')}>
|
||||
{renderHeaderText()}
|
||||
<PeerPicker
|
||||
className={styles.picker}
|
||||
itemIds={displayedUserIds}
|
||||
filterValue={searchQuery}
|
||||
filterPlaceholder={oldLang('Search')}
|
||||
onFilterChange={setSearchQuery}
|
||||
isSearchable
|
||||
withDefaultPadding
|
||||
withStatus
|
||||
onSelectedIdChange={handleSelectedUserIdsChange}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
listIds,
|
||||
},
|
||||
currentUserId,
|
||||
} = global;
|
||||
|
||||
return {
|
||||
userIds: global.contactList?.userIds,
|
||||
activeListIds: listIds.active,
|
||||
archivedListIds: listIds.archived,
|
||||
currentUserId,
|
||||
};
|
||||
})(StarsGiftingPickerModal));
|
||||
@ -13,7 +13,7 @@ import type { FocusDirection, ThreadId } from '../../types';
|
||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||
|
||||
import {
|
||||
getChatTitle, getMessageHtmlId, isJoinedChannelMessage,
|
||||
getChatTitle, getMessageHtmlId, getSenderTitle, isJoinedChannelMessage,
|
||||
} from '../../global/helpers';
|
||||
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||
import {
|
||||
@ -35,6 +35,7 @@ import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||
import useEnsureMessage from '../../hooks/useEnsureMessage';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
import { useIsIntersecting, useOnIntersect } from '../../hooks/useIntersectionObserver';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
import useFocusMessage from './message/hooks/useFocusMessage';
|
||||
@ -103,10 +104,11 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
onPinnedIntersectionChange,
|
||||
}) => {
|
||||
const {
|
||||
openPremiumModal, requestConfetti, checkGiftCode, getReceipt,
|
||||
openPremiumModal, requestConfetti, checkGiftCode, getReceipt, openStarGiftInfoModal,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
@ -133,6 +135,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
const isGiftCode = Boolean(message.content.action?.text.startsWith('BoostingReceivedGift'));
|
||||
const isSuggestedAvatar = message.content.action?.type === 'suggestProfilePhoto' && message.content.action!.photo;
|
||||
const isJoinedMessage = isJoinedChannelMessage(message);
|
||||
const hasStars = Boolean(message.content.action?.stars);
|
||||
|
||||
useEffect(() => {
|
||||
if (noAppearanceAnimation) {
|
||||
@ -168,7 +171,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
return renderActionMessageText(
|
||||
lang,
|
||||
oldLang,
|
||||
message,
|
||||
senderUser,
|
||||
senderChat,
|
||||
@ -181,7 +184,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
observeIntersectionForPlaying,
|
||||
);
|
||||
}, [
|
||||
isEmbedded, lang, message, observeIntersectionForLoading, observeIntersectionForPlaying,
|
||||
isEmbedded, message, observeIntersectionForLoading, observeIntersectionForPlaying, oldLang,
|
||||
senderChat, senderUser, targetChatId, targetMessage, targetUsers, topic,
|
||||
]);
|
||||
|
||||
@ -197,6 +200,14 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
handleBeforeContextMenu(e);
|
||||
};
|
||||
|
||||
const handleStarGiftClick = () => {
|
||||
openStarGiftInfoModal({
|
||||
toUserId: targetUserIds?.[0],
|
||||
stars: message.content.action!.stars,
|
||||
date: message.date,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePremiumGiftClick = () => {
|
||||
openPremiumModal({
|
||||
isGift: true,
|
||||
@ -233,7 +244,12 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
|
||||
function renderGift() {
|
||||
return (
|
||||
<span className="action-message-gift" tabIndex={0} role="button" onClick={handlePremiumGiftClick}>
|
||||
<span
|
||||
className="action-message-gift action-message-stars-gift"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={hasStars ? handleStarGiftClick : handlePremiumGiftClick}
|
||||
>
|
||||
<AnimatedIconFromSticker
|
||||
key={message.id}
|
||||
sticker={premiumGiftSticker}
|
||||
@ -241,10 +257,14 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
noLoop
|
||||
nonInteractive
|
||||
/>
|
||||
<strong>{lang('ActionGiftPremiumTitle')}</strong>
|
||||
<span>{lang('ActionGiftPremiumSubtitle', lang('Months', message.content.action?.months, 'i'))}</span>
|
||||
<strong>{hasStars ? oldLang('Stars', message.content.action?.stars)
|
||||
: oldLang('ActionGiftPremiumTitle')}
|
||||
</strong>
|
||||
<span>{hasStars ? oldLang('ActionGiftStarsSubtitleYou')
|
||||
: oldLang('ActionGiftPremiumSubtitle', oldLang('Months', message.content.action?.months, 'i'))}
|
||||
</span>
|
||||
|
||||
<span className="action-message-button">{lang('ActionGiftPremiumView')}</span>
|
||||
<span className="action-message-button">{oldLang('ActionGiftPremiumView')}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -257,7 +277,7 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
className="action-message-gift action-message-gift-code"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={handleGiftCodeClick}
|
||||
onClick={hasStars ? handleStarGiftClick : handleGiftCodeClick}
|
||||
>
|
||||
<AnimatedIconFromSticker
|
||||
key={message.id}
|
||||
@ -266,21 +286,36 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
||||
noLoop
|
||||
nonInteractive
|
||||
/>
|
||||
<strong>{lang(isUnclaimed ? 'BoostingUnclaimedPrize' : 'BoostingCongratulations')}</strong>
|
||||
<strong>{hasStars ? oldLang('Stars', message.content.action?.stars)
|
||||
: oldLang(isUnclaimed ? 'BoostingUnclaimedPrize' : 'BoostingCongratulations')}
|
||||
</strong>
|
||||
<span className="action-message-subtitle">
|
||||
{targetChat && renderText(lang(isFromGiveaway ? 'BoostingReceivedGiftFrom' : isUnclaimed
|
||||
{hasStars ? lang('GiftStarsOutgoing', {
|
||||
user: (
|
||||
<b>
|
||||
{senderUser && renderText(getSenderTitle(oldLang, senderUser) || '', ['simple_markdown'])}
|
||||
</b>
|
||||
),
|
||||
}, {
|
||||
withNodes: true,
|
||||
}) : targetChat && renderText(oldLang(isFromGiveaway ? 'BoostingReceivedGiftFrom' : isUnclaimed
|
||||
? 'BoostingReceivedPrizeFrom' : 'BoostingYouHaveUnclaimedPrize',
|
||||
getChatTitle(lang, targetChat)),
|
||||
getChatTitle(oldLang, targetChat)),
|
||||
['simple_markdown'])}
|
||||
</span>
|
||||
<span className="action-message-subtitle">
|
||||
{renderText(lang(
|
||||
'BoostingUnclaimedPrizeDuration',
|
||||
lang('Months', message.content.action?.months, 'i'),
|
||||
), ['simple_markdown'])}
|
||||
</span>
|
||||
{!hasStars && (
|
||||
<span className="action-message-subtitle">
|
||||
{renderText(oldLang(
|
||||
'BoostingUnclaimedPrizeDuration',
|
||||
oldLang('Months', message.content.action?.months, 'i'),
|
||||
), ['simple_markdown'])}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span className="action-message-button">{lang('BoostingReceivedGiftOpenBtn')}</span>
|
||||
<span className="action-message-button">{
|
||||
oldLang(hasStars ? 'ActionGiftPremiumView' : 'BoostingReceivedGiftOpenBtn')
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
requestMasterAndRequestCall,
|
||||
toggleStatistics,
|
||||
openBoostStatistics,
|
||||
openGiftPremiumModal,
|
||||
openPremiumGiftModal,
|
||||
openThreadWithInfo,
|
||||
openCreateTopicPanel,
|
||||
openEditTopicPanel,
|
||||
@ -313,7 +313,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleGiftPremiumClick = useLastCallback(() => {
|
||||
openGiftPremiumModal({ forUserIds: [chatId] });
|
||||
openPremiumGiftModal({ forUserIds: [chatId] });
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
|
||||
@ -277,6 +277,11 @@
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.action-message-stars-gift {
|
||||
width: 15rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.action-message-subtitle {
|
||||
margin-top: 1rem;
|
||||
font-weight: normal;
|
||||
|
||||
@ -82,7 +82,8 @@ import Composer from '../common/Composer';
|
||||
import PrivacySettingsNoticeModal from '../common/PrivacySettingsNoticeModal.async';
|
||||
import SeenByModal from '../common/SeenByModal.async';
|
||||
import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async';
|
||||
import GiftPremiumModal from '../main/premium/GiftPremiumModal.async';
|
||||
import PremiumGiftModal from '../main/premium/PremiumGiftModal.async';
|
||||
import StarsGiftModal from '../main/premium/StarsGiftModal.async';
|
||||
import Button from '../ui/Button';
|
||||
import Transition from '../ui/Transition';
|
||||
import ChatLanguageModal from './ChatLanguageModal.async';
|
||||
@ -133,7 +134,8 @@ type StateProps = {
|
||||
isSeenByModalOpen: boolean;
|
||||
isPrivacySettingsNoticeModalOpen: boolean;
|
||||
isReactorListModalOpen: boolean;
|
||||
isGiftPremiumModalOpen?: boolean;
|
||||
isPremiumGiftModalOpen?: boolean;
|
||||
isStarsGiftModalOpen?: boolean;
|
||||
isChatLanguageModalOpen?: boolean;
|
||||
withInterfaceAnimations?: boolean;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
@ -193,7 +195,8 @@ function MiddleColumn({
|
||||
isSeenByModalOpen,
|
||||
isPrivacySettingsNoticeModalOpen,
|
||||
isReactorListModalOpen,
|
||||
isGiftPremiumModalOpen,
|
||||
isPremiumGiftModalOpen,
|
||||
isStarsGiftModalOpen,
|
||||
isChatLanguageModalOpen,
|
||||
withInterfaceAnimations,
|
||||
shouldSkipHistoryAnimations,
|
||||
@ -715,7 +718,8 @@ function MiddleColumn({
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<GiftPremiumModal isOpen={isGiftPremiumModalOpen} />
|
||||
<PremiumGiftModal isOpen={isPremiumGiftModalOpen} />
|
||||
<StarsGiftModal isOpen={isStarsGiftModalOpen} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -729,7 +733,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const {
|
||||
messageLists, isLeftColumnShown, activeEmojiInteractions,
|
||||
seenByModal, giftPremiumModal, reactorModal, audioPlayer, shouldSkipHistoryAnimations,
|
||||
seenByModal, giftModal, starsGiftModal, reactorModal, audioPlayer, shouldSkipHistoryAnimations,
|
||||
chatLanguageModal, privacySettingsNoticeModal,
|
||||
} = selectTabState(global);
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
@ -748,7 +752,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
isSeenByModalOpen: Boolean(seenByModal),
|
||||
isPrivacySettingsNoticeModalOpen: Boolean(privacySettingsNoticeModal),
|
||||
isReactorListModalOpen: Boolean(reactorModal),
|
||||
isGiftPremiumModalOpen: giftPremiumModal?.isOpen,
|
||||
isPremiumGiftModalOpen: giftModal?.isOpen,
|
||||
isStarsGiftModalOpen: starsGiftModal?.isOpen,
|
||||
isChatLanguageModalOpen: Boolean(chatLanguageModal),
|
||||
withInterfaceAnimations: selectCanAnimateInterface(global),
|
||||
currentTransitionKey: Math.max(0, messageLists.length - 1),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -19,10 +21,30 @@
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
|
||||
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 1rem;
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
align-self: center;
|
||||
min-height: 6.25rem;
|
||||
}
|
||||
|
||||
.logoBackground {
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
|
||||
@ -15,6 +15,8 @@ import Modal from '../../ui/Modal';
|
||||
|
||||
import styles from './TableInfoModal.module.scss';
|
||||
|
||||
import StarsBackground from '../../../assets/stars-bg.png';
|
||||
|
||||
type ChatItem = { chatId: string };
|
||||
|
||||
export type TableData = [TeactNode, TeactNode | ChatItem][];
|
||||
@ -24,6 +26,7 @@ type OwnProps = {
|
||||
title?: string;
|
||||
tableData?: TableData;
|
||||
headerImageUrl?: string;
|
||||
logoBackground?: string;
|
||||
headerAvatarPeer?: ApiPeer | CustomPeer;
|
||||
headerAvatarWebPhoto?: ApiWebDocument;
|
||||
noHeaderImage?: boolean;
|
||||
@ -40,6 +43,7 @@ const TableInfoModal = ({
|
||||
title,
|
||||
tableData,
|
||||
headerImageUrl,
|
||||
logoBackground,
|
||||
headerAvatarPeer,
|
||||
headerAvatarWebPhoto,
|
||||
noHeaderImage,
|
||||
@ -73,7 +77,11 @@ const TableInfoModal = ({
|
||||
withAvatar ? (
|
||||
<Avatar peer={headerAvatarPeer} webPhoto={headerAvatarWebPhoto} size="jumbo" className={styles.avatar} />
|
||||
) : (
|
||||
<img className={styles.logo} src={headerImageUrl} alt="" draggable={false} />
|
||||
<div className={styles.section}>
|
||||
<img className={styles.logo} src={headerImageUrl} alt="" draggable={false} />
|
||||
{Boolean(logoBackground)
|
||||
&& <img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{header}
|
||||
@ -98,7 +106,7 @@ const TableInfoModal = ({
|
||||
</table>
|
||||
{footer}
|
||||
{buttonText && (
|
||||
<Button onClick={onButtonClick || onClose}>{buttonText}</Button>
|
||||
<Button size="smaller" onClick={onButtonClick || onClose}>{buttonText}</Button>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
18
src/components/modals/stars/StarGiftInfoModal.async.tsx
Normal file
18
src/components/modals/stars/StarGiftInfoModal.async.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './StarGiftInfoModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const StarGiftInfoModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const StarGiftInfoModal = useModuleLoader(Bundles.Extra, 'StarGiftInfoModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return StarGiftInfoModal ? <StarGiftInfoModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default StarGiftInfoModalAsync;
|
||||
32
src/components/modals/stars/StarGiftInfoModal.module.scss
Normal file
32
src/components/modals/stars/StarGiftInfoModal.module.scss
Normal file
@ -0,0 +1,32 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.centered {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
|
||||
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.starTitle {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin: 1rem 0;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
150
src/components/modals/stars/StarGiftInfoModal.tsx
Normal file
150
src/components/modals/stars/StarGiftInfoModal.tsx
Normal file
@ -0,0 +1,150 @@
|
||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiPeer } from '../../../api/types';
|
||||
|
||||
import { getSenderTitle } from '../../../global/helpers';
|
||||
import { selectTabState, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatDateTimeToString } from '../../../util/dates/dateFormat';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import SafeLink from '../../common/SafeLink';
|
||||
import TableInfoModal, { type TableData } from '../common/TableInfoModal';
|
||||
|
||||
import styles from './StarGiftInfoModal.module.scss';
|
||||
|
||||
import StarLogo from '../../../assets/icons/StarLogo.svg';
|
||||
import StarsBackground from '../../../assets/stars-bg.png';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
export type StateProps = {
|
||||
stars?: number;
|
||||
user?: ApiPeer;
|
||||
date?: number;
|
||||
};
|
||||
|
||||
const StarGiftInfoModal = ({
|
||||
isOpen,
|
||||
stars,
|
||||
user,
|
||||
date,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeStarGiftInfoModal,
|
||||
} = getActions();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
const infoText = useMemo(() => {
|
||||
const linkText = oldLang('GiftStarsSubtitleLinkName');
|
||||
|
||||
return lang('CreditsBoxHistoryEntryGiftOutAbout',
|
||||
{
|
||||
user: (
|
||||
<b>
|
||||
{user && renderText(getSenderTitle(oldLang, user) || '', ['simple_markdown'])}
|
||||
</b>
|
||||
),
|
||||
link: (
|
||||
<SafeLink
|
||||
url={oldLang('lng_paid_about_link_url')}
|
||||
text={linkText}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
withNodes: true,
|
||||
});
|
||||
}, [lang, oldLang, user]);
|
||||
|
||||
const footerText = useMemo(() => {
|
||||
const linkText = oldLang('lng_payments_terms_link');
|
||||
return lang('CreditsBoxOutAbout', {
|
||||
link: (
|
||||
<SafeLink
|
||||
url={oldLang('StarsTOSLink')}
|
||||
text={linkText}
|
||||
/>
|
||||
),
|
||||
}, {
|
||||
withNodes: true,
|
||||
});
|
||||
}, [lang, oldLang]);
|
||||
|
||||
const handleButtonClick = useLastCallback(() => {
|
||||
closeStarGiftInfoModal();
|
||||
});
|
||||
|
||||
const modalData = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
const header = (
|
||||
<>
|
||||
<h2 className={buildClassName(styles.starTitle, styles.centered)}>{oldLang('StarsGiftSent')}</h2>
|
||||
<div className={styles.info}>
|
||||
<p className={buildClassName(styles.starTitle, styles.centered)}>{stars}</p>
|
||||
<StarIcon type="gold" size="middle" />
|
||||
</div>
|
||||
<p className={styles.centered}>{infoText}</p>
|
||||
</>
|
||||
);
|
||||
|
||||
const tableData = [
|
||||
[oldLang('Recipient'), user ? { chatId: user.id } : oldLang('BoostingNoRecipient')],
|
||||
[oldLang('BoostingDate'), formatDateTimeToString(date! * 1000, lang.code, true)],
|
||||
] satisfies TableData;
|
||||
|
||||
const footer = (
|
||||
<span className={buildClassName(styles.footer, styles.centered)}>
|
||||
{footerText}
|
||||
</span>
|
||||
);
|
||||
|
||||
return {
|
||||
header,
|
||||
tableData,
|
||||
footer,
|
||||
};
|
||||
}, [isOpen, oldLang, stars, infoText, user, date, lang.code, footerText]);
|
||||
|
||||
if (!modalData) return undefined;
|
||||
|
||||
return (
|
||||
<TableInfoModal
|
||||
isOpen={isOpen}
|
||||
headerImageUrl={StarLogo}
|
||||
logoBackground={StarsBackground}
|
||||
tableData={modalData.tableData}
|
||||
header={modalData.header}
|
||||
footer={modalData.footer}
|
||||
buttonText={lang('Close')}
|
||||
onButtonClick={handleButtonClick}
|
||||
onClose={closeStarGiftInfoModal}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
starGiftInfoModal,
|
||||
} = selectTabState(global);
|
||||
const toUserId = starGiftInfoModal?.toUserId;
|
||||
const user = toUserId ? selectUser(global, toUserId) : undefined;
|
||||
|
||||
return {
|
||||
stars: starGiftInfoModal?.stars,
|
||||
user,
|
||||
date: starGiftInfoModal?.date,
|
||||
};
|
||||
},
|
||||
)(StarGiftInfoModal));
|
||||
62
src/components/modals/stars/StarTopupOptionList.module.scss
Normal file
62
src/components/modals/stars/StarTopupOptionList.module.scss
Normal file
@ -0,0 +1,62 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.option {
|
||||
--_background-color: var(--color-background-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.125rem;
|
||||
|
||||
padding: 1rem;
|
||||
border-radius: 0.625rem;
|
||||
|
||||
background-color: var(--_background-color);
|
||||
transition: background-color 0.25s ease-out;
|
||||
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
--_background-color: var(--color-background-secondary-accent);
|
||||
}
|
||||
}
|
||||
|
||||
.wideOption {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.optionTop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stackedStars {
|
||||
display: grid;
|
||||
grid-auto-columns: 0.4375rem;
|
||||
grid-auto-flow: column;
|
||||
justify-items: end;
|
||||
}
|
||||
|
||||
.stackedStar {
|
||||
@include mixins.filter-outline(0.0625rem, var(--_background-color));
|
||||
transition: filter 0.25s ease-out;
|
||||
}
|
||||
|
||||
.optionBottom {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.moreOptions {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.iconDown {
|
||||
margin-inline-start: 0.25rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
110
src/components/modals/stars/StarTopupOptionList.tsx
Normal file
110
src/components/modals/stars/StarTopupOptionList.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useEffect, useMemo } from '../../../lib/teact/teact';
|
||||
|
||||
import type { ApiStarTopupOption } from '../../../api/types';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
import { formatInteger } from '../../../util/textFormat';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import StarIcon from '../../common/icons/StarIcon';
|
||||
import Button from '../../ui/Button';
|
||||
|
||||
import styles from './StarTopupOptionList.module.scss';
|
||||
|
||||
const MAX_STARS_COUNT = 6;
|
||||
|
||||
type OwnProps = {
|
||||
isActive?: boolean;
|
||||
options?: ApiStarTopupOption[];
|
||||
starsNeeded?: number;
|
||||
onClick: (option: ApiStarTopupOption) => void;
|
||||
};
|
||||
|
||||
const StarTopupOptionList: FC<OwnProps> = ({
|
||||
isActive,
|
||||
options,
|
||||
starsNeeded,
|
||||
onClick,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
|
||||
const [areOptionsExtended, markOptionsExtended, unmarkOptionsExtended] = useFlag();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive) {
|
||||
unmarkOptionsExtended();
|
||||
}
|
||||
}, [isActive]);
|
||||
|
||||
const [renderingOptions, canExtend] = useMemo(() => {
|
||||
if (!options) {
|
||||
return [undefined, false];
|
||||
}
|
||||
|
||||
const maxOption = options.reduce((max, option) => (
|
||||
max.stars > option.stars ? max : option
|
||||
));
|
||||
const forceShowAll = starsNeeded && maxOption.stars < starsNeeded;
|
||||
|
||||
const result: { option: ApiStarTopupOption; starsCount: number; isWide: boolean }[] = [];
|
||||
let currentStackedStarsCount = 0;
|
||||
let canExtendOptions = false;
|
||||
options.forEach((option, index) => {
|
||||
if (!option.isExtended) currentStackedStarsCount++;
|
||||
|
||||
if (starsNeeded && !forceShowAll && option.stars < starsNeeded) return;
|
||||
if (!areOptionsExtended && option.isExtended) {
|
||||
canExtendOptions = true;
|
||||
return;
|
||||
}
|
||||
result.push({
|
||||
option,
|
||||
starsCount: Math.min(currentStackedStarsCount, MAX_STARS_COUNT),
|
||||
isWide: index === options.length - 1,
|
||||
});
|
||||
});
|
||||
|
||||
return [result, canExtendOptions];
|
||||
}, [areOptionsExtended, options, starsNeeded]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderingOptions?.map(({ option, starsCount, isWide }) => {
|
||||
const length = renderingOptions?.length;
|
||||
const isOdd = length % 2 === 0;
|
||||
return (
|
||||
<div
|
||||
className={buildClassName(styles.option, (!isOdd && isWide) && styles.wideOption)}
|
||||
key={option.stars}
|
||||
onClick={() => onClick?.(option)}
|
||||
>
|
||||
<div className={styles.optionTop}>
|
||||
+{formatInteger(option.stars)}
|
||||
<div className={styles.stackedStars} dir={lang.isRtl ? 'ltr' : 'rtl'}>
|
||||
{Array.from({ length: starsCount }).map(() => (
|
||||
<StarIcon className={styles.stackedStar} type="gold" size="big" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.optionBottom}>
|
||||
{formatCurrency(option.amount, option.currency, lang.code)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!areOptionsExtended && canExtend && (
|
||||
<Button className={styles.moreOptions} isText noForcedUpperCase onClick={markOptionsExtended}>
|
||||
{lang('Stars.Purchase.ShowMore')}
|
||||
<Icon className={styles.iconDown} name="down" />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(StarTopupOptionList);
|
||||
@ -24,6 +24,11 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -138,63 +143,16 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.option {
|
||||
--_background-color: var(--color-background-secondary);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.125rem;
|
||||
|
||||
padding: 1rem;
|
||||
border-radius: 0.625rem;
|
||||
|
||||
background-color: var(--_background-color);
|
||||
transition: background-color 0.25s ease-out;
|
||||
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
|
||||
&:hover {
|
||||
--_background-color: var(--color-background-secondary-accent);
|
||||
}
|
||||
.optionFullWidth {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.optionTop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.stackedStars {
|
||||
display: grid;
|
||||
grid-auto-columns: 0.4375rem;
|
||||
grid-auto-flow: column;
|
||||
justify-items: center;
|
||||
margin-left: 0.375rem;
|
||||
}
|
||||
|
||||
.stackedStar {
|
||||
@include mixins.filter-outline(0.0625rem, var(--_background-color));
|
||||
transition: filter 0.25s ease-out;
|
||||
}
|
||||
|
||||
.optionBottom {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.moreOptions {
|
||||
.starButton {
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.iconDown {
|
||||
margin-inline-start: 0.25rem;
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.paymentContent {
|
||||
|
||||
@ -7,13 +7,11 @@ import type { ApiStarTopupOption, ApiUser } from '../../../api/types';
|
||||
import type { GlobalState, TabState } from '../../../global/types';
|
||||
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
import { selectUser } from '../../../global/selectors';
|
||||
import { selectIsPremiumPurchaseBlocked, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
import { formatInteger } from '../../../util/textFormat';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
@ -27,6 +25,7 @@ import TabList, { type TabWithProperties } from '../../ui/TabList';
|
||||
import Transition from '../../ui/Transition';
|
||||
import BalanceBlock from './BalanceBlock';
|
||||
import TransactionItem from './StarsTransactionItem';
|
||||
import StarTopupOptionList from './StarTopupOptionList';
|
||||
|
||||
import styles from './StarsBalanceModal.module.scss';
|
||||
|
||||
@ -39,7 +38,6 @@ const TRANSACTION_TABS: TabWithProperties[] = [
|
||||
{ title: 'StarsTransactionsIncoming' },
|
||||
{ title: 'StarsTransactionsOutgoing' },
|
||||
];
|
||||
const MAX_STARS_COUNT = 6;
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['starsBalanceModal'];
|
||||
@ -48,19 +46,22 @@ export type OwnProps = {
|
||||
type StateProps = {
|
||||
starsBalanceState?: GlobalState['stars'];
|
||||
originPaymentBot?: ApiUser;
|
||||
canBuyPremium?: boolean;
|
||||
};
|
||||
|
||||
const StarsBalanceModal = ({
|
||||
modal, starsBalanceState, originPaymentBot,
|
||||
modal, starsBalanceState, originPaymentBot, canBuyPremium,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { closeStarsBalanceModal, loadStarsTransactions, openInvoice } = getActions();
|
||||
const {
|
||||
closeStarsBalanceModal, loadStarsTransactions, openInvoice, openStarsGiftingModal,
|
||||
} = getActions();
|
||||
|
||||
const { balance, history, topupOptions } = starsBalanceState || {};
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
const [isHeaderHidden, setHeaderHidden] = useState(true);
|
||||
const [areOptionsExtended, markOptionsExtended, unmarkOptionsExtended] = useFlag();
|
||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||
|
||||
const isOpen = Boolean(modal && starsBalanceState);
|
||||
@ -73,52 +74,21 @@ const StarsBalanceModal = ({
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setHeaderHidden(true);
|
||||
unmarkOptionsExtended();
|
||||
setSelectedTabIndex(0);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const [renderingOptions, canExtend] = useMemo(() => {
|
||||
if (!topupOptions) {
|
||||
return [undefined, false];
|
||||
}
|
||||
|
||||
const maxOption = topupOptions.reduce((max, option) => (
|
||||
max.stars > option.stars ? max : option
|
||||
));
|
||||
const forceShowAll = starsNeeded && maxOption.stars < starsNeeded;
|
||||
|
||||
const result: { option: ApiStarTopupOption; starsCount: number }[] = [];
|
||||
let currentStackedStarsCount = 0;
|
||||
let canExtendOptions = false;
|
||||
topupOptions.forEach((option) => {
|
||||
if (!option.isExtended) currentStackedStarsCount++;
|
||||
|
||||
if (starsNeeded && !forceShowAll && option.stars < starsNeeded) return;
|
||||
if (!areOptionsExtended && option.isExtended) {
|
||||
canExtendOptions = true;
|
||||
return;
|
||||
}
|
||||
result.push({
|
||||
option,
|
||||
starsCount: Math.min(currentStackedStarsCount, MAX_STARS_COUNT),
|
||||
});
|
||||
});
|
||||
|
||||
return [result, canExtendOptions];
|
||||
}, [areOptionsExtended, topupOptions, starsNeeded]);
|
||||
|
||||
const tosText = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
const text = lang('lng_credits_summary_options_about');
|
||||
const text = oldLang('lng_credits_summary_options_about');
|
||||
const parts = text.split('{link}');
|
||||
return [
|
||||
parts[0],
|
||||
<SafeLink url={lang('StarsTOSLink')} text={lang('lng_credits_summary_options_about_link')} />,
|
||||
<SafeLink url={oldLang('StarsTOSLink')} text={oldLang('lng_credits_summary_options_about_link')} />,
|
||||
parts[1],
|
||||
];
|
||||
}, [isOpen, lang]);
|
||||
}, [isOpen, oldLang]);
|
||||
|
||||
function handleScroll(e: React.UIEvent<HTMLDivElement>) {
|
||||
const { scrollTop } = e.currentTarget;
|
||||
@ -135,12 +105,27 @@ const StarsBalanceModal = ({
|
||||
});
|
||||
});
|
||||
|
||||
function renderStarOptionList() {
|
||||
return (
|
||||
<StarTopupOptionList
|
||||
isActive={isOpen}
|
||||
options={topupOptions}
|
||||
starsNeeded={starsNeeded}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const handleLoadMore = useLastCallback(() => {
|
||||
loadStarsTransactions({
|
||||
type: TRANSACTION_TYPES[selectedTabIndex],
|
||||
});
|
||||
});
|
||||
|
||||
const openPremiumGiftingModalHandler = useLastCallback(() => {
|
||||
openStarsGiftingModal({});
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal className={styles.root} isOpen={isOpen} onClose={closeStarsBalanceModal}>
|
||||
<div className={buildClassName(styles.main, 'custom-scroll')} onScroll={handleScroll}>
|
||||
@ -158,29 +143,31 @@ const StarsBalanceModal = ({
|
||||
<BalanceBlock balance={balance || 0} className={styles.modalBalance} />
|
||||
<div className={buildClassName(styles.header, isHeaderHidden && styles.hiddenHeader)}>
|
||||
<h2 className={styles.starHeaderText}>
|
||||
{lang('TelegramStars')}
|
||||
{oldLang('TelegramStars')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className={styles.section}>
|
||||
<img className={styles.logo} src={StarLogo} alt="" draggable={false} />
|
||||
<img className={styles.logoBackground} src={StarsBackground} alt="" draggable={false} />
|
||||
<h2 className={styles.headerText}>
|
||||
{starsNeeded ? lang('StarsNeededTitle', starsNeeded) : lang('TelegramStars')}
|
||||
{starsNeeded ? oldLang('StarsNeededTitle', starsNeeded) : oldLang('TelegramStars')}
|
||||
</h2>
|
||||
<div className={styles.description}>
|
||||
{renderText(
|
||||
starsNeeded ? lang('StarsNeededText', originBotName) : lang('TelegramStarsInfo'),
|
||||
starsNeeded ? oldLang('StarsNeededText', originBotName) : oldLang('TelegramStarsInfo'),
|
||||
['simple_markdown', 'emoji'],
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.options}>
|
||||
{renderingOptions?.map(({ option, starsCount }) => (
|
||||
<StarTopupOption option={option} starsCount={starsCount} onClick={handleClick} />
|
||||
))}
|
||||
{!areOptionsExtended && canExtend && (
|
||||
<Button className={styles.moreOptions} isText noForcedUpperCase onClick={markOptionsExtended}>
|
||||
{lang('Stars.Purchase.ShowMore')}
|
||||
<Icon className={styles.iconDown} name="down" />
|
||||
{renderStarOptionList()}
|
||||
{canBuyPremium && (
|
||||
<Button
|
||||
className={buildClassName(styles.starButton, 'settings-main-menu-star')}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={openPremiumGiftingModalHandler}
|
||||
>
|
||||
<StarIcon className="icon" type="gold" size="big" />
|
||||
{oldLang('TelegramStarsGift')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -189,13 +176,7 @@ const StarsBalanceModal = ({
|
||||
{tosText}
|
||||
</div>
|
||||
{shouldShowTransactions && (
|
||||
<>
|
||||
<TabList
|
||||
big
|
||||
activeTab={selectedTabIndex}
|
||||
tabs={TRANSACTION_TABS}
|
||||
onSwitchTab={setSelectedTabIndex}
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.section}>
|
||||
<Transition
|
||||
name={lang.isRtl ? 'slideOptimizedRtl' : 'slideOptimized'}
|
||||
@ -219,38 +200,19 @@ const StarsBalanceModal = ({
|
||||
</InfiniteScroll>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
<TabList
|
||||
big
|
||||
activeTab={selectedTabIndex}
|
||||
tabs={TRANSACTION_TABS}
|
||||
onSwitchTab={setSelectedTabIndex}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
function StarTopupOption({
|
||||
option, starsCount, onClick,
|
||||
}: {
|
||||
option: ApiStarTopupOption; starsCount: number; onClick?: (option: ApiStarTopupOption) => void;
|
||||
}) {
|
||||
const lang = useOldLang();
|
||||
|
||||
return (
|
||||
<div className={styles.option} key={option.stars} onClick={() => onClick?.(option)}>
|
||||
<div className={styles.optionTop}>
|
||||
+{formatInteger(option.stars)}
|
||||
{/* Switch directionality for correct order. Can't use flex because https://issues.chromium.org/issues/40249030 */}
|
||||
<div className={styles.stackedStars} dir={lang.isRtl ? 'ltr' : 'rtl'}>
|
||||
{Array.from({ length: starsCount }).map(() => (
|
||||
<StarIcon className={styles.stackedStar} type="gold" size="big" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.optionBottom}>
|
||||
{formatCurrency(option.amount, option.currency, lang.code)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { modal }): StateProps => {
|
||||
const botId = modal?.originPayment?.botId;
|
||||
@ -259,6 +221,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
return {
|
||||
starsBalanceState: global.stars,
|
||||
originPaymentBot: bot,
|
||||
canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
|
||||
};
|
||||
},
|
||||
)(StarsBalanceModal));
|
||||
|
||||
@ -51,7 +51,7 @@ export const MEDIA_PROGRESSIVE_CACHE_DISABLED = false;
|
||||
export const MEDIA_PROGRESSIVE_CACHE_NAME = 'tt-media-progressive';
|
||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v38';
|
||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v39';
|
||||
export const ASSET_CACHE_NAME = 'tt-assets';
|
||||
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
||||
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
||||
|
||||
@ -511,7 +511,63 @@ addActionHandler('closePremiumGiftingModal', (global, actions, payload): ActionR
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openGiftPremiumModal', async (global, actions, payload): Promise<void> => {
|
||||
addActionHandler('openStarsGiftingModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
global = updateTabState(global, {
|
||||
starsGiftingModal: {
|
||||
isOpen: true,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsGiftingModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
starsGiftingModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openStarGiftInfoModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
toUserId,
|
||||
stars,
|
||||
date,
|
||||
tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
|
||||
if (!stars || !toUserId || !date) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
|
||||
global = updateTabState(global, {
|
||||
starGiftInfoModal: {
|
||||
toUserId,
|
||||
stars,
|
||||
date,
|
||||
isOpen: true,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarGiftInfoModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
|
||||
return updateTabState(global, {
|
||||
starGiftInfoModal: undefined,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openPremiumGiftModal', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
forUserIds, tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
@ -524,7 +580,7 @@ addActionHandler('openGiftPremiumModal', async (global, actions, payload): Promi
|
||||
const gifts = await callApi('getPremiumGiftCodeOptions', {});
|
||||
|
||||
global = updateTabState(global, {
|
||||
giftPremiumModal: {
|
||||
giftModal: {
|
||||
isOpen: true,
|
||||
forUserIds,
|
||||
gifts,
|
||||
@ -533,10 +589,35 @@ addActionHandler('openGiftPremiumModal', async (global, actions, payload): Promi
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('closeGiftPremiumModal', (global, actions, payload): ActionReturnType => {
|
||||
addActionHandler('closePremiumGiftModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
global = updateTabState(global, {
|
||||
giftPremiumModal: { isOpen: false },
|
||||
giftModal: { isOpen: false },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('openStarsGiftModal', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
forUserId, tabId = getCurrentTabId(),
|
||||
} = payload || {};
|
||||
|
||||
const starsGiftOptions = await callApi('getStarsGiftOptions', {});
|
||||
|
||||
global = updateTabState(global, {
|
||||
starsGiftModal: {
|
||||
isOpen: true,
|
||||
forUserId,
|
||||
starsGiftOptions,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('closeStarsGiftModal', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
global = updateTabState(global, {
|
||||
starsGiftModal: { isOpen: false },
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
@ -32,12 +32,12 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
if (!inputInvoice.userIds) {
|
||||
return;
|
||||
}
|
||||
const giftModalState = selectTabState(global, tabId).giftPremiumModal;
|
||||
const giftModalState = selectTabState(global, tabId).giftModal;
|
||||
|
||||
if (giftModalState && giftModalState.isOpen
|
||||
&& areDeepEqual(inputInvoice.userIds, giftModalState.forUserIds)) {
|
||||
global = updateTabState(global, {
|
||||
giftPremiumModal: {
|
||||
giftModal: {
|
||||
...giftModalState,
|
||||
isCompleted: true,
|
||||
},
|
||||
@ -46,6 +46,24 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
}
|
||||
}
|
||||
|
||||
if (inputInvoice?.type === 'starsgift') {
|
||||
if (!inputInvoice.userId) {
|
||||
return;
|
||||
}
|
||||
const starsModalState = selectTabState(global, tabId).starsGiftModal;
|
||||
|
||||
if (starsModalState && starsModalState.isOpen
|
||||
&& areDeepEqual(inputInvoice.userId, starsModalState.forUserId)) {
|
||||
global = updateTabState(global, {
|
||||
starsGiftModal: {
|
||||
...starsModalState,
|
||||
isCompleted: true,
|
||||
},
|
||||
}, tabId);
|
||||
global = closeInvoice(global, tabId);
|
||||
}
|
||||
}
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
|
||||
@ -12,6 +12,26 @@ export function getRequestInputInvoice<T extends GlobalState>(
|
||||
): ApiRequestInputInvoice | undefined {
|
||||
if (inputInvoice.type === 'slug') return inputInvoice;
|
||||
|
||||
if (inputInvoice.type === 'starsgift') {
|
||||
const {
|
||||
userId, stars, amount, currency,
|
||||
} = inputInvoice;
|
||||
const user = selectUser(global, userId!);
|
||||
|
||||
if (!user) return undefined;
|
||||
|
||||
return {
|
||||
type: 'stars',
|
||||
purpose: {
|
||||
type: 'starsgift',
|
||||
user,
|
||||
stars,
|
||||
amount,
|
||||
currency,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (inputInvoice.type === 'stars') {
|
||||
const {
|
||||
stars, amount, currency,
|
||||
|
||||
@ -61,7 +61,7 @@ import type {
|
||||
ApiSendMessageAction,
|
||||
ApiSession,
|
||||
ApiSessionData,
|
||||
ApiSponsoredMessage,
|
||||
ApiSponsoredMessage, ApiStarsGiftOption,
|
||||
ApiStarsTransaction,
|
||||
ApiStarTopupOption,
|
||||
ApiStealthMode,
|
||||
@ -709,11 +709,30 @@ export type TabState = {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
giftPremiumModal?: {
|
||||
starsGiftingModal?: {
|
||||
isOpen?: boolean;
|
||||
};
|
||||
|
||||
starGiftInfoModal?: {
|
||||
isOpen?: boolean;
|
||||
toUserId: string;
|
||||
date: number;
|
||||
stars: number;
|
||||
};
|
||||
|
||||
starsGiftModal?: {
|
||||
isCompleted?: boolean;
|
||||
isOpen?: boolean;
|
||||
forUserId?: string;
|
||||
starsGiftOptions?: ApiStarsGiftOption[];
|
||||
};
|
||||
|
||||
giftModal?: {
|
||||
isCompleted?: boolean;
|
||||
isOpen?: boolean;
|
||||
forUserIds?: string[];
|
||||
gifts?: ApiPremiumGiftCodeOption[];
|
||||
starsGiftOptions?: ApiStarsGiftOption[];
|
||||
};
|
||||
|
||||
limitReachedModal?: {
|
||||
@ -3162,6 +3181,9 @@ export interface ActionPayloads {
|
||||
openPremiumGiftingModal: WithTabId | undefined;
|
||||
closePremiumGiftingModal: WithTabId | undefined;
|
||||
|
||||
openStarsGiftingModal: WithTabId | undefined;
|
||||
closeStarsGiftingModal: WithTabId | undefined;
|
||||
|
||||
openDeleteMessageModal: ({
|
||||
message?: ApiMessage;
|
||||
isSchedule?: boolean;
|
||||
@ -3179,12 +3201,26 @@ export interface ActionPayloads {
|
||||
loadDefaultTopicIcons: undefined;
|
||||
loadPremiumStickers: undefined;
|
||||
|
||||
openGiftPremiumModal: ({
|
||||
openPremiumGiftModal: ({
|
||||
chatId?: string;
|
||||
forMultipleUsers?: boolean;
|
||||
forUserIds?: string[];
|
||||
isStarsGifting?: boolean;
|
||||
} & WithTabId) | undefined;
|
||||
closeGiftPremiumModal: WithTabId | undefined;
|
||||
closePremiumGiftModal: WithTabId | undefined;
|
||||
|
||||
openStarsGiftModal: ({
|
||||
chatId?: string;
|
||||
forUserId?: string;
|
||||
} & WithTabId) | undefined;
|
||||
closeStarsGiftModal: WithTabId | undefined;
|
||||
|
||||
openStarGiftInfoModal: ({
|
||||
toUserId?: string;
|
||||
stars?: number;
|
||||
date?: number;
|
||||
} & WithTabId) | undefined;
|
||||
closeStarGiftInfoModal: WithTabId | undefined;
|
||||
|
||||
setEmojiStatus: {
|
||||
emojiStatus: ApiSticker;
|
||||
|
||||
11
src/types/language.d.ts
vendored
11
src/types/language.d.ts
vendored
@ -1514,7 +1514,16 @@ export interface LangPair {
|
||||
'ReplyInPrivateMessage': undefined;
|
||||
'AriaSearchOlderResult': undefined;
|
||||
'AriaSearchNewerResult': undefined;
|
||||
|
||||
'CreditsBoxHistoryEntryGiftOutAbout': {
|
||||
'user': string | number;
|
||||
'link': string | number;
|
||||
};
|
||||
'CreditsBoxOutAbout': {
|
||||
'link': string | number;
|
||||
};
|
||||
'GiftStarsOutgoing': {
|
||||
'user': string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export type LangKey = keyof LangPair;
|
||||
|
||||
@ -91,6 +91,7 @@ async function loadFallbackPack() {
|
||||
if (!language) {
|
||||
updateLanguage(fallbackData.language);
|
||||
} else {
|
||||
translationFn = createTranslationFn();
|
||||
scheduleCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user