import { ReactElement, useCallback, useContext, useEffect, useRef, useState } from 'react' import './UpgradeDriveModal.scss' import '../../styles/global.scss' import { CustomDropdown } from '../CustomDropdown/CustomDropdown' import { Button } from '../Button/Button' import { createPortal } from 'react-dom' import DriveIcon from 'remixicon-react/HardDrive2LineIcon' import DatabaseIcon from 'remixicon-react/Database2LineIcon' import WalletIcon from 'remixicon-react/Wallet3LineIcon' import ExternalLinkIcon from 'remixicon-react/ExternalLinkLineIcon' import CalendarIcon from 'remixicon-react/CalendarLineIcon' import { desiredLifetimeOptions } from '../../constants/stamps' import { Context as BeeContext } from '../../../../providers/Bee' import { fromBytesConversion, getExpiryDateByLifetime } from '../../utils/common' import { Context as SettingsContext } from '../../../../providers/Settings' import { Context as FMContext } from '../../../../providers/FileManager' import { BatchId, BeeRequestOptions, BZZ, capacityBreakpoints, Duration, PostageBatch, RedundancyLevel, Size, Utils, } from '@ethersphere/bee-js' import { DriveInfo } from '@solarpunkltd/file-manager-lib' import { getHumanReadableFileSize } from '../../../../utils/file' import { Warning } from '@material-ui/icons' interface UpgradeDriveModalProps { stamp: PostageBatch drive: DriveInfo onCancelClick: () => void containerColor?: string setErrorMessage?: (error: string) => void } const defaultErasureCodeLevel = RedundancyLevel.OFF const encryption_off = 'ENCRYPTION_OFF' export function UpgradeDriveModal({ stamp, onCancelClick, containerColor, drive, setErrorMessage, }: UpgradeDriveModalProps): ReactElement { const { nodeAddresses, walletBalance } = useContext(BeeContext) const { beeApi } = useContext(SettingsContext) const { setShowError } = useContext(FMContext) const [isBalanceSufficient, setIsBalanceSufficient] = useState(true) const [capacity, setCapacity] = useState(Size.fromBytes(0)) const [capacityExtensionCost, setCapacityExtensionCost] = useState('') const [capacityIndex, setCapacityIndex] = useState(0) const [durationExtensionCost, setDurationExtensionCost] = useState('') const [lifetimeIndex, setLifetimeIndex] = useState(0) const [validityEndDate, setValidityEndDate] = useState(new Date()) const [sizeMarks, setSizeMarks] = useState<{ value: number; label: string }[]>([]) const [extensionCost, setExtensionCost] = useState('0') const [isSubmitting, setIsSubmitting] = useState(false) const modalRoot = document.querySelector('.fm-main') || document.body const isMountedRef = useRef(true) useEffect(() => { return () => { isMountedRef.current = false } }, []) const handleCapacityChange = (value: number, index: number) => { setCapacity(Size.fromBytes(value === -1 ? 0 : value)) setCapacityIndex(index) } const handleCostCalculation = useCallback( async ( batchId: BatchId, capacity: Size, duration: Duration, options: BeeRequestOptions | undefined, encryption: boolean, erasureCodeLevel: RedundancyLevel, isCapacityExtensionSet: boolean, isDurationExtensionSet: boolean, ) => { setIsBalanceSufficient(true) let cost: BZZ | undefined try { cost = await beeApi?.getExtensionCost(batchId, capacity, duration, options, encryption, erasureCodeLevel) } catch (e) { setErrorMessage?.('Failed to calculate extension cost') setShowError(true) return } const costText = cost ? cost.toSignificantDigits(2) : '0' if (!isMountedRef.current) return if ((walletBalance && cost && cost.gte(walletBalance.bzzBalance)) || !walletBalance) { setIsBalanceSufficient(false) } const bothExtensions = isCapacityExtensionSet && isDurationExtensionSet const capacityOnly = isCapacityExtensionSet && !isDurationExtensionSet const durationOnly = !isCapacityExtensionSet && isDurationExtensionSet const noExtensions = !isCapacityExtensionSet && !isDurationExtensionSet if (bothExtensions) { setCapacityExtensionCost('') setDurationExtensionCost('') } else if (capacityOnly) { setCapacityExtensionCost(costText) setDurationExtensionCost('0') } else if (durationOnly) { setCapacityExtensionCost('0') setDurationExtensionCost(costText) } else { setCapacityExtensionCost('0') setDurationExtensionCost('0') } setExtensionCost(noExtensions ? '0' : costText) }, [beeApi, walletBalance, setErrorMessage, setShowError], ) useEffect(() => { const fetchSizes = () => { const sizes = Array.from(Utils.getStampEffectiveBytesBreakpoints(false, defaultErasureCodeLevel).values()) const capacityValues = capacityBreakpoints[encryption_off][defaultErasureCodeLevel] const fromIndex = capacityValues.findIndex(item => item.batchDepth === stamp.depth) const newSizes = sizes.slice(fromIndex + 1) const updatedSizes = [ { value: -1, label: 'No additional storage (0 GB)' }, ...newSizes.map(size => ({ value: size, label: getHumanReadableFileSize(size - stamp.size.toBytes()), })), ] setSizeMarks(updatedSizes) } fetchSizes() }, [stamp.depth, stamp.size]) useEffect(() => { const fetchExtensionCost = () => { const isCapacitySet = capacityIndex > 0 const isDurationSet = true const duration = Duration.fromEndDate(validityEndDate) handleCostCalculation( stamp.batchID, capacity, duration, undefined, false, defaultErasureCodeLevel, isCapacitySet, isDurationSet, ) } fetchExtensionCost() }, [capacity, validityEndDate, capacityIndex, handleCostCalculation, lifetimeIndex, stamp.batchID]) useEffect(() => { setValidityEndDate(getExpiryDateByLifetime(lifetimeIndex, stamp.duration.toEndDate())) }, [lifetimeIndex, stamp.duration]) const batchIdStr = stamp.batchID.toString() const shortBatchId = batchIdStr.length > 12 ? `${batchIdStr.slice(0, 4)}...${batchIdStr.slice(-4)}` : batchIdStr return createPortal(