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