Gifts Modal: Support marketplace (#5936)
This commit is contained in:
parent
d7ea4a5748
commit
4330472674
@ -1,3 +1,4 @@
|
|||||||
|
import bigInt from 'big-integer';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -6,13 +7,18 @@ import type {
|
|||||||
ApiSavedStarGift,
|
ApiSavedStarGift,
|
||||||
ApiStarGift,
|
ApiStarGift,
|
||||||
ApiStarGiftAttribute,
|
ApiStarGiftAttribute,
|
||||||
|
ApiStarGiftAttributeCounter,
|
||||||
|
ApiStarGiftAttributeId,
|
||||||
|
ApiTypeResaleStarGifts,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { numberToHexColor } from '../../../util/colors';
|
import { numberToHexColor } from '../../../util/colors';
|
||||||
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { addDocumentToLocalDb } from '../helpers/localDb';
|
import { addDocumentToLocalDb } from '../helpers/localDb';
|
||||||
import { buildApiFormattedText } from './common';
|
import { buildApiFormattedText } from './common';
|
||||||
import { getApiChatIdFromMtpPeer } from './peers';
|
import { getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { buildStickerFromDocument } from './symbols';
|
import { buildStickerFromDocument } from './symbols';
|
||||||
|
import { buildApiUser } from './users';
|
||||||
|
|
||||||
export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
||||||
if (starGift instanceof GramJs.StarGiftUnique) {
|
if (starGift instanceof GramJs.StarGiftUnique) {
|
||||||
@ -40,7 +46,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut,
|
id, limited, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, soldOut,
|
||||||
birthday, upgradeStars, resellMinStars, title,
|
birthday, upgradeStars, resellMinStars, title, availabilityResale,
|
||||||
} = starGift;
|
} = starGift;
|
||||||
|
|
||||||
addDocumentToLocalDb(starGift.sticker);
|
addDocumentToLocalDb(starGift.sticker);
|
||||||
@ -63,6 +69,7 @@ export function buildApiStarGift(starGift: GramJs.TypeStarGift): ApiStarGift {
|
|||||||
upgradeStars: upgradeStars?.toJSNumber(),
|
upgradeStars: upgradeStars?.toJSNumber(),
|
||||||
title,
|
title,
|
||||||
resellMinStars: resellMinStars?.toJSNumber(),
|
resellMinStars: resellMinStars?.toJSNumber(),
|
||||||
|
availabilityResale: availabilityResale?.toJSNumber(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,11 +108,12 @@ export function buildApiStarGiftAttribute(attribute: GramJs.TypeStarGiftAttribut
|
|||||||
|
|
||||||
if (attribute instanceof GramJs.StarGiftAttributeBackdrop) {
|
if (attribute instanceof GramJs.StarGiftAttributeBackdrop) {
|
||||||
const {
|
const {
|
||||||
name, rarityPermille, centerColor, edgeColor, patternColor, textColor,
|
name, rarityPermille, centerColor, edgeColor, patternColor, textColor, backdropId,
|
||||||
} = attribute;
|
} = attribute;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'backdrop',
|
type: 'backdrop',
|
||||||
|
backdropId,
|
||||||
name,
|
name,
|
||||||
rarityPercent: rarityPermille / 10,
|
rarityPercent: rarityPermille / 10,
|
||||||
centerColor: numberToHexColor(centerColor),
|
centerColor: numberToHexColor(centerColor),
|
||||||
@ -180,3 +188,92 @@ export function buildApiDisallowedGiftsSettings(
|
|||||||
shouldDisallowPremiumGifts: disallowPremiumGifts,
|
shouldDisallowPremiumGifts: disallowPremiumGifts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiStarGiftAttributeId(
|
||||||
|
result: GramJs.TypeStarGiftAttributeId,
|
||||||
|
): ApiStarGiftAttributeId | undefined {
|
||||||
|
if (result instanceof GramJs.StarGiftAttributeIdModel) {
|
||||||
|
return {
|
||||||
|
type: 'model',
|
||||||
|
documentId: result.documentId.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof GramJs.StarGiftAttributeIdPattern) {
|
||||||
|
return {
|
||||||
|
type: 'pattern',
|
||||||
|
documentId: result.documentId.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result instanceof GramJs.StarGiftAttributeIdBackdrop) {
|
||||||
|
return {
|
||||||
|
type: 'backdrop',
|
||||||
|
backdropId: result.backdropId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiStarGiftAttributeCounter(
|
||||||
|
result: GramJs.TypeStarGiftAttributeCounter,
|
||||||
|
): ApiStarGiftAttributeCounter | undefined {
|
||||||
|
const {
|
||||||
|
count,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
const attribute = buildApiStarGiftAttributeId(result.attribute);
|
||||||
|
if (!attribute) return undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
count,
|
||||||
|
attribute,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApiResaleGifts(
|
||||||
|
result: GramJs.payments.TypeResaleStarGifts,
|
||||||
|
): ApiTypeResaleStarGifts {
|
||||||
|
const {
|
||||||
|
count,
|
||||||
|
nextOffset,
|
||||||
|
attributesHash,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
const gifts = result.gifts.map((g) => buildApiStarGift(g));
|
||||||
|
const attributes = result.attributes?.map((a) => buildApiStarGiftAttribute(a)).filter(Boolean);
|
||||||
|
const users = result.users.map((u) => buildApiUser(u)).filter(Boolean);
|
||||||
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
|
const counters = result.counters?.map((c) => buildApiStarGiftAttributeCounter(c)).filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
count,
|
||||||
|
gifts,
|
||||||
|
nextOffset,
|
||||||
|
attributes,
|
||||||
|
attributesHash: attributesHash?.toString(),
|
||||||
|
chats,
|
||||||
|
counters,
|
||||||
|
users,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildInputResaleGiftsAttributes(attributes: ApiStarGiftAttributeId[]):
|
||||||
|
GramJs.TypeStarGiftAttributeId[] {
|
||||||
|
return attributes.map((attr) => {
|
||||||
|
switch (attr.type) {
|
||||||
|
case 'model':
|
||||||
|
return new GramJs.StarGiftAttributeIdModel({ documentId: bigInt(attr.documentId) });
|
||||||
|
|
||||||
|
case 'pattern':
|
||||||
|
return new GramJs.StarGiftAttributeIdPattern({ documentId: bigInt(attr.documentId) });
|
||||||
|
|
||||||
|
case 'backdrop':
|
||||||
|
return new GramJs.StarGiftAttributeIdBackdrop({ backdropId: attr.backdropId });
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown attribute type: ${(attr as any).type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,15 +1,17 @@
|
|||||||
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 { GiftProfileFilterOptions, ResaleGiftsFilterOptions } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiRequestInputSavedStarGift,
|
ApiRequestInputSavedStarGift,
|
||||||
|
ApiStarGiftAttributeId,
|
||||||
ApiStarGiftRegular,
|
ApiStarGiftRegular,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildApiSavedStarGift, buildApiStarGift, buildApiStarGiftAttribute } from '../apiBuilders/gifts';
|
import { buildApiResaleGifts, buildApiSavedStarGift, buildApiStarGift,
|
||||||
|
buildApiStarGiftAttribute, buildInputResaleGiftsAttributes } from '../apiBuilders/gifts';
|
||||||
import {
|
import {
|
||||||
buildApiStarsAmount,
|
buildApiStarsAmount,
|
||||||
buildApiStarsGiftOptions,
|
buildApiStarsGiftOptions,
|
||||||
@ -44,6 +46,48 @@ export async function fetchStarGifts() {
|
|||||||
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
|
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchResaleGifts({
|
||||||
|
giftId,
|
||||||
|
offset = '',
|
||||||
|
limit,
|
||||||
|
attributesHash = '0',
|
||||||
|
filter,
|
||||||
|
}: {
|
||||||
|
giftId: string;
|
||||||
|
offset?: string;
|
||||||
|
limit?: number;
|
||||||
|
attributesHash?: string;
|
||||||
|
filter?: ResaleGiftsFilterOptions;
|
||||||
|
}) {
|
||||||
|
type GetResaleStarGifts = ConstructorParameters<typeof GramJs.payments.GetResaleStarGifts>[0];
|
||||||
|
|
||||||
|
const attributes: ApiStarGiftAttributeId[] = [
|
||||||
|
...(filter?.backdropAttributes ?? []),
|
||||||
|
...(filter?.modelAttributes ?? []),
|
||||||
|
...(filter?.patternAttributes ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
const params: GetResaleStarGifts = {
|
||||||
|
giftId: bigInt(giftId),
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
attributesHash: attributesHash ? bigInt(attributesHash) : undefined,
|
||||||
|
attributes: buildInputResaleGiftsAttributes(attributes),
|
||||||
|
...(filter && {
|
||||||
|
sortByPrice: filter.sortType === 'byPrice' || undefined,
|
||||||
|
sortByNum: filter.sortType === 'byNumber' || undefined,
|
||||||
|
} satisfies GetResaleStarGifts),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await invokeRequest(new GramJs.payments.GetResaleStarGifts(params));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiResaleGifts(result);
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchSavedStarGifts({
|
export async function fetchSavedStarGifts({
|
||||||
peer,
|
peer,
|
||||||
offset = '',
|
offset = '',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { ApiWebDocument } from './bots';
|
import type { ApiWebDocument } from './bots';
|
||||||
import type { ApiChat } from './chats';
|
import type { ApiChat } from './chats';
|
||||||
import type { ApiFormattedText, ApiSticker, BoughtPaidMedia } from './messages';
|
import type { ApiFormattedText, ApiSticker, BoughtPaidMedia } from './messages';
|
||||||
|
import type { ApiUser } from './users';
|
||||||
|
|
||||||
export interface ApiStarGiftRegular {
|
export interface ApiStarGiftRegular {
|
||||||
type: 'starGift';
|
type: 'starGift';
|
||||||
@ -10,6 +11,7 @@ export interface ApiStarGiftRegular {
|
|||||||
stars: number;
|
stars: number;
|
||||||
availabilityRemains?: number;
|
availabilityRemains?: number;
|
||||||
availabilityTotal?: number;
|
availabilityTotal?: number;
|
||||||
|
availabilityResale?: number;
|
||||||
starsToConvert: number;
|
starsToConvert: number;
|
||||||
isSoldOut?: true;
|
isSoldOut?: true;
|
||||||
firstSaleDate?: number;
|
firstSaleDate?: number;
|
||||||
@ -54,6 +56,7 @@ export interface ApiStarGiftAttributePattern {
|
|||||||
|
|
||||||
export interface ApiStarGiftAttributeBackdrop {
|
export interface ApiStarGiftAttributeBackdrop {
|
||||||
type: 'backdrop';
|
type: 'backdrop';
|
||||||
|
backdropId: number;
|
||||||
name: string;
|
name: string;
|
||||||
centerColor: string;
|
centerColor: string;
|
||||||
edgeColor: string;
|
edgeColor: string;
|
||||||
@ -95,6 +98,37 @@ export interface ApiSavedStarGift {
|
|||||||
upgradeMsgId?: number; // Local field, used for Action Message
|
upgradeMsgId?: number; // Local field, used for Action Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StarGiftAttributeIdModel = {
|
||||||
|
type: 'model';
|
||||||
|
documentId: string;
|
||||||
|
};
|
||||||
|
export type ApiStarGiftAttributeIdPattern = {
|
||||||
|
type: 'pattern';
|
||||||
|
documentId: string;
|
||||||
|
};
|
||||||
|
export type ApiStarGiftAttributeIdBackdrop = {
|
||||||
|
type: 'backdrop';
|
||||||
|
backdropId: number;
|
||||||
|
};
|
||||||
|
export type ApiStarGiftAttributeId = StarGiftAttributeIdModel |
|
||||||
|
ApiStarGiftAttributeIdPattern | ApiStarGiftAttributeIdBackdrop;
|
||||||
|
|
||||||
|
export interface ApiStarGiftAttributeCounter<T extends ApiStarGiftAttributeId = ApiStarGiftAttributeId> {
|
||||||
|
attribute: T;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiTypeResaleStarGifts {
|
||||||
|
count: number;
|
||||||
|
gifts: ApiStarGift[];
|
||||||
|
nextOffset?: string;
|
||||||
|
attributes?: ApiStarGiftAttribute[];
|
||||||
|
attributesHash?: string;
|
||||||
|
chats: ApiChat[];
|
||||||
|
counters?: ApiStarGiftAttributeCounter[];
|
||||||
|
users: ApiUser[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiInputSavedStarGiftUser {
|
export interface ApiInputSavedStarGiftUser {
|
||||||
type: 'user';
|
type: 'user';
|
||||||
messageId: number;
|
messageId: number;
|
||||||
|
|||||||
1
src/assets/font-icons/dropdown-arrows.svg
Normal file
1
src/assets/font-icons/dropdown-arrows.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="m16 29-5.558-5.483a1.475 1.475 0 0 1 0-2.106 1.524 1.524 0 0 1 2.135 0L16 24.788l3.423-3.377a1.524 1.524 0 0 1 2.135 0c.59.581.59 1.524 0 2.106zm-3.423-18.41a1.524 1.524 0 0 1-2.135 0 1.475 1.475 0 0 1 0-2.107L16 3l5.558 5.483c.59.582.59 1.525 0 2.106a1.524 1.524 0 0 1-2.135 0L16 7.212z"/></svg>
|
||||||
|
After Width: | Height: | Size: 392 B |
1
src/assets/font-icons/sort-by-date.svg
Normal file
1
src/assets/font-icons/sort-by-date.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M29.722 21.277c.375.34.37.918-.011 1.251a.904.904 0 0 1-1.193-.01l-2.615-2.368v7.977c0 .482-.404.873-.903.873s-.903-.391-.903-.873V20.15l-2.615 2.369a.904.904 0 0 1-1.193.01.832.832 0 0 1-.01-1.252L25 17z"/><path fill="#000" d="M20 4a1 1 0 0 1 1 1v1a5 5 0 0 1 5 5v2.5a1 1 0 1 1-2 0V11a3 3 0 0 0-3-3v1a1 1 0 1 1-2 0V8H9v1a1 1 0 0 1-2 0V8a3 3 0 0 0-3 3v11a3 3 0 0 0 3 3h12.5a1 1 0 1 1 0 2H7a5 5 0 0 1-5-5V11a5 5 0 0 1 5-5V5a1 1 0 0 1 2 0v1h10V5a1 1 0 0 1 1-1"/><path fill="#000" d="M21 12a1 1 0 1 1 0 2H7a1 1 0 1 1 0-2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 622 B |
1
src/assets/font-icons/sort-by-number.svg
Normal file
1
src/assets/font-icons/sort-by-number.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M29.722 21.277c.375.34.37.918-.012 1.252a.9.9 0 0 1-1.192-.01l-2.615-2.369v7.977c0 .482-.404.873-.903.873s-.903-.391-.903-.873V20.15l-2.615 2.368a.904.904 0 0 1-1.193.011.832.832 0 0 1-.01-1.252L25 17zM18.755 7.72a1 1 0 0 1 1.94.449l-.02.1-.86 3.73H25l.102.006a1.001 1.001 0 0 1 0 1.99L25 14h-5.648l-2.58 11.18a1 1 0 0 1-1.95-.449L15.684 21H9.736l-.965 4.18a1 1 0 0 1-1.949-.449L7.684 21H3a1 1 0 0 1 0-2h5.146L9.3 14H5a1 1 0 0 1 0-2h4.762l.965-4.18.028-.1a1 1 0 0 1 1.94.449l-.02.1-.86 3.73h5.947l.965-4.18zM10.198 19H16l.102.005q.02.002.04.006L17.3 14h-5.948z"/></svg>
|
||||||
|
After Width: | Height: | Size: 665 B |
1
src/assets/font-icons/sort-by-price.svg
Normal file
1
src/assets/font-icons/sort-by-price.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#000" d="M29.722 21.278c.375.34.37.917-.012 1.25a.9.9 0 0 1-1.192-.01l-2.615-2.368v7.977c0 .482-.404.873-.903.873s-.903-.39-.903-.873V20.15l-2.615 2.369a.9.9 0 0 1-1.192.01.83.83 0 0 1-.012-1.251L25 17zM14.924 24a.866.866 0 0 1-.876-.856V22.09a4.44 4.44 0 0 1-2.005-1.022q-.61-.546-.985-1.416c-.172-.4.054-.844.463-1.012.484-.2 1.024.065 1.28.511q.255.442.6.739.646.555 1.699.555.897 0 1.522-.41.625-.412.625-1.278 0-.778-.482-1.234-.483-.455-2.235-1.033-1.884-.6-2.586-1.433-.7-.834-.701-2.034 0-1.444.92-2.244t1.885-.911V8.856c0-.473.392-.856.876-.856s.877.383.877.856v1.01q1.095.18 1.807.812.412.364.694.823c.24.386.023.864-.399 1.049-.472.207-1.003-.047-1.342-.429a2 2 0 0 0-.278-.254q-.483-.356-1.315-.356-.964 0-1.468.433a1.37 1.37 0 0 0-.504 1.078q0 .734.657 1.156.658.422 2.28.889 1.51.444 2.29 1.41A3.46 3.46 0 0 1 19 18.712q0 1.578-.92 2.4t-2.28 1.022v1.011a.866.866 0 0 1-.876.856"/><path fill="#000" d="M2 16.5C2 9.596 7.596 4 14.5 4c5.456 0 10.095 3.495 11.8 8.367l.156.477.025.098a1 1 0 0 1-1.903.581l-.034-.095-.13-.4C22.98 8.934 19.08 6 14.5 6 8.701 6 4 10.701 4 16.5S8.701 27 14.5 27c2.01 0 3.884-.563 5.477-1.54a1 1 0 1 1 1.046 1.705A12.45 12.45 0 0 1 14.5 29C7.596 29 2 23.404 2 16.5"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -1977,5 +1977,25 @@
|
|||||||
"StarGiftSaleTransaction" = "Gift Purchase";
|
"StarGiftSaleTransaction" = "Gift Purchase";
|
||||||
"StarGiftPurchaseTransaction" = "Gift Sale";
|
"StarGiftPurchaseTransaction" = "Gift Sale";
|
||||||
"GiftBuyConfirmDescription" = "Do you want to buy **{gift}** for **{stars}**?";
|
"GiftBuyConfirmDescription" = "Do you want to buy **{gift}** for **{stars}**?";
|
||||||
|
"GiftBuyForPeerConfirmDescription" = "Do you want to buy **{gift}** for **{stars}** and gift it to **{peer}**?";
|
||||||
"ComposerTitleForwardFrom" = "From: **{users}**";
|
"ComposerTitleForwardFrom" = "From: **{users}**";
|
||||||
"ContextMenuItemMention" = "Mention";
|
"ContextMenuItemMention" = "Mention";
|
||||||
|
"GiftRibbonResale" = "resale";
|
||||||
|
"GiftCategoryResale" = "Resale";
|
||||||
|
"HeaderDescriptionResaleGifts_one" = "{count} for resale";
|
||||||
|
"HeaderDescriptionResaleGifts_other" = "{count} for resale";
|
||||||
|
"GiftSortByPrice" = "Sort by Price";
|
||||||
|
"GiftSortByNumber" = "Sort by Number";
|
||||||
|
"ContextMenuItemSelectAll" = "Select All";
|
||||||
|
"ButtonSort" = "Sort";
|
||||||
|
"GiftAttributeModelPlural_one" = "{count} Model";
|
||||||
|
"GiftAttributeModelPlural_other" = "{count} Models";
|
||||||
|
"GiftAttributeBackdropPlural_one" = "{count} Backdrop";
|
||||||
|
"GiftAttributeBackdropPlural_other" = "{count} Backdrops";
|
||||||
|
"GiftAttributeSymbolPlural_one" = "{count} Symbol";
|
||||||
|
"GiftAttributeSymbolPlural_other" = "{count} Symbols";
|
||||||
|
"ValueGiftSortByDate" = "Date";
|
||||||
|
"ValueGiftSortByPrice" = "Price";
|
||||||
|
"ValueGiftSortByNumber" = "Number";
|
||||||
|
"ResellGiftsNoFound" = "No gifts found";
|
||||||
|
"ResellGiftsClearFilters" = "Clear Filters";
|
||||||
@ -19,9 +19,10 @@ const COLORS = {
|
|||||||
type ColorKey = keyof typeof COLORS;
|
type ColorKey = keyof typeof COLORS;
|
||||||
|
|
||||||
const COLOR_KEYS = new Set(Object.keys(COLORS) as ColorKey[]);
|
const COLOR_KEYS = new Set(Object.keys(COLORS) as ColorKey[]);
|
||||||
|
type GradientColor = readonly [string, string];
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
color: ColorKey | (string & {});
|
color: ColorKey | GradientColor | (string & {});
|
||||||
text: string;
|
text: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@ -40,7 +41,13 @@ const GiftRibbon = ({
|
|||||||
|
|
||||||
const isDarkTheme = theme === 'dark';
|
const isDarkTheme = theme === 'dark';
|
||||||
|
|
||||||
const gradientColor = colorKey ? COLORS[colorKey][isDarkTheme ? 1 : 0] : undefined;
|
const gradientColor: GradientColor | undefined
|
||||||
|
= Array.isArray(color)
|
||||||
|
? color as GradientColor
|
||||||
|
: colorKey
|
||||||
|
? COLORS[colorKey][isDarkTheme ? 1 : 0]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const startColor = gradientColor ? gradientColor[0] : color;
|
const startColor = gradientColor ? gradientColor[0] : color;
|
||||||
const endColor = gradientColor ? gradientColor[1] : color;
|
const endColor = gradientColor ? gradientColor[1] : color;
|
||||||
|
|
||||||
|
|||||||
@ -94,13 +94,14 @@ function GiftComposer({
|
|||||||
}
|
}
|
||||||
}, [shouldDisallowLimitedStarGifts, shouldPayForUpgrade]);
|
}, [shouldDisallowLimitedStarGifts, shouldPayForUpgrade]);
|
||||||
|
|
||||||
const isStarGift = 'id' in gift;
|
const isStarGift = 'id' in gift && gift.type === 'starGift';
|
||||||
|
const isPremiumGift = 'months' in gift;
|
||||||
const hasPremiumByStars = giftByStars && 'amount' in giftByStars;
|
const hasPremiumByStars = giftByStars && 'amount' in giftByStars;
|
||||||
const isPeerUser = peer && isApiPeerUser(peer);
|
const isPeerUser = peer && isApiPeerUser(peer);
|
||||||
const isSelf = peerId === currentUserId;
|
const isSelf = peerId === currentUserId;
|
||||||
|
|
||||||
const localMessage = useMemo(() => {
|
const localMessage = useMemo(() => {
|
||||||
if (!isStarGift) {
|
if (isPremiumGift) {
|
||||||
const currentGift = shouldPayByStars && hasPremiumByStars ? giftByStars : gift;
|
const currentGift = shouldPayByStars && hasPremiumByStars ? giftByStars : gift;
|
||||||
return {
|
return {
|
||||||
id: -1,
|
id: -1,
|
||||||
@ -121,32 +122,35 @@ function GiftComposer({
|
|||||||
} satisfies ApiMessage;
|
} satisfies ApiMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (isStarGift) {
|
||||||
id: -1,
|
return {
|
||||||
chatId: '0',
|
id: -1,
|
||||||
isOutgoing: false,
|
chatId: '0',
|
||||||
senderId: currentUserId,
|
isOutgoing: false,
|
||||||
date: Math.floor(Date.now() / 1000),
|
senderId: currentUserId,
|
||||||
content: {
|
date: Math.floor(Date.now() / 1000),
|
||||||
action: {
|
content: {
|
||||||
mediaType: 'action',
|
action: {
|
||||||
type: 'starGift',
|
mediaType: 'action',
|
||||||
message: giftMessage?.length ? {
|
type: 'starGift',
|
||||||
text: giftMessage,
|
message: giftMessage?.length ? {
|
||||||
} : undefined,
|
text: giftMessage,
|
||||||
isNameHidden: shouldHideName || undefined,
|
} : undefined,
|
||||||
starsToConvert: gift.starsToConvert,
|
isNameHidden: shouldHideName || undefined,
|
||||||
canUpgrade: shouldPayForUpgrade || undefined,
|
starsToConvert: gift.starsToConvert,
|
||||||
alreadyPaidUpgradeStars: shouldPayForUpgrade ? gift.upgradeStars : undefined,
|
canUpgrade: shouldPayForUpgrade || undefined,
|
||||||
gift,
|
alreadyPaidUpgradeStars: shouldPayForUpgrade ? gift.upgradeStars : undefined,
|
||||||
peerId,
|
gift,
|
||||||
fromId: currentUserId,
|
peerId,
|
||||||
|
fromId: currentUserId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
} satisfies ApiMessage;
|
||||||
} satisfies ApiMessage;
|
}
|
||||||
|
return undefined;
|
||||||
}, [currentUserId, gift, giftMessage, isStarGift,
|
}, [currentUserId, gift, giftMessage, isStarGift,
|
||||||
shouldHideName, shouldPayForUpgrade, peerId,
|
shouldHideName, shouldPayForUpgrade, peerId,
|
||||||
shouldPayByStars, hasPremiumByStars, giftByStars]);
|
shouldPayByStars, hasPremiumByStars, giftByStars, isPremiumGift]);
|
||||||
|
|
||||||
const handleGiftMessageChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
const handleGiftMessageChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setGiftMessage(e.target.value);
|
setGiftMessage(e.target.value);
|
||||||
@ -198,14 +202,16 @@ function GiftComposer({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
openInvoice({
|
if (isPremiumGift) {
|
||||||
type: 'giftcode',
|
openInvoice({
|
||||||
userIds: [peerId],
|
type: 'giftcode',
|
||||||
currency: gift.currency,
|
userIds: [peerId],
|
||||||
amount: gift.amount,
|
currency: gift.currency,
|
||||||
option: gift,
|
amount: gift.amount,
|
||||||
message: giftMessage ? { text: giftMessage } : undefined,
|
option: gift,
|
||||||
});
|
message: giftMessage ? { text: giftMessage } : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const canUseStarsPayment = hasPremiumByStars && starBalance && (starBalance.amount > giftByStars.amount);
|
const canUseStarsPayment = hasPremiumByStars && starBalance && (starBalance.amount > giftByStars.amount);
|
||||||
@ -320,7 +326,7 @@ function GiftComposer({
|
|||||||
? formatStarsAsIcon(lang, giftByStars.amount, { asFont: true })
|
? formatStarsAsIcon(lang, giftByStars.amount, { asFont: true })
|
||||||
: isStarGift
|
: isStarGift
|
||||||
? formatStarsAsIcon(lang, gift.stars + (shouldPayForUpgrade ? gift.upgradeStars! : 0), { asFont: true })
|
? formatStarsAsIcon(lang, gift.stars + (shouldPayForUpgrade ? gift.upgradeStars! : 0), { asFont: true })
|
||||||
: formatCurrency(lang, gift.amount, gift.currency);
|
: isPremiumGift ? formatCurrency(lang, gift.amount, gift.currency) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.footer}>
|
<div className={styles.footer}>
|
||||||
@ -359,6 +365,8 @@ function GiftComposer({
|
|||||||
customBackground && isBackgroundBlurred && styles.blurred,
|
customBackground && isBackgroundBlurred && styles.blurred,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ((!isStarGift && !isPremiumGift) || !localMessage) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.root, 'custom-scroll')}>
|
<div className={buildClassName(styles.root, 'custom-scroll')}>
|
||||||
<div
|
<div
|
||||||
@ -375,7 +383,7 @@ function GiftComposer({
|
|||||||
style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined}
|
style={customBackgroundValue ? `--custom-background: ${customBackgroundValue}` : undefined}
|
||||||
/>
|
/>
|
||||||
<ActionMessage
|
<ActionMessage
|
||||||
key={isStarGift ? gift.id : gift.months}
|
key={isStarGift ? gift.id : isPremiumGift ? gift.months : undefined}
|
||||||
message={localMessage}
|
message={localMessage}
|
||||||
threadId={MAIN_THREAD_ID}
|
threadId={MAIN_THREAD_ID}
|
||||||
appearanceOrder={0}
|
appearanceOrder={0}
|
||||||
|
|||||||
@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:global(html.theme-dark) & {
|
:global(html.theme-dark) & {
|
||||||
background-color: var(--color-background);
|
background-color: rgb(33, 33, 33);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover::before {
|
&:hover::before {
|
||||||
@ -68,6 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.star {
|
.star {
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
margin-inline-end: 0.125rem;
|
margin-inline-end: 0.125rem;
|
||||||
font-size: 0.75rem !important;
|
font-size: 0.75rem !important;
|
||||||
}
|
}
|
||||||
@ -86,3 +87,12 @@
|
|||||||
margin-inline-start: 0 !important;
|
margin-inline-start: 0 !important;
|
||||||
margin-inline-end: 0.125rem !important;
|
margin-inline-end: 0.125rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.radialPattern {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stickerWrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,83 +1,160 @@
|
|||||||
import React, { memo, useRef } from '../../../lib/teact/teact';
|
import React, { memo, useMemo, useRef, useState } from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiStarGiftRegular,
|
ApiStarGift,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||||
|
import { getStickerFromGift } from '../../common/helpers/gifts';
|
||||||
|
import { getGiftAttributes } from '../../common/helpers/gifts';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
|
||||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
|
||||||
import GiftRibbon from '../../common/gift/GiftRibbon';
|
import GiftRibbon from '../../common/gift/GiftRibbon';
|
||||||
import Icon from '../../common/icons/Icon';
|
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
|
||||||
|
import StickerView from '../../common/StickerView';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
import styles from './GiftItem.module.scss';
|
import styles from './GiftItem.module.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
gift: ApiStarGiftRegular;
|
gift: ApiStarGift;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onClick: (gift: ApiStarGiftRegular) => void;
|
onClick: (gift: ApiStarGift, target: 'original' | 'resell') => void;
|
||||||
|
isResale?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GIFT_STICKER_SIZE = 90;
|
const GIFT_STICKER_SIZE = 90;
|
||||||
|
|
||||||
function GiftItemStar({ gift, observeIntersection, onClick }: OwnProps) {
|
function GiftItemStar({
|
||||||
|
gift, observeIntersection, onClick, isResale,
|
||||||
|
}: OwnProps) {
|
||||||
const { openGiftInfoModal } = getActions();
|
const { openGiftInfoModal } = getActions();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
const stickerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const [shouldPlay, play] = useFlag();
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
const {
|
const sticker = getStickerFromGift(gift);
|
||||||
stars,
|
const isGiftUnique = gift.type === 'starGiftUnique';
|
||||||
isLimited,
|
const uniqueGift = isGiftUnique ? gift : undefined;
|
||||||
isSoldOut,
|
const regularGift = !isGiftUnique ? gift : undefined;
|
||||||
sticker,
|
|
||||||
} = gift;
|
const stars = !isGiftUnique ? regularGift?.stars : uniqueGift?.resellPriceInStars;
|
||||||
|
const resellMinStars = regularGift?.resellMinStars;
|
||||||
|
const priceInStarsAsString = !isGiftUnique && isResale && resellMinStars
|
||||||
|
? lang.number(resellMinStars) + '+' : stars;
|
||||||
|
const isLimited = !isGiftUnique && Boolean(regularGift?.isLimited);
|
||||||
|
const isSoldOut = !isGiftUnique && Boolean(regularGift?.isSoldOut);
|
||||||
|
|
||||||
const handleGiftClick = useLastCallback(() => {
|
const handleGiftClick = useLastCallback(() => {
|
||||||
if (isSoldOut) {
|
if (isSoldOut && !isResale) {
|
||||||
openGiftInfoModal({ gift });
|
openGiftInfoModal({ gift });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick(gift);
|
onClick(gift, isResale ? 'resell' : 'original');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const radialPatternBackdrop = useMemo(() => {
|
||||||
|
const { backdrop, pattern } = getGiftAttributes(gift) || {};
|
||||||
|
|
||||||
|
if (!backdrop || !pattern) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backdropColors = [backdrop.centerColor, backdrop.edgeColor];
|
||||||
|
const patternColor = backdrop.patternColor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadialPatternBackground
|
||||||
|
className={styles.radialPattern}
|
||||||
|
backgroundColors={backdropColors}
|
||||||
|
patternColor={patternColor}
|
||||||
|
patternIcon={pattern.sticker}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [gift]);
|
||||||
|
|
||||||
|
const giftNumber = isGiftUnique ? gift.number : 0;
|
||||||
|
|
||||||
|
const giftRibbon = useMemo(() => {
|
||||||
|
if (isGiftUnique) {
|
||||||
|
const { backdrop } = getGiftAttributes(gift) || {};
|
||||||
|
if (!backdrop) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<GiftRibbon
|
||||||
|
color={[backdrop.centerColor, backdrop.edgeColor]}
|
||||||
|
text={
|
||||||
|
lang('GiftSavedNumber', { number: giftNumber })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isResale) {
|
||||||
|
return <GiftRibbon color="green" text={lang('GiftRibbonResale')} />;
|
||||||
|
}
|
||||||
|
if (isSoldOut) {
|
||||||
|
return <GiftRibbon color="red" text={lang('GiftSoldOut')} />;
|
||||||
|
}
|
||||||
|
if (isLimited) {
|
||||||
|
return <GiftRibbon color="blue" text={lang('GiftLimited')} />;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [isGiftUnique, isResale, gift, isSoldOut, isLimited, lang, giftNumber]);
|
||||||
|
|
||||||
useOnIntersect(ref, observeIntersection, (entry) => {
|
useOnIntersect(ref, observeIntersection, (entry) => {
|
||||||
if (entry.isIntersecting) play();
|
const visible = entry.isIntersecting;
|
||||||
|
setIsVisible(visible);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={buildClassName(styles.container, styles.starGift)}
|
className={buildClassName(styles.container, styles.starGift, 'starGiftItem')}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
onClick={handleGiftClick}
|
onClick={handleGiftClick}
|
||||||
>
|
>
|
||||||
<AnimatedIconFromSticker
|
{radialPatternBackdrop}
|
||||||
sticker={sticker}
|
|
||||||
noLoop
|
<div
|
||||||
play={shouldPlay}
|
ref={stickerRef}
|
||||||
|
className={styles.stickerWrapper}
|
||||||
|
style={`width: ${GIFT_STICKER_SIZE}px; height: ${GIFT_STICKER_SIZE}px`}
|
||||||
|
>
|
||||||
|
{sticker && (
|
||||||
|
<StickerView
|
||||||
|
observeIntersectionForPlaying={observeIntersection}
|
||||||
|
observeIntersectionForLoading={observeIntersection}
|
||||||
|
containerRef={stickerRef}
|
||||||
|
sticker={sticker}
|
||||||
|
size={GIFT_STICKER_SIZE}
|
||||||
|
shouldPreloadPreview
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className={styles.buy}
|
||||||
nonInteractive
|
nonInteractive
|
||||||
size={GIFT_STICKER_SIZE}
|
size="tiny"
|
||||||
/>
|
color={isGiftUnique ? 'bluredStarsBadge' : 'stars'}
|
||||||
<Button className={styles.buy} nonInteractive size="tiny" color="stars" withSparkleEffect pill fluid>
|
withSparkleEffect={isVisible}
|
||||||
<Icon name="star" className={styles.star} />
|
pill
|
||||||
<div className={styles.amount}>
|
fluid
|
||||||
{stars}
|
>
|
||||||
</div>
|
{formatStarsAsIcon(lang, priceInStarsAsString || 0, { asFont: true, className: styles.star })}
|
||||||
</Button>
|
</Button>
|
||||||
{isLimited && !isSoldOut && <GiftRibbon color="blue" text={lang('GiftLimited')} />}
|
{giftRibbon}
|
||||||
{isSoldOut && <GiftRibbon color="red" text={lang('GiftSoldOut')} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,17 +9,37 @@
|
|||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
|
|
||||||
:global(html.theme-dark) & {
|
:global(html.theme-dark) & {
|
||||||
background-color: #181818;
|
--color-background: #181818;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.root :global(.modal-dialog),
|
.root :global(.modal-dialog),
|
||||||
.transition,
|
.transition,
|
||||||
.content {
|
.content {
|
||||||
height: min(92vh, 45rem);
|
height: min(92vh, 49rem);
|
||||||
max-height: none !important;
|
max-height: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.root :global(.modal-dialog),
|
||||||
|
.transition,
|
||||||
|
.content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.root :global(.modal-dialog) {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: 100%;
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.root :global(.modal-dialog),
|
.root :global(.modal-dialog),
|
||||||
.root :global(.modal-content),
|
.root :global(.modal-content),
|
||||||
.transition {
|
.transition {
|
||||||
@ -31,13 +51,22 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resaleScreenRoot,
|
||||||
.main {
|
.main {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
height: 100%;
|
padding-bottom: 0.5rem;
|
||||||
padding-bottom: 1rem;
|
padding-inline: 0.5rem;
|
||||||
padding-inline: 1rem;
|
|
||||||
|
|
||||||
@include mixins.adapt-padding-to-scrollbar(1rem);
|
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resaleScreenRoot {
|
||||||
|
height: calc(100% - 6.25rem);
|
||||||
|
margin-top: 6.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.giftSection {
|
.giftSection {
|
||||||
@ -48,6 +77,7 @@
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resaleStarGiftsContainer,
|
||||||
.starGiftsContainer,
|
.starGiftsContainer,
|
||||||
.premiumGiftsGallery {
|
.premiumGiftsGallery {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -58,6 +88,7 @@
|
|||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resaleStarGiftsContainer,
|
||||||
.starGiftsContainer {
|
.starGiftsContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
@ -67,6 +98,10 @@
|
|||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resaleStarGiftsContainer {
|
||||||
|
padding-bottom: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@ -83,8 +118,19 @@
|
|||||||
border-bottom: 0.0625rem solid var(--color-borders);
|
border-bottom: 0.0625rem solid var(--color-borders);
|
||||||
|
|
||||||
background: var(--color-background);
|
background: var(--color-background);
|
||||||
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
|
transition: height 0.25s ease-out, transform 0.25s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
transition: 0.25s ease-out transform;
|
.resaleHeader {
|
||||||
|
overflow: visible;
|
||||||
|
height: 6.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resaleHeaderContentContainer {
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerSlide {
|
.headerSlide {
|
||||||
@ -102,11 +148,27 @@
|
|||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resaleHeaderText,
|
||||||
.commonHeaderText {
|
.commonHeaderText {
|
||||||
unicode-bidi: plaintext;
|
unicode-bidi: plaintext;
|
||||||
|
|
||||||
margin: 0 0 0 4.5rem;
|
margin: 0 0 0 4.5rem;
|
||||||
|
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resaleHeaderText {
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resaleHeaderDescription {
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.closeButton {
|
.closeButton {
|
||||||
@ -186,3 +248,36 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
min-height: calc(100% - 3.5rem);
|
min-height: calc(100% - 3.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notFoundGiftsRoot {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notFoundGiftsDescription {
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
|
||||||
|
margin-block: 1rem;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notFoundGiftsLink {
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
color: var(--color-links);
|
||||||
|
transition: opacity 0.15s ease-in;
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-links);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
ApiDisallowedGifts,
|
ApiDisallowedGifts,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPremiumGiftCodeOption,
|
ApiPremiumGiftCodeOption,
|
||||||
|
ApiStarGift,
|
||||||
ApiStarGiftRegular,
|
ApiStarGiftRegular,
|
||||||
ApiStarsAmount,
|
ApiStarsAmount,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
@ -17,6 +18,7 @@ import type { StarGiftCategory } from '../../../types';
|
|||||||
import { STARS_CURRENCY_CODE } from '../../../config';
|
import { STARS_CURRENCY_CODE } from '../../../config';
|
||||||
import { getUserFullName } from '../../../global/helpers';
|
import { getUserFullName } from '../../../global/helpers';
|
||||||
import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers';
|
import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../global/helpers/peers';
|
||||||
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { selectPeer, selectUserFullInfo } from '../../../global/selectors';
|
import { selectPeer, selectUserFullInfo } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
@ -36,6 +38,8 @@ import BalanceBlock from '../stars/BalanceBlock';
|
|||||||
import GiftSendingOptions from './GiftComposer';
|
import GiftSendingOptions from './GiftComposer';
|
||||||
import GiftItemPremium from './GiftItemPremium';
|
import GiftItemPremium from './GiftItemPremium';
|
||||||
import GiftItemStar from './GiftItemStar';
|
import GiftItemStar from './GiftItemStar';
|
||||||
|
import GiftModalResaleScreen from './GiftModalResaleScreen';
|
||||||
|
import GiftResaleFilters from './GiftResaleFilters';
|
||||||
import StarGiftCategoryList from './StarGiftCategoryList';
|
import StarGiftCategoryList from './StarGiftCategoryList';
|
||||||
|
|
||||||
import styles from './GiftModal.module.scss';
|
import styles from './GiftModal.module.scss';
|
||||||
@ -46,7 +50,7 @@ export type OwnProps = {
|
|||||||
modal: TabState['giftModal'];
|
modal: TabState['giftModal'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GiftOption = ApiPremiumGiftCodeOption | ApiStarGiftRegular;
|
export type GiftOption = ApiPremiumGiftCodeOption | ApiStarGift;
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
boostPerSentGift?: number;
|
boostPerSentGift?: number;
|
||||||
@ -56,6 +60,8 @@ type StateProps = {
|
|||||||
peer?: ApiPeer;
|
peer?: ApiPeer;
|
||||||
isSelf?: boolean;
|
isSelf?: boolean;
|
||||||
disallowedGifts?: ApiDisallowedGifts;
|
disallowedGifts?: ApiDisallowedGifts;
|
||||||
|
resaleGiftsCount?: number;
|
||||||
|
areResaleGiftsLoading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AVATAR_SIZE = 100;
|
const AVATAR_SIZE = 100;
|
||||||
@ -72,9 +78,11 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
peer,
|
peer,
|
||||||
isSelf,
|
isSelf,
|
||||||
disallowedGifts,
|
disallowedGifts,
|
||||||
|
resaleGiftsCount,
|
||||||
|
areResaleGiftsLoading,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
closeGiftModal,
|
closeGiftModal, openGiftInfoModal, resetResaleGifts, loadResaleGifts,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const dialogRef = useRef<HTMLDivElement>();
|
const dialogRef = useRef<HTMLDivElement>();
|
||||||
const transitionRef = useRef<HTMLDivElement>();
|
const transitionRef = useRef<HTMLDivElement>();
|
||||||
@ -89,6 +97,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
const chat = peer && isApiPeerChat(peer) ? peer : undefined;
|
const chat = peer && isApiPeerChat(peer) ? peer : undefined;
|
||||||
|
|
||||||
const [selectedGift, setSelectedGift] = useState<GiftOption | undefined>();
|
const [selectedGift, setSelectedGift] = useState<GiftOption | undefined>();
|
||||||
|
const [selectedResellGift, setSelectedResellGift] = useState<ApiStarGift | undefined>();
|
||||||
const [shouldShowMainScreenHeader, setShouldShowMainScreenHeader] = useState(false);
|
const [shouldShowMainScreenHeader, setShouldShowMainScreenHeader] = useState(false);
|
||||||
const [isMainScreenHeaderForStarGifts, setIsMainScreenHeaderForStarGifts] = useState(false);
|
const [isMainScreenHeaderForStarGifts, setIsMainScreenHeaderForStarGifts] = useState(false);
|
||||||
const [isGiftScreenHeaderForStarGifts, setIsGiftScreenHeaderForStarGifts] = useState(false);
|
const [isGiftScreenHeaderForStarGifts, setIsGiftScreenHeaderForStarGifts] = useState(false);
|
||||||
@ -143,14 +152,22 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
observe: observeIntersection,
|
observe: observeIntersection,
|
||||||
} = useIntersectionObserver({ rootRef: scrollerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
|
} = useIntersectionObserver({ rootRef: scrollerRef, throttleMs: INTERSECTION_THROTTLE, isDisabled: !isOpen });
|
||||||
|
|
||||||
|
const isResaleScreen = Boolean(selectedResellGift) && !selectedGift;
|
||||||
const isGiftScreen = Boolean(selectedGift);
|
const isGiftScreen = Boolean(selectedGift);
|
||||||
const shouldShowHeader = isGiftScreen || shouldShowMainScreenHeader;
|
const shouldShowHeader = isResaleScreen || isGiftScreen || shouldShowMainScreenHeader;
|
||||||
const isHeaderForStarGifts = isGiftScreen ? isGiftScreenHeaderForStarGifts : isMainScreenHeaderForStarGifts;
|
const isHeaderForStarGifts = isGiftScreen ? isGiftScreenHeaderForStarGifts : isMainScreenHeaderForStarGifts;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedResellGift) {
|
||||||
|
loadResaleGifts({ giftId: selectedResellGift.id });
|
||||||
|
}
|
||||||
|
}, [selectedResellGift]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setShouldShowMainScreenHeader(false);
|
setShouldShowMainScreenHeader(false);
|
||||||
setSelectedGift(undefined);
|
setSelectedGift(undefined);
|
||||||
|
setSelectedResellGift(undefined);
|
||||||
setSelectedCategory('all');
|
setSelectedCategory('all');
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
@ -228,7 +245,18 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGiftClick = useLastCallback((gift: GiftOption) => {
|
const handleGiftClick = useLastCallback((gift: GiftOption, target?: 'resell' | 'original') => {
|
||||||
|
if (target === 'resell') {
|
||||||
|
if (!('id' in gift)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isResaleScreen) {
|
||||||
|
openGiftInfoModal({ gift, recipientId: renderingModal?.forPeerId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelectedResellGift(gift);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setSelectedGift(gift);
|
setSelectedGift(gift);
|
||||||
setIsGiftScreenHeaderForStarGifts('id' in gift);
|
setIsGiftScreenHeaderForStarGifts('id' in gift);
|
||||||
});
|
});
|
||||||
@ -254,16 +282,34 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.starGiftsContainer}>
|
<div className={styles.starGiftsContainer}>
|
||||||
{starGiftsById && filteredGiftIds?.map((giftId) => {
|
{starGiftsById && filteredGiftIds?.flatMap((giftId) => {
|
||||||
const gift = starGiftsById[giftId];
|
const gift = starGiftsById[giftId];
|
||||||
return (
|
const shouldShowResale = selectedCategory !== 'stock' && Boolean(gift.availabilityResale);
|
||||||
|
const shouldDuplicateAsResale = selectedCategory !== 'resale' && shouldShowResale && !gift.isSoldOut;
|
||||||
|
|
||||||
|
const elements = [
|
||||||
<GiftItemStar
|
<GiftItemStar
|
||||||
key={giftId}
|
key={giftId}
|
||||||
gift={gift}
|
gift={gift}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
|
isResale={shouldShowResale && !shouldDuplicateAsResale}
|
||||||
onClick={handleGiftClick}
|
onClick={handleGiftClick}
|
||||||
/>
|
/>,
|
||||||
);
|
];
|
||||||
|
|
||||||
|
if (shouldDuplicateAsResale) {
|
||||||
|
elements.push(
|
||||||
|
<GiftItemStar
|
||||||
|
key={`resale_${giftId}`}
|
||||||
|
isResale
|
||||||
|
gift={gift}
|
||||||
|
observeIntersection={observeIntersection}
|
||||||
|
onClick={handleGiftClick}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -290,12 +336,24 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
setSelectedCategory(category);
|
setSelectedCategory(category);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleCloseModal = useLastCallback(() => {
|
||||||
|
setSelectedGift(undefined);
|
||||||
|
setSelectedResellGift(undefined);
|
||||||
|
resetResaleGifts();
|
||||||
|
closeGiftModal();
|
||||||
|
});
|
||||||
|
|
||||||
const handleCloseButtonClick = useLastCallback(() => {
|
const handleCloseButtonClick = useLastCallback(() => {
|
||||||
|
if (isResaleScreen) {
|
||||||
|
setSelectedResellGift(undefined);
|
||||||
|
resetResaleGifts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isGiftScreen) {
|
if (isGiftScreen) {
|
||||||
setSelectedGift(undefined);
|
setSelectedGift(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
closeGiftModal();
|
handleCloseModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
function renderMainScreen() {
|
function renderMainScreen() {
|
||||||
@ -337,17 +395,51 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBackButton = isGiftScreen;
|
const isBackButton = isGiftScreen || isResaleScreen;
|
||||||
|
|
||||||
const buttonClassName = buildClassName(
|
const buttonClassName = buildClassName(
|
||||||
'animated-close-icon',
|
'animated-close-icon',
|
||||||
isBackButton && 'state-back',
|
isBackButton && 'state-back',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function renderHeader() {
|
||||||
|
if (!shouldShowHeader) return undefined;
|
||||||
|
if (isResaleScreen) {
|
||||||
|
const isFirstLoading = areResaleGiftsLoading && !resaleGiftsCount;
|
||||||
|
return (
|
||||||
|
<div className={styles.resaleHeaderContentContainer}>
|
||||||
|
<h2 className={styles.resaleHeaderText}>
|
||||||
|
{selectedResellGift.title}
|
||||||
|
</h2>
|
||||||
|
{isFirstLoading
|
||||||
|
&& (
|
||||||
|
<div className={styles.resaleHeaderDescription}>
|
||||||
|
{lang('Loading')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isFirstLoading && resaleGiftsCount !== undefined
|
||||||
|
&& (
|
||||||
|
<div className={styles.resaleHeaderDescription}>
|
||||||
|
{lang('HeaderDescriptionResaleGifts', {
|
||||||
|
count: resaleGiftsCount,
|
||||||
|
}, { withNodes: true, withMarkdown: true, pluralValue: resaleGiftsCount })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<GiftResaleFilters dialogRef={dialogRef} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<h2 className={styles.commonHeaderText}>
|
||||||
|
{lang(isHeaderForStarGifts ? (isSelf ? 'StarsGiftHeaderSelf' : 'StarsGiftHeader') : 'GiftPremiumHeader')}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
dialogRef={dialogRef}
|
dialogRef={dialogRef}
|
||||||
onClose={closeGiftModal}
|
onClose={handleCloseModal}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
isSlim
|
isSlim
|
||||||
contentClassName={styles.content}
|
contentClassName={styles.content}
|
||||||
@ -365,24 +457,32 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
|||||||
<div className={buttonClassName} />
|
<div className={buttonClassName} />
|
||||||
</Button>
|
</Button>
|
||||||
<BalanceBlock className={styles.balance} balance={starBalance} withAddButton />
|
<BalanceBlock className={styles.balance} balance={starBalance} withAddButton />
|
||||||
<div className={buildClassName(styles.header, !shouldShowHeader && styles.hiddenHeader)}>
|
<div className={buildClassName(
|
||||||
|
styles.header,
|
||||||
|
isResaleScreen && styles.resaleHeader,
|
||||||
|
!shouldShowHeader && styles.hiddenHeader)}
|
||||||
|
>
|
||||||
<Transition
|
<Transition
|
||||||
name="slideVerticalFade"
|
name="slideVerticalFade"
|
||||||
activeKey={Number(isHeaderForStarGifts)}
|
activeKey={!shouldShowHeader ? 0 : isResaleScreen ? 1 : isHeaderForStarGifts ? 2 : 3}
|
||||||
slideClassName={styles.headerSlide}
|
slideClassName={styles.headerSlide}
|
||||||
>
|
>
|
||||||
<h2 className={styles.commonHeaderText}>
|
{renderHeader()}
|
||||||
{lang(isHeaderForStarGifts ? (isSelf ? 'StarsGiftHeaderSelf' : 'StarsGiftHeader') : 'GiftPremiumHeader')}
|
|
||||||
</h2>
|
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<Transition
|
<Transition
|
||||||
ref={transitionRef}
|
ref={transitionRef}
|
||||||
className={styles.transition}
|
className={styles.transition}
|
||||||
name="pushSlide"
|
name="pushSlide"
|
||||||
activeKey={isGiftScreen ? 1 : 0}
|
activeKey={isGiftScreen ? 1 : isResaleScreen ? 2 : 0}
|
||||||
>
|
>
|
||||||
{!isGiftScreen && renderMainScreen()}
|
{!isGiftScreen && !isResaleScreen && renderMainScreen()}
|
||||||
|
{isResaleScreen && selectedResellGift
|
||||||
|
&& (
|
||||||
|
<GiftModalResaleScreen
|
||||||
|
onGiftClick={handleGiftClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{isGiftScreen && renderingModal?.forPeerId && (
|
{isGiftScreen && renderingModal?.forPeerId && (
|
||||||
<GiftSendingOptions
|
<GiftSendingOptions
|
||||||
gift={selectedGift}
|
gift={selectedGift}
|
||||||
@ -406,6 +506,10 @@ export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => {
|
|||||||
const isSelf = Boolean(currentUserId && modal?.forPeerId === currentUserId);
|
const isSelf = Boolean(currentUserId && modal?.forPeerId === currentUserId);
|
||||||
const userFullInfo = peer ? selectUserFullInfo(global, peer?.id) : undefined;
|
const userFullInfo = peer ? selectUserFullInfo(global, peer?.id) : undefined;
|
||||||
|
|
||||||
|
const { resaleGifts } = selectTabState(global);
|
||||||
|
const resaleGiftsCount = resaleGifts.count;
|
||||||
|
const areResaleGiftsLoading = resaleGifts.isLoading !== false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
boostPerSentGift: global.appConfig?.boostsPerSentGift,
|
boostPerSentGift: global.appConfig?.boostsPerSentGift,
|
||||||
starGiftsById: starGifts?.byId,
|
starGiftsById: starGifts?.byId,
|
||||||
@ -414,12 +518,15 @@ export default memo(withGlobal<OwnProps>((global, { modal }): StateProps => {
|
|||||||
peer,
|
peer,
|
||||||
isSelf,
|
isSelf,
|
||||||
disallowedGifts: userFullInfo?.disallowedGifts,
|
disallowedGifts: userFullInfo?.disallowedGifts,
|
||||||
|
resaleGiftsCount,
|
||||||
|
areResaleGiftsLoading,
|
||||||
};
|
};
|
||||||
})(GiftModal));
|
})(GiftModal));
|
||||||
|
|
||||||
function getCategoryKey(category: StarGiftCategory) {
|
function getCategoryKey(category: StarGiftCategory) {
|
||||||
if (category === 'all') return -2;
|
if (category === 'all') return 0;
|
||||||
if (category === 'limited') return -1;
|
if (category === 'limited') return 1;
|
||||||
if (category === 'stock') return 0;
|
if (category === 'resale') return 2;
|
||||||
return category;
|
if (category === 'stock') return 3;
|
||||||
|
return category + 3;
|
||||||
}
|
}
|
||||||
|
|||||||
171
src/components/modals/gift/GiftModalResaleScreen.tsx
Normal file
171
src/components/modals/gift/GiftModalResaleScreen.tsx
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo,
|
||||||
|
useMemo,
|
||||||
|
useRef } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiStarGift,
|
||||||
|
} from '../../../api/types';
|
||||||
|
import type { ResaleGiftsFilterOptions } from '../../../types';
|
||||||
|
|
||||||
|
import { RESALE_GIFTS_LIMIT } from '../../../config';
|
||||||
|
import { selectTabState,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { LOCAL_TGS_URLS } from '../../common/helpers/animatedAssets';
|
||||||
|
|
||||||
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import AnimatedIconWithPreview from '../../common/AnimatedIconWithPreview';
|
||||||
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
|
import Link from '../../ui/Link';
|
||||||
|
import Transition from '../../ui/Transition';
|
||||||
|
import GiftItemStar from './GiftItemStar';
|
||||||
|
|
||||||
|
import styles from './GiftModal.module.scss';
|
||||||
|
|
||||||
|
export type OwnProps = {
|
||||||
|
onGiftClick: (gift: ApiStarGift) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
gift?: ApiStarGift;
|
||||||
|
resellGifts?: ApiStarGift[];
|
||||||
|
filter: ResaleGiftsFilterOptions;
|
||||||
|
areGiftsAllLoaded?: boolean;
|
||||||
|
areGiftsLoading?: boolean;
|
||||||
|
updateIteration: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const INTERSECTION_THROTTLE = 200;
|
||||||
|
|
||||||
|
const GiftModalResaleScreen: FC<OwnProps & StateProps> = ({
|
||||||
|
resellGifts,
|
||||||
|
gift,
|
||||||
|
filter,
|
||||||
|
areGiftsAllLoaded,
|
||||||
|
areGiftsLoading,
|
||||||
|
updateIteration,
|
||||||
|
onGiftClick,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
loadResaleGifts,
|
||||||
|
updateResaleGiftsFilter,
|
||||||
|
} = getActions();
|
||||||
|
const scrollerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
const resellGiftsIds = useMemo(() => {
|
||||||
|
return resellGifts?.map((g) => g.id);
|
||||||
|
}, [resellGifts]);
|
||||||
|
|
||||||
|
const hasFilter = Boolean(filter?.modelAttributes?.length
|
||||||
|
|| filter?.patternAttributes?.length || filter?.backdropAttributes?.length);
|
||||||
|
|
||||||
|
const handleLoadMoreResellGifts = useLastCallback(() => {
|
||||||
|
if (gift) {
|
||||||
|
loadResaleGifts({ giftId: gift.id });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isGiftsEmpty = Boolean(!resellGifts || resellGifts.length === 0);
|
||||||
|
|
||||||
|
const [viewportIds, onLoadMore] = useInfiniteScroll(
|
||||||
|
handleLoadMoreResellGifts,
|
||||||
|
resellGiftsIds,
|
||||||
|
!gift,
|
||||||
|
RESALE_GIFTS_LIMIT,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { observe } = useIntersectionObserver({ rootRef: scrollerRef, throttleMs: INTERSECTION_THROTTLE });
|
||||||
|
|
||||||
|
const handleResetGiftsFilter = useLastCallback(() => {
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
modelAttributes: [],
|
||||||
|
backdropAttributes: [],
|
||||||
|
patternAttributes: [],
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderNothingFoundGiftsWithFilter() {
|
||||||
|
return (
|
||||||
|
<div className={styles.notFoundGiftsRoot}>
|
||||||
|
<AnimatedIconWithPreview
|
||||||
|
size={160}
|
||||||
|
tgsUrl={LOCAL_TGS_URLS.SearchingDuck}
|
||||||
|
nonInteractive
|
||||||
|
noLoop
|
||||||
|
/>
|
||||||
|
<div className={styles.notFoundGiftsDescription}>
|
||||||
|
{lang('ResellGiftsNoFound')}
|
||||||
|
</div>
|
||||||
|
{hasFilter && (
|
||||||
|
<Link
|
||||||
|
className={styles.notFoundGiftsLink}
|
||||||
|
onClick={handleResetGiftsFilter}
|
||||||
|
>
|
||||||
|
{lang('ResellGiftsClearFilters')}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={scrollerRef} className={buildClassName(styles.resaleScreenRoot, 'custom-scroll')}>
|
||||||
|
<Transition
|
||||||
|
name="zoomFade"
|
||||||
|
activeKey={updateIteration}
|
||||||
|
>
|
||||||
|
{isGiftsEmpty && areGiftsAllLoaded && renderNothingFoundGiftsWithFilter()}
|
||||||
|
<InfiniteScroll
|
||||||
|
className={buildClassName(styles.resaleStarGiftsContainer)}
|
||||||
|
items={viewportIds}
|
||||||
|
onLoadMore={onLoadMore}
|
||||||
|
itemSelector=".starGiftItem"
|
||||||
|
noFastList
|
||||||
|
preloadBackwards={RESALE_GIFTS_LIMIT}
|
||||||
|
scrollContainerClosest={`.${styles.resaleScreenRoot}`}
|
||||||
|
>
|
||||||
|
{resellGifts?.map((gift) => (
|
||||||
|
<GiftItemStar
|
||||||
|
key={gift.id}
|
||||||
|
gift={gift}
|
||||||
|
observeIntersection={observe}
|
||||||
|
isResale
|
||||||
|
onClick={onGiftClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</InfiniteScroll>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||||
|
const {
|
||||||
|
starGifts,
|
||||||
|
} = global;
|
||||||
|
|
||||||
|
const { resaleGifts } = selectTabState(global);
|
||||||
|
const gift = resaleGifts?.giftId ? starGifts?.byId[resaleGifts.giftId] : undefined;
|
||||||
|
const filter = resaleGifts.filter;
|
||||||
|
const areGiftsAllLoaded = resaleGifts.isAllLoaded;
|
||||||
|
const areGiftsLoading = resaleGifts.isLoading;
|
||||||
|
const updateIteration = resaleGifts.updateIteration;
|
||||||
|
|
||||||
|
return {
|
||||||
|
resellGifts: resaleGifts.gifts,
|
||||||
|
gift,
|
||||||
|
filter,
|
||||||
|
areGiftsAllLoaded,
|
||||||
|
areGiftsLoading,
|
||||||
|
updateIteration,
|
||||||
|
};
|
||||||
|
})(GiftModalResaleScreen));
|
||||||
160
src/components/modals/gift/GiftResaleFilters.module.scss
Normal file
160
src/components/modals/gift/GiftResaleFilters.module.scss
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
@use '../../../styles/mixins';
|
||||||
|
|
||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonsContainer {
|
||||||
|
scrollbar-color: rgba(0, 0, 0, 0);
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
@include mixins.gradient-border-horizontal(0.5rem, 0.5rem);
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemIcon {
|
||||||
|
margin-inline-start: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticker {
|
||||||
|
margin-inline-start: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropAttributeMenuItemText,
|
||||||
|
.menuItemStickerText,
|
||||||
|
.menuItemText {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItemStickerText {
|
||||||
|
margin-inline-start: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropAttributeMenuItemText {
|
||||||
|
margin-inline-start: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdrop {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.625rem;
|
||||||
|
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItemIcon {
|
||||||
|
margin-inline: 0 !important;
|
||||||
|
margin-inline-start: 0.5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: auto;
|
||||||
|
margin-inline: 0.25rem;
|
||||||
|
padding: 0.125rem 0.75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
background-color: var(--color-background-secondary);
|
||||||
|
|
||||||
|
transition: 0.15s background-color;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: var(--color-background-secondary-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuContentContainer {
|
||||||
|
overflow-y: scroll;
|
||||||
|
max-height: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.MenuItem) {
|
||||||
|
:global(.icon) {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
:global(.bubble) {
|
||||||
|
top: 2.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
:global(.bubble) {
|
||||||
|
right: auto !important;
|
||||||
|
left: 0.125rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.right {
|
||||||
|
:global(.bubble) {
|
||||||
|
right: 0.125rem !important;
|
||||||
|
left: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.SearchInput) {
|
||||||
|
--color-placeholders: var(--color-text-secondary);
|
||||||
|
|
||||||
|
width: 15rem;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 0.0625rem solid var(--color-borders);
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
:global(.form-control) {
|
||||||
|
caret-color: var(--color-icon-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.icon) {
|
||||||
|
color: var(--color-icon-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.icon-container-left) {
|
||||||
|
width: 1.25rem;
|
||||||
|
margin-inline-start: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.icon) {
|
||||||
|
font-size: 1.25rem !important;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
655
src/components/modals/gift/GiftResaleFilters.tsx
Normal file
655
src/components/modals/gift/GiftResaleFilters.tsx
Normal file
@ -0,0 +1,655 @@
|
|||||||
|
import { type MouseEvent as ReactMouseEvent } from 'react';
|
||||||
|
import type { ElementRef, FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiStarGiftAttribute,
|
||||||
|
ApiStarGiftAttributeBackdrop,
|
||||||
|
ApiStarGiftAttributeCounter,
|
||||||
|
ApiStarGiftAttributeIdBackdrop,
|
||||||
|
ApiStarGiftAttributeIdPattern,
|
||||||
|
ApiStarGiftAttributeModel,
|
||||||
|
ApiStarGiftAttributePattern,
|
||||||
|
StarGiftAttributeIdModel,
|
||||||
|
} from '../../../api/types';
|
||||||
|
import type { ResaleGiftsFilterOptions, ResaleGiftsSortType } from '../../../types';
|
||||||
|
|
||||||
|
import { selectTabState,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import Icon from '../../common/icons/Icon';
|
||||||
|
import RadialPatternBackground from '../../common/profile/RadialPatternBackground';
|
||||||
|
import Menu from '../../ui/Menu';
|
||||||
|
import MenuItem from '../../ui/MenuItem';
|
||||||
|
import SearchInput from '../../ui/SearchInput';
|
||||||
|
import ResaleGiftMenuAttributeSticker from './ResaleGiftMenuAttributeSticker';
|
||||||
|
|
||||||
|
import styles from './GiftResaleFilters.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
dialogRef: ElementRef<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
type StateProps = {
|
||||||
|
filter: ResaleGiftsFilterOptions;
|
||||||
|
attributes?: ApiStarGiftAttribute[];
|
||||||
|
counters?: ApiStarGiftAttributeCounter[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||||
|
attributes,
|
||||||
|
counters,
|
||||||
|
filter,
|
||||||
|
dialogRef,
|
||||||
|
}) => {
|
||||||
|
const lang = useLang();
|
||||||
|
const {
|
||||||
|
updateResaleGiftsFilter,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const [searchModelQuery, setSearchModelQuery] = useState('');
|
||||||
|
const [searchBackdropQuery, setSearchBackdropQuery] = useState('');
|
||||||
|
const [searchPatternQuery, setSearchPatternQuery] = useState('');
|
||||||
|
const filteredAttributes = useMemo(() => {
|
||||||
|
const map: {
|
||||||
|
model: ApiStarGiftAttributeModel[];
|
||||||
|
pattern: ApiStarGiftAttributePattern[];
|
||||||
|
backdrop: ApiStarGiftAttributeBackdrop[];
|
||||||
|
} = {
|
||||||
|
model: [],
|
||||||
|
pattern: [],
|
||||||
|
backdrop: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const counter of counters ?? []) {
|
||||||
|
const { attribute } = counter;
|
||||||
|
|
||||||
|
if (!counter.count) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const found = attributes?.find((attr) => {
|
||||||
|
if (attr.type === 'backdrop' && attribute.type === 'backdrop') {
|
||||||
|
return attr.backdropId === attribute.backdropId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr.type === 'model' && attribute.type === 'model') {
|
||||||
|
return attr.sticker.id === attribute.documentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr.type === 'pattern' && attribute.type === 'pattern') {
|
||||||
|
return attr.sticker.id === attribute.documentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found?.type === 'backdrop') {
|
||||||
|
map.backdrop.push(found);
|
||||||
|
}
|
||||||
|
if (found?.type === 'model') {
|
||||||
|
map.model.push(found);
|
||||||
|
}
|
||||||
|
if (found?.type === 'pattern') {
|
||||||
|
map.pattern.push(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}, [attributes, counters]);
|
||||||
|
|
||||||
|
const filteredAndSearchedAttributes = useMemo(() => {
|
||||||
|
const filterBySearch = <T extends { name?: string }>(items: T[], query: string): T[] => {
|
||||||
|
if (!query.trim()) return items;
|
||||||
|
|
||||||
|
return items.filter(
|
||||||
|
(item): item is T => Boolean(item.name?.toLowerCase().includes(query.toLowerCase())),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
model: filterBySearch(filteredAttributes.model, searchModelQuery),
|
||||||
|
pattern: filterBySearch(filteredAttributes.pattern, searchPatternQuery),
|
||||||
|
backdrop: filterBySearch(filteredAttributes.backdrop, searchBackdropQuery),
|
||||||
|
};
|
||||||
|
}, [filteredAttributes, searchModelQuery, searchBackdropQuery, searchPatternQuery]);
|
||||||
|
|
||||||
|
// Sort Menu
|
||||||
|
const sortMenuRef = useRef<HTMLDivElement>();
|
||||||
|
const {
|
||||||
|
isContextMenuOpen: isSortContextMenuOpen,
|
||||||
|
contextMenuAnchor: sortContextMenuAnchor,
|
||||||
|
handleContextMenu: handleSortContextMenu,
|
||||||
|
handleContextMenuClose: handleSortContextMenuClose,
|
||||||
|
handleContextMenuHide: handleSortContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(dialogRef);
|
||||||
|
const getSortMenuElement = useLastCallback(() => sortMenuRef.current!);
|
||||||
|
|
||||||
|
// Model Menu
|
||||||
|
const modelMenuRef = useRef<HTMLDivElement>();
|
||||||
|
const {
|
||||||
|
isContextMenuOpen: isModelContextMenuOpen,
|
||||||
|
contextMenuAnchor: modelContextMenuAnchor,
|
||||||
|
handleContextMenu: handleModelContextMenu,
|
||||||
|
handleContextMenuClose: handleModelContextMenuClose,
|
||||||
|
handleContextMenuHide: handleModelContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(dialogRef);
|
||||||
|
const getModelMenuElement = useLastCallback(
|
||||||
|
() => modelMenuRef.current!,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Backdrop Menu
|
||||||
|
const backdropMenuRef = useRef<HTMLDivElement>();
|
||||||
|
const {
|
||||||
|
isContextMenuOpen: isBackdropContextMenuOpen,
|
||||||
|
contextMenuAnchor: backdropContextMenuAnchor,
|
||||||
|
handleContextMenu: handleBackdropContextMenu,
|
||||||
|
handleContextMenuClose: handleBackdropContextMenuClose,
|
||||||
|
handleContextMenuHide: handleBackdropContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(dialogRef);
|
||||||
|
const getBackdropMenuElement = useLastCallback(() => backdropMenuRef.current!);
|
||||||
|
|
||||||
|
// Pattern Menu
|
||||||
|
const patternMenuRef = useRef<HTMLDivElement>();
|
||||||
|
const {
|
||||||
|
isContextMenuOpen: isPatternContextMenuOpen,
|
||||||
|
contextMenuAnchor: patternContextMenuAnchor,
|
||||||
|
handleContextMenu: handlePatternContextMenu,
|
||||||
|
handleContextMenuClose: handlePatternContextMenuClose,
|
||||||
|
handleContextMenuHide: handlePatternContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(dialogRef);
|
||||||
|
const getPatternMenuElement = useLastCallback(() => patternMenuRef.current!);
|
||||||
|
|
||||||
|
const SortMenuButton: FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||||
|
= useMemo(() => {
|
||||||
|
const sortType = filter.sortType;
|
||||||
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
|
<div
|
||||||
|
className={styles.item}
|
||||||
|
onClick={onTrigger}
|
||||||
|
>
|
||||||
|
{sortType === 'byDate' && lang('ValueGiftSortByDate')}
|
||||||
|
{sortType === 'byNumber' && lang('ValueGiftSortByNumber')}
|
||||||
|
{sortType === 'byPrice' && lang('ValueGiftSortByPrice')}
|
||||||
|
<Icon
|
||||||
|
name="dropdown-arrows"
|
||||||
|
className={styles.itemIcon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [lang, filter]);
|
||||||
|
|
||||||
|
const ModelMenuButton:
|
||||||
|
FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||||
|
= useMemo(() => {
|
||||||
|
const attributesCount = filter?.modelAttributes?.length || 0;
|
||||||
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
|
<div
|
||||||
|
className={styles.item}
|
||||||
|
onClick={onTrigger}
|
||||||
|
>
|
||||||
|
{attributesCount === 0 && lang('GiftAttributeModel')}
|
||||||
|
{attributesCount > 0
|
||||||
|
&& lang('GiftAttributeModelPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||||
|
<Icon
|
||||||
|
name="dropdown-arrows"
|
||||||
|
className={styles.itemIcon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [lang, filter]);
|
||||||
|
const BackdropMenuButton:
|
||||||
|
FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||||
|
= useMemo(() => {
|
||||||
|
const attributesCount = filter?.backdropAttributes?.length || 0;
|
||||||
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
|
<div
|
||||||
|
className={styles.item}
|
||||||
|
onClick={onTrigger}
|
||||||
|
>
|
||||||
|
{attributesCount === 0 && lang('GiftAttributeBackdrop')}
|
||||||
|
{attributesCount > 0
|
||||||
|
&& lang('GiftAttributeBackdropPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||||
|
<Icon
|
||||||
|
name="dropdown-arrows"
|
||||||
|
className={styles.itemIcon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [lang, filter]);
|
||||||
|
const PatternMenuButton: FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||||
|
= useMemo(() => {
|
||||||
|
const attributesCount = filter?.patternAttributes?.length || 0;
|
||||||
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
|
<div
|
||||||
|
className={styles.item}
|
||||||
|
onClick={onTrigger}
|
||||||
|
>
|
||||||
|
{attributesCount === 0 && lang('GiftAttributeSymbol')}
|
||||||
|
{attributesCount > 0
|
||||||
|
&& lang('GiftAttributeSymbolPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||||
|
<Icon
|
||||||
|
name="dropdown-arrows"
|
||||||
|
className={styles.itemIcon}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, [lang, filter]);
|
||||||
|
|
||||||
|
const handleSortMenuItemClick = useLastCallback((type: ResaleGiftsSortType) => {
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
sortType: type,
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectedAllModelsClick = useLastCallback(() => {
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
modelAttributes: [],
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
const handleSelectedAllPatternsClick = useLastCallback(() => {
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
patternAttributes: [],
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
const handleSelectedAllBackdropsClick = useLastCallback(() => {
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
backdropAttributes: [],
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleModelMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeModel) => {
|
||||||
|
if (!counters) return;
|
||||||
|
const attributes = filter.modelAttributes || [];
|
||||||
|
const modelAttribute
|
||||||
|
= counters.find((counter): counter is ApiStarGiftAttributeCounter<StarGiftAttributeIdModel> =>
|
||||||
|
counter.attribute.type === 'model' && counter.attribute.documentId === attribute.sticker.id,
|
||||||
|
)?.attribute;
|
||||||
|
|
||||||
|
if (!modelAttribute) return;
|
||||||
|
|
||||||
|
const isActive = attributes.some((item) => item.documentId === modelAttribute.documentId);
|
||||||
|
const updatedAttributes = isActive
|
||||||
|
? attributes.filter((item) => item.documentId !== modelAttribute.documentId)
|
||||||
|
: [...attributes, modelAttribute];
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
modelAttributes: updatedAttributes,
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePatternMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributePattern) => {
|
||||||
|
if (!counters) return;
|
||||||
|
const attributes = filter.patternAttributes || [];
|
||||||
|
const patternAttribute = counters.find(
|
||||||
|
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdPattern> =>
|
||||||
|
counter.attribute.type === 'pattern' && counter.attribute.documentId === attribute.sticker.id,
|
||||||
|
)?.attribute;
|
||||||
|
|
||||||
|
if (!patternAttribute) return;
|
||||||
|
|
||||||
|
const isActive = attributes.some((item) => item.documentId === patternAttribute.documentId);
|
||||||
|
const updatedAttributes = isActive
|
||||||
|
? attributes.filter((item) => item.documentId !== patternAttribute.documentId)
|
||||||
|
: [...attributes, patternAttribute];
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
patternAttributes: updatedAttributes,
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBackdropMenuItemClick = useLastCallback((attribute: ApiStarGiftAttributeBackdrop) => {
|
||||||
|
if (!counters) return;
|
||||||
|
const attributes = filter.backdropAttributes || [];
|
||||||
|
const backdropAttribute = counters.find(
|
||||||
|
(counter): counter is ApiStarGiftAttributeCounter<ApiStarGiftAttributeIdBackdrop> =>
|
||||||
|
counter.attribute.type === 'backdrop' && counter.attribute.backdropId === attribute.backdropId,
|
||||||
|
)?.attribute;
|
||||||
|
|
||||||
|
if (!backdropAttribute) return;
|
||||||
|
|
||||||
|
const isActive = attributes.some((item) => item.backdropId === backdropAttribute.backdropId);
|
||||||
|
const updatedAttributes = isActive
|
||||||
|
? attributes.filter((item) => item.backdropId !== backdropAttribute.backdropId)
|
||||||
|
: [...attributes, backdropAttribute];
|
||||||
|
updateResaleGiftsFilter({ filter: {
|
||||||
|
...filter,
|
||||||
|
backdropAttributes: updatedAttributes,
|
||||||
|
} });
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderSortMenuItems() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem icon="sort-by-price" onClick={() => { handleSortMenuItemClick('byPrice'); }}>
|
||||||
|
<div className={styles.menuItemText}>
|
||||||
|
{lang('GiftSortByPrice')}
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
className={styles.menuItemIcon}
|
||||||
|
name={filter?.sortType === 'byPrice' ? 'check' : 'placeholder'}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="sort-by-date" onClick={() => { handleSortMenuItemClick('byDate'); }}>
|
||||||
|
<div className={styles.menuItemText}>
|
||||||
|
{lang('GiftSortByDate')}
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
className={styles.menuItemIcon}
|
||||||
|
name={filter?.sortType === 'byDate' ? 'check' : 'placeholder'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon="sort-by-number"onClick={() => { handleSortMenuItemClick('byNumber'); }}>
|
||||||
|
<div className={styles.menuItemText}>
|
||||||
|
{lang('GiftSortByNumber')}
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
className={styles.menuItemIcon}
|
||||||
|
name={filter?.sortType === 'byNumber' ? 'check' : 'placeholder'}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSortMenu() {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
isOpen={isSortContextMenuOpen}
|
||||||
|
anchor={sortContextMenuAnchor}
|
||||||
|
ref={sortMenuRef}
|
||||||
|
className={buildClassName(
|
||||||
|
styles.menu,
|
||||||
|
styles.left,
|
||||||
|
'with-menu-transitions',
|
||||||
|
)}
|
||||||
|
getMenuElement={getSortMenuElement}
|
||||||
|
autoClose
|
||||||
|
onClose={handleSortContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleSortContextMenuHide}
|
||||||
|
positionX="left"
|
||||||
|
>
|
||||||
|
{renderSortMenuItems()}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearchModelInputReset = useCallback(() => {
|
||||||
|
setSearchModelQuery('');
|
||||||
|
}, []);
|
||||||
|
const handleSearchBackdropInputReset = useCallback(() => {
|
||||||
|
setSearchBackdropQuery('');
|
||||||
|
}, []);
|
||||||
|
const handleSearchPatternInputReset = useCallback(() => {
|
||||||
|
setSearchPatternQuery('');
|
||||||
|
}, []);
|
||||||
|
const handleSearchInputClick = useLastCallback((e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelMenuItemsContainerRef = useRef<HTMLDivElement>();
|
||||||
|
const { observe } = useIntersectionObserver({
|
||||||
|
rootRef: modelMenuItemsContainerRef,
|
||||||
|
isDisabled: !modelContextMenuAnchor,
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderModelMenuItems() {
|
||||||
|
const models = filteredAndSearchedAttributes.model;
|
||||||
|
const selectedAttributes = filter.modelAttributes ?? [];
|
||||||
|
const isSelectedAll = selectedAttributes.length === 0;
|
||||||
|
return (
|
||||||
|
<div className={styles.menuContentContainer} ref={modelMenuItemsContainerRef}>
|
||||||
|
<SearchInput
|
||||||
|
onClick={handleSearchInputClick}
|
||||||
|
className={styles.search}
|
||||||
|
value={searchModelQuery}
|
||||||
|
onChange={setSearchModelQuery}
|
||||||
|
onReset={handleSearchModelInputReset}
|
||||||
|
placeholder={lang('Search')}
|
||||||
|
/>
|
||||||
|
<MenuItem icon="select" onClick={handleSelectedAllModelsClick} disabled={isSelectedAll}>
|
||||||
|
{lang('ContextMenuItemSelectAll')}
|
||||||
|
</MenuItem>
|
||||||
|
{models.map((model) => {
|
||||||
|
const isSelected = isSelectedAll
|
||||||
|
|| selectedAttributes.some((attr) => attr.documentId === model.sticker.id);
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={model.name}
|
||||||
|
onClick={() => {
|
||||||
|
handleModelMenuItemClick(model);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResaleGiftMenuAttributeSticker
|
||||||
|
className={styles.sticker}
|
||||||
|
sticker={model.sticker}
|
||||||
|
type="model"
|
||||||
|
observeIntersectionForLoading={observe}
|
||||||
|
observeIntersectionForPlaying={observe}
|
||||||
|
/>
|
||||||
|
<div className={styles.menuItemStickerText}>
|
||||||
|
{model.name}
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
className={styles.menuItemIcon}
|
||||||
|
name={isSelected ? 'check' : 'placeholder'}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderModelMenu() {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
isOpen={isModelContextMenuOpen}
|
||||||
|
anchor={modelContextMenuAnchor}
|
||||||
|
ref={modelMenuRef}
|
||||||
|
className={buildClassName(
|
||||||
|
styles.menu,
|
||||||
|
styles.left,
|
||||||
|
'with-menu-transitions',
|
||||||
|
)}
|
||||||
|
getMenuElement={getModelMenuElement}
|
||||||
|
autoClose
|
||||||
|
onClose={handleModelContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleModelContextMenuHide}
|
||||||
|
>
|
||||||
|
{renderModelMenuItems()}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBackdropMenuItems() {
|
||||||
|
const backdrops = filteredAndSearchedAttributes.backdrop;
|
||||||
|
const selectedAttributes = filter.backdropAttributes ?? [];
|
||||||
|
const isSelectedAll = selectedAttributes.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.menuContentContainer}>
|
||||||
|
<SearchInput
|
||||||
|
onClick={handleSearchInputClick}
|
||||||
|
className={styles.search}
|
||||||
|
value={searchBackdropQuery}
|
||||||
|
onChange={setSearchBackdropQuery}
|
||||||
|
onReset={handleSearchBackdropInputReset}
|
||||||
|
placeholder={lang('Search')}
|
||||||
|
/>
|
||||||
|
<MenuItem icon="select" onClick={handleSelectedAllBackdropsClick} disabled={isSelectedAll}>
|
||||||
|
{lang('ContextMenuItemSelectAll')}
|
||||||
|
</MenuItem>
|
||||||
|
{backdrops.map((backdrop) => {
|
||||||
|
const isSelected = isSelectedAll
|
||||||
|
|| selectedAttributes.some((attr) => attr.backdropId === backdrop.backdropId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={backdrop.name}
|
||||||
|
onClick={() => {
|
||||||
|
handleBackdropMenuItemClick(backdrop);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RadialPatternBackground
|
||||||
|
className={styles.backdrop}
|
||||||
|
backgroundColors={[backdrop.centerColor, backdrop.edgeColor]}
|
||||||
|
patternColor={backdrop.patternColor}
|
||||||
|
/>
|
||||||
|
<div className={styles.backdropAttributeMenuItemText}>
|
||||||
|
{backdrop.name}
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
className={styles.menuItemIcon}
|
||||||
|
name={isSelected ? 'check' : 'placeholder'}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBackdropMenu() {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
isOpen={isBackdropContextMenuOpen}
|
||||||
|
anchor={backdropContextMenuAnchor}
|
||||||
|
ref={backdropMenuRef}
|
||||||
|
className={buildClassName(styles.menu, styles.right, 'with-menu-transitions')}
|
||||||
|
getMenuElement={getBackdropMenuElement}
|
||||||
|
autoClose
|
||||||
|
onClose={handleBackdropContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleBackdropContextMenuHide}
|
||||||
|
positionX="right"
|
||||||
|
>
|
||||||
|
{renderBackdropMenuItems()}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPatternMenuItems() {
|
||||||
|
const patterns = filteredAndSearchedAttributes.pattern;
|
||||||
|
const selectedAttributes = filter.patternAttributes ?? [];
|
||||||
|
const isSelectedAll = selectedAttributes.length === 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.menuContentContainer}>
|
||||||
|
<SearchInput
|
||||||
|
onClick={handleSearchInputClick}
|
||||||
|
className={styles.search}
|
||||||
|
value={searchPatternQuery}
|
||||||
|
onChange={setSearchPatternQuery}
|
||||||
|
onReset={handleSearchPatternInputReset}
|
||||||
|
placeholder={lang('Search')}
|
||||||
|
/>
|
||||||
|
<MenuItem icon="select" onClick={handleSelectedAllPatternsClick} disabled={isSelectedAll}>
|
||||||
|
{lang('ContextMenuItemSelectAll')}
|
||||||
|
</MenuItem>
|
||||||
|
{patterns.map((pattern) => {
|
||||||
|
const isSelected = isSelectedAll
|
||||||
|
|| selectedAttributes.some((attr) => attr.documentId === pattern.sticker.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
key={pattern.name}
|
||||||
|
onClick={() => {
|
||||||
|
handlePatternMenuItemClick(pattern);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<ResaleGiftMenuAttributeSticker
|
||||||
|
className={styles.sticker}
|
||||||
|
sticker={pattern.sticker}
|
||||||
|
type="pattern"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.menuItemStickerText}>
|
||||||
|
{pattern.name}
|
||||||
|
</div>
|
||||||
|
<Icon
|
||||||
|
className={styles.menuItemIcon}
|
||||||
|
name={isSelected ? 'check' : 'placeholder'}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPatternMenu() {
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
isOpen={isPatternContextMenuOpen}
|
||||||
|
anchor={patternContextMenuAnchor}
|
||||||
|
ref={patternMenuRef}
|
||||||
|
className={buildClassName(styles.menu, styles.right, 'with-menu-transitions')}
|
||||||
|
getMenuElement={getPatternMenuElement}
|
||||||
|
autoClose
|
||||||
|
onClose={handlePatternContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handlePatternContextMenuHide}
|
||||||
|
>
|
||||||
|
{renderPatternMenuItems()}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
{Boolean(sortContextMenuAnchor) && renderSortMenu()}
|
||||||
|
{Boolean(modelContextMenuAnchor) && renderModelMenu()}
|
||||||
|
{Boolean(backdropContextMenuAnchor) && renderBackdropMenu()}
|
||||||
|
{Boolean(patternContextMenuAnchor) && renderPatternMenu()}
|
||||||
|
<div className={styles.buttonsContainer}>
|
||||||
|
<SortMenuButton
|
||||||
|
onTrigger={handleSortContextMenu}
|
||||||
|
isOpen={isSortContextMenuOpen}
|
||||||
|
/>
|
||||||
|
<ModelMenuButton
|
||||||
|
onTrigger={handleModelContextMenu}
|
||||||
|
isOpen={isModelContextMenuOpen}
|
||||||
|
/>
|
||||||
|
<BackdropMenuButton
|
||||||
|
onTrigger={handleBackdropContextMenu}
|
||||||
|
isOpen={isBackdropContextMenuOpen}
|
||||||
|
/>
|
||||||
|
<PatternMenuButton
|
||||||
|
onTrigger={handlePatternContextMenu}
|
||||||
|
isOpen={isPatternContextMenuOpen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal((global): StateProps => {
|
||||||
|
const { resaleGifts } = selectTabState(global);
|
||||||
|
|
||||||
|
const attributes = resaleGifts.attributes;
|
||||||
|
const counters = resaleGifts.counters;
|
||||||
|
const filter = resaleGifts.filter;
|
||||||
|
|
||||||
|
return {
|
||||||
|
attributes,
|
||||||
|
counters,
|
||||||
|
filter,
|
||||||
|
};
|
||||||
|
})(GiftResaleFilters));
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
.root {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, { memo, useRef } from '../../../lib/teact/teact';
|
||||||
|
import { withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiSticker } from '../../../api/types';
|
||||||
|
import type { ThemeKey } from '../../../types';
|
||||||
|
|
||||||
|
import { selectTheme } from '../../../global/selectors';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
|
import useDynamicColorListener from '../../../hooks/stickers/useDynamicColorListener';
|
||||||
|
import { type ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
|
import StickerView from '../../common/StickerView';
|
||||||
|
|
||||||
|
import styles from './ResaleGiftMenuAttributeSticker.module.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
className?: string;
|
||||||
|
type: 'model' | 'pattern';
|
||||||
|
sticker: ApiSticker;
|
||||||
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
theme: ThemeKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ATTRIBUTE_STICKER_SIZE = 1.5 * REM;
|
||||||
|
|
||||||
|
const ResaleGiftMenuAttributeSticker: FC<StateProps & OwnProps> = ({
|
||||||
|
className,
|
||||||
|
type,
|
||||||
|
sticker,
|
||||||
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
|
theme,
|
||||||
|
}) => {
|
||||||
|
const stickerRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const customColor = useDynamicColorListener(stickerRef, undefined, type !== 'pattern');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={stickerRef}
|
||||||
|
className={buildClassName(styles.root, className)}
|
||||||
|
style={`width: ${ATTRIBUTE_STICKER_SIZE}px; height: ${ATTRIBUTE_STICKER_SIZE}px`}
|
||||||
|
>
|
||||||
|
<StickerView
|
||||||
|
containerRef={stickerRef}
|
||||||
|
sticker={sticker}
|
||||||
|
size={ATTRIBUTE_STICKER_SIZE}
|
||||||
|
shouldPreloadPreview
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
thumbClassName={styles.thumb}
|
||||||
|
customColor={customColor}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>((global): StateProps => {
|
||||||
|
return {
|
||||||
|
theme: selectTheme(global),
|
||||||
|
};
|
||||||
|
})(ResaleGiftMenuAttributeSticker));
|
||||||
@ -37,6 +37,8 @@ const StarGiftCategoryList = ({
|
|||||||
.sort((a, b) => a - b),
|
.sort((a, b) => a - b),
|
||||||
[idsByCategory]);
|
[idsByCategory]);
|
||||||
|
|
||||||
|
const hasResale = idsByCategory && idsByCategory['resale'].length > 0;
|
||||||
|
|
||||||
const [selectedCategory, setSelectedCategory] = useState<StarGiftCategory>('all');
|
const [selectedCategory, setSelectedCategory] = useState<StarGiftCategory>('all');
|
||||||
|
|
||||||
function handleItemClick(category: StarGiftCategory) {
|
function handleItemClick(category: StarGiftCategory) {
|
||||||
@ -50,6 +52,7 @@ const StarGiftCategoryList = ({
|
|||||||
if (category === 'all') return lang('AllGiftsCategory');
|
if (category === 'all') return lang('AllGiftsCategory');
|
||||||
if (category === 'stock') return lang('StockGiftsCategory');
|
if (category === 'stock') return lang('StockGiftsCategory');
|
||||||
if (category === 'limited') return lang('LimitedGiftsCategory');
|
if (category === 'limited') return lang('LimitedGiftsCategory');
|
||||||
|
if (category === 'resale') return lang('GiftCategoryResale');
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +83,7 @@ const StarGiftCategoryList = ({
|
|||||||
<div ref={ref} className={buildClassName(styles.list, 'no-scrollbar')}>
|
<div ref={ref} className={buildClassName(styles.list, 'no-scrollbar')}>
|
||||||
{renderCategoryItem('all')}
|
{renderCategoryItem('all')}
|
||||||
{!areLimitedStarGiftsDisallowed && renderCategoryItem('limited')}
|
{!areLimitedStarGiftsDisallowed && renderCategoryItem('limited')}
|
||||||
|
{!areLimitedStarGiftsDisallowed && hasResale && renderCategoryItem('resale')}
|
||||||
{renderCategoryItem('stock')}
|
{renderCategoryItem('stock')}
|
||||||
{starCategories?.map(renderCategoryItem)}
|
{starCategories?.map(renderCategoryItem)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.starAmountIcon,
|
||||||
.giftResalePriceStar {
|
.giftResalePriceStar {
|
||||||
margin-inline-start: 0 !important;
|
margin-inline-start: 0 !important;
|
||||||
}
|
}
|
||||||
@ -121,10 +122,6 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.starAmountIcon {
|
|
||||||
margin-inline-start: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modalHeader {
|
.modalHeader {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|||||||
@ -58,6 +58,7 @@ type StateProps = {
|
|||||||
collectibleEmojiStatuses?: ApiEmojiStatusType[];
|
collectibleEmojiStatuses?: ApiEmojiStatusType[];
|
||||||
tonExplorerUrl?: string;
|
tonExplorerUrl?: string;
|
||||||
currentUser?: ApiUser;
|
currentUser?: ApiUser;
|
||||||
|
recipientPeer?: ApiPeer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const STICKER_SIZE = 120;
|
const STICKER_SIZE = 120;
|
||||||
@ -73,6 +74,7 @@ const GiftInfoModal = ({
|
|||||||
collectibleEmojiStatuses,
|
collectibleEmojiStatuses,
|
||||||
tonExplorerUrl,
|
tonExplorerUrl,
|
||||||
currentUser,
|
currentUser,
|
||||||
|
recipientPeer,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
closeGiftInfoModal,
|
closeGiftInfoModal,
|
||||||
@ -83,6 +85,7 @@ const GiftInfoModal = ({
|
|||||||
openGiftUpgradeModal,
|
openGiftUpgradeModal,
|
||||||
showNotification,
|
showNotification,
|
||||||
buyStarGift,
|
buyStarGift,
|
||||||
|
closeGiftModal,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
const [isConvertConfirmOpen, openConvertConfirm, closeConvertConfirm] = useFlag();
|
||||||
@ -161,7 +164,7 @@ const GiftInfoModal = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleBuyGift = useLastCallback(() => {
|
const handleBuyGift = useLastCallback(() => {
|
||||||
if (!savedGift || gift?.type !== 'starGiftUnique' || !gift.resellPriceInStars) return;
|
if (gift?.type !== 'starGiftUnique' || !gift.resellPriceInStars) return;
|
||||||
setIsConfirmModalOpen(true);
|
setIsConfirmModalOpen(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -170,9 +173,11 @@ const GiftInfoModal = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleConfirmBuyGift = useLastCallback(() => {
|
const handleConfirmBuyGift = useLastCallback(() => {
|
||||||
if (!savedGift || gift?.type !== 'starGiftUnique' || !gift.resellPriceInStars) return;
|
const peer = recipientPeer || currentUser;
|
||||||
|
if (!peer || gift?.type !== 'starGiftUnique' || !gift.resellPriceInStars) return;
|
||||||
closeConfirmModal();
|
closeConfirmModal();
|
||||||
buyStarGift({ slug: gift.slug, stars: gift.resellPriceInStars });
|
closeGiftModal();
|
||||||
|
buyStarGift({ peerId: peer.id, slug: gift.slug, stars: gift.resellPriceInStars });
|
||||||
});
|
});
|
||||||
|
|
||||||
const giftAttributes = useMemo(() => {
|
const giftAttributes = useMemo(() => {
|
||||||
@ -726,18 +731,34 @@ const GiftInfoModal = ({
|
|||||||
>
|
>
|
||||||
|
|
||||||
<GiftTransferPreview
|
<GiftTransferPreview
|
||||||
peer={currentUser}
|
peer={recipientPeer || currentUser}
|
||||||
gift={uniqueGift}
|
gift={uniqueGift}
|
||||||
/>
|
/>
|
||||||
<p>
|
{!recipientPeer
|
||||||
{lang('GiftBuyConfirmDescription', {
|
&& (
|
||||||
gift: lang('GiftUnique', { title: uniqueGift.title, number: uniqueGift.number }),
|
<p>
|
||||||
stars: formatStarsAsText(lang, resellPriceInStars),
|
{lang('GiftBuyConfirmDescription', {
|
||||||
}, {
|
gift: lang('GiftUnique', { title: uniqueGift.title, number: uniqueGift.number }),
|
||||||
withNodes: true,
|
stars: formatStarsAsText(lang, resellPriceInStars),
|
||||||
withMarkdown: true,
|
}, {
|
||||||
})}
|
withNodes: true,
|
||||||
</p>
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{recipientPeer
|
||||||
|
&& (
|
||||||
|
<p>
|
||||||
|
{lang('GiftBuyForPeerConfirmDescription', {
|
||||||
|
gift: lang('GiftUnique', { title: uniqueGift.title, number: uniqueGift.number }),
|
||||||
|
stars: formatStarsAsText(lang, resellPriceInStars),
|
||||||
|
peer: getPeerTitle(lang, recipientPeer),
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
)}
|
)}
|
||||||
{savedGift && (
|
{savedGift && (
|
||||||
@ -786,6 +807,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const chat = targetPeer && isApiPeerChat(targetPeer) ? targetPeer : undefined;
|
const chat = targetPeer && isApiPeerChat(targetPeer) ? targetPeer : undefined;
|
||||||
const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages');
|
const hasAdminRights = chat && getHasAdminRight(chat, 'postMessages');
|
||||||
const currentUser = selectUser(global, currentUserId!);
|
const currentUser = selectUser(global, currentUserId!);
|
||||||
|
const recipientPeer = modal?.recipientId && currentUserId !== modal.recipientId
|
||||||
|
? selectPeer(global, modal.recipientId) : undefined;
|
||||||
const currentUserEmojiStatus = currentUser?.emojiStatus;
|
const currentUserEmojiStatus = currentUser?.emojiStatus;
|
||||||
const collectibleEmojiStatuses = global.collectibleEmojiStatuses?.statuses;
|
const collectibleEmojiStatuses = global.collectibleEmojiStatuses?.statuses;
|
||||||
|
|
||||||
@ -799,6 +822,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
currentUserEmojiStatus,
|
currentUserEmojiStatus,
|
||||||
collectibleEmojiStatuses,
|
collectibleEmojiStatuses,
|
||||||
currentUser,
|
currentUser,
|
||||||
|
recipientPeer,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(GiftInfoModal));
|
)(GiftInfoModal));
|
||||||
|
|||||||
@ -317,6 +317,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.bluredStarsBadge {
|
||||||
|
color: var(--color-white);
|
||||||
|
background: rgba(0, 0, 0, 0.2) !important;
|
||||||
|
backdrop-filter: blur(50px);
|
||||||
|
}
|
||||||
|
|
||||||
&.smaller {
|
&.smaller {
|
||||||
height: 2.75rem;
|
height: 2.75rem;
|
||||||
padding: 0.3125rem;
|
padding: 0.3125rem;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export type OwnProps = {
|
|||||||
size?: 'default' | 'smaller' | 'tiny';
|
size?: 'default' | 'smaller' | 'tiny';
|
||||||
color?: (
|
color?: (
|
||||||
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
|
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
|
||||||
| 'translucent-bordered' | 'dark' | 'green' | 'adaptive' | 'stars'
|
| 'translucent-bordered' | 'dark' | 'green' | 'adaptive' | 'stars' | 'bluredStarsBadge'
|
||||||
);
|
);
|
||||||
backgroundImage?: string;
|
backgroundImage?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import type { ElementRef, FC } from '../../lib/teact/teact';
|
import type { ElementRef, FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useEffect, useRef,
|
memo, useEffect, useRef,
|
||||||
@ -43,7 +44,7 @@ type OwnProps = {
|
|||||||
onReset?: NoneToVoidFunction;
|
onReset?: NoneToVoidFunction;
|
||||||
onFocus?: NoneToVoidFunction;
|
onFocus?: NoneToVoidFunction;
|
||||||
onBlur?: NoneToVoidFunction;
|
onBlur?: NoneToVoidFunction;
|
||||||
onClick?: NoneToVoidFunction;
|
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
onUpClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
onUpClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onDownClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
onDownClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onSpinnerClick?: NoneToVoidFunction;
|
onSpinnerClick?: NoneToVoidFunction;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type {
|
|||||||
} from './api/types';
|
} from './api/types';
|
||||||
import type {
|
import type {
|
||||||
GiftProfileFilterOptions,
|
GiftProfileFilterOptions,
|
||||||
|
ResaleGiftsFilterOptions,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
export const APP_CODE_NAME = 'A';
|
export const APP_CODE_NAME = 'A';
|
||||||
@ -104,6 +105,7 @@ export const GROUP_CALL_PARTICIPANTS_LIMIT = 100;
|
|||||||
export const STORY_LIST_LIMIT = 100;
|
export const STORY_LIST_LIMIT = 100;
|
||||||
export const API_GENERAL_ID_LIMIT = 100;
|
export const API_GENERAL_ID_LIMIT = 100;
|
||||||
export const STATISTICS_PUBLIC_FORWARDS_LIMIT = 50;
|
export const STATISTICS_PUBLIC_FORWARDS_LIMIT = 50;
|
||||||
|
export const RESALE_GIFTS_LIMIT = 50;
|
||||||
|
|
||||||
export const STORY_VIEWS_MIN_SEARCH = 15;
|
export const STORY_VIEWS_MIN_SEARCH = 15;
|
||||||
export const STORY_MIN_REACTIONS_SORT = 10;
|
export const STORY_MIN_REACTIONS_SORT = 10;
|
||||||
@ -465,3 +467,7 @@ export const DEFAULT_GIFT_PROFILE_FILTER_OPTIONS: GiftProfileFilterOptions = {
|
|||||||
shouldIncludeDisplayed: true,
|
shouldIncludeDisplayed: true,
|
||||||
shouldIncludeHidden: true,
|
shouldIncludeHidden: true,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const DEFAULT_RESALE_GIFTS_FILTER_OPTIONS: ResaleGiftsFilterOptions = {
|
||||||
|
sortType: 'byDate',
|
||||||
|
};
|
||||||
|
|||||||
@ -146,13 +146,13 @@ addActionHandler('sendStarGift', (global, actions, payload): ActionReturnType =>
|
|||||||
|
|
||||||
addActionHandler('buyStarGift', (global, actions, payload): ActionReturnType => {
|
addActionHandler('buyStarGift', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
slug, stars, tabId = getCurrentTabId(),
|
slug, peerId, stars, tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const inputInvoice: ApiInputInvoiceStarGiftResale = {
|
const inputInvoice: ApiInputInvoiceStarGiftResale = {
|
||||||
type: 'stargiftResale',
|
type: 'stargiftResale',
|
||||||
slug,
|
slug,
|
||||||
peerId: global.currentUserId!,
|
peerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
payInputStarInvoice(global, inputInvoice, stars, tabId);
|
payInputStarInvoice(global, inputInvoice, stars, tabId);
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { ApiSavedStarGift, ApiStarGiftUnique } from '../../../api/types';
|
|||||||
import type { StarGiftCategory } from '../../../types';
|
import type { StarGiftCategory } from '../../../types';
|
||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
|
|
||||||
|
import { DEFAULT_RESALE_GIFTS_FILTER_OPTIONS, RESALE_GIFTS_LIMIT } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
@ -11,8 +12,10 @@ import {
|
|||||||
appendStarsSubscriptions,
|
appendStarsSubscriptions,
|
||||||
appendStarsTransactions,
|
appendStarsTransactions,
|
||||||
replacePeerSavedGifts,
|
replacePeerSavedGifts,
|
||||||
|
updateChats,
|
||||||
updateStarsBalance,
|
updateStarsBalance,
|
||||||
updateStarsSubscriptionLoading,
|
updateStarsSubscriptionLoading,
|
||||||
|
updateUsers,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
@ -102,6 +105,7 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
|||||||
all: [],
|
all: [],
|
||||||
stock: [],
|
stock: [],
|
||||||
limited: [],
|
limited: [],
|
||||||
|
resale: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const allStarGiftIds = Object.keys(byId);
|
const allStarGiftIds = Object.keys(byId);
|
||||||
@ -114,9 +118,13 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
|||||||
gift.availabilityRemains || !gift.availabilityTotal ? gift.id : undefined
|
gift.availabilityRemains || !gift.availabilityTotal ? gift.id : undefined
|
||||||
)).filter(Boolean);
|
)).filter(Boolean);
|
||||||
|
|
||||||
|
const resaleStarGiftIds = allStarGifts.map((gift) => (gift.availabilityResale ? gift.id : undefined))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
idsByCategoryName.all = allStarGiftIds;
|
idsByCategoryName.all = allStarGiftIds;
|
||||||
idsByCategoryName.limited = limitedStarGiftIds;
|
idsByCategoryName.limited = limitedStarGiftIds;
|
||||||
idsByCategoryName.stock = stockedStarGiftIds;
|
idsByCategoryName.stock = stockedStarGiftIds;
|
||||||
|
idsByCategoryName.resale = resaleStarGiftIds;
|
||||||
|
|
||||||
allStarGifts.forEach((gift) => {
|
allStarGifts.forEach((gift) => {
|
||||||
const starsCategory = gift.stars;
|
const starsCategory = gift.stars;
|
||||||
@ -137,6 +145,115 @@ addActionHandler('loadStarGifts', async (global): Promise<void> => {
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateResaleGiftsFilter', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
filter, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
global = updateTabState(global, {
|
||||||
|
resaleGifts: {
|
||||||
|
...tabState.resaleGifts,
|
||||||
|
filter,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
if (tabState.resaleGifts.giftId) {
|
||||||
|
actions.loadResaleGifts({ giftId: tabState.resaleGifts.giftId, shouldRefresh: true, tabId });
|
||||||
|
}
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('loadResaleGifts', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
giftId, shouldRefresh, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
let tabState = selectTabState(global, tabId);
|
||||||
|
if (tabState.resaleGifts.isLoading || (tabState.resaleGifts.isAllLoaded && !shouldRefresh)) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
resaleGifts: {
|
||||||
|
...tabState.resaleGifts,
|
||||||
|
isLoading: true,
|
||||||
|
...(shouldRefresh && {
|
||||||
|
count: 0,
|
||||||
|
nextOffset: undefined,
|
||||||
|
isAllLoaded: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
tabState = selectTabState(global, tabId);
|
||||||
|
const nextOffset = tabState.resaleGifts.nextOffset;
|
||||||
|
const attributesHash = tabState.resaleGifts.attributesHash;
|
||||||
|
const filter = tabState.resaleGifts.filter;
|
||||||
|
|
||||||
|
const result = await callApi('fetchResaleGifts', {
|
||||||
|
giftId,
|
||||||
|
offset: nextOffset,
|
||||||
|
limit: RESALE_GIFTS_LIMIT,
|
||||||
|
attributesHash,
|
||||||
|
filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
chats,
|
||||||
|
users,
|
||||||
|
} = result;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
tabState = selectTabState(global, tabId);
|
||||||
|
|
||||||
|
const currentGifts = tabState.resaleGifts.gifts;
|
||||||
|
const newGifts = !shouldRefresh ? currentGifts.concat(result.gifts) : result.gifts;
|
||||||
|
const currentUpdateIteration = tabState.resaleGifts.updateIteration;
|
||||||
|
const shouldUpdateIteration = tabState.resaleGifts.giftId !== giftId || shouldRefresh;
|
||||||
|
const updateIteration = shouldUpdateIteration ? currentUpdateIteration + 1 : currentUpdateIteration;
|
||||||
|
global = updateTabState(global, {
|
||||||
|
resaleGifts: {
|
||||||
|
...tabState.resaleGifts,
|
||||||
|
giftId,
|
||||||
|
count: result.count || tabState.resaleGifts.count,
|
||||||
|
gifts: newGifts,
|
||||||
|
attributes: result.attributes || tabState.resaleGifts.attributes,
|
||||||
|
counters: result.counters || tabState.resaleGifts.counters,
|
||||||
|
attributesHash: result.attributesHash,
|
||||||
|
nextOffset: result.nextOffset,
|
||||||
|
isLoading: false,
|
||||||
|
isAllLoaded: !result.nextOffset,
|
||||||
|
updateIteration,
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
|
||||||
|
global = updateUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
|
global = updateChats(global, buildCollectionByKey(chats, 'id'));
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('resetResaleGifts', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload || {};
|
||||||
|
|
||||||
|
const tabState = selectTabState(global, tabId);
|
||||||
|
return updateTabState(global, {
|
||||||
|
resaleGifts: {
|
||||||
|
updateIteration: tabState.resaleGifts.updateIteration + 1,
|
||||||
|
filter: DEFAULT_RESALE_GIFTS_FILTER_OPTIONS,
|
||||||
|
count: 0,
|
||||||
|
gifts: [],
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadPeerSavedGifts', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
peerId, shouldRefresh, tabId = getCurrentTabId(),
|
peerId, shouldRefresh, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -256,11 +256,13 @@ addActionHandler('openGiftInfoModal', (global, actions, payload): ActionReturnTy
|
|||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const peerId = 'peerId' in payload ? payload.peerId : undefined;
|
const peerId = 'peerId' in payload ? payload.peerId : undefined;
|
||||||
|
const recipientId = 'recipientId' in payload ? payload.recipientId : undefined;
|
||||||
|
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
giftInfoModal: {
|
giftInfoModal: {
|
||||||
peerId,
|
peerId,
|
||||||
gift,
|
gift,
|
||||||
|
recipientId,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
DEFAULT_MESSAGE_TEXT_SIZE_PX,
|
DEFAULT_MESSAGE_TEXT_SIZE_PX,
|
||||||
DEFAULT_PATTERN_COLOR,
|
DEFAULT_PATTERN_COLOR,
|
||||||
DEFAULT_PLAYBACK_RATE,
|
DEFAULT_PLAYBACK_RATE,
|
||||||
|
DEFAULT_RESALE_GIFTS_FILTER_OPTIONS,
|
||||||
DEFAULT_VOLUME,
|
DEFAULT_VOLUME,
|
||||||
IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX,
|
IOS_DEFAULT_MESSAGE_TEXT_SIZE_PX,
|
||||||
MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX,
|
MACOS_DEFAULT_MESSAGE_TEXT_SIZE_PX,
|
||||||
@ -389,6 +390,15 @@ export const INITIAL_TAB_STATE: TabState = {
|
|||||||
giftsByPeerId: {},
|
giftsByPeerId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resaleGifts: {
|
||||||
|
gifts: [],
|
||||||
|
count: 0,
|
||||||
|
updateIteration: 0,
|
||||||
|
filter: {
|
||||||
|
...DEFAULT_RESALE_GIFTS_FILTER_OPTIONS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
storyViewer: {
|
storyViewer: {
|
||||||
isMuted: true,
|
isMuted: true,
|
||||||
isRibbonShown: false,
|
isRibbonShown: false,
|
||||||
|
|||||||
@ -80,6 +80,7 @@ import type {
|
|||||||
PerformanceType,
|
PerformanceType,
|
||||||
Point,
|
Point,
|
||||||
ProfileTabType,
|
ProfileTabType,
|
||||||
|
ResaleGiftsFilterOptions,
|
||||||
ScrollTargetPosition,
|
ScrollTargetPosition,
|
||||||
SendMessageParams,
|
SendMessageParams,
|
||||||
SettingsScreens,
|
SettingsScreens,
|
||||||
@ -2394,6 +2395,14 @@ export interface ActionPayloads {
|
|||||||
|
|
||||||
loadPremiumGifts: undefined;
|
loadPremiumGifts: undefined;
|
||||||
loadStarGifts: undefined;
|
loadStarGifts: undefined;
|
||||||
|
updateResaleGiftsFilter: {
|
||||||
|
filter: ResaleGiftsFilterOptions;
|
||||||
|
} & WithTabId;
|
||||||
|
loadResaleGifts: {
|
||||||
|
giftId: string;
|
||||||
|
shouldRefresh?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
|
resetResaleGifts: WithTabId | undefined;
|
||||||
loadDefaultTopicIcons: undefined;
|
loadDefaultTopicIcons: undefined;
|
||||||
loadPremiumStickers: undefined;
|
loadPremiumStickers: undefined;
|
||||||
|
|
||||||
@ -2403,6 +2412,7 @@ export interface ActionPayloads {
|
|||||||
closeGiftModal: WithTabId | undefined;
|
closeGiftModal: WithTabId | undefined;
|
||||||
sendStarGift: StarGiftInfo & WithTabId;
|
sendStarGift: StarGiftInfo & WithTabId;
|
||||||
buyStarGift: {
|
buyStarGift: {
|
||||||
|
peerId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
stars: number;
|
stars: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
@ -2419,6 +2429,7 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
openGiftInfoModal: ({
|
openGiftInfoModal: ({
|
||||||
peerId: string;
|
peerId: string;
|
||||||
|
recipientId?: string;
|
||||||
gift: ApiSavedStarGift;
|
gift: ApiSavedStarGift;
|
||||||
} | {
|
} | {
|
||||||
gift: ApiStarGift;
|
gift: ApiStarGift;
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import type {
|
|||||||
ApiSponsoredPeer,
|
ApiSponsoredPeer,
|
||||||
ApiStarGift,
|
ApiStarGift,
|
||||||
ApiStarGiftAttribute,
|
ApiStarGiftAttribute,
|
||||||
|
ApiStarGiftAttributeCounter,
|
||||||
ApiStarGiveawayOption,
|
ApiStarGiveawayOption,
|
||||||
ApiStarsSubscription,
|
ApiStarsSubscription,
|
||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
@ -76,6 +77,7 @@ import type {
|
|||||||
PaymentStep,
|
PaymentStep,
|
||||||
ProfileEditProgress,
|
ProfileEditProgress,
|
||||||
ProfileTabType,
|
ProfileTabType,
|
||||||
|
ResaleGiftsFilterOptions,
|
||||||
ScrollTargetPosition,
|
ScrollTargetPosition,
|
||||||
SettingsScreens,
|
SettingsScreens,
|
||||||
SharedMediaType,
|
SharedMediaType,
|
||||||
@ -217,6 +219,20 @@ export type TabState = {
|
|||||||
filter: GiftProfileFilterOptions;
|
filter: GiftProfileFilterOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resaleGifts: {
|
||||||
|
giftId?: string;
|
||||||
|
gifts: ApiStarGift[];
|
||||||
|
count: number;
|
||||||
|
attributes?: ApiStarGiftAttribute[];
|
||||||
|
counters?: ApiStarGiftAttributeCounter[];
|
||||||
|
nextOffset?: string;
|
||||||
|
attributesHash?: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
isAllLoaded?: boolean;
|
||||||
|
filter: ResaleGiftsFilterOptions;
|
||||||
|
updateIteration: number;
|
||||||
|
};
|
||||||
|
|
||||||
leftColumn: {
|
leftColumn: {
|
||||||
contentKey: LeftColumnContent;
|
contentKey: LeftColumnContent;
|
||||||
settingsScreen: SettingsScreens;
|
settingsScreen: SettingsScreens;
|
||||||
@ -761,6 +777,7 @@ export type TabState = {
|
|||||||
|
|
||||||
giftInfoModal?: {
|
giftInfoModal?: {
|
||||||
peerId?: string;
|
peerId?: string;
|
||||||
|
recipientId?: string;
|
||||||
gift: ApiSavedStarGift | ApiStarGift;
|
gift: ApiSavedStarGift | ApiStarGift;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1765,6 +1765,7 @@ 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;
|
payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<InputSavedStarGift> = Bool;
|
||||||
|
payments.getResaleStarGifts#7a5fa236 flags:# sort_by_price:flags.1?true sort_by_num:flags.2?true attributes_hash:flags.0?long gift_id:long attributes:flags.3?Vector<StarGiftAttributeId> offset:string limit:int = payments.ResaleStarGifts;
|
||||||
payments.updateStarGiftPrice#3baea4e1 stargift:InputSavedStarGift resell_stars:long = Updates;
|
payments.updateStarGiftPrice#3baea4e1 stargift:InputSavedStarGift resell_stars:long = Updates;
|
||||||
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
|
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser 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;
|
||||||
|
|||||||
@ -319,6 +319,7 @@
|
|||||||
"payments.getUniqueStarGift",
|
"payments.getUniqueStarGift",
|
||||||
"payments.getStarGiftWithdrawalUrl",
|
"payments.getStarGiftWithdrawalUrl",
|
||||||
"payments.toggleStarGiftsPinnedToTop",
|
"payments.toggleStarGiftsPinnedToTop",
|
||||||
|
"payments.getResaleStarGifts",
|
||||||
"payments.updateStarGiftPrice",
|
"payments.updateStarGiftPrice",
|
||||||
"langpack.getLangPack",
|
"langpack.getLangPack",
|
||||||
"langpack.getStrings",
|
"langpack.getStrings",
|
||||||
|
|||||||
@ -109,193 +109,197 @@ $icons-map: (
|
|||||||
"double-badge": "\f148",
|
"double-badge": "\f148",
|
||||||
"down": "\f149",
|
"down": "\f149",
|
||||||
"download": "\f14a",
|
"download": "\f14a",
|
||||||
"eats": "\f14b",
|
"dropdown-arrows": "\f14b",
|
||||||
"edit": "\f14c",
|
"eats": "\f14c",
|
||||||
"email": "\f14d",
|
"edit": "\f14d",
|
||||||
"enter": "\f14e",
|
"email": "\f14e",
|
||||||
"expand-modal": "\f14f",
|
"enter": "\f14f",
|
||||||
"expand": "\f150",
|
"expand-modal": "\f150",
|
||||||
"eye-crossed-outline": "\f151",
|
"expand": "\f151",
|
||||||
"eye-crossed": "\f152",
|
"eye-crossed-outline": "\f152",
|
||||||
"eye-outline": "\f153",
|
"eye-crossed": "\f153",
|
||||||
"eye": "\f154",
|
"eye-outline": "\f154",
|
||||||
"favorite-filled": "\f155",
|
"eye": "\f155",
|
||||||
"favorite": "\f156",
|
"favorite-filled": "\f156",
|
||||||
"file-badge": "\f157",
|
"favorite": "\f157",
|
||||||
"flag": "\f158",
|
"file-badge": "\f158",
|
||||||
"folder-badge": "\f159",
|
"flag": "\f159",
|
||||||
"folder": "\f15a",
|
"folder-badge": "\f15a",
|
||||||
"fontsize": "\f15b",
|
"folder": "\f15b",
|
||||||
"forums": "\f15c",
|
"fontsize": "\f15c",
|
||||||
"forward": "\f15d",
|
"forums": "\f15d",
|
||||||
"fragment": "\f15e",
|
"forward": "\f15e",
|
||||||
"frozen-time": "\f15f",
|
"fragment": "\f15f",
|
||||||
"fullscreen": "\f160",
|
"frozen-time": "\f160",
|
||||||
"gifs": "\f161",
|
"fullscreen": "\f161",
|
||||||
"gift": "\f162",
|
"gifs": "\f162",
|
||||||
"group-filled": "\f163",
|
"gift": "\f163",
|
||||||
"group": "\f164",
|
"group-filled": "\f164",
|
||||||
"grouped-disable": "\f165",
|
"group": "\f165",
|
||||||
"grouped": "\f166",
|
"grouped-disable": "\f166",
|
||||||
"hand-stop": "\f167",
|
"grouped": "\f167",
|
||||||
"hashtag": "\f168",
|
"hand-stop": "\f168",
|
||||||
"heart-outline": "\f169",
|
"hashtag": "\f169",
|
||||||
"heart": "\f16a",
|
"heart-outline": "\f16a",
|
||||||
"help": "\f16b",
|
"heart": "\f16b",
|
||||||
"info-filled": "\f16c",
|
"help": "\f16c",
|
||||||
"info": "\f16d",
|
"info-filled": "\f16d",
|
||||||
"install": "\f16e",
|
"info": "\f16e",
|
||||||
"italic": "\f16f",
|
"install": "\f16f",
|
||||||
"key": "\f170",
|
"italic": "\f170",
|
||||||
"keyboard": "\f171",
|
"key": "\f171",
|
||||||
"lamp": "\f172",
|
"keyboard": "\f172",
|
||||||
"language": "\f173",
|
"lamp": "\f173",
|
||||||
"large-pause": "\f174",
|
"language": "\f174",
|
||||||
"large-play": "\f175",
|
"large-pause": "\f175",
|
||||||
"link-badge": "\f176",
|
"large-play": "\f176",
|
||||||
"link-broken": "\f177",
|
"link-badge": "\f177",
|
||||||
"link": "\f178",
|
"link-broken": "\f178",
|
||||||
"location": "\f179",
|
"link": "\f179",
|
||||||
"lock-badge": "\f17a",
|
"location": "\f17a",
|
||||||
"lock": "\f17b",
|
"lock-badge": "\f17b",
|
||||||
"logout": "\f17c",
|
"lock": "\f17c",
|
||||||
"loop": "\f17d",
|
"logout": "\f17d",
|
||||||
"mention": "\f17e",
|
"loop": "\f17e",
|
||||||
"message-failed": "\f17f",
|
"mention": "\f17f",
|
||||||
"message-pending": "\f180",
|
"message-failed": "\f180",
|
||||||
"message-read": "\f181",
|
"message-pending": "\f181",
|
||||||
"message-succeeded": "\f182",
|
"message-read": "\f182",
|
||||||
"message": "\f183",
|
"message-succeeded": "\f183",
|
||||||
"microphone-alt": "\f184",
|
"message": "\f184",
|
||||||
"microphone": "\f185",
|
"microphone-alt": "\f185",
|
||||||
"monospace": "\f186",
|
"microphone": "\f186",
|
||||||
"more-circle": "\f187",
|
"monospace": "\f187",
|
||||||
"more": "\f188",
|
"more-circle": "\f188",
|
||||||
"move-caption-down": "\f189",
|
"more": "\f189",
|
||||||
"move-caption-up": "\f18a",
|
"move-caption-down": "\f18a",
|
||||||
"mute": "\f18b",
|
"move-caption-up": "\f18b",
|
||||||
"muted": "\f18c",
|
"mute": "\f18c",
|
||||||
"my-notes": "\f18d",
|
"muted": "\f18d",
|
||||||
"new-chat-filled": "\f18e",
|
"my-notes": "\f18e",
|
||||||
"next": "\f18f",
|
"new-chat-filled": "\f18f",
|
||||||
"nochannel": "\f190",
|
"next": "\f190",
|
||||||
"noise-suppression": "\f191",
|
"nochannel": "\f191",
|
||||||
"non-contacts": "\f192",
|
"noise-suppression": "\f192",
|
||||||
"one-filled": "\f193",
|
"non-contacts": "\f193",
|
||||||
"open-in-new-tab": "\f194",
|
"one-filled": "\f194",
|
||||||
"password-off": "\f195",
|
"open-in-new-tab": "\f195",
|
||||||
"pause": "\f196",
|
"password-off": "\f196",
|
||||||
"permissions": "\f197",
|
"pause": "\f197",
|
||||||
"phone-discard-outline": "\f198",
|
"permissions": "\f198",
|
||||||
"phone-discard": "\f199",
|
"phone-discard-outline": "\f199",
|
||||||
"phone": "\f19a",
|
"phone-discard": "\f19a",
|
||||||
"photo": "\f19b",
|
"phone": "\f19b",
|
||||||
"pin-badge": "\f19c",
|
"photo": "\f19c",
|
||||||
"pin-list": "\f19d",
|
"pin-badge": "\f19d",
|
||||||
"pin": "\f19e",
|
"pin-list": "\f19e",
|
||||||
"pinned-chat": "\f19f",
|
"pin": "\f19f",
|
||||||
"pinned-message": "\f1a0",
|
"pinned-chat": "\f1a0",
|
||||||
"pip": "\f1a1",
|
"pinned-message": "\f1a1",
|
||||||
"play-story": "\f1a2",
|
"pip": "\f1a2",
|
||||||
"play": "\f1a3",
|
"play-story": "\f1a3",
|
||||||
"poll": "\f1a4",
|
"play": "\f1a4",
|
||||||
"previous": "\f1a5",
|
"poll": "\f1a5",
|
||||||
"privacy-policy": "\f1a6",
|
"previous": "\f1a6",
|
||||||
"proof-of-ownership": "\f1a7",
|
"privacy-policy": "\f1a7",
|
||||||
"quote-text": "\f1a8",
|
"proof-of-ownership": "\f1a8",
|
||||||
"quote": "\f1a9",
|
"quote-text": "\f1a9",
|
||||||
"radial-badge": "\f1aa",
|
"quote": "\f1aa",
|
||||||
"readchats": "\f1ab",
|
"radial-badge": "\f1ab",
|
||||||
"recent": "\f1ac",
|
"readchats": "\f1ac",
|
||||||
"reload": "\f1ad",
|
"recent": "\f1ad",
|
||||||
"remove-quote": "\f1ae",
|
"reload": "\f1ae",
|
||||||
"remove": "\f1af",
|
"remove-quote": "\f1af",
|
||||||
"reopen-topic": "\f1b0",
|
"remove": "\f1b0",
|
||||||
"replace": "\f1b1",
|
"reopen-topic": "\f1b1",
|
||||||
"replies": "\f1b2",
|
"replace": "\f1b2",
|
||||||
"reply-filled": "\f1b3",
|
"replies": "\f1b3",
|
||||||
"reply": "\f1b4",
|
"reply-filled": "\f1b4",
|
||||||
"revenue-split": "\f1b5",
|
"reply": "\f1b5",
|
||||||
"revote": "\f1b6",
|
"revenue-split": "\f1b6",
|
||||||
"save-story": "\f1b7",
|
"revote": "\f1b7",
|
||||||
"saved-messages": "\f1b8",
|
"save-story": "\f1b8",
|
||||||
"schedule": "\f1b9",
|
"saved-messages": "\f1b9",
|
||||||
"search": "\f1ba",
|
"schedule": "\f1ba",
|
||||||
"select": "\f1bb",
|
"search": "\f1bb",
|
||||||
"sell-outline": "\f1bc",
|
"select": "\f1bc",
|
||||||
"sell": "\f1bd",
|
"sell-outline": "\f1bd",
|
||||||
"send-outline": "\f1be",
|
"sell": "\f1be",
|
||||||
"send": "\f1bf",
|
"send-outline": "\f1bf",
|
||||||
"settings-filled": "\f1c0",
|
"send": "\f1c0",
|
||||||
"settings": "\f1c1",
|
"settings-filled": "\f1c1",
|
||||||
"share-filled": "\f1c2",
|
"settings": "\f1c2",
|
||||||
"share-screen-outlined": "\f1c3",
|
"share-filled": "\f1c3",
|
||||||
"share-screen-stop": "\f1c4",
|
"share-screen-outlined": "\f1c4",
|
||||||
"share-screen": "\f1c5",
|
"share-screen-stop": "\f1c5",
|
||||||
"show-message": "\f1c6",
|
"share-screen": "\f1c6",
|
||||||
"sidebar": "\f1c7",
|
"show-message": "\f1c7",
|
||||||
"skip-next": "\f1c8",
|
"sidebar": "\f1c8",
|
||||||
"skip-previous": "\f1c9",
|
"skip-next": "\f1c9",
|
||||||
"smallscreen": "\f1ca",
|
"skip-previous": "\f1ca",
|
||||||
"smile": "\f1cb",
|
"smallscreen": "\f1cb",
|
||||||
"sort": "\f1cc",
|
"smile": "\f1cc",
|
||||||
"speaker-muted-story": "\f1cd",
|
"sort-by-date": "\f1cd",
|
||||||
"speaker-outline": "\f1ce",
|
"sort-by-number": "\f1ce",
|
||||||
"speaker-story": "\f1cf",
|
"sort-by-price": "\f1cf",
|
||||||
"speaker": "\f1d0",
|
"sort": "\f1d0",
|
||||||
"spoiler-disable": "\f1d1",
|
"speaker-muted-story": "\f1d1",
|
||||||
"spoiler": "\f1d2",
|
"speaker-outline": "\f1d2",
|
||||||
"sport": "\f1d3",
|
"speaker-story": "\f1d3",
|
||||||
"star": "\f1d4",
|
"speaker": "\f1d4",
|
||||||
"stars-lock": "\f1d5",
|
"spoiler-disable": "\f1d5",
|
||||||
"stats": "\f1d6",
|
"spoiler": "\f1d6",
|
||||||
"stealth-future": "\f1d7",
|
"sport": "\f1d7",
|
||||||
"stealth-past": "\f1d8",
|
"star": "\f1d8",
|
||||||
"stickers": "\f1d9",
|
"stars-lock": "\f1d9",
|
||||||
"stop-raising-hand": "\f1da",
|
"stats": "\f1da",
|
||||||
"stop": "\f1db",
|
"stealth-future": "\f1db",
|
||||||
"story-caption": "\f1dc",
|
"stealth-past": "\f1dc",
|
||||||
"story-expired": "\f1dd",
|
"stickers": "\f1dd",
|
||||||
"story-priority": "\f1de",
|
"stop-raising-hand": "\f1de",
|
||||||
"story-reply": "\f1df",
|
"stop": "\f1df",
|
||||||
"strikethrough": "\f1e0",
|
"story-caption": "\f1e0",
|
||||||
"tag-add": "\f1e1",
|
"story-expired": "\f1e1",
|
||||||
"tag-crossed": "\f1e2",
|
"story-priority": "\f1e2",
|
||||||
"tag-filter": "\f1e3",
|
"story-reply": "\f1e3",
|
||||||
"tag-name": "\f1e4",
|
"strikethrough": "\f1e4",
|
||||||
"tag": "\f1e5",
|
"tag-add": "\f1e5",
|
||||||
"timer": "\f1e6",
|
"tag-crossed": "\f1e6",
|
||||||
"toncoin": "\f1e7",
|
"tag-filter": "\f1e7",
|
||||||
"trade": "\f1e8",
|
"tag-name": "\f1e8",
|
||||||
"transcribe": "\f1e9",
|
"tag": "\f1e9",
|
||||||
"truck": "\f1ea",
|
"timer": "\f1ea",
|
||||||
"unarchive": "\f1eb",
|
"toncoin": "\f1eb",
|
||||||
"underlined": "\f1ec",
|
"trade": "\f1ec",
|
||||||
"unique-profile": "\f1ed",
|
"transcribe": "\f1ed",
|
||||||
"unlist-outline": "\f1ee",
|
"truck": "\f1ee",
|
||||||
"unlist": "\f1ef",
|
"unarchive": "\f1ef",
|
||||||
"unlock-badge": "\f1f0",
|
"underlined": "\f1f0",
|
||||||
"unlock": "\f1f1",
|
"unique-profile": "\f1f1",
|
||||||
"unmute": "\f1f2",
|
"unlist-outline": "\f1f2",
|
||||||
"unpin": "\f1f3",
|
"unlist": "\f1f3",
|
||||||
"unread": "\f1f4",
|
"unlock-badge": "\f1f4",
|
||||||
"up": "\f1f5",
|
"unlock": "\f1f5",
|
||||||
"user-filled": "\f1f6",
|
"unmute": "\f1f6",
|
||||||
"user-online": "\f1f7",
|
"unpin": "\f1f7",
|
||||||
"user": "\f1f8",
|
"unread": "\f1f8",
|
||||||
"video-outlined": "\f1f9",
|
"up": "\f1f9",
|
||||||
"video-stop": "\f1fa",
|
"user-filled": "\f1fa",
|
||||||
"video": "\f1fb",
|
"user-online": "\f1fb",
|
||||||
"view-once": "\f1fc",
|
"user": "\f1fc",
|
||||||
"voice-chat": "\f1fd",
|
"video-outlined": "\f1fd",
|
||||||
"volume-1": "\f1fe",
|
"video-stop": "\f1fe",
|
||||||
"volume-2": "\f1ff",
|
"video": "\f1ff",
|
||||||
"volume-3": "\f200",
|
"view-once": "\f200",
|
||||||
"web": "\f201",
|
"voice-chat": "\f201",
|
||||||
"webapp": "\f202",
|
"volume-1": "\f202",
|
||||||
"word-wrap": "\f203",
|
"volume-2": "\f203",
|
||||||
"zoom-in": "\f204",
|
"volume-3": "\f204",
|
||||||
"zoom-out": "\f205",
|
"web": "\f205",
|
||||||
|
"webapp": "\f206",
|
||||||
|
"word-wrap": "\f207",
|
||||||
|
"zoom-in": "\f208",
|
||||||
|
"zoom-out": "\f209",
|
||||||
);
|
);
|
||||||
|
|
||||||
.icon-active-sessions::before {
|
.icon-active-sessions::before {
|
||||||
@ -520,6 +524,9 @@ $icons-map: (
|
|||||||
.icon-download::before {
|
.icon-download::before {
|
||||||
content: map.get($icons-map, "download");
|
content: map.get($icons-map, "download");
|
||||||
}
|
}
|
||||||
|
.icon-dropdown-arrows::before {
|
||||||
|
content: map.get($icons-map, "dropdown-arrows");
|
||||||
|
}
|
||||||
.icon-eats::before {
|
.icon-eats::before {
|
||||||
content: map.get($icons-map, "eats");
|
content: map.get($icons-map, "eats");
|
||||||
}
|
}
|
||||||
@ -907,6 +914,15 @@ $icons-map: (
|
|||||||
.icon-smile::before {
|
.icon-smile::before {
|
||||||
content: map.get($icons-map, "smile");
|
content: map.get($icons-map, "smile");
|
||||||
}
|
}
|
||||||
|
.icon-sort-by-date::before {
|
||||||
|
content: map.get($icons-map, "sort-by-date");
|
||||||
|
}
|
||||||
|
.icon-sort-by-number::before {
|
||||||
|
content: map.get($icons-map, "sort-by-number");
|
||||||
|
}
|
||||||
|
.icon-sort-by-price::before {
|
||||||
|
content: map.get($icons-map, "sort-by-price");
|
||||||
|
}
|
||||||
.icon-sort::before {
|
.icon-sort::before {
|
||||||
content: map.get($icons-map, "sort");
|
content: map.get($icons-map, "sort");
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -73,6 +73,7 @@ export type FontIconName =
|
|||||||
| 'double-badge'
|
| 'double-badge'
|
||||||
| 'down'
|
| 'down'
|
||||||
| 'download'
|
| 'download'
|
||||||
|
| 'dropdown-arrows'
|
||||||
| 'eats'
|
| 'eats'
|
||||||
| 'edit'
|
| 'edit'
|
||||||
| 'email'
|
| 'email'
|
||||||
@ -202,6 +203,9 @@ export type FontIconName =
|
|||||||
| 'skip-previous'
|
| 'skip-previous'
|
||||||
| 'smallscreen'
|
| 'smallscreen'
|
||||||
| 'smile'
|
| 'smile'
|
||||||
|
| 'sort-by-date'
|
||||||
|
| 'sort-by-number'
|
||||||
|
| 'sort-by-price'
|
||||||
| 'sort'
|
| 'sort'
|
||||||
| 'speaker-muted-story'
|
| 'speaker-muted-story'
|
||||||
| 'speaker-outline'
|
| 'speaker-outline'
|
||||||
|
|||||||
@ -26,6 +26,8 @@ import type {
|
|||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiReactionWithPaid,
|
ApiReactionWithPaid,
|
||||||
|
ApiStarGiftAttributeIdBackdrop,
|
||||||
|
ApiStarGiftAttributeIdPattern,
|
||||||
ApiStarGiftRegular,
|
ApiStarGiftRegular,
|
||||||
ApiStarsSubscription,
|
ApiStarsSubscription,
|
||||||
ApiStarsTransaction,
|
ApiStarsTransaction,
|
||||||
@ -37,6 +39,7 @@ import type {
|
|||||||
ApiTopic,
|
ApiTopic,
|
||||||
ApiTypingStatus,
|
ApiTypingStatus,
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
|
StarGiftAttributeIdModel,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
import type { DC_IDS } from '../config';
|
import type { DC_IDS } from '../config';
|
||||||
import type { SearchResultKey } from '../util/keys/searchResultKey';
|
import type { SearchResultKey } from '../util/keys/searchResultKey';
|
||||||
@ -687,7 +690,7 @@ export interface Point {
|
|||||||
|
|
||||||
export type WebPageMediaSize = 'large' | 'small';
|
export type WebPageMediaSize = 'large' | 'small';
|
||||||
|
|
||||||
export type StarGiftCategory = number | 'all' | 'limited' | 'stock';
|
export type StarGiftCategory = number | 'all' | 'limited' | 'stock' | 'resale';
|
||||||
|
|
||||||
export type CallSound = (
|
export type CallSound = (
|
||||||
'join' | 'allowTalk' | 'leave' | 'connecting' | 'incoming' | 'end' | 'connect' | 'busy' | 'ringing'
|
'join' | 'allowTalk' | 'leave' | 'connecting' | 'incoming' | 'end' | 'connect' | 'busy' | 'ringing'
|
||||||
@ -705,6 +708,13 @@ export type GiftProfileFilterOptions = {
|
|||||||
shouldIncludeDisplayed: boolean;
|
shouldIncludeDisplayed: boolean;
|
||||||
shouldIncludeHidden: boolean;
|
shouldIncludeHidden: boolean;
|
||||||
};
|
};
|
||||||
|
export type ResaleGiftsSortType = 'byDate' | 'byPrice' | 'byNumber';
|
||||||
|
export type ResaleGiftsFilterOptions = {
|
||||||
|
sortType: ResaleGiftsSortType;
|
||||||
|
modelAttributes?: StarGiftAttributeIdModel[];
|
||||||
|
patternAttributes?: ApiStarGiftAttributeIdPattern[];
|
||||||
|
backdropAttributes?: ApiStarGiftAttributeIdBackdrop[];
|
||||||
|
};
|
||||||
|
|
||||||
export type SendMessageParams = {
|
export type SendMessageParams = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
|
|||||||
28
src/types/language.d.ts
vendored
28
src/types/language.d.ts
vendored
@ -1514,6 +1514,17 @@ export interface LangPair {
|
|||||||
'StarGiftSaleTransaction': undefined;
|
'StarGiftSaleTransaction': undefined;
|
||||||
'StarGiftPurchaseTransaction': undefined;
|
'StarGiftPurchaseTransaction': undefined;
|
||||||
'ContextMenuItemMention': undefined;
|
'ContextMenuItemMention': undefined;
|
||||||
|
'GiftRibbonResale': undefined;
|
||||||
|
'GiftCategoryResale': undefined;
|
||||||
|
'GiftSortByPrice': undefined;
|
||||||
|
'GiftSortByNumber': undefined;
|
||||||
|
'ContextMenuItemSelectAll': undefined;
|
||||||
|
'ButtonSort': undefined;
|
||||||
|
'ValueGiftSortByDate': undefined;
|
||||||
|
'ValueGiftSortByPrice': undefined;
|
||||||
|
'ValueGiftSortByNumber': undefined;
|
||||||
|
'ResellGiftsNoFound': undefined;
|
||||||
|
'ResellGiftsClearFilters': undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LangPairWithVariables<V = LangVariable> {
|
export interface LangPairWithVariables<V = LangVariable> {
|
||||||
@ -2464,6 +2475,11 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'gift': V;
|
'gift': V;
|
||||||
'stars': V;
|
'stars': V;
|
||||||
};
|
};
|
||||||
|
'GiftBuyForPeerConfirmDescription': {
|
||||||
|
'gift': V;
|
||||||
|
'stars': V;
|
||||||
|
'peer': V;
|
||||||
|
};
|
||||||
'ComposerTitleForwardFrom': {
|
'ComposerTitleForwardFrom': {
|
||||||
'users': V;
|
'users': V;
|
||||||
};
|
};
|
||||||
@ -2758,6 +2774,18 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
|||||||
'PaidMessageTransaction': {
|
'PaidMessageTransaction': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'HeaderDescriptionResaleGifts': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftAttributeModelPlural': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftAttributeBackdropPlural': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'GiftAttributeSymbolPlural': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export type RegularLangKey = keyof LangPair;
|
export type RegularLangKey = keyof LangPair;
|
||||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user