From 10009ce9baf41a753b9baf5f9226f5b0fe3b4d4b Mon Sep 17 00:00:00 2001
From: zubiden <19638254+zubiden@users.noreply.github.com>
Date: Mon, 27 Jan 2025 23:50:53 +0100
Subject: [PATCH] Gifts: Support channels (#5527)
---
src/api/gramjs/apiBuilders/gifts.ts | 25 +-
src/api/gramjs/apiBuilders/messages.ts | 47 ++-
src/api/gramjs/apiBuilders/peers.ts | 5 +-
src/api/gramjs/gramjsBuilders/index.ts | 28 +-
src/api/gramjs/methods/chats.ts | 4 +
src/api/gramjs/methods/payments.ts | 41 ++-
src/api/types/chats.ts | 2 +
src/api/types/messages.ts | 157 +--------
src/api/types/payments.ts | 171 +++++++++-
src/api/types/users.ts | 6 +-
src/assets/localization/fallback.strings | 41 ++-
...Gift.module.scss => SavedGift.module.scss} | 0
.../gift/{UserGift.tsx => SavedGift.tsx} | 24 +-
src/components/middle/ActionMessage.tsx | 48 ++-
src/components/modals/gift/GiftComposer.tsx | 60 ++--
src/components/modals/gift/GiftModal.tsx | 53 +--
.../modals/gift/StarGiftCategoryList.tsx | 2 +-
.../gift/info/GiftInfoModal.module.scss | 15 +
.../modals/gift/info/GiftInfoModal.tsx | 196 +++++++----
.../modals/gift/upgrade/GiftUpgradeModal.tsx | 7 +-
.../modals/stars/StarsBalanceModal.tsx | 10 +-
src/components/right/Profile.tsx | 20 +-
src/global/actions/api/payments.ts | 18 +-
src/global/actions/api/stars.ts | 58 ++--
src/global/actions/apiUpdaters/misc.ts | 7 +-
src/global/actions/apiUpdaters/payments.ts | 2 +-
src/global/actions/ui/stars.ts | 11 +-
src/global/cache.ts | 17 +-
src/global/helpers/messages.ts | 12 +-
src/global/helpers/payments.ts | 43 ++-
src/global/initialState.ts | 7 +-
src/global/reducers/peers.ts | 14 +-
src/global/reducers/users.ts | 16 +-
src/global/selectors/peers.ts | 18 +-
src/global/selectors/users.ts | 9 -
src/global/types/actions.ts | 19 +-
src/global/types/globalState.ts | 9 +-
src/global/types/tabState.ts | 12 +-
src/lib/gramjs/tl/AllTLObjects.ts | 2 +-
src/lib/gramjs/tl/api.d.ts | 322 ++++++++++++------
src/lib/gramjs/tl/apiTl.ts | 51 +--
src/lib/gramjs/tl/static/api.json | 2 +-
src/lib/gramjs/tl/static/api.tl | 62 ++--
src/types/index.ts | 2 +-
src/types/language.d.ts | 63 ++--
45 files changed, 1075 insertions(+), 663 deletions(-)
rename src/components/common/gift/{UserGift.module.scss => SavedGift.module.scss} (100%)
rename src/components/common/gift/{UserGift.tsx => SavedGift.tsx} (86%)
diff --git a/src/api/gramjs/apiBuilders/gifts.ts b/src/api/gramjs/apiBuilders/gifts.ts
index e0307d4e2..d5fa6d2d0 100644
--- a/src/api/gramjs/apiBuilders/gifts.ts
+++ b/src/api/gramjs/apiBuilders/gifts.ts
@@ -1,29 +1,31 @@
import { Api as GramJs } from '../../../lib/gramjs';
import type {
+ ApiInputSavedStarGift,
+ ApiSavedStarGift,
ApiStarGift,
ApiStarGiftAttribute,
- ApiUserStarGift,
} from '../../types';
import { numberToHexColor } from '../../../util/colors';
import { addDocumentToLocalDb } from '../helpers';
import { buildApiFormattedText } from './common';
-import { buildApiPeerId } from './peers';
+import { getApiChatIdFromMtpPeer } from './peers';
import { buildStickerFromDocument } from './symbols';
export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
if (starGift instanceof GramJs.StarGiftUnique) {
const {
- id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug,
+ id, num, ownerId, ownerName, title, attributes, availabilityIssued, availabilityTotal, slug, ownerAddress,
} = starGift;
return {
type: 'starGiftUnique',
id: id.toString(),
number: num,
- ownerId: ownerId && buildApiPeerId(ownerId, 'user'),
+ ownerId: ownerId && getApiChatIdFromMtpPeer(ownerId),
ownerName,
+ ownerAddress,
attributes: attributes.map(buildApiStarGiftAttribute).filter(Boolean),
title,
totalCount: availabilityTotal,
@@ -115,25 +117,30 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
return {
type: 'originalDetails',
date,
- recipientId: recipientId && buildApiPeerId(recipientId, 'user'),
+ recipientId: recipientId && getApiChatIdFromMtpPeer(recipientId),
message: message && buildApiFormattedText(message),
- senderId: senderId && buildApiPeerId(senderId, 'user'),
+ senderId: senderId && getApiChatIdFromMtpPeer(senderId),
};
}
return undefined;
}
-export function buildApiUserStarGift(userStarGift: GramJs.UserStarGift): ApiUserStarGift {
+export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
const {
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
+ savedId,
} = userStarGift;
+ const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
+ ? { type: 'chat', chatId: peerId, savedId: savedId.toString() }
+ : msgId ? { type: 'user', messageId: msgId } : undefined;
+
return {
gift: buildApiStarGift(gift),
date,
starsToConvert: convertStars?.toJSNumber(),
- fromId: fromId && buildApiPeerId(fromId, 'user'),
+ fromId: fromId && getApiChatIdFromMtpPeer(fromId),
message: message && buildApiFormattedText(message),
messageId: msgId,
isNameHidden: nameHidden,
@@ -141,5 +148,7 @@ export function buildApiUserStarGift(userStarGift: GramJs.UserStarGift): ApiUser
canUpgrade,
alreadyPaidUpgradeStars: upgradeStars?.toJSNumber(),
transferStars: transferStars?.toJSNumber(),
+ inputGift,
+ savedId: savedId?.toString(),
};
}
diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts
index 10c0f8d97..4c4d7c838 100644
--- a/src/api/gramjs/apiBuilders/messages.ts
+++ b/src/api/gramjs/apiBuilders/messages.ts
@@ -11,6 +11,7 @@ import type {
ApiGroupCall,
ApiInputMessageReplyInfo,
ApiInputReplyInfo,
+ ApiInputSavedStarGift,
ApiKeyboardButton,
ApiMessage,
ApiMessageActionStarGift,
@@ -193,7 +194,7 @@ export function buildApiMessageWithChatId(
: isSavedOutgoing;
const content = buildMessageContent(mtpMessage);
const action = mtpMessage.action
- && buildAction(mtpMessage.action, fromId, peerId, Boolean(mtpMessage.post), isOutgoing);
+ && buildAction(mtpMessage.action, mtpMessage.id, fromId, peerId, Boolean(mtpMessage.post), isOutgoing);
if (action) {
content.action = action;
}
@@ -369,16 +370,29 @@ export function buildApiFactCheck(factCheck: GramJs.FactCheck): ApiFactCheck {
};
}
-function buildApiMessageActionStarGift(action: GramJs.MessageActionStarGift) : ApiMessageActionStarGift {
+function buildApiMessageActionStarGift(
+ action: GramJs.MessageActionStarGift, messageId: number,
+): ApiMessageActionStarGift {
const {
nameHidden, saved, converted, gift, message, convertStars, canUpgrade, upgraded, upgradeMsgId, upgradeStars,
+ peer, savedId, fromId,
} = action;
+ const inputSavedGift: ApiInputSavedStarGift = savedId && peer ? {
+ type: 'chat',
+ chatId: getApiChatIdFromMtpPeer(peer),
+ savedId: savedId.toString(),
+ } : {
+ type: 'user',
+ messageId,
+ };
+
return {
type: 'starGift',
isNameHidden: Boolean(nameHidden),
isSaved: Boolean(saved),
isConverted: converted,
+ fromId: fromId && getApiChatIdFromMtpPeer(fromId),
gift: buildApiStarGift(gift) as ApiStarGiftRegular,
message: message && buildApiFormattedText(message),
starsToConvert: convertStars?.toJSNumber(),
@@ -386,16 +400,28 @@ function buildApiMessageActionStarGift(action: GramJs.MessageActionStarGift) : A
isUpgraded: upgraded,
upgradeMsgId,
alreadyPaidUpgradeStars: upgradeStars?.toJSNumber(),
+ peerId: peer && getApiChatIdFromMtpPeer(peer),
+ savedId: savedId?.toString(),
+ inputSavedGift,
};
}
function buildApiMessageActionStarGiftUnique(
- action: GramJs.MessageActionStarGiftUnique,
+ action: GramJs.MessageActionStarGiftUnique, messageId: number,
): ApiMessageActionStarGiftUnique {
const {
- gift, canExportAt, refunded, saved, transferStars, transferred, upgrade,
+ gift, canExportAt, refunded, saved, transferStars, transferred, upgrade, fromId, peer, savedId,
} = action;
+ const inputSavedGift: ApiInputSavedStarGift = savedId && peer ? {
+ type: 'chat',
+ chatId: getApiChatIdFromMtpPeer(peer),
+ savedId: savedId.toString(),
+ } : {
+ type: 'user',
+ messageId,
+ };
+
return {
type: 'starGiftUnique',
gift: buildApiStarGift(gift) as ApiStarGiftUnique,
@@ -405,11 +431,16 @@ function buildApiMessageActionStarGiftUnique(
transferStars: transferStars?.toJSNumber(),
isTransferred: transferred,
isUpgrade: upgrade,
+ fromId: fromId && getApiChatIdFromMtpPeer(fromId),
+ peerId: peer && getApiChatIdFromMtpPeer(peer),
+ savedId: savedId?.toString(),
+ inputSavedGift,
};
}
function buildAction(
action: GramJs.TypeMessageAction,
+ messageId: number,
senderId: string | undefined,
targetPeerId: string | undefined,
isChannelPost: boolean,
@@ -738,7 +769,7 @@ function buildAction(
transactionId = action.transactionId;
} else if (action instanceof GramJs.MessageActionStarGift && action.gift instanceof GramJs.StarGift) {
type = 'starGift';
- starGift = buildApiMessageActionStarGift(action);
+ starGift = buildApiMessageActionStarGift(action, messageId);
if (isOutgoing) {
text = 'ActionGiftOutbound';
translationValues.push('%gift_payment_amount%');
@@ -763,9 +794,11 @@ function buildAction(
translationValues.push('%action_origin_chat%');
}
- starGift = buildApiMessageActionStarGiftUnique(action);
+ starGift = buildApiMessageActionStarGiftUnique(action, messageId);
- if (targetPeerId) {
+ if (action.peer) {
+ targetChatId = getApiChatIdFromMtpPeer(action.peer);
+ } else if (targetPeerId) {
targetUserIds.push(targetPeerId);
targetChatId = targetPeerId;
}
diff --git a/src/api/gramjs/apiBuilders/peers.ts b/src/api/gramjs/apiBuilders/peers.ts
index a0b25404b..6dc3da250 100644
--- a/src/api/gramjs/apiBuilders/peers.ts
+++ b/src/api/gramjs/apiBuilders/peers.ts
@@ -52,10 +52,11 @@ export function buildApiPeerColor(peerColor: GramJs.TypePeerColor): ApiPeerColor
export function buildApiEmojiStatus(mtpEmojiStatus: GramJs.TypeEmojiStatus): ApiEmojiStatus | undefined {
if (mtpEmojiStatus instanceof GramJs.EmojiStatus) {
- return { documentId: mtpEmojiStatus.documentId.toString() };
+ return { documentId: mtpEmojiStatus.documentId.toString(), until: mtpEmojiStatus.until };
}
- if (mtpEmojiStatus instanceof GramJs.EmojiStatusUntil) {
+ // TODO: Support other parameters
+ if (mtpEmojiStatus instanceof GramJs.EmojiStatusCollectible) {
return { documentId: mtpEmojiStatus.documentId.toString(), until: mtpEmojiStatus.until };
}
diff --git a/src/api/gramjs/gramjsBuilders/index.ts b/src/api/gramjs/gramjsBuilders/index.ts
index c5ad1f416..241cc026b 100644
--- a/src/api/gramjs/gramjsBuilders/index.ts
+++ b/src/api/gramjs/gramjsBuilders/index.ts
@@ -23,6 +23,7 @@ import type {
ApiReactionWithPaid,
ApiReportReason,
ApiRequestInputInvoice,
+ ApiRequestInputSavedStarGift,
ApiSendMessageAction,
ApiSticker,
ApiStory,
@@ -643,10 +644,10 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
case 'stargift': {
const {
- user, shouldHideName, giftId, message, shouldUpgrade,
+ peer, shouldHideName, giftId, message, shouldUpgrade,
} = invoice;
return new GramJs.InputInvoiceStarGift({
- userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
+ peer: buildInputPeer(peer.id, peer.accessHash),
hideName: shouldHideName || undefined,
giftId: BigInt(giftId),
message: message && buildInputTextWithEntities(message),
@@ -676,7 +677,7 @@ export function buildInputInvoice(invoice: ApiRequestInputInvoice) {
case 'stargiftUpgrade': {
return new GramJs.InputInvoiceStarGiftUpgrade({
- msgId: invoice.messageId,
+ stargift: buildInputSavedStarGift(invoice.inputSavedGift),
keepOriginalDetails: invoice.shouldKeepOriginalDetails,
});
}
@@ -732,15 +733,9 @@ export function buildInputEmojiStatus(emojiStatusId: string, expires?: number) {
return new GramJs.EmojiStatusEmpty();
}
- if (expires) {
- return new GramJs.EmojiStatusUntil({
- documentId: BigInt(emojiStatusId),
- until: expires,
- });
- }
-
return new GramJs.EmojiStatus({
documentId: BigInt(emojiStatusId),
+ until: expires,
});
}
@@ -845,3 +840,16 @@ export function buildInputPrivacyRules(
return privacyRules;
}
+
+export function buildInputSavedStarGift(inputGift: ApiRequestInputSavedStarGift) {
+ if (inputGift.type === 'user') {
+ return new GramJs.InputSavedStarGiftUser({
+ msgId: inputGift.messageId,
+ });
+ }
+
+ return new GramJs.InputSavedStarGiftChat({
+ peer: buildInputPeer(inputGift.chat.id, inputGift.chat.accessHash),
+ savedId: BigInt(inputGift.savedId),
+ });
+}
diff --git a/src/api/gramjs/methods/chats.ts b/src/api/gramjs/methods/chats.ts
index 6101ad0d3..5acc8c53f 100644
--- a/src/api/gramjs/methods/chats.ts
+++ b/src/api/gramjs/methods/chats.ts
@@ -608,6 +608,8 @@ async function getFullChannelInfo(
canViewRevenue: canViewMonetization,
paidReactionsAvailable,
hasScheduled,
+ stargiftsCount,
+ stargiftsAvailable,
} = result.fullChat;
if (chatPhoto) {
@@ -700,6 +702,8 @@ async function getFullChannelInfo(
botVerification: botVerification && buildApiBotVerification(botVerification),
isPaidReactionAvailable: paidReactionsAvailable,
hasScheduledMessages: hasScheduled,
+ starGiftCount: stargiftsCount,
+ areStarGiftsAvailable: Boolean(stargiftsAvailable),
},
chats,
userStatusesById: statusesById,
diff --git a/src/api/gramjs/methods/payments.ts b/src/api/gramjs/methods/payments.ts
index c8302989e..41d2e2980 100644
--- a/src/api/gramjs/methods/payments.ts
+++ b/src/api/gramjs/methods/payments.ts
@@ -6,16 +6,16 @@ import type {
ApiInputStorePaymentPurpose,
ApiPeer,
ApiRequestInputInvoice,
+ ApiRequestInputSavedStarGift,
ApiStarGiftRegular,
ApiThemeParameters,
- ApiUser,
} from '../../types';
import { DEBUG } from '../../../config';
import {
+ buildApiSavedStarGift,
buildApiStarGift,
buildApiStarGiftAttribute,
- buildApiUserStarGift,
} from '../apiBuilders/gifts';
import {
buildApiBoost,
@@ -37,7 +37,12 @@ import {
} from '../apiBuilders/payments';
import { buildApiPeerId } from '../apiBuilders/peers';
import {
- buildInputInvoice, buildInputPeer, buildInputStorePaymentPurpose, buildInputThemeParams, buildShippingInfo,
+ buildInputInvoice,
+ buildInputPeer,
+ buildInputSavedStarGift,
+ buildInputStorePaymentPurpose,
+ buildInputThemeParams,
+ buildShippingInfo,
} from '../gramjsBuilders';
import {
deserializeBytes,
@@ -440,17 +445,17 @@ export async function fetchStarGifts() {
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
}
-export async function fetchUserStarGifts({
- user,
+export async function fetchSavedStarGifts({
+ peer,
offset = '',
limit,
}: {
- user: ApiUser;
+ peer: ApiPeer;
offset?: string;
limit?: number;
}) {
- const result = await invokeRequest(new GramJs.payments.GetUserStarGifts({
- userId: buildInputPeer(user.id, user.accessHash),
+ const result = await invokeRequest(new GramJs.payments.GetSavedStarGifts({
+ peer: buildInputPeer(peer.id, peer.accessHash),
offset,
limit,
}));
@@ -459,7 +464,7 @@ export async function fetchUserStarGifts({
return undefined;
}
- const gifts = result.gifts.map(buildApiUserStarGift);
+ const gifts = result.gifts.map((g) => buildApiSavedStarGift(g, peer.id));
return {
gifts,
@@ -468,25 +473,25 @@ export async function fetchUserStarGifts({
}
export function saveStarGift({
- messageId,
+ inputGift,
shouldUnsave,
}: {
- messageId: number;
+ inputGift: ApiRequestInputSavedStarGift;
shouldUnsave?: boolean;
}) {
return invokeRequest(new GramJs.payments.SaveStarGift({
- msgId: messageId,
+ stargift: buildInputSavedStarGift(inputGift),
unsave: shouldUnsave || undefined,
}));
}
export function convertStarGift({
- messageId,
+ inputSavedGift,
}: {
- messageId: number;
+ inputSavedGift: ApiRequestInputSavedStarGift;
}) {
return invokeRequest(new GramJs.payments.ConvertStarGift({
- msgId: messageId,
+ stargift: buildInputSavedStarGift(inputSavedGift),
}));
}
@@ -671,14 +676,14 @@ export async function fetchStarGiftUpgradePreview({
}
export function upgradeGift({
- messageId,
+ inputSavedGift,
shouldKeepOriginalDetails,
}: {
- messageId: number;
+ inputSavedGift: ApiRequestInputSavedStarGift;
shouldKeepOriginalDetails?: true;
}) {
return invokeRequest(new GramJs.payments.UpgradeStarGift({
- msgId: messageId,
+ stargift: buildInputSavedStarGift(inputSavedGift),
keepOriginalDetails: shouldKeepOriginalDetails,
}), {
shouldReturnTrue: true,
diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts
index 66ff41434..dc9df6b5c 100644
--- a/src/api/types/chats.ts
+++ b/src/api/types/chats.ts
@@ -141,6 +141,8 @@ export interface ApiChatFullInfo {
hasPinnedStories?: boolean;
isPaidReactionAvailable?: boolean;
hasScheduledMessages?: boolean;
+ starGiftCount?: number;
+ areStarGiftsAvailable?: boolean;
boostsApplied?: number;
boostsToUnrestrict?: number;
diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts
index d422e86f4..79cfd96a0 100644
--- a/src/api/types/messages.ts
+++ b/src/api/types/messages.ts
@@ -1,18 +1,16 @@
import type { ThreadId, WebPageMediaSize } from '../../types';
import type { ApiWebDocument } from './bots';
import type { ApiGroupCall, PhoneCallAction } from './calls';
-import type { ApiChat, ApiPeerColor } from './chats';
+import type { ApiPeerColor } from './chats';
import type {
- ApiInputStorePaymentPurpose,
+ ApiInputSavedStarGift,
ApiLabeledPrice,
- ApiPremiumGiftCodeOption,
ApiStarGiftRegular,
ApiStarGiftUnique,
} from './payments';
import type {
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
} from './stories';
-import type { ApiUser } from './users';
export interface ApiDimensions {
width: number;
@@ -207,149 +205,6 @@ export interface ApiPoll {
};
}
-/* Used for Invoice UI */
-export type ApiInputInvoiceMessage = {
- type: 'message';
- chatId: string;
- messageId: number;
- isExtendedMedia?: boolean;
-};
-
-export type ApiInputInvoiceSlug = {
- type: 'slug';
- slug: string;
-};
-
-export type ApiInputInvoiceGiveaway = {
- type: 'giveaway';
- chatId: string;
- additionalChannelIds?: string[];
- isOnlyForNewSubscribers?: boolean;
- areWinnersVisible?: boolean;
- prizeDescription?: string;
- countries?: string[];
- untilDate: number;
- currency: string;
- amount: number;
- option: ApiPremiumGiftCodeOption;
-};
-
-export type ApiInputInvoiceGiftCode = {
- type: 'giftcode';
- userIds: string[];
- boostChannelId?: string;
- currency: string;
- amount: number;
- option: ApiPremiumGiftCodeOption;
- message?: ApiFormattedText;
-};
-
-export type ApiInputInvoiceStars = {
- type: 'stars';
- stars: number;
- currency: string;
- amount: number;
-};
-
-export type ApiInputInvoiceStarsGift = {
- type: 'starsgift';
- userId: string;
- stars: number;
- currency: string;
- amount: number;
-};
-
-export type ApiInputInvoiceStarGift = {
- type: 'stargift';
- shouldHideName?: boolean;
- userId: string;
- giftId: string;
- message?: ApiFormattedText;
- shouldUpgrade?: true;
-};
-
-export type ApiInputInvoiceStarsGiveaway = {
- type: 'starsgiveaway';
- chatId: string;
- additionalChannelIds?: string[];
- isOnlyForNewSubscribers?: boolean;
- areWinnersVisible?: boolean;
- prizeDescription?: string;
- countries?: string[];
- untilDate: number;
- currency: string;
- amount: number;
- stars: number;
- users: number;
-};
-
-export type ApiInputInvoiceChatInviteSubscription = {
- type: 'chatInviteSubscription';
- hash: string;
-};
-
-export type ApiInputInvoiceStarGiftUpgrade = {
- type: 'stargiftUpgrade';
- messageId: number;
- shouldKeepOriginalDetails?: true;
-};
-
-export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
-| ApiInputInvoiceGiftCode | ApiInputInvoiceStars | ApiInputInvoiceStarsGift | ApiInputInvoiceStarGiftUpgrade
-| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription;
-
-/* Used for Invoice request */
-export type ApiRequestInputInvoiceMessage = {
- type: 'message';
- chat: ApiChat;
- messageId: number;
-};
-
-export type ApiRequestInputInvoiceSlug = {
- type: 'slug';
- slug: string;
-};
-
-export type ApiRequestInputInvoiceGiveaway = {
- type: 'giveaway';
- purpose: ApiInputStorePaymentPurpose;
- option: ApiPremiumGiftCodeOption;
-};
-
-export type ApiRequestInputInvoiceStars = {
- type: 'stars';
- purpose: ApiInputStorePaymentPurpose;
-};
-
-export type ApiRequestInputInvoiceStarsGiveaway = {
- type: 'starsgiveaway';
- purpose: ApiInputStorePaymentPurpose;
-};
-
-export type ApiRequestInputInvoiceStarGift = {
- type: 'stargift';
- shouldHideName?: boolean;
- user: ApiUser;
- giftId: string;
- message?: ApiFormattedText;
- shouldUpgrade?: true;
-};
-
-export type ApiRequestInputInvoiceChatInviteSubscription = {
- type: 'chatInviteSubscription';
- hash: string;
-};
-
-export type ApiRequestInputInvoiceStarGiftUpgrade = {
- type: 'stargiftUpgrade';
- messageId: number;
- shouldKeepOriginalDetails?: true;
-};
-
-export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
-| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway
-| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift | ApiRequestInputInvoiceStarGiftUpgrade;
-
export interface ApiInvoice {
prices: ApiLabeledPrice[];
totalAmount: number;
@@ -485,6 +340,10 @@ export interface ApiMessageActionStarGift {
isUpgraded?: true;
upgradeMsgId?: number;
alreadyPaidUpgradeStars?: number;
+ fromId?: string;
+ peerId?: string;
+ savedId?: string;
+ inputSavedGift?: ApiInputSavedStarGift;
}
export interface ApiMessageActionStarGiftUnique {
@@ -496,6 +355,10 @@ export interface ApiMessageActionStarGiftUnique {
gift: ApiStarGiftUnique;
canExportAt?: number;
transferStars?: number;
+ fromId?: string;
+ peerId?: string;
+ savedId?: string;
+ inputSavedGift?: ApiInputSavedStarGift;
}
export interface ApiAction {
diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts
index 33aa30298..bf62bb3c2 100644
--- a/src/api/types/payments.ts
+++ b/src/api/types/payments.ts
@@ -1,6 +1,6 @@
import type { PREMIUM_FEATURE_SECTIONS } from '../../config';
import type { ApiWebDocument } from './bots';
-import type { ApiChat } from './chats';
+import type { ApiChat, ApiPeer } from './chats';
import type {
ApiDocument,
ApiFormattedText,
@@ -213,6 +213,7 @@ export interface ApiStarGiftUnique {
number: number;
ownerId?: string;
ownerName?: string;
+ ownerAddress?: string;
issuedCount: number;
totalCount: number;
attributes: ApiStarGiftAttribute[];
@@ -256,12 +257,14 @@ export interface ApiStarGiftAttributeOriginalDetails {
export type ApiStarGiftAttribute = ApiStarGiftAttributeModel | ApiStarGiftAttributePattern
| ApiStarGiftAttributeBackdrop | ApiStarGiftAttributeOriginalDetails;
-export interface ApiUserStarGift {
+export interface ApiSavedStarGift {
isNameHidden?: boolean;
isUnsaved?: boolean;
fromId?: string;
date: number;
gift: ApiStarGift;
+ inputGift?: ApiInputSavedStarGift;
+ savedId?: string;
message?: ApiFormattedText;
messageId?: number;
starsToConvert?: number;
@@ -272,6 +275,27 @@ export interface ApiUserStarGift {
upgradeMsgId?: number; // Local field, used for Action Message
}
+export interface ApiInputSavedStarGiftUser {
+ type: 'user';
+ messageId: number;
+}
+
+export interface ApiInputSavedStarGiftChat {
+ type: 'chat';
+ chatId: string;
+ savedId: string;
+}
+
+export type ApiInputSavedStarGift = ApiInputSavedStarGiftUser | ApiInputSavedStarGiftChat;
+
+export type ApiRequestInputSavedStarGiftUser = ApiInputSavedStarGiftUser;
+export type ApiRequestInputSavedStarGiftChat = {
+ type: 'chat';
+ chat: ApiChat;
+ savedId: string;
+};
+export type ApiRequestInputSavedStarGift = ApiRequestInputSavedStarGiftUser | ApiRequestInputSavedStarGiftChat;
+
export interface ApiPremiumGiftCodeOption {
users: number;
months: number;
@@ -476,3 +500,146 @@ export interface ApiStarGiveawayOption {
}
export type ApiPaymentStatus = 'paid' | 'failed' | 'pending' | 'cancelled';
+
+/* Used for Invoice UI */
+export type ApiInputInvoiceMessage = {
+ type: 'message';
+ chatId: string;
+ messageId: number;
+ isExtendedMedia?: boolean;
+};
+
+export type ApiInputInvoiceSlug = {
+ type: 'slug';
+ slug: string;
+};
+
+export type ApiInputInvoiceGiveaway = {
+ type: 'giveaway';
+ chatId: string;
+ additionalChannelIds?: string[];
+ isOnlyForNewSubscribers?: boolean;
+ areWinnersVisible?: boolean;
+ prizeDescription?: string;
+ countries?: string[];
+ untilDate: number;
+ currency: string;
+ amount: number;
+ option: ApiPremiumGiftCodeOption;
+};
+
+export type ApiInputInvoiceGiftCode = {
+ type: 'giftcode';
+ userIds: string[];
+ boostChannelId?: string;
+ currency: string;
+ amount: number;
+ option: ApiPremiumGiftCodeOption;
+ message?: ApiFormattedText;
+};
+
+export type ApiInputInvoiceStars = {
+ type: 'stars';
+ stars: number;
+ currency: string;
+ amount: number;
+};
+
+export type ApiInputInvoiceStarsGift = {
+ type: 'starsgift';
+ userId: string;
+ stars: number;
+ currency: string;
+ amount: number;
+};
+
+export type ApiInputInvoiceStarGift = {
+ type: 'stargift';
+ shouldHideName?: boolean;
+ peerId: string;
+ giftId: string;
+ message?: ApiFormattedText;
+ shouldUpgrade?: true;
+};
+
+export type ApiInputInvoiceStarsGiveaway = {
+ type: 'starsgiveaway';
+ chatId: string;
+ additionalChannelIds?: string[];
+ isOnlyForNewSubscribers?: boolean;
+ areWinnersVisible?: boolean;
+ prizeDescription?: string;
+ countries?: string[];
+ untilDate: number;
+ currency: string;
+ amount: number;
+ stars: number;
+ users: number;
+};
+
+export type ApiInputInvoiceChatInviteSubscription = {
+ type: 'chatInviteSubscription';
+ hash: string;
+};
+
+export type ApiInputInvoiceStarGiftUpgrade = {
+ type: 'stargiftUpgrade';
+ inputSavedGift: ApiInputSavedStarGift;
+ shouldKeepOriginalDetails?: true;
+};
+
+export type ApiInputInvoice = ApiInputInvoiceMessage | ApiInputInvoiceSlug | ApiInputInvoiceGiveaway
+| ApiInputInvoiceGiftCode | ApiInputInvoiceStars | ApiInputInvoiceStarsGift | ApiInputInvoiceStarGiftUpgrade
+| ApiInputInvoiceStarsGiveaway | ApiInputInvoiceStarGift | ApiInputInvoiceChatInviteSubscription;
+
+/* Used for Invoice request */
+export type ApiRequestInputInvoiceMessage = {
+ type: 'message';
+ chat: ApiChat;
+ messageId: number;
+};
+
+export type ApiRequestInputInvoiceSlug = {
+ type: 'slug';
+ slug: string;
+};
+
+export type ApiRequestInputInvoiceGiveaway = {
+ type: 'giveaway';
+ purpose: ApiInputStorePaymentPurpose;
+ option: ApiPremiumGiftCodeOption;
+};
+
+export type ApiRequestInputInvoiceStars = {
+ type: 'stars';
+ purpose: ApiInputStorePaymentPurpose;
+};
+
+export type ApiRequestInputInvoiceStarsGiveaway = {
+ type: 'starsgiveaway';
+ purpose: ApiInputStorePaymentPurpose;
+};
+
+export type ApiRequestInputInvoiceStarGift = {
+ type: 'stargift';
+ shouldHideName?: boolean;
+ peer: ApiPeer;
+ giftId: string;
+ message?: ApiFormattedText;
+ shouldUpgrade?: true;
+};
+
+export type ApiRequestInputInvoiceChatInviteSubscription = {
+ type: 'chatInviteSubscription';
+ hash: string;
+};
+
+export type ApiRequestInputInvoiceStarGiftUpgrade = {
+ type: 'stargiftUpgrade';
+ inputSavedGift: ApiRequestInputSavedStarGift;
+ shouldKeepOriginalDetails?: true;
+};
+
+export type ApiRequestInputInvoice = ApiRequestInputInvoiceMessage | ApiRequestInputInvoiceSlug
+| ApiRequestInputInvoiceGiveaway | ApiRequestInputInvoiceStars | ApiRequestInputInvoiceStarsGiveaway
+| ApiRequestInputInvoiceChatInviteSubscription | ApiRequestInputInvoiceStarGift | ApiRequestInputInvoiceStarGiftUpgrade;
diff --git a/src/api/types/users.ts b/src/api/types/users.ts
index 687332434..c8bfb32db 100644
--- a/src/api/types/users.ts
+++ b/src/api/types/users.ts
@@ -4,7 +4,7 @@ import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from
import type { ApiPeerColor } from './chats';
import type { ApiDocument, ApiPhoto } from './messages';
import type { ApiBotVerification } from './misc';
-import type { ApiUserStarGift } from './payments';
+import type { ApiSavedStarGift } from './payments';
export interface ApiUser {
id: string;
@@ -89,8 +89,8 @@ export interface ApiUserCommonChats {
isFullyLoaded: boolean;
}
-export interface ApiUserGifts {
- gifts: ApiUserStarGift[];
+export interface ApiSavedGifts {
+ gifts: ApiSavedStarGift[];
nextOffset?: string;
}
diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings
index 5586bf9ea..2b0f7a6dc 100644
--- a/src/assets/localization/fallback.strings
+++ b/src/assets/localization/fallback.strings
@@ -244,6 +244,7 @@
"SavedMessagesInfo" = "Forward here to save";
"BlockedListNotFound" = "No users found.";
"TextCopied" = "Text copied to clipboard.";
+"WalletAddressCopied" = "Wallet address copied to clipboard.";
"Copy" = "Copy";
"ChatListDeleteAndLeaveGroupConfirmation" = "Are you sure you want to leave and delete {chat}?";
"ChannelLeaveAlertWithName" = "Are you sure you want to leave **{chat}**?";
@@ -1361,6 +1362,7 @@
"StarsGiftHeaderSelf" = "Buy a Gift";
"StarGiftDescription" = "Give {user} gifts that can be kept on the profile or converted to Stars.";
"StarGiftDescriptionSelf" = "Buy yourself a gift to add to your profile or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others.";
+"StarGiftDescriptionChannel" = "Select gift to show appreciation to **{peer}**.";
"GiftLimited" = "limited";
"GiftDiscount" = "-{percent}%";
"GiftSoldCount" = "{count} sold";
@@ -1368,23 +1370,24 @@
"GiftSoldOut" = "sold out";
"GiftMessagePlaceholder" = "Enter Message (Optional)";
"GiftHideMyName" = "Hide My Name";
-"GiftHideNameDescription" = "Hide my name and message from visitors to {profile}'s profile. {receiver} will still see your name and message.";
+"GiftHideNameDescription" = "You can hide your name and message from visitors to {receiver}'s profile. {receiver} will still see your name and message.";
+"GiftHideNameDescriptionChannel" = "You can hide your name and message from all visitors of this channel except its admins.";
"GiftSend" = "Send a Gift for {amount}";
"GiftInfoSent" = "Sent Gift";
"GiftInfoReceived" = "Received Gift";
"GiftInfoTitle" = "Gift";
"GiftInfoDescription_one" = "You can keep this gift in your Profile or convert it to **{amount}** Star.";
"GiftInfoDescription_other" = "You can keep this gift in your Profile or convert it to **{amount}** Stars.";
-"GiftInfoDescriptionOut_one" = "{user} can keep this gift in profile or convert it to **{amount}** Star.";
-"GiftInfoDescriptionOut_other" = "{user} can keep this gift in profile or convert it to **{amount}** Stars.";
+"GiftInfoPeerDescriptionOut_one" = "{peer} can keep this gift in profile or convert it to **{amount}** Star.";
+"GiftInfoPeerDescriptionOut_other" = "{peer} can keep this gift in profile or convert it to **{amount}** Stars.";
"GiftInfoDescriptionUpgrade_one" = "You can keep this gift, upgrade it, or sell it for **{amount}** Star.";
"GiftInfoDescriptionUpgrade_other" = "You can keep this gift, upgrade it, or sell it for **{amount}** Stars.";
"GiftInfoDescriptionConverted_one" = "You converted this gift to **{amount}** Star.";
"GiftInfoDescriptionConverted_other" = "You converted this gift to **{amount}** Stars.";
-"GiftInfoDescriptionOutConverted_one" = "{user} converted this gift to **{amount}** Star.";
-"GiftInfoDescriptionOutConverted_other" = "{user} converted this gift to **{amount}** Stars.";
+"GiftInfoPeerDescriptionOutConverted_one" = "{peer} converted this gift to **{amount}** Star.";
+"GiftInfoPeerDescriptionOutConverted_other" = "{peer} converted this gift to **{amount}** Stars.";
"GiftInfoDescriptionFreeUpgrade" = "Upgrade this gift for free to turn it to a unique collectible.";
-"GiftInfoDescriptionFreeUpgradeOut" = "{user} can turn this gift to a unique collectible";
+"GiftInfoPeerDescriptionFreeUpgradeOut" = "{peer} can turn this gift to a unique collectible";
"GiftInfoDescriptionUpgraded" = "This gift was turned into a unique collectible.";
"GiftInfoFrom" = "From";
"GiftInfoDate" = "Date";
@@ -1392,14 +1395,16 @@
"GiftInfoConvert_one" = "Convert to {amount} Star";
"GiftInfoConvert_other" = "Convert to {amount} Stars";
"GiftInfoConvertTitle" = "Convert Gift to Stars";
-"GiftInfoConvertDescription1" = "Do you want to convert this gift from **{user}** to **{amount}**?";
+"GiftInfoPeerConvertDescription" = "Do you want to convert this gift from **{peer}** to **{amount}**?";
"GiftInfoConvertDescription2" = "This action cannot be undone. This will permanently destroy the gift.";
"GiftInfoConvertDescriptionPeriod_one" = "Conversion is available for the next **{count} days**.";
"GiftInfoConvertDescriptionPeriod_other" = "Conversion is available for the next **{count} days**.";
"GiftInfoSaved" = "This gift is visible on your profile. {link}";
+"GiftInfoHidden" = "This gift is hidden. Only you can see it. {link}";
+"GiftInfoChannelSaved" = "This gift is visible in your channel's Gifts. {link}";
+"GiftInfoChannelHidden" = "This gift is hidden from visitors of your channel. {link}";
"GiftInfoSavedHide" = "Hide >";
"GiftInfoSavedShow" = "Show >";
-"GiftInfoHidden" = "This gift is hidden. Only you can see it. {link}";
"GiftInfoAvailability" = "Availability";
"GiftInfoAvailabilityValue_one" = "{count} of {total} left";
"GiftInfoAvailabilityValue_other" = "{count} of {total} left";
@@ -1415,10 +1420,10 @@
"GiftAttributeModel" = "Model";
"GiftAttributeBackdrop" = "Backdrop";
"GiftAttributeSymbol" = "Symbol";
-"GiftInfoOriginalInfo" = "Gifted to {user} on {date}.";
-"GiftInfoOriginalInfoSender" = "Gifted by {sender} to {user} on {date}.";
-"GiftInfoOriginalInfoText" = "Gifted to {user} on {date} with comment \"{text}\".";
-"GiftInfoOriginalInfoTextSender" = "Gifted by {sender} to {user} on {date} with comment \"{text}\".";
+"GiftInfoPeerOriginalInfo" = "Gifted to {peer} on {date}.";
+"GiftInfoPeerOriginalInfoSender" = "Gifted by {sender} to {peer} on {date}.";
+"GiftInfoPeerOriginalInfoText" = "Gifted to {peer} on {date} with comment \"{text}\".";
+"GiftInfoPeerOriginalInfoTextSender" = "Gifted by {sender} to {peer} on {date} with comment \"{text}\".";
"GiftInfoStatus" = "Status";
"GiftInfoStatusNonUnique" = "Non-Unique";
"GiftInfoViewUpgraded" = "View Upgraded Gift";
@@ -1431,7 +1436,7 @@
"GiftUpgradeTradeableTitle" = "Tradable";
"GiftUpgradeTradeableDescription" = "Sell or auction your gift on third-party NFT marketplaces.";
"GiftUpgradeTitle" = "Make unique";
-"GiftUpgradeText" = "Let {peer} turn your gift into a unique collectible.";
+"GiftPeerUpgradeText" = "Let {peer} turn your gift into a unique collectible.";
"GiftUpgradeTextOwn" = "Turn your gift into a unique collectible that you can transfer or auction.";
"GiftUpgradeKeepDetails" = "Keep sender's name and comment";
"GiftUpgradeButton" = "Upgrade {amount}";
@@ -1440,6 +1445,7 @@
"GiftMakeUnique" = "Make unique for {stars}";
"GiftMakeUniqueAcc" = "Make unique";
"GiftMakeUniqueDescription" = "Enable this to let {user} turn your gift into a unique collectible. {link}";
+"GiftMakeUniqueDescriptionChannel" = "Enable this to let admins of {peer} to turn your gift into a unique collectible. {link}";
"GiftMakeUniqueLink" = "Learn More >";
"StarsAmount" = "⭐️{amount}";
"StarsAmountText_one" = "{amount} Star";
@@ -1457,14 +1463,15 @@
"MiniAppsMoreTabs_other" = "{botName} & {count} Others";
"PrizeCredits2_one" = "Your prize is {count} Star.";
"PrizeCredits2_other" = "Your prize is {count} Stars.";
-"ActionStarGiftTitle" = "{user} sent you a Gift for {count} Stars";
+"ActionStarGiftPeerTitle" = "{peer} sent you a Gift for {count} Stars";
"ActionStarGiftOutTitle" = "You have sent a gift for {count} Stars";
-"ActionStarGiftOutDescription2_one" = "{user} can display this gift on their profile or convert it to {count} Star.";
-"ActionStarGiftOutDescription2_other" = "{user} can display this gift on their profile or convert it to {count} Stars.";
+"ActionStarGiftPeerOutDescription_one" = "{peer} can display this gift on their profile or convert it to {count} Star.";
+"ActionStarGiftPeerOutDescription_other" = "{peer} can display this gift on their profile or convert it to {count} Stars.";
"ActionStarGiftDescription2_one" = "Add this gift to your profile or convert it to {count} Star.";
"ActionStarGiftDescription2_other" = "Add this gift to your profile or convert it to {count} Stars.";
"ActionStarGiftDisplaying" = "You kept this gift on your profile.";
-"ActionStarGiftOutDescriptionUpgrade" = "{user} can turn this gift to a unique collectible.";
+"ActionStarGiftChannelDisplaying" = "This gift is displayed to visitors of your channel.";
+"ActionStarGiftPeerOutDescriptionUpgrade" = "{peer} can turn this gift to a unique collectible.";
"ActionStarGiftDescriptionUpgrade" = "Tap “Unpack” to turn this gift to a unique collectible.";
"ActionStarGiftUpgraded" = "This gift was upgraded.";
"ActionStarGiftUnpack" = "Unpack";
diff --git a/src/components/common/gift/UserGift.module.scss b/src/components/common/gift/SavedGift.module.scss
similarity index 100%
rename from src/components/common/gift/UserGift.module.scss
rename to src/components/common/gift/SavedGift.module.scss
diff --git a/src/components/common/gift/UserGift.tsx b/src/components/common/gift/SavedGift.tsx
similarity index 86%
rename from src/components/common/gift/UserGift.tsx
rename to src/components/common/gift/SavedGift.tsx
index 49cbac0fc..88368fa2e 100644
--- a/src/components/common/gift/UserGift.tsx
+++ b/src/components/common/gift/SavedGift.tsx
@@ -1,9 +1,9 @@
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
-import type { ApiUser, ApiUserStarGift } from '../../../api/types';
+import type { ApiPeer, ApiSavedStarGift } from '../../../api/types';
-import { selectUser } from '../../../global/selectors';
+import { selectPeer } from '../../../global/selectors';
import { CUSTOM_PEER_HIDDEN } from '../../../util/objects/customPeer';
import { formatIntegerCompact } from '../../../util/textFormat';
import { getGiftAttributes, getStickerFromGift, getTotalGiftAvailability } from '../helpers/gifts';
@@ -19,22 +19,22 @@ import Icon from '../icons/Icon';
import RadialPatternBackground from '../profile/RadialPatternBackground';
import GiftRibbon from './GiftRibbon';
-import styles from './UserGift.module.scss';
+import styles from './SavedGift.module.scss';
type OwnProps = {
- userId: string;
- gift: ApiUserStarGift;
+ peerId: string;
+ gift: ApiSavedStarGift;
observeIntersection?: ObserveFn;
};
type StateProps = {
- fromPeer?: ApiUser;
+ fromPeer?: ApiPeer;
};
const GIFT_STICKER_SIZE = 90;
-const UserGift = ({
- userId,
+const SavedGift = ({
+ peerId,
gift,
fromPeer,
observeIntersection,
@@ -50,7 +50,7 @@ const UserGift = ({
const handleClick = useLastCallback(() => {
openGiftInfoModal({
- userId,
+ peerId,
gift,
});
});
@@ -92,7 +92,7 @@ const UserGift = ({
return (
{radialPatternBackdrop}
-
+ {!radialPatternBackdrop &&
}
(
(global, { gift }): StateProps => {
- const fromPeer = gift.fromId ? selectUser(global, gift.fromId) : undefined;
+ const fromPeer = gift.fromId ? selectPeer(global, gift.fromId) : undefined;
return {
fromPeer,
};
},
-)(UserGift));
+)(SavedGift));
diff --git a/src/components/middle/ActionMessage.tsx b/src/components/middle/ActionMessage.tsx
index 8f74c50b3..903dbe094 100644
--- a/src/components/middle/ActionMessage.tsx
+++ b/src/components/middle/ActionMessage.tsx
@@ -11,7 +11,9 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
import type { FocusDirection, MessageListType, ThreadId } from '../../types';
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
-import { getChatTitle, getMessageHtmlId, isJoinedChannelMessage } from '../../global/helpers';
+import {
+ getChatTitle, getMessageHtmlId, getPeerTitle, isJoinedChannelMessage,
+} from '../../global/helpers';
import { getMessageReplyInfo } from '../../global/helpers/replies';
import {
selectCanPlayAnimatedEmojis,
@@ -21,6 +23,7 @@ import {
selectGiftStickerForStars,
selectIsCurrentUserPremium,
selectIsMessageFocused,
+ selectPeer,
selectTabState,
selectTheme,
selectTopicFromMessage,
@@ -86,6 +89,7 @@ type StateProps = {
starsGiftSticker?: ApiSticker;
canPlayAnimatedEmojis?: boolean;
patternColor?: string;
+ currentUserId?: string;
isCurrentUserPremium?: boolean;
};
@@ -119,6 +123,7 @@ const ActionMessage: FC = ({
observeIntersectionForLoading,
observeIntersectionForPlaying,
onIntersectPinnedMessage,
+ currentUserId,
isCurrentUserPremium,
}) => {
const {
@@ -407,16 +412,24 @@ const ActionMessage: FC = ({
}
function renderStarGiftUserCaption() {
- const targetUser = targetUsers && targetUsers[0];
const starGift = message.content.action?.starGift;
- if (!targetUser || !senderUser || !starGift) return undefined;
+ if (!starGift) return undefined;
+ const { fromId, peerId } = starGift;
- if (message.isOutgoing || (starGift.type === 'starGiftUnique' && starGift.isUpgrade)) {
+ const fromPeer = fromId ? selectPeer(getGlobal(), fromId) : undefined;
+ const targetPeer = peerId
+ ? selectPeer(getGlobal(), peerId)
+ : starGift.type === 'starGiftUnique' && !message.isOutgoing
+ ? targetChat : undefined;
+
+ if (targetPeer && targetPeer.id !== currentUserId) {
return (
{lang('GiftTo')}
-
-
{targetUser.firstName}
+ {starGift.type === 'starGift' && (
+
+ )}
+
{getPeerTitle(lang, targetPeer)}
);
}
@@ -424,15 +437,17 @@ const ActionMessage: FC = ({
return (
{lang('GiftFrom')}
-
-
{senderUser.firstName}
+ {starGift.type === 'starGift' && (
+
+ )}
+
{getPeerTitle(lang, fromPeer || senderUser!)}
);
}
function renderStarGiftUserDescription() {
const starGift = message.content.action?.starGift as ApiMessageActionStarGift;
- const targetUser = targetUsers && targetUsers[0]?.firstName;
+ const targetChatTitle = targetChat && getPeerTitle(lang, targetChat);
const starGiftMessage = starGift?.message;
if (!starGift) return undefined;
@@ -442,7 +457,7 @@ const ActionMessage: FC = ({
const amountToConvert = starGift?.starsToConvert;
if (starGift.isSaved) {
- return lang('ActionStarGiftDisplaying');
+ return lang(starGift.savedId ? 'ActionStarGiftChannelDisplaying' : 'ActionStarGiftDisplaying');
}
if (starGift.isUpgraded) {
@@ -451,24 +466,24 @@ const ActionMessage: FC = ({
if (message.isOutgoing) {
if (amountToConvert) {
- return lang('ActionStarGiftOutDescription2', {
- user: targetUser || 'User',
+ return lang('ActionStarGiftPeerOutDescription', {
+ peer: targetChatTitle || 'Someone',
count: amountToConvert,
}, { withNodes: true, pluralValue: amountToConvert });
}
if (starGift.canUpgrade) {
- return lang('ActionStarGiftOutDescriptionUpgrade', {
- user: targetUser || 'User',
+ return lang('ActionStarGiftPeerOutDescriptionUpgrade', {
+ peer: targetChatTitle || 'Someone',
});
}
}
if (starGift.isConverted) {
return message.isOutgoing
- ? lang('GiftInfoDescriptionOutConverted', {
+ ? lang('GiftInfoPeerDescriptionOutConverted', {
amount: formatInteger(amountToConvert!),
- user: targetUser || 'User',
+ peer: targetChatTitle || 'Chat',
}, {
pluralValue: amountToConvert!,
withNodes: true,
@@ -768,6 +783,7 @@ export default memo(withGlobal(
noFocusHighlight,
}),
isCurrentUserPremium: selectIsCurrentUserPremium(global),
+ currentUserId: global.currentUserId,
};
},
)(ActionMessage));
diff --git a/src/components/modals/gift/GiftComposer.tsx b/src/components/modals/gift/GiftComposer.tsx
index cf0a78587..1c82eeebb 100644
--- a/src/components/modals/gift/GiftComposer.tsx
+++ b/src/components/modals/gift/GiftComposer.tsx
@@ -4,13 +4,14 @@ import React, {
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
-import type { ApiMessage, ApiUser } from '../../../api/types';
+import type { ApiMessage, ApiPeer } from '../../../api/types';
import type { ThemeKey } from '../../../types';
import type { GiftOption } from './GiftModal';
import { STARS_CURRENCY_CODE } from '../../../config';
-import { getUserFullName } from '../../../global/helpers';
-import { selectTabState, selectTheme, selectUser } from '../../../global/selectors';
+import { getPeerTitle } from '../../../global/helpers';
+import { isApiPeerUser } from '../../../global/helpers/peers';
+import { selectPeer, selectTabState, selectTheme } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import { formatCurrency } from '../../../util/formatCurrency';
@@ -32,7 +33,7 @@ import styles from './GiftComposer.module.scss';
export type OwnProps = {
gift: GiftOption;
- userId: string;
+ peerId: string;
};
export type StateProps = {
@@ -42,7 +43,7 @@ export type StateProps = {
patternColor?: string;
customBackground?: string;
backgroundColor?: string;
- user?: ApiUser;
+ peer?: ApiPeer;
currentUserId?: string;
isPaymentFormLoading?: boolean;
};
@@ -51,8 +52,8 @@ const LIMIT_DISPLAY_THRESHOLD = 50;
function GiftComposer({
gift,
- userId,
- user,
+ peerId,
+ peer,
captionLimit,
theme,
isBackgroundBlurred,
@@ -73,6 +74,7 @@ function GiftComposer({
const customBackgroundValue = useCustomBackground(theme, customBackground);
const isStarGift = 'id' in gift;
+ const isPeerUser = peer && isApiPeerUser(peer);
const localMessage = useMemo(() => {
if (!isStarGift) {
@@ -84,7 +86,7 @@ function GiftComposer({
date: Math.floor(Date.now() / 1000),
content: {
action: {
- targetUserIds: [userId],
+ targetChatId: peerId,
mediaType: 'action',
text: 'ActionGiftInbound',
type: 'giftPremium',
@@ -108,7 +110,7 @@ function GiftComposer({
date: Math.floor(Date.now() / 1000),
content: {
action: {
- targetUserIds: [userId],
+ targetChatId: peerId,
mediaType: 'action',
text: 'ActionGiftInbound',
type: 'starGift',
@@ -122,14 +124,17 @@ function GiftComposer({
isNameHidden: shouldHideName,
starsToConvert: gift.starsToConvert,
canUpgrade: shouldPayForUpgrade || undefined,
+ alreadyPaidUpgradeStars: shouldPayForUpgrade ? gift.upgradeStars : undefined,
isSaved: false,
gift,
+ peerId,
+ fromId: currentUserId,
},
translationValues: ['%action_origin%', '%gift_payment_amount%'],
},
},
} satisfies ApiMessage;
- }, [currentUserId, gift, giftMessage, isStarGift, shouldHideName, shouldPayForUpgrade, userId]);
+ }, [currentUserId, gift, giftMessage, isStarGift, shouldHideName, shouldPayForUpgrade, peerId]);
const handleGiftMessageChange = useLastCallback((e: ChangeEvent) => {
setGiftMessage(e.target.value);
@@ -147,14 +152,14 @@ function GiftComposer({
if (!isStarGift) return;
openGiftUpgradeModal({
giftId: gift.id,
- peerId: userId,
+ peerId,
});
});
const handleMainButtonClick = useLastCallback(() => {
if (isStarGift) {
sendStarGift({
- userId,
+ peerId,
shouldHideName,
gift,
message: giftMessage ? { text: giftMessage } : undefined,
@@ -165,7 +170,7 @@ function GiftComposer({
openInvoice({
type: 'giftcode',
- userIds: [userId],
+ userIds: [peerId],
currency: gift.currency,
amount: gift.amount,
option: gift,
@@ -176,7 +181,7 @@ function GiftComposer({
function renderOptionsSection() {
const symbolsLeft = captionLimit ? captionLimit - giftMessage.length : undefined;
- const userFullName = getUserFullName(user)!;
+ const title = getPeerTitle(lang, peer!)!;
return (
)}
@@ -225,7 +237,7 @@ function GiftComposer({
)}
{isStarGift && (
- {lang('GiftHideNameDescription', { profile: userFullName, receiver: userFullName })}
+ {isPeerUser ? lang('GiftHideNameDescription', { receiver: title }) : lang('GiftHideNameDescriptionChannel')}
)}
@@ -298,7 +310,7 @@ function GiftComposer({
}
export default memo(withGlobal(
- (global, { userId }): StateProps => {
+ (global, { peerId }): StateProps => {
const theme = selectTheme(global);
const {
isBlurred: isBackgroundBlurred,
@@ -306,12 +318,12 @@ export default memo(withGlobal(
background: customBackground,
backgroundColor,
} = global.settings.themes[theme] || {};
- const user = selectUser(global, userId);
+ const peer = selectPeer(global, peerId);
const tabState = selectTabState(global);
return {
- user,
+ peer,
theme,
isBackgroundBlurred,
patternColor,
diff --git a/src/components/modals/gift/GiftModal.tsx b/src/components/modals/gift/GiftModal.tsx
index 135c927d9..ea76a343b 100644
--- a/src/components/modals/gift/GiftModal.tsx
+++ b/src/components/modals/gift/GiftModal.tsx
@@ -5,16 +5,17 @@ import React, {
import { getActions, withGlobal } from '../../../global';
import type {
+ ApiPeer,
ApiPremiumGiftCodeOption,
ApiStarGiftRegular,
ApiStarsAmount,
- ApiUser,
} from '../../../api/types';
import type { TabState } from '../../../global/types';
import type { StarGiftCategory } from '../../../types';
-import { getUserFullName } from '../../../global/helpers';
-import { selectUser } from '../../../global/selectors';
+import { getPeerTitle, getUserFullName } from '../../../global/helpers';
+import { isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers';
+import { selectPeer } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
@@ -49,7 +50,7 @@ type StateProps = {
starGiftsById?: Record;
starGiftIdsByCategory?: Record;
starBalance?: ApiStarsAmount;
- user?: ApiUser;
+ peer?: ApiPeer;
isSelf?: boolean;
};
@@ -61,7 +62,7 @@ const PremiumGiftModal: FC = ({
starGiftsById,
starGiftIdsByCategory,
starBalance,
- user,
+ peer,
isSelf,
}) => {
const {
@@ -80,6 +81,9 @@ const PremiumGiftModal: FC = ({
const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal);
+ const user = peer && isApiPeerUser(peer) ? peer : undefined;
+ const chat = peer && isApiPeerChat(peer) ? peer : undefined;
+
const [selectedGift, setSelectedGift] = useState();
const [isHeaderHidden, setIsHeaderHidden] = useState(true);
const [isHeaderForStarGifts, setIsHeaderForStarGifts] = useState(false);
@@ -156,14 +160,19 @@ const PremiumGiftModal: FC = ({
),
}, { withNodes: true });
- const starGiftDescription = isSelf
- ? lang('StarGiftDescriptionSelf', undefined, {
+ const starGiftDescription = chat
+ ? lang('StarGiftDescriptionChannel', { peer: getPeerTitle(lang, chat) }, {
withNodes: true,
- renderTextFilters: ['br'],
+ withMarkdown: true,
})
- : lang('StarGiftDescription', {
- user: getUserFullName(user)!,
- }, { withNodes: true, withMarkdown: true });
+ : isSelf
+ ? lang('StarGiftDescriptionSelf', undefined, {
+ withNodes: true,
+ renderTextFilters: ['br'],
+ })
+ : lang('StarGiftDescription', {
+ user: getUserFullName(user)!,
+ }, { withNodes: true, withMarkdown: true });
function renderGiftPremiumHeader() {
return (
@@ -254,13 +263,13 @@ const PremiumGiftModal: FC = ({
- {!isSelf && renderGiftPremiumHeader()}
- {!isSelf && renderGiftPremiumDescription()}
- {!isSelf && renderPremiumGifts()}
+ {!isSelf && !chat && renderGiftPremiumHeader()}
+ {!isSelf && !chat && renderGiftPremiumDescription()}
+ {!isSelf && !chat && renderPremiumGifts()}
{renderStarGiftsHeader()}
{renderStarGiftsDescription()}
@@ -321,8 +330,8 @@ const PremiumGiftModal: FC = ({
activeKey={selectedGift ? 1 : 0}
>
{!selectedGift && renderMainScreen()}
- {selectedGift && renderingModal?.forUserId && (
-
+ {selectedGift && renderingModal?.forPeerId && (
+
)}
@@ -336,22 +345,22 @@ export default memo(withGlobal((global, { modal }): StateProps => {
currentUserId,
} = global;
- const user = modal?.forUserId ? selectUser(global, modal.forUserId) : undefined;
- const isSelf = Boolean(currentUserId && modal?.forUserId === currentUserId);
+ const peer = modal?.forPeerId ? selectPeer(global, modal.forPeerId) : undefined;
+ const isSelf = Boolean(currentUserId && modal?.forPeerId === currentUserId);
return {
boostPerSentGift: global.appConfig?.boostsPerSentGift,
starGiftsById: starGifts?.byId,
starGiftIdsByCategory: starGifts?.idsByCategory,
starBalance: stars?.balance,
- user,
+ peer,
isSelf,
};
})(PremiumGiftModal));
function getCategoryKey(category: StarGiftCategory) {
if (category === 'all') return -2;
- if (category === 'stock') return -1;
- if (category === 'limited') return 0;
+ if (category === 'limited') return -1;
+ if (category === 'stock') return 0;
return category;
}
diff --git a/src/components/modals/gift/StarGiftCategoryList.tsx b/src/components/modals/gift/StarGiftCategoryList.tsx
index b4d73f0d3..e48861a6a 100644
--- a/src/components/modals/gift/StarGiftCategoryList.tsx
+++ b/src/components/modals/gift/StarGiftCategoryList.tsx
@@ -78,8 +78,8 @@ const StarGiftCategoryList = ({
return (
{renderCategoryItem('all')}
- {renderCategoryItem('stock')}
{renderCategoryItem('limited')}
+ {renderCategoryItem('stock')}
{starCategories?.map(renderCategoryItem)}
);
diff --git a/src/components/modals/gift/info/GiftInfoModal.module.scss b/src/components/modals/gift/info/GiftInfoModal.module.scss
index 6c61b02c4..97f8a9d4c 100644
--- a/src/components/modals/gift/info/GiftInfoModal.module.scss
+++ b/src/components/modals/gift/info/GiftInfoModal.module.scss
@@ -52,6 +52,7 @@
gap: 0.25rem;
}
+/* stylelint-disable-next-line plugin/stylelint-group-selectors */
.uniqueGift {
margin-bottom: 0;
}
@@ -59,3 +60,17 @@
.starAmountIcon {
margin-inline-start: 0 !important;
}
+
+.ownerAddress {
+ font-family: var(--font-family-monospace);
+ font-size: 0.875rem;
+ cursor: pointer;
+
+ overflow: hidden;
+}
+
+.copyIcon {
+ margin-inline-start: 0.25rem;
+ color: var(--color-primary);
+ pointer-events: none;
+}
diff --git a/src/components/modals/gift/info/GiftInfoModal.tsx b/src/components/modals/gift/info/GiftInfoModal.tsx
index a77ed8256..81786da05 100644
--- a/src/components/modals/gift/info/GiftInfoModal.tsx
+++ b/src/components/modals/gift/info/GiftInfoModal.tsx
@@ -3,13 +3,15 @@ import React, { memo, useMemo } from '../../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../../global';
import type {
- ApiUser,
+ ApiPeer,
} from '../../../../api/types';
import type { TabState } from '../../../../global/types';
-import { getUserFullName } from '../../../../global/helpers';
-import { selectUser } from '../../../../global/selectors';
+import { getHasAdminRight, getPeerTitle } from '../../../../global/helpers';
+import { isApiPeerChat } from '../../../../global/helpers/peers';
+import { selectPeer } from '../../../../global/selectors';
import buildClassName from '../../../../util/buildClassName';
+import { copyTextToClipboard } from '../../../../util/clipboard';
import { formatDateTimeToString } from '../../../../util/dates/dateFormat';
import { formatStarsAsIcon, formatStarsAsText } from '../../../../util/localization/format';
import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer';
@@ -27,6 +29,7 @@ import useOldLang from '../../../../hooks/useOldLang';
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
import Avatar from '../../../common/Avatar';
import BadgeButton from '../../../common/BadgeButton';
+import Icon from '../../../common/icons/Icon';
import Button from '../../../ui/Button';
import ConfirmDialog from '../../../ui/ConfirmDialog';
import Link from '../../../ui/Link';
@@ -40,16 +43,22 @@ export type OwnProps = {
};
type StateProps = {
- userFrom?: ApiUser;
- targetUser?: ApiUser;
+ fromPeer?: ApiPeer;
+ targetPeer?: ApiPeer;
currentUserId?: string;
starGiftMaxConvertPeriod?: number;
+ hasAdminRights?: boolean;
};
const STICKER_SIZE = 120;
const GiftInfoModal = ({
- modal, userFrom, targetUser, currentUserId, starGiftMaxConvertPeriod,
+ modal,
+ fromPeer,
+ targetPeer,
+ currentUserId,
+ starGiftMaxConvertPeriod,
+ hasAdminRights,
}: OwnProps & StateProps) => {
const {
closeGiftInfoModal,
@@ -58,6 +67,7 @@ const GiftInfoModal = ({
openChatWithInfo,
focusMessage,
openGiftUpgradeModal,
+ showNotification,
} = getActions();
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
@@ -67,48 +77,55 @@ const GiftInfoModal = ({
const isOpen = Boolean(modal);
const renderingModal = useCurrentOrPrev(modal);
+ const renderingFromPeer = useCurrentOrPrev(fromPeer);
+ const renderingTargetPeer = useCurrentOrPrev(targetPeer);
+
+ const isTargetChat = renderingTargetPeer && isApiPeerChat(renderingTargetPeer);
+
const { gift: typeGift } = renderingModal || {};
- const isUserGift = typeGift && 'gift' in typeGift;
- const userGift = isUserGift ? typeGift : undefined;
- const isSender = userGift?.fromId === currentUserId;
- const canConvertDifference = (userGift && starGiftMaxConvertPeriod && (
- userGift.date + starGiftMaxConvertPeriod - getServerTime()
+ const isSavedGift = typeGift && 'gift' in typeGift;
+ const savedGift = isSavedGift ? typeGift : undefined;
+ const isSender = savedGift?.fromId === currentUserId;
+ const canConvertDifference = (savedGift && starGiftMaxConvertPeriod && (
+ savedGift.date + starGiftMaxConvertPeriod - getServerTime()
)) || 0;
const conversionLeft = Math.ceil(canConvertDifference / 60 / 60 / 24);
- const gift = isUserGift ? typeGift.gift : typeGift;
+ const gift = isSavedGift ? typeGift.gift : typeGift;
const giftSticker = gift && getStickerFromGift(gift);
- const canFocusUpgrade = Boolean(userGift?.upgradeMsgId);
- const canUpdate = Boolean(userGift?.messageId) && targetUser?.id === currentUserId && !canFocusUpgrade;
+ const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId);
+ const canUpdate = !canFocusUpgrade && savedGift?.inputGift && (
+ isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId
+ );
const handleClose = useLastCallback(() => {
closeGiftInfoModal();
});
const handleFocusUpgraded = useLastCallback(() => {
- if (!userGift?.upgradeMsgId || !targetUser) return;
- const { upgradeMsgId } = userGift;
- focusMessage({ chatId: targetUser.id, messageId: upgradeMsgId! });
+ if (!savedGift?.upgradeMsgId || !renderingTargetPeer) return;
+ const { upgradeMsgId } = savedGift;
+ focusMessage({ chatId: renderingTargetPeer.id, messageId: upgradeMsgId! });
handleClose();
});
const handleTriggerVisibility = useLastCallback(() => {
- const { messageId, isUnsaved } = userGift!;
- changeGiftVisibility({ messageId: messageId!, shouldUnsave: !isUnsaved });
+ const { inputGift, isUnsaved } = savedGift!;
+ changeGiftVisibility({ gift: inputGift!, shouldUnsave: !isUnsaved });
handleClose();
});
const handleConvertToStars = useLastCallback(() => {
- const { messageId } = userGift!;
- convertGiftToStars({ messageId: messageId! });
+ const { inputGift } = savedGift!;
+ convertGiftToStars({ gift: inputGift! });
closeConvertConfirm();
handleClose();
});
const handleOpenUpgradeModal = useLastCallback(() => {
- if (!userGift) return;
- openGiftUpgradeModal({ giftId: userGift.gift.id, gift: userGift });
+ if (!savedGift) return;
+ openGiftUpgradeModal({ giftId: savedGift.gift.id, gift: savedGift });
});
const giftAttributes = useMemo(() => {
@@ -124,7 +141,7 @@ const GiftInfoModal = ({
);
}
- if (canUpdate && userGift?.alreadyPaidUpgradeStars && !userGift.upgradeMsgId) {
+ if (canUpdate && savedGift?.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
return (