import {
  Box,
  Button, Checkbox,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  Divider, FormControlLabel,
  Stack,
  Typography,
} from "@mui/material";
import FormatPrice from "../../Common/FormatPrice";
import {FormattedNumber} from "react-intl";
import React, {useEffect, useState} from "react";
import {useSnackbar} from "notistack";
import {useAuthContext} from "../../AuthProvider/AuthProvider";
import {useApiKeyProvider} from "../../../providers/ApiKeyProvider/ApiKeyProvider";
import {SpotBalanceProps} from "./SpotBalance.props";
import {useBinanceWsProvider} from "../../../providers/BinanceWsProvider/BinanceWsProvider";
import {useSelector} from "react-redux";
import {AppState} from "../../../store/store";
import {selectSpotBinanceSymbolState} from "../../../store/binanceSymbol";
import {convertAssets} from "../../../actions/cicap-diary-account";

const skipAssets = ["BNB", "DNT", "DON", "ETHW"];

const SpotBalance = ({spotBalances}: SpotBalanceProps) => {
  const [spotFinResult] = useState(0);
  const [convertBalances, setConvertBalances] = useState<SpotBalanceElementPriceChecked[]>([]);
  const [showConvertSpotBalanceModal, setShowConvertSpotBalanceModal] = useState(false);
  const {diaryToken} = useAuthContext();
  const {selectedSingleApiKey} = useApiKeyProvider();
  const {enqueueSnackbar} = useSnackbar();
  const {spotSubscribe} = useBinanceWsProvider();
  const symbols = useSelector(selectSpotBinanceSymbolState);


  const handleChangeModal = (asset: string, event: boolean) => {
    setConvertBalances(prevState => prevState?.map(b => b.asset === asset ? ({...b, checked: event}) : b));
  };

  const convertAssetsHandler = () => {
    const assets = convertBalances?.filter(a => a.checked === true).map(a => a.asset);

    if (assets.length === 0) {
      enqueueSnackbar("Выберите монеты для конвертации.", {variant: "error"});

      return;
    }

    if (!diaryToken || null === selectedSingleApiKey) return;

    convertAssets(selectedSingleApiKey, diaryToken, assets)
      .then(data => {
        if (!data) return;

        if (data.result) {
          enqueueSnackbar("Конвертация завершена успешно.", {variant: "success"});
        }

        setShowConvertSpotBalanceModal(false);
      });
  };

  useEffect(() => {
    if (symbols.length === 0) return;

    convertBalances
      .filter(symbol => symbols.includes(`${symbol.asset}USDT`.toUpperCase()))
      .forEach((symbol: SpotBalanceElementPriceChecked) => {
        spotSubscribe(`${symbol.asset}usdt`.toLowerCase());
      });
      
  }, [convertBalances, symbols]);

  useEffect(() => {
    setConvertBalances(
      spotBalances
        .filter(b => !skipAssets.includes(b.asset))
        .map(a => ({...a, checked: false})),
    );
  }, [spotBalances]);

  return <>
    <Box sx={{backgroundColor: "#f5f5f5", borderRadius: "8px", p: 2, mb: 2}}>
      <Typography fontWeight="bold">Спотовый счет</Typography>
      <Typography color={spotFinResult >= 0 ? theme => theme.palette.green.main : "red"} fontSize="xx-large" fontWeight="bold">
        {spotFinResult > 0 ? "+" : ""}{FormatPrice({amount: spotFinResult, currency_iso_code: "USD"})}
      </Typography>
      <Divider sx={{mb: 2}} />
      {
        spotBalances?.map((balance) => {
          return <Stack direction="row" justifyContent="space-between" alignItems="center" key={balance.asset}>
            <Typography>{balance.asset}</Typography>
            <Typography>
              <FormattedNumber
                value={balance.free}
                minimumFractionDigits={2}
                maximumFractionDigits={10}
              />
            </Typography>
          </Stack>;
        })
      }
      <Divider sx={{my: 2}} />
      <Box sx={{display: "flex", justifyContent: "center", fontSize: "small"}} >
        <Button
          variant="contained"
          sx={{fontSize: "small"}}
          onClick={() => {
            setShowConvertSpotBalanceModal(true);
          }}
        >
          Конвертировать в BNB
        </Button>
      </Box>
    </Box>

    <Dialog
      open={showConvertSpotBalanceModal}
      onClose={() => {
        setShowConvertSpotBalanceModal(false);
      }}
      fullWidth
      maxWidth="xs"
    >
      <DialogTitle>Конвертировать в BNB</DialogTitle>
      <DialogContent dividers sx={{px: 0}}>
        {
          convertBalances.length > 0 ? (
            convertBalances?.map((balance, index) =>
              <SpotBalanceComponent
                handleChangeModal={handleChangeModal}
                key={balance.asset}
                balance={balance}
                index={index}
              />
            )
          ) : (
            <Typography>Нет монет для конвертации</Typography>
          )
        }
      </DialogContent>
      <DialogActions>
        <Button
          variant="contained"
          onClick={convertAssetsHandler}
        >
          Конвертировать
        </Button>
      </DialogActions>
    </Dialog>
  </>;
};

export default SpotBalance;

type SpotBalanceElementPriceChecked = {
  asset: string;
  free: number;
  locked: number;
  checked?: boolean;
  lastPrice?: string;
}

type SpotBalanceComponentType = {
  balance: SpotBalanceElementPriceChecked;
  handleChangeModal: (asset: string, event: boolean) => void;
  index: number;
}

const SpotBalanceComponent = ({balance, handleChangeModal, index}:SpotBalanceComponentType) => {
  const price = useSelector((state: AppState) => state.binancePrice.spot[`${balance.asset}usdt`.toLowerCase()]);
console.log(price);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    handleChangeModal(balance.asset, event.target.checked);
  };

  return <Stack
    direction="row"
    justifyContent="space-between"
    alignItems="center"
    sx={{
      backgroundColor: (index - 1) % 2 ? "#f5f5f5" : "#ffffff",
      px: 2,
    }}
  >
    <FormControlLabel
      control={
        <Checkbox
          name="spot-balance-element-checkbox"
          disabled={balance.free === 0}
          checked={balance.checked}
          onChange={handleChange}
        />
      }
      label={balance.asset}
    />
    <Stack direction="column" alignItems="flex-end" justifyContent="center" spacing={0} py={1}>
      <Typography fontSize="small" fontWeight="bold" sx={{lineHeight: 1.2}}>
        <FormattedNumber
          value={balance.free}
          minimumFractionDigits={2}
          maximumFractionDigits={10}
        />
      </Typography>
      <Typography fontSize="small" sx={{lineHeight: 1.2}}>
        {
          price && +price !== 0
            ? <FormatPrice amount={+price * balance.free} currency_iso_code="USD" />
            : <></>
        }
      </Typography>
    </Stack>
  </Stack>;
};
import {BinanceWsProviderProps} from "./BinanceWsProvider.props";
import {ReadyState, useBinancePrice} from "../../hooks/binancePrices";
import {createContext, useContext, useEffect, useState} from "react";

interface BinanceWsProviderContext {
  futuresSubscribe: (symbol: string) => void;
  futuresUnsubscribe: (symbol: string) => void;
  futuresReadyState: number;
  spotSubscribe: (symbol: string) => void;
  spotUnsubscribe: (symbol: string) => void;
  spotReadyState: number;
}

const defaultContextValue: BinanceWsProviderContext = {
  futuresSubscribe: (symbol: string) => {},
  futuresUnsubscribe: (symbol: string) => {},
  futuresReadyState: 0,
  spotSubscribe: (symbol: string) => {},
  spotUnsubscribe: (symbol: string) => {},
  spotReadyState: 0,
};

const Context = createContext(defaultContextValue);

export function useBinanceWsProvider() {
  return useContext(Context);
}

const BinanceWsProvider = ({children}: BinanceWsProviderProps) => {
  const [futuresSymbols, setFuturesSymbols] = useState<Array<string>>([]);
  const [futuresQueue, setFuturesQueue] = useState<Array<string>>([]);
  const [spotSymbols, setSpotSymbols] = useState<Array<string>>([]);
  const [spotQueue, setSpotQueue] = useState<Array<string>>([]);
  const {
    futuresSubscribe,
    futuresUnsubscribe,
    futuresReadyState,
    spotSubscribe,
    spotUnsubscribe,
    spotReadyState,
  } = useBinancePrice();

  const futuresSymbolSubscribe = (symbol: string) => {
    if (futuresSymbols.includes(symbol) || futuresQueue.includes(symbol)) return;

    setFuturesQueue(prev => [...prev, symbol]);
  };

  const futuresSymbolUnsubscribe = (symbol: string) => {
    if (!futuresSymbols.includes(symbol) && !futuresQueue.includes(symbol)) return;

    // TODO: сделать счетчик слушателей, если все отпишутся - отправлять UNSUBSCRIBE
    // futuresUnsubscribe(symbol);
    setFuturesQueue(prev => prev.filter(item => item !== symbol));
  };

  const subscribeFromFuturesQueue = () => {
    futuresQueue.forEach(symbol => {
      futuresSubscribe(symbol);
    });
    setFuturesSymbols([...futuresSymbols, ...futuresQueue]);
    setFuturesQueue([]);
  };

  useEffect(() => {
    if (futuresQueue.length > 0 && futuresReadyState === ReadyState.OPEN) {
      subscribeFromFuturesQueue();
    }
  }, [futuresQueue]);

  useEffect(() => {
    if (futuresReadyState === ReadyState.CLOSED) {
      setFuturesQueue([...futuresSymbols, ...futuresQueue]);
      setFuturesSymbols([]);
    }

    if (futuresQueue.length > 0 && futuresReadyState === ReadyState.OPEN) {
      subscribeFromFuturesQueue();
    }
  }, [futuresReadyState]);

  const spotSymbolSubscribe = (symbol: string) => {
    if (spotSymbols.includes(symbol) || spotQueue.includes(symbol)) return;

    setSpotQueue(prev => [...prev, symbol]);
  };

  const spotSymbolUnsubscribe = (symbol: string) => {
    if (!spotSymbols.includes(symbol) && !spotQueue.includes(symbol)) return;

    // TODO: сделать счетчик слушателей, если все отпишутся - отправлять UNSUBSCRIBE
    // spotUnsubscribe(symbol);
    setSpotQueue(prev => prev.filter(item => item !== symbol));
  };

  const subscribeFromSpotQueue = () => {
    spotQueue.forEach(symbol => {
      spotSubscribe(symbol);
    });
    setSpotSymbols([...spotSymbols, ...spotQueue]);
    setSpotQueue([]);
  };

  useEffect(() => {
    if (spotQueue.length > 0 && spotReadyState === ReadyState.OPEN) {
      subscribeFromSpotQueue();
    }
  }, [spotQueue]);

  useEffect(() => {
    if (spotReadyState === ReadyState.CLOSED) {
      setSpotQueue([...spotSymbols, ...spotQueue]);
      setSpotSymbols([]);
    }

    if (spotQueue.length > 0 && spotReadyState === ReadyState.OPEN) {
      subscribeFromSpotQueue();
    }
  }, [spotReadyState]);

  return <Context.Provider
    value={{
      futuresSubscribe: futuresSymbolSubscribe,
      futuresUnsubscribe: futuresSymbolUnsubscribe,
      futuresReadyState: futuresReadyState,
      spotSubscribe: spotSymbolSubscribe,
      spotUnsubscribe: spotSymbolUnsubscribe,
      spotReadyState: spotReadyState,
    }}
  >
    {children}
  </Context.Provider>;
};

export default BinanceWsProvider;
import {useCallback, useEffect, useReducer, useState} from "react";
import {useDispatch} from "react-redux";
import {setFuturesBinancePriceState, setSpotBinancePriceState} from "../store/binancePriceSlice";

export enum ReadyState {
  UNINSTANTIATED = -1,
  CONNECTING = 0,
  OPEN = 1,
  CLOSING = 2,
  CLOSED = 3,
}

export function useBinancePrice() {
  const stream = "@ticker";
  const dispatch = useDispatch();
  const [futuresWs, setFuturesWs] = useState<WebSocket|null>(null);
  const [futuresReadyState, setFuturesReadyState] = useState(0);
  const [spotWs, setSpotWs] = useState<WebSocket|null>(null);
  const [spotReadyState, setSpotReadyState] = useState(0);

  const connectToWs = () => {
    if ("undefined" === typeof window) {
      setTimeout("connectToWs", 1000);

      return;
    }

    setFuturesWs(new WebSocket("wss://fstream.binance.com/stream"));
    setSpotWs(new WebSocket("wss://stream.binance.com:9443/stream"));
  };

  const futuresSubscribe = useCallback((symbol: string) => {
    if (null === futuresWs || futuresReadyState !== ReadyState.OPEN) return;

    futuresWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "SUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
  }, [futuresReadyState]);

  const futuresUnsubscribe = useCallback((symbol: string) => {
    if (null === futuresWs || futuresReadyState !== ReadyState.OPEN) return;

    futuresWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "UNSUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
  }, [futuresReadyState]);

  useEffect(() => {
    if (null === futuresWs) return;

    futuresWs.onmessage = (message: MessageEvent) => {
      if (!message.data) return;

      const data = JSON.parse(message.data);

      if (!data?.stream) return;

      dispatch(setFuturesBinancePriceState({symbol: data.stream.replace(stream, ""), price: data.data.c}));
    };

    futuresWs.onopen = () => {
      setFuturesReadyState(ReadyState.OPEN);
    };

    futuresWs.onclose = () => {
      setFuturesReadyState(ReadyState.CLOSED);
    };
  }, [futuresWs]);

  const spotSubscribe = useCallback((symbol: string) => {
    if (null === spotWs || spotReadyState !== ReadyState.OPEN) return;

    spotWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "SUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
    // console.log(spotWs);
  }, [spotReadyState]);

  const spotUnsubscribe = useCallback((symbol: string) => {
    if (null === spotWs || spotReadyState !== ReadyState.OPEN) return;

    spotWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "UNSUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
    // console.log(spotWs);
  }, [spotReadyState]);

  useEffect(() => {
    if (null === spotWs) return;

    spotWs.onmessage = (message: MessageEvent) => {
      // console.log("message", message);
      if (!message.data) return;

      const data = JSON.parse(message.data);

      if (!data?.stream) return;

      dispatch(setSpotBinancePriceState({symbol: data.stream.replace(stream, ""), price: data.data.c}));
    };

    spotWs.onopen = () => {
      setSpotReadyState(ReadyState.OPEN);
    };

    spotWs.onclose = () => {
      setSpotReadyState(ReadyState.CLOSED);
    };
  }, [spotWs]);

  useEffect(() => {
    connectToWs();
  }, []);

  return {
    futuresSubscribe,
    futuresUnsubscribe,
    futuresReadyState,
    spotSubscribe,
    spotUnsubscribe,
    spotReadyState,
  };
}

import {createSlice} from "@reduxjs/toolkit";
import {HYDRATE} from "next-redux-wrapper";

export interface BinancePriceSlice {
  futures: {[key: string]: number};
  spot: {[key: string]: number};
}

const initialState: BinancePriceSlice = {
  futures: {},
  spot: {},
};

export const binancePriceSlice = createSlice({
  name: "binancePrice",
  initialState,
  reducers: {
    setFuturesBinancePriceState(state, action) {
      state.futures[action.payload.symbol] = action.payload.price;
    },
    setSpotBinancePriceState(state, action) {
      state.spot[action.payload.symbol] = action.payload.price;
    },
  },
  extraReducers: {
    [HYDRATE]: (state, action) => {
      return {
        ...state,
        ...action.payload,
      };
    },
  },
});

export const {
  setFuturesBinancePriceState,
  setSpotBinancePriceState,
} = binancePriceSlice.actions;

export default binancePriceSlice.reducer;

проанализируй код и функцию  

const SpotBalanceComponent = ({balance, handleChangeModal, index}:SpotBalanceComponentType) => {
  const price = useSelector((state: AppState) => state.binancePrice.spot[`${balance.asset}usdt`.toLowerCase()]);
как работает const price  и почему он всегда undefined?