import { ClubOfferData, GiftReceiverNotFoundEvent, PurchaseFromCatalogAsGiftComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CatalogPurchaseState, LocalizeText, SanitizeHtml, SendMessageComposer } from '../../../../../api'; import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutLoadingSpinnerView, Text } from '../../../../../common'; import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events'; import { useCatalogData, useClubOffers, useMessageEvent, usePurse, useUiEvent, useUserDataSnapshot } from '../../../../../hooks'; import { CatalogLayoutProps } from './CatalogLayout.types'; const VIP_WINDOW_ID = 1; export const CatalogLayoutVipBuyView: FC = props => { const [ pendingOffer, setPendingOffer ] = useState(null); const [ purchaseState, setPurchaseState ] = useState(CatalogPurchaseState.NONE); const [ giftMode, setGiftMode ] = useState(false); const [ giftRecipient, setGiftRecipient ] = useState(''); const [ giftError, setGiftError ] = useState(null); const [ giftSuccess, setGiftSuccess ] = useState(false); const { currentPage = null } = useCatalogData(); const { purse = null, getCurrencyAmount = null } = usePurse(); const { data: offers = null } = useClubOffers(VIP_WINDOW_ID); const { userName: ownUserName = '' } = useUserDataSnapshot(); const isPurchasingRef = useRef(false); const wasGiftPurchaseRef = useRef(false); const giftSuccessTimerRef = useRef | null>(null); const isSelfGift = giftMode && !!ownUserName && giftRecipient.trim().toLowerCase() === ownUserName.toLowerCase(); const onCatalogEvent = useCallback((event: CatalogEvent) => { switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: isPurchasingRef.current = false; setPurchaseState(CatalogPurchaseState.NONE); setGiftError(null); if(wasGiftPurchaseRef.current) { wasGiftPurchaseRef.current = false; setGiftRecipient(''); setGiftMode(false); setGiftSuccess(true); if(giftSuccessTimerRef.current) clearTimeout(giftSuccessTimerRef.current); giftSuccessTimerRef.current = setTimeout(() => setGiftSuccess(false), 3500); } return; case CatalogPurchaseFailureEvent.PURCHASE_FAILED: isPurchasingRef.current = false; wasGiftPurchaseRef.current = false; setPurchaseState(CatalogPurchaseState.FAILED); return; } }, []); useUiEvent(CatalogPurchasedEvent.PURCHASE_SUCCESS, onCatalogEvent); useUiEvent(CatalogPurchaseFailureEvent.PURCHASE_FAILED, onCatalogEvent); useEffect(() => () => { if(giftSuccessTimerRef.current) clearTimeout(giftSuccessTimerRef.current); }, []); const handleGiftReceiverNotFound = useCallback(() => { if(!isPurchasingRef.current) return; isPurchasingRef.current = false; setPurchaseState(CatalogPurchaseState.NONE); setGiftError(LocalizeText('catalog.gift_wrapping.receiver_not_found.title')); }, []); useMessageEvent(GiftReceiverNotFoundEvent, handleGiftReceiverNotFound); const getOfferText = useCallback((offer: ClubOfferData) => { let offerText = ''; if(offer.months > 0) { offerText = LocalizeText('catalog.vip.item.header.months', [ 'num_months' ], [ offer.months.toString() ]); } if(offer.extraDays > 0) { if(offerText !== '') offerText += ' '; offerText += (' ' + LocalizeText('catalog.vip.item.header.days', [ 'num_days' ], [ offer.extraDays.toString() ])); } return offerText; }, []); const getPurchaseHeader = useCallback(() => { if(!purse) return ''; const extensionOrSubscription = (purse.clubDays > 0 || purse.clubPeriods > 0) ? 'extension.' : 'subscription.'; const daysOrMonths = ((pendingOffer.months === 0) ? 'days' : 'months'); const daysOrMonthsText = ((pendingOffer.months === 0) ? pendingOffer.extraDays : pendingOffer.months); const locale = LocalizeText('catalog.vip.buy.confirm.' + extensionOrSubscription + daysOrMonths); return locale.replace('%NUM_' + daysOrMonths.toUpperCase() + '%', daysOrMonthsText.toString()); }, [ pendingOffer, purse ]); const getPurchaseValidUntil = useCallback(() => { let locale = LocalizeText('catalog.vip.buy.confirm.end_date'); locale = locale.replace('%month%', pendingOffer.month.toString()); locale = locale.replace('%day%', pendingOffer.day.toString()); locale = locale.replace('%year%', pendingOffer.year.toString()); return locale; }, [ pendingOffer ]); const getSubscriptionDetails = useMemo(() => { const clubDays = purse.clubDays; const clubPeriods = purse.clubPeriods; const totalDays = (clubPeriods * 31) + clubDays; return LocalizeText('catalog.vip.extend.info', [ 'days' ], [ totalDays.toString() ]); }, [ purse ]); const purchaseSubscription = useCallback(() => { if(!pendingOffer || isPurchasingRef.current) return; if(giftMode && !giftRecipient.trim()) return; if(isSelfGift) return; isPurchasingRef.current = true; wasGiftPurchaseRef.current = giftMode; setPurchaseState(CatalogPurchaseState.PURCHASE); setGiftError(null); setGiftSuccess(false); if(giftMode) { SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(currentPage.pageId, pendingOffer.offerId, '', giftRecipient.trim(), '', 0, 0, 0, false)); } else { SendMessageComposer(new PurchaseFromCatalogComposer(currentPage.pageId, pendingOffer.offerId, null, 1)); } }, [ pendingOffer, currentPage, giftMode, giftRecipient, isSelfGift ]); const setOffer = useCallback((offer: ClubOfferData) => { setPurchaseState(CatalogPurchaseState.NONE); setPendingOffer(offer); setGiftError(null); setGiftSuccess(false); if(!offer?.giftable) setGiftMode(false); }, []); const onGiftRecipientChange = useCallback((value: string) => { setGiftRecipient(value); setGiftError(null); setGiftSuccess(false); }, []); const getPurchaseButton = useCallback(() => { if(!pendingOffer) return null; if(pendingOffer.priceCredits > getCurrencyAmount(-1)) { return ; } if(pendingOffer.priceActivityPoints > getCurrencyAmount(pendingOffer.priceActivityPointsType)) { return ; } const giftBlocked = giftMode && (!giftRecipient.trim() || isSelfGift); const buyLabel = giftMode ? LocalizeText('catalog.gift_wrapping.give_gift') : LocalizeText('buy'); switch(purchaseState) { case CatalogPurchaseState.CONFIRM: return ; case CatalogPurchaseState.PURCHASE: return ; case CatalogPurchaseState.FAILED: return ; case CatalogPurchaseState.NONE: default: return ; } }, [ pendingOffer, purchaseState, purchaseSubscription, getCurrencyAmount, giftMode, giftRecipient, isSelfGift ]); return ( { offers && (offers.length > 0) && offers.map((offer, index) => { const isActive = (pendingOffer === offer); return (
setOffer(offer) }>
{ getOfferText(offer) }
{ (offer.priceCredits > 0) && { offer.priceCredits } } { (offer.priceActivityPoints > 0) && { offer.priceActivityPoints } }
); }) }
{ currentPage.localization.getImage(1) && } { pendingOffer && { giftMode ? LocalizeText('catalog.purchase_confirmation.gift') : getPurchaseHeader() } { getPurchaseValidUntil() }
{ (pendingOffer.priceCredits > 0) && { pendingOffer.priceCredits } } { (pendingOffer.priceActivityPoints > 0) && { pendingOffer.priceActivityPoints } }
{ pendingOffer.giftable && { giftMode && onGiftRecipientChange(event.target.value) } /> } { giftMode && isSelfGift && { LocalizeText('catalog.gift_wrapping.cannot_send_to_self') } } { giftMode && giftError && !isSelfGift && { giftError } } { giftSuccess && { LocalizeText('catalog.gift_wrapping.gift_sent') } } } { getPurchaseButton() }
}
); };