From 699dfa4a1e7e1963a8657c36a1f5d042b725e97d Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:11:39 +0400 Subject: [PATCH] Gifts: Show more info (#5114) --- src/api/gramjs/apiBuilders/appConfig.ts | 2 + src/api/gramjs/apiBuilders/payments.ts | 6 +- src/api/types/misc.ts | 1 + src/api/types/payments.ts | 3 + src/assets/localization/fallback.strings | 14 +- src/components/modals/gift/GiftItemStar.tsx | 9 +- .../gift/info/GiftInfoModal.module.scss | 4 + .../modals/gift/info/GiftInfoModal.tsx | 156 +++++++++++++----- src/global/actions/ui/stars.ts | 4 +- src/global/types.ts | 10 +- src/types/language.d.ts | 17 +- 11 files changed, 166 insertions(+), 60 deletions(-) diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 42374cc7f..77086bc6c 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -85,6 +85,7 @@ export interface GramJsAppConfig extends LimitsConfig { upload_premium_speedup_upload?: number; stars_gifts_enabled?: boolean; stargifts_message_length_max?: number; + stargifts_convert_period_max?: number; } function buildEmojiSounds(appConfig: GramJsAppConfig) { @@ -169,5 +170,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp isChannelRevenueWithdrawalEnabled: appConfig.channel_revenue_withdrawal_enabled, isStarsGiftEnabled: appConfig.stars_gifts_enabled, starGiftMaxMessageLength: appConfig.stargifts_message_length_max, + starGiftMaxConvertPeriod: appConfig.stargifts_convert_period_max, }; } diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index bee6be99c..ab42e9880 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -585,7 +585,8 @@ export function buildApiStarTopupOption(option: GramJs.TypeStarsTopupOption): Ap export function buildApiStarGift(startGift: GramJs.StarGift): ApiStarGift { const { - id, limited, sticker, stars, availabilityRemains, availabilityTotal, convertStars, + id, limited, sticker, stars, availabilityRemains, availabilityTotal, convertStars, firstSaleDate, lastSaleDate, + soldOut, } = startGift; return { @@ -596,6 +597,9 @@ export function buildApiStarGift(startGift: GramJs.StarGift): ApiStarGift { availabilityRemains, availabilityTotal, starsToConvert: convertStars.toJSNumber(), + firstSaleDate, + lastSaleDate, + isSoldOut: soldOut, }; } diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 98d0082ce..955306b14 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -237,6 +237,7 @@ export interface ApiAppConfig { isChannelRevenueWithdrawalEnabled?: boolean; isStarsGiftEnabled?: boolean; starGiftMaxMessageLength?: number; + starGiftMaxConvertPeriod?: number; } export interface ApiConfig { diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts index 0bffc4782..a10d51980 100644 --- a/src/api/types/payments.ts +++ b/src/api/types/payments.ts @@ -196,6 +196,9 @@ export type ApiStarGift = { availabilityRemains?: number; availabilityTotal?: number; starsToConvert: number; + isSoldOut?: true; + firstSaleDate?: number; + lastSaleDate?: number; }; export interface ApiUserStarGift { diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 2ef78d6f3..dbfd97b4a 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1296,7 +1296,6 @@ "GiftSoldCount" = "{count} sold"; "GiftLeftCount" = "{count} left"; "GiftSoldOut" = "sold out"; -"GiftSoldOutInfo" = "Sorry, this gift is sold out."; "GiftMessagePlaceholder" = "Enter Message (Optional)"; "GiftHideMyName" = "Hide My Name"; "GiftHideNameDescription" = "Hide my name and message from visitors to {profile}'s profile. {receiver} will still see your name and message."; @@ -1320,10 +1319,21 @@ "GiftInfoConvert_one" = "Convert to {amount} Star"; "GiftInfoConvert_other" = "Convert to {amount} Stars"; "GiftInfoConvertTitle" = "Convert Gift to Stars"; -"GiftInfoConvertDescription" = "Do you want to convert this gift from **{user}** to **{amount}**?\n\nThis action cannot be undone. This will permanently destroy the gift."; +"GiftInfoConvertDescription1" = "Do you want to convert this gift from **{user}** to **{amount}**?"; +"GiftInfoConvertDescription2" = "This action cannot be undone. This will permanently destroy the gift."; +"GiftInfoConvertDescriptionPeriod_one" = "Conversion is available for the next **{count} days**." +"GiftInfoConvertDescriptionPeriod_other" = "Conversion is available for the next **{count} days**." "GiftInfoSaved" = "This gift is visible on your profile. {link}"; "GiftInfoSavedView" = "View >"; "GiftInfoHidden" = "This gift is hidden. Only you can see it."; +"GiftInfoAvailability" = "Availability"; +"GiftInfoAvailabilityValue_one" = "{count} of {total} left"; +"GiftInfoAvailabilityValue_other" = "{count} of {total} left"; +"GiftInfoFirstSale" = "First Sale"; +"GiftInfoLastSale" = "Last Sale"; +"GiftInfoSoldOutTitle" = "Unavailable"; +"GiftInfoSoldOutDescription" = "This gift has been sold out"; +"GiftInfoSenderHidden" = "Only you can see the sender's name and message."; "StarsAmount" = "⭐️{amount}"; "StarsAmountText_one" = "{amount} Star"; "StarsAmountText_other" = "{amount} Stars"; diff --git a/src/components/modals/gift/GiftItemStar.tsx b/src/components/modals/gift/GiftItemStar.tsx index 7cbb59624..856760247 100644 --- a/src/components/modals/gift/GiftItemStar.tsx +++ b/src/components/modals/gift/GiftItemStar.tsx @@ -30,21 +30,18 @@ export type StateProps = { const GIFT_STICKER_SIZE = 90; function GiftItemStar({ sticker, gift, onClick }: OwnProps & StateProps) { - const { showNotification } = getActions(); + const { openGiftInfoModal } = getActions(); const lang = useLang(); const { stars, isLimited, - availabilityRemains, - availabilityTotal, + isSoldOut, } = gift; - const isSoldOut = availabilityTotal && !availabilityRemains; - const handleGiftClick = useLastCallback(() => { if (isSoldOut) { - showNotification({ message: lang('GiftSoldOutInfo') }); + openGiftInfoModal({ gift }); return; } diff --git a/src/components/modals/gift/info/GiftInfoModal.module.scss b/src/components/modals/gift/info/GiftInfoModal.module.scss index b102c52c8..406ca628e 100644 --- a/src/components/modals/gift/info/GiftInfoModal.module.scss +++ b/src/components/modals/gift/info/GiftInfoModal.module.scss @@ -19,6 +19,10 @@ margin-bottom: 0; } +.soldOut { + color: var(--color-error); +} + .description { text-align: center; } diff --git a/src/components/modals/gift/info/GiftInfoModal.tsx b/src/components/modals/gift/info/GiftInfoModal.tsx index 3aea49dd2..935218b67 100644 --- a/src/components/modals/gift/info/GiftInfoModal.tsx +++ b/src/components/modals/gift/info/GiftInfoModal.tsx @@ -7,6 +7,7 @@ import type { TabState } from '../../../../global/types'; import { STARS_ICON_PLACEHOLDER } from '../../../../config'; import { getUserFullName } from '../../../../global/helpers'; import { selectStarGiftSticker, selectUser } from '../../../../global/selectors'; +import buildClassName from '../../../../util/buildClassName'; import { formatDateTimeToString } from '../../../../util/dates/dateFormat'; import { CUSTOM_PEER_HIDDEN } from '../../../../util/objects/customPeer'; import { formatInteger } from '../../../../util/textFormat'; @@ -38,12 +39,13 @@ type StateProps = { userFrom?: ApiUser; targetUser?: ApiUser; currentUserId?: string; + starGiftMaxConvertPeriod?: number; }; const STICKER_SIZE = 120; const GiftInfoModal = ({ - modal, sticker, userFrom, targetUser, currentUserId, + modal, sticker, userFrom, targetUser, currentUserId, starGiftMaxConvertPeriod, }: OwnProps & StateProps) => { const { closeGiftInfoModal, @@ -59,9 +61,14 @@ const GiftInfoModal = ({ const isOpen = Boolean(modal); const renderingModal = useCurrentOrPrev(modal); - const { gift: userGift } = renderingModal || {}; + const { gift: typeGift } = renderingModal || {}; + const isUserGift = typeGift && 'gift' in typeGift; + const userGift = isUserGift ? typeGift : undefined; const canUpdate = Boolean(userGift?.fromId && userGift.messageId); const isSender = userGift?.fromId === currentUserId; + const canConvertDifference = (userGift && starGiftMaxConvertPeriod && ( + userGift.date + starGiftMaxConvertPeriod - Date.now() / 1000 + )) || 0; const handleClose = useLastCallback(() => { closeGiftInfoModal(); @@ -86,16 +93,23 @@ const GiftInfoModal = ({ }); const modalData = useMemo(() => { - if (!userGift) { + if (!typeGift) { return undefined; } const { - gift, date, fromId, isNameHidden, message, starsToConvert, isUnsaved, isConverted, - } = userGift; + fromId, isNameHidden, message, starsToConvert, isUnsaved, isConverted, + } = userGift || {}; + const gift = isUserGift ? typeGift.gift : typeGift; + + const isVisibleForMe = isNameHidden && targetUser; const description = (() => { + if (!userGift) { + return lang('GiftInfoSoldOutDescription'); + } if (!canUpdate && !isSender) return undefined; + if (!starsToConvert || canConvertDifference < 0) return undefined; if (isConverted) { return canUpdate ? lang('GiftInfoDescriptionConverted', { @@ -135,16 +149,19 @@ const GiftInfoModal = ({
-
- {formatInteger(gift.stars)}
-
-
+
+ {formatInteger(gift.stars)}
+
+
+
{description}
)} @@ -164,10 +181,26 @@ const GiftInfoModal = ({ ]); } - tableData.push([ - lang('GiftInfoDate'), - formatDateTimeToString(date * 1000, lang.code, true), - ]); + if (userGift?.date) { + tableData.push([ + lang('GiftInfoDate'), + formatDateTimeToString(userGift.date * 1000, lang.code, true), + ]); + } + + if (gift.firstSaleDate) { + tableData.push([ + lang('GiftInfoFirstSale'), + formatDateTimeToString(gift.firstSaleDate * 1000, lang.code, true), + ]); + } + + if (gift.lastSaleDate) { + tableData.push([ + lang('GiftInfoLastSale'), + formatDateTimeToString(gift.lastSaleDate * 1000, lang.code, true), + ]); + } tableData.push([ lang('GiftInfoValue'), @@ -180,7 +213,7 @@ const GiftInfoModal = ({ [STARS_ICON_PLACEHOLDER]:- {isUnsaved ? lang('GiftInfoHidden') - : lang('GiftInfoSaved', { - link: {lang('GiftInfoSavedView')}, - }, { - withNodes: true, - })} -
+