import React, { memo, useEffect, useMemo, useRef, } from '../../../lib/teact/teact'; import { getActions, withGlobal } from '../../../global'; import { type ApiBirthday, ApiMediaFormat, type ApiStickerSet, type ApiUser, } from '../../../api/types'; import { requestMeasure } from '../../../lib/fasterdom/fasterdom'; import { getStickerMediaHash } from '../../../global/helpers'; import { selectIsPremiumPurchaseBlocked } from '../../../global/selectors'; import buildClassName from '../../../util/buildClassName'; import { formatDateToString } from '../../../util/dates/dateFormat'; import { buildCollectionByKey } from '../../../util/iteratees'; import * as mediaLoader from '../../../util/mediaLoader'; import { IS_OFFSET_PATH_SUPPORTED } from '../../../util/windowEnvironment'; import renderText from '../helpers/renderText'; import useTimeout from '../../../hooks/schedulers/useTimeout'; import useFlag from '../../../hooks/useFlag'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import ListItem from '../../ui/ListItem'; import StickerView from '../StickerView'; import styles from './UserBirthday.module.scss'; const NUMBER_EMOJI_SUFFIX = '\uFE0F\u20E3'; const NUMBER_STICKER_SIZE = 128; const EFFECT_EMOJIS = ['🎉', '🎆', '🎈']; const EFFECT_SIZE = 288; const ANIMATION_DURATION = 3000; type OwnProps = { user: ApiUser; birthday: ApiBirthday; isInSettings?: boolean; }; type StateProps = { isPremiumPurchaseBlocked?: boolean; birthdayNumbers?: ApiStickerSet; animatedEmojiEffects?: ApiStickerSet; }; const UserBirthday = ({ user, birthday, isPremiumPurchaseBlocked, birthdayNumbers, animatedEmojiEffects, isInSettings, }: OwnProps & StateProps) => { const { openGiftModal, requestConfetti } = getActions(); // eslint-disable-next-line no-null/no-null const ref = useRef(null); const animationPlayedRef = useRef(false); const [isPlayingAnimation, playAnimation, stopAnimation] = useFlag(); const lang = useOldLang(); const { formattedDate, isToday, age, } = useMemo(() => { const today = new Date(); const date = new Date(); if (birthday.year) { date.setFullYear(birthday.year); } date.setMonth(birthday.month - 1); date.setDate(birthday.day); date.setHours(0, 0, 0, 0); const formatted = formatDateToString(date, lang.code, true, 'long'); const isBirthdayToday = date.getDate() === today.getDate() && date.getMonth() === today.getMonth(); return { formattedDate: formatted, isToday: isBirthdayToday, age: birthday.year && getAge(date), }; }, [birthday, lang]); const numbersForAge = useMemo(() => { if (!age || !isToday) return undefined; const numbers = birthdayNumbers?.stickers?.filter(({ emoji }) => emoji?.endsWith(NUMBER_EMOJI_SUFFIX)); if (!numbers) return undefined; const byEmoji = buildCollectionByKey(numbers, 'emoji'); const ageDigits = age.toString().split(''); return ageDigits.map((digit) => byEmoji[digit + NUMBER_EMOJI_SUFFIX]); }, [age, birthdayNumbers?.stickers, isToday]); const effectSticker = useMemo(() => { if (!isToday) return undefined; const randomEffect = EFFECT_EMOJIS[Math.floor(Math.random() * EFFECT_EMOJIS.length)]; return animatedEmojiEffects?.stickers?.find(({ emoji }) => emoji === randomEffect); }, [animatedEmojiEffects?.stickers, isToday]); // Preload stickers useEffect(() => { if (!isToday || !numbersForAge) return; numbersForAge.forEach((sticker) => { const hash = getStickerMediaHash(sticker, 'preview'); mediaLoader.fetch(hash, ApiMediaFormat.BlobUrl); }); if (effectSticker) { const effectHash = getStickerMediaHash(effectSticker, 'preview'); mediaLoader.fetch(effectHash, ApiMediaFormat.BlobUrl); } }, [effectSticker, isToday, numbersForAge]); useTimeout(stopAnimation, isPlayingAnimation ? ANIMATION_DURATION : undefined); useEffect(() => { if (isPlayingAnimation) { animationPlayedRef.current = true; const column = document.getElementById(isInSettings ? 'LeftColumn' : 'RightColumn'); if (!column) return; requestMeasure(() => { const { top, left, width, height, } = column.getBoundingClientRect(); requestConfetti({ top, left, width, height, style: 'top-down', }); }); } }, [isInSettings, isPlayingAnimation]); const valueKey = `ProfileBirthday${isToday ? 'Today' : ''}Value${age ? 'Year' : ''}`; const canGiftPremium = isToday && !user.isPremium && !user.isSelf && !isPremiumPurchaseBlocked; const handleOpenGiftModal = useLastCallback(() => { openGiftModal({ forUserId: user.id }); }); const handleClick = useLastCallback(() => { if (!isToday) return; if (canGiftPremium && animationPlayedRef.current) { handleOpenGiftModal(); return; } playAnimation(); }); const isStatic = !isToday && !canGiftPremium; return (
{renderText(lang(valueKey, [formattedDate, age], undefined, age))}
{lang(isToday ? 'ProfileBirthdayToday' : 'ProfileBirthday')}
{isPlayingAnimation && IS_OFFSET_PATH_SUPPORTED && numbersForAge?.map((sticker, index) => (
0 && styles.shiftOrigin)} style={`--digit-offset: ${index}`} >
))} {isPlayingAnimation && effectSticker && (
)}
); }; export default memo(withGlobal( (global): StateProps => { const { birthdayNumbers, animatedEmojiEffects } = global; return { birthdayNumbers, animatedEmojiEffects, isPremiumPurchaseBlocked: selectIsPremiumPurchaseBlocked(global), }; }, )(UserBirthday)); // https://stackoverflow.com/a/7091965 function getAge(birthdate: Date) { const today = new Date(); let age = today.getFullYear() - birthdate.getFullYear(); const m = today.getMonth() - birthdate.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birthdate.getDate())) { age--; } return age; }