better newSubscripton, pass args from drawer to modal

post-ABI
Tim Pechersky 2021-11-04 14:37:06 +01:00
rodzic 35ed79a7ac
commit e3cde8a7cb
4 zmienionych plików z 464 dodań i 161 usunięć

Wyświetl plik

@ -40,7 +40,8 @@
"react-xarrows": "^2.0.2",
"showdown": "^1.9.1",
"showdown-highlight": "^2.1.8",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"web3": "^1.6.0"
},
"devDependencies": {
"@babel/core": "^7.14.3",

Wyświetl plik

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useContext, useEffect } from "react";
import {
chakra,
FormLabel,
@ -24,8 +24,13 @@ 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 NewDashboard = (props) => {
const ui = useContext(UIContext);
const overlay = useContext(OverlayContext);
const [newDashboardForm, setNewDashboardForm] = useStorage(
sessionStorage,
"new_dashboard",
@ -36,6 +41,7 @@ const NewDashboard = (props) => {
label: "",
id: null,
isMethods: false,
isEvents: false,
},
],
}
@ -104,7 +110,8 @@ const NewDashboard = (props) => {
<Tr>
<Th>Address</Th>
<Th w="90px">ABI State</Th>
<Th w="90px">Track ABI</Th>
<Th w="90px">Methods</Th>
<Th w="90px">Events</Th>
<Th w="60px"></Th>
</Tr>
</Thead>
@ -155,7 +162,9 @@ const NewDashboard = (props) => {
<InputLeftAddon
isTruncated
maxW="60px"
fontSize="sm"
fontSize={
ui.isMobileView ? "xs" : "sm"
}
bgColor={
subscibedItem?.color ?? "gray.100"
}
@ -178,9 +187,9 @@ const NewDashboard = (props) => {
placeholder="Subscription to use in dashboard"
isTruncated
fontSize="sm"
defaultValue={
subscibedItem?.label ?? "yoyoy"
}
// defaultValue={
// subscibedItem?.label ?? "yoyoy"
// }
{...getInputProps({
defaultValue:
subscibedItem?.label ?? "iha",
@ -233,9 +242,20 @@ const NewDashboard = (props) => {
colorScheme="orange"
variant="outline"
size="sm"
fontSize="sm"
w="100%"
m={0}
isTruncated
onClick={() => {
overlay.toggleModal(
MODAL_TYPES.NEW_SUBSCRIPTON
);
overlay.setModalProps({
initialValue: inputValue,
});
}}
>
Set new subscription to:{" "}
{inputValue}{" "}
Subscribe to: {inputValue}{" "}
</Button>
)}
{pickerItems &&
@ -306,6 +326,23 @@ const NewDashboard = (props) => {
</Stack>
);
})}
{inputValue === "" && (
<Button
colorScheme="orange"
variant="outline"
w="100%"
m={0}
size="sm"
onClick={() =>
overlay.toggleModal(
MODAL_TYPES.NEW_SUBSCRIPTON
)
}
>
New subscription
{inputValue}{" "}
</Button>
)}
</Stack>
) : null}
{/* </Menu> */}
@ -355,6 +392,9 @@ const NewDashboard = (props) => {
size="xs"
py={2}
disabled={!subscibedItem.address}
onClick={() =>
overlay.toggleModal(MODAL_TYPES.UPLOAD_ABI)
}
>
Upload
</Button>
@ -369,6 +409,14 @@ const NewDashboard = (props) => {
isChecked={subscibedItem.isMethods}
></Checkbox>
</Td>
<Td w="60px">
<Checkbox
isDisabled={
!subscibedItem.address || !subscibedItem.hasABI
}
isChecked={subscibedItem.isEvents}
></Checkbox>
</Td>
<Td>
{idx > 0 && (

Wyświetl plik

@ -1,10 +1,9 @@
import React, { useState, useEffect, useCallback } from "react";
import React, { useState, useEffect, useCallback, useRef } from "react";
import { useSubscriptions } from "../core/hooks";
import {
Input,
Stack,
Text,
HStack,
useRadioGroup,
FormControl,
FormErrorMessage,
@ -13,14 +12,26 @@ import {
IconButton,
ButtonGroup,
Flex,
Box,
InputGroup,
Image,
InputLeftAddon,
InputRightAddon,
chakra,
Tooltip,
VStack,
FormLabel,
useToast,
} from "@chakra-ui/react";
import RadioCard from "./RadioCard";
// import { useForm } from "react-hook-form";
import { CirclePicker } from "react-color";
import { BiRefresh } from "react-icons/bi";
import { GithubPicker } from "react-color";
import { makeColor } from "../core/utils/makeColor";
import { useForm } from "react-hook-form";
import Web3 from "web3";
import Downshift from "downshift";
import { QuestionIcon } from "@chakra-ui/icons";
const _NewSubscription = ({
isFreeOption,
onClose,
@ -28,15 +39,32 @@ const _NewSubscription = ({
initialAddress,
initialType,
isModal,
initialValue,
}) => {
const [color, setColor] = useState(makeColor());
const { handleSubmit, errors, register } = useForm({});
const [address, setAddress] = useState();
const [label, setLabel] = useState();
const [type, setType] = useState();
const { typesCache, createSubscription } = useSubscriptions();
const [radioState, setRadioState] = useState(
initialType ?? "ethereum_blockchain"
);
const [pickerItems, setPickerItems] = useState();
const toast = useToast();
useEffect(() => {
if (!typesCache.isLoading) {
const massaged = typesCache.data?.map((item) => {
return { value: item.name, ...item };
});
setPickerItems(massaged);
}
}, [typesCache.data, typesCache.isLoading]);
const mapper = {
"tag:erc721": "NFTs",
"input:address": "Address",
@ -57,6 +85,16 @@ const _NewSubscription = ({
onChange: setsubscriptionAdressFormatRadio,
});
useEffect(() => {
if (initialValue && initialValue !== "") {
console.log("iv:", initialValue, Web3.utils.isAddress(initialValue));
if (Web3.utils.isAddress(initialValue)) {
setAddress(initialValue);
} else {
setLabel(initialValue);
}
}
}, [initialValue]);
useEffect(() => {
if (setIsLoading) {
setIsLoading(createSubscription.isLoading);
@ -71,7 +109,8 @@ const _NewSubscription = ({
const createSubscriptionWrapper = useCallback(
(props) => {
props.label = "Address";
props.label = props.label ?? "Address";
props.type = type.id;
if (
subscriptionAdressFormatRadio.startsWith("tag") &&
radioState != "ethereum_whalewatch"
@ -79,21 +118,36 @@ const _NewSubscription = ({
props.address = subscriptionAdressFormatRadio;
props.label = "Tag";
}
if (!props.address) {
props.address = "0x000000000000000000000000000000000000dead";
}
createSubscription.mutate({
...props,
color: color,
type: isFreeOption ? "ethereum_blockchain" : radioState,
});
if (Web3.utils.isAddress(props.address)) {
createSubscription.mutate({
...props,
color: color,
});
} else {
if (!toast.isActive("address_toast"))
toast({
title: "Is not valid Ethereum Address",
status: "error",
position: "bottom",
id: "address_toast",
description: "Web3.utils.isAddress returned false",
});
}
},
[
toast,
createSubscription,
isFreeOption,
color,
radioState,
subscriptionAdressFormatRadio,
type,
]
);
const downshiftRef = useRef(null);
if (typesCache.isLoading) return <Spinner />;
@ -111,11 +165,21 @@ const _NewSubscription = ({
if (!errors) return "";
console.log("selected type", type);
console.log("pickerItems", pickerItems);
const filterFn = (item, inputValue) => {
console.log("filterFN", item.name, inputValue);
return (
!inputValue || item.name.toUpperCase().includes(inputValue.toUpperCase())
);
};
return (
<form onSubmit={handleSubmit(createSubscriptionWrapper)}>
<Stack mb={0} direction="column">
<Stack spacing={1} w="100%">
<Text fontWeight="600">Source:</Text>
<Stack spacing={1} w="100%" pb={2}>
<Text fontWeight="600">Type:</Text>
{/* position must be relative otherwise radio boxes add strange spacing on selection */}
<Stack
spacing={1}
@ -124,7 +188,164 @@ const _NewSubscription = ({
flexWrap="wrap"
position="relative"
>
{typesCache.data
{!typesCache.isLoading && typesCache.data && pickerItems && (
<>
<Downshift
onSelect={(selectedItem) => {
setType(selectedItem);
}}
// isOpen={showSuggestions}
itemToString={(item) => (item ? item.name : "")}
// initialSelectedItem={pickerItems[1] ?? undefined}
ref={downshiftRef}
// initialInputValue={}
>
{({
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,
isOpen,
inputValue,
highlightedIndex,
selectedItem,
getRootProps,
}) => {
console.log("selected item,", selectedItem?.name);
return (
<Box pos="relative" w="100%">
<Box
// style={comboboxStyles}
{...getRootProps({}, { suppressRefError: true })}
>
<InputGroup>
<InputLeftAddon
isTruncated
maxW="60px"
fontSize="sm"
bgColor={"gray.100"}
>
<Image h="24px" src={selectedItem?.icon_url} />
</InputLeftAddon>
<Input
placeholder="What do you want to subscribe to"
isTruncated
fontSize="sm"
{...getInputProps()}
// defaultValue={selectedItem.name ?? undefined}
// value={selectedItem.name}
></Input>
<InputRightAddon p={0}>
<Button
variant="outline"
w="100%"
m={0}
p={0}
colorScheme="gray"
{...getToggleButtonProps({
// onClick: () =>
// console.log(
// "ref: ",
// downshiftRef.current.clearSelection()
// ),
})}
aria-label={"toggle menu"}
>
&#8595;
</Button>
</InputRightAddon>
</InputGroup>
</Box>
{/* <Menu
isOpen={isOpen}
// style={menuStyles}
// position="absolute"
colorScheme="blue"
bgColor="gray.300"
inset="unset"
// spacing={2}
// p={2}
> */}
{isOpen ? (
<Stack
// display="flex"
direction="column"
className="menuListTim"
{...getMenuProps()}
bgColor="gray.300"
borderRadius="md"
boxShadow="lg"
pos="absolute"
left={0}
right={0}
spacing={2}
zIndex={1000}
py={2}
>
{pickerItems &&
pickerItems
.filter((item) => filterFn(item, inputValue))
.map((item, index) => {
return (
<Stack
px={4}
py={1}
alignItems="center"
key={item.value}
{...getItemProps({
key: item.value,
index,
item,
})}
direction="row"
w="100%"
bgColor={
index === highlightedIndex
? "orange.900"
: "inherit"
}
color={
index === highlightedIndex
? "gray.100"
: "inherit"
}
justifyContent="space-between"
>
<Image
h="24px"
src={item.icon_url}
alignSelf="flex-start"
/>
<chakra.span
whiteSpace="nowrap"
alignSelf="center"
>
{item.name}
</chakra.span>
<Tooltip
label={item.description}
colorScheme="blue"
variant="onboarding"
>
<QuestionIcon h="24px" />
</Tooltip>
</Stack>
);
})}
</Stack>
) : null}
{/* </Menu> */}
</Box>
);
}}
</Downshift>
</>
)}
{/* {typesCache.data
.sort((a, b) =>
a?.name > b?.name ? 1 : b?.name > a?.name ? -1 : 0
)
@ -136,7 +357,7 @@ const _NewSubscription = ({
!type.active ||
(isFreeOption && type.id !== "ethereum_blockchain"),
});
if (type.id === "ethereum_whalewatch") return "";
return (
<RadioCard
px="8px"
@ -151,151 +372,176 @@ const _NewSubscription = ({
{type.name.slice(9, type.name.length)}
</RadioCard>
);
})}
})} */}
</Stack>
</Stack>
<Flex direction="row" w="100%" flexWrap="wrap" pt={4}>
{/* position must be relative otherwise radio boxes add strange spacing on selection */}
<HStack flexGrow={0} flexBasis="140px" position="relative">
{search(radioState, typesCache.data).choices.length > 0 && (
<Text fontWeight="600">Type:</Text>
)}
{search(radioState, typesCache.data).choices.map(
(addition_selects) => {
const radio = getRadioPropsSubscription({
value: addition_selects,
isDisabled: addition_selects.startsWith("tag"),
});
return (
<RadioCard
px="4px"
py="2px"
key={`subscription_tags_${addition_selects}`}
{...radio}
>
{mapper[addition_selects]}
</RadioCard>
);
}
)}
</HStack>
{subscriptionAdressFormatRadio.startsWith("input") &&
radioState != "ethereum_whalewatch" && (
<Flex flexBasis="240px" flexGrow={1}>
<FormControl isInvalid={errors?.address}>
<Input
type="text"
autoComplete="off"
my={2}
placeholder="Address to subscribe to"
name="address"
ref={register({ required: "address is required!" })}
></Input>
{!type?.id?.includes("whalewatch") && type && (
<Flex direction="row" w="100%" flexWrap="wrap">
{/* position must be relative otherwise radio boxes add strange spacing on selection */}
<VStack w="100%" spacing={0}>
{subscriptionAdressFormatRadio.startsWith("input") && (
<Flex w="100%">
<FormControl isInvalid={errors?.address}>
<InputGroup my={2} fontSize="xs">
<InputLeftAddon>
<FormLabel
fontWeight="600"
// alignSelf="flex-start"
m={0}
>
Address:
</FormLabel>
</InputLeftAddon>
<Input
type="text"
autoComplete="off"
placeholder="Address to subscribe to"
name="address"
value={address}
onChange={(e) => setAddress(e.target.value)}
ref={register({ required: "address is required!" })}
></Input>
</InputGroup>
<FormErrorMessage color="red.400" pl="1">
{errors?.address && errors?.address.message}
</FormErrorMessage>
</FormControl>
</Flex>
)}
</VStack>
</Flex>
)}
{!type?.id?.includes("whalewatch") && type && (
<Flex direction="row" w="100%" flexWrap="wrap">
{/* position must be relative otherwise radio boxes add strange spacing on selection */}
<VStack w="100%" spacing={0}>
<Flex w="100%">
<FormControl isInvalid={errors?.label}>
<InputGroup my={2} fontSize="xs">
<InputLeftAddon>
<FormLabel
fontWeight="600"
// alignSelf="flex-start"
m={0}
>
Label:
</FormLabel>
</InputLeftAddon>
<Input
type="text"
autoComplete="off"
placeholder="Name your label"
name="label"
value={label}
onChange={(e) => setLabel(e.target.value)}
ref={register({ required: "label is required!" })}
></Input>
</InputGroup>
<FormErrorMessage color="red.400" pl="1">
{errors?.address && errors?.address.message}
{errors?.label && errors?.label.message}
</FormErrorMessage>
</FormControl>
</Flex>
)}
</Flex>
<Input
type="hidden"
placeholder="subscription_type"
name="subscription_type"
ref={register({ required: "select type" })}
value={radioState}
onChange={() => null}
></Input>
</Stack>
<FormControl isInvalid={errors?.color}>
{!isModal ? (
<Flex direction="row" pb={2} flexWrap="wrap" alignItems="baseline">
<Text fontWeight="600" alignSelf="center">
Label color
</Text>{" "}
<Stack
// pt={2}
direction={["row", "row", null]}
h="min-content"
alignSelf="center"
>
<IconButton
size="md"
// colorScheme="blue"
color={"white.100"}
_hover={{ bgColor: { color } }}
bgColor={color}
variant="outline"
onClick={() => setColor(makeColor())}
icon={<BiRefresh />}
/>
<Input
type="input"
placeholder="color"
name="color"
ref={register({ required: "color is required!" })}
value={color}
onChange={() => null}
w="200px"
></Input>
</Stack>
<Flex p={2} flexBasis="120px" flexGrow={1} alignSelf="center">
<CirclePicker
width="100%"
onChangeComplete={handleChangeColorComplete}
circleSpacing={1}
circleSize={24}
/>
</Flex>
</VStack>
</Flex>
) : (
<>
<Stack direction="row" pb={2}>
<Text fontWeight="600" alignSelf="center">
Label color
</Text>{" "}
<IconButton
size="md"
color={"white.100"}
_hover={{ bgColor: { color } }}
bgColor={color}
variant="outline"
onClick={() => setColor(makeColor())}
icon={<BiRefresh />}
/>
<Input
type="input"
placeholder="color"
name="color"
ref={register({ required: "color is required!" })}
value={color}
onChange={() => null}
w="200px"
></Input>
</Stack>
<GithubPicker onChangeComplete={handleChangeColorComplete} />
</>
)}
<FormErrorMessage color="red.400" pl="1">
{errors?.color && errors?.color.message}
</FormErrorMessage>
</FormControl>
<ButtonGroup direction="row" justifyContent="flex-end" w="100%">
<Button
type="submit"
colorScheme="green"
isLoading={createSubscription.isLoading}
>
Confirm
</Button>
{type && (
<FormControl isInvalid={errors?.color}>
{!isModal ? (
<Flex
direction="row"
pb={2}
flexWrap="wrap"
alignItems="baseline"
>
<Text fontWeight="600" alignSelf="center">
Label color
</Text>{" "}
<Stack
// pt={2}
direction={["row", "row", null]}
h="min-content"
alignSelf="center"
>
<IconButton
size="md"
// colorScheme="blue"
color={"white.100"}
_hover={{ bgColor: { color } }}
bgColor={color}
variant="outline"
onClick={() => setColor(makeColor())}
icon={<BiRefresh />}
/>
<Input
type="input"
placeholder="color"
name="color"
ref={register({ required: "color is required!" })}
value={color}
onChange={() => null}
w="200px"
></Input>
</Stack>
<Flex p={2} flexBasis="120px" flexGrow={1} alignSelf="center">
<CirclePicker
width="100%"
onChangeComplete={handleChangeColorComplete}
circleSpacing={1}
circleSize={24}
/>
</Flex>
</Flex>
) : (
<>
<Stack direction="row" pb={2}>
<Text fontWeight="600" alignSelf="center">
Label color
</Text>{" "}
<IconButton
size="md"
color={"white.100"}
_hover={{ bgColor: { color } }}
bgColor={color}
variant="outline"
onClick={() => setColor(makeColor())}
icon={<BiRefresh />}
/>
<Input
type="input"
placeholder="color"
name="color"
ref={register({ required: "color is required!" })}
value={color}
onChange={() => null}
w="200px"
></Input>
</Stack>
<Button colorScheme="gray" onClick={onClose}>
Cancel
</Button>
</ButtonGroup>
<GithubPicker onChangeComplete={handleChangeColorComplete} />
</>
)}
<FormErrorMessage color="red.400" pl="1">
{errors?.color && errors?.color.message}
</FormErrorMessage>
</FormControl>
)}
<ButtonGroup direction="row" justifyContent="flex-end" w="100%">
<Button
type="submit"
colorScheme="green"
isLoading={createSubscription.isLoading}
>
Confirm
</Button>
<Button colorScheme="red" onClick={onClose}>
Cancel
</Button>
</ButtonGroup>
</Stack>
</form>
);
};

Wyświetl plik

@ -1,4 +1,9 @@
import React, { useState, useLayoutEffect, useContext, Suspense } from "react";
import React, {
useState,
useLayoutEffect,
useContext,
Suspense,
} from "react";
import OverlayContext from "./context";
import { MODAL_TYPES, DRAWER_TYPES } from "./constants";
import {
@ -51,6 +56,7 @@ const OverlayProvider = ({ children }) => {
const drawerDisclosure = useDisclosure();
const modalDisclosure = useDisclosure();
const alertDisclosure = useDisclosure();
const [modalProps, setModalProps] = useState();
useLayoutEffect(() => {
if (modal === MODAL_TYPES.OFF && modalDisclosure.isOpen) {
@ -106,7 +112,7 @@ const OverlayProvider = ({ children }) => {
return (
<OverlayContext.Provider
value={{ modal, toggleModal, drawer, toggleDrawer }}
value={{ modal, toggleModal, drawer, toggleDrawer, setModalProps }}
>
<AlertDialog
isOpen={alertDisclosure.isOpen}
@ -169,6 +175,7 @@ const OverlayProvider = ({ children }) => {
<NewSubscription
onClose={() => toggleModal(MODAL_TYPES.OFF)}
isModal={true}
{...modalProps}
/>
)}
{modal === MODAL_TYPES.FORGOT && <ForgotPassword />}
@ -193,6 +200,7 @@ const OverlayProvider = ({ children }) => {
</Modal>
{/* )} */}
<Drawer
trapFocus={false}
isOpen={drawerDisclosure.isOpen}
placement="right"
size="xl"
@ -201,13 +209,13 @@ const OverlayProvider = ({ children }) => {
onClose={() => toggleAlert(() => toggleDrawer(DRAWER_TYPES.OFF))}
>
<DrawerOverlay />
<DrawerContent>
<DrawerContent overflowY="scroll">
<DrawerCloseButton />
<DrawerHeader borderBottomWidth="1px">
{DRAWER_TYPES.NEW_DASHBOARD && "New dashboard"}
</DrawerHeader>
<DrawerBody>
<DrawerBody h="auto">
{DRAWER_TYPES.NEW_DASHBOARD && (
<Suspense fallback={<Spinner />}>
<NewDashboard firstField={firstField} />