diff --git a/frontend/pages/dashboard/[dashboardId].js b/frontend/pages/dashboard/[dashboardId].js index 67630496..363f9df4 100644 --- a/frontend/pages/dashboard/[dashboardId].js +++ b/frontend/pages/dashboard/[dashboardId].js @@ -160,6 +160,7 @@ const Analytics = () => { const dashboard = { ...dashboardCache.data.data.resource_data }; dashboard.name = name; updateDashboard.mutate({ id: dashboardCache.data.data.id, dashboard }); + }; const addReportClicked = () => { console.log("click"); }; @@ -218,7 +219,10 @@ const Analytics = () => { /> + + + + {isOpen ? ( + + {pickerItems && + pickerItems.filter((item) => filterFn(item, inputValue)) + .length === 0 && + empyListCTA(inputValue)} + {pickerItems && + pickerItems + .filter((item) => filterFn(item, inputValue)) + .map((item, index) => { + return ( + + {dropdownItem(item)} + + ); + })} + {inputValue === "" && empyListCTA(inputValue)} + + ) : null} + {/* */} + + ); + }} + + ); +}; + +export default AutoCompleter; diff --git a/frontend/src/components/NewDashboardChart.js b/frontend/src/components/NewDashboardChart.js new file mode 100644 index 00000000..0a923c21 --- /dev/null +++ b/frontend/src/components/NewDashboardChart.js @@ -0,0 +1,240 @@ +import React, { useContext, useEffect, useState } from "react"; +import { + chakra, + FormLabel, + Input, + Stack, + InputGroup, + Box, + Button, + Table, + Th, + Td, + Tr, + Thead, + Tbody, + Center, + Checkbox, + CloseButton, + InputRightAddon, + Badge, + InputLeftAddon, + Heading, + Spinner, + Text, + ButtonGroup, +} from "@chakra-ui/react"; +import { CheckCircleIcon } from "@chakra-ui/icons"; +import { useSubscriptions } from "../core/hooks"; +import Downshift from "downshift"; +import color from "color"; +import OverlayContext from "../core/providers/OverlayProvider/context"; +import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants"; +import UIContext from "../core/providers/UIProvider/context"; +import SuggestABI from "./SuggestABI"; +import { v4 } from "uuid"; +import AutoCompleter from "./AutoCompleter"; +import { CHART_METRICS } from "../core/constants"; + +const NewDashboardChart = (props) => { + const [metric, setMetric] = useState(); + const ui = useContext(UIContext); + const overlay = useContext(OverlayContext); + const [newChartForm, setNewChartForm] = useState([ + { + subscription: undefined, //subscription + type: undefined, // generic | events | transactions + name: undefined, // transactions_in | transactions_out | value_in | value_out | balance | abi.events.some() | abi.methods.some() + }, + ]); + + const [pickerItems, setPickerItems] = useState(); + + useEffect(() => { + if (!subscriptionsCache.isLoading) { + const massaged = subscriptionsCache.data?.subscriptions.map((item) => { + return { value: item.address, ...item }; + }); + setPickerItems(massaged); + } + }, [subscriptionsCache]); + + const { subscriptionsCache } = useSubscriptions(); + + if (subscriptionsCache.isLoading) return ; + console.log("newChartForm:", newChartForm); + + const filterFn = (item, inputValue) => + (item.subscription_type_id === "ethereum_blockchain" || + item.subscription_type_id === "polygon_blockchain") && + (!inputValue || + item.address.toUpperCase().includes(inputValue.toUpperCase()) || + item.label.toUpperCase().includes(inputValue.toUpperCase())); + + return ( + <> + + {newChartForm.map((subscibedItem, idx) => { + const onAbiSuggestionSelect = ({ type, value }) => { + setNewChartForm((currentValue) => { + const newValue = [...currentValue]; + newValue[idx].type = type; + newValue[idx].name = value; + return newValue; + }); + }; + const onMetricSelect = ({ type, value }) => { + setNewChartForm((currentValue) => { + const newValue = [...currentValue]; + newValue[idx].type = type; + newValue[idx].name = ""; + return newValue; + }); + }; + return ( + + Subscription: + item.label} + onSelect={(selectedItem) => + setNewChartForm((currentValue) => { + const newValue = [...currentValue]; + newValue[idx].subscription = selectedItem; + return newValue; + }) + } + getLeftAddonColor={(item) => item?.color ?? "inherit"} + getLabelColor={(item) => + item?.color ? color(item.color) : undefined + } + placeholder="Select subcription" + getDefaultValue={(item) => (item?.label ? item.label : "")} + filterFn={filterFn} + empyListCTA={(inputValue) => ( + + )} + dropdownItem={(item) => { + console.log("dropdownItem,", item); + const badgeColor = color(`${item.color}`); + return ( + <> + + {item.label} + + + ABI + + + {item.address} + + + ); + }} + /> + {subscibedItem?.subscription?.id && ( + + Metric: + + + + + + + + )} + + ); + })} + + + ); +}; + +const ChakraNewDashboardChart = chakra(NewDashboardChart); + +export default ChakraNewDashboardChart; diff --git a/frontend/src/components/NewDashboardElement.js b/frontend/src/components/NewDashboardElement.js deleted file mode 100644 index 30fdfd67..00000000 --- a/frontend/src/components/NewDashboardElement.js +++ /dev/null @@ -1,122 +0,0 @@ -import React, { useContext, useEffect } from "react"; -import { - chakra, - FormLabel, - Input, - Stack, - InputGroup, - Box, - Button, - Table, - Th, - Td, - Tr, - Thead, - Tbody, - Center, - Checkbox, - CloseButton, - InputRightAddon, - Badge, - InputLeftAddon, -} from "@chakra-ui/react"; -import { CheckCircleIcon } from "@chakra-ui/icons"; -import { useStorage, useSubscriptions } from "../core/hooks"; -import Downshift from "downshift"; -import color from "color"; -import OverlayContext from "../core/providers/OverlayProvider/context"; -import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants"; -import UIContext from "../core/providers/UIProvider/context"; - -const NewDashboardElement = (props) => { - const ui = useContext(UIContext); - const overlay = useContext(OverlayContext); - const [newDashboardForm, setNewDashboardForm] = useStorage( - sessionStorage, - "new_dashboard", - { - name: "", - subscriptions: [ - { - label: "", - abi: false, - subscription_id: null, - isMethods: false, - isEvents: false, - generic: { - transactions: { - in: false, - out: false, - }, - value: { - in: false, - out: false, - balance: false, - }, - }, - }, - ], - } - ); - - const subscriptions = useSubscriptions(); - - const [pickerItems, setPickerItems] = React.useState( - subscriptions.subscriptionsCache.data?.subscriptions - ); - - useEffect(() => { - newDashboardForm.subscriptions.forEach((element, idx) => { - const subscription = - subscriptions.subscriptionsCache.data?.subscriptions.find( - (subscription_item) => - element.subscription_id === subscription_item.id - ); - - if ( - element.subscription_id && - subscription && - newDashboardForm.subscriptions[idx].abi !== subscription?.abi - ) { - const newestDashboardForm = { ...newDashboardForm }; - newestDashboardForm.subscriptions[idx].abi = subscription.abi; - setNewDashboardForm(newestDashboardForm); - } - }); - }, [ - subscriptions.subscriptionsCache.data, - newDashboardForm, - setNewDashboardForm, - ]); - - useEffect(() => { - if (!subscriptions.subscriptionsCache.isLoading) { - const massaged = subscriptions.subscriptionsCache.data?.subscriptions.map( - (item) => { - return { value: item.address, ...item }; - } - ); - setPickerItems(massaged); - } - }, [ - subscriptions.subscriptionsCache.data, - subscriptions.subscriptionsCache.isLoading, - ]); - - const filterFn = (item, inputValue) => - (item.subscription_type_id === "ethereum_blockchain" || - item.subscription_type_id === "polygon_blockchain") && - (!inputValue || - item.address.toUpperCase().includes(inputValue.toUpperCase()) || - item.label.toUpperCase().includes(inputValue.toUpperCase())); - - return ( - <> - - - ); -}; - -const ChakraNewDashboard = chakra(NewDashboardElement); - -export default ChakraNewDashboard; diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.js index 0213a1f5..52ed7e7f 100644 --- a/frontend/src/components/Sidebar.js +++ b/frontend/src/components/Sidebar.js @@ -147,7 +147,7 @@ const Sidebar = () => { colorScheme="orange" size="sm" onClick={() => - overlay.toggleDrawer(DRAWER_TYPES.NEW_DASHBOARD) + overlay.toggleDrawer({ type: DRAWER_TYPES.NEW_DASHBOARD }) } // w="100%" // borderRadius={0} diff --git a/frontend/src/components/SuggestABI.js b/frontend/src/components/SuggestABI.js new file mode 100644 index 00000000..48683fdf --- /dev/null +++ b/frontend/src/components/SuggestABI.js @@ -0,0 +1,207 @@ +import React, { useContext, useEffect, useState } from "react"; +import { + chakra, + FormLabel, + Input, + Stack, + InputGroup, + Box, + Button, + Table, + Th, + Td, + Tr, + Thead, + Tbody, + Center, + Checkbox, + CloseButton, + InputRightAddon, + Badge, + InputLeftAddon, + Spinner, + Heading, + Menu, + MenuButton, + MenuList, + MenuItem, + MenuGroup, + MenuDivider, + ButtonGroup, + Text, +} from "@chakra-ui/react"; +import { CheckCircleIcon } from "@chakra-ui/icons"; +import { useSubscription, usePresignedURL } from "../core/hooks"; +import Downshift from "downshift"; +import color from "color"; +import OverlayContext from "../core/providers/OverlayProvider/context"; +import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants"; +import UIContext from "../core/providers/UIProvider/context"; +import { v4 } from "uuid"; +import AutoCompleter from "./AutoCompleter"; +import { CHART_METRICS } from "../core/constants"; + +const SuggestABI = ({ subscriptionId, onSelect, stateContainer }) => { + const ui = useContext(UIContext); + const overlay = useContext(OverlayContext); + + const { subscriptionLinksCache } = useSubscription({ + id: subscriptionId, + }); + + const { data, isLoading } = usePresignedURL({ + url: subscriptionLinksCache?.data?.data?.url, + isEnabled: true, + id: subscriptionId, + cacheType: "abi", + requestNewURLCallback: subscriptionLinksCache.refetch, + }); + + const [pickerItems, setPickerItems] = React.useState(); + + // useEffect(() => { + // newDashboardForm.subscriptions.forEach((element, idx) => { + // const subscription = + // subscriptions.subscriptionsCache.data?.subscriptions.find( + // (subscription_item) => + // element.subscription_id === subscription_item.id + // ); + + // if ( + // element.subscription_id && + // subscription && + // newDashboardForm.subscriptions[idx].abi !== subscription?.abi + // ) { + // const newestDashboardForm = { ...newDashboardForm }; + // newestDashboardForm.subscriptions[idx].abi = subscription.abi; + // setNewDashboardForm(newestDashboardForm); + // } + // }); + // }, [ + // subscriptions.subscriptionsCache.data, + // newDashboardForm, + // setNewDashboardForm, + // ]); + + useEffect(() => { + if (!isLoading) { + const massaged = data?.map((item) => { + return { value: item.name ?? item.type, ...item }; + }); + setPickerItems( + massaged?.filter((item) => item.type === stateContainer.type) + ); + } + }, [data, isLoading, stateContainer]); + + const filterFn = (item, inputValue) => + !inputValue || item.value.toUpperCase().includes(inputValue.toUpperCase()); + + if (isLoading || !pickerItems) return ; + + console.log("pickerItems", pickerItems); + + return ( + <> + + {stateContainer.type === CHART_METRICS.EVENTS && ( + + Event: + item.type === "event")} + initialSelectedItem={stateContainer} + itemToString={(item) => item.name} + onSelect={onSelect} + getLeftAddonColor={(item) => item?.color ?? "inherit"} + getLabelColor={() => undefined} + placeholder="Select metric" + getDefaultValue={(item) => (item?.value ? item.value : "")} + filterFn={filterFn} + empyListCTA={() => ( + No Events found {`>_<`} + )} + dropdownItem={(item) => { + console.log("dropdownItem,", item); + return ( + <> + {item.value} + + {item.type} + + + ); + }} + /> + + )} + {stateContainer.type === CHART_METRICS.FUNCTIONS && ( + + Function: + item.type === "function" + )} + initialSelectedItem={stateContainer} + itemToString={(item) => item.name} + onSelect={onSelect} + getLeftAddonColor={(item) => item?.color ?? "inherit"} + getLabelColor={() => undefined} + placeholder="Select metric" + getDefaultValue={(item) => (item?.value ? item.value : "")} + filterFn={filterFn} + empyListCTA={() => ( + No Functions found {`>_<`} + )} + dropdownItem={(item) => { + console.log("dropdownItem,", item); + return ( + <> + {item.value} + + {item.type} + + + ); + }} + /> + + )} + {stateContainer.type === CHART_METRICS.GENERIC && ( + + Balance: + item.name} + onSelect={onSelect} + getLeftAddonColor={(item) => item?.color ?? "inherit"} + getLabelColor={() => undefined} + placeholder="Select metric" + getDefaultValue={(item) => (item?.value ? item.value : "")} + filterFn={filterFn} + empyListCTA={() => ""} + dropdownItem={(item) => { + console.log("dropdownItem,", item); + return ( + <> + {item.value} + + {item.type} + + + ); + }} + /> + + )} + + + ); +}; + +const ChakraSuggestABI = chakra(SuggestABI); + +export default ChakraSuggestABI; diff --git a/frontend/src/core/constants.js b/frontend/src/core/constants.js index d83419c2..e8dd161a 100644 --- a/frontend/src/core/constants.js +++ b/frontend/src/core/constants.js @@ -71,3 +71,9 @@ export const TIME_RANGE_SECONDS = { week: 86400 * 7, month: 86400 * 28, }; + +export const CHART_METRICS = { + GENERIC: "genetic", + FUNCTIONS: "function", + EVENTS: "event", +}; diff --git a/frontend/src/core/hooks/index.js b/frontend/src/core/hooks/index.js index f5fcb76a..ca8e591f 100644 --- a/frontend/src/core/hooks/index.js +++ b/frontend/src/core/hooks/index.js @@ -3,6 +3,7 @@ export { default as useAuthResultHandler } from "./useAuthResultHandler"; export { default as useChangePassword } from "./useChangePassword"; export { default as useClientID } from "./useClientID"; export { default as useCodeVerification } from "./useCodeVerification"; +export { default as useDashboard } from "./useDashboard"; export { default as useForgotPassword } from "./useForgotPassword"; export { default as useInviteAccept } from "./useInviteAccept"; export { default as useJournalEntry } from "./useJournalEntry"; @@ -18,8 +19,10 @@ export { default as useStatus } from "./useStatus"; export { default as useStorage } from "./useStorage"; export { default as useStream } from "./useStream"; export { default as useStripe } from "./useStripe"; +export { default as useSubscription } from "./useSubscription"; export { default as useSubscriptions } from "./useSubscriptions"; export { default as useToast } from "./useToast"; export { default as useTokens } from "./useTokens"; export { default as useTxInfo } from "./useTxInfo"; export { default as useUser } from "./useUser"; +export { default as useWindowSize } from "./useWindowSize"; diff --git a/frontend/src/core/hooks/usePresignedURL.js b/frontend/src/core/hooks/usePresignedURL.js index 66dfc7f0..e6673ae3 100644 --- a/frontend/src/core/hooks/usePresignedURL.js +++ b/frontend/src/core/hooks/usePresignedURL.js @@ -11,6 +11,7 @@ const usePresignedURL = ({ requestNewURLCallback, isEnabled, }) => { + console.log("usePresignedURL:", url, id, isEnabled); const toast = useToast(); const getFromPresignedURL = async () => { @@ -20,15 +21,17 @@ const usePresignedURL = ({ // url: `https://example.com/s3`, method: "GET", }); + console.log("getFromPresignedURL", response); return response.data; }; const { data, isLoading, error, refetch } = useQuery( - [`${cacheType}`, { id }], + ["presignedURL", cacheType, id], getFromPresignedURL, { ...queryCacheProps, enabled: isEnabled && url ? true : false, + keepPreviousData: true, onError: (e) => { if ( e?.response?.data?.includes("Request has expired") || @@ -44,9 +47,10 @@ const usePresignedURL = ({ useEffect(() => { if (url && isEnabled) { + console.log("useeffect:", url, isEnabled); refetch(); } - }, [url, refetch, isEnabled]); + }, [url, isEnabled, refetch]); return { data, diff --git a/frontend/src/core/hooks/useSubscription.js b/frontend/src/core/hooks/useSubscription.js index 3f368325..faad4cc6 100644 --- a/frontend/src/core/hooks/useSubscription.js +++ b/frontend/src/core/hooks/useSubscription.js @@ -9,9 +9,10 @@ const useSubscription = ({ id }) => { const toast = useToast(); const user = useContext(UserContext); + console.log("useSubscription:", id, user); const subscriptionLinksCache = useQuery( - ["dashboardLinks", { id }], - SubscriptionsService.getSubscriptionABI, + ["dashboardLinks", id], + SubscriptionsService.getSubscriptionABI(id), { ...queryCacheProps, onError: (error) => { @@ -20,6 +21,7 @@ const useSubscription = ({ id }) => { enabled: !!user && !!id, } ); + return { subscriptionLinksCache }; }; export default useSubscription; diff --git a/frontend/src/core/hooks/useToast.js b/frontend/src/core/hooks/useToast.js index 6a5a8e7a..81a80568 100644 --- a/frontend/src/core/hooks/useToast.js +++ b/frontend/src/core/hooks/useToast.js @@ -24,15 +24,17 @@ const useToast = () => { detail: userMessage, }); } - - chakraToast({ - id: `${userTitle}${userMessage}${type}`, - position: "bottom", - title: userTitle, - description: userMessage, - status: type, - // duration: 3000, - }); + const id = `${userTitle}${userMessage}${type}`; + if (!chakraToast.isActive(id)) { + chakraToast({ + id: id, + position: "bottom", + title: userTitle, + description: userMessage, + status: type, + // duration: 3000, + }); + } }, [chakraToast] ); diff --git a/frontend/src/core/providers/OverlayProvider/index.js b/frontend/src/core/providers/OverlayProvider/index.js index c4066700..59833f03 100644 --- a/frontend/src/core/providers/OverlayProvider/index.js +++ b/frontend/src/core/providers/OverlayProvider/index.js @@ -36,7 +36,7 @@ import UserContext from "../UserProvider/context"; import UIContext from "../UIProvider/context"; import useDashboard from "../../hooks/useDashboard"; import SignUp from "../../../components/SignUp"; -import NewDashboardElement from "../../../components/NewDashboardElement"; +import NewDashboardElement from "../../../components/NewDashboardChart"; const ForgotPassword = React.lazy(() => import("../../../components/ForgotPassword") ); @@ -59,7 +59,10 @@ const OverlayProvider = ({ children }) => { type: MODAL_TYPES.OFF, props: undefined, }); - const [drawer, toggleDrawer] = useState(DRAWER_TYPES.OFF); + const [drawer, toggleDrawer] = useState({ + type: DRAWER_TYPES.OFF, + props: undefined, + }); const [alertCallback, setAlertCallback] = useState(null); const drawerDisclosure = useDisclosure(); const modalDisclosure = useDisclosure(); @@ -74,9 +77,9 @@ const OverlayProvider = ({ children }) => { }, [modal.type, modalDisclosure]); useLayoutEffect(() => { - if (drawer === DRAWER_TYPES.OFF && drawerDisclosure.isOpen) { + if (drawer.type === DRAWER_TYPES.OFF && drawerDisclosure.isOpen) { drawerDisclosure.onClose(); - } else if (drawer !== DRAWER_TYPES.OFF && !drawerDisclosure.isOpen) { + } else if (drawer.type !== DRAWER_TYPES.OFF && !drawerDisclosure.isOpen) { drawerDisclosure.onOpen(); } }, [drawer, drawerDisclosure]); @@ -92,7 +95,7 @@ const OverlayProvider = ({ children }) => { }; console.assert( - Object.values(DRAWER_TYPES).some((element) => element === drawer) + Object.values(DRAWER_TYPES).some((element) => element === drawer.type) ); console.assert( Object.values(MODAL_TYPES).some((element) => element === modal.type) @@ -118,7 +121,7 @@ const OverlayProvider = ({ children }) => { }, [ui.isAppView, ui.isAppReady, user, ui.isLoggingOut, modal.type]); const finishNewDashboard = () => { - toggleDrawer(DRAWER_TYPES.OFF); + toggleDrawer({ type: DRAWER_TYPES.OFF, props: undefined }); window.sessionStorage.removeItem("new_dashboard"); }; @@ -265,19 +268,23 @@ const OverlayProvider = ({ children }) => { - {DRAWER_TYPES.NEW_DASHBOARD && "New dashboard"} - {DRAWER_TYPES.NEW_DASHBOARD_ITEM && "New dashboard element"} + {drawer.type === DRAWER_TYPES.NEW_DASHBOARD && "New dashboard"} + {drawer.type === DRAWER_TYPES.NEW_DASHBOARD_ITEM && + "New dashboard element"} - {DRAWER_TYPES.NEW_DASHBOARD && ( + {drawer.type === DRAWER_TYPES.NEW_DASHBOARD && ( }> - + )} - {DRAWER_TYPES.NEW_DASHBOARD_ITEM && ( + {drawer.type === DRAWER_TYPES.NEW_DASHBOARD_ITEM && ( }> - + )} @@ -285,7 +292,17 @@ const OverlayProvider = ({ children }) => { @@ -293,8 +310,10 @@ const OverlayProvider = ({ children }) => { colorScheme="blue" isLoading={createDashboard.isLoading} onClick={() => { - DRAWER_TYPES.NEW_DASHBOARD && submitNewDashboard(); - DRAWER_TYPES.NEW_DASHBOARD_ITEM && submitNewDashboardItem(); + drawer.type === DRAWER_TYPES.NEW_DASHBOARD && + submitNewDashboard(); + drawer.type === DRAWER_TYPES.NEW_DASHBOARD_ITEM && + submitNewDashboardItem(); }} > Submit diff --git a/frontend/src/core/services/subscriptions.service.js b/frontend/src/core/services/subscriptions.service.js index 842b1768..299a1367 100644 --- a/frontend/src/core/services/subscriptions.service.js +++ b/frontend/src/core/services/subscriptions.service.js @@ -68,7 +68,8 @@ export const deleteSubscription = () => (id) => { }); }; -export const getSubscriptionABI = () => (id) => { +export const getSubscriptionABI = (id) => () => { + console.log("service", id); return http({ method: "GET", url: `${API}/subscriptions/${id}/abi`,