import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react' import { init, mute, onClick, unmute } from '../../lib/render' import { Claim } from '@blockworks-foundation/mango-mints-redemption' import { Token } from 'types/jupiter' import BigNumber from 'bignumber.js' import { IconButton } from '@components/shared/Button' import { XMarkIcon } from '@heroicons/react/20/solid' import { PublicKey } from '@solana/web3.js' import { CUSTOM_TOKEN_ICONS } from 'utils/constants' import { Sft, SftWithToken, Nft, NftWithToken } from '@metaplex-foundation/js' import { Lalezar } from 'next/font/google' import { usePlausible } from 'next-plausible' import { TelemetryEvents } from 'utils/telemetry' import mangoStore from '@store/mangoStore' const lalezar = Lalezar({ weight: '400', subsets: ['latin'], display: 'swap', }) export type Prize = { //symbol item: string //amount info: string rarity: 'rare' | 'legendary' | 'common' //eg [32, 32] token, nft [400,400] itemResolution: number[] itemUrl: string particleId: 'particles-coins' | 'particles-fireworks' stencilUrl: | '/models/tex_procedural/tex_card_front_gold_albedo.png' | '/models/tex_procedural/tex_card_front_gold_circle_albedo.png' | '/models/tex_procedural/tex_card_front_silver_square_albedo.png' | '/models/tex_procedural/tex_card_front_slver_albedo.png' frontMaterialId: | 'loader_mat_card_gold_front' | 'loader_mat_card_gold_front_circle' | 'loader_mat_card_silver_front_square' | 'loader_mat_card_silver_front' backMaterialId: 'loader_mat_card_gold_back' | 'loader_mat_card_silver_back' } export const getFallbackImg = ( mint: PublicKey, jupiterLogoUrl: string | undefined, ) => { const group = mangoStore.getState().group const bank = group?.getFirstBankByMint(mint) const tokenSymbol = bank?.name.toLowerCase() const hasCustomIcon = tokenSymbol ? CUSTOM_TOKEN_ICONS[tokenSymbol] : false if (hasCustomIcon) { return `/icons/${tokenSymbol}.svg` } else { return jupiterLogoUrl || `/icons/mngo.svg` } } export const getClaimsAsPrizes = ( claims: Claim[], tokensInfo: Token[], nftsRewardsInfo: (Sft | SftWithToken | Nft | NftWithToken)[], ) => claims.map((x) => { const rarity = x.mintProperties.rarity.toLowerCase() const type = x.mintProperties.type.toLowerCase() const tokenInfo = type === 'token' ? tokensInfo.find((t) => t.address === x.mint.toBase58()) : null const nftInfo = type === 'nft' ? nftsRewardsInfo.find( (ni) => ni.address.toBase58() === x.mint.toBase58(), ) : null const resultion = type === 'token' ? [32, 32] : [400, 400] const Materials: Record< string, Omit > = { goldCircle: { stencilUrl: '/models/tex_procedural/tex_card_front_gold_circle_albedo.png', frontMaterialId: 'loader_mat_card_gold_front_circle', backMaterialId: 'loader_mat_card_gold_back', particleId: 'particles-coins', }, goldSquare: { stencilUrl: '/models/tex_procedural/tex_card_front_gold_albedo.png', frontMaterialId: 'loader_mat_card_gold_front', backMaterialId: 'loader_mat_card_gold_back', particleId: 'particles-fireworks', }, silverCircle: { stencilUrl: '/models/tex_procedural/tex_card_front_slver_albedo.png', frontMaterialId: 'loader_mat_card_silver_front', backMaterialId: 'loader_mat_card_silver_back', particleId: 'particles-coins', }, silverSquare: { stencilUrl: '/models/tex_procedural/tex_card_front_silver_square_albedo.png', frontMaterialId: 'loader_mat_card_silver_front_square', backMaterialId: 'loader_mat_card_silver_back', particleId: 'particles-fireworks', }, } const MaterialMapping: Record = { 'token-common': 'silverCircle', 'token-rare': 'goldCircle', 'token-legendary': 'goldCircle', 'nft-common': 'silverSquare', 'nft-rare': 'goldSquare', 'nft-legendary': 'goldSquare', } const getMaterials = ( type: 'token' | 'nft', rarity: 'common' | 'rare' | 'legendary', ) => { const materialKey = `${type}-${rarity}` return { material: Materials[MaterialMapping[materialKey]], } } const material = getMaterials(type, rarity) return { item: x.mintProperties.name, info: type === 'token' && tokenInfo ? new BigNumber(x.quantity.toString()) .shiftedBy(-tokenInfo.decimals) .toString() : x.quantity.toString(), rarity: rarity.charAt(0).toUpperCase() + rarity.slice(1), itemResolution: resultion, //fallback of img from files if mint matches mango bank itemUrl: type === 'token' ? getFallbackImg(x.mint, tokenInfo?.logoURI) : nftInfo?.json?.image || `/icons/mngo.svg`, ...material.material, } }) export default function RewardsComponent({ setShowRender, claims, tokensInfo, nftsRewardsInfo, start, }: { setShowRender: Dispatch> claims: Claim[] tokensInfo: Token[] nftsRewardsInfo: (Sft | SftWithToken | Nft | NftWithToken)[] start: boolean }) { const renderLoaded = useRef(false) // eslint-disable-next-line @typescript-eslint/no-explicit-any const [collectedPrizes, setCollectedPrize] = useState([] as any[]) const [prizes, setPrizes] = useState([]) const [isAnimationFinished, setIsAnimationFinished] = useState(false) const [currentPrize, setCurrentPrize] = useState() const telemetry = usePlausible() function iOS() { return ( [ 'iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod', ].includes(navigator.platform) || // iPad on iOS 13 detection (navigator.userAgent.includes('Mac') && 'ontouchend' in document) ) } useEffect(() => { if (!renderLoaded.current && prizes.length && start) { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any const v1 = document.getElementById('particles-fireworks') as any v1.onloadedmetadata = () => (v1.currentTime = v1.duration) // eslint-disable-next-line @typescript-eslint/no-explicit-any const v2 = document.getElementById('particles-coins') as any v2.onloadedmetadata = () => (v2.currentTime = v2.duration) // eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any init(document, window, prizes, (prize: any, isLast: boolean) => { console.log('callback:showPrize', prize) setCurrentPrize(prize) collectedPrizes.push(prize) setCollectedPrize(collectedPrizes) setTimeout(() => { console.log('callback:hidePrize') setCurrentPrize(undefined) }, 5000) if (isLast) { setIsAnimationFinished(true) } }) renderLoaded.current = true } catch (e) { //if webgl is turned off or someone uses old computer telemetry('rewardsRenderUnsupported', { props: { message: (e as Error).toString() }, }) console.log(e) setShowRender(false) } } }, [collectedPrizes, prizes, setShowRender, start, telemetry]) useEffect(() => { if (tokensInfo.length) { const claimsAsPrizes = getClaimsAsPrizes( claims, tokensInfo, nftsRewardsInfo, ) setPrizes(claimsAsPrizes) } }, [claims, nftsRewardsInfo, tokensInfo]) // close after animation finishes useEffect(() => { if (isAnimationFinished) { setTimeout(() => { setShowRender(false) }, 10000) } }, [isAnimationFinished]) return (
{start && (
{ telemetry('rewardsCloseRender', { props: { rewards: collectedPrizes.length, early: isAnimationFinished, }, }) setShowRender(false) mute() document.getElementById('render-output')?.remove() }} >
)}
{collectedPrizes.map((p, i) => { return ( // eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text ) })}
{!!currentPrize && ( <>

{currentPrize['item']}

{currentPrize['rarity']}

{currentPrize['info']}

)}
{ unmute() if (!iOS()) { onClick() } }} >
) }