Untitled

 avatar
unknown
plain_text
2 months ago
13 kB
3
Indexable
import { GQLStatusAirdrop, GQLStatusCheckAirdrop } from '@/app-clients/airdrop/schemas';
import { PageContainer } from '@/app-components/layout/PageContainer';
import { ProtocolType } from '@/app-constants';
import {
	compareAddress,
	compareTonAddress,
	get,
	isTonAddress,
	parseErrorMessage,
} from '@/app-helpers';
import { useQueryAirdropDetail, useQueryVerifyClaimed } from '@/app-hooks/airdrop';
import { useI18n, useOnEventCallback, useSearchWithUrlParams } from '@/app-hooks/common';
import { useIsMainWallet, useWalletAddressByProtocol } from '@/app-hooks/wallet';
import {
	useIsAwaitingHash,
	useIsClaimAirdrop,
	useSetClaimAirdrop,
	useSetIsAwaitingHash,
	useSetStatusClaimAll,
	useSetTimeDelayClaim,
	useStatusClaimAll,
	useTimeDelayClaim,
} from '@/app-stores/reward';
import classNames from 'classnames';
import { isNil } from 'lodash-es';
import React, { useState } from 'react';
import { Button, Spinner } from 'react-bootstrap';
import { toast } from 'sonner';
import { CheckEligibility } from '../../components/CheckEligibility';
import { ClaimSuccessScreen } from '../../components/ClaimSuccessScreen';
import { CountdownAirdrop } from '../../components/CountdownAirdrop';
import { InfomationAirdropDetail } from '../../components/InfomationAirdropDetail';
import { useAirdropClaimReward } from '../../hooks';
import { SwitchToMainWalletSheet } from '../../components/SwitchToMainWalletSheet';
import { AppRoutes } from '@/app-routes';

export const NEXT_SHOW_CLAIM_BUTTON_TIMEOUT = 45;

const AirdropDetailScreen: React.FC = () => {
	const [isShow, setIsShow] = useState(false);
	const [transactionHash, setTransactionHash] = useState<string>('');
	const [status, setStatus] = useState<
		'claim' | 'claimed' | 'processing' | 'dismiss' | undefined
	>(undefined);
	const [checkTimeOut, setCheckTimeOut] = useState<boolean>(true);
	const [isShowSwitchWallet, setIsShowSwitchWallet] = useState<boolean>(true);

	const navigate = useNavigate();

	const { t: t_airdrop } = useI18n('airdrop');
	const { searchQuery } = useSearchWithUrlParams({ paramName: 'id' });
	const { onChange: setAmountClaim } = useSearchWithUrlParams({
		paramName: 'amountClaim',
	});

	const isCurrentMainWallet = useIsMainWallet();

	const claimReward = useAirdropClaimReward();

	const isShowClaimScreen = useIsClaimAirdrop();
	const setIsShowClaimScreen = useSetClaimAirdrop();

	const statusClaimAll = useStatusClaimAll();
	const setStatusClaimAll = useSetStatusClaimAll();

	const timeDelay = useTimeDelayClaim();
	const setTimeDelay = useSetTimeDelayClaim();

	const isAwaitingHash = useIsAwaitingHash();
	const setIsAwaitingHash = useSetIsAwaitingHash();

	const [, forceRender] = useReducer((x) => x + 1, 0);

	const {
		data: airdropDetail,
		loading,
		refetch,
	} = useQueryAirdropDetail({ id: searchQuery });

	const {
		isClaim = false,
		tokenCanClaim = 0,
		timeCountDown = 0,
		tokenCanClaimBigNumber = '0',
		signature = '',
		receiver = '',
		claimId = 0,
	} = get(airdropDetail, (d) => d.allocationInfo.pools[0]) || {};

	const isMultiClaim =
		airdropDetail.allocationInfo?.pools && airdropDetail.allocationInfo?.pools.length > 1;

	const isDelayProcessing = useOnEventCallback(() => {
		const delay = timeDelay.find((item) => item.claimId === claimId);

		if (!delay) return false;

		const now = Math.floor(Date.now() / 1000);
		return now < delay.time;
	});

	useEffect(() => {
		if (isDelayProcessing()) {
			const interval = setInterval(() => forceRender(), 3000);

			return () => {
				reCheckClaimed();
				clearInterval(interval);
				refetch();
			};
		}
	}, [isDelayProcessing]);

	const {
		data: isCheckClaimed,
		refetch: reCheckClaimed,
		loading: loadingCheck,
	} = useQueryVerifyClaimed(
		{
			input: {
				chainId: airdropDetail.tokenInfo?.chainId?.toString()!,
				protocol: airdropDetail.tokenInfo?.protocol! as ProtocolType,
				address: receiver!,
				rewardPoolAddress: airdropDetail.contractAddress!,
				claimId: String(claimId),
			},
		},
		{
			fetchPolicy: 'network-only',
			skip:
				!airdropDetail.tokenInfo?.chainId ||
				!airdropDetail.tokenInfo.protocol ||
				!airdropDetail.contractAddress ||
				airdropDetail.allocationInfo?.isCheck === false,
			onError: (error) => {
				console.error('Fail to check claimed:', error);
				setTimeout(() => {
					reCheckClaimed();
				}, 2000);
			},
		},
	);

	useEffect(() => {
		if (isMultiClaim) return;

		const statusClaimItem = statusClaimAll.find(
			(item) => item.claimId === claimId,
		)?.status;

		if (!isNil(statusClaimItem)) {
			setStatus(statusClaimItem);
		} else {
			setStatus('claim');
			setStatusClaimAll('claim', claimId);
		}
	}, [statusClaimAll]);

	useEffect(() => {
		if (loadingCheck || isNil(isCheckClaimed.data)) {
			airdropDetail.allocationInfo?.isCheck === false
				? setCheckTimeOut(false)
				: setCheckTimeOut(true);
		} else {
			setCheckTimeOut(false);

			if (!isNil(status) || !isMultiClaim) {
				if (status === 'processing' && isCheckClaimed.data === true) {
					// CASE: 1
					// Đầu tiên vào thì lấy statusClaimAirdrop nếu là processing và check đã claimed thành công
					// ==> thì set button về claimed
					setStatusClaimAll('claimed', claimId);
				}

				if (status === 'dismiss') {
					// CASE: 2
					// Nếu statusClaimAirdrop là dismiss thì kiểm tra xem đã claimed chưa

					if (isCheckClaimed.data === true) {
						// Trong case này check đã claimed thành công thì set statusClaimAirdrop về claimed
						setStatusClaimAll('claimed', claimId);
					} else {
						// Nếu chưa claim thì set về claim
						setStatusClaimAll('claim', claimId);
					}
				}

				if (isCheckClaimed.data === true) {
					// CASE: 2
					// Trong case này check đã claimed thành công thì set statusClaimAirdrop về claimed
					setStatusClaimAll('claimed', claimId);
				} else {
					// CASE: 3
					// Nếu chưa claim hoặc đang trong tiến trình claim thì rơi vào case này

					if (status !== 'processing' && status !== 'dismiss') {
						// Nếu không phải processing thì set về claim
						setStatusClaimAll('claim', claimId);
					}
				}
			}
		}
	}, [isCheckClaimed, loadingCheck, status]);

	const isCheckEligible = airdropDetail.allocationInfo?.isCheck;
	const statusCheck = airdropDetail.allocationInfo?.statusCheck;

	const isShowCountdown =
		!isMultiClaim &&
		Number(timeCountDown!) > 0 &&
		isCheckEligible &&
		statusCheck === GQLStatusCheckAirdrop.ELIGIBLE;

	const walletAddress = useWalletAddressByProtocol({
		protocol: airdropDetail.tokenInfo?.protocol! as ProtocolType,
	});

	const isWalletAllowedToClaim = () => {
		if (!walletAddress || !receiver) {
			return false;
		}

		if (isTonAddress(receiver)) {
			return compareTonAddress(walletAddress, receiver);
		}

		return compareAddress(walletAddress, receiver);
	};

	const onEnterClaimAirdrop = useOnEventCallback(async () => {
		setStatusClaimAll('processing', claimId);

		try {
			if (isCheckClaimed.data === true) return;
			if (isMultiClaim) return;
			if (isNil(airdropDetail)) return;

			setAmountClaim(tokenCanClaim.toString());

			const result = await claimReward({
				chainId: airdropDetail.tokenInfo?.chainId?.toString()!,
				rewardPoolAddress: airdropDetail.contractAddress!,
				amount: tokenCanClaimBigNumber!,
				to: receiver!,
				signature: signature,
				claimId: BigInt(claimId),
			});

			if (result) {
				setStatusClaimAll('claimed', claimId);
				setIsShowClaimScreen(true);
				setTransactionHash(result);

				const now = Math.floor(Date.now() / 1000);
				setTimeDelay(now + NEXT_SHOW_CLAIM_BUTTON_TIMEOUT, claimId!);
			}
		} catch (error) {
			toast.error(parseErrorMessage(error));
			setStatusClaimAll('claim', claimId);
		} finally {
			reCheckClaimed();
			refetch();
		}
	});

	useEffect(() => {
		const now = Math.floor(Date.now() / 1000);
		if (!loading) {
			if (
				(signature === '' || typeof signature !== 'string') &&
				!isNil(airdropDetail.allocationInfo?.pools) &&
				timeCountDown === 0 &&
				now < airdropDetail.endDatePeriod! &&
				statusCheck === GQLStatusCheckAirdrop.ELIGIBLE
			) {
				setIsAwaitingHash(true);
			} else {
				setIsAwaitingHash(false);
			}
		}
	}, [airdropDetail, loading]);

	const renderFooterButton = useOnEventCallback(
		(
			text: { content: string; textColor?: string },
			variant: string,
			disabled = false,
			disableAndLoading = false,
			onClick?: () => void,
		) => (
			<div className={classNames('viewFooter', loading ? 'd-none' : 'block')}>
				<div className="inner-footer">
					<Button
						disabled={disabled}
						variant={variant ?? 'primary'}
						onClick={onClick}
						className={classNames(text.textColor ? text.textColor : '')}
					>
						{!disableAndLoading ? (
							<Spinner
								size="sm"
								style={{ borderWidth: '1px' }}
								animation="border"
								role="status"
							/>
						) : null}
						{text.content}
					</Button>
				</div>
			</div>
		),
	);

	return (
		<PageContainer
			id="AirdropDetailScreen"
			backable
			transition={false}
			onBack={() => {
				navigate(AppRoutes.Reward.path);
				setIsShowClaimScreen(false);
			}}
		>
			<InfomationAirdropDetail
				loading={loading}
				airdropDetail={airdropDetail}
				isDelayProcessing={isDelayProcessing() && !isMultiClaim}
				refetch={refetch}
			/>

			{/* Countdown */}
			{isShowCountdown && !isMultiClaim && (
				<CountdownAirdrop
					style={{
						paddingLeft: '24px',
						paddingRight: '24px',
						paddingTop: '10px',
						position: 'fixed',
						bottom: '0',
						left: '0',
						right: '0',
					}}
					airdropName={airdropDetail.tokenInfo?.tokenName!}
					airdropEndTime={timeCountDown}
					allocationCanClaim={tokenCanClaim ?? 0}
					isCoundown={true}
					onCountdownEnd={() => refetch && refetch()}
				/>
			)}

			{/* Footer Buttons */}
			{/* Check */}
			{!isDelayProcessing() && isCurrentMainWallet && (
				<>
					{!isCheckEligible &&
						airdropDetail.status !== GQLStatusAirdrop.COMPLETED &&
						renderFooterButton(
							{
								content:
									airdropDetail.status === GQLStatusAirdrop.COMING_SOON
										? t_airdrop('comingSoon')
										: t_airdrop('checkEligibility'),
							},
							'primary',
							airdropDetail.status === GQLStatusAirdrop.COMING_SOON,
							true,
							() => setIsShow(!isShow),
						)}

					{/* Không đủ điều kiện để claim */}
					{isCheckEligible &&
						airdropDetail.status !== GQLStatusAirdrop.COMPLETED &&
						statusCheck === GQLStatusCheckAirdrop.NOT_ELIGIBLE &&
						renderFooterButton(
							{ content: t_airdrop('notEligible'), textColor: 'text-danger' },
							'secondary',
							true,
							true,
						)}

					{/* Claim - Bấm vào để nhận */}
					{timeCountDown <= 0 &&
						!isMultiClaim &&
						isWalletAllowedToClaim() &&
						airdropDetail.status !== GQLStatusAirdrop.COMPLETED &&
						checkTimeOut === false &&
						renderFooterButton(
							{
								content:
									status === 'claimed'
										? t_airdrop('claimed')
										: status === 'processing' || status === 'dismiss'
											? t_airdrop('processing')
											: t_airdrop('claim'),
								textColor: status === 'claimed' ? 'text-success' : '',
							},
							status === 'claimed' ? 'secondary' : 'success',
							status !== 'claim' || isAwaitingHash,
							status !== 'processing' && status !== 'dismiss',
							() => onEnterClaimAirdrop(),
						)}

					{checkTimeOut === true &&
						!isShowCountdown &&
						!isMultiClaim &&
						statusCheck !== GQLStatusCheckAirdrop.NOT_ELIGIBLE &&
						renderFooterButton(
							{
								content: 'Checking',
							},
							'success',
							true,
							false,
							() => onEnterClaimAirdrop(),
						)}
				</>
			)}

			{/* Check Eligibility Modal */}
			{isShow && (
				<CheckEligibility
					show={isShow}
					onDismiss={() => setIsShow(false)}
					airdropItem={airdropDetail}
					refetch={refetch}
				/>
			)}

			{/* Claim Successul */}
			{isShowClaimScreen && !isMultiClaim && (
				<ClaimSuccessScreen
					onDismiss={() => setIsShowClaimScreen(false)}
					network={airdropDetail.tokenInfo?.chainName!}
					amount={tokenCanClaim ?? 0}
					symbol={airdropDetail.tokenInfo?.tokenName!}
					urlGoback={airdropDetail.metaData?.urlCallBack}
					refetch={reCheckClaimed}
					transactionHash={transactionHash}
					airdropDetail={airdropDetail}
					claimId={claimId}
					receiver={receiver}
				/>
			)}

			{!isCurrentMainWallet && (
				<SwitchToMainWalletSheet
					show={isShowSwitchWallet}
					onDismiss={() => setIsShowSwitchWallet(false)}
				/>
			)}
		</PageContainer>
	);
};

export default AirdropDetailScreen;
Editor is loading...
Leave a Comment