diff --git a/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryArea.tsx b/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryArea.tsx index 0a8436a319..ea83c483ce 100644 --- a/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryArea.tsx +++ b/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryArea.tsx @@ -2,26 +2,35 @@ // SPDX-License-Identifier: Apache-2.0 import { EmptyListContent } from '@subwallet/extension-koni-ui/components/Mythical'; +import { BookaSdk } from '@subwallet/extension-koni-ui/connector/booka/sdk'; +import { Reward } from '@subwallet/extension-koni-ui/connector/booka/types'; import { ThemeProps } from '@subwallet/extension-koni-ui/types'; -import React, { useMemo } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from 'styled-components'; -import { RewardHistoryItem, RewardHistoryItemType } from './RewardHistoryItem'; +import { RewardHistoryItem } from './RewardHistoryItem'; type Props = ThemeProps; +const apiSDK = BookaSdk.instance; const Component = ({ className }: Props): React.ReactElement => { const { t } = useTranslation(); + const [rewardHistories, setRewardHistories] = useState(apiSDK.getRewardHistoryList()); - const items: RewardHistoryItemType[] = useMemo(() => { - return [ - ] as RewardHistoryItemType[]; + useEffect(() => { + const unsub = apiSDK.subscribeRewardList().subscribe((rewards) => { + setRewardHistories(rewards); + }); + + return () => { + unsub.unsubscribe(); + }; }, []); return (
- {items.length > 0 + {rewardHistories.length > 0 ? ( <>
@@ -30,11 +39,11 @@ const Component = ({ className }: Props): React.ReactElement => {
{ - items.map((item) => ( + rewardHistories.map((item) => ( )) } @@ -68,6 +77,12 @@ export const RewardHistoryArea = styled(Component)(({ theme: { exten marginBottom: 12 }, + '.__list-container': { + display: 'flex', + flexDirection: 'column', + gap: 3 + }, + '.empty-list-content': { paddingBottom: 103, paddingTop: 56 diff --git a/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryItem.tsx b/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryItem.tsx index 2b372f9cc6..17ef1e255b 100644 --- a/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryItem.tsx +++ b/packages/extension-koni-ui/src/Popup/Home/MyProfile/RewardHistoryItem.tsx @@ -1,34 +1,74 @@ // Copyright 2019-2022 @subwallet/extension-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 +import { getRewardStatus } from '@subwallet/extension-koni-ui/connector/booka/sdk'; +import { Reward, RewardStatus } from '@subwallet/extension-koni-ui/connector/booka/types'; import { ThemeProps } from '@subwallet/extension-koni-ui/types'; -import { toDisplayNumber } from '@subwallet/extension-koni-ui/utils'; -import React from 'react'; +import { customFormatDate, preloadImages, toDisplayNumber } from '@subwallet/extension-koni-ui/utils'; +import { Icon } from '@subwallet/react-ui'; +import CN from 'classnames'; +import { Check, ClockCounterClockwise, SpinnerGap } from 'phosphor-react'; +import React, { useEffect, useMemo } from 'react'; import styled from 'styled-components'; -export type RewardHistoryItemType = { - ordinal: number; - name: string, - date: string, - tokenValue: string, +type Props = ThemeProps & { + reward: Reward; }; -type Props = ThemeProps & RewardHistoryItemType; +const RewardHistoryStatusItem = { + [RewardStatus.EXPIRED]: { + icon: ClockCounterClockwise, + isShowStatus: true, + value: 'EXPIRED', + color: '#FF596B' + }, + + [RewardStatus.SUCCESS]: { + icon: Check, + isShowStatus: false, + value: 'SUCCESS', + color: '#28C89F' + }, + + [RewardStatus.PENDING]: { + icon: SpinnerGap, + isShowStatus: true, + value: 'PENDING', + color: '#C4FD38' + } +}; + +const Component = ({ className, reward }: Props): React.ReactElement => { + const status = useMemo(() => RewardHistoryStatusItem[getRewardStatus(reward.status)], [reward.status]); + + useEffect(() => { + preloadImages([ + '/images/mythical/reward-history-background-item.png' + ]); + }, []); -const Component = ({ className, date, - name, - ordinal, - tokenValue }: Props): React.ReactElement => { return (
-
{ordinal}
-
{name}
+
+ +
+
{reward.campaign_name}
-
{date}
+ { + status.isShowStatus + ?
{status.value}
+ :
{customFormatDate(reward?.completeDate || 0, '#MMM# #DD#', 'en')}
+ }
- +{toDisplayNumber(tokenValue)}  - MYTH + +{toDisplayNumber(reward.token)}  + {'MYTH'}
@@ -50,7 +90,10 @@ export const RewardHistoryItem = styled(Component)(({ theme: { exten position: 'relative', zIndex: 2, paddingTop: 6, - paddingBottom: 10 + paddingBottom: 10, + backgroundImage: 'url(/images/mythical/reward-history-background-item.png)', + backgroundPosition: 'center center', + backgroundSize: '100% 100%' }, '.__ordinal': { @@ -88,7 +131,7 @@ export const RewardHistoryItem = styled(Component)(({ theme: { exten textAlign: 'right' }, - '.__date': { + '.__status-label': { fontFamily: extendToken.fontBarlowCondensed, fontSize: '12px', fontStyle: 'normal', @@ -141,6 +184,18 @@ export const RewardHistoryItem = styled(Component)(({ theme: { exten backgroundImage: 'linear-gradient(75deg, rgba(54, 53, 53, 0.32) 25.94%, rgba(25, 25, 25, 0.32) 63.11%)', backdropFilter: 'blur(16px)' } + }, + + '.-success': { + color: '#28C89F' + }, + + '.-pending': { + color: '#C4FD38' + }, + + '.-expired': { + color: '#FF596B' } }; }); diff --git a/packages/extension-koni-ui/src/Popup/Home/MyProfile/index.tsx b/packages/extension-koni-ui/src/Popup/Home/MyProfile/index.tsx index 44fb232739..7853dc5578 100644 --- a/packages/extension-koni-ui/src/Popup/Home/MyProfile/index.tsx +++ b/packages/extension-koni-ui/src/Popup/Home/MyProfile/index.tsx @@ -96,10 +96,28 @@ const Component = ({ className }: Props): React.ReactElement => { /> {isLinkedMyth ? ( - mythicalWallet?.address && <> - - - + mythicalWallet?.address + ? <> + + + + : <> +
+ + + {t('CONNECT NOW')} + +
+ ) : ( <> diff --git a/packages/extension-koni-ui/src/Popup/Home/index.tsx b/packages/extension-koni-ui/src/Popup/Home/index.tsx index 483da3a855..15a9f50416 100644 --- a/packages/extension-koni-ui/src/Popup/Home/index.tsx +++ b/packages/extension-koni-ui/src/Popup/Home/index.tsx @@ -4,13 +4,20 @@ import { CampaignBanner } from '@subwallet/extension-base/background/KoniTypes'; import { CampaignBannerModal, Layout } from '@subwallet/extension-koni-ui/components'; import { LayoutBaseProps } from '@subwallet/extension-koni-ui/components/Layout/base/Base'; +import { AlertModal, MythicalAlertRewardModal } from '@subwallet/extension-koni-ui/components/Modal'; import { MaintenanceInfo, MetadataHandler } from '@subwallet/extension-koni-ui/connector/booka/metadata'; import { BookaSdk } from '@subwallet/extension-koni-ui/connector/booka/sdk'; +import { Reward, RewardStatus } from '@subwallet/extension-koni-ui/connector/booka/types'; +import { MYTHICAL_ALERT_LINKING_TO_REWARDS_MODAL, MYTHICAL_ALERT_REWARD_MODAL } from '@subwallet/extension-koni-ui/constants'; +import { AuthenticationMythContext } from '@subwallet/extension-koni-ui/contexts/AuthenticationMythProvider'; import { HomeContext } from '@subwallet/extension-koni-ui/contexts/screen/HomeContext'; import { useAccountBalance, useGetBannerByScreen, useGetChainSlugsByAccountType, useTokenGroup } from '@subwallet/extension-koni-ui/hooks'; import { ThemeProps } from '@subwallet/extension-koni-ui/types'; +import { noop } from '@subwallet/extension-koni-ui/utils'; +import { ModalContext } from '@subwallet/react-ui'; import CN from 'classnames'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Outlet } from 'react-router'; import { useNavigate } from 'react-router-dom'; import styled from 'styled-components'; @@ -20,22 +27,86 @@ type Props = ThemeProps; export const GlobalSearchTokenModalId = 'globalSearchToken'; const apiSDK = BookaSdk.instance; const metadataHandler = MetadataHandler.instance; +const alertLinkingToRewardModal = MYTHICAL_ALERT_LINKING_TO_REWARDS_MODAL; +const alertRewardModal = MYTHICAL_ALERT_REWARD_MODAL; function Component ({ className = '' }: Props): React.ReactElement { const chainsByAccountType = useGetChainSlugsByAccountType(); const tokenGroupStructure = useTokenGroup(chainsByAccountType); + const { t } = useTranslation(); const accountBalance = useAccountBalance(tokenGroupStructure.tokenGroupMap); const [containerClass, setContainerClass] = useState(); - + const { activeModal, inactiveModal } = useContext(ModalContext); const banners = useGetBannerByScreen('home'); // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-return const firstBanner = useMemo((): CampaignBanner | undefined => banners[0], [banners]); - + const { checkAlreadyLinked, isLinkedMyth, onLogin } = useContext(AuthenticationMythContext); const [backgroundStyle, setBackgroundStyle] = useState(); + const [rewardsEligible, setRewardsEligible] = useState([]); + const [hasSuccessReward, setHasSuccessReward] = useState(false); + const [hasFullPendingReward, setHasFullPendingReward] = useState(false); const navigate = useNavigate(); + const alertLinkingToRewardModalProps = useMemo(() => { + const totalTokenOfPendingReward = rewardsEligible.reduce((acc, reward) => { + if (reward.status !== RewardStatus.SUCCESS) { + acc += reward.token; + } + + return acc; + }, 0); + + return { + title: t(`you’re eligible for ${totalTokenOfPendingReward} myth`), + content: t(`Link your Mythical account now to receive ${totalTokenOfPendingReward} MYTH. Make sure to connect to NFL Rivals app first and create a wallet address to receive rewards`), + okButton: { + text: 'Link account', + onClick: () => { + apiSDK.updateRewardHistory(true).catch(console.error); + inactiveModal(alertLinkingToRewardModal); + + if (isLinkedMyth) { + navigate('/home/my-profile'); + } else { + onLogin(); + } + } + }, + cancelButton: { + text: 'Cancel', + onClick: noop + }, + onCancel: () => { + inactiveModal(alertLinkingToRewardModal); + apiSDK.updateRewardHistory(true).catch(console.error); + } + }; + }, [inactiveModal, isLinkedMyth, navigate, onLogin, rewardsEligible, t]); + + const handleAlertReward = useCallback(async (rewardList: Reward[]) => { + if (rewardList.length > 0) { + const isHasRewardIsDistributed = rewardList.find((reward) => reward.status === RewardStatus.SUCCESS); + + if (isHasRewardIsDistributed) { + setHasSuccessReward(true); + activeModal(alertRewardModal); + + return; + } + + const isAlreadyLinked = await checkAlreadyLinked(); + + if (isAlreadyLinked) { + return; + } + + setHasFullPendingReward(true); + activeModal(alertLinkingToRewardModal); + } + }, [activeModal, checkAlreadyLinked]); + useEffect(() => { const handleMaintenance = (info: MaintenanceInfo) => { if (info.isMaintenance) { @@ -59,6 +130,16 @@ function Component ({ className = '' }: Props): React.ReactElement { }; }, [navigate]); + useEffect(() => { + apiSDK.getRewardListIsNotChecked().then((res) => { + setRewardsEligible(res); + }).catch(console.error); + }, []); + + useEffect(() => { + handleAlertReward(rewardsEligible).catch(console.error); + }, [handleAlertReward, rewardsEligible]); + return ( <> { {firstBanner && } + {hasSuccessReward && } + {hasFullPendingReward && } ); } diff --git a/packages/extension-koni-ui/src/components/Modal/MythicalReward/MythicalAlertRewardModal.tsx b/packages/extension-koni-ui/src/components/Modal/MythicalReward/MythicalAlertRewardModal.tsx new file mode 100644 index 0000000000..5a6776a597 --- /dev/null +++ b/packages/extension-koni-ui/src/components/Modal/MythicalReward/MythicalAlertRewardModal.tsx @@ -0,0 +1,348 @@ +// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { MythButton } from '@subwallet/extension-koni-ui/components/Mythical'; +import { BookaSdk } from '@subwallet/extension-koni-ui/connector/booka/sdk'; +import { Reward, RewardStatus } from '@subwallet/extension-koni-ui/connector/booka/types'; +import { MYTHICAL_ALERT_REWARD_MODAL } from '@subwallet/extension-koni-ui/constants'; +import useTranslation from '@subwallet/extension-koni-ui/hooks/common/useTranslation'; +import { ThemeProps } from '@subwallet/extension-koni-ui/types'; +import { preloadImages, toDisplayNumber } from '@subwallet/extension-koni-ui/utils'; +import { openInNewTab } from '@subwallet/extension-koni-ui/utils/common/browser'; +import { Logo, ModalContext, SwModal } from '@subwallet/react-ui'; +import CN from 'classnames'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import styled from 'styled-components'; + +type Props = ThemeProps & { + rewardsEligible: Reward[]; +}; + +const MythicalAlertRewardModalId = MYTHICAL_ALERT_REWARD_MODAL; +const apiSDK = BookaSdk.instance; +const giftMythReward = '/images/mythical/gift-myth-reward.png'; +const closeIcon = '/images/mythical/close-button.png'; +const infoIcon = '/images/mythical/info-button.png'; + +function Component ({ className, rewardsEligible }: Props): React.ReactElement { + const { t } = useTranslation(); + const { inactiveModal } = useContext(ModalContext); + const navigate = useNavigate(); + const [totalReward, setTotalReward] = useState(0); + + useEffect(() => { + preloadImages([ + giftMythReward, + closeIcon, + infoIcon, + '/images/mythical/gift-myth-reward-mask-image.png' + ]); + }, []); + + useEffect(() => { + const totalReward = rewardsEligible.reduce((acc, reward) => { + if (reward.status === RewardStatus.SUCCESS) { + acc += reward.token; + } + + return acc; + }, 0); + + setTotalReward(totalReward); + }, [rewardsEligible]); + + const onCancel = useCallback(() => { + apiSDK.updateRewardHistory().catch(console.error); + inactiveModal(MythicalAlertRewardModalId); + }, [inactiveModal]); + + const shareToTwitter = useCallback(() => { + apiSDK.updateRewardHistory().catch(console.error); + inactiveModal(MythicalAlertRewardModalId); + }, [inactiveModal]); + + const goMyProfile = useCallback(() => { + navigate('/home/my-profile'); + apiSDK.updateRewardHistory().catch(console.error); + inactiveModal(MythicalAlertRewardModalId); + }, [inactiveModal, navigate]); + + const openUserGuide = useCallback(() => { + openInNewTab('https://www.mythical.games/mythical-rewards'); + }, []); + + const footerModal = useMemo(() => { + return ( + <> + + {t('VIEW DETAILS')} + + + + {t('SHARE TO TWITTER')} + + + ); + }, [goMyProfile, shareToTwitter, t]); + + return ( + <> + } + footer={footerModal} + id={MythicalAlertRewardModalId} + maskClosable={false} + onCancel={onCancel} + rightIconProps={{ + icon: {'info'}, + onClick: openUserGuide + }} + title={t('YOUR REWARDS')} + > +
+
+ gift +
+ +
+ +
+ {toDisplayNumber(totalReward)}  + {'MYTH'} +
+
+ +
+
+ + ); +} + +const MythicalAlertRewardModal = styled(Component)(({ theme: { extendToken, token } }: Props) => { + return { + '.ant-sw-modal-body': { + margin: 0, + padding: token.paddingXL, + position: 'relative' + }, + + '.ant-sw-modal-body:before': { + content: '""', + display: 'block', + position: 'absolute', + left: '-3px', + top: '-26px', + zIndex: 1, + width: '100%', + height: '100%', + backgroundPosition: 'center center', + backgroundSize: '100% 100%', + backgroundImage: 'url(/images/mythical/gift-myth-reward-mask-image.png)' + }, + + '.ant-sw-modal-title': { + flexDirection: 'row-reverse' + }, + + '.ant-sw-header-center-part.ant-sw-header-center-part': { + marginLeft: 16, + marginRight: 16 + }, + + '.ant-sw-modal-footer': { + display: 'flex', + borderTop: 0, + gap: token.sizeXXS, + paddingBottom: 34 + }, + + '.ant-sw-header-center-part': { + position: 'relative', + marginLeft: 16, + marginRight: 16 + }, + + '.ant-sw-sub-header-title-content.ant-sw-sub-header-title-content.ant-sw-sub-header-title-content': { + color: token.colorWhite, + textAlign: 'center', + fontFamily: extendToken.fontPermanentMarker, + fontWeight: 400, + lineHeight: '40px', + fontSize: 32, + textTransform: 'uppercase', + 'white-space': 'normal' + }, + + '&.ant-sw-modal .ant-sw-modal-body.ant-sw-modal-body': { + paddingTop: token.paddingXL + }, + + '.ant-sw-modal-content.ant-sw-modal-content': { + borderRadius: 0, + paddingTop: 27, + backgroundImage: 'url(/images/mythical/alert-modal-bg.png)', + backgroundSize: '100% 100%', + backgroundRepeat: 'no-repeat', + // filter: 'drop-shadow(4px 6px 0px #000)', + backgroundColor: 'transparent', + boxShadow: 'none', + width: '100%' + }, + + '.__modal-content': { + color: extendToken.mythColorGray1, + fontSize: 16, + fontFamily: extendToken.fontBarlowCondensed, + display: 'flex', + flexDirection: 'column', + gap: token.sizeXL, + lineHeight: '18px', + textAlign: 'center', + letterSpacing: 0.32, + paddingLeft: 22, + paddingRight: 22, + fontWeight: 400, + position: 'relative', + zIndex: 2 + }, + + '.__buttons-container': { + display: 'flex', + gap: 12, + justifyContent: 'space-between', + flex: 1 + }, + + '.__action-button': { + height: 52, + paddingLeft: 12, + paddingRight: 10, + + '.__button-content': { + fontSize: '22px', + lineHeight: '24px', + color: extendToken.mythColorDark + }, + + '.__button-background': { + // filter: 'drop-shadow(1.444px 2.167px 0px #000)' + }, + + '.__button-background:before': { + maskSize: '100% 100%', + maskPosition: 'top left' + }, + + '.__button-inner': { + flexDirection: 'row-reverse' + } + }, + + '.__left-button': { + maxWidth: '45%', + flex: '1 5 auto', + + '.__button-inner': { + gap: 4 + }, + + '.__action-button-icon': { + order: 1, + color: extendToken.mythColorDark, + fontSize: 24 + }, + + '.__button-background:before': { + backgroundColor: token.colorWhite, + maskImage: 'url(/images/mythical/alert-modal-cancel-button.png)' + } + }, + + '.__right-button': { + flex: '1 0 auto', + + '.__button-inner': { + gap: 4 + }, + + '.__button-background:before': { + backgroundColor: token.colorPrimary, + maskImage: 'url(/images/mythical/alert-modal-ok-button.png)' + } + }, + + '.__token-value': { + fontFamily: extendToken.fontBarlowCondensed, + fontSize: '32px', + fontStyle: 'italic', + fontWeight: 500, + lineHeight: '18px', + letterSpacing: '-0.16px', + color: token.colorWhite + }, + + '.__token-symbol': { + fontFamily: extendToken.fontBarlowCondensed, + fontSize: '32px', + fontStyle: 'italic', + fontWeight: 500, + lineHeight: '18px', + color: extendToken.mythColorGray2, + letterSpacing: '-0.16px' + }, + + '.__token-logo': { + marginRight: token.marginXS + }, + + '.__token-value-wrapper': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + + '.__token-content-wrapper': { + justifyContent: 'center', + display: 'inline-flex', + alignItems: 'baseline' + }, + + '.__gift-reward-image': { + width: 213.4, + height: 164.67 + }, + + '.__icon-close, .icon-info': { + width: 30, + height: 32 + } + }; +}); + +export default MythicalAlertRewardModal; diff --git a/packages/extension-koni-ui/src/components/Modal/MythicalReward/index.tsx b/packages/extension-koni-ui/src/components/Modal/MythicalReward/index.tsx new file mode 100644 index 0000000000..c2277e2b77 --- /dev/null +++ b/packages/extension-koni-ui/src/components/Modal/MythicalReward/index.tsx @@ -0,0 +1,4 @@ +// Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +export { default as MythicalAlertRewardModal } from './MythicalAlertRewardModal'; diff --git a/packages/extension-koni-ui/src/components/Modal/index.tsx b/packages/extension-koni-ui/src/components/Modal/index.tsx index d3c19de6f1..926a7ef120 100644 --- a/packages/extension-koni-ui/src/components/Modal/index.tsx +++ b/packages/extension-koni-ui/src/components/Modal/index.tsx @@ -20,3 +20,4 @@ export * from './ReceiveModal'; export * from './Common'; export * from './Announcement'; export * from './Shop'; +export * from './MythicalReward'; diff --git a/packages/extension-koni-ui/src/connector/booka/sdk.ts b/packages/extension-koni-ui/src/connector/booka/sdk.ts index 316dcd3bdd..b0fed55618 100644 --- a/packages/extension-koni-ui/src/connector/booka/sdk.ts +++ b/packages/extension-koni-ui/src/connector/booka/sdk.ts @@ -1,21 +1,21 @@ // Copyright 2019-2022 @subwallet/extension authors & contributors // SPDX-License-Identifier: Apache-2.0 -import {InGameItem} from '@playnation/game-sdk'; -import {GameState} from '@playnation/game-sdk/dist/types'; -import {SWStorage} from '@subwallet/extension-base/storage'; -import {createPromiseHandler, detectTranslate} from '@subwallet/extension-base/utils'; -import {AppMetadata, MetadataHandler} from '@subwallet/extension-koni-ui/connector/booka/metadata'; -import {AccountRankType, Achievement, AirdropCampaign, AirdropEligibility, AirdropRaffle, AirdropRewardHistoryLog, BookaAccount, ClaimableAchievement, EnergyConfig, Game, GameEvent, GameInventoryItem, GameItem, GamePlay, LeaderboardPerson, LeaderboardResult, MythicalWallet, NFLRivalCard, RankInfo, ReferralData, Reward, RewardStatus, RewardType, Task, TaskCategory} from '@subwallet/extension-koni-ui/connector/booka/types'; -import {TelegramConnector} from '@subwallet/extension-koni-ui/connector/telegram'; -import {signRaw} from '@subwallet/extension-koni-ui/messaging'; -import {populateTemplateString} from '@subwallet/extension-koni-ui/utils'; -import {formatDateFully} from '@subwallet/extension-koni-ui/utils/date'; +import { InGameItem } from '@playnation/game-sdk'; +import { GameState } from '@playnation/game-sdk/dist/types'; +import { SWStorage } from '@subwallet/extension-base/storage'; +import { createPromiseHandler, detectTranslate } from '@subwallet/extension-base/utils'; +import { AppMetadata, MetadataHandler } from '@subwallet/extension-koni-ui/connector/booka/metadata'; +import { AccountRankType, Achievement, AirdropCampaign, AirdropEligibility, AirdropRaffle, AirdropRewardHistoryLog, BookaAccount, ClaimableAchievement, EnergyConfig, Game, GameEvent, GameInventoryItem, GameItem, GamePlay, LeaderboardPerson, LeaderboardResult, MythicalWallet, NFLRivalCard, RankInfo, ReferralData, Reward, RewardHistoryStored, RewardStatus, Task, TaskCategory } from '@subwallet/extension-koni-ui/connector/booka/types'; +import { TelegramConnector } from '@subwallet/extension-koni-ui/connector/telegram'; +import { signRaw } from '@subwallet/extension-koni-ui/messaging'; +import { populateTemplateString } from '@subwallet/extension-koni-ui/utils'; +import { formatDateFully } from '@subwallet/extension-koni-ui/utils/date'; import fetch from 'cross-fetch'; -import {deflate, inflate} from 'pako'; -import {BehaviorSubject} from 'rxjs'; +import { deflate, inflate } from 'pako'; +import { BehaviorSubject } from 'rxjs'; -import {stringToU8a, u8aToString} from '@polkadot/util'; +import { stringToU8a, u8aToString } from '@polkadot/util'; export const DEFAULT_INIT_DATA = process.env.DEFAULT_INIT_DATA; export const GAME_API_HOST = process.env.GAME_API_HOST || 'https://game-api.anhmtv.xyz'; @@ -41,6 +41,10 @@ const CACHE_KEYS = { nflRivalCardList: 'data--nfl-rival-cards-cache' }; +const CLOUD_STORAGE_KEYS = { + rewardHistories: 'data--reward-histories-storage' +}; + function parseCache (key: string, useDecompress?: boolean): T | undefined { let data = localStorage.getItem(key); @@ -73,6 +77,18 @@ function decompressData (data: string) { return inflate(compressed, { to: 'string' }); } +function generateCloudKey (keys: number[], type: string) { + return `${type}:${keys.join('-')}`; +} + +export function getRewardStatus (status: RewardStatus) { + if (status === RewardStatus.SUCCESS || status === RewardStatus.EXPIRED) { + return status; + } + + return RewardStatus.PENDING; +} + const metadataHandler = MetadataHandler.instance; export class BookaSdk { @@ -737,7 +753,8 @@ export class BookaSdk { this.fetchLeaderboardConfigList(), this.fetchGameList(), this.fetchGameEventList(), - this.fetchAchievementList() + this.fetchAchievementList(), + this.fetchRewardList() // this.fetchNFLRivalCardList(), // Run in the mythical login to get token // this.fetchTaskCategoryList(), // this.fetchTaskList(), @@ -1049,135 +1066,88 @@ export class BookaSdk { } } + getRewardHistoryList () { + return this.rewardListSubject.value; + } + async fetchRewardList (): Promise { - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const result = [ - { - airdrop_log_id: 1, - airdrop_record_id: 1, - type: RewardType.TOKEN, - campaign_id: 1, - campaign_method: 'raffle', - campaign_name: 'Weekly Leaderboard (W45)', - eligibility_name: 'Top 10 Weekly Leaderboard (W45)', - eligibility_end: 1, - expiryDate: 1735116370, - status: RewardStatus.EXPIRED, - account_id: 1, - eligibility_id: 1, - address: '', - point: 0, - network: 'mythos', - token_slug: 'mythos-NATIVE-MYTH', - decimal: 18, - token: 10 - }, - { - airdrop_log_id: 2, - airdrop_record_id: 2, - type: RewardType.TOKEN, - campaign_id: 2, - campaign_method: 'raffle', - campaign_name: 'Weekly Leaderboard (W46)', - eligibility_name: 'Top 10 Weekly Leaderboard (W46)', - eligibility_end: 2, - expiryDate: 1735116370, - status: RewardStatus.SUCCESS, - account_id: 2, - eligibility_id: 2, - address: '', - point: 0, - network: 'mythos', - token_slug: 'mythos-NATIVE-MYTH', - decimal: 18, - token: 15 - }, - { - airdrop_log_id: 3, - airdrop_record_id: 3, - type: RewardType.TOKEN, - campaign_id: 3, - campaign_method: 'raffle', - campaign_name: 'Weekly Leaderboard (W47)', - eligibility_name: 'Top 10 Weekly Leaderboard (W47)', - eligibility_end: 3, - expiryDate: 1735116370, - status: RewardStatus.PROCESSING, - account_id: 3, - eligibility_id: 3, - address: '', - point: 0, - network: 'mythos', - token_slug: 'mythos-NATIVE-MYTH', - decimal: 18, - token: 18 - }, - { - airdrop_log_id: 4, - airdrop_record_id: 4, - type: RewardType.TOKEN, - campaign_id: 4, - campaign_method: 'raffle', - campaign_name: 'Weekly Leaderboard (W48)', - eligibility_name: 'Top 10 Weekly Leaderboard (W48)', - eligibility_end: 4, - expiryDate: 1735116370, - status: RewardStatus.FAILED, - account_id: 4, - eligibility_id: 4, - address: '', - point: 0, - network: 'mythos', - token_slug: 'mythos-NATIVE-MYTH', - decimal: 18, - token: 14 - }, - { - airdrop_log_id: 6, - airdrop_record_id: 6, - type: RewardType.TOKEN, - campaign_id: 6, - campaign_method: 'raffle', - campaign_name: 'Weekly Leaderboard (W49)', - eligibility_name: 'Top 10 Weekly Leaderboard (W49)', - eligibility_end: 6, - expiryDate: 1735116370, - status: RewardStatus.PENDING, - account_id: 6, - eligibility_id: 6, - address: '', - point: 0, - network: 'mythos', - token_slug: 'mythos-NATIVE-MYTH', - decimal: 18, - token: 12 - }, - { - airdrop_log_id: 7, - airdrop_record_id: 7, - type: RewardType.TOKEN, - campaign_id: 7, - campaign_method: 'raffle', - campaign_name: 'Weekly Leaderboard (50)', - eligibility_name: 'Top 10 Weekly Leaderboard (50)', - eligibility_end: 7, - expiryDate: 1735116370, - status: RewardStatus.IN_REVIEW, - account_id: 7, - eligibility_id: 7, - address: '', - point: 0, - network: 'mythos', - token_slug: 'mythos-NATIVE-MYTH', - decimal: 18, - token: 11 + await this.waitForSync; + const result = await this.postRequest(`${GAME_API_HOST}/api/airdrop/reward_list`, {}); + const listFilter = result.filter(({ account_id: id }) => id === this.account?.info.id); + this.rewardListSubject.next(listFilter); + + return listFilter; + } + + async getRewardListIsNotChecked (): Promise { + const rewardHistory = await storage.getItem(CLOUD_STORAGE_KEYS.rewardHistories); + let rewardHistoryData: Record = {}; + let rewardList = this.rewardListSubject.value; + + if (rewardList.length === 0) { + try { + rewardList = await this.fetchRewardList(); + } catch (e) { + console.error('Error in getRewardListIsNotChecked:', e); + + return []; } - ] + } - this.rewardListSubject.next(result); + try { + if (rewardHistory) { + rewardHistoryData = JSON.parse(rewardHistory) as Record; + } + + const rewardListFiltered = rewardList.filter((item) => { + const cloudKey = generateCloudKey([item.airdrop_log_id, item.account_id, item.campaign_id, item.eligibility_id], item.type); + + if (rewardHistoryData[cloudKey]) { + const prevStatus = getRewardStatus(rewardHistoryData[cloudKey].status); + + if (prevStatus === RewardStatus.PENDING && item.status === RewardStatus.SUCCESS) { + rewardHistoryData[cloudKey].isCheck = false; + } + + rewardHistoryData[cloudKey].status = item.status; + } else { + rewardHistoryData[cloudKey] = { + status: item.status, + isCheck: false + }; + } + + return !rewardHistoryData[cloudKey].isCheck && item.status !== RewardStatus.EXPIRED; + }); + + await storage.setItem(CLOUD_STORAGE_KEYS.rewardHistories, JSON.stringify(rewardHistoryData)); - return result; + return rewardListFiltered; + } catch (e) { + console.error('Error in getRewardListIsNotChecked:', e); + + return []; + } + } + + async updateRewardHistory (isOnlyPending = false) { + const rewardHistoryCloudStored = await storage.getItem(CLOUD_STORAGE_KEYS.rewardHistories); + + if (rewardHistoryCloudStored) { + const rewardHistoryData = JSON.parse(rewardHistoryCloudStored) as Record; + + Object.keys(rewardHistoryData).forEach((key) => { + const status = getRewardStatus(rewardHistoryData[key].status); + + if (isOnlyPending && status === RewardStatus.PENDING) { + rewardHistoryData[key].isCheck = true; + } else if (!isOnlyPending && (status === RewardStatus.SUCCESS || status === RewardStatus.EXPIRED)) { + rewardHistoryData[key].isCheck = true; + } + }); + + await storage.setItem(CLOUD_STORAGE_KEYS.rewardHistories, JSON.stringify(rewardHistoryData)); + } } subscribeRewardList () { diff --git a/packages/extension-koni-ui/src/connector/booka/types.ts b/packages/extension-koni-ui/src/connector/booka/types.ts index ca4476787b..443a42166d 100644 --- a/packages/extension-koni-ui/src/connector/booka/types.ts +++ b/packages/extension-koni-ui/src/connector/booka/types.ts @@ -517,6 +517,7 @@ export interface Reward { campaign_name: string eligibility_name?: string eligibility_end?: number + completeDate?: number expiryDate: number status: RewardStatus account_id: number @@ -529,6 +530,11 @@ export interface Reward { decimal: number } +export interface RewardHistoryStored { + isCheck: boolean, + status: RewardStatus +} + export interface AirdropRaffle { airdropRecordLogId: number, rewardAmount: number, diff --git a/packages/extension-koni-ui/src/constants/modal.ts b/packages/extension-koni-ui/src/constants/modal.ts index c375711baa..e357c271d9 100644 --- a/packages/extension-koni-ui/src/constants/modal.ts +++ b/packages/extension-koni-ui/src/constants/modal.ts @@ -61,4 +61,8 @@ export const SWAP_IDLE_WARNING_MODAL = 'swap-idle-warning-modal'; /* Announcement */ export const CLAIM_DAPP_STAKING_REWARDS_MODAL = 'claim-dapp-staking-rewards-modal'; export const EARNING_MIGRATION_MODAL = 'earning-migration-modal'; +export const MYTHICAL_ALERT_REWARD_MODAL = 'mythical-alert-reward-modal'; + +export const MYTHICAL_ALERT_LINKING_TO_REWARDS_MODAL = 'mythical-alert-linking-to-rewards-modal'; + /* Announcement */ diff --git a/packages/extension-koni-ui/src/contexts/AuthenticationMythProvider.tsx b/packages/extension-koni-ui/src/contexts/AuthenticationMythProvider.tsx index 0d1d11c551..2be5aa996f 100644 --- a/packages/extension-koni-ui/src/contexts/AuthenticationMythProvider.tsx +++ b/packages/extension-koni-ui/src/contexts/AuthenticationMythProvider.tsx @@ -21,6 +21,7 @@ export interface AuthenticationMythContextProps { isLinkedMyth: boolean; mythicalWallet: MythicalWallet; linkMythAccount: (path: string) => Promise; + checkAlreadyLinked: () => Promise; onLogin: VoidFunction; onLogout: () => Promise; } @@ -31,6 +32,7 @@ export const LOCAL_NAVIGATE_AFTER_LOGIN_KEY = 'mythical_navigate_after_login'; export const AuthenticationMythContext = createContext({ isLinkedMyth: false, linkMythAccount: (path: string) => Promise.resolve(), + checkAlreadyLinked: () => Promise.resolve(false), mythicalWallet: { address: '', balanceInMyth: '' } as MythicalWallet, // eslint-disable-next-line @typescript-eslint/no-empty-function onLogin: () => {}, @@ -147,6 +149,22 @@ export const AuthenticationMythProvider = ({ children }: AuthenticationMythProvi return Promise.resolve(); }, [onLogoutMythAccount]); + const checkAlreadyLinked = useCallback(async () => { + try { + const linkedData = await linkSDK.findLink({ + telegram_id: startData.user?.id + }); + + if (linkedData.success && linkedData.data?.link_address) { + return true; + } + + return false; + } catch (e) { + return false; + } + }, []); + useEffect(() => { if (linkData) { setAccount((prev) => { @@ -185,7 +203,6 @@ export const AuthenticationMythProvider = ({ children }: AuthenticationMythProvi }); } } else { - console.log('tokenData', tokenData); onSubmitMythAccount().catch(console.error); } }).catch(console.error); @@ -197,6 +214,7 @@ export const AuthenticationMythProvider = ({ children }: AuthenticationMythProvi isLinkedMyth: isLinked, mythicalWallet, linkMythAccount, + checkAlreadyLinked, onLogin, onLogout }; diff --git a/packages/webapp/public/images/mythical/gift-myth-reward-mask-image.png b/packages/webapp/public/images/mythical/gift-myth-reward-mask-image.png new file mode 100644 index 0000000000..bf306a12af Binary files /dev/null and b/packages/webapp/public/images/mythical/gift-myth-reward-mask-image.png differ diff --git a/packages/webapp/public/images/mythical/gift-myth-reward.png b/packages/webapp/public/images/mythical/gift-myth-reward.png new file mode 100644 index 0000000000..123f95be0e Binary files /dev/null and b/packages/webapp/public/images/mythical/gift-myth-reward.png differ diff --git a/packages/webapp/public/images/mythical/info-button.png b/packages/webapp/public/images/mythical/info-button.png new file mode 100644 index 0000000000..35cc3cb414 Binary files /dev/null and b/packages/webapp/public/images/mythical/info-button.png differ diff --git a/packages/webapp/public/images/mythical/reward-history-background-item.png b/packages/webapp/public/images/mythical/reward-history-background-item.png new file mode 100644 index 0000000000..95bd6ca017 Binary files /dev/null and b/packages/webapp/public/images/mythical/reward-history-background-item.png differ