Support pinned gifts (#5744)
This commit is contained in:
parent
905124fe96
commit
440001b938
7
package-lock.json
generated
7
package-lock.json
generated
@ -44,6 +44,7 @@
|
|||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@twbs/fantasticon": "^3.0.0",
|
"@twbs/fantasticon": "^3.0.0",
|
||||||
"@types/croppie": "^2.6.4",
|
"@types/croppie": "^2.6.4",
|
||||||
|
"@types/dom-view-transitions": "^1.0.6",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/react": "^18.3.1",
|
"@types/react": "^18.3.1",
|
||||||
@ -5146,6 +5147,12 @@
|
|||||||
"@types/ms": "*"
|
"@types/ms": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/dom-view-transitions": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/dom-view-transitions/-/dom-view-transitions-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-Q5IoDouTOcZKEs4nDpmUti2MXP48MQDBkS2nUQKFrsDeTFIaArVmhWUon28vlDfNkbbaK4EROu4Fv0iKObWjSg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "8.56.10",
|
"version": "8.56.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
|
||||||
|
|||||||
@ -66,6 +66,7 @@
|
|||||||
"@testing-library/jest-dom": "^6.4.5",
|
"@testing-library/jest-dom": "^6.4.5",
|
||||||
"@twbs/fantasticon": "^3.0.0",
|
"@twbs/fantasticon": "^3.0.0",
|
||||||
"@types/croppie": "^2.6.4",
|
"@types/croppie": "^2.6.4",
|
||||||
|
"@types/dom-view-transitions": "^1.0.6",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/react": "^18.3.1",
|
"@types/react": "^18.3.1",
|
||||||
|
|||||||
@ -87,6 +87,7 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
stargifts_convert_period_max?: number;
|
stargifts_convert_period_max?: number;
|
||||||
starref_start_param_prefixes?: string[];
|
starref_start_param_prefixes?: string[];
|
||||||
ton_blockchain_explorer_url?: string;
|
ton_blockchain_explorer_url?: string;
|
||||||
|
stargifts_pinned_to_top_limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||||
@ -174,5 +175,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
starGiftMaxConvertPeriod: appConfig.stargifts_convert_period_max,
|
starGiftMaxConvertPeriod: appConfig.stargifts_convert_period_max,
|
||||||
starRefStartPrefixes: appConfig.starref_start_param_prefixes,
|
starRefStartPrefixes: appConfig.starref_start_param_prefixes,
|
||||||
tonExplorerUrl: appConfig.ton_blockchain_explorer_url,
|
tonExplorerUrl: appConfig.ton_blockchain_explorer_url,
|
||||||
|
savedGiftPinLimit: appConfig.stargifts_pinned_to_top_limit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,7 +131,7 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
|
|||||||
export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
|
export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId: string): ApiSavedStarGift {
|
||||||
const {
|
const {
|
||||||
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
|
gift, date, convertStars, fromId, message, msgId, nameHidden, unsaved, upgradeStars, transferStars, canUpgrade,
|
||||||
savedId, canExportAt,
|
savedId, canExportAt, pinnedToTop,
|
||||||
} = userStarGift;
|
} = userStarGift;
|
||||||
|
|
||||||
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
|
const inputGift: ApiInputSavedStarGift | undefined = savedId && peerId
|
||||||
@ -153,5 +153,6 @@ export function buildApiSavedStarGift(userStarGift: GramJs.SavedStarGift, peerId
|
|||||||
inputGift,
|
inputGift,
|
||||||
savedId: savedId?.toString(),
|
savedId: savedId?.toString(),
|
||||||
canExportAt,
|
canExportAt,
|
||||||
|
isPinned: pinnedToTop,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,3 +42,5 @@ export * from './stories';
|
|||||||
export * from './payments';
|
export * from './payments';
|
||||||
|
|
||||||
export * from './fragment';
|
export * from './fragment';
|
||||||
|
|
||||||
|
export * from './stars';
|
||||||
|
|||||||
@ -1,25 +1,15 @@
|
|||||||
import BigInt from 'big-integer';
|
import BigInt from 'big-integer';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
|
||||||
GiftProfileFilterOptions,
|
|
||||||
} from '../../../types';
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiInputStorePaymentPurpose,
|
ApiInputStorePaymentPurpose,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiRequestInputInvoice,
|
ApiRequestInputInvoice,
|
||||||
ApiRequestInputSavedStarGift,
|
|
||||||
ApiStarGiftRegular,
|
|
||||||
ApiThemeParameters,
|
ApiThemeParameters,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { DEBUG } from '../../../config';
|
import { DEBUG } from '../../../config';
|
||||||
import {
|
|
||||||
buildApiSavedStarGift,
|
|
||||||
buildApiStarGift,
|
|
||||||
buildApiStarGiftAttribute,
|
|
||||||
} from '../apiBuilders/gifts';
|
|
||||||
import {
|
import {
|
||||||
buildApiBoost,
|
buildApiBoost,
|
||||||
buildApiBoostsStatus,
|
buildApiBoostsStatus,
|
||||||
@ -30,33 +20,24 @@ import {
|
|||||||
buildApiPremiumGiftCodeOption,
|
buildApiPremiumGiftCodeOption,
|
||||||
buildApiPremiumPromo,
|
buildApiPremiumPromo,
|
||||||
buildApiReceipt,
|
buildApiReceipt,
|
||||||
buildApiStarsAmount,
|
|
||||||
buildApiStarsGiftOptions,
|
|
||||||
buildApiStarsGiveawayOptions,
|
|
||||||
buildApiStarsSubscription,
|
|
||||||
buildApiStarsTransaction,
|
|
||||||
buildApiStarTopupOption,
|
|
||||||
buildShippingOptions,
|
buildShippingOptions,
|
||||||
} from '../apiBuilders/payments';
|
} from '../apiBuilders/payments';
|
||||||
import { buildApiPeerId } from '../apiBuilders/peers';
|
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||||
import {
|
import {
|
||||||
buildInputInvoice,
|
buildInputInvoice,
|
||||||
buildInputPeer,
|
buildInputPeer,
|
||||||
buildInputSavedStarGift,
|
|
||||||
buildInputStorePaymentPurpose,
|
buildInputStorePaymentPurpose,
|
||||||
buildInputThemeParams,
|
buildInputThemeParams,
|
||||||
buildShippingInfo,
|
buildShippingInfo,
|
||||||
} from '../gramjsBuilders';
|
} from '../gramjsBuilders';
|
||||||
import {
|
import {
|
||||||
checkErrorType,
|
|
||||||
deserializeBytes,
|
deserializeBytes,
|
||||||
serializeBytes,
|
serializeBytes,
|
||||||
wrapError,
|
|
||||||
} from '../helpers/misc';
|
} from '../helpers/misc';
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
import { sendApiUpdate } from '../updates/apiUpdateEmitter';
|
import { sendApiUpdate } from '../updates/apiUpdateEmitter';
|
||||||
import { handleGramJsUpdate, invokeRequest } from './client';
|
import { handleGramJsUpdate, invokeRequest } from './client';
|
||||||
import { getPassword, getTemporaryPaymentPassword } from './twoFaSettings';
|
import { getTemporaryPaymentPassword } from './twoFaSettings';
|
||||||
|
|
||||||
export async function validateRequestedInfo({
|
export async function validateRequestedInfo({
|
||||||
inputInvoice,
|
inputInvoice,
|
||||||
@ -413,107 +394,6 @@ 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 async function fetchStarsGiveawayOptions() {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarsGiveawayOptions());
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.map(buildApiStarsGiveawayOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarGifts() {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarGifts({}));
|
|
||||||
|
|
||||||
if (!result || result instanceof GramJs.payments.StarGiftsNotModified) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right now, only regular star gifts can be bought, but API are not specific
|
|
||||||
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchSavedStarGifts({
|
|
||||||
peer,
|
|
||||||
offset = '',
|
|
||||||
limit,
|
|
||||||
filter,
|
|
||||||
}: {
|
|
||||||
peer: ApiPeer;
|
|
||||||
offset?: string;
|
|
||||||
limit?: number;
|
|
||||||
filter?: GiftProfileFilterOptions;
|
|
||||||
}) {
|
|
||||||
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
|
||||||
|
|
||||||
const params : GetSavedStarGiftsParams = {
|
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
|
||||||
offset,
|
|
||||||
limit,
|
|
||||||
...(filter && {
|
|
||||||
sortByValue: filter.sortType === 'byValue' || undefined,
|
|
||||||
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
|
||||||
excludeLimited: !filter.shouldIncludeLimited || undefined,
|
|
||||||
excludeUnique: !filter.shouldIncludeUnique || undefined,
|
|
||||||
excludeSaved: !filter.shouldIncludeDisplayed || undefined,
|
|
||||||
excludeUnsaved: !filter.shouldIncludeHidden || undefined,
|
|
||||||
} satisfies GetSavedStarGiftsParams),
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetSavedStarGifts(params));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gifts = result.gifts.map((g) => buildApiSavedStarGift(g, peer.id));
|
|
||||||
|
|
||||||
return {
|
|
||||||
gifts,
|
|
||||||
nextOffset: result.nextOffset,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveStarGift({
|
|
||||||
inputGift,
|
|
||||||
shouldUnsave,
|
|
||||||
}: {
|
|
||||||
inputGift: ApiRequestInputSavedStarGift;
|
|
||||||
shouldUnsave?: boolean;
|
|
||||||
}) {
|
|
||||||
return invokeRequest(new GramJs.payments.SaveStarGift({
|
|
||||||
stargift: buildInputSavedStarGift(inputGift),
|
|
||||||
unsave: shouldUnsave || undefined,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertStarGift({
|
|
||||||
inputSavedGift,
|
|
||||||
}: {
|
|
||||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
|
||||||
}) {
|
|
||||||
return invokeRequest(new GramJs.payments.ConvertStarGift({
|
|
||||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function launchPrepaidGiveaway({
|
export function launchPrepaidGiveaway({
|
||||||
chat,
|
chat,
|
||||||
giveawayId,
|
giveawayId,
|
||||||
@ -531,234 +411,3 @@ export function launchPrepaidGiveaway({
|
|||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchStarsStatus() {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
|
|
||||||
peer: new GramJs.InputPeerSelf(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nextHistoryOffset: result.nextOffset,
|
|
||||||
history: result.history?.map(buildApiStarsTransaction),
|
|
||||||
nextSubscriptionOffset: result.subscriptionsNextOffset,
|
|
||||||
subscriptions: result.subscriptions?.map(buildApiStarsSubscription),
|
|
||||||
balance: buildApiStarsAmount(result.balance),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarsTransactions({
|
|
||||||
peer,
|
|
||||||
offset,
|
|
||||||
isInbound,
|
|
||||||
isOutbound,
|
|
||||||
}: {
|
|
||||||
peer?: ApiPeer;
|
|
||||||
offset?: string;
|
|
||||||
isInbound?: true;
|
|
||||||
isOutbound?: true;
|
|
||||||
}) {
|
|
||||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
|
|
||||||
peer: inputPeer,
|
|
||||||
offset,
|
|
||||||
inbound: isInbound,
|
|
||||||
outbound: isOutbound,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nextOffset: result.nextOffset,
|
|
||||||
history: result.history?.map(buildApiStarsTransaction),
|
|
||||||
balance: buildApiStarsAmount(result.balance),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarsTransactionById({
|
|
||||||
id, peer,
|
|
||||||
}: {
|
|
||||||
id: string;
|
|
||||||
peer?: ApiPeer;
|
|
||||||
}) {
|
|
||||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({
|
|
||||||
peer: inputPeer,
|
|
||||||
id: [new GramJs.InputStarsTransaction({
|
|
||||||
id,
|
|
||||||
})],
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result?.history?.[0]) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
transaction: buildApiStarsTransaction(result?.history[0]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarsSubscriptions({
|
|
||||||
offset, peer,
|
|
||||||
}: {
|
|
||||||
offset?: string;
|
|
||||||
peer?: ApiPeer;
|
|
||||||
}) {
|
|
||||||
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarsSubscriptions({
|
|
||||||
peer: inputPeer,
|
|
||||||
offset,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result?.subscriptions) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
nextOffset: result.subscriptionsNextOffset,
|
|
||||||
subscriptions: result.subscriptions.map(buildApiStarsSubscription),
|
|
||||||
balance: buildApiStarsAmount(result.balance),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function changeStarsSubscription({
|
|
||||||
peer, subscriptionId, isCancelled,
|
|
||||||
}: {
|
|
||||||
peer?: ApiPeer;
|
|
||||||
subscriptionId: string;
|
|
||||||
isCancelled: boolean;
|
|
||||||
}) {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.ChangeStarsSubscription({
|
|
||||||
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
|
||||||
subscriptionId,
|
|
||||||
canceled: isCancelled,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fulfillStarsSubscription({
|
|
||||||
peer, subscriptionId,
|
|
||||||
}: {
|
|
||||||
peer?: ApiPeer;
|
|
||||||
subscriptionId: string;
|
|
||||||
}) {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.FulfillStarsSubscription({
|
|
||||||
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
|
||||||
subscriptionId,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarsTopupOptions() {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions());
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.map(buildApiStarTopupOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchUniqueStarGift({ slug }: {
|
|
||||||
slug: string;
|
|
||||||
}) {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetUniqueStarGift({ slug }));
|
|
||||||
|
|
||||||
if (!result) return undefined;
|
|
||||||
|
|
||||||
const gift = buildApiStarGift(result.gift);
|
|
||||||
if (gift.type !== 'starGiftUnique') return undefined;
|
|
||||||
return gift;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarGiftUpgradePreview({
|
|
||||||
giftId,
|
|
||||||
}: {
|
|
||||||
giftId: string;
|
|
||||||
}) {
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradePreview({
|
|
||||||
giftId: BigInt(giftId),
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function upgradeStarGift({
|
|
||||||
inputSavedGift,
|
|
||||||
shouldKeepOriginalDetails,
|
|
||||||
}: {
|
|
||||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
|
||||||
shouldKeepOriginalDetails?: true;
|
|
||||||
}) {
|
|
||||||
return invokeRequest(new GramJs.payments.UpgradeStarGift({
|
|
||||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
|
||||||
keepOriginalDetails: shouldKeepOriginalDetails,
|
|
||||||
}), {
|
|
||||||
shouldReturnTrue: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transferStarGift({
|
|
||||||
inputSavedGift,
|
|
||||||
toPeer,
|
|
||||||
}: {
|
|
||||||
inputSavedGift: ApiRequestInputSavedStarGift;
|
|
||||||
toPeer: ApiPeer;
|
|
||||||
}) {
|
|
||||||
return invokeRequest(new GramJs.payments.TransferStarGift({
|
|
||||||
stargift: buildInputSavedStarGift(inputSavedGift),
|
|
||||||
toId: buildInputPeer(toPeer.id, toPeer.accessHash),
|
|
||||||
}), {
|
|
||||||
shouldReturnTrue: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchStarGiftWithdrawalUrl({
|
|
||||||
inputGift,
|
|
||||||
password,
|
|
||||||
}: {
|
|
||||||
inputGift: ApiRequestInputSavedStarGift;
|
|
||||||
password: string;
|
|
||||||
}) {
|
|
||||||
try {
|
|
||||||
const passwordCheck = await getPassword(password);
|
|
||||||
|
|
||||||
if (!passwordCheck) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('error' in passwordCheck) {
|
|
||||||
return passwordCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await invokeRequest(new GramJs.payments.GetStarGiftWithdrawalUrl({
|
|
||||||
stargift: buildInputSavedStarGift(inputGift),
|
|
||||||
password: passwordCheck,
|
|
||||||
}), {
|
|
||||||
shouldThrow: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { url: result.url };
|
|
||||||
} catch (err: unknown) {
|
|
||||||
if (!checkErrorType(err)) return undefined;
|
|
||||||
|
|
||||||
return wrapError(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|||||||
371
src/api/gramjs/methods/stars.ts
Normal file
371
src/api/gramjs/methods/stars.ts
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
import bigInt from 'big-integer';
|
||||||
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
|
import type { GiftProfileFilterOptions } from '../../../types';
|
||||||
|
import type {
|
||||||
|
ApiChat,
|
||||||
|
ApiPeer,
|
||||||
|
ApiRequestInputSavedStarGift,
|
||||||
|
ApiStarGiftRegular,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
import { buildApiSavedStarGift, buildApiStarGift, buildApiStarGiftAttribute } from '../apiBuilders/gifts';
|
||||||
|
import {
|
||||||
|
buildApiStarsAmount,
|
||||||
|
buildApiStarsGiftOptions,
|
||||||
|
buildApiStarsGiveawayOptions,
|
||||||
|
buildApiStarsSubscription,
|
||||||
|
buildApiStarsTransaction,
|
||||||
|
buildApiStarTopupOption,
|
||||||
|
} from '../apiBuilders/payments';
|
||||||
|
import { buildInputPeer, buildInputSavedStarGift } from '../gramjsBuilders';
|
||||||
|
import { checkErrorType, wrapError } from '../helpers/misc';
|
||||||
|
import { invokeRequest } from './client';
|
||||||
|
import { getPassword } from './twoFaSettings';
|
||||||
|
|
||||||
|
export async function fetchStarsGiveawayOptions() {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarsGiveawayOptions());
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map(buildApiStarsGiveawayOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarGifts() {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarGifts({}));
|
||||||
|
|
||||||
|
if (!result || result instanceof GramJs.payments.StarGiftsNotModified) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right now, only regular star gifts can be bought, but API are not specific
|
||||||
|
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchSavedStarGifts({
|
||||||
|
peer,
|
||||||
|
offset = '',
|
||||||
|
limit,
|
||||||
|
filter,
|
||||||
|
}: {
|
||||||
|
peer: ApiPeer;
|
||||||
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
|
filter?: GiftProfileFilterOptions;
|
||||||
|
}) {
|
||||||
|
type GetSavedStarGiftsParams = ConstructorParameters<typeof GramJs.payments.GetSavedStarGifts>[0];
|
||||||
|
|
||||||
|
const params : GetSavedStarGiftsParams = {
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
...(filter && {
|
||||||
|
sortByValue: filter.sortType === 'byValue' || undefined,
|
||||||
|
excludeUnlimited: !filter.shouldIncludeUnlimited || undefined,
|
||||||
|
excludeLimited: !filter.shouldIncludeLimited || undefined,
|
||||||
|
excludeUnique: !filter.shouldIncludeUnique || undefined,
|
||||||
|
excludeSaved: !filter.shouldIncludeDisplayed || undefined,
|
||||||
|
excludeUnsaved: !filter.shouldIncludeHidden || undefined,
|
||||||
|
} satisfies GetSavedStarGiftsParams),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetSavedStarGifts(params));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gifts = result.gifts.map((g) => buildApiSavedStarGift(g, peer.id));
|
||||||
|
|
||||||
|
return {
|
||||||
|
gifts,
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveStarGift({
|
||||||
|
inputGift,
|
||||||
|
shouldUnsave,
|
||||||
|
}: {
|
||||||
|
inputGift: ApiRequestInputSavedStarGift;
|
||||||
|
shouldUnsave?: boolean;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.SaveStarGift({
|
||||||
|
stargift: buildInputSavedStarGift(inputGift),
|
||||||
|
unsave: shouldUnsave || undefined,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertStarGift({
|
||||||
|
inputSavedGift,
|
||||||
|
}: {
|
||||||
|
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.ConvertStarGift({
|
||||||
|
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 async function fetchStarsStatus() {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
|
||||||
|
peer: new GramJs.InputPeerSelf(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nextHistoryOffset: result.nextOffset,
|
||||||
|
history: result.history?.map(buildApiStarsTransaction),
|
||||||
|
nextSubscriptionOffset: result.subscriptionsNextOffset,
|
||||||
|
subscriptions: result.subscriptions?.map(buildApiStarsSubscription),
|
||||||
|
balance: buildApiStarsAmount(result.balance),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarsTransactions({
|
||||||
|
peer,
|
||||||
|
offset,
|
||||||
|
isInbound,
|
||||||
|
isOutbound,
|
||||||
|
}: {
|
||||||
|
peer?: ApiPeer;
|
||||||
|
offset?: string;
|
||||||
|
isInbound?: true;
|
||||||
|
isOutbound?: true;
|
||||||
|
}) {
|
||||||
|
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
|
||||||
|
peer: inputPeer,
|
||||||
|
offset,
|
||||||
|
inbound: isInbound,
|
||||||
|
outbound: isOutbound,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
history: result.history?.map(buildApiStarsTransaction),
|
||||||
|
balance: buildApiStarsAmount(result.balance),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarsTransactionById({
|
||||||
|
id, peer,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
peer?: ApiPeer;
|
||||||
|
}) {
|
||||||
|
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({
|
||||||
|
peer: inputPeer,
|
||||||
|
id: [new GramJs.InputStarsTransaction({
|
||||||
|
id,
|
||||||
|
})],
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result?.history?.[0]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
transaction: buildApiStarsTransaction(result?.history[0]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarsSubscriptions({
|
||||||
|
offset, peer,
|
||||||
|
}: {
|
||||||
|
offset?: string;
|
||||||
|
peer?: ApiPeer;
|
||||||
|
}) {
|
||||||
|
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarsSubscriptions({
|
||||||
|
peer: inputPeer,
|
||||||
|
offset,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result?.subscriptions) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nextOffset: result.subscriptionsNextOffset,
|
||||||
|
subscriptions: result.subscriptions.map(buildApiStarsSubscription),
|
||||||
|
balance: buildApiStarsAmount(result.balance),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changeStarsSubscription({
|
||||||
|
peer, subscriptionId, isCancelled,
|
||||||
|
}: {
|
||||||
|
peer?: ApiPeer;
|
||||||
|
subscriptionId: string;
|
||||||
|
isCancelled: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.ChangeStarsSubscription({
|
||||||
|
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
||||||
|
subscriptionId,
|
||||||
|
canceled: isCancelled,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fulfillStarsSubscription({
|
||||||
|
peer, subscriptionId,
|
||||||
|
}: {
|
||||||
|
peer?: ApiPeer;
|
||||||
|
subscriptionId: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.FulfillStarsSubscription({
|
||||||
|
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
|
||||||
|
subscriptionId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarsTopupOptions() {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions());
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map(buildApiStarTopupOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchUniqueStarGift({ slug }: {
|
||||||
|
slug: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetUniqueStarGift({ slug }));
|
||||||
|
|
||||||
|
if (!result) return undefined;
|
||||||
|
|
||||||
|
const gift = buildApiStarGift(result.gift);
|
||||||
|
if (gift.type !== 'starGiftUnique') return undefined;
|
||||||
|
return gift;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarGiftUpgradePreview({
|
||||||
|
giftId,
|
||||||
|
}: {
|
||||||
|
giftId: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradePreview({
|
||||||
|
giftId: bigInt(giftId),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function upgradeStarGift({
|
||||||
|
inputSavedGift,
|
||||||
|
shouldKeepOriginalDetails,
|
||||||
|
}: {
|
||||||
|
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||||
|
shouldKeepOriginalDetails?: true;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.UpgradeStarGift({
|
||||||
|
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||||
|
keepOriginalDetails: shouldKeepOriginalDetails,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transferStarGift({
|
||||||
|
inputSavedGift,
|
||||||
|
toPeer,
|
||||||
|
}: {
|
||||||
|
inputSavedGift: ApiRequestInputSavedStarGift;
|
||||||
|
toPeer: ApiPeer;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.TransferStarGift({
|
||||||
|
stargift: buildInputSavedStarGift(inputSavedGift),
|
||||||
|
toId: buildInputPeer(toPeer.id, toPeer.accessHash),
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleSavedGiftPinned({
|
||||||
|
inputSavedGifts,
|
||||||
|
peer,
|
||||||
|
}: {
|
||||||
|
inputSavedGifts: ApiRequestInputSavedStarGift[];
|
||||||
|
peer: ApiPeer;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.payments.ToggleStarGiftsPinnedToTop({
|
||||||
|
stargift: inputSavedGifts.map(buildInputSavedStarGift),
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchStarGiftWithdrawalUrl({
|
||||||
|
inputGift,
|
||||||
|
password,
|
||||||
|
}: {
|
||||||
|
inputGift: ApiRequestInputSavedStarGift;
|
||||||
|
password: string;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const passwordCheck = await getPassword(password);
|
||||||
|
|
||||||
|
if (!passwordCheck) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('error' in passwordCheck) {
|
||||||
|
return passwordCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetStarGiftWithdrawalUrl({
|
||||||
|
stargift: buildInputSavedStarGift(inputGift),
|
||||||
|
password: passwordCheck,
|
||||||
|
}), {
|
||||||
|
shouldThrow: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { url: result.url };
|
||||||
|
} catch (err: unknown) {
|
||||||
|
if (!checkErrorType(err)) return undefined;
|
||||||
|
|
||||||
|
return wrapError(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@ -11,3 +11,4 @@ export * from './calls';
|
|||||||
export * from './statistics';
|
export * from './statistics';
|
||||||
export * from './stories';
|
export * from './stories';
|
||||||
export * from './business';
|
export * from './business';
|
||||||
|
export * from './stars';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls';
|
import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls';
|
||||||
import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages';
|
import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages';
|
||||||
import type { ApiStarGiftRegular, ApiStarGiftUnique } from './payments';
|
import type { ApiStarGiftRegular, ApiStarGiftUnique } from './stars';
|
||||||
|
|
||||||
interface ActionMediaType {
|
interface ActionMediaType {
|
||||||
mediaType: 'action';
|
mediaType: 'action';
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import type { ApiPeerColor } from './chats';
|
|||||||
import type { ApiMessageAction } from './messageActions';
|
import type { ApiMessageAction } from './messageActions';
|
||||||
import type {
|
import type {
|
||||||
ApiLabeledPrice,
|
ApiLabeledPrice,
|
||||||
ApiStarGiftUnique,
|
|
||||||
} from './payments';
|
} from './payments';
|
||||||
|
import type { ApiStarGiftUnique } from './stars';
|
||||||
import type {
|
import type {
|
||||||
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
||||||
} from './stories';
|
} from './stories';
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import type { CallbackAction } from '../../global/types';
|
|||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import type { RegularLangFnParameters } from '../../util/localization';
|
import type { RegularLangFnParameters } from '../../util/localization';
|
||||||
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
import type { ApiDocument, ApiPhoto, ApiReaction } from './messages';
|
||||||
import type { ApiPremiumSection, ApiStarsSubscriptionPricing } from './payments';
|
import type { ApiPremiumSection } from './payments';
|
||||||
|
import type { ApiStarsSubscriptionPricing } from './stars';
|
||||||
import type { ApiUser } from './users';
|
import type { ApiUser } from './users';
|
||||||
|
|
||||||
export interface ApiInitialArgs {
|
export interface ApiInitialArgs {
|
||||||
@ -235,6 +236,7 @@ export interface ApiAppConfig {
|
|||||||
starGiftMaxConvertPeriod?: number;
|
starGiftMaxConvertPeriod?: number;
|
||||||
starRefStartPrefixes?: string[];
|
starRefStartPrefixes?: string[];
|
||||||
tonExplorerUrl?: string;
|
tonExplorerUrl?: string;
|
||||||
|
savedGiftPinLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiConfig {
|
export interface ApiConfig {
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import type {
|
|||||||
ApiInvoice,
|
ApiInvoice,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiPaymentCredentials,
|
ApiPaymentCredentials,
|
||||||
ApiSticker,
|
|
||||||
BoughtPaidMedia,
|
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
import type {
|
||||||
|
ApiInputSavedStarGift, ApiRequestInputSavedStarGift, ApiStarsGiveawayWinnerOption,
|
||||||
|
} from './stars';
|
||||||
import type { StatisticsOverviewPercentage } from './statistics';
|
import type { StatisticsOverviewPercentage } from './statistics';
|
||||||
import type { ApiUser } from './users';
|
import type { ApiUser } from './users';
|
||||||
|
|
||||||
@ -190,114 +191,6 @@ export type ApiInputStorePaymentStarsGiveaway = {
|
|||||||
export type ApiInputStorePaymentPurpose = ApiInputStorePaymentGiveaway | ApiInputStorePaymentGiftcode |
|
export type ApiInputStorePaymentPurpose = ApiInputStorePaymentGiveaway | ApiInputStorePaymentGiftcode |
|
||||||
ApiInputStorePaymentStarsTopup | ApiInputStorePaymentStarsGift | ApiInputStorePaymentStarsGiveaway;
|
ApiInputStorePaymentStarsTopup | ApiInputStorePaymentStarsGift | ApiInputStorePaymentStarsGiveaway;
|
||||||
|
|
||||||
export interface ApiStarGiftRegular {
|
|
||||||
type: 'starGift';
|
|
||||||
isLimited?: true;
|
|
||||||
id: string;
|
|
||||||
sticker: ApiSticker;
|
|
||||||
stars: number;
|
|
||||||
availabilityRemains?: number;
|
|
||||||
availabilityTotal?: number;
|
|
||||||
starsToConvert: number;
|
|
||||||
isSoldOut?: true;
|
|
||||||
firstSaleDate?: number;
|
|
||||||
lastSaleDate?: number;
|
|
||||||
isBirthday?: true;
|
|
||||||
upgradeStars?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarGiftUnique {
|
|
||||||
type: 'starGiftUnique';
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
number: number;
|
|
||||||
ownerId?: string;
|
|
||||||
ownerName?: string;
|
|
||||||
ownerAddress?: string;
|
|
||||||
issuedCount: number;
|
|
||||||
totalCount: number;
|
|
||||||
attributes: ApiStarGiftAttribute[];
|
|
||||||
slug: string;
|
|
||||||
giftAddress?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
|
||||||
|
|
||||||
export interface ApiStarGiftAttributeModel {
|
|
||||||
type: 'model';
|
|
||||||
name: string;
|
|
||||||
rarityPercent: number;
|
|
||||||
sticker: ApiSticker;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarGiftAttributePattern {
|
|
||||||
type: 'pattern';
|
|
||||||
name: string;
|
|
||||||
rarityPercent: number;
|
|
||||||
sticker: ApiSticker;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarGiftAttributeBackdrop {
|
|
||||||
type: 'backdrop';
|
|
||||||
name: string;
|
|
||||||
centerColor: string;
|
|
||||||
edgeColor: string;
|
|
||||||
patternColor: string;
|
|
||||||
textColor: string;
|
|
||||||
rarityPercent: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarGiftAttributeOriginalDetails {
|
|
||||||
type: 'originalDetails';
|
|
||||||
senderId?: string;
|
|
||||||
recipientId: string;
|
|
||||||
date: number;
|
|
||||||
message?: ApiFormattedText;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiStarGiftAttribute = ApiStarGiftAttributeModel | ApiStarGiftAttributePattern
|
|
||||||
| ApiStarGiftAttributeBackdrop | ApiStarGiftAttributeOriginalDetails;
|
|
||||||
|
|
||||||
export interface ApiSavedStarGift {
|
|
||||||
isNameHidden?: boolean;
|
|
||||||
isUnsaved?: boolean;
|
|
||||||
fromId?: string;
|
|
||||||
date: number;
|
|
||||||
gift: ApiStarGift;
|
|
||||||
inputGift?: ApiInputSavedStarGift;
|
|
||||||
savedId?: string;
|
|
||||||
message?: ApiFormattedText;
|
|
||||||
messageId?: number;
|
|
||||||
starsToConvert?: number;
|
|
||||||
canUpgrade?: true;
|
|
||||||
alreadyPaidUpgradeStars?: number;
|
|
||||||
transferStars?: number;
|
|
||||||
canExportAt?: number;
|
|
||||||
isConverted?: boolean; // Local field, used for Action Message
|
|
||||||
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 {
|
export interface ApiPremiumGiftCodeOption {
|
||||||
users: number;
|
users: number;
|
||||||
months: number;
|
months: number;
|
||||||
@ -387,110 +280,6 @@ export type ApiCheckedGiftCode = {
|
|||||||
usedAt?: number;
|
usedAt?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ApiStarsAmount {
|
|
||||||
amount: number;
|
|
||||||
nanos: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerUnsupported {
|
|
||||||
type: 'unsupported';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerAppStore {
|
|
||||||
type: 'appStore';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerPlayMarket {
|
|
||||||
type: 'playMarket';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerPremiumBot {
|
|
||||||
type: 'premiumBot';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerFragment {
|
|
||||||
type: 'fragment';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerAds {
|
|
||||||
type: 'ads';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionApi {
|
|
||||||
type: 'api';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsTransactionPeerPeer {
|
|
||||||
type: 'peer';
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiStarsTransactionPeer =
|
|
||||||
| ApiStarsTransactionPeerUnsupported
|
|
||||||
| ApiStarsTransactionPeerAppStore
|
|
||||||
| ApiStarsTransactionPeerPlayMarket
|
|
||||||
| ApiStarsTransactionPeerPremiumBot
|
|
||||||
| ApiStarsTransactionPeerFragment
|
|
||||||
| ApiStarsTransactionPeerAds
|
|
||||||
| ApiStarsTransactionApi
|
|
||||||
| ApiStarsTransactionPeerPeer;
|
|
||||||
|
|
||||||
export interface ApiStarsTransaction {
|
|
||||||
id?: string;
|
|
||||||
peer: ApiStarsTransactionPeer;
|
|
||||||
messageId?: number;
|
|
||||||
stars: ApiStarsAmount;
|
|
||||||
isRefund?: true;
|
|
||||||
isGift?: true;
|
|
||||||
starGift?: ApiStarGift;
|
|
||||||
giveawayPostId?: number;
|
|
||||||
isMyGift?: true; // Used only for outgoing star gift messages
|
|
||||||
isReaction?: true;
|
|
||||||
hasFailed?: true;
|
|
||||||
isPending?: true;
|
|
||||||
date: number;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
photo?: ApiWebDocument;
|
|
||||||
extendedMedia?: BoughtPaidMedia[];
|
|
||||||
subscriptionPeriod?: number;
|
|
||||||
starRefCommision?: number;
|
|
||||||
isGiftUpgrade?: true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsSubscription {
|
|
||||||
id: string;
|
|
||||||
peerId: string;
|
|
||||||
until: number;
|
|
||||||
pricing: ApiStarsSubscriptionPricing;
|
|
||||||
isCancelled?: true;
|
|
||||||
canRefulfill?: true;
|
|
||||||
hasMissingBalance?: true;
|
|
||||||
chatInviteHash?: string;
|
|
||||||
hasBotCancelled?: true;
|
|
||||||
title?: string;
|
|
||||||
photo?: ApiWebDocument;
|
|
||||||
invoiceSlug?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiStarsSubscriptionPricing = {
|
|
||||||
period: number;
|
|
||||||
amount: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ApiStarTopupOption {
|
|
||||||
isExtended?: true;
|
|
||||||
stars: number;
|
|
||||||
currency: string;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarsGiveawayWinnerOption {
|
|
||||||
isDefault?: true;
|
|
||||||
users: number;
|
|
||||||
perUserStars: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ApiStarGiveawayOption {
|
export interface ApiStarGiveawayOption {
|
||||||
isExtended?: true;
|
isExtended?: true;
|
||||||
isDefault?: true;
|
isDefault?: true;
|
||||||
|
|||||||
216
src/api/types/stars.ts
Normal file
216
src/api/types/stars.ts
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import type { ApiWebDocument } from './bots';
|
||||||
|
import type { ApiChat } from './chats';
|
||||||
|
import type { ApiFormattedText, ApiSticker, BoughtPaidMedia } from './messages';
|
||||||
|
|
||||||
|
export interface ApiStarGiftRegular {
|
||||||
|
type: 'starGift';
|
||||||
|
isLimited?: true;
|
||||||
|
id: string;
|
||||||
|
sticker: ApiSticker;
|
||||||
|
stars: number;
|
||||||
|
availabilityRemains?: number;
|
||||||
|
availabilityTotal?: number;
|
||||||
|
starsToConvert: number;
|
||||||
|
isSoldOut?: true;
|
||||||
|
firstSaleDate?: number;
|
||||||
|
lastSaleDate?: number;
|
||||||
|
isBirthday?: true;
|
||||||
|
upgradeStars?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarGiftUnique {
|
||||||
|
type: 'starGiftUnique';
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
number: number;
|
||||||
|
ownerId?: string;
|
||||||
|
ownerName?: string;
|
||||||
|
ownerAddress?: string;
|
||||||
|
issuedCount: number;
|
||||||
|
totalCount: number;
|
||||||
|
attributes: ApiStarGiftAttribute[];
|
||||||
|
slug: string;
|
||||||
|
giftAddress?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiStarGift = ApiStarGiftRegular | ApiStarGiftUnique;
|
||||||
|
|
||||||
|
export interface ApiStarGiftAttributeModel {
|
||||||
|
type: 'model';
|
||||||
|
name: string;
|
||||||
|
rarityPercent: number;
|
||||||
|
sticker: ApiSticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarGiftAttributePattern {
|
||||||
|
type: 'pattern';
|
||||||
|
name: string;
|
||||||
|
rarityPercent: number;
|
||||||
|
sticker: ApiSticker;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarGiftAttributeBackdrop {
|
||||||
|
type: 'backdrop';
|
||||||
|
name: string;
|
||||||
|
centerColor: string;
|
||||||
|
edgeColor: string;
|
||||||
|
patternColor: string;
|
||||||
|
textColor: string;
|
||||||
|
rarityPercent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarGiftAttributeOriginalDetails {
|
||||||
|
type: 'originalDetails';
|
||||||
|
senderId?: string;
|
||||||
|
recipientId: string;
|
||||||
|
date: number;
|
||||||
|
message?: ApiFormattedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiStarGiftAttribute = ApiStarGiftAttributeModel | ApiStarGiftAttributePattern
|
||||||
|
| ApiStarGiftAttributeBackdrop | ApiStarGiftAttributeOriginalDetails;
|
||||||
|
|
||||||
|
export interface ApiSavedStarGift {
|
||||||
|
isNameHidden?: boolean;
|
||||||
|
isUnsaved?: boolean;
|
||||||
|
fromId?: string;
|
||||||
|
date: number;
|
||||||
|
gift: ApiStarGift;
|
||||||
|
inputGift?: ApiInputSavedStarGift;
|
||||||
|
savedId?: string;
|
||||||
|
message?: ApiFormattedText;
|
||||||
|
messageId?: number;
|
||||||
|
starsToConvert?: number;
|
||||||
|
canUpgrade?: true;
|
||||||
|
alreadyPaidUpgradeStars?: number;
|
||||||
|
transferStars?: number;
|
||||||
|
canExportAt?: number;
|
||||||
|
isPinned?: boolean;
|
||||||
|
isConverted?: boolean; // Local field, used for Action Message
|
||||||
|
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 ApiStarsAmount {
|
||||||
|
amount: number;
|
||||||
|
nanos: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerUnsupported {
|
||||||
|
type: 'unsupported';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerAppStore {
|
||||||
|
type: 'appStore';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerPlayMarket {
|
||||||
|
type: 'playMarket';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerPremiumBot {
|
||||||
|
type: 'premiumBot';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerFragment {
|
||||||
|
type: 'fragment';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerAds {
|
||||||
|
type: 'ads';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionApi {
|
||||||
|
type: 'api';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsTransactionPeerPeer {
|
||||||
|
type: 'peer';
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiStarsTransactionPeer =
|
||||||
|
| ApiStarsTransactionPeerUnsupported
|
||||||
|
| ApiStarsTransactionPeerAppStore
|
||||||
|
| ApiStarsTransactionPeerPlayMarket
|
||||||
|
| ApiStarsTransactionPeerPremiumBot
|
||||||
|
| ApiStarsTransactionPeerFragment
|
||||||
|
| ApiStarsTransactionPeerAds
|
||||||
|
| ApiStarsTransactionApi
|
||||||
|
| ApiStarsTransactionPeerPeer;
|
||||||
|
|
||||||
|
export interface ApiStarsTransaction {
|
||||||
|
id?: string;
|
||||||
|
peer: ApiStarsTransactionPeer;
|
||||||
|
messageId?: number;
|
||||||
|
stars: ApiStarsAmount;
|
||||||
|
isRefund?: true;
|
||||||
|
isGift?: true;
|
||||||
|
starGift?: ApiStarGift;
|
||||||
|
giveawayPostId?: number;
|
||||||
|
isMyGift?: true; // Used only for outgoing star gift messages
|
||||||
|
isReaction?: true;
|
||||||
|
hasFailed?: true;
|
||||||
|
isPending?: true;
|
||||||
|
date: number;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
photo?: ApiWebDocument;
|
||||||
|
extendedMedia?: BoughtPaidMedia[];
|
||||||
|
subscriptionPeriod?: number;
|
||||||
|
starRefCommision?: number;
|
||||||
|
isGiftUpgrade?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsSubscription {
|
||||||
|
id: string;
|
||||||
|
peerId: string;
|
||||||
|
until: number;
|
||||||
|
pricing: ApiStarsSubscriptionPricing;
|
||||||
|
isCancelled?: true;
|
||||||
|
canRefulfill?: true;
|
||||||
|
hasMissingBalance?: true;
|
||||||
|
chatInviteHash?: string;
|
||||||
|
hasBotCancelled?: true;
|
||||||
|
title?: string;
|
||||||
|
photo?: ApiWebDocument;
|
||||||
|
invoiceSlug?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiStarsSubscriptionPricing = {
|
||||||
|
period: number;
|
||||||
|
amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ApiStarTopupOption {
|
||||||
|
isExtended?: true;
|
||||||
|
stars: number;
|
||||||
|
currency: string;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiStarsGiveawayWinnerOption {
|
||||||
|
isDefault?: true;
|
||||||
|
users: number;
|
||||||
|
perUserStars: number;
|
||||||
|
}
|
||||||
@ -41,8 +41,8 @@ import type {
|
|||||||
ApiPeerNotifySettings,
|
ApiPeerNotifySettings,
|
||||||
ApiSessionData,
|
ApiSessionData,
|
||||||
} from './misc';
|
} from './misc';
|
||||||
import type { ApiStarsAmount } from './payments';
|
|
||||||
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
|
import type { ApiPrivacyKey, LangPackStringValue, PrivacyVisibility } from './settings';
|
||||||
|
import type { ApiStarsAmount } from './stars';
|
||||||
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
import type { ApiStealthMode, ApiStory, ApiStorySkipped } from './stories';
|
||||||
import type {
|
import type {
|
||||||
ApiEmojiStatusType, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
ApiEmojiStatusType, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from
|
|||||||
import type { ApiPeerColor } from './chats';
|
import type { ApiPeerColor } from './chats';
|
||||||
import type { ApiDocument, ApiPhoto } from './messages';
|
import type { ApiDocument, ApiPhoto } from './messages';
|
||||||
import type { ApiBotVerification } from './misc';
|
import type { ApiBotVerification } from './misc';
|
||||||
import type { ApiSavedStarGift } from './payments';
|
import type { ApiSavedStarGift } from './stars';
|
||||||
|
|
||||||
export interface ApiUser {
|
export interface ApiUser {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
@ -1413,6 +1413,8 @@
|
|||||||
"GiftInfoChannelHidden" = "This gift is hidden from visitors of your channel. {link}";
|
"GiftInfoChannelHidden" = "This gift is hidden from visitors of your channel. {link}";
|
||||||
"GiftInfoSavedHide" = "Hide >";
|
"GiftInfoSavedHide" = "Hide >";
|
||||||
"GiftInfoSavedShow" = "Show >";
|
"GiftInfoSavedShow" = "Show >";
|
||||||
|
"GiftActionShow" = "Show";
|
||||||
|
"GiftActionHide" = "Hide";
|
||||||
"GiftInfoTonText" = "This gift is on the TON Blockchain. {link}";
|
"GiftInfoTonText" = "This gift is on the TON Blockchain. {link}";
|
||||||
"GiftInfoTonLinkText" = "View >";
|
"GiftInfoTonLinkText" = "View >";
|
||||||
"GiftInfoAvailability" = "Availability";
|
"GiftInfoAvailability" = "Availability";
|
||||||
|
|||||||
@ -29,5 +29,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.transition {
|
.transition {
|
||||||
width: 1.5rem;
|
height: 1.5em;
|
||||||
|
width: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transitionSlide {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,10 +141,11 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
{canShowEmojiStatus && emojiStatus && (
|
{canShowEmojiStatus && emojiStatus && (
|
||||||
<Transition
|
<Transition
|
||||||
className={styles.transition}
|
className={styles.transition}
|
||||||
|
slideClassName={styles.transitionSlide}
|
||||||
activeKey={Number(emojiStatus.documentId)}
|
activeKey={Number(emojiStatus.documentId)}
|
||||||
name="fade"
|
name="slideFade"
|
||||||
|
direction={-1}
|
||||||
shouldCleanup
|
shouldCleanup
|
||||||
shouldRestoreHeight
|
|
||||||
>
|
>
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
forceAlways
|
forceAlways
|
||||||
|
|||||||
@ -150,7 +150,7 @@ const PasswordForm: FC<OwnProps> = ({
|
|||||||
title="Toggle password visibility"
|
title="Toggle password visibility"
|
||||||
aria-label="Toggle password visibility"
|
aria-label="Toggle password visibility"
|
||||||
>
|
>
|
||||||
<Icon name={isPasswordVisible ? 'eye' : 'eye-closed'} />
|
<Icon name={isPasswordVisible ? 'eye' : 'eye-crossed'} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{description && <p className="description">{description}</p>}
|
{description && <p className="description">{description}</p>}
|
||||||
|
|||||||
145
src/components/common/gift/GiftMenuItems.tsx
Normal file
145
src/components/common/gift/GiftMenuItems.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiEmojiStatusCollectible, ApiEmojiStatusType, ApiSavedStarGift, ApiStarGift,
|
||||||
|
} from '../../../api/types';
|
||||||
|
|
||||||
|
import { DEFAULT_STATUS_ICON_ID, TME_LINK_PREFIX } from '../../../config';
|
||||||
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
|
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
peerId: string;
|
||||||
|
canManage?: boolean;
|
||||||
|
gift: ApiSavedStarGift | ApiStarGift;
|
||||||
|
currentUserEmojiStatus?: ApiEmojiStatusType;
|
||||||
|
collectibleEmojiStatuses?: ApiEmojiStatusType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const GiftMenuItems = ({
|
||||||
|
peerId,
|
||||||
|
canManage,
|
||||||
|
gift: typeGift,
|
||||||
|
currentUserEmojiStatus,
|
||||||
|
collectibleEmojiStatuses,
|
||||||
|
}: OwnProps) => {
|
||||||
|
const {
|
||||||
|
showNotification,
|
||||||
|
openChatWithDraft,
|
||||||
|
openGiftTransferModal,
|
||||||
|
openGiftStatusInfoModal,
|
||||||
|
setEmojiStatus,
|
||||||
|
toggleSavedGiftPinned,
|
||||||
|
changeGiftVisibility,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isSavedGift = typeGift && 'gift' in typeGift;
|
||||||
|
const savedGift = isSavedGift ? typeGift : undefined;
|
||||||
|
const gift = isSavedGift ? typeGift.gift : typeGift;
|
||||||
|
|
||||||
|
const starGiftUniqueSlug = gift?.type === 'starGiftUnique' ? gift.slug : undefined;
|
||||||
|
const starGiftUniqueLink = useMemo(() => {
|
||||||
|
if (!starGiftUniqueSlug) return undefined;
|
||||||
|
return `${TME_LINK_PREFIX}nft/${starGiftUniqueSlug}`;
|
||||||
|
}, [starGiftUniqueSlug]);
|
||||||
|
const userCollectibleStatus = useMemo(() => {
|
||||||
|
if (!starGiftUniqueSlug) return undefined;
|
||||||
|
return collectibleEmojiStatuses?.find((
|
||||||
|
status,
|
||||||
|
) => status.type === 'collectible' && status.slug === starGiftUniqueSlug) as ApiEmojiStatusCollectible | undefined;
|
||||||
|
}, [starGiftUniqueSlug, collectibleEmojiStatuses]);
|
||||||
|
|
||||||
|
const currenUniqueEmojiStatusSlug = currentUserEmojiStatus?.type === 'collectible'
|
||||||
|
? currentUserEmojiStatus.slug : undefined;
|
||||||
|
|
||||||
|
const isGiftUnique = gift && gift.type === 'starGiftUnique';
|
||||||
|
const canTakeOff = isGiftUnique && currenUniqueEmojiStatusSlug === gift.slug;
|
||||||
|
const canWear = userCollectibleStatus && !canTakeOff;
|
||||||
|
|
||||||
|
const hasPinOptions = canManage && savedGift && !savedGift.isUnsaved && isGiftUnique;
|
||||||
|
|
||||||
|
const handleTriggerVisibility = useLastCallback(() => {
|
||||||
|
const { inputGift, isUnsaved } = savedGift!;
|
||||||
|
changeGiftVisibility({ gift: inputGift!, shouldUnsave: !isUnsaved });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCopyLink = useLastCallback(() => {
|
||||||
|
if (!starGiftUniqueLink) return;
|
||||||
|
copyTextToClipboard(starGiftUniqueLink);
|
||||||
|
showNotification({
|
||||||
|
message: lang('LinkCopied'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLinkShare = useLastCallback(() => {
|
||||||
|
if (!starGiftUniqueLink) return;
|
||||||
|
openChatWithDraft({ text: { text: starGiftUniqueLink } });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTransfer = useLastCallback(() => {
|
||||||
|
if (savedGift?.gift.type !== 'starGiftUnique') return;
|
||||||
|
openGiftTransferModal({ gift: savedGift });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleWear = useLastCallback(() => {
|
||||||
|
if (gift?.type !== 'starGiftUnique' || !userCollectibleStatus) return;
|
||||||
|
openGiftStatusInfoModal({ emojiStatus: userCollectibleStatus });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTakeOff = useLastCallback(() => {
|
||||||
|
if (canTakeOff) {
|
||||||
|
setEmojiStatus({
|
||||||
|
emojiStatus: { type: 'regular', documentId: DEFAULT_STATUS_ICON_ID },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTogglePin = useLastCallback(() => {
|
||||||
|
toggleSavedGiftPinned({ peerId, gift: savedGift! });
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{hasPinOptions && (
|
||||||
|
<MenuItem icon={savedGift.isPinned ? 'unpin' : 'pin'} onClick={handleTogglePin}>
|
||||||
|
{lang(savedGift.isPinned ? 'UnpinFromTop' : 'PinToTop')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem icon="link-badge" onClick={handleCopyLink}>
|
||||||
|
{lang('CopyLink')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="forward" onClick={handleLinkShare}>
|
||||||
|
{lang('Share')}
|
||||||
|
</MenuItem>
|
||||||
|
{canManage && isGiftUnique && (
|
||||||
|
<MenuItem icon="diamond" onClick={handleTransfer}>
|
||||||
|
{lang('GiftInfoTransfer')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canManage && savedGift && (
|
||||||
|
<MenuItem icon={savedGift.isUnsaved ? 'eye-outline' : 'eye-crossed-outline'} onClick={handleTriggerVisibility}>
|
||||||
|
{lang(savedGift.isUnsaved ? 'GiftActionShow' : 'GiftActionHide')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canWear && (
|
||||||
|
<MenuItem icon="crown-wear" onClick={handleWear}>
|
||||||
|
{lang('GiftInfoWear')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{canTakeOff && (
|
||||||
|
<MenuItem icon="crown-take-off" onClick={handleTakeOff}>
|
||||||
|
{lang('GiftInfoTakeOff')}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(GiftMenuItems);
|
||||||
@ -29,10 +29,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.topIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.25rem;
|
top: 0.25rem;
|
||||||
left: 0.25rem;
|
left: 0.25rem;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hiddenGift {
|
.hiddenGift {
|
||||||
|
|||||||
@ -1,23 +1,27 @@
|
|||||||
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiPeer, ApiSavedStarGift } from '../../../api/types';
|
import type { ApiEmojiStatusType, ApiPeer, ApiSavedStarGift } from '../../../api/types';
|
||||||
|
|
||||||
import { selectPeer } from '../../../global/selectors';
|
import { getHasAdminRight } from '../../../global/helpers';
|
||||||
|
import { selectChat, selectPeer, selectUser } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { CUSTOM_PEER_HIDDEN } from '../../../util/objects/customPeer';
|
import { CUSTOM_PEER_HIDDEN } from '../../../util/objects/customPeer';
|
||||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||||
import { getGiftAttributes, getStickerFromGift, getTotalGiftAvailability } from '../helpers/gifts';
|
import { getGiftAttributes, getStickerFromGift, getTotalGiftAvailability } from '../helpers/gifts';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
|
import Menu from '../../ui/Menu';
|
||||||
import AnimatedIconFromSticker from '../AnimatedIconFromSticker';
|
import AnimatedIconFromSticker from '../AnimatedIconFromSticker';
|
||||||
import Avatar from '../Avatar';
|
import Avatar from '../Avatar';
|
||||||
import Icon from '../icons/Icon';
|
import Icon from '../icons/Icon';
|
||||||
import RadialPatternBackground from '../profile/RadialPatternBackground';
|
import RadialPatternBackground from '../profile/RadialPatternBackground';
|
||||||
|
import GiftMenuItems from './GiftMenuItems';
|
||||||
import GiftRibbon from './GiftRibbon';
|
import GiftRibbon from './GiftRibbon';
|
||||||
|
|
||||||
import styles from './SavedGift.module.scss';
|
import styles from './SavedGift.module.scss';
|
||||||
@ -25,11 +29,16 @@ import styles from './SavedGift.module.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
gift: ApiSavedStarGift;
|
gift: ApiSavedStarGift;
|
||||||
|
style?: string;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
fromPeer?: ApiPeer;
|
fromPeer?: ApiPeer;
|
||||||
|
currentUserId?: string;
|
||||||
|
hasAdminRights?: boolean;
|
||||||
|
currentUserEmojiStatus?: ApiEmojiStatusType;
|
||||||
|
collectibleEmojiStatuses?: ApiEmojiStatusType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const GIFT_STICKER_SIZE = 90;
|
const GIFT_STICKER_SIZE = 90;
|
||||||
@ -37,7 +46,12 @@ const GIFT_STICKER_SIZE = 90;
|
|||||||
const SavedGift = ({
|
const SavedGift = ({
|
||||||
peerId,
|
peerId,
|
||||||
gift,
|
gift,
|
||||||
|
style,
|
||||||
fromPeer,
|
fromPeer,
|
||||||
|
currentUserId,
|
||||||
|
hasAdminRights,
|
||||||
|
collectibleEmojiStatuses,
|
||||||
|
currentUserEmojiStatus,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const { openGiftInfoModal } = getActions();
|
const { openGiftInfoModal } = getActions();
|
||||||
@ -49,6 +63,21 @@ const SavedGift = ({
|
|||||||
|
|
||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
|
||||||
|
const canManage = peerId === currentUserId || hasAdminRights;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
|
const getTriggerElement = useLastCallback(() => ref.current);
|
||||||
|
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll'));
|
||||||
|
const getMenuElement = useLastCallback(() => (
|
||||||
|
document.querySelector('#portals')?.querySelector('.saved-gift-context-menu .bubble')
|
||||||
|
));
|
||||||
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
openGiftInfoModal({
|
openGiftInfoModal({
|
||||||
peerId,
|
peerId,
|
||||||
@ -94,10 +123,14 @@ const SavedGift = ({
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={buildClassName(styles.root, 'scroll-item')}
|
className={buildClassName(styles.root, 'scroll-item')}
|
||||||
|
style={style}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onMouseDown={handleBeforeContextMenu}
|
||||||
>
|
>
|
||||||
{radialPatternBackdrop}
|
{radialPatternBackdrop}
|
||||||
{!radialPatternBackdrop && <Avatar className={styles.avatar} peer={avatarPeer} size="micro" />}
|
{!radialPatternBackdrop && <Avatar className={styles.topIcon} peer={avatarPeer} size="micro" />}
|
||||||
|
{gift.isPinned && <Icon name="pinned-message" className={styles.topIcon} />}
|
||||||
<AnimatedIconFromSticker
|
<AnimatedIconFromSticker
|
||||||
sticker={sticker}
|
sticker={sticker}
|
||||||
noLoop
|
noLoop
|
||||||
@ -107,7 +140,7 @@ const SavedGift = ({
|
|||||||
/>
|
/>
|
||||||
{gift.isUnsaved && (
|
{gift.isUnsaved && (
|
||||||
<div className={styles.hiddenGift}>
|
<div className={styles.hiddenGift}>
|
||||||
<Icon name="eye-closed-outline" />
|
<Icon name="eye-crossed-outline" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{totalIssued && (
|
{totalIssued && (
|
||||||
@ -116,16 +149,50 @@ const SavedGift = ({
|
|||||||
text={oldLang('Gift2Limited1OfRibbon', formatIntegerCompact(totalIssued))}
|
text={oldLang('Gift2Limited1OfRibbon', formatIntegerCompact(totalIssued))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{contextMenuAnchor !== undefined && (
|
||||||
|
<Menu
|
||||||
|
isOpen={isContextMenuOpen}
|
||||||
|
anchor={contextMenuAnchor}
|
||||||
|
className="saved-gift-context-menu"
|
||||||
|
autoClose
|
||||||
|
withPortal
|
||||||
|
getMenuElement={getMenuElement}
|
||||||
|
getTriggerElement={getTriggerElement}
|
||||||
|
getRootElement={getRootElement}
|
||||||
|
getLayout={getLayout}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
>
|
||||||
|
<GiftMenuItems
|
||||||
|
peerId={peerId}
|
||||||
|
gift={gift}
|
||||||
|
canManage={canManage}
|
||||||
|
collectibleEmojiStatuses={collectibleEmojiStatuses}
|
||||||
|
currentUserEmojiStatus={currentUserEmojiStatus}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { gift }): StateProps => {
|
(global, { peerId, gift }): StateProps => {
|
||||||
const fromPeer = gift.fromId ? selectPeer(global, gift.fromId) : undefined;
|
const fromPeer = gift.fromId ? selectPeer(global, gift.fromId) : undefined;
|
||||||
|
const chat = selectChat(global, peerId);
|
||||||
|
const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages');
|
||||||
|
|
||||||
|
const currentUserId = global.currentUserId;
|
||||||
|
const currentUser = currentUserId ? selectUser(global, currentUserId) : undefined;
|
||||||
|
const currentUserEmojiStatus = currentUser?.emojiStatus;
|
||||||
|
const collectibleEmojiStatuses = global.collectibleEmojiStatuses?.statuses;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fromPeer,
|
fromPeer,
|
||||||
|
hasAdminRights,
|
||||||
|
currentUserId,
|
||||||
|
currentUserEmojiStatus,
|
||||||
|
collectibleEmojiStatuses,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(SavedGift));
|
)(SavedGift));
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const STORY_FEATURE_DESCRIPTIONS = {
|
|||||||
|
|
||||||
const STORY_FEATURE_ICONS: Record<string, IconName> = {
|
const STORY_FEATURE_ICONS: Record<string, IconName> = {
|
||||||
stories_order: 'story-priority',
|
stories_order: 'story-priority',
|
||||||
stories_stealth: 'eye-closed-outline',
|
stories_stealth: 'eye-crossed-outline',
|
||||||
stories_views: 'eye-outline',
|
stories_views: 'eye-outline',
|
||||||
stories_timer: 'timer',
|
stories_timer: 'timer',
|
||||||
stories_save: 'arrow-down-circle',
|
stories_save: 'arrow-down-circle',
|
||||||
|
|||||||
@ -422,7 +422,7 @@ const TextFormatter: FC<OwnProps> = ({
|
|||||||
className={getFormatButtonClassName('spoiler')}
|
className={getFormatButtonClassName('spoiler')}
|
||||||
onClick={handleSpoilerText}
|
onClick={handleSpoilerText}
|
||||||
>
|
>
|
||||||
<Icon name="eye-closed" />
|
<Icon name="eye-crossed" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="TextFormatter-divider" />
|
<div className="TextFormatter-divider" />
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export type StateProps = {
|
|||||||
isPremium?: boolean;
|
isPremium?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const INTERVAL = 3000;
|
const INTERVAL = 3200;
|
||||||
|
|
||||||
const EmojiStatusAccessModal: FC<OwnProps & StateProps> = ({
|
const EmojiStatusAccessModal: FC<OwnProps & StateProps> = ({
|
||||||
modal,
|
modal,
|
||||||
|
|||||||
@ -3,13 +3,11 @@ import React, { memo, useMemo } from '../../../../lib/teact/teact';
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiEmojiStatusCollectible,
|
|
||||||
ApiEmojiStatusType,
|
ApiEmojiStatusType,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { TabState } from '../../../../global/types';
|
import type { TabState } from '../../../../global/types';
|
||||||
|
|
||||||
import { DEFAULT_STATUS_ICON_ID, TME_LINK_PREFIX } from '../../../../config';
|
|
||||||
import { getHasAdminRight, getPeerTitle } from '../../../../global/helpers';
|
import { getHasAdminRight, getPeerTitle } from '../../../../global/helpers';
|
||||||
import { isApiPeerChat } from '../../../../global/helpers/peers';
|
import { isApiPeerChat } from '../../../../global/helpers/peers';
|
||||||
import { selectPeer, selectUser } from '../../../../global/selectors';
|
import { selectPeer, selectUser } from '../../../../global/selectors';
|
||||||
@ -32,13 +30,13 @@ import useOldLang from '../../../../hooks/useOldLang';
|
|||||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
||||||
import Avatar from '../../../common/Avatar';
|
import Avatar from '../../../common/Avatar';
|
||||||
import BadgeButton from '../../../common/BadgeButton';
|
import BadgeButton from '../../../common/BadgeButton';
|
||||||
|
import GiftMenuItems from '../../../common/gift/GiftMenuItems';
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../../common/icons/Icon';
|
||||||
import SafeLink from '../../../common/SafeLink';
|
import SafeLink from '../../../common/SafeLink';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../../ui/Button';
|
||||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
||||||
import DropdownMenu from '../../../ui/DropdownMenu';
|
import DropdownMenu from '../../../ui/DropdownMenu';
|
||||||
import Link from '../../../ui/Link';
|
import Link from '../../../ui/Link';
|
||||||
import MenuItem from '../../../ui/MenuItem';
|
|
||||||
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
import TableInfoModal, { type TableData } from '../../common/TableInfoModal';
|
||||||
import UniqueGiftHeader from '../UniqueGiftHeader';
|
import UniqueGiftHeader from '../UniqueGiftHeader';
|
||||||
|
|
||||||
@ -80,10 +78,6 @@ const GiftInfoModal = ({
|
|||||||
focusMessage,
|
focusMessage,
|
||||||
openGiftUpgradeModal,
|
openGiftUpgradeModal,
|
||||||
showNotification,
|
showNotification,
|
||||||
openChatWithDraft,
|
|
||||||
openGiftStatusInfoModal,
|
|
||||||
setEmojiStatus,
|
|
||||||
openGiftTransferModal,
|
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
||||||
@ -111,27 +105,10 @@ const GiftInfoModal = ({
|
|||||||
const giftSticker = gift && getStickerFromGift(gift);
|
const giftSticker = gift && getStickerFromGift(gift);
|
||||||
const hasConvertOption = canConvertDifference > 0 && Boolean(savedGift?.starsToConvert);
|
const hasConvertOption = canConvertDifference > 0 && Boolean(savedGift?.starsToConvert);
|
||||||
|
|
||||||
const currenUniqueEmojiStatusSlug = currentUserEmojiStatus?.type === 'collectible'
|
|
||||||
? currentUserEmojiStatus.slug : undefined;
|
|
||||||
|
|
||||||
const starGiftUniqueSlug = gift?.type === 'starGiftUnique' ? gift.slug : undefined;
|
|
||||||
const starGiftUniqueLink = useMemo(() => {
|
|
||||||
if (!starGiftUniqueSlug) return undefined;
|
|
||||||
return `${TME_LINK_PREFIX}nft/${starGiftUniqueSlug}`;
|
|
||||||
}, [starGiftUniqueSlug]);
|
|
||||||
const userCollectibleStatus = useMemo(() => {
|
|
||||||
if (!starGiftUniqueSlug) return undefined;
|
|
||||||
return collectibleEmojiStatuses?.find((
|
|
||||||
status,
|
|
||||||
) => status.type === 'collectible' && status.slug === starGiftUniqueSlug) as ApiEmojiStatusCollectible | undefined;
|
|
||||||
}, [starGiftUniqueSlug, collectibleEmojiStatuses]);
|
|
||||||
|
|
||||||
const isGiftUnique = gift && gift.type === 'starGiftUnique';
|
const isGiftUnique = gift && gift.type === 'starGiftUnique';
|
||||||
const canTakeOff = isGiftUnique && currenUniqueEmojiStatusSlug === gift.slug;
|
|
||||||
const canWear = userCollectibleStatus && !canTakeOff;
|
|
||||||
|
|
||||||
const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId);
|
const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId);
|
||||||
const canUpdate = !canFocusUpgrade && savedGift?.inputGift && (
|
const canManage = !canFocusUpgrade && savedGift?.inputGift && (
|
||||||
isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId
|
isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -139,38 +116,6 @@ const GiftInfoModal = ({
|
|||||||
closeGiftInfoModal();
|
closeGiftInfoModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCopyLink = useLastCallback(() => {
|
|
||||||
if (!starGiftUniqueLink) return;
|
|
||||||
copyTextToClipboard(starGiftUniqueLink);
|
|
||||||
showNotification({
|
|
||||||
message: lang('LinkCopied'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleLinkShare = useLastCallback(() => {
|
|
||||||
if (!starGiftUniqueLink) return;
|
|
||||||
openChatWithDraft({ text: { text: starGiftUniqueLink } });
|
|
||||||
handleClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleTransfer = useLastCallback(() => {
|
|
||||||
if (savedGift?.gift.type !== 'starGiftUnique') return;
|
|
||||||
openGiftTransferModal({ gift: savedGift });
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleWear = useLastCallback(() => {
|
|
||||||
if (gift?.type !== 'starGiftUnique' || !userCollectibleStatus) return;
|
|
||||||
openGiftStatusInfoModal({ emojiStatus: userCollectibleStatus });
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleTakeOff = useLastCallback(() => {
|
|
||||||
if (canTakeOff) {
|
|
||||||
setEmojiStatus({
|
|
||||||
emojiStatus: { type: 'regular', documentId: DEFAULT_STATUS_ICON_ID },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleFocusUpgraded = useLastCallback(() => {
|
const handleFocusUpgraded = useLastCallback(() => {
|
||||||
const giftChat = isSender ? renderingTargetPeer : renderingFromPeer;
|
const giftChat = isSender ? renderingTargetPeer : renderingFromPeer;
|
||||||
if (!savedGift?.upgradeMsgId || !giftChat) return;
|
if (!savedGift?.upgradeMsgId || !giftChat) return;
|
||||||
@ -225,7 +170,7 @@ const GiftInfoModal = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canUpdate && savedGift?.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
|
if (canManage && savedGift?.alreadyPaidUpgradeStars && !savedGift.upgradeMsgId) {
|
||||||
return (
|
return (
|
||||||
<Button size="smaller" isShiny onClick={handleOpenUpgradeModal}>
|
<Button size="smaller" isShiny onClick={handleOpenUpgradeModal}>
|
||||||
{lang('GiftInfoUpgradeForFree')}
|
{lang('GiftInfoUpgradeForFree')}
|
||||||
@ -258,13 +203,13 @@ const GiftInfoModal = ({
|
|||||||
|
|
||||||
if (savedGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded');
|
if (savedGift.upgradeMsgId) return lang('GiftInfoDescriptionUpgraded');
|
||||||
if (savedGift.canUpgrade && savedGift.alreadyPaidUpgradeStars) {
|
if (savedGift.canUpgrade && savedGift.alreadyPaidUpgradeStars) {
|
||||||
return canUpdate
|
return canManage
|
||||||
? lang('GiftInfoDescriptionFreeUpgrade')
|
? lang('GiftInfoDescriptionFreeUpgrade')
|
||||||
: lang('GiftInfoPeerDescriptionFreeUpgradeOut', { peer: getPeerTitle(lang, renderingTargetPeer!)! });
|
: lang('GiftInfoPeerDescriptionFreeUpgradeOut', { peer: getPeerTitle(lang, renderingTargetPeer!)! });
|
||||||
}
|
}
|
||||||
if (!canUpdate && !isSender) return undefined;
|
if (!canManage && !isSender) return undefined;
|
||||||
if (isConverted && canConvert) {
|
if (isConverted && canConvert) {
|
||||||
return canUpdate
|
return canManage
|
||||||
? lang('GiftInfoDescriptionConverted', {
|
? lang('GiftInfoDescriptionConverted', {
|
||||||
amount: starsToConvert,
|
amount: starsToConvert,
|
||||||
}, {
|
}, {
|
||||||
@ -282,7 +227,7 @@ const GiftInfoModal = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedGift.canUpgrade && canUpdate) {
|
if (savedGift.canUpgrade && canManage) {
|
||||||
if (canConvert) {
|
if (canConvert) {
|
||||||
return lang('GiftInfoDescriptionUpgrade', {
|
return lang('GiftInfoDescriptionUpgrade', {
|
||||||
amount: starsToConvert,
|
amount: starsToConvert,
|
||||||
@ -296,7 +241,7 @@ const GiftInfoModal = ({
|
|||||||
return lang('GiftInfoDescriptionUpgradeRegular');
|
return lang('GiftInfoDescriptionUpgradeRegular');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canUpdate) {
|
if (canManage) {
|
||||||
if (canConvert) {
|
if (canConvert) {
|
||||||
return lang('GiftInfoDescription', {
|
return lang('GiftInfoDescription', {
|
||||||
amount: starsToConvert,
|
amount: starsToConvert,
|
||||||
@ -328,7 +273,7 @@ const GiftInfoModal = ({
|
|||||||
if (isGiftUnique) return gift.title;
|
if (isGiftUnique) return gift.title;
|
||||||
if (!savedGift) return lang('GiftInfoSoldOutTitle');
|
if (!savedGift) return lang('GiftInfoSoldOutTitle');
|
||||||
|
|
||||||
return canUpdate ? lang('GiftInfoReceived') : lang('GiftInfoTitle');
|
return canManage ? lang('GiftInfoReceived') : lang('GiftInfoTitle');
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueGiftContextMenu = (
|
const uniqueGiftContextMenu = (
|
||||||
@ -337,27 +282,13 @@ const GiftInfoModal = ({
|
|||||||
trigger={SettingsMenuButton}
|
trigger={SettingsMenuButton}
|
||||||
positionX="right"
|
positionX="right"
|
||||||
>
|
>
|
||||||
<MenuItem icon="link-badge" onClick={handleCopyLink}>
|
<GiftMenuItems
|
||||||
{lang('CopyLink')}
|
peerId={renderingModal!.peerId!}
|
||||||
</MenuItem>
|
gift={typeGift}
|
||||||
<MenuItem icon="forward" onClick={handleLinkShare}>
|
canManage={canManage}
|
||||||
{lang('Share')}
|
collectibleEmojiStatuses={collectibleEmojiStatuses}
|
||||||
</MenuItem>
|
currentUserEmojiStatus={currentUserEmojiStatus}
|
||||||
{canUpdate && isGiftUnique && (
|
/>
|
||||||
<MenuItem icon="diamond" onClick={handleTransfer}>
|
|
||||||
{lang('GiftInfoTransfer')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{canWear && (
|
|
||||||
<MenuItem icon="crown-wear" onClick={handleWear}>
|
|
||||||
{lang('GiftInfoWear')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
{canTakeOff && (
|
|
||||||
<MenuItem icon="crown-take-off" onClick={handleTakeOff}>
|
|
||||||
{lang('GiftInfoTakeOff')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -452,7 +383,7 @@ const GiftInfoModal = ({
|
|||||||
lang('GiftInfoValue'),
|
lang('GiftInfoValue'),
|
||||||
<div className={styles.giftValue}>
|
<div className={styles.giftValue}>
|
||||||
{formatStarsAsIcon(lang, starsValue, { className: styles.starAmountIcon })}
|
{formatStarsAsIcon(lang, starsValue, { className: styles.starAmountIcon })}
|
||||||
{canUpdate && hasConvertOption && Boolean(starsToConvert) && (
|
{canManage && hasConvertOption && Boolean(starsToConvert) && (
|
||||||
<BadgeButton onClick={openConvertConfirm}>
|
<BadgeButton onClick={openConvertConfirm}>
|
||||||
{lang('GiftInfoConvert', { amount: starsToConvert }, { pluralValue: starsToConvert })}
|
{lang('GiftInfoConvert', { amount: starsToConvert }, { pluralValue: starsToConvert })}
|
||||||
</BadgeButton>
|
</BadgeButton>
|
||||||
@ -477,7 +408,7 @@ const GiftInfoModal = ({
|
|||||||
lang('GiftInfoStatus'),
|
lang('GiftInfoStatus'),
|
||||||
<div className={styles.giftValue}>
|
<div className={styles.giftValue}>
|
||||||
{lang('GiftInfoStatusNonUnique')}
|
{lang('GiftInfoStatusNonUnique')}
|
||||||
{canUpdate && <BadgeButton onClick={handleOpenUpgradeModal}>{lang('GiftInfoUpgradeBadge')}</BadgeButton>}
|
{canManage && <BadgeButton onClick={handleOpenUpgradeModal}>{lang('GiftInfoUpgradeBadge')}</BadgeButton>}
|
||||||
</div>,
|
</div>,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -627,7 +558,7 @@ const GiftInfoModal = ({
|
|||||||
|
|
||||||
const footer = (
|
const footer = (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
{(canUpdate || tonLink) && (
|
{(canManage || tonLink) && (
|
||||||
<div className={styles.footerDescription}>
|
<div className={styles.footerDescription}>
|
||||||
{tonLink && (
|
{tonLink && (
|
||||||
<div>
|
<div>
|
||||||
@ -636,7 +567,7 @@ const GiftInfoModal = ({
|
|||||||
}, { withNodes: true })}
|
}, { withNodes: true })}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{canUpdate && (
|
{canManage && (
|
||||||
<div>
|
<div>
|
||||||
{lang(`GiftInfo${isTargetChat ? 'Channel' : ''}${isUnsaved ? 'Hidden' : 'Saved'}`, {
|
{lang(`GiftInfo${isTargetChat ? 'Channel' : ''}${isUnsaved ? 'Hidden' : 'Saved'}`, {
|
||||||
link: (
|
link: (
|
||||||
@ -668,9 +599,10 @@ const GiftInfoModal = ({
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
typeGift, savedGift, renderingTargetPeer, giftSticker, lang,
|
typeGift, savedGift, renderingTargetPeer, giftSticker, lang,
|
||||||
canUpdate, hasConvertOption, isSender, oldLang, tonExplorerUrl,
|
canManage, hasConvertOption, isSender, oldLang, tonExplorerUrl,
|
||||||
gift, giftAttributes, renderFooterButton, isTargetChat,
|
gift, giftAttributes, renderFooterButton, isTargetChat,
|
||||||
SettingsMenuButton, isOpen, isGiftUnique, canWear, canTakeOff,
|
SettingsMenuButton, isOpen, isGiftUnique, renderingModal,
|
||||||
|
collectibleEmojiStatuses, currentUserEmojiStatus,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -67,6 +67,7 @@ import { LOCAL_TGS_URLS } from '../common/helpers/animatedAssets';
|
|||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
import { getSenderName } from '../left/search/helpers/getSenderName';
|
import { getSenderName } from '../left/search/helpers/getSenderName';
|
||||||
|
|
||||||
|
import { useViewTransition } from '../../hooks/animations/useViewTransition';
|
||||||
import usePeerStoriesPolling from '../../hooks/polling/usePeerStoriesPolling';
|
import usePeerStoriesPolling from '../../hooks/polling/usePeerStoriesPolling';
|
||||||
import useCacheBuster from '../../hooks/useCacheBuster';
|
import useCacheBuster from '../../hooks/useCacheBuster';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
@ -130,7 +131,6 @@ type StateProps = {
|
|||||||
hasPreviewMediaTab?: boolean;
|
hasPreviewMediaTab?: boolean;
|
||||||
hasGiftsTab?: boolean;
|
hasGiftsTab?: boolean;
|
||||||
gifts?: ApiSavedStarGift[];
|
gifts?: ApiSavedStarGift[];
|
||||||
giftsTransitionKey: number;
|
|
||||||
areMembersHidden?: boolean;
|
areMembersHidden?: boolean;
|
||||||
canAddMembers?: boolean;
|
canAddMembers?: boolean;
|
||||||
canDeleteMembers?: boolean;
|
canDeleteMembers?: boolean;
|
||||||
@ -198,7 +198,6 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
hasPreviewMediaTab,
|
hasPreviewMediaTab,
|
||||||
hasGiftsTab,
|
hasGiftsTab,
|
||||||
gifts,
|
gifts,
|
||||||
giftsTransitionKey,
|
|
||||||
botPreviewMedia,
|
botPreviewMedia,
|
||||||
areMembersHidden,
|
areMembersHidden,
|
||||||
canAddMembers,
|
canAddMembers,
|
||||||
@ -356,9 +355,13 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [chatId, isBot, similarBots, isSynced]);
|
}, [chatId, isBot, similarBots, isSynced]);
|
||||||
|
|
||||||
const giftIds = useMemo(() => {
|
const [renderingGifts, setRenderingGifts] = useState(gifts);
|
||||||
return gifts?.map(({ date, gift, fromId }) => `${date}-${fromId}-${gift.id}`);
|
const { startViewTransition, shouldApplyVtn } = useViewTransition();
|
||||||
}, [gifts]);
|
|
||||||
|
const getGiftId = useLastCallback((gift: ApiSavedStarGift) => (
|
||||||
|
`${gift.date}-${gift.fromId}-${gift.gift.id}`
|
||||||
|
));
|
||||||
|
const giftIds = useMemo(() => renderingGifts?.map(getGiftId), [renderingGifts]);
|
||||||
|
|
||||||
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
|
const renderingActiveTab = activeTab > tabs.length - 1 ? tabs.length - 1 : activeTab;
|
||||||
const tabType = tabs[renderingActiveTab].type as ProfileTabType;
|
const tabType = tabs[renderingActiveTab].type as ProfileTabType;
|
||||||
@ -375,6 +378,25 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
loadPeerSavedGifts({ peerId: chatId });
|
loadPeerSavedGifts({ peerId: chatId });
|
||||||
}, [chatId]);
|
}, [chatId]);
|
||||||
|
|
||||||
|
useEffectWithPrevDeps(([prevGifts]) => {
|
||||||
|
if (!gifts || !prevGifts) {
|
||||||
|
setRenderingGifts(gifts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevGiftIds = prevGifts.map(getGiftId);
|
||||||
|
const newGiftIds = gifts.map(getGiftId);
|
||||||
|
const hasOrderChanged = prevGiftIds.some((id, index) => id !== newGiftIds[index]);
|
||||||
|
|
||||||
|
if (hasOrderChanged) {
|
||||||
|
startViewTransition(() => {
|
||||||
|
setRenderingGifts(gifts);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setRenderingGifts(gifts);
|
||||||
|
}
|
||||||
|
}, [gifts, startViewTransition]);
|
||||||
|
|
||||||
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
|
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds({
|
||||||
loadMoreMembers,
|
loadMoreMembers,
|
||||||
searchMessages: searchSharedMediaMessages,
|
searchMessages: searchSharedMediaMessages,
|
||||||
@ -816,38 +838,24 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : resultType === 'gifts' ? (
|
) : resultType === 'gifts' ? (
|
||||||
(gifts?.map((gift) => (
|
(renderingGifts?.map((gift) => {
|
||||||
<SavedGift
|
return (
|
||||||
peerId={chatId}
|
<SavedGift
|
||||||
key={`${gift.date}-${gift.fromId}-${gift.gift.id}`}
|
peerId={chatId}
|
||||||
gift={gift}
|
key={getGiftId(gift)}
|
||||||
observeIntersection={observeIntersectionForMedia}
|
style={shouldApplyVtn ? `view-transition-name: vt${getGiftId(gift)}` : undefined}
|
||||||
/>
|
gift={gift}
|
||||||
)))
|
observeIntersection={observeIntersectionForMedia}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}))
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldUseTransitionForContent = resultType === 'gifts';
|
|
||||||
const contentTransitionKey = giftsTransitionKey;
|
|
||||||
|
|
||||||
function renderContentWithTransition() {
|
|
||||||
return (
|
|
||||||
<Transition
|
|
||||||
className={`${resultType}-list`}
|
|
||||||
activeKey={contentTransitionKey}
|
|
||||||
name="fade"
|
|
||||||
>
|
|
||||||
{renderContent()}
|
|
||||||
</Transition>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeListSelector = `.shared-media-transition > .Transition_slide-active.${resultType}-list`;
|
const activeListSelector = `.shared-media-transition > .Transition_slide-active.${resultType}-list`;
|
||||||
const itemSelector = !shouldUseTransitionForContent
|
const itemSelector = `${activeListSelector} > .scroll-item`;
|
||||||
? `${activeListSelector} > .scroll-item`
|
|
||||||
: `${activeListSelector} > .Transition_slide-active > .content > .scroll-item`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
@ -881,7 +889,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
onStart={applyTransitionFix}
|
onStart={applyTransitionFix}
|
||||||
onStop={handleTransitionStop}
|
onStop={handleTransitionStop}
|
||||||
>
|
>
|
||||||
{shouldUseTransitionForContent ? renderContentWithTransition() : renderContent()}
|
{renderContent()}
|
||||||
</Transition>
|
</Transition>
|
||||||
<TabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
<TabList activeTab={renderingActiveTab} tabs={tabs} onSwitchTab={handleSwitchTab} />
|
||||||
</div>
|
</div>
|
||||||
@ -974,7 +982,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
|
const hasGiftsTab = Boolean(peerFullInfo?.starGiftCount) && !isSavedDialog;
|
||||||
const peerGifts = selectTabState(global).savedGifts.giftsByPeerId[chatId];
|
const peerGifts = selectTabState(global).savedGifts.giftsByPeerId[chatId];
|
||||||
const giftsTransitionKey = selectTabState(global).savedGifts.transitionKey || 0;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
@ -1000,7 +1007,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
storyIds,
|
storyIds,
|
||||||
hasGiftsTab,
|
hasGiftsTab,
|
||||||
gifts: peerGifts?.gifts,
|
gifts: peerGifts?.gifts,
|
||||||
giftsTransitionKey,
|
|
||||||
pinnedStoryIds,
|
pinnedStoryIds,
|
||||||
archiveStoryIds,
|
archiveStoryIds,
|
||||||
storyByIds,
|
storyByIds,
|
||||||
|
|||||||
@ -84,7 +84,7 @@ const StealthModeModal = ({ isOpen, stealthMode, isCurrentUserPremium } : StateP
|
|||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</Button>
|
</Button>
|
||||||
<div className={styles.stealthIcon}>
|
<div className={styles.stealthIcon}>
|
||||||
<Icon name="eye-closed-outline" />
|
<Icon name="eye-crossed-outline" />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.title}>{lang('StealthMode')}</div>
|
<div className={styles.title}>{lang('StealthMode')}</div>
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
|
|||||||
@ -730,7 +730,7 @@ function Story({
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{!isOut && isUserStory && (
|
{!isOut && isUserStory && (
|
||||||
<MenuItem icon="eye-closed-outline" onClick={handleOpenStealthModal}>
|
<MenuItem icon="eye-crossed-outline" onClick={handleOpenStealthModal}>
|
||||||
{lang('StealthMode')}
|
{lang('StealthMode')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
view-transition-name: open-menu-bubble;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.closing {
|
&.closing {
|
||||||
@ -115,3 +116,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hacky way to fix z-index issues with overlays in View Transitions
|
||||||
|
html::view-transition-group(open-menu-bubble) {
|
||||||
|
z-index: var(--z-portal-menu);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ApiSavedStarGift } from '../../../api/types';
|
import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
|
||||||
import type { StarGiftCategory } from '../../../types';
|
import type { StarGiftCategory } from '../../../types';
|
||||||
|
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -138,7 +138,7 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
|||||||
|
|
||||||
addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
peerId, shouldRefresh, withTransition, tabId = getCurrentTabId(),
|
peerId, shouldRefresh, tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const peer = selectPeer(global, peerId);
|
const peer = selectPeer(global, peerId);
|
||||||
@ -167,17 +167,6 @@ addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise
|
|||||||
|
|
||||||
const newGifts = currentGifts && !shouldRefresh ? currentGifts.gifts.concat(result.gifts) : result.gifts;
|
const newGifts = currentGifts && !shouldRefresh ? currentGifts.gifts.concat(result.gifts) : result.gifts;
|
||||||
|
|
||||||
const tabState = selectTabState(global, tabId);
|
|
||||||
|
|
||||||
if (withTransition) {
|
|
||||||
global = updateTabState(global, {
|
|
||||||
savedGifts: {
|
|
||||||
...tabState.savedGifts,
|
|
||||||
transitionKey: (tabState?.savedGifts.transitionKey || 0) + 1,
|
|
||||||
},
|
|
||||||
}, tabId);
|
|
||||||
}
|
|
||||||
|
|
||||||
global = replacePeerSavedGifts(global, peerId, newGifts, result.nextOffset, tabId);
|
global = replacePeerSavedGifts(global, peerId, newGifts, result.nextOffset, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
@ -295,7 +284,7 @@ addActionHandler('convertGiftToStars', async (global, actions, payload): Promise
|
|||||||
|
|
||||||
const peerId = gift.type === 'user' ? global.currentUserId! : gift.chatId;
|
const peerId = gift.type === 'user' ? global.currentUserId! : gift.chatId;
|
||||||
Object.values(global.byTabId).forEach((tabState) => {
|
Object.values(global.byTabId).forEach((tabState) => {
|
||||||
if (selectPeerSavedGifts(global, peerId, tabId)) {
|
if (selectPeerSavedGifts(global, peerId, tabState.id)) {
|
||||||
actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id });
|
actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -325,3 +314,36 @@ addActionHandler('openGiftUpgradeModal', async (global, actions, payload): Promi
|
|||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleSavedGiftPinned', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { gift, peerId, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
|
const peer = selectPeer(global, peerId);
|
||||||
|
if (!peer) return;
|
||||||
|
|
||||||
|
const savedGifts = selectPeerSavedGifts(global, peerId, tabId);
|
||||||
|
if (!savedGifts) return;
|
||||||
|
const pinLimit = global.appConfig?.savedGiftPinLimit;
|
||||||
|
const currentPinnedGifts = savedGifts.gifts.filter((g) => g.isPinned);
|
||||||
|
const newPinnedGifts = gift.isPinned
|
||||||
|
? currentPinnedGifts.filter((g) => (g.gift as ApiStarGiftUnique).slug !== (gift.gift as ApiStarGiftUnique).slug)
|
||||||
|
: [...currentPinnedGifts, gift];
|
||||||
|
|
||||||
|
const trimmedPinnedGifts = pinLimit ? newPinnedGifts.slice(-pinLimit) : newPinnedGifts;
|
||||||
|
|
||||||
|
const inputSavedGifts = trimmedPinnedGifts.map((g) => getRequestInputSavedStarGift(global, g.inputGift!))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const result = await callApi('toggleSavedGiftPinned', {
|
||||||
|
inputSavedGifts,
|
||||||
|
peer,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
Object.values(global.byTabId).forEach((tabState) => {
|
||||||
|
if (selectPeerSavedGifts(global, peerId, tabState.id)) {
|
||||||
|
actions.loadPeerSavedGifts({ peerId, shouldRefresh: true, tabId: tabState.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -110,7 +110,7 @@ addActionHandler('updateGiftProfileFilter', (global, actions, payload): ActionRe
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
actions.loadPeerSavedGifts({
|
actions.loadPeerSavedGifts({
|
||||||
peerId, shouldRefresh: true, withTransition: true, tabId: tabState.id,
|
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -132,6 +132,6 @@ addActionHandler('resetGiftProfileFilter', (global, actions, payload): ActionRet
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
actions.loadPeerSavedGifts({
|
actions.loadPeerSavedGifts({
|
||||||
peerId, shouldRefresh: true, withTransition: true, tabId: tabState.id,
|
peerId, shouldRefresh: true, tabId: tabState.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2412,7 +2412,6 @@ export interface ActionPayloads {
|
|||||||
loadPeerSavedGifts: {
|
loadPeerSavedGifts: {
|
||||||
peerId: string;
|
peerId: string;
|
||||||
shouldRefresh?: boolean;
|
shouldRefresh?: boolean;
|
||||||
withTransition?: boolean;
|
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
changeGiftVisibility: {
|
changeGiftVisibility: {
|
||||||
gift: ApiInputSavedStarGift;
|
gift: ApiInputSavedStarGift;
|
||||||
@ -2421,6 +2420,10 @@ export interface ActionPayloads {
|
|||||||
convertGiftToStars: {
|
convertGiftToStars: {
|
||||||
gift: ApiInputSavedStarGift;
|
gift: ApiInputSavedStarGift;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
toggleSavedGiftPinned: {
|
||||||
|
peerId: string;
|
||||||
|
gift: ApiSavedStarGift;
|
||||||
|
} & WithTabId;
|
||||||
|
|
||||||
openStarsGiftModal: ({
|
openStarsGiftModal: ({
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
|
|||||||
@ -210,7 +210,6 @@ export type TabState = {
|
|||||||
savedGifts: {
|
savedGifts: {
|
||||||
giftsByPeerId: Record<string, ApiSavedGifts>;
|
giftsByPeerId: Record<string, ApiSavedGifts>;
|
||||||
filter: GiftProfileFilterOptions;
|
filter: GiftProfileFilterOptions;
|
||||||
transitionKey?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
globalSearch: {
|
globalSearch: {
|
||||||
|
|||||||
75
src/hooks/animations/useViewTransition.ts
Normal file
75
src/hooks/animations/useViewTransition.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
|
import Deferred from '../../util/Deferred';
|
||||||
|
import { IS_VIEW_TRANSITION_SUPPORTED } from '../../util/windowEnvironment';
|
||||||
|
|
||||||
|
type TransitionFunction = () => Promise<void> | void;
|
||||||
|
|
||||||
|
type TransitionState = 'idle' | 'capturing-old' | 'capturing-new' | 'animating' | 'skipped';
|
||||||
|
interface ViewTransitionController {
|
||||||
|
transitionState: TransitionState;
|
||||||
|
shouldApplyVtn?: boolean;
|
||||||
|
startViewTransition: (domUpdateCallback?: TransitionFunction) => PromiseLike<void> | void;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasActiveTransition = false;
|
||||||
|
export function hasActiveViewTransition(): boolean {
|
||||||
|
return hasActiveTransition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useViewTransition(): ViewTransitionController {
|
||||||
|
const domUpdaterFn = useRef<TransitionFunction>();
|
||||||
|
const [transitionState, setTransitionState] = useState<TransitionState>('idle');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (transitionState !== 'capturing-old') return;
|
||||||
|
|
||||||
|
const transition = document.startViewTransition(async () => {
|
||||||
|
setTransitionState('capturing-new');
|
||||||
|
if (domUpdaterFn.current) await domUpdaterFn.current();
|
||||||
|
const deferred = new Deferred<void>();
|
||||||
|
requestNextMutation(() => {
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
transition.finished.then(() => {
|
||||||
|
setTransitionState('idle');
|
||||||
|
hasActiveTransition = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
transition.ready.then(() => {
|
||||||
|
setTransitionState('animating');
|
||||||
|
}).catch((e) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(e);
|
||||||
|
setTransitionState('skipped');
|
||||||
|
hasActiveTransition = false;
|
||||||
|
});
|
||||||
|
}, [transitionState]);
|
||||||
|
|
||||||
|
function startViewTransition(updateCallback?: TransitionFunction): PromiseLike<void> | void {
|
||||||
|
// Fallback: simply run the callback immediately if view transitions aren't supported.
|
||||||
|
if (!IS_VIEW_TRANSITION_SUPPORTED) {
|
||||||
|
if (updateCallback) updateCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
domUpdaterFn.current = updateCallback;
|
||||||
|
setTransitionState('capturing-old');
|
||||||
|
hasActiveTransition = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldApplyVtn: transitionState === 'capturing-old'
|
||||||
|
|| transitionState === 'capturing-new' || transitionState === 'animating',
|
||||||
|
transitionState,
|
||||||
|
startViewTransition,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import { useEffect } from '../lib/teact/teact';
|
import { useEffect } from '../lib/teact/teact';
|
||||||
|
|
||||||
|
import { hasActiveViewTransition } from './animations/useViewTransition';
|
||||||
|
|
||||||
const BACKDROP_CLASSNAME = 'backdrop';
|
const BACKDROP_CLASSNAME = 'backdrop';
|
||||||
|
|
||||||
// This effect implements closing menus by clicking outside of them
|
// This effect implements closing menus by clicking outside of them
|
||||||
@ -20,7 +22,7 @@ export default function useVirtualBackdrop(
|
|||||||
const handleEvent = (e: MouseEvent) => {
|
const handleEvent = (e: MouseEvent) => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
const target = e.target as HTMLElement | null;
|
const target = e.target as HTMLElement | null;
|
||||||
if (!container || !target || (ignoreRightClick && e.button === 2)) {
|
if (!container || !target || (ignoreRightClick && e.button === 2) || hasActiveViewTransition()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1729,6 +1729,7 @@ payments.transferStarGift#7f18176a stargift:InputSavedStarGift to_id:InputPeer =
|
|||||||
payments.getUniqueStarGift#a1974d72 slug:string = payments.UniqueStarGift;
|
payments.getUniqueStarGift#a1974d72 slug:string = payments.UniqueStarGift;
|
||||||
payments.getSavedStarGifts#23830de9 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer offset:string limit:int = payments.SavedStarGifts;
|
payments.getSavedStarGifts#23830de9 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer offset:string limit:int = payments.SavedStarGifts;
|
||||||
payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl;
|
payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl;
|
||||||
|
payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<InputSavedStarGift> = Bool;
|
||||||
phone.requestCall#a6c4600c flags:# video:flags.0?true user_id:InputUser conference_call:flags.1?InputGroupCall random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.requestCall#a6c4600c flags:# video:flags.0?true user_id:InputUser conference_call:flags.1?InputGroupCall random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall;
|
||||||
|
|||||||
@ -314,6 +314,7 @@
|
|||||||
"payments.transferStarGift",
|
"payments.transferStarGift",
|
||||||
"payments.getUniqueStarGift",
|
"payments.getUniqueStarGift",
|
||||||
"payments.getStarGiftWithdrawalUrl",
|
"payments.getStarGiftWithdrawalUrl",
|
||||||
|
"payments.toggleStarGiftsPinnedToTop",
|
||||||
"langpack.getLangPack",
|
"langpack.getLangPack",
|
||||||
"langpack.getStrings",
|
"langpack.getStrings",
|
||||||
"langpack.getLanguages",
|
"langpack.getLanguages",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -113,8 +113,8 @@ $icons-map: (
|
|||||||
"enter": "\f14c",
|
"enter": "\f14c",
|
||||||
"expand-modal": "\f14d",
|
"expand-modal": "\f14d",
|
||||||
"expand": "\f14e",
|
"expand": "\f14e",
|
||||||
"eye-closed-outline": "\f14f",
|
"eye-crossed-outline": "\f14f",
|
||||||
"eye-closed": "\f150",
|
"eye-crossed": "\f150",
|
||||||
"eye-outline": "\f151",
|
"eye-outline": "\f151",
|
||||||
"eye": "\f152",
|
"eye": "\f152",
|
||||||
"favorite-filled": "\f153",
|
"favorite-filled": "\f153",
|
||||||
@ -525,11 +525,11 @@ $icons-map: (
|
|||||||
.icon-expand::before {
|
.icon-expand::before {
|
||||||
content: map.get($icons-map, "expand");
|
content: map.get($icons-map, "expand");
|
||||||
}
|
}
|
||||||
.icon-eye-closed-outline::before {
|
.icon-eye-crossed-outline::before {
|
||||||
content: map.get($icons-map, "eye-closed-outline");
|
content: map.get($icons-map, "eye-crossed-outline");
|
||||||
}
|
}
|
||||||
.icon-eye-closed::before {
|
.icon-eye-crossed::before {
|
||||||
content: map.get($icons-map, "eye-closed");
|
content: map.get($icons-map, "eye-crossed");
|
||||||
}
|
}
|
||||||
.icon-eye-outline::before {
|
.icon-eye-outline::before {
|
||||||
content: map.get($icons-map, "eye-outline");
|
content: map.get($icons-map, "eye-outline");
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -77,8 +77,8 @@ export type FontIconName =
|
|||||||
| 'enter'
|
| 'enter'
|
||||||
| 'expand-modal'
|
| 'expand-modal'
|
||||||
| 'expand'
|
| 'expand'
|
||||||
| 'eye-closed-outline'
|
| 'eye-crossed-outline'
|
||||||
| 'eye-closed'
|
| 'eye-crossed'
|
||||||
| 'eye-outline'
|
| 'eye-outline'
|
||||||
| 'eye'
|
| 'eye'
|
||||||
| 'favorite-filled'
|
| 'favorite-filled'
|
||||||
|
|||||||
2
src/types/language.d.ts
vendored
2
src/types/language.d.ts
vendored
@ -1183,6 +1183,8 @@ export interface LangPair {
|
|||||||
'GiftInfoConvertDescription2': undefined;
|
'GiftInfoConvertDescription2': undefined;
|
||||||
'GiftInfoSavedHide': undefined;
|
'GiftInfoSavedHide': undefined;
|
||||||
'GiftInfoSavedShow': undefined;
|
'GiftInfoSavedShow': undefined;
|
||||||
|
'GiftActionShow': undefined;
|
||||||
|
'GiftActionHide': undefined;
|
||||||
'GiftInfoTonLinkText': undefined;
|
'GiftInfoTonLinkText': undefined;
|
||||||
'GiftInfoAvailability': undefined;
|
'GiftInfoAvailability': undefined;
|
||||||
'GiftInfoFirstSale': undefined;
|
'GiftInfoFirstSale': undefined;
|
||||||
|
|||||||
@ -114,6 +114,7 @@ export const IS_MULTITAB_SUPPORTED = 'BroadcastChannel' in window;
|
|||||||
export const IS_OPEN_IN_NEW_TAB_SUPPORTED = IS_MULTITAB_SUPPORTED && !(IS_PWA && IS_MOBILE);
|
export const IS_OPEN_IN_NEW_TAB_SUPPORTED = IS_MULTITAB_SUPPORTED && !(IS_PWA && IS_MOBILE);
|
||||||
export const IS_TRANSLATION_SUPPORTED = !IS_TEST;
|
export const IS_TRANSLATION_SUPPORTED = !IS_TEST;
|
||||||
export const IS_INTL_LIST_FORMAT_SUPPORTED = 'ListFormat' in Intl;
|
export const IS_INTL_LIST_FORMAT_SUPPORTED = 'ListFormat' in Intl;
|
||||||
|
export const IS_VIEW_TRANSITION_SUPPORTED = 'ViewTransition' in window;
|
||||||
|
|
||||||
export const IS_BAD_URL_PARSER = new URL('tg://host').host !== 'host';
|
export const IS_BAD_URL_PARSER = new URL('tg://host').host !== 'host';
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user