Profile Info: Support rating (#6140)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
@ -3,6 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import type {
|
||||
ApiBirthday,
|
||||
ApiPeerSettings,
|
||||
ApiStarsRating,
|
||||
ApiUser,
|
||||
ApiUserFullInfo,
|
||||
ApiUserStatus,
|
||||
@ -27,6 +28,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
||||
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
|
||||
botCanManageEmojiStatus, settings, sendPaidMessagesStars, displayGiftsButton, disallowedGifts,
|
||||
starsRating, starsMyPendingRating, starsMyPendingRatingDate,
|
||||
},
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
@ -57,6 +59,9 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
botVerification: botVerification && buildApiBotVerification(botVerification),
|
||||
areAdsEnabled: sponsoredEnabled,
|
||||
starGiftCount: stargiftsCount,
|
||||
starsRating: starsRating && buildApiStarsRating(starsRating),
|
||||
starsMyPendingRating: starsMyPendingRating && buildApiStarsRating(starsMyPendingRating),
|
||||
starsMyPendingRatingDate,
|
||||
isBotCanManageEmojiStatus: botCanManageEmojiStatus,
|
||||
hasScheduledMessages: hasScheduled,
|
||||
paidMessagesStars: sendPaidMessagesStars?.toJSNumber(),
|
||||
@ -182,3 +187,12 @@ export function buildApiUserStatuses(mtpUsers: GramJs.TypeUser[]) {
|
||||
export function buildApiBirthday(birthday: GramJs.TypeBirthday): ApiBirthday {
|
||||
return omitVirtualClassFields(birthday);
|
||||
}
|
||||
|
||||
export function buildApiStarsRating(starsRating: GramJs.StarsRating): ApiStarsRating {
|
||||
return {
|
||||
level: starsRating.level,
|
||||
currentLevelStars: starsRating.currentLevelStars.toJSNumber(),
|
||||
stars: starsRating.stars.toJSNumber(),
|
||||
nextLevelStars: starsRating.nextLevelStars?.toJSNumber(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -281,3 +281,10 @@ export interface ApiDisallowedGiftsSettings {
|
||||
shouldDisallowUniqueStarGifts?: true;
|
||||
shouldDisallowPremiumGifts?: true;
|
||||
}
|
||||
|
||||
export interface ApiStarsRating {
|
||||
level: number;
|
||||
currentLevelStars: number;
|
||||
stars: number;
|
||||
nextLevelStars?: number;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from
|
||||
import type { ApiPeerColor, ApiPeerSettings } from './chats';
|
||||
import type { ApiDocument, ApiPhoto } from './messages';
|
||||
import type { ApiBotVerification } from './misc';
|
||||
import type { ApiSavedStarGift } from './stars';
|
||||
import type { ApiSavedStarGift, ApiStarsRating } from './stars';
|
||||
|
||||
export interface ApiUser {
|
||||
id: string;
|
||||
@ -65,6 +65,9 @@ export interface ApiUserFullInfo {
|
||||
businessWorkHours?: ApiBusinessWorkHours;
|
||||
businessIntro?: ApiBusinessIntro;
|
||||
starGiftCount?: number;
|
||||
starsRating?: ApiStarsRating;
|
||||
starsMyPendingRating?: ApiStarsRating;
|
||||
starsMyPendingRatingDate?: number;
|
||||
isBotCanManageEmojiStatus?: boolean;
|
||||
isBotAccessEmojiGranted?: boolean;
|
||||
hasScheduledMessages?: boolean;
|
||||
|
||||
1
src/assets/font-icons/rating-icons/level1.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M48.512 13a10 10 0 0 1 9.973 9.292c.731 10.345.201 18.298-1.69 23.973-2.526 7.577-14.793 15.84-20.846 15.734-5.774-.1-18.458-8.877-20.744-15.734-1.891-5.675-2.421-13.628-1.69-23.975A10 10 0 0 1 23.49 13z"/></svg>
|
||||
|
After Width: | Height: | Size: 304 B |
1
src/assets/font-icons/rating-icons/level10.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><g ><path d="M49 11c6.627 0 12 5.373 12 12v23.843a9 9 0 0 1-5.13 8.126l-16.86 8.028a7 7 0 0 1-6.02 0L16.13 54.97A9 9 0 0 1 11 46.843V23c0-6.627 5.373-12 12-12z"/><path d="M23 15h26a8 8 0 0 1 8 8v23.843a5 5 0 0 1-2.85 4.514l-16.86 8.029a3 3 0 0 1-2.58 0l-16.86-8.029A5 5 0 0 1 15 46.843V23a8 8 0 0 1 8-8"/></g></svg>
|
||||
|
After Width: | Height: | Size: 398 B |
1
src/assets/font-icons/rating-icons/level2.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M44 11c9.389 0 17 7.611 17 17v16c0 9.389-7.611 17-17 17H28c-9.389 0-17-7.611-17-17V28c0-9.389 7.611-17 17-17z"/></svg>
|
||||
|
After Width: | Height: | Size: 210 B |
1
src/assets/font-icons/rating-icons/level20.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M60.804 13.571a6 6 0 0 1 .196 1.52V50c0 6.075-4.925 11-11 11H22c-6.075 0-11-4.925-11-11V15.091a6 6 0 0 1 7.52-5.804L36 13.864l17.48-4.577a6 6 0 0 1 7.324 4.284"/></svg>
|
||||
|
After Width: | Height: | Size: 261 B |
1
src/assets/font-icons/rating-icons/level3.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M41.006 61.833Q49.502 56.412 55.5 46q5.314-9.225 7.343-19.241l.157-.803a9.45 9.45 0 0 0-6.862-10.882Q46.425 12.5 35.854 12.5q-10.236 0-19.531 2.413l-.883.236a9.45 9.45 0 0 0-6.868 10.389Q10.075 36.583 15.5 46q5.902 10.245 15.616 15.997a9.44 9.44 0 0 0 9.615.006z"/></svg>
|
||||
|
After Width: | Height: | Size: 364 B |
1
src/assets/font-icons/rating-icons/level30.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M60.804 13.571a6 6 0 0 1 .196 1.52v32.183a9 9 0 0 1-5.29 8.2l-16.825 7.611a7 7 0 0 1-5.77 0l-16.825-7.61a9 9 0 0 1-5.29-8.2V15.09a6 6 0 0 1 7.52-5.804L36 13.865l17.48-4.578a6 6 0 0 1 7.324 4.284"/></svg>
|
||||
|
After Width: | Height: | Size: 296 B |
1
src/assets/font-icons/rating-icons/level4.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M47.199 13.639 58.361 24.8c6.185 6.185 6.185 16.213 0 22.398L47.2 58.361c-6.185 6.185-16.213 6.185-22.398 0L13.639 47.2c-6.185-6.185-6.185-16.213 0-22.398L24.8 13.639c6.185-6.185 16.213-6.185 22.398 0"/></svg>
|
||||
|
After Width: | Height: | Size: 302 B |
1
src/assets/font-icons/rating-icons/level40.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m39.8 7.94 8.307 6.798 9.369-1.672a6 6 0 0 1 6.918 4.631l.043.22a6 6 0 0 1 .002 2.103l-5.33 30.074A12 12 0 0 1 47.293 60H24.707a12 12 0 0 1-11.816-9.906L7.561 20.02a6 6 0 0 1 6.963-6.954l9.368 1.672 8.309-6.798a6 6 0 0 1 7.598 0"/></svg>
|
||||
|
After Width: | Height: | Size: 330 B |
1
src/assets/font-icons/rating-icons/level5.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M46.391 10.5a9.03 9.03 0 0 1 8.45 5.752L58.747 26.3a9 9 0 0 1 .16.445l3.64 11.046A9 9 0 0 1 59.5 47.73l-9.307 7.199-8.345 6.615a9.046 9.046 0 0 1-10.971.202l-8.579-6.301-9.548-6.867a9 9 0 0 1-3.392-9.827l3.29-11.291.06-.19 3.568-10.3a9.03 9.03 0 0 1 8.228-6.063l10.88-.368.122-.003z"/></svg>
|
||||
|
After Width: | Height: | Size: 384 B |
1
src/assets/font-icons/rating-icons/level50.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m39.8 7.94 8.307 6.798 9.344-1.668a6 6 0 0 1 6.918 4.632l.043.22a6 6 0 0 1-.007 2.15L59.19 48.154a12 12 0 0 1-6.788 8.713L39.34 62.867a8 8 0 0 1-6.68 0l-13.062-6a12 12 0 0 1-6.788-8.714l-5.215-28.08a6 6 0 0 1 6.954-7.003l9.343 1.668 8.309-6.798a6 6 0 0 1 7.598 0"/></svg>
|
||||
|
After Width: | Height: | Size: 364 B |
1
src/assets/font-icons/rating-icons/level6.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m40.7 10.268 15.122 8.792a9.42 9.42 0 0 1 4.678 8.148v17.584a9.42 9.42 0 0 1-4.678 8.148l-15.123 8.792a9.35 9.35 0 0 1-9.398 0L16.178 52.94a9.42 9.42 0 0 1-4.678-8.148V27.208a9.42 9.42 0 0 1 4.678-8.148l15.123-8.792a9.35 9.35 0 0 1 9.398 0"/></svg>
|
||||
|
After Width: | Height: | Size: 341 B |
1
src/assets/font-icons/rating-icons/level60.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m42.967 9.658 5.377 5.327c.145.143.326.247.526.3l7.335 1.96c4.884 1.306 7.792 6.307 6.482 11.182l-1.958 7.288c-.05.187-.05.383 0 .57l1.958 7.288c1.31 4.875-1.598 9.876-6.482 11.181l-7.335 1.96c-.2.054-.381.158-.526.301l-5.377 5.327c-3.577 3.544-9.357 3.544-12.934 0l-5.377-5.327a1.2 1.2 0 0 0-.526-.3l-7.335-1.96c-4.884-1.306-7.792-6.307-6.482-11.182l1.958-7.288c.05-.187.05-.383 0-.57l-1.958-7.288c-1.31-4.875 1.598-9.876 6.482-11.181l7.335-1.96c.2-.054.381-.158.526-.301l5.377-5.327c3.577-3.544 9.357-3.544 12.934 0"/></svg>
|
||||
|
After Width: | Height: | Size: 619 B |
1
src/assets/font-icons/rating-icons/level7.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m28.489 10 15.23.078a8.86 8.86 0 0 1 6.955 3.445l9.437 12.182a9.06 9.06 0 0 1 1.66 7.562L58.308 48.38a8.96 8.96 0 0 1-4.817 6.073l-13.755 6.664a8.8 8.8 0 0 1-7.766-.04l-13.69-6.803a8.97 8.97 0 0 1-4.755-6.12L10.21 33.007a9.05 9.05 0 0 1 1.734-7.546L21.5 13.375A8.86 8.86 0 0 1 28.49 10"/></svg>
|
||||
|
After Width: | Height: | Size: 387 B |
1
src/assets/font-icons/rating-icons/level70.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m53.1 13.65 1.873 5.163c.106.292.3.534.547.69l4.601 2.89c4.31 2.708 5.66 8.401 3.055 12.785l-2.798 4.709c-.16.27-.229.591-.191.91l.648 5.464c.603 5.086-2.952 9.74-8.01 10.41l-5.361.709c-.273.036-.53.166-.732.375l-3.793 3.922a9.215 9.215 0 0 1-13.113.165l-3.888-3.825a1.24 1.24 0 0 0-.739-.355l-5.377-.573c-5.074-.54-8.744-5.106-8.266-10.206l.514-5.479a1.45 1.45 0 0 0-.212-.904l-2.913-4.636c-2.712-4.317-1.501-10.042 2.738-12.857L16.212 20c.244-.162.433-.41.532-.705l1.746-5.208C20.124 9.21 25.357 6.563 30.215 8.2l5.134 1.729c.258.087.536.083.793-.01l5.09-1.86c4.816-1.758 10.115.757 11.868 5.592"/></svg>
|
||||
|
After Width: | Height: | Size: 699 B |
1
src/assets/font-icons/rating-icons/level8.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M44.015 11a10 10 0 0 1 7.07 2.929l6.986 6.985A10 10 0 0 1 61 27.985v16.03a10 10 0 0 1-2.929 7.07l-6.985 6.986A10 10 0 0 1 44.015 61h-16.03a10 10 0 0 1-7.07-2.929l-6.986-6.985A10 10 0 0 1 11 44.015v-16.03a10 10 0 0 1 2.929-7.07l6.985-6.986A10 10 0 0 1 27.985 11z"/></svg>
|
||||
|
After Width: | Height: | Size: 363 B |
1
src/assets/font-icons/rating-icons/level80.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m42.992 9.395 3.545 3.277c.155.144.35.235.56.262l4.79.609a9.014 9.014 0 0 1 7.87 8.588l.189 4.824c.008.211.082.415.211.582l2.956 3.817a9.014 9.014 0 0 1-.508 11.638l-3.277 3.545a1 1 0 0 0-.262.56l-.609 4.79a9.014 9.014 0 0 1-8.588 7.87l-4.824.189a1 1 0 0 0-.582.211l-3.817 2.956a9.014 9.014 0 0 1-11.638-.508l-3.545-3.277a1 1 0 0 0-.56-.262l-4.79-.609a9.014 9.014 0 0 1-7.87-8.588l-.189-4.824a1 1 0 0 0-.211-.582l-2.956-3.817a9.014 9.014 0 0 1 .508-11.638l3.277-3.545c.144-.155.235-.35.262-.56l.609-4.79a9.014 9.014 0 0 1 8.588-7.87l4.824-.189c.211-.008.415-.082.582-.211l3.817-2.956a9.014 9.014 0 0 1 11.638.508"/></svg>
|
||||
|
After Width: | Height: | Size: 714 B |
1
src/assets/font-icons/rating-icons/level9.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="M36 11c13.807 0 25 11.193 25 25S49.807 61 36 61 11 49.807 11 36s11.193-25 25-25"/></svg>
|
||||
|
After Width: | Height: | Size: 181 B |
1
src/assets/font-icons/rating-icons/level90.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 72 72"><path d="m39.395 9.26 17.933 8.438a9.025 9.025 0 0 1 5.105 9.264l-2.336 19.111a9.03 9.03 0 0 1-3.503 6.098L41.421 63.676a8.97 8.97 0 0 1-10.842 0L15.406 52.171a9.03 9.03 0 0 1-3.503-6.098L9.567 26.962a9.025 9.025 0 0 1 5.105-9.264L32.605 9.26a7.97 7.97 0 0 1 6.79 0"/></svg>
|
||||
|
After Width: | Height: | Size: 358 B |
1
src/assets/font-icons/rating-icons/negative.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M31 25.4 18.5 3.7c-1.1-1.9-3.8-1.9-4.9 0L1 25.4c-1.1 1.9.3 4.3 2.5 4.3h25.1c2.2 0 3.5-2.4 2.4-4.3"/></svg>
|
||||
|
After Width: | Height: | Size: 196 B |
1
src/assets/font-icons/refund.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M19.7 1.4C13.8 0 8 2.1 4.4 6.4V3.9c0-.6-.5-1.2-1.2-1.2-.6 0-1.2.5-1.2 1.2v5.4c0 .6.5 1.2 1.2 1.2h5.4c.6 0 1.2-.5 1.2-1.2 0-.6-.5-1.2-1.2-1.2H6.1c3.1-4 8.4-5.8 13.6-4.3 4.7 1.4 8.3 5.5 8.9 10.4 1 7.8-5 14.4-12.6 14.4-6.6 0-12.1-5.1-12.6-11.6 0-.6-.6-1.1-1.2-1.1-.7.1-1.2.7-1.2 1.4C1.7 25 8.1 31 16 31c8.9 0 16.1-7.8 14.9-17-.8-6-5.3-11.1-11.2-12.6"/><path d="M21.2 19c0-2.1-1-3.6-4.5-4.6-2.3-.7-3-1.1-3-2.2 0-.8.4-1.7 2-1.7 1.4 0 2 .6 2.2 1.2.1.3.4.5.7.5h1.1c.6 0 1-.6.8-1.1-.4-1-1.3-2-3-2.4-.3-.1-.5-.4-.5-.8s-.4-.8-.8-.8h-.6c-.4 0-.8.4-.8.8s-.2.7-.6.8c-1.8.5-3.1 1.7-3.1 3.7 0 2.1 1.2 3.4 4.6 4.4 2.2.6 2.8 1.3 2.8 2.5s-.8 2-2.5 2c-1.3 0-2.2-.5-2.6-1.6-.1-.3-.4-.5-.7-.5h-1c-.6 0-.9.6-.8 1.1q.75 2.1 3.3 2.7c.4.1.6.4.6.8v.3c0 .4.4.8.8.8h.6c.4 0 .8-.4.8-.8v-.3c0-.4.2-.7.6-.8 2.3-.5 3.6-1.9 3.6-4"/></svg>
|
||||
|
After Width: | Height: | Size: 895 B |
1
src/assets/font-icons/understood.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M8 18c2.4.4 2.4 3.4 4.3 4.5 1.8 1.1 4.7 1 5.8-1.1 1.5-2.9-.7-5.8-3.7-6.1-2.1-.2-3.9 1.4-6 1.4-.8 0-1.5-.6-1.6-1.4-.1-.7.1-1.2.6-1.7C9 12 11 11.1 12.5 10.9c1.9-.2 5 .1 7.5 1.1 1.1.5 2.9 1.7 3.6 2.6 1-.3-2.8-2.9-2.8-2.9s-.9-1.7-1.5-3c-.9-1.7-1.8-3-2.6-4.7-.4-.8-.4-2.1.4-2.7.3-.2.7-.3 1.1-.3 1.8.2 2.7 1.7 3.6 3C23 5.8 24 7.7 24.9 9.6c1 2.2 2.4 4.4 2.5 6.9.2 2.6-1.1 5.5-1.9 8-.5 1.6-1.3 3.4-2.7 4.5s-3.4 1.5-5.1 1.6c-2.3.1-5 .1-7.1-.8S7.6 27 6.5 25c-.9-1.6-2.1-3.9-1.4-5.7.6-1 1.7-1.5 2.9-1.3"/><path d="m13.3 3.7-.6.3c-.7.6-.3 1.7 0 2.4.3 1 .6 1.6 1.2 2.4.1.1.5.9.6.9 2.4.2 4.7 1.1 4.7 1.1s-1-1.9-1.3-2.3c-.3-.6-.6-1.1-1-1.6-.5-.8-.9-1.6-1.5-2.3-.5-.7-1.1-1.1-2-.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 764 B |
1
src/assets/font-icons/user-stars.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M19.5 26.4c-.5 0-.9-.4-1-.8-.4-2.1-2.2-3.6-4.4-3.6H7.6c-2.2 0-4 1.6-4.4 3.6-.1.5-.5.8-1 .8-.6 0-1.1-.6-1-1.2.5-3 3.2-5.3 6.4-5.3h6.6c3.2 0 5.8 2.3 6.4 5.3 0 .7-.5 1.2-1.1 1.2m10.3 0c-.5 0-.9-.4-1-.8-.4-2.1-2.2-3.6-4.4-3.6h-2.8l-1.5-2h4.3c3.2 0 5.8 2.3 6.4 5.3.1.6-.4 1.1-1 1.1M15.9 9.3h-2.3c-.2 0-.4-.1-.4-.3l-.7-2.2c-.2-.7-.8-1.1-1.6-1.1s-1.4.3-1.6 1l-.7 2.2c-.1.2-.2.4-.4.4H5.8c-.7 0-1.3.5-1.6 1.1s0 1.4.6 1.8l1.9 1.4c.2.1.2.3.2.5l-.7 2.2c-.2.7 0 1.4.6 1.8s1.3.4 1.9 0l1.9-1.4c.2-.1.4-.1.5 0l1.9 1.4c.3.2.6.3 1 .3s.7-.1 1-.3c.6-.4.8-1.1.6-1.8l-.7-2.2c-.1-.2 0-.4.2-.5l1.9-1.4c.6-.4.8-1.1.6-1.8s-1-1.1-1.7-1.1m-2.3 3.1c-.6.4-.8 1.2-.6 1.8l.7 2.2-1.9-1.4c-.3-.2-.6-.3-1-.3s-.7.1-1 .3L8 16.3l.6-1.7c.1-.2.2-.4.3-.5l1.9-1.6-2.3.1-.4-.2-1.8-1.3h2.3c.7 0 1.3-.5 1.6-1.1l.7-2.2.7 2.2c.2.7.8 1.1 1.6 1.1h2.3zm16-.6c-.2-.6-.8-1-1.4-1h-1.4c-.3 0-.6-.2-.6-.5L25.7 9c-.2-.6-.8-1-1.4-1s-1.2.4-1.4 1l-.4 1.3c-.1.3-.4.5-.6.5h-1.4c-.6 0-1.2.4-1.4 1s0 1.3.5 1.6l1.1.8c.2.2.3.5.2.8l-.4 1.3c-.2.6 0 1.3.5 1.6q.45.3.9.3c.45 0 .6-.1.9-.3L24 17c.2-.2.5-.2.7 0l1.2.9c.5.4 1.2.4 1.7 0s.7-1 .5-1.6l-.4-1.3c-.1-.3 0-.6.2-.8l1.1-.8c.5-.3.7-1 .6-1.6"/><path d="M25.8 13.6c-.3.2-.4.6-.3.9L26 16l-1.3-.9c-.3-.2-.7-.2-1 0l-1.2.9.4-1.4c0-.1.1-.2.2-.3l1.2-1-1.4.1c-.1 0-.3 0-.4-.1l-1.1-.8H23c.4 0 .7-.2.8-.6l.5-1.5.5 1.5c.1.3.4.6.8.6h1.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/font-icons/warning.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M31 25.4 18.5 3.7c-1.1-1.9-3.8-1.9-4.9 0L1 25.4c-1.1 1.9.3 4.3 2.5 4.3h25.1c2.2 0 3.5-2.4 2.4-4.3m-15 .2c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2m2-7c0 1.2-1.1 2.2-2.3 2-1-.1-1.7-1.1-1.7-2.1v-3.7c0-1 .7-2 1.7-2.1 1.2-.2 2.3.8 2.3 2z"/></svg>
|
||||
|
After Width: | Height: | Size: 333 B |
@ -2200,3 +2200,22 @@
|
||||
"PublicPostsSubscribeToPremium" = "Subscribe to Premium";
|
||||
"NotificationPaidExtraSearch" = "{stars} spent on extra search.";
|
||||
"PostsSearchTransaction" = "Posts Search";
|
||||
"TitleRating" = "Rating";
|
||||
"RatingReflectsActivity" = "This rating reflects {name}'s activity on Telegram. It is based on:";
|
||||
"RatingYourReflectsActivity" = "This rating reflects your activity on Telegram. It is based on:";
|
||||
"RatingGiftsFromTelegram" = "Gifts from Telegram";
|
||||
"RatingGiftsFromTelegramDesc" = "100% of the Stars spent on gifts purchased from Telegram.";
|
||||
"RatingGiftsAndPostsFromUsers" = "Gifts and Posts from Users";
|
||||
"RatingGiftsAndPostsFromUsersDesc" = "20% of the Stars spent on gifts or posts from users and channels.";
|
||||
"RatingRefundsAndConversions" = "Refunds and Conversions";
|
||||
"RatingRefundsAndConversionsDesc" = "10x of refunded Stars and 85% of bought gifts converted to Stars.";
|
||||
"RatingBadgeAdded" = "Added";
|
||||
"RatingBadgeDeducted" = "Deducted";
|
||||
"RatingLevel" = "Level {level}";
|
||||
"RatingNegativeLevel" = "Negative Rating";
|
||||
"DescriptionPendingRating_one" = "Rating updates in {time} from purchases. {points} point is pending. {link}";
|
||||
"DescriptionPendingRating_other" = "Rating updates in {time} from purchases. {points} points are pending. {link}";
|
||||
"DescriptionFutureRating_one" = "This will be your rating in {time}, after {points} point is added. {link}";
|
||||
"DescriptionFutureRating_other" = "This will be your rating in {time}, after {points} points are added. {link}";
|
||||
"LinkDescriptionRatingBack" = "Back >";
|
||||
"LinkDescriptionRatingPreview" = "Preview >";
|
||||
@ -103,3 +103,4 @@ export { default as InviteViaLinkModal } from '../components/modals/inviteViaLin
|
||||
export { default as OneTimeMediaModal } from '../components/modals/oneTimeMedia/OneTimeMediaModal';
|
||||
export { default as WebAppsCloseConfirmationModal } from '../components/main/WebAppsCloseConfirmationModal';
|
||||
export { default as FrozenAccountModal } from '../components/modals/frozenAccount/FrozenAccountModal';
|
||||
export { default as ProfileRatingModal } from '../components/modals/profileRating/ProfileRatingModal';
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
/* stylelint-disable plugin/no-low-performance-animation-properties */
|
||||
|
||||
.root {
|
||||
--percent: calc(var(--progress, 0.5) * 100%);
|
||||
--color-negative-progress: #E05356;
|
||||
|
||||
position: relative;
|
||||
|
||||
@ -73,21 +76,44 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 2rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
color: #ffffff;
|
||||
|
||||
background-color: #7E85FF;
|
||||
|
||||
transition: width 0.25s ease-in-out;
|
||||
|
||||
&.noTransition {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.floatingBadgeContent {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: fit-content;
|
||||
max-width: 20rem;
|
||||
margin-inline: auto;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.floating-badge-triangle {
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
left: calc(var(--tail-position, 0.5) * 100%);
|
||||
transform: translateX(-50%);
|
||||
|
||||
display: inline-block;
|
||||
|
||||
color: #7E85FF;
|
||||
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.floating-badge-icon {
|
||||
@ -109,6 +135,10 @@
|
||||
align-items: center;
|
||||
|
||||
font-weight: var(--font-weight-medium);
|
||||
|
||||
opacity: 1;
|
||||
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.left {
|
||||
@ -119,8 +149,21 @@
|
||||
right: 0.75rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
--multiplier: calc(1 / var(--progress) - 1);
|
||||
.progressWrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: 0.625rem;
|
||||
}
|
||||
|
||||
.positiveLayer,
|
||||
.positiveProgress {
|
||||
--multiplier: calc(1 / var(--positive-progress) - 1);
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -129,16 +172,18 @@
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: max(var(--percent), 0.625rem);
|
||||
border-top-left-radius: 0.625rem;
|
||||
border-bottom-left-radius: 0.625rem;
|
||||
width: calc(var(--positive-progress) * 100%);
|
||||
|
||||
background-image: var(--premium-gradient);
|
||||
background-size: calc(1 / var(--progress) * 100%) 100%;
|
||||
background-size: calc(1 / var(--positive-progress) * 100%) 100%;
|
||||
|
||||
transition: opacity 0.15s ease, width 0.2s, background-size 0.3s ease;
|
||||
|
||||
.left, .right {
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.right {
|
||||
@ -146,12 +191,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fullProgress {
|
||||
border-radius: 0.625rem;
|
||||
.negativeLayer,
|
||||
.negativeProgress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: auto;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: calc(var(--negative-progress) * 100%);
|
||||
|
||||
background-color: var(--color-negative-progress);
|
||||
background-image: none;
|
||||
|
||||
transition: opacity 0.15s, width 0.2s;
|
||||
|
||||
.left, .right {
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.right {
|
||||
right: 0.75rem;
|
||||
}
|
||||
|
||||
.left {
|
||||
right: calc(100% - 0.75rem);
|
||||
left: auto;
|
||||
transition: right 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.primary {
|
||||
.progress {
|
||||
.positiveLayer,
|
||||
.positiveProgress {
|
||||
background-color: var(--color-primary);
|
||||
background-image: none;
|
||||
}
|
||||
@ -159,4 +236,69 @@
|
||||
.floating-badge {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.floating-badge-triangle {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.negative {
|
||||
.floating-badge {
|
||||
background-color: var(--color-negative-progress);
|
||||
}
|
||||
|
||||
.floating-badge-triangle {
|
||||
color: var(--color-negative-progress);
|
||||
}
|
||||
}
|
||||
|
||||
.transitioning {
|
||||
.left,
|
||||
.right {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.noTransition {
|
||||
&.positiveProgress,
|
||||
&.negativeProgress {
|
||||
transition: opacity 0.15s !important;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
.cycling {
|
||||
.badgeContainer {
|
||||
transition-duration: var(--cycling-animation-badge-position);
|
||||
}
|
||||
|
||||
.positiveProgress,
|
||||
.negativeProgress {
|
||||
transition: opacity 0.15s, width var(--cycling-animation-progress), background-size 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.positiveLayer {
|
||||
--positive-progress: var(--layer-progress);
|
||||
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.negativeLayer {
|
||||
--negative-progress: var(--layer-progress);
|
||||
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,14 +7,22 @@ import type { IconName } from '../../types/icons';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
import { REM } from './helpers/mediaDimensions';
|
||||
|
||||
import { useTransitionActiveKey } from '../../hooks/animations/useTransitionActiveKey';
|
||||
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import usePrevious from '../../hooks/usePrevious';
|
||||
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import Icon from './icons/Icon';
|
||||
|
||||
import styles from './PremiumProgress.module.scss';
|
||||
|
||||
export type AnimationDirection = 'forward' | 'backward' | 'none';
|
||||
|
||||
type OwnProps = {
|
||||
leftText?: string;
|
||||
rightText?: string;
|
||||
@ -22,6 +30,8 @@ type OwnProps = {
|
||||
floatingBadgeText?: string;
|
||||
progress?: number;
|
||||
isPrimary?: boolean;
|
||||
isNegative?: boolean;
|
||||
animationDirection?: AnimationDirection;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@ -30,47 +40,216 @@ const PremiumProgress: FC<OwnProps> = ({
|
||||
rightText,
|
||||
floatingBadgeText,
|
||||
floatingBadgeIcon,
|
||||
progress,
|
||||
progress = 0,
|
||||
isPrimary,
|
||||
isNegative,
|
||||
animationDirection = 'none',
|
||||
className,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
const floatingBadgeRef = useRef<HTMLDivElement>();
|
||||
const floatingBadgeContentRef = useRef<HTMLDivElement>();
|
||||
const parentContainerRef = useRef<HTMLDivElement>();
|
||||
|
||||
const [shiftX, setShiftX] = useState(0);
|
||||
const [tailPosition, setTailPosition] = useState(0);
|
||||
const [beakPosition, setBeakPosition] = useState(0);
|
||||
const [badgeWidth, setBadgeWidth] = useState(0);
|
||||
const prevBadgeWidth = usePrevious(badgeWidth);
|
||||
const [positiveProgress, setPositiveProgress] = useState(isNegative ? 0 : progress);
|
||||
const [negativeProgress, setNegativeProgress] = useState(isNegative ? progress : 0);
|
||||
const [badgeProgress, setBadgeProgress] = useState(progress);
|
||||
|
||||
const [layerProgress, setLayerProgress] = useState(0);
|
||||
const [showLayer, setShowLayer] = useState(false);
|
||||
const [disableMainProgressTransition, setDisableMainProgressTransition] = useState(false);
|
||||
const [disableLayerProgressTransition, setDisableLayerProgressTransition] = useState(false);
|
||||
const [hideMainLayer, setHideMainLayer] = useState(false);
|
||||
const [isCycling, setIsCycling] = useState(false);
|
||||
|
||||
const badgeActiveKey = useTransitionActiveKey([floatingBadgeText, floatingBadgeIcon]);
|
||||
|
||||
const shouldAnimateCaptionsRef = useRef(false);
|
||||
const prevLeftText = usePrevious(leftText);
|
||||
const prevRightText = usePrevious(rightText);
|
||||
const prevIsNegative = usePrevious(isNegative);
|
||||
|
||||
const BEAK_WIDTH_PX = 28;
|
||||
const PROGRESS_BORDER_RADIUS_PX = REM;
|
||||
const CORNER_BEAK_THRESHOLD = BEAK_WIDTH_PX / 2 + PROGRESS_BORDER_RADIUS_PX;
|
||||
const BADGE_HORIZONTAL_PADDING_PX = 0.75 * 2 * REM;
|
||||
|
||||
const LAYER_PROGRESS_TRANSITION_MS = 400;
|
||||
const FULL_CYCLE_TRANSITION_MS = LAYER_PROGRESS_TRANSITION_MS * 2;
|
||||
const APPLY_TRANSITION_DELAY_MS = 50;
|
||||
|
||||
const updateBadgePosition = () => {
|
||||
if (floatingBadgeRef.current && parentContainerRef.current && progress !== undefined) {
|
||||
const badgeWidth = floatingBadgeRef.current.offsetWidth;
|
||||
if (floatingBadgeContentRef.current && parentContainerRef.current) {
|
||||
const parentWidth = parentContainerRef.current.offsetWidth;
|
||||
const minShift = badgeWidth / 2;
|
||||
const maxShift = parentWidth - badgeWidth / 2;
|
||||
const currentShift = progress * parentWidth;
|
||||
const safeShift = Math.max(minShift, Math.min(currentShift, maxShift));
|
||||
const halfBadgeWidth = badgeWidth / 2;
|
||||
const minBadgeShift = halfBadgeWidth;
|
||||
const maxBadgeShift = parentWidth - halfBadgeWidth;
|
||||
const halfBeakWidth = BEAK_WIDTH_PX / 2;
|
||||
const currentShift = isNegative ? (1 - badgeProgress) * parentWidth : badgeProgress * parentWidth;
|
||||
|
||||
let safeShift = Math.max(minBadgeShift, Math.min(currentShift, maxBadgeShift));
|
||||
if (currentShift < CORNER_BEAK_THRESHOLD) {
|
||||
safeShift = currentShift + halfBadgeWidth;
|
||||
}
|
||||
if (currentShift > parentWidth - CORNER_BEAK_THRESHOLD) {
|
||||
safeShift = currentShift - halfBadgeWidth;
|
||||
}
|
||||
|
||||
const beakOffsetFromCenter = currentShift - safeShift;
|
||||
const newBeakPositionPx = halfBadgeWidth + beakOffsetFromCenter - halfBeakWidth;
|
||||
|
||||
setShiftX(safeShift / parentWidth);
|
||||
|
||||
let newTailPosition;
|
||||
if (currentShift < minShift) {
|
||||
newTailPosition = (progress * parentWidth) / (minShift * 2);
|
||||
} else if (currentShift > maxShift) {
|
||||
const progressMapped = (progress - (maxShift / parentWidth)) / (1 - maxShift / parentWidth);
|
||||
newTailPosition = 0.5 + (progressMapped * 0.4);
|
||||
} else {
|
||||
newTailPosition = 0.5;
|
||||
}
|
||||
setTailPosition(newTailPosition);
|
||||
setBeakPosition(newBeakPositionPx);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(updateBadgePosition, [progress]);
|
||||
useEffect(updateBadgePosition, [badgeProgress, badgeWidth, isNegative, CORNER_BEAK_THRESHOLD]);
|
||||
|
||||
useResizeObserver(parentContainerRef, updateBadgePosition);
|
||||
|
||||
useEffect(() => {
|
||||
const width = floatingBadgeContentRef?.current?.clientWidth || 0;
|
||||
setBadgeWidth(width + BADGE_HORIZONTAL_PADDING_PX);
|
||||
}, [floatingBadgeText, floatingBadgeIcon, BADGE_HORIZONTAL_PADDING_PX]);
|
||||
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
useSyncEffect(() => {
|
||||
let timeoutId: number | undefined;
|
||||
|
||||
const isNegativeTransition = prevIsNegative !== undefined && prevIsNegative !== isNegative;
|
||||
const shouldAnimateCaptions = (prevLeftText || prevRightText) && (isNegativeTransition || isCycling);
|
||||
|
||||
if (shouldAnimateCaptions && !shouldAnimateCaptionsRef.current) {
|
||||
shouldAnimateCaptionsRef.current = true;
|
||||
|
||||
const timeoutMs = isCycling ? LAYER_PROGRESS_TRANSITION_MS * 2 : LAYER_PROGRESS_TRANSITION_MS;
|
||||
timeoutId = window.setTimeout(() => {
|
||||
shouldAnimateCaptionsRef.current = false;
|
||||
forceUpdate();
|
||||
}, timeoutMs);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
shouldAnimateCaptionsRef.current = false;
|
||||
}
|
||||
};
|
||||
}, [
|
||||
leftText, prevLeftText, rightText, prevRightText,
|
||||
prevIsNegative, isNegative, animationDirection, isCycling,
|
||||
]);
|
||||
|
||||
const shouldAnimateCaptions = shouldAnimateCaptionsRef.current;
|
||||
|
||||
useEffect(() => {
|
||||
if (isNegative) {
|
||||
setPositiveProgress(0);
|
||||
setNegativeProgress(progress);
|
||||
} else {
|
||||
setNegativeProgress(0);
|
||||
setPositiveProgress(progress);
|
||||
}
|
||||
setBadgeProgress(progress);
|
||||
}, [progress, isNegative]);
|
||||
|
||||
const hasFloatingBadge = Boolean(floatingBadgeIcon || floatingBadgeText);
|
||||
const isProgressFull = Boolean(progress) && progress > 0.99;
|
||||
|
||||
const displayLeftText = shouldAnimateCaptions ? prevLeftText : leftText;
|
||||
const displayRightText = shouldAnimateCaptions ? prevRightText : rightText;
|
||||
|
||||
const prevProgress = usePrevious(progress);
|
||||
|
||||
useEffect(() => {
|
||||
const timers: number[] = [];
|
||||
|
||||
if (animationDirection === 'none' || prevProgress === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetProgress = progress;
|
||||
|
||||
const setMainProgress = (value: number) => {
|
||||
if (isNegative) {
|
||||
setNegativeProgress(value);
|
||||
} else {
|
||||
setPositiveProgress(value);
|
||||
}
|
||||
};
|
||||
|
||||
if (animationDirection === 'forward' || animationDirection === 'backward') {
|
||||
const isForward = animationDirection === 'forward';
|
||||
|
||||
setIsCycling(true);
|
||||
setMainProgress(isForward ? 1 : 0);
|
||||
|
||||
setDisableLayerProgressTransition(true);
|
||||
setLayerProgress(isForward ? 0 : 1);
|
||||
|
||||
timers.push(window.setTimeout(() => {
|
||||
setDisableLayerProgressTransition(false);
|
||||
setShowLayer(true);
|
||||
setLayerProgress(targetProgress);
|
||||
if (isForward) {
|
||||
setDisableMainProgressTransition(true);
|
||||
setMainProgress(0);
|
||||
}
|
||||
}, LAYER_PROGRESS_TRANSITION_MS));
|
||||
|
||||
timers.push(window.setTimeout(() => {
|
||||
setDisableMainProgressTransition(true);
|
||||
setDisableLayerProgressTransition(true);
|
||||
setHideMainLayer(false);
|
||||
setMainProgress(targetProgress);
|
||||
setShowLayer(false);
|
||||
|
||||
timers.push(window.setTimeout(() => {
|
||||
setDisableMainProgressTransition(false);
|
||||
setDisableLayerProgressTransition(false);
|
||||
setIsCycling(false);
|
||||
}, APPLY_TRANSITION_DELAY_MS));
|
||||
}, FULL_CYCLE_TRANSITION_MS));
|
||||
}
|
||||
|
||||
return () => {
|
||||
timers.forEach(clearTimeout);
|
||||
};
|
||||
}, [
|
||||
progress, animationDirection, isNegative,
|
||||
prevProgress, FULL_CYCLE_TRANSITION_MS,
|
||||
]);
|
||||
|
||||
const renderProgressLayer = (
|
||||
isPositive: boolean,
|
||||
layerProgress: number,
|
||||
layerClassName?: string,
|
||||
disableTransition?: boolean,
|
||||
) => {
|
||||
const className = isPositive ? styles.positiveProgress : styles.negativeProgress;
|
||||
const progressVar = '--layer-progress';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={buildClassName(
|
||||
className,
|
||||
layerClassName,
|
||||
disableTransition && styles.noTransition,
|
||||
)}
|
||||
style={`${progressVar}: ${layerProgress}`}
|
||||
>
|
||||
<div className={styles.left}>
|
||||
<span>{displayLeftText}</span>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<span>{displayRightText}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -79,46 +258,88 @@ const PremiumProgress: FC<OwnProps> = ({
|
||||
styles.root,
|
||||
hasFloatingBadge && styles.withBadge,
|
||||
isPrimary && styles.primary,
|
||||
isNegative && styles.negative,
|
||||
shouldAnimateCaptions && styles.transitioning,
|
||||
isCycling && styles.cycling,
|
||||
className,
|
||||
)}
|
||||
style={buildStyle(
|
||||
progress !== undefined && `--progress: ${progress}`,
|
||||
tailPosition !== undefined && `--tail-position: ${tailPosition}`,
|
||||
`--positive-progress: ${positiveProgress}`,
|
||||
`--negative-progress: ${negativeProgress}`,
|
||||
`--layer-progress: ${layerProgress}`,
|
||||
`--shift-x: ${shiftX}`,
|
||||
`--cycling-animation-badge-position: ${FULL_CYCLE_TRANSITION_MS}ms`,
|
||||
`--cycling-animation-progress: ${LAYER_PROGRESS_TRANSITION_MS}ms`,
|
||||
)}
|
||||
>
|
||||
{hasFloatingBadge && (
|
||||
<div className={styles.badgeContainer}>
|
||||
<div className={styles.floatingBadgeWrapper}>
|
||||
<div className={styles.floatingBadge} ref={floatingBadgeRef}>
|
||||
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
|
||||
{floatingBadgeText && (
|
||||
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{floatingBadgeText}
|
||||
<div
|
||||
className={
|
||||
buildClassName(styles.floatingBadge,
|
||||
(!prevBadgeWidth || prevBadgeWidth === 0)
|
||||
&& styles.noTransition)
|
||||
}
|
||||
style={`width: ${badgeWidth}px;`}
|
||||
>
|
||||
<Transition
|
||||
activeKey={badgeActiveKey}
|
||||
name="fade"
|
||||
shouldCleanup
|
||||
>
|
||||
<div
|
||||
ref={floatingBadgeContentRef}
|
||||
className={styles.floatingBadgeContent}
|
||||
>
|
||||
{floatingBadgeIcon && <Icon name={floatingBadgeIcon} className={styles.floatingBadgeIcon} />}
|
||||
{floatingBadgeText && (
|
||||
<div className={styles.floatingBadgeValue} dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{floatingBadgeText}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
<div className={styles.floatingBadgeTriangle}>
|
||||
<div className={styles.floatingBadgeTriangle} style={`left: ${beakPosition}px`}>
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||||
<path d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z" fill="#7E85FF" />
|
||||
<path d="m 28,4 v 9 c 0.0089,7.283278 -3.302215,5.319646 -6.750951,8.589815 l -5.8284,5.82843 c -0.781,0.78105 -2.0474,0.78104 -2.8284,0 L 6.7638083,21.589815 C 2.8288652,17.959047 0.04527024,20.332086 0,13 V 4 C 0,4 0.00150581,0.97697493 3,1 5.3786658,1.018266 22.594519,0.9142007 25,1 c 2.992326,0.1067311 3,3 3,3 z" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.left}>
|
||||
<span>{leftText}</span>
|
||||
<span>{displayLeftText}</span>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<span>{rightText}</span>
|
||||
<span>{displayRightText}</span>
|
||||
</div>
|
||||
<div className={buildClassName(styles.progress, isProgressFull && styles.fullProgress)}>
|
||||
<div className={styles.left}>
|
||||
<span>{leftText}</span>
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<span>{rightText}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.progressWrapper}>
|
||||
{renderProgressLayer(
|
||||
true,
|
||||
positiveProgress,
|
||||
buildClassName(hideMainLayer && styles.hidden),
|
||||
disableMainProgressTransition,
|
||||
)}
|
||||
|
||||
{renderProgressLayer(
|
||||
false,
|
||||
negativeProgress,
|
||||
buildClassName(hideMainLayer && styles.hidden),
|
||||
disableMainProgressTransition,
|
||||
)}
|
||||
|
||||
{renderProgressLayer(
|
||||
!isNegative,
|
||||
layerProgress,
|
||||
buildClassName(
|
||||
isNegative ? styles.negativeLayer : styles.positiveLayer,
|
||||
showLayer && styles.show,
|
||||
),
|
||||
disableLayerProgressTransition,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -191,11 +191,64 @@
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.user-status {
|
||||
.userRatingNegativeWrapper,
|
||||
.userRatingWrapper {
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
margin-right: 0.25rem;
|
||||
|
||||
font-size: 1rem;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.userRatingWrapper {
|
||||
width: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.ratingNegativeIcon {
|
||||
pointer-events: none;
|
||||
font-size: 1rem;
|
||||
color: var(--color-white);
|
||||
|
||||
-webkit-text-stroke: 1px #000000;
|
||||
}
|
||||
|
||||
.ratingIcon {
|
||||
pointer-events: none;
|
||||
color: var(--color-white);
|
||||
|
||||
-webkit-text-stroke: 1px #000000;
|
||||
}
|
||||
|
||||
.ratingLevel {
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
font-size: 0.625rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: 1;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.userStatus {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.get-status {
|
||||
.getStatus {
|
||||
--blured-background-color: #c4c9cc42;
|
||||
|
||||
pointer-events: all;
|
||||
|
||||
@ -3,9 +3,10 @@ import { memo, useEffect, useState } from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserStatus,
|
||||
ApiChat, ApiPeerPhotos, ApiSticker, ApiTopic, ApiUser, ApiUserFullInfo, ApiUserStatus,
|
||||
} from '../../api/types';
|
||||
import type { AnimationLevel } from '../../types';
|
||||
import type { IconName } from '../../types/icons';
|
||||
import { MediaViewerOrigin } from '../../types';
|
||||
|
||||
import {
|
||||
@ -20,6 +21,7 @@ import {
|
||||
selectThreadMessagesCount,
|
||||
selectTopic,
|
||||
selectUser,
|
||||
selectUserFullInfo,
|
||||
selectUserStatus,
|
||||
} from '../../global/selectors';
|
||||
import { selectSharedSettings } from '../../global/selectors/sharedState.ts';
|
||||
@ -40,12 +42,15 @@ import usePhotosPreload from './hooks/usePhotosPreload';
|
||||
import Transition from '../ui/Transition';
|
||||
import Avatar from './Avatar';
|
||||
import FullNameTitle from './FullNameTitle';
|
||||
import Icon from './icons/Icon';
|
||||
import ProfilePhoto from './ProfilePhoto';
|
||||
import TopicIcon from './TopicIcon';
|
||||
|
||||
import './ProfileInfo.scss';
|
||||
import styles from './ProfileInfo.module.scss';
|
||||
|
||||
const MAX_LEVEL_ICON = 90;
|
||||
|
||||
type OwnProps = {
|
||||
peerId: string;
|
||||
forceShowSelf?: boolean;
|
||||
@ -56,6 +61,7 @@ type OwnProps = {
|
||||
type StateProps =
|
||||
{
|
||||
user?: ApiUser;
|
||||
userFullInfo?: ApiUserFullInfo;
|
||||
userStatus?: ApiUserStatus;
|
||||
chat?: ApiChat;
|
||||
mediaIndex?: number;
|
||||
@ -78,6 +84,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
forceShowSelf,
|
||||
canPlayVideo,
|
||||
user,
|
||||
userFullInfo,
|
||||
userStatus,
|
||||
chat,
|
||||
mediaIndex,
|
||||
@ -98,6 +105,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
openPrivacySettingsNoticeModal,
|
||||
loadMoreProfilePhotos,
|
||||
openUniqueGiftBySlug,
|
||||
openProfileRatingModal,
|
||||
} = getActions();
|
||||
|
||||
const oldLang = useOldLang();
|
||||
@ -182,6 +190,12 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
openPrivacySettingsNoticeModal({ chatId: chat!.id, isReadDate: false });
|
||||
});
|
||||
|
||||
const handleRatingClick = useLastCallback((level: number) => {
|
||||
if (user) {
|
||||
openProfileRatingModal({ userId: user.id, level });
|
||||
}
|
||||
});
|
||||
|
||||
function handleSelectFallbackPhoto() {
|
||||
if (!isFirst) return;
|
||||
setHasSlideAnimation(true);
|
||||
@ -268,6 +282,43 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
function renderUserRating() {
|
||||
if (!userFullInfo?.starsRating) return undefined;
|
||||
|
||||
const level = userFullInfo.starsRating.level;
|
||||
const isNegative = level < 0;
|
||||
|
||||
const onRatingClick = () => handleRatingClick(level);
|
||||
|
||||
if (isNegative) {
|
||||
return (
|
||||
<span className={styles.userRatingNegativeWrapper} onClick={onRatingClick}>
|
||||
<Icon
|
||||
name="rating-icons-negative"
|
||||
className={styles.ratingNegativeIcon}
|
||||
/>
|
||||
<span className={styles.ratingLevel}>!</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const safeLevel = Math.max(level, 1);
|
||||
const iconLevel = Math.min(safeLevel, MAX_LEVEL_ICON);
|
||||
const iconName = (iconLevel < 10
|
||||
? `rating-icons-level${iconLevel}`
|
||||
: `rating-icons-level${Math.floor(iconLevel / 10) * 10}`) as IconName;
|
||||
|
||||
return (
|
||||
<span className={styles.userRatingWrapper} onClick={onRatingClick}>
|
||||
<Icon
|
||||
name={iconName}
|
||||
className={styles.ratingIcon}
|
||||
/>
|
||||
<span className={styles.ratingLevel}>{level}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function renderStatus() {
|
||||
const isAnonymousForwards = isAnonymousForwardsChat(peerId);
|
||||
const isSystemBotChat = isSystemBot(peerId);
|
||||
@ -290,6 +341,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
isUserOnline(user, userStatus) && 'online',
|
||||
)}
|
||||
>
|
||||
{renderUserRating()}
|
||||
<span className={styles.userStatus} dir="auto">
|
||||
{getUserStatus(oldLang, user, userStatus)}
|
||||
</span>
|
||||
@ -400,6 +452,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { peerId }): StateProps => {
|
||||
const user = selectUser(global, peerId);
|
||||
const userFullInfo = user ? selectUserFullInfo(global, peerId) : undefined;
|
||||
const userStatus = selectUserStatus(global, peerId);
|
||||
const chat = selectChat(global, peerId);
|
||||
const profilePhotos = selectPeerPhotos(global, peerId);
|
||||
@ -415,6 +468,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
return {
|
||||
user,
|
||||
userFullInfo,
|
||||
userStatus,
|
||||
chat,
|
||||
mediaIndex,
|
||||
|
||||
@ -35,6 +35,7 @@ import OneTimeMediaModal from './oneTimeMedia/OneTimeMediaModal.async';
|
||||
import PaidReactionModal from './paidReaction/PaidReactionModal.async';
|
||||
import PreparedMessageModal from './preparedMessage/PreparedMessageModal.async';
|
||||
import PriceConfirmModal from './priceConfirm/PriceConfirmModal.async';
|
||||
import ProfileRatingModal from './profileRating/ProfileRatingModal.async';
|
||||
import ReportAdModal from './reportAd/ReportAdModal.async';
|
||||
import ReportModal from './reportModal/ReportModal.async';
|
||||
import SharePreparedMessageModal from './sharePreparedMessage/SharePreparedMessageModal.async';
|
||||
@ -93,7 +94,8 @@ type ModalKey = keyof Pick<TabState,
|
||||
'priceConfirmModal' |
|
||||
'isFrozenAccountModalOpen' |
|
||||
'deleteAccountModal' |
|
||||
'isAgeVerificationModalOpen'
|
||||
'isAgeVerificationModalOpen' |
|
||||
'profileRatingModal'
|
||||
>;
|
||||
|
||||
type StateProps = {
|
||||
@ -151,6 +153,7 @@ const MODALS: ModalRegistry = {
|
||||
isFrozenAccountModalOpen: FrozenAccountModal,
|
||||
deleteAccountModal: DeleteAccountModal,
|
||||
isAgeVerificationModalOpen: AgeVerificationModal,
|
||||
profileRatingModal: ProfileRatingModal,
|
||||
};
|
||||
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
|
||||
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './ProfileRatingModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const ProfileRatingModalAsync: FC<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const ProfileRatingModal = useModuleLoader(Bundles.Extra, 'ProfileRatingModal', !modal);
|
||||
|
||||
return ProfileRatingModal ? <ProfileRatingModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default ProfileRatingModalAsync;
|
||||
@ -0,0 +1,91 @@
|
||||
.header {
|
||||
width: 100%;
|
||||
padding-top: 0;
|
||||
padding-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
padding-inline: 1rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.descriptionPreview {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
margin-inline: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.previewLink,
|
||||
.backLink {
|
||||
cursor: pointer;
|
||||
color: var(--color-primary);
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 4.75rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.ratingProgress {
|
||||
margin: 0 auto;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
&.withPreview {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
transform: translateY(-1px);
|
||||
|
||||
display: inline-block;
|
||||
|
||||
margin-right: 0.25rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.375rem;
|
||||
|
||||
font-size: 0.6875rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 0.875rem;
|
||||
color: var(--color-white);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.understoodIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.badgeAdded {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.badgeDeducted {
|
||||
color: var(--color-background);
|
||||
background-color: var(--color-text-secondary);
|
||||
}
|
||||
260
src/components/modals/profileRating/ProfileRatingModal.tsx
Normal file
@ -0,0 +1,260 @@
|
||||
import { memo, useEffect, useMemo, useState } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiStarsRating, ApiUser } from '../../../api/types';
|
||||
import type { TabState } from '../../../global/types';
|
||||
|
||||
import { getPeerTitle } from '../../../global/helpers/peers';
|
||||
import { selectUser, selectUserFullInfo } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatShortDuration } from '../../../util/dates/dateFormat';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import PremiumProgress, { type AnimationDirection } from '../../common/PremiumProgress';
|
||||
import Button from '../../ui/Button';
|
||||
import Transition from '../../ui/Transition';
|
||||
import TableAboutModal, { type TableAboutData } from '../common/TableAboutModal';
|
||||
|
||||
import styles from './ProfileRatingModal.module.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['profileRatingModal'];
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
user?: ApiUser;
|
||||
currentUserId?: string;
|
||||
starsRating?: ApiStarsRating;
|
||||
pendingRating?: ApiStarsRating;
|
||||
pendingRatingDate?: number;
|
||||
};
|
||||
|
||||
const ProfileRatingModal = ({
|
||||
modal,
|
||||
user,
|
||||
currentUserId,
|
||||
starsRating,
|
||||
pendingRating,
|
||||
pendingRatingDate,
|
||||
}: OwnProps & StateProps) => {
|
||||
const {
|
||||
closeProfileRatingModal,
|
||||
} = getActions();
|
||||
const lang = useLang();
|
||||
const isOpen = Boolean(modal);
|
||||
const [showFutureRating, setShowFutureRating] = useState(false);
|
||||
|
||||
const handleClose = useLastCallback(() => {
|
||||
closeProfileRatingModal();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setShowFutureRating(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const handleShowFuture = useLastCallback(() => {
|
||||
setShowFutureRating(true);
|
||||
});
|
||||
|
||||
const handleShowCurrent = useLastCallback(() => {
|
||||
setShowFutureRating(false);
|
||||
});
|
||||
|
||||
const renderBadge = (type: 'added' | 'deducted') => {
|
||||
const isAdded = type === 'added';
|
||||
const badgeText = isAdded ? lang('RatingBadgeAdded') : lang('RatingBadgeDeducted');
|
||||
const badgeClass = isAdded ? styles.badgeAdded : styles.badgeDeducted;
|
||||
|
||||
return (
|
||||
<span className={buildClassName(styles.badge, badgeClass)}>
|
||||
{badgeText}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const header = useMemo(() => {
|
||||
if (!modal || !user || !starsRating || !isOpen) return undefined;
|
||||
|
||||
const rating = showFutureRating && pendingRating ? pendingRating : starsRating;
|
||||
const currentStars = rating.stars;
|
||||
const currentLevelStars = rating.currentLevelStars;
|
||||
const nextLevelStars = rating.nextLevelStars;
|
||||
const currentLevel = rating.level;
|
||||
const nextLevel = currentLevel + 1;
|
||||
const isNegative = currentLevel < 0;
|
||||
const pendingLevel = !showFutureRating && pendingRating ? pendingRating.level : starsRating.level;
|
||||
|
||||
let levelProgress = 0;
|
||||
|
||||
if (!nextLevelStars) {
|
||||
levelProgress = 1;
|
||||
} else if (nextLevelStars > currentLevelStars) {
|
||||
levelProgress = Math.max(0.03, (currentStars - currentLevelStars) / (nextLevelStars - currentLevelStars));
|
||||
} else {
|
||||
levelProgress = 1;
|
||||
}
|
||||
|
||||
const progress = isNegative ? 0.5 : Math.max(0, Math.min(1, levelProgress));
|
||||
|
||||
const waitTime = pendingRatingDate ? pendingRatingDate - Math.floor(Date.now() / 1000) : 0;
|
||||
const pendingPoints = pendingRating ? pendingRating.stars - starsRating.stars : 0;
|
||||
const shouldShowPreview = pendingRating && pendingRatingDate;
|
||||
|
||||
const renderPreviewDescription = () => {
|
||||
if (!shouldShowPreview) return undefined;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
name="fade"
|
||||
className={styles.descriptionPreview}
|
||||
activeKey={showFutureRating ? 1 : 0}
|
||||
shouldCleanup
|
||||
shouldRestoreHeight
|
||||
>
|
||||
{showFutureRating ? (
|
||||
<p>
|
||||
{lang('DescriptionFutureRating', {
|
||||
time: formatShortDuration(lang, waitTime),
|
||||
points: Math.abs(pendingPoints),
|
||||
link: (
|
||||
<span className={styles.backLink} onClick={handleShowCurrent}>
|
||||
{lang('LinkDescriptionRatingBack')}
|
||||
</span>
|
||||
),
|
||||
}, {
|
||||
pluralValue: Math.abs(pendingPoints),
|
||||
withNodes: true,
|
||||
})}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
{lang('DescriptionPendingRating', {
|
||||
time: formatShortDuration(lang, waitTime),
|
||||
points: Math.abs(pendingPoints),
|
||||
link: (
|
||||
<span className={styles.previewLink} onClick={handleShowFuture}>
|
||||
{lang('LinkDescriptionRatingPreview')}
|
||||
</span>
|
||||
),
|
||||
}, {
|
||||
pluralValue: Math.abs(pendingPoints),
|
||||
withNodes: true,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
let animationDirection: AnimationDirection = 'none';
|
||||
if (currentLevel >= 0 && pendingLevel >= 0 && currentLevel !== pendingLevel) {
|
||||
animationDirection = currentLevel > pendingLevel ? 'forward' : 'backward';
|
||||
}
|
||||
|
||||
if (currentLevel < 0 && pendingLevel < 0 && currentLevel !== pendingLevel) {
|
||||
animationDirection = currentLevel < pendingLevel ? 'backward' : 'forward';
|
||||
}
|
||||
|
||||
const userFallbackText = lang('ActionFallbackUser');
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.title}>
|
||||
{lang('TitleRating')}
|
||||
</div>
|
||||
<PremiumProgress
|
||||
leftText={isNegative ? undefined : lang('RatingLevel', { level: currentLevel })}
|
||||
rightText={isNegative ? lang('RatingNegativeLevel') : lang('RatingLevel', { level: nextLevel })}
|
||||
floatingBadgeIcon={isNegative ? 'warning' : 'crown-wear'}
|
||||
floatingBadgeText={isNegative ? currentLevel.toString()
|
||||
: `${lang.number(currentStars)} / ${lang.number(nextLevelStars || currentStars)}`}
|
||||
progress={progress}
|
||||
isPrimary={currentLevel >= 0}
|
||||
isNegative={currentLevel < 0}
|
||||
animationDirection={animationDirection}
|
||||
className={buildClassName(styles.ratingProgress, shouldShowPreview && styles.withPreview)}
|
||||
/>
|
||||
{renderPreviewDescription()}
|
||||
<p className={styles.description}>
|
||||
{user?.id === currentUserId
|
||||
? lang('RatingYourReflectsActivity')
|
||||
: lang('RatingReflectsActivity', { name: getPeerTitle(lang, user) || userFallbackText })}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}, [modal, user, currentUserId, starsRating,
|
||||
pendingRating, pendingRatingDate, showFutureRating,
|
||||
lang, handleShowFuture, handleShowCurrent, isOpen]);
|
||||
|
||||
const listItemData = [
|
||||
['gift', lang('RatingGiftsFromTelegram'), (
|
||||
<span className={styles.subtitle}>
|
||||
{renderBadge('added')}
|
||||
{lang('RatingGiftsFromTelegramDesc')}
|
||||
</span>
|
||||
)],
|
||||
['user-stars', lang('RatingGiftsAndPostsFromUsers'), (
|
||||
<span className={styles.subtitle}>
|
||||
{renderBadge('added')}
|
||||
{lang('RatingGiftsAndPostsFromUsersDesc')}
|
||||
</span>
|
||||
)],
|
||||
['refund', lang('RatingRefundsAndConversions'), (
|
||||
<span className={styles.subtitle}>
|
||||
{renderBadge('deducted')}
|
||||
{lang('RatingRefundsAndConversionsDesc')}
|
||||
</span>
|
||||
)],
|
||||
] satisfies TableAboutData;
|
||||
|
||||
const footer = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
return (
|
||||
<div className={styles.footer}>
|
||||
<Button
|
||||
size="smaller"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<Icon name="understood" className={styles.understoodIcon} />
|
||||
{lang('ButtonUnderstood')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}, [lang, isOpen, handleClose]);
|
||||
|
||||
return (
|
||||
<TableAboutModal
|
||||
isOpen={isOpen}
|
||||
header={header}
|
||||
listItemData={listItemData}
|
||||
footer={footer}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { modal }): StateProps => {
|
||||
const currentUserId = global.currentUserId;
|
||||
const user = modal?.userId ? selectUser(global, modal.userId) : undefined;
|
||||
const userFullInfo = modal?.userId
|
||||
? selectUserFullInfo(global, modal.userId) : undefined;
|
||||
|
||||
const starsRating = userFullInfo?.starsRating;
|
||||
const pendingRating = userFullInfo?.starsMyPendingRating;
|
||||
const pendingRatingDate = userFullInfo?.starsMyPendingRatingDate;
|
||||
|
||||
return {
|
||||
user,
|
||||
currentUserId,
|
||||
starsRating,
|
||||
pendingRating,
|
||||
pendingRatingDate,
|
||||
};
|
||||
},
|
||||
)(ProfileRatingModal));
|
||||
@ -64,3 +64,16 @@ addActionHandler('closeSuggestedStatusModal', (global, actions, payload): Action
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeChatRefundModal', 'chatRefundModal');
|
||||
|
||||
addActionHandler('openProfileRatingModal', (global, actions, payload): ActionReturnType => {
|
||||
const { userId, level, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
profileRatingModal: {
|
||||
userId,
|
||||
level,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeProfileRatingModal', 'profileRatingModal');
|
||||
|
||||
@ -1850,6 +1850,11 @@ export interface ActionPayloads {
|
||||
userId: string;
|
||||
} & WithTabId;
|
||||
closeChatRefundModal: WithTabId | undefined;
|
||||
openProfileRatingModal: {
|
||||
userId: string;
|
||||
level: number;
|
||||
} & WithTabId;
|
||||
closeProfileRatingModal: WithTabId | undefined;
|
||||
loadMoreProfilePhotos: {
|
||||
peerId: string;
|
||||
isPreload?: boolean;
|
||||
|
||||
@ -852,6 +852,11 @@ export type TabState = {
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
profileRatingModal?: {
|
||||
userId: string;
|
||||
level: number;
|
||||
};
|
||||
|
||||
monetizationVerificationModal?: {
|
||||
chatId: string;
|
||||
isLoading?: boolean;
|
||||
|
||||
@ -207,101 +207,124 @@ $icons-map: (
|
||||
"quote-text": "\f1aa",
|
||||
"quote": "\f1ab",
|
||||
"radial-badge": "\f1ac",
|
||||
"readchats": "\f1ad",
|
||||
"recent": "\f1ae",
|
||||
"reload": "\f1af",
|
||||
"remove-quote": "\f1b0",
|
||||
"remove": "\f1b1",
|
||||
"reopen-topic": "\f1b2",
|
||||
"replace": "\f1b3",
|
||||
"replies": "\f1b4",
|
||||
"reply-filled": "\f1b5",
|
||||
"reply": "\f1b6",
|
||||
"revenue-split": "\f1b7",
|
||||
"revote": "\f1b8",
|
||||
"save-story": "\f1b9",
|
||||
"saved-messages": "\f1ba",
|
||||
"schedule": "\f1bb",
|
||||
"sd-photo": "\f1bc",
|
||||
"search": "\f1bd",
|
||||
"select": "\f1be",
|
||||
"sell-outline": "\f1bf",
|
||||
"sell": "\f1c0",
|
||||
"send-outline": "\f1c1",
|
||||
"send": "\f1c2",
|
||||
"settings-filled": "\f1c3",
|
||||
"settings": "\f1c4",
|
||||
"share-filled": "\f1c5",
|
||||
"share-screen-outlined": "\f1c6",
|
||||
"share-screen-stop": "\f1c7",
|
||||
"share-screen": "\f1c8",
|
||||
"show-message": "\f1c9",
|
||||
"sidebar": "\f1ca",
|
||||
"skip-next": "\f1cb",
|
||||
"skip-previous": "\f1cc",
|
||||
"smallscreen": "\f1cd",
|
||||
"smile": "\f1ce",
|
||||
"sort-by-date": "\f1cf",
|
||||
"sort-by-number": "\f1d0",
|
||||
"sort-by-price": "\f1d1",
|
||||
"sort": "\f1d2",
|
||||
"speaker-muted-story": "\f1d3",
|
||||
"speaker-outline": "\f1d4",
|
||||
"speaker-story": "\f1d5",
|
||||
"speaker": "\f1d6",
|
||||
"spoiler-disable": "\f1d7",
|
||||
"spoiler": "\f1d8",
|
||||
"sport": "\f1d9",
|
||||
"star": "\f1da",
|
||||
"stars-lock": "\f1db",
|
||||
"stats": "\f1dc",
|
||||
"stealth-future": "\f1dd",
|
||||
"stealth-past": "\f1de",
|
||||
"stickers": "\f1df",
|
||||
"stop-raising-hand": "\f1e0",
|
||||
"stop": "\f1e1",
|
||||
"story-caption": "\f1e2",
|
||||
"story-expired": "\f1e3",
|
||||
"story-priority": "\f1e4",
|
||||
"story-reply": "\f1e5",
|
||||
"strikethrough": "\f1e6",
|
||||
"tag-add": "\f1e7",
|
||||
"tag-crossed": "\f1e8",
|
||||
"tag-filter": "\f1e9",
|
||||
"tag-name": "\f1ea",
|
||||
"tag": "\f1eb",
|
||||
"timer": "\f1ec",
|
||||
"toncoin": "\f1ed",
|
||||
"trade": "\f1ee",
|
||||
"transcribe": "\f1ef",
|
||||
"truck": "\f1f0",
|
||||
"unarchive": "\f1f1",
|
||||
"underlined": "\f1f2",
|
||||
"unique-profile": "\f1f3",
|
||||
"unlist-outline": "\f1f4",
|
||||
"unlist": "\f1f5",
|
||||
"unlock-badge": "\f1f6",
|
||||
"unlock": "\f1f7",
|
||||
"unmute": "\f1f8",
|
||||
"unpin": "\f1f9",
|
||||
"unread": "\f1fa",
|
||||
"up": "\f1fb",
|
||||
"user-filled": "\f1fc",
|
||||
"user-online": "\f1fd",
|
||||
"user": "\f1fe",
|
||||
"video-outlined": "\f1ff",
|
||||
"video-stop": "\f200",
|
||||
"video": "\f201",
|
||||
"view-once": "\f202",
|
||||
"voice-chat": "\f203",
|
||||
"volume-1": "\f204",
|
||||
"volume-2": "\f205",
|
||||
"volume-3": "\f206",
|
||||
"web": "\f207",
|
||||
"webapp": "\f208",
|
||||
"word-wrap": "\f209",
|
||||
"zoom-in": "\f20a",
|
||||
"zoom-out": "\f20b",
|
||||
"rating-icons-level1": "\f1ad",
|
||||
"rating-icons-level10": "\f1ae",
|
||||
"rating-icons-level2": "\f1af",
|
||||
"rating-icons-level20": "\f1b0",
|
||||
"rating-icons-level3": "\f1b1",
|
||||
"rating-icons-level30": "\f1b2",
|
||||
"rating-icons-level4": "\f1b3",
|
||||
"rating-icons-level40": "\f1b4",
|
||||
"rating-icons-level5": "\f1b5",
|
||||
"rating-icons-level50": "\f1b6",
|
||||
"rating-icons-level6": "\f1b7",
|
||||
"rating-icons-level60": "\f1b8",
|
||||
"rating-icons-level7": "\f1b9",
|
||||
"rating-icons-level70": "\f1ba",
|
||||
"rating-icons-level8": "\f1bb",
|
||||
"rating-icons-level80": "\f1bc",
|
||||
"rating-icons-level9": "\f1bd",
|
||||
"rating-icons-level90": "\f1be",
|
||||
"rating-icons-negative": "\f1bf",
|
||||
"readchats": "\f1c0",
|
||||
"recent": "\f1c1",
|
||||
"refund": "\f1c2",
|
||||
"reload": "\f1c3",
|
||||
"remove-quote": "\f1c4",
|
||||
"remove": "\f1c5",
|
||||
"reopen-topic": "\f1c6",
|
||||
"replace": "\f1c7",
|
||||
"replies": "\f1c8",
|
||||
"reply-filled": "\f1c9",
|
||||
"reply": "\f1ca",
|
||||
"revenue-split": "\f1cb",
|
||||
"revote": "\f1cc",
|
||||
"save-story": "\f1cd",
|
||||
"saved-messages": "\f1ce",
|
||||
"schedule": "\f1cf",
|
||||
"sd-photo": "\f1d0",
|
||||
"search": "\f1d1",
|
||||
"select": "\f1d2",
|
||||
"sell-outline": "\f1d3",
|
||||
"sell": "\f1d4",
|
||||
"send-outline": "\f1d5",
|
||||
"send": "\f1d6",
|
||||
"settings-filled": "\f1d7",
|
||||
"settings": "\f1d8",
|
||||
"share-filled": "\f1d9",
|
||||
"share-screen-outlined": "\f1da",
|
||||
"share-screen-stop": "\f1db",
|
||||
"share-screen": "\f1dc",
|
||||
"show-message": "\f1dd",
|
||||
"sidebar": "\f1de",
|
||||
"skip-next": "\f1df",
|
||||
"skip-previous": "\f1e0",
|
||||
"smallscreen": "\f1e1",
|
||||
"smile": "\f1e2",
|
||||
"sort-by-date": "\f1e3",
|
||||
"sort-by-number": "\f1e4",
|
||||
"sort-by-price": "\f1e5",
|
||||
"sort": "\f1e6",
|
||||
"speaker-muted-story": "\f1e7",
|
||||
"speaker-outline": "\f1e8",
|
||||
"speaker-story": "\f1e9",
|
||||
"speaker": "\f1ea",
|
||||
"spoiler-disable": "\f1eb",
|
||||
"spoiler": "\f1ec",
|
||||
"sport": "\f1ed",
|
||||
"star": "\f1ee",
|
||||
"stars-lock": "\f1ef",
|
||||
"stats": "\f1f0",
|
||||
"stealth-future": "\f1f1",
|
||||
"stealth-past": "\f1f2",
|
||||
"stickers": "\f1f3",
|
||||
"stop-raising-hand": "\f1f4",
|
||||
"stop": "\f1f5",
|
||||
"story-caption": "\f1f6",
|
||||
"story-expired": "\f1f7",
|
||||
"story-priority": "\f1f8",
|
||||
"story-reply": "\f1f9",
|
||||
"strikethrough": "\f1fa",
|
||||
"tag-add": "\f1fb",
|
||||
"tag-crossed": "\f1fc",
|
||||
"tag-filter": "\f1fd",
|
||||
"tag-name": "\f1fe",
|
||||
"tag": "\f1ff",
|
||||
"timer": "\f200",
|
||||
"toncoin": "\f201",
|
||||
"trade": "\f202",
|
||||
"transcribe": "\f203",
|
||||
"truck": "\f204",
|
||||
"unarchive": "\f205",
|
||||
"underlined": "\f206",
|
||||
"understood": "\f207",
|
||||
"unique-profile": "\f208",
|
||||
"unlist-outline": "\f209",
|
||||
"unlist": "\f20a",
|
||||
"unlock-badge": "\f20b",
|
||||
"unlock": "\f20c",
|
||||
"unmute": "\f20d",
|
||||
"unpin": "\f20e",
|
||||
"unread": "\f20f",
|
||||
"up": "\f210",
|
||||
"user-filled": "\f211",
|
||||
"user-online": "\f212",
|
||||
"user-stars": "\f213",
|
||||
"user": "\f214",
|
||||
"video-outlined": "\f215",
|
||||
"video-stop": "\f216",
|
||||
"video": "\f217",
|
||||
"view-once": "\f218",
|
||||
"voice-chat": "\f219",
|
||||
"volume-1": "\f21a",
|
||||
"volume-2": "\f21b",
|
||||
"volume-3": "\f21c",
|
||||
"warning": "\f21d",
|
||||
"web": "\f21e",
|
||||
"webapp": "\f21f",
|
||||
"word-wrap": "\f220",
|
||||
"zoom-in": "\f221",
|
||||
"zoom-out": "\f222",
|
||||
);
|
||||
|
||||
.icon-active-sessions::before {
|
||||
@ -820,12 +843,72 @@ $icons-map: (
|
||||
.icon-radial-badge::before {
|
||||
content: map.get($icons-map, "radial-badge");
|
||||
}
|
||||
.icon-rating-icons-level1::before {
|
||||
content: map.get($icons-map, "rating-icons-level1");
|
||||
}
|
||||
.icon-rating-icons-level10::before {
|
||||
content: map.get($icons-map, "rating-icons-level10");
|
||||
}
|
||||
.icon-rating-icons-level2::before {
|
||||
content: map.get($icons-map, "rating-icons-level2");
|
||||
}
|
||||
.icon-rating-icons-level20::before {
|
||||
content: map.get($icons-map, "rating-icons-level20");
|
||||
}
|
||||
.icon-rating-icons-level3::before {
|
||||
content: map.get($icons-map, "rating-icons-level3");
|
||||
}
|
||||
.icon-rating-icons-level30::before {
|
||||
content: map.get($icons-map, "rating-icons-level30");
|
||||
}
|
||||
.icon-rating-icons-level4::before {
|
||||
content: map.get($icons-map, "rating-icons-level4");
|
||||
}
|
||||
.icon-rating-icons-level40::before {
|
||||
content: map.get($icons-map, "rating-icons-level40");
|
||||
}
|
||||
.icon-rating-icons-level5::before {
|
||||
content: map.get($icons-map, "rating-icons-level5");
|
||||
}
|
||||
.icon-rating-icons-level50::before {
|
||||
content: map.get($icons-map, "rating-icons-level50");
|
||||
}
|
||||
.icon-rating-icons-level6::before {
|
||||
content: map.get($icons-map, "rating-icons-level6");
|
||||
}
|
||||
.icon-rating-icons-level60::before {
|
||||
content: map.get($icons-map, "rating-icons-level60");
|
||||
}
|
||||
.icon-rating-icons-level7::before {
|
||||
content: map.get($icons-map, "rating-icons-level7");
|
||||
}
|
||||
.icon-rating-icons-level70::before {
|
||||
content: map.get($icons-map, "rating-icons-level70");
|
||||
}
|
||||
.icon-rating-icons-level8::before {
|
||||
content: map.get($icons-map, "rating-icons-level8");
|
||||
}
|
||||
.icon-rating-icons-level80::before {
|
||||
content: map.get($icons-map, "rating-icons-level80");
|
||||
}
|
||||
.icon-rating-icons-level9::before {
|
||||
content: map.get($icons-map, "rating-icons-level9");
|
||||
}
|
||||
.icon-rating-icons-level90::before {
|
||||
content: map.get($icons-map, "rating-icons-level90");
|
||||
}
|
||||
.icon-rating-icons-negative::before {
|
||||
content: map.get($icons-map, "rating-icons-negative");
|
||||
}
|
||||
.icon-readchats::before {
|
||||
content: map.get($icons-map, "readchats");
|
||||
}
|
||||
.icon-recent::before {
|
||||
content: map.get($icons-map, "recent");
|
||||
}
|
||||
.icon-refund::before {
|
||||
content: map.get($icons-map, "refund");
|
||||
}
|
||||
.icon-reload::before {
|
||||
content: map.get($icons-map, "reload");
|
||||
}
|
||||
@ -1030,6 +1113,9 @@ $icons-map: (
|
||||
.icon-underlined::before {
|
||||
content: map.get($icons-map, "underlined");
|
||||
}
|
||||
.icon-understood::before {
|
||||
content: map.get($icons-map, "understood");
|
||||
}
|
||||
.icon-unique-profile::before {
|
||||
content: map.get($icons-map, "unique-profile");
|
||||
}
|
||||
@ -1063,6 +1149,9 @@ $icons-map: (
|
||||
.icon-user-online::before {
|
||||
content: map.get($icons-map, "user-online");
|
||||
}
|
||||
.icon-user-stars::before {
|
||||
content: map.get($icons-map, "user-stars");
|
||||
}
|
||||
.icon-user::before {
|
||||
content: map.get($icons-map, "user");
|
||||
}
|
||||
@ -1090,6 +1179,9 @@ $icons-map: (
|
||||
.icon-volume-3::before {
|
||||
content: map.get($icons-map, "volume-3");
|
||||
}
|
||||
.icon-warning::before {
|
||||
content: map.get($icons-map, "warning");
|
||||
}
|
||||
.icon-web::before {
|
||||
content: map.get($icons-map, "web");
|
||||
}
|
||||
|
||||
@ -171,8 +171,28 @@ export type FontIconName =
|
||||
| 'quote-text'
|
||||
| 'quote'
|
||||
| 'radial-badge'
|
||||
| 'rating-icons-level1'
|
||||
| 'rating-icons-level10'
|
||||
| 'rating-icons-level2'
|
||||
| 'rating-icons-level20'
|
||||
| 'rating-icons-level3'
|
||||
| 'rating-icons-level30'
|
||||
| 'rating-icons-level4'
|
||||
| 'rating-icons-level40'
|
||||
| 'rating-icons-level5'
|
||||
| 'rating-icons-level50'
|
||||
| 'rating-icons-level6'
|
||||
| 'rating-icons-level60'
|
||||
| 'rating-icons-level7'
|
||||
| 'rating-icons-level70'
|
||||
| 'rating-icons-level8'
|
||||
| 'rating-icons-level80'
|
||||
| 'rating-icons-level9'
|
||||
| 'rating-icons-level90'
|
||||
| 'rating-icons-negative'
|
||||
| 'readchats'
|
||||
| 'recent'
|
||||
| 'refund'
|
||||
| 'reload'
|
||||
| 'remove-quote'
|
||||
| 'remove'
|
||||
@ -241,6 +261,7 @@ export type FontIconName =
|
||||
| 'truck'
|
||||
| 'unarchive'
|
||||
| 'underlined'
|
||||
| 'understood'
|
||||
| 'unique-profile'
|
||||
| 'unlist-outline'
|
||||
| 'unlist'
|
||||
@ -252,6 +273,7 @@ export type FontIconName =
|
||||
| 'up'
|
||||
| 'user-filled'
|
||||
| 'user-online'
|
||||
| 'user-stars'
|
||||
| 'user'
|
||||
| 'video-outlined'
|
||||
| 'video-stop'
|
||||
@ -261,6 +283,7 @@ export type FontIconName =
|
||||
| 'volume-1'
|
||||
| 'volume-2'
|
||||
| 'volume-3'
|
||||
| 'warning'
|
||||
| 'web'
|
||||
| 'webapp'
|
||||
| 'word-wrap'
|
||||
|
||||
29
src/types/language.d.ts
vendored
@ -1642,6 +1642,19 @@ export interface LangPair {
|
||||
'PublicPostsPremiumFeatureSubtitle': undefined;
|
||||
'PublicPostsSubscribeToPremium': undefined;
|
||||
'PostsSearchTransaction': undefined;
|
||||
'TitleRating': undefined;
|
||||
'RatingYourReflectsActivity': undefined;
|
||||
'RatingGiftsFromTelegram': undefined;
|
||||
'RatingGiftsFromTelegramDesc': undefined;
|
||||
'RatingGiftsAndPostsFromUsers': undefined;
|
||||
'RatingGiftsAndPostsFromUsersDesc': undefined;
|
||||
'RatingRefundsAndConversions': undefined;
|
||||
'RatingRefundsAndConversionsDesc': undefined;
|
||||
'RatingBadgeAdded': undefined;
|
||||
'RatingBadgeDeducted': undefined;
|
||||
'RatingNegativeLevel': undefined;
|
||||
'LinkDescriptionRatingBack': undefined;
|
||||
'LinkDescriptionRatingPreview': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V = LangVariable> {
|
||||
@ -2850,6 +2863,12 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'NotificationPaidExtraSearch': {
|
||||
'stars': V;
|
||||
};
|
||||
'RatingReflectsActivity': {
|
||||
'name': V;
|
||||
};
|
||||
'RatingLevel': {
|
||||
'level': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
@ -3177,6 +3196,16 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
||||
'HintPublicPostsSearchQuota': {
|
||||
'count': V;
|
||||
};
|
||||
'DescriptionPendingRating': {
|
||||
'time': V;
|
||||
'points': V;
|
||||
'link': V;
|
||||
};
|
||||
'DescriptionFutureRating': {
|
||||
'time': V;
|
||||
'points': V;
|
||||
'link': V;
|
||||
};
|
||||
}
|
||||
export type RegularLangKey = keyof LangPair;
|
||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||
|
||||