kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into etherscan-nft
commit
457858c23e
|
@ -0,0 +1,32 @@
|
|||
"""Unique const for addr
|
||||
|
||||
Revision ID: ea8185bd24c7
|
||||
Revises: 40871a7807f6
|
||||
Create Date: 2021-08-18 09:41:00.512462
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ea8185bd24c7'
|
||||
down_revision = '40871a7807f6'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index('ix_ethereum_addresses_address', table_name='ethereum_addresses')
|
||||
op.create_index(op.f('ix_ethereum_addresses_address'), 'ethereum_addresses', ['address'], unique=True)
|
||||
op.create_unique_constraint(op.f('uq_ethereum_labels_id'), 'ethereum_labels', ['id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(op.f('uq_ethereum_labels_id'), 'ethereum_labels', type_='unique')
|
||||
op.drop_index(op.f('ix_ethereum_addresses_address'), table_name='ethereum_addresses')
|
||||
op.create_index('ix_ethereum_addresses_address', 'ethereum_addresses', ['address'], unique=False)
|
||||
# ### end Alembic commands ###
|
|
@ -113,7 +113,7 @@ class EthereumAddress(Base): # type: ignore
|
|||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
address = Column(VARCHAR(256), nullable=False, index=True)
|
||||
address = Column(VARCHAR(256), nullable=False, unique=True, index=True)
|
||||
created_at = Column(
|
||||
DateTime(timezone=True), server_default=utcnow(), nullable=False
|
||||
)
|
||||
|
|
|
@ -39,7 +39,6 @@ export default function CachingApp({ Component, pageProps }) {
|
|||
router.events.on("routeChangeComplete", handleStop);
|
||||
router.events.on("routeChangeError", handleStop);
|
||||
|
||||
console.log("_app", router.asPath);
|
||||
return () => {
|
||||
router.events.off("routeChangeStart", handleStart);
|
||||
router.events.off("routeChangeComplete", handleStop);
|
||||
|
@ -49,8 +48,6 @@ export default function CachingApp({ Component, pageProps }) {
|
|||
const getLayout =
|
||||
Component.getLayout || ((page) => <DefaultLayout>{page}</DefaultLayout>);
|
||||
|
||||
console.log("_app loaded", router.asPath);
|
||||
|
||||
return (
|
||||
<>
|
||||
<style global jsx>{`
|
||||
|
|
|
@ -10,7 +10,9 @@ import {
|
|||
UnorderedList,
|
||||
ListItem,
|
||||
} from "@chakra-ui/react";
|
||||
import RouteButton from "../../src/components/RouteButton";
|
||||
const Entry = () => {
|
||||
console.count("render stream!");
|
||||
const ui = useContext(UIContext);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -49,6 +51,14 @@ const Entry = () => {
|
|||
subscription screen
|
||||
</ListItem>
|
||||
</UnorderedList>
|
||||
<RouteButton
|
||||
variant="solid"
|
||||
size="md"
|
||||
colorScheme="suggested"
|
||||
href="/welcome"
|
||||
>
|
||||
Learn how to use moonstream
|
||||
</RouteButton>
|
||||
</Stack>
|
||||
</>
|
||||
</Box>
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
ModalOverlay,
|
||||
ModalContent,
|
||||
} from "@chakra-ui/react";
|
||||
import NewSubscription from "../src/components/NewSubscription";
|
||||
import NewSubscription from "../src/components/NewModalSubscripton";
|
||||
import { AiOutlinePlusCircle } from "react-icons/ai";
|
||||
|
||||
const Subscriptions = () => {
|
||||
|
|
|
@ -0,0 +1,468 @@
|
|||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import { getLayout } from "../src/layouts/AppLayout";
|
||||
import UIContext from "../src/core/providers/UIProvider/context";
|
||||
import {
|
||||
Heading,
|
||||
Text,
|
||||
Button,
|
||||
Stack,
|
||||
ButtonGroup,
|
||||
Spacer,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
UnorderedList,
|
||||
ListItem,
|
||||
Fade,
|
||||
chakra,
|
||||
useBoolean,
|
||||
Flex,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import StepProgress from "../src/components/StepProgress";
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
|
||||
import Scrollable from "../src/components/Scrollable";
|
||||
import AnalyticsContext from "../src/core/providers/AnalyticsProvider/context";
|
||||
import NewSubscription from "../src/components/NewSubscription";
|
||||
import StreamEntry from "../src/components/StreamEntry";
|
||||
import SubscriptionsList from "../src/components/SubscriptionsList";
|
||||
import { useSubscriptions } from "../src/core/hooks";
|
||||
import router from "next/router";
|
||||
import { FaFilter } from "react-icons/fa";
|
||||
|
||||
const Welcome = () => {
|
||||
console.count("render welcome!");
|
||||
const { subscriptionsCache } = useSubscriptions();
|
||||
const ui = useContext(UIContext);
|
||||
const { mixpanel, isLoaded, MIXPANEL_PROPS } = useContext(AnalyticsContext);
|
||||
const [profile, setProfile] = React.useState();
|
||||
const [showSubscriptionForm, setShowSubscriptionForm] = useBoolean(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
document.title = `Welcome to moonstream.to!`;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const progressButtonCallback = (index) => {
|
||||
ui.setOnboardingStep(index);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (profile && isLoaded) {
|
||||
mixpanel.people.set({
|
||||
[`${MIXPANEL_PROPS.USER_SPECIALITY}`]: profile,
|
||||
});
|
||||
}
|
||||
}, [profile, MIXPANEL_PROPS, isLoaded, mixpanel]);
|
||||
|
||||
const SubscriptonCreatedCallback = () => {
|
||||
setShowSubscriptionForm.off();
|
||||
};
|
||||
|
||||
const scrollRef = useRef();
|
||||
const handleNextClick = () => {
|
||||
if (ui.onboardingStep < ui.onboardingSteps.length - 1) {
|
||||
ui.setOnboardingStep(ui.onboardingStep + 1);
|
||||
scrollRef?.current?.scrollIntoView();
|
||||
} else {
|
||||
ui.setisOnboardingComplete(true);
|
||||
router.push("/stream");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Scrollable>
|
||||
<Stack px="7%" pt={4} w="100%" spacing={4} ref={scrollRef}>
|
||||
<StepProgress
|
||||
numSteps={ui.onboardingSteps.length}
|
||||
currentStep={ui.onboardingStep}
|
||||
colorScheme="primary"
|
||||
buttonCallback={progressButtonCallback}
|
||||
buttonTitles={[
|
||||
"Moonstream basics",
|
||||
"Setup subscriptions",
|
||||
"How to read stream",
|
||||
]}
|
||||
style="arrows"
|
||||
/>
|
||||
|
||||
{ui.onboardingStep === 0 && (
|
||||
<Fade in>
|
||||
<Stack spacing={4}>
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
Greetings traveller!
|
||||
</Heading>
|
||||
<Text fontWeight="semibold" pl={2}>
|
||||
{" "}
|
||||
We are very excited to see you onboard!
|
||||
</Text>
|
||||
|
||||
<Text fontWeight="semibold" pl={2}>
|
||||
Moonstream is a product which helps anyone participate in
|
||||
decentralized finance. From the most sophisticated flash
|
||||
arbitrageurs to people looking for yield from currency that
|
||||
would otherwise lie dormant in their exchange accounts.
|
||||
</Text>
|
||||
<Text fontWeight="semibold" pl={2}>
|
||||
Moonstream is ment to give you critical insights needed to
|
||||
succeed in your crypto quest!
|
||||
</Text>
|
||||
</Stack>
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
How does Moonstream work?
|
||||
</Heading>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
We run nodes
|
||||
</Text>{" "}
|
||||
- Now get most precise and accurate data you can just query
|
||||
our database. You {`don't`} need to maintain your node, and
|
||||
still have data that miners have access to!
|
||||
</chakra.span>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
We crawl data
|
||||
</Text>{" "}
|
||||
We analyze millions of transactions, data, smart contract code
|
||||
to link all them together
|
||||
</chakra.span>
|
||||
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
We provide data
|
||||
</Text>{" "}
|
||||
We allow you to fetch data trough the website or trough API
|
||||
</chakra.span>
|
||||
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
We analyze data
|
||||
</Text>{" "}
|
||||
We find most interesting stuff and show it to you!
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
UI 101?
|
||||
</Heading>
|
||||
<Text fontWeight="semibold" pl={2}>
|
||||
On the left side corner there is sidebar that you can use to
|
||||
navigate across the website. There are following views you can
|
||||
navigate to:
|
||||
</Text>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
Subscriptions
|
||||
</Text>{" "}
|
||||
- Use this screen to set up addresses you would like to
|
||||
monitor to.{" "}
|
||||
<i>
|
||||
NB: Without setting up subscriptions moonstream will have
|
||||
quite empty feel!{" "}
|
||||
</i>{" "}
|
||||
No worries, we will help you to set up your subscriptions in
|
||||
the next steps!
|
||||
</chakra.span>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
Stream
|
||||
</Text>{" "}
|
||||
This view is somewhat similar to a bank statement where you
|
||||
can define time range and see what happened in that time over
|
||||
your subscriptions. In next steps we will show how you can
|
||||
apply filters to it!
|
||||
</chakra.span>
|
||||
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
Stream Entry
|
||||
</Text>{" "}
|
||||
You can see detailed view of stream cards with very specific
|
||||
and essential data, like methods called in smart contracts
|
||||
etc!!
|
||||
</chakra.span>
|
||||
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
<Text fontWeight="bold" display="inline">
|
||||
Analytics
|
||||
</Text>{" "}
|
||||
This section is under construction yet. Soon you will be able
|
||||
to create your dashboard with data of your interest. We will
|
||||
really appretiate if you visit that section, and fill up form
|
||||
describing what analytical tools you would love to see there!
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
Tell us more about your needs?
|
||||
</Heading>
|
||||
<Text fontWeight="semibold" pl={2}>
|
||||
In order to fetch best possible experience, we would like to
|
||||
know some details about you
|
||||
</Text>
|
||||
<Text fontWeight="semibold" pl={2}>
|
||||
Please tell us what profile describes you best?{" "}
|
||||
<i>
|
||||
This is purely analytical data, you can change it anytime
|
||||
later
|
||||
</i>
|
||||
</Text>
|
||||
<RadioGroup
|
||||
onChange={setProfile}
|
||||
value={profile}
|
||||
fontWeight="bold"
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-evenly">
|
||||
<Radio value="trader">I am trading crypto currency</Radio>
|
||||
<Radio value="fund">I represent investment fund</Radio>
|
||||
<Radio value="developer">I am developer</Radio>
|
||||
</Stack>
|
||||
</RadioGroup>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Fade>
|
||||
)}
|
||||
{ui.onboardingStep === 1 && (
|
||||
<Fade in>
|
||||
<Stack px="7%">
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
my={2}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
Subscriptions
|
||||
</Heading>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
Subscriptions is essential tool of Moonstream. We gather data
|
||||
for you based on addresses you have subscribed for.
|
||||
<br />
|
||||
Subscribe to any addres which you are interested in and they
|
||||
will become part of your stream!
|
||||
<UnorderedList>
|
||||
<ListItem>
|
||||
Color - you can define color to easily identify this
|
||||
subscription in your stream
|
||||
</ListItem>
|
||||
<ListItem>Address - thing you subscribe to</ListItem>
|
||||
<ListItem>
|
||||
Label - Its good idea to use human readible name that
|
||||
represents address
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
Source - In Alpha we support only Ethereum blockchain,
|
||||
more sources are coming soon!
|
||||
</ListItem>
|
||||
</UnorderedList>
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
{subscriptionsCache.data.subscriptions.length > 0 &&
|
||||
!subscriptionsCache.isLoading && (
|
||||
<>
|
||||
<Heading>
|
||||
{" "}
|
||||
You already have some subscriptions set up
|
||||
</Heading>
|
||||
</>
|
||||
)}
|
||||
<SubscriptionsList />
|
||||
{showSubscriptionForm && (
|
||||
<>
|
||||
<Heading pt={12}>{`Let's add new subscription!`}</Heading>
|
||||
|
||||
<NewSubscription
|
||||
isFreeOption={true}
|
||||
onClose={SubscriptonCreatedCallback}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!showSubscriptionForm && (
|
||||
<Button
|
||||
colorScheme="suggested"
|
||||
variant="solid"
|
||||
onClick={() => setShowSubscriptionForm.on()}
|
||||
>
|
||||
Add another subscription
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Fade>
|
||||
)}
|
||||
{ui.onboardingStep === 2 && (
|
||||
<Fade in>
|
||||
<Stack>
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
my={2}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
Stream
|
||||
</Heading>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
We are almost done!
|
||||
<br />
|
||||
{`Stream is where you can read data you've subscribed for. Here
|
||||
you have different cards for different subscription types.`}
|
||||
<br />
|
||||
If card has some extra details - there will be orange button
|
||||
on right hand side inviting you to see more!
|
||||
<br />
|
||||
Below is typical card for ethereum blockchain event. Useful
|
||||
information right on the card:
|
||||
<UnorderedList py={2}>
|
||||
<ListItem>Hash - unique ID of the event </ListItem>
|
||||
<ListItem>
|
||||
From - sender address. If it is one of your subscription
|
||||
addresses - will appear in color and with label{" "}
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
To - receiver address. If it is one of your subscription
|
||||
addresses - will appear in color and with label{" "}
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
Nonce - Counter how many transactions address has sent. It
|
||||
also determines sequence of transaction!{" "}
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
Gas Price - this is how much ether is being paid per gas
|
||||
unit
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
Gas - Ammount of gas this event consumes
|
||||
</ListItem>
|
||||
</UnorderedList>
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
<Stack
|
||||
pb={ui.isMobileView ? 24 : 8}
|
||||
w={ui.isMobileView ? "100%" : "calc(100% - 300px)"}
|
||||
alignSelf="center"
|
||||
>
|
||||
<Flex h="3rem" w="100%" bgColor="gray.100" alignItems="center">
|
||||
<Flex maxW="90%"></Flex>
|
||||
<Spacer />
|
||||
<Tooltip
|
||||
variant="onboarding"
|
||||
placement={ui.isMobileView ? "bottom" : "right"}
|
||||
label="Filtering menu"
|
||||
isOpen={true}
|
||||
maxW="150px"
|
||||
hasArrow
|
||||
>
|
||||
<IconButton
|
||||
mr={4}
|
||||
// onClick={onOpen}
|
||||
colorScheme="primary"
|
||||
variant="ghost"
|
||||
icon={<FaFilter />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
<StreamEntry
|
||||
mt={20}
|
||||
entry={{
|
||||
from_address: "this is address from",
|
||||
to_address: "this is to address",
|
||||
hash: "this is hash",
|
||||
}}
|
||||
showOnboardingTooltips={true}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
px={12}
|
||||
// mt={24}
|
||||
bgColor="gray.50"
|
||||
borderRadius="xl"
|
||||
boxShadow="xl"
|
||||
py={4}
|
||||
my={2}
|
||||
>
|
||||
<Heading as="h4" size="md">
|
||||
Applying filters
|
||||
</Heading>
|
||||
<chakra.span fontWeight="semibold" pl={2}>
|
||||
You can apply various filters by clicking filter menu button
|
||||
<br />
|
||||
{`Right now you can use it to select address from and to, we are adding more complex queries soon, stay tuna! `}
|
||||
<br />
|
||||
</chakra.span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Fade>
|
||||
)}
|
||||
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
colorScheme="secondary"
|
||||
leftIcon={<ArrowLeftIcon />}
|
||||
variant="outline"
|
||||
hidden={ui.onboardingStep === 0}
|
||||
disabled={ui.onboardingStep === 0}
|
||||
onClick={() => {
|
||||
ui.setOnboardingStep(ui.onboardingStep - 1);
|
||||
scrollRef?.current?.scrollIntoView();
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
colorScheme="secondary"
|
||||
variant="solid"
|
||||
rightIcon={<ArrowRightIcon />}
|
||||
// hidden={!(ui.onboardingStep < ui.onboardingSteps.length - 1)}
|
||||
// disabled={!(ui.onboardingStep < ui.onboardingSteps.length - 1)}
|
||||
onClick={() => handleNextClick()}
|
||||
>
|
||||
{ui.onboardingStep < ui.onboardingSteps.length - 1
|
||||
? `Next`
|
||||
: `Finish `}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</Scrollable>
|
||||
);
|
||||
};
|
||||
Welcome.getLayout = getLayout;
|
||||
export default Welcome;
|
|
@ -1,13 +1,38 @@
|
|||
const Spinner = {
|
||||
baseStyle: {
|
||||
color: "primary.400",
|
||||
thickness: "4px",
|
||||
speed: "1.5s",
|
||||
my: 8,
|
||||
const baseStyle = {
|
||||
color: "primary.400",
|
||||
thickness: "4px",
|
||||
speed: "1.5s",
|
||||
my: 8,
|
||||
};
|
||||
const variants = {
|
||||
basic: { thickness: "4px", speed: "1.5s" },
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
xs: {
|
||||
"--spinner-size": "0.75rem",
|
||||
},
|
||||
variants: {
|
||||
basic: { thickness: "4px", speed: "1.5s" },
|
||||
sm: {
|
||||
"--spinner-size": "1rem",
|
||||
},
|
||||
md: {
|
||||
"--spinner-size": "1.5rem",
|
||||
},
|
||||
lg: {
|
||||
"--spinner-size": "2rem",
|
||||
},
|
||||
xl: {
|
||||
"--spinner-size": "3rem",
|
||||
},
|
||||
};
|
||||
|
||||
export default Spinner;
|
||||
const defaultProps = {
|
||||
size: "md",
|
||||
};
|
||||
|
||||
export default {
|
||||
baseStyle,
|
||||
sizes,
|
||||
defaultProps,
|
||||
variants,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { mode } from "@chakra-ui/theme-tools";
|
||||
|
||||
const baseStyle = (props) => {
|
||||
const bg = mode("gray.700", "gray.300")(props);
|
||||
return {
|
||||
"--tooltip-bg": `colors.${bg}`,
|
||||
px: "8px",
|
||||
py: "2px",
|
||||
bg: "var(--tooltip-bg)",
|
||||
"--popper-arrow-bg": "var(--tooltip-bg)",
|
||||
color: mode("whiteAlpha.900", "gray.900")(props),
|
||||
borderRadius: "sm",
|
||||
fontWeight: "medium",
|
||||
fontSize: "sm",
|
||||
boxShadow: "md",
|
||||
maxW: "320px",
|
||||
zIndex: "tooltip",
|
||||
};
|
||||
};
|
||||
|
||||
const variantOnboarding = (props) => {
|
||||
const bg = mode("secondary.700", "secondary.300")(props);
|
||||
return {
|
||||
"--tooltip-bg": `colors.${bg}`,
|
||||
px: "8px",
|
||||
py: "2px",
|
||||
bg: "var(--tooltip-bg)",
|
||||
"--popper-arrow-bg": "var(--tooltip-bg)",
|
||||
color: mode("whiteAlpha.900", "gray.900")(props),
|
||||
borderRadius: "md",
|
||||
fontWeight: "medium",
|
||||
fontSize: "sm",
|
||||
boxShadow: "md",
|
||||
maxW: "320px",
|
||||
zIndex: "tooltip",
|
||||
};
|
||||
};
|
||||
|
||||
const variants = {
|
||||
onboarding: variantOnboarding,
|
||||
};
|
||||
|
||||
export default {
|
||||
baseStyle,
|
||||
variants,
|
||||
};
|
|
@ -8,6 +8,8 @@ import NumberInput from "./NumberInput";
|
|||
import Badge from "./Badge";
|
||||
import Checkbox from "./Checkbox";
|
||||
import Table from "./Table";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Spinner from "./Spinner";
|
||||
import { createBreakpoints } from "@chakra-ui/theme-tools";
|
||||
|
||||
const breakpointsCustom = createBreakpoints({
|
||||
|
@ -53,6 +55,8 @@ const theme = extendTheme({
|
|||
Badge,
|
||||
Checkbox,
|
||||
Table,
|
||||
Spinner,
|
||||
Tooltip,
|
||||
},
|
||||
|
||||
fonts: {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
PopoverCloseButton,
|
||||
useBreakpointValue,
|
||||
Spacer,
|
||||
ButtonGroup,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
HamburgerIcon,
|
||||
|
@ -26,6 +27,7 @@ import { MdTimeline } from "react-icons/md";
|
|||
import useRouter from "../core/hooks/useRouter";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
import AccountIconButton from "./AccountIconButton";
|
||||
import RouteButton from "./RouteButton";
|
||||
|
||||
const AppNavbar = () => {
|
||||
const ui = useContext(UIContext);
|
||||
|
@ -95,6 +97,14 @@ const AppNavbar = () => {
|
|||
<Flex width="100%" px={2}>
|
||||
<Spacer />
|
||||
<Flex placeSelf="flex-end">
|
||||
<ButtonGroup spacing={4}>
|
||||
{/* <RouteButton variant="link" href="/docs">
|
||||
Docs
|
||||
</RouteButton> */}
|
||||
<RouteButton variant="link" href="/welcome">
|
||||
Learn how to
|
||||
</RouteButton>
|
||||
</ButtonGroup>
|
||||
<SupportPopover />
|
||||
<AccountIconButton
|
||||
colorScheme="primary"
|
||||
|
|
|
@ -204,7 +204,6 @@ const EntriesNavigation = () => {
|
|||
};
|
||||
|
||||
const dropFilterArrayItem = (idx) => {
|
||||
console.log("dropFilterArrayItem", idx, filterState);
|
||||
const oldArray = [...filterState];
|
||||
const newArray = oldArray.filter(function (ele) {
|
||||
return ele != oldArray[idx];
|
||||
|
@ -242,7 +241,6 @@ const EntriesNavigation = () => {
|
|||
};
|
||||
|
||||
const handleFilterStateCallback = (props) => {
|
||||
console.log("handleFilterStateCallback", props);
|
||||
const currentFilterState = [...filterState];
|
||||
currentFilterState.push({ ...props });
|
||||
|
||||
|
@ -529,6 +527,7 @@ const EntriesNavigation = () => {
|
|||
?.sort((a, b) => b.timestamp - a.timestamp) // TODO(Andrey) improve that for bi chunks of data sorting can take time
|
||||
.map((entry, idx) => (
|
||||
<StreamEntry
|
||||
showOnboardingTooltips={false}
|
||||
key={`entry-list-${idx}`}
|
||||
entry={entry}
|
||||
disableDelete={!canDelete}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { HamburgerIcon } from "@chakra-ui/icons";
|
|||
import useModals from "../core/hooks/useModals";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
import ChakraAccountIconButton from "./AccountIconButton";
|
||||
import RouteButton from "./RouteButton";
|
||||
|
||||
const LandingNavbar = () => {
|
||||
const ui = useContext(UIContext);
|
||||
|
@ -52,6 +53,17 @@ const LandingNavbar = () => {
|
|||
spacing={4}
|
||||
pr={16}
|
||||
>
|
||||
{ui.isLoggedIn && (
|
||||
<ButtonGroup spacing={4}>
|
||||
{/* <RouteButton variant="link" href="/docs">
|
||||
Docs
|
||||
</RouteButton> */}
|
||||
<RouteButton variant="link" href="/welcome">
|
||||
Learn how to
|
||||
</RouteButton>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
|
||||
{ui.isLoggedIn && (
|
||||
<RouterLink href="/stream" passHref>
|
||||
<Button
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useSubscriptions } from "../core/hooks";
|
||||
import {
|
||||
Input,
|
||||
Stack,
|
||||
Text,
|
||||
HStack,
|
||||
useRadioGroup,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalHeader,
|
||||
Button,
|
||||
ModalFooter,
|
||||
Spinner,
|
||||
IconButton,
|
||||
} from "@chakra-ui/react";
|
||||
import RadioCard from "./RadioCard";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { GithubPicker } from "react-color";
|
||||
import { BiRefresh } from "react-icons/bi";
|
||||
import { makeColor } from "../core/utils/makeColor";
|
||||
const NewSubscription = ({ isFreeOption, onClose }) => {
|
||||
const [color, setColor] = useState(makeColor());
|
||||
const { typesCache, createSubscription } = useSubscriptions();
|
||||
const { handleSubmit, errors, register } = useForm({});
|
||||
const [radioState, setRadioState] = useState("ethereum_blockchain");
|
||||
let { getRootProps, getRadioProps } = useRadioGroup({
|
||||
name: "type",
|
||||
defaultValue: radioState,
|
||||
onChange: setRadioState,
|
||||
});
|
||||
|
||||
const group = getRootProps();
|
||||
|
||||
useEffect(() => {
|
||||
if (createSubscription.isSuccess) {
|
||||
onClose();
|
||||
}
|
||||
}, [createSubscription.isSuccess, onClose]);
|
||||
|
||||
if (typesCache.isLoading) return <Spinner />;
|
||||
|
||||
const createSubscriptionWrap = (props) => {
|
||||
createSubscription.mutate({
|
||||
...props,
|
||||
color: color,
|
||||
type: isFreeOption ? "free" : radioState,
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeColorComplete = (color) => {
|
||||
setColor(color.hex);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(createSubscriptionWrap)}>
|
||||
<ModalHeader>Subscribe to a new address</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<FormControl isInvalid={errors.label}>
|
||||
<Input
|
||||
my={2}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder="Enter label"
|
||||
name="label"
|
||||
ref={register({ required: "label is required!" })}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.label && errors.label.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={errors.address}>
|
||||
<Input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
my={2}
|
||||
placeholder="Enter address"
|
||||
name="address"
|
||||
ref={register({ required: "address is required!" })}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.address && errors.address.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Stack my={16} direction="column">
|
||||
<Text fontWeight="600">
|
||||
{isFreeOption
|
||||
? `Free subscription is only availible Ethereum blockchain source`
|
||||
: `On which source?`}
|
||||
</Text>
|
||||
|
||||
<FormControl isInvalid={errors.subscription_type}>
|
||||
<HStack {...group} alignItems="stretch">
|
||||
{typesCache.data.subscriptions.map((type) => {
|
||||
const radio = getRadioProps({
|
||||
value: type.id,
|
||||
isDisabled:
|
||||
!type.active ||
|
||||
(isFreeOption &&
|
||||
type.subscription_type !== "ethereum_blockchain"),
|
||||
});
|
||||
return (
|
||||
<RadioCard key={`subscription_type_${type.id}`} {...radio}>
|
||||
{type.name}
|
||||
</RadioCard>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
<Input
|
||||
type="hidden"
|
||||
placeholder="subscription_type"
|
||||
name="subscription_type"
|
||||
ref={register({ required: "select type" })}
|
||||
value={radioState}
|
||||
onChange={() => null}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.subscription_type && errors.subscription_type.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<FormControl isInvalid={errors.color}>
|
||||
<Stack direction="row" pb={2}>
|
||||
<Text fontWeight="600" alignSelf="center">
|
||||
Label color
|
||||
</Text>{" "}
|
||||
<IconButton
|
||||
size="md"
|
||||
// colorScheme="primary"
|
||||
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
|
||||
// color={this.state.background}
|
||||
onChangeComplete={handleChangeColorComplete}
|
||||
/>
|
||||
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.color && errors.color.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
colorScheme="suggested"
|
||||
isLoading={createSubscription.isLoading}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button colorScheme="gray" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewSubscription;
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useSubscriptions } from "../core/hooks";
|
||||
import {
|
||||
Input,
|
||||
|
@ -8,23 +8,24 @@ import {
|
|||
useRadioGroup,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalHeader,
|
||||
Button,
|
||||
ModalFooter,
|
||||
Spinner,
|
||||
IconButton,
|
||||
ButtonGroup,
|
||||
Spacer,
|
||||
Flex,
|
||||
} from "@chakra-ui/react";
|
||||
import RadioCard from "./RadioCard";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { GithubPicker } from "react-color";
|
||||
// import { useForm } from "react-hook-form";
|
||||
import { CirclePicker } from "react-color";
|
||||
import { BiRefresh } from "react-icons/bi";
|
||||
import { makeColor } from "../core/utils/makeColor";
|
||||
const NewSubscription = ({ isFreeOption, onClose }) => {
|
||||
import { useForm } from "react-hook-form";
|
||||
const _NewSubscription = ({ isFreeOption, onClose, setIsLoading }) => {
|
||||
const [color, setColor] = useState(makeColor());
|
||||
const { typesCache, createSubscription } = useSubscriptions();
|
||||
const { handleSubmit, errors, register } = useForm({});
|
||||
const { typesCache, createSubscription } = useSubscriptions();
|
||||
// const { handleSubmit, errors, register } = useForm({});
|
||||
const [radioState, setRadioState] = useState("ethereum_blockchain");
|
||||
let { getRootProps, getRadioProps } = useRadioGroup({
|
||||
name: "type",
|
||||
|
@ -34,96 +35,105 @@ const NewSubscription = ({ isFreeOption, onClose }) => {
|
|||
|
||||
const group = getRootProps();
|
||||
|
||||
useEffect(() => {
|
||||
if (setIsLoading) {
|
||||
setIsLoading(createSubscription.isLoading);
|
||||
}
|
||||
}, [createSubscription.isLoading, setIsLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (createSubscription.isSuccess) {
|
||||
onClose();
|
||||
}
|
||||
}, [createSubscription.isSuccess, onClose]);
|
||||
|
||||
if (typesCache.isLoading) return <Spinner />;
|
||||
const createSubscriptionWrapper = useCallback(
|
||||
(props) => {
|
||||
createSubscription.mutate({
|
||||
...props,
|
||||
color: color,
|
||||
type: isFreeOption ? 0 : radioState,
|
||||
});
|
||||
},
|
||||
[createSubscription, isFreeOption, color, radioState]
|
||||
);
|
||||
|
||||
const createSubscriptionWrap = (props) => {
|
||||
createSubscription.mutate({
|
||||
...props,
|
||||
color: color,
|
||||
type: isFreeOption ? "free" : radioState,
|
||||
});
|
||||
};
|
||||
if (typesCache.isLoading) return <Spinner />;
|
||||
|
||||
const handleChangeColorComplete = (color) => {
|
||||
setColor(color.hex);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(createSubscriptionWrap)}>
|
||||
<ModalHeader>Subscribe to a new address</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<FormControl isInvalid={errors.label}>
|
||||
<Input
|
||||
my={2}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder="Enter label"
|
||||
name="label"
|
||||
ref={register({ required: "label is required!" })}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.label && errors.label.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={errors.address}>
|
||||
<Input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
my={2}
|
||||
placeholder="Enter address"
|
||||
name="address"
|
||||
ref={register({ required: "address is required!" })}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.address && errors.address.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Stack my={16} direction="column">
|
||||
<Text fontWeight="600">
|
||||
{isFreeOption
|
||||
? `Free subscription is only availible Ethereum blockchain source`
|
||||
: `On which source?`}
|
||||
</Text>
|
||||
if (!errors) return "";
|
||||
|
||||
<FormControl isInvalid={errors.subscription_type}>
|
||||
<HStack {...group} alignItems="stretch">
|
||||
{typesCache.data.subscriptions.map((type) => {
|
||||
const radio = getRadioProps({
|
||||
value: type.id,
|
||||
isDisabled:
|
||||
!type.active ||
|
||||
(isFreeOption &&
|
||||
type.subscription_type !== "ethereum_blockchain"),
|
||||
});
|
||||
return (
|
||||
<RadioCard key={`subscription_type_${type.id}`} {...radio}>
|
||||
{type.name}
|
||||
</RadioCard>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
<Input
|
||||
type="hidden"
|
||||
placeholder="subscription_type"
|
||||
name="subscription_type"
|
||||
ref={register({ required: "select type" })}
|
||||
value={radioState}
|
||||
onChange={() => null}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.subscription_type && errors.subscription_type.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<FormControl isInvalid={errors.color}>
|
||||
<Stack direction="row" pb={2}>
|
||||
return (
|
||||
<form onSubmit={handleSubmit(createSubscriptionWrapper)}>
|
||||
<FormControl isInvalid={errors?.label}>
|
||||
<Input
|
||||
my={2}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder="Meaningful name of your subscription"
|
||||
name="label"
|
||||
ref={register({ required: "label is required!" })}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors?.label && errors?.label.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<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>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors?.address && errors?.address.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Stack my={4} direction="column">
|
||||
{/* <Text fontWeight="600">
|
||||
{isFreeOption
|
||||
? `Right now you can subscribe only to ethereum blockchain`
|
||||
: `On which source?`}
|
||||
</Text> */}
|
||||
|
||||
<FormControl isInvalid={errors?.subscription_type}>
|
||||
<HStack {...group} alignItems="stretch">
|
||||
{typesCache.data.subscriptions.map((type) => {
|
||||
const radio = getRadioProps({
|
||||
value: type.id,
|
||||
isDisabled:
|
||||
!type.active ||
|
||||
(isFreeOption &&
|
||||
type.subscription_type !== "ethereum_blockchain"),
|
||||
});
|
||||
return (
|
||||
<RadioCard key={`subscription_type_${type.id}`} {...radio}>
|
||||
{type.name}
|
||||
</RadioCard>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
<Input
|
||||
type="hidden"
|
||||
placeholder="subscription_type"
|
||||
name="subscription_type"
|
||||
ref={register({ required: "select type" })}
|
||||
value={radioState}
|
||||
onChange={() => null}
|
||||
></Input>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors?.subscription_type && errors?.subscription_type.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<FormControl isInvalid={errors?.color}>
|
||||
<Flex direction="row" pb={2} flexWrap="wrap">
|
||||
<Stack pt={2} direction="row" h="min-content">
|
||||
<Text fontWeight="600" alignSelf="center">
|
||||
Label color
|
||||
</Text>{" "}
|
||||
|
@ -147,18 +157,21 @@ const NewSubscription = ({ isFreeOption, onClose }) => {
|
|||
w="200px"
|
||||
></Input>
|
||||
</Stack>
|
||||
<Flex p={2}>
|
||||
<CirclePicker
|
||||
onChangeComplete={handleChangeColorComplete}
|
||||
circleSpacing={1}
|
||||
circleSize={24}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<GithubPicker
|
||||
// color={this.state.background}
|
||||
onChangeComplete={handleChangeColorComplete}
|
||||
/>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors?.color && errors?.color.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.color && errors.color.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<ButtonGroup direction="row" justifyContent="space-evenly">
|
||||
<Button
|
||||
type="submit"
|
||||
colorScheme="suggested"
|
||||
|
@ -166,12 +179,13 @@ const NewSubscription = ({ isFreeOption, onClose }) => {
|
|||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button colorScheme="gray" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ButtonGroup>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewSubscription;
|
||||
export default _NewSubscription;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react";
|
||||
import { chakra, Button, Link } from "@chakra-ui/react";
|
||||
import NextLink from "next/link";
|
||||
|
||||
const _RouteButton = (props) => {
|
||||
return (
|
||||
<NextLink href={props.href} passHref>
|
||||
<Button as={Link} {...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
</NextLink>
|
||||
);
|
||||
};
|
||||
|
||||
const RouteButton = chakra(_RouteButton, "button");
|
||||
|
||||
export default RouteButton;
|
|
@ -14,6 +14,7 @@ import React from "react";
|
|||
import { HamburgerIcon, ArrowLeftIcon, ArrowRightIcon } from "@chakra-ui/icons";
|
||||
import { MdTimeline, MdSettings } from "react-icons/md";
|
||||
import { ImStatsBars } from "react-icons/im";
|
||||
import { HiAcademicCap } from "react-icons/hi";
|
||||
|
||||
const Sidebar = () => {
|
||||
const ui = useContext(UIContext);
|
||||
|
@ -79,6 +80,13 @@ const Sidebar = () => {
|
|||
<RouterLink href="/subscriptions">Subscriptions </RouterLink>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
{ui.isMobileView && (
|
||||
<Menu iconShape="square">
|
||||
<MenuItem icon={<HiAcademicCap />}>
|
||||
<RouterLink href="/welcome">Learn how to</RouterLink>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
)}
|
||||
</SidebarContent>
|
||||
)}
|
||||
{!ui.isLoggedIn && (
|
||||
|
|
|
@ -36,17 +36,16 @@ const SteamEntryDetails = () => {
|
|||
>
|
||||
<HStack id="EntryHeader" width="100%" m={0}>
|
||||
<Heading
|
||||
overflow="hidden"
|
||||
width={entry?.context_url ? "calc(100% - 28px)" : "100%"}
|
||||
minH="36px"
|
||||
style={{ marginLeft: "0" }}
|
||||
m={0}
|
||||
p={0}
|
||||
fontWeight="600"
|
||||
fontSize="1.5rem"
|
||||
fontSize="md"
|
||||
textAlign="left"
|
||||
>
|
||||
{entry && entry.tx.hash}
|
||||
{entry && `Hash: ${entry.tx.hash}`}
|
||||
</Heading>
|
||||
</HStack>
|
||||
</Skeleton>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import React, { useContext } from "react";
|
||||
import { Box, Button, Progress, ButtonGroup } from "@chakra-ui/react";
|
||||
import _ from "lodash";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
const StepProgress = ({
|
||||
numSteps,
|
||||
currentStep,
|
||||
colorScheme,
|
||||
buttonCallback,
|
||||
buttonTitles,
|
||||
}) => {
|
||||
const ui = useContext(UIContext);
|
||||
return (
|
||||
<Box w="100%" h="auto" pos="relative">
|
||||
<ButtonGroup
|
||||
display="inline-flex"
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
w="100%"
|
||||
m={0}
|
||||
p={0}
|
||||
spacing={0}
|
||||
>
|
||||
{_.times(numSteps, (i) => {
|
||||
const setActive = i === parseInt(currentStep) ? true : false;
|
||||
return (
|
||||
<Button
|
||||
key={`${i}-progress-steps`}
|
||||
size={ui.isMobileView ? "md" : "sm"}
|
||||
borderRadius={ui.isMobileView ? "full" : "md"}
|
||||
// size="sm"
|
||||
// bgColor={`${colorScheme}.200`}
|
||||
_active={{ bgColor: `${colorScheme}.1200` }}
|
||||
zIndex={1}
|
||||
m={0}
|
||||
colorScheme={colorScheme}
|
||||
isActive={setActive}
|
||||
onClick={() => buttonCallback(i)}
|
||||
>
|
||||
{ui.isMobileView && i + 1}
|
||||
{!ui.isMobileView && buttonTitles[i]}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</ButtonGroup>
|
||||
<Progress
|
||||
position="absolute"
|
||||
top="50%"
|
||||
transform="translateY(-50%)"
|
||||
h={2}
|
||||
w="full"
|
||||
// hasStripe
|
||||
// isAnimated
|
||||
max={numSteps - 1}
|
||||
min={0}
|
||||
value={currentStep}
|
||||
/>
|
||||
{/* <Flex
|
||||
h="1rem"
|
||||
flexGrow={1}
|
||||
flexBasis="10px"
|
||||
backgroundColor={`${colorScheme}.300`}
|
||||
/> */}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepProgress;
|
|
@ -11,6 +11,7 @@ import {
|
|||
useMediaQuery,
|
||||
Spacer,
|
||||
Spinner,
|
||||
chakra,
|
||||
} from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { ArrowRightIcon } from "@chakra-ui/icons";
|
||||
|
@ -18,7 +19,7 @@ import UIContext from "../core/providers/UIProvider/context";
|
|||
import { useToast } from "../core/hooks";
|
||||
import { useSubscriptions } from "../core/hooks";
|
||||
|
||||
const StreamEntry = ({ entry }) => {
|
||||
const StreamEntry = ({ entry, showOnboardingTooltips, className }) => {
|
||||
const { subscriptionsCache } = useSubscriptions();
|
||||
const ui = useContext(UIContext);
|
||||
const [copyString, setCopyString] = useState(false);
|
||||
|
@ -49,6 +50,7 @@ const StreamEntry = ({ entry }) => {
|
|||
|
||||
return (
|
||||
<Flex
|
||||
className={className}
|
||||
p={0}
|
||||
m={1}
|
||||
mr={2}
|
||||
|
@ -89,35 +91,44 @@ const StreamEntry = ({ entry }) => {
|
|||
overflowX="hidden"
|
||||
overflowY="visible"
|
||||
>
|
||||
<Stack
|
||||
className="title"
|
||||
direction="row"
|
||||
w="100%"
|
||||
h="1.6rem"
|
||||
minH="1.6rem"
|
||||
textAlign="center"
|
||||
spacing={0}
|
||||
alignItems="center"
|
||||
bgColor="gray.300"
|
||||
<Tooltip
|
||||
hasArrow
|
||||
isOpen={showOnboardingTooltips}
|
||||
// shouldWrapChildren
|
||||
label="Top of card describes type of event. Ethereum blockchain in this case. It as unique hash shown here"
|
||||
variant="onboarding"
|
||||
placement="top"
|
||||
>
|
||||
<Image
|
||||
boxSize="16px"
|
||||
src={
|
||||
"https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg"
|
||||
}
|
||||
/>
|
||||
<Heading px={1} size="xs">
|
||||
Hash
|
||||
</Heading>
|
||||
<Spacer />
|
||||
<Text
|
||||
isTruncated
|
||||
onClick={() => setCopyString(entry.hash)}
|
||||
pr={12}
|
||||
<Stack
|
||||
className="title"
|
||||
direction="row"
|
||||
w="100%"
|
||||
h="1.6rem"
|
||||
minH="1.6rem"
|
||||
textAlign="center"
|
||||
spacing={0}
|
||||
alignItems="center"
|
||||
bgColor="gray.300"
|
||||
>
|
||||
{entry.hash}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Image
|
||||
boxSize="16px"
|
||||
src={
|
||||
"https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg"
|
||||
}
|
||||
/>
|
||||
<Heading px={1} size="xs">
|
||||
Hash
|
||||
</Heading>
|
||||
<Spacer />
|
||||
<Text
|
||||
isTruncated
|
||||
onClick={() => setCopyString(entry.hash)}
|
||||
pr={12}
|
||||
>
|
||||
{entry.hash}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
<Stack
|
||||
className="CardAddressesRow"
|
||||
direction={showFullView ? "row" : "column"}
|
||||
|
@ -143,16 +154,25 @@ const StreamEntry = ({ entry }) => {
|
|||
placeContent="center"
|
||||
spacing={0}
|
||||
>
|
||||
<Text
|
||||
bgColor="gray.600"
|
||||
h="100%"
|
||||
fontSize="sm"
|
||||
py="2px"
|
||||
px={2}
|
||||
w={showFullView ? null : "120px"}
|
||||
<Tooltip
|
||||
hasArrow
|
||||
isOpen={showOnboardingTooltips && !ui.isMobileView}
|
||||
label="From and to addresses, clicking names will copy address to clipboard!"
|
||||
variant="onboarding"
|
||||
placement={ui.isMobileView ? "bottom" : "left"}
|
||||
maxW="150px"
|
||||
>
|
||||
From:
|
||||
</Text>
|
||||
<Text
|
||||
bgColor="gray.600"
|
||||
h="100%"
|
||||
fontSize="sm"
|
||||
py="2px"
|
||||
px={2}
|
||||
w={showFullView ? null : "120px"}
|
||||
>
|
||||
From:
|
||||
</Text>
|
||||
</Tooltip>
|
||||
<Tooltip label={entry.from_address} aria-label="From:">
|
||||
<Text
|
||||
mx={0}
|
||||
|
@ -168,6 +188,7 @@ const StreamEntry = ({ entry }) => {
|
|||
</Text>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
|
@ -322,22 +343,33 @@ const StreamEntry = ({ entry }) => {
|
|||
</Stack>
|
||||
)}
|
||||
<Flex>
|
||||
<IconButton
|
||||
m={0}
|
||||
onClick={() => ui.setCurrentTransaction(entry)}
|
||||
h="full"
|
||||
// minH="24px"
|
||||
borderLeftRadius={0}
|
||||
variant="solid"
|
||||
px={0}
|
||||
minW="24px"
|
||||
colorScheme="secondary"
|
||||
icon={<ArrowRightIcon w="24px" />}
|
||||
/>
|
||||
<Tooltip
|
||||
hasArrow
|
||||
isOpen={showOnboardingTooltips}
|
||||
placement={ui.isMobileView ? "bottom" : "right"}
|
||||
label="Clicking side arrow will bring up detailed view"
|
||||
variant="onboarding"
|
||||
maxW="150px"
|
||||
>
|
||||
<IconButton
|
||||
m={0}
|
||||
onClick={() => ui.setCurrentTransaction(entry)}
|
||||
h="full"
|
||||
// minH="24px"
|
||||
borderLeftRadius={0}
|
||||
variant="solid"
|
||||
px={0}
|
||||
minW="24px"
|
||||
colorScheme="secondary"
|
||||
icon={<ArrowRightIcon w="24px" />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default StreamEntry;
|
||||
const StreamChakraEntry = chakra(StreamEntry);
|
||||
|
||||
export default StreamChakraEntry;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { Skeleton, IconButton } from "@chakra-ui/react";
|
||||
import { Skeleton, IconButton, Container } from "@chakra-ui/react";
|
||||
import {
|
||||
Table,
|
||||
Th,
|
||||
|
@ -12,6 +12,7 @@ import {
|
|||
EditableInput,
|
||||
Image,
|
||||
EditablePreview,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import moment from "moment";
|
||||
|
@ -20,7 +21,7 @@ import { useSubscriptions } from "../core/hooks";
|
|||
import ConfirmationRequest from "./ConfirmationRequest";
|
||||
import ColorSelector from "./ColorSelector";
|
||||
|
||||
const SubscriptionsList = () => {
|
||||
const SubscriptionsList = ({ emptyCTA }) => {
|
||||
const { subscriptionsCache, updateSubscription, deleteSubscription } =
|
||||
useSubscriptions();
|
||||
|
||||
|
@ -31,7 +32,10 @@ const SubscriptionsList = () => {
|
|||
updateSubscription.mutate(data);
|
||||
};
|
||||
|
||||
if (subscriptionsCache.data) {
|
||||
if (
|
||||
subscriptionsCache.data &&
|
||||
subscriptionsCache.data.subscriptions.length > 0
|
||||
) {
|
||||
return (
|
||||
<Table
|
||||
borderColor="gray.200"
|
||||
|
@ -138,6 +142,16 @@ const SubscriptionsList = () => {
|
|||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
} else if (
|
||||
subscriptionsCache.data &&
|
||||
subscriptionsCache.data.subscriptions.length === 0
|
||||
) {
|
||||
return (
|
||||
<Container>
|
||||
{` You don't have any subscriptions at the moment.`}
|
||||
{emptyCTA && <Button variant="suggested">Create one</Button>}
|
||||
</Container>
|
||||
);
|
||||
} else if (subscriptionsCache.isLoading) {
|
||||
return <Skeleton />;
|
||||
} else {
|
||||
|
|
|
@ -16,7 +16,6 @@ const toEth = (wei) => {
|
|||
const TxABI = (props) => {
|
||||
const byteCode = props.byteCode;
|
||||
const abi = props.abi;
|
||||
console.log(abi.functions);
|
||||
return (
|
||||
<VStack spacing={3}>
|
||||
<br />
|
||||
|
@ -58,25 +57,29 @@ const TxInfo = (props) => {
|
|||
return (
|
||||
<Box boxShadow="xs" p="6" rounded="md">
|
||||
<StatGroup>
|
||||
<Stat>
|
||||
<Stat px={2}>
|
||||
<StatLabel>Value</StatLabel>
|
||||
<StatNumber>{toEth(transaction.tx.value)} eth</StatNumber>
|
||||
<StatNumber fontSize="md">
|
||||
{toEth(transaction.tx.value)} eth
|
||||
</StatNumber>
|
||||
<StatHelpText>amount of ETH to transfer</StatHelpText>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel>Gas limit</StatLabel>
|
||||
<StatNumber>{transaction.tx.gas}</StatNumber>
|
||||
<StatNumber fontSize="md">{transaction.tx.gas}</StatNumber>
|
||||
<StatHelpText>Maximum amount of gas </StatHelpText>
|
||||
<StatHelpText>provided for the transaction</StatHelpText>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel>Gas price</StatLabel>
|
||||
<StatNumber>{toEth(transaction.tx.gasPrice)} eth</StatNumber>
|
||||
<StatNumber fontSize="md">
|
||||
{toEth(transaction.tx.gasPrice)} eth
|
||||
</StatNumber>
|
||||
<StatHelpText>the fee the sender pays per unit of gas</StatHelpText>
|
||||
</Stat>
|
||||
</StatGroup>
|
||||
|
||||
<Table variant="simple">
|
||||
<Table variant="simple" size="sm">
|
||||
<Tbody>
|
||||
{Object.keys(transaction.tx)
|
||||
.filter(dont_display)
|
||||
|
@ -85,7 +88,7 @@ const TxInfo = (props) => {
|
|||
transaction.tx[key] != undefined && (
|
||||
<Tr key={key}>
|
||||
<Td>{key}</Td>
|
||||
<Td>{transaction.tx[key]}</Td>
|
||||
<Td wordBreak="break-all">{transaction.tx[key]}</Td>
|
||||
</Tr>
|
||||
)
|
||||
)}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { useEffect } from "react";
|
||||
import { useContext } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
import { AuthService } from "../services";
|
||||
import { useUser, useToast, useInviteAccept, useRouter, useAnalytics } from ".";
|
||||
import UIContext from "../providers/UIProvider/context";
|
||||
|
||||
const useSignUp = (source) => {
|
||||
const ui = useContext(UIContext);
|
||||
const router = useRouter();
|
||||
const { getUser } = useUser();
|
||||
const toast = useToast();
|
||||
|
@ -30,28 +32,16 @@ const useSignUp = (source) => {
|
|||
{ full_url: router.nextRouter.asPath, code: source }
|
||||
);
|
||||
}
|
||||
getUser();
|
||||
ui.setisOnboardingComplete(false);
|
||||
ui.setOnboardingState({ welcome: 0, subscriptions: 0, stream: 0 });
|
||||
router.push("/welcome", undefined, { shallow: false });
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(error, "error");
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
getUser();
|
||||
|
||||
const requested_pricing = window.sessionStorage.getItem(
|
||||
"requested_pricing_plan"
|
||||
);
|
||||
const redirectURL = requested_pricing ? "/subscriptions" : "/stream";
|
||||
|
||||
router.push(redirectURL);
|
||||
window.sessionStorage.clear("requested_pricing");
|
||||
}, [data, getUser, router]);
|
||||
|
||||
return {
|
||||
signUp,
|
||||
isLoading,
|
||||
|
|
|
@ -5,6 +5,7 @@ export const MIXPANEL_PROPS = {
|
|||
PRICING_PLAN: "Pricing Plan",
|
||||
TOGGLE_LEFT_SIDEBAR: "Toggle left sidebar",
|
||||
BUTTON_NAME: "Button",
|
||||
USER_SPECIALITY: "user speciality",
|
||||
};
|
||||
|
||||
export const MIXPANEL_EVENTS = {
|
||||
|
|
|
@ -17,15 +17,7 @@ const UIProvider = ({ children }) => {
|
|||
xl: false,
|
||||
"2xl": false,
|
||||
});
|
||||
|
||||
const currentBreakpoint = useBreakpointValue({
|
||||
base: 0,
|
||||
sm: 1,
|
||||
md: 2,
|
||||
lg: 3,
|
||||
xl: 4,
|
||||
"2xl": 5,
|
||||
});
|
||||
// const isMobileView = true;
|
||||
|
||||
const { modal, toggleModal } = useContext(ModalContext);
|
||||
const [searchTerm, setSearchTerm] = useQuery("q", "", true, false);
|
||||
|
@ -44,7 +36,8 @@ const UIProvider = ({ children }) => {
|
|||
router.nextRouter.asPath.includes("/stream") ||
|
||||
router.nextRouter.asPath.includes("/account") ||
|
||||
router.nextRouter.asPath.includes("/subscriptions") ||
|
||||
router.nextRouter.asPath.includes("/analytics")
|
||||
router.nextRouter.asPath.includes("/analytics") ||
|
||||
router.nextRouter.asPath.includes("/welcome")
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -81,7 +74,8 @@ const UIProvider = ({ children }) => {
|
|||
router.nextRouter.asPath.includes("/stream") ||
|
||||
router.nextRouter.asPath.includes("/account") ||
|
||||
router.nextRouter.asPath.includes("/subscriptions") ||
|
||||
router.nextRouter.asPath.includes("/analytics")
|
||||
router.nextRouter.asPath.includes("/analytics") ||
|
||||
router.nextRouter.asPath.includes("/welcome")
|
||||
);
|
||||
}, [router.nextRouter.asPath, user]);
|
||||
|
||||
|
@ -119,7 +113,7 @@ const UIProvider = ({ children }) => {
|
|||
//Sidebar is visible at at breakpoint value less then 2
|
||||
//Sidebar is visible always in appView
|
||||
useEffect(() => {
|
||||
if (currentBreakpoint < 2) {
|
||||
if (isMobileView) {
|
||||
setSidebarVisible(true);
|
||||
setSidebarCollapsed(false);
|
||||
} else {
|
||||
|
@ -130,7 +124,7 @@ const UIProvider = ({ children }) => {
|
|||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentBreakpoint, isAppView]);
|
||||
}, [isMobileView, isAppView]);
|
||||
|
||||
// *********** Entries layout states **********************
|
||||
|
||||
|
@ -159,6 +153,81 @@ const UIProvider = ({ children }) => {
|
|||
);
|
||||
}, [isEntryDetailView, isMobileView]);
|
||||
|
||||
// *********** Onboarding state **********************
|
||||
|
||||
const onboardingSteps = [
|
||||
{
|
||||
step: "welcome",
|
||||
description: "Basics of how Moonstream works",
|
||||
},
|
||||
{
|
||||
step: "subscriptions",
|
||||
description: "Learn how to subscribe to blockchain events",
|
||||
},
|
||||
{
|
||||
step: "stream",
|
||||
description: "Learn how to use your Moonstream to analyze blah blah blah",
|
||||
},
|
||||
];
|
||||
|
||||
const [onboardingState, setOnboardingState] = useStorage(
|
||||
window.localStorage,
|
||||
"onboardingState",
|
||||
{
|
||||
welcome: 0,
|
||||
subscriptions: 0,
|
||||
stream: 0,
|
||||
}
|
||||
);
|
||||
|
||||
const [onboardingStep, setOnboardingStep] = useState(() => {
|
||||
const step = onboardingSteps.findIndex(
|
||||
(event) => onboardingState[event.step] === 0
|
||||
);
|
||||
if (step === -1 && isOnboardingComplete) return onboardingSteps.length - 1;
|
||||
else if (step === -1 && !isOnboardingComplete) return 0;
|
||||
else return step;
|
||||
});
|
||||
|
||||
const [isOnboardingComplete, setisOnboardingComplete] = useStorage(
|
||||
window.localStorage,
|
||||
"isOnboardingComplete",
|
||||
isLoggedIn ? true : false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && !isOnboardingComplete) {
|
||||
router.replace("/welcome");
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [isLoggedIn, isOnboardingComplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
onboardingSteps.findIndex(
|
||||
(event) => onboardingState[event.step] === 0
|
||||
) === -1
|
||||
) {
|
||||
setisOnboardingComplete(true);
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
}, [onboardingState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.nextRouter.pathname === "/welcome") {
|
||||
const newOnboardingState = {
|
||||
...onboardingState,
|
||||
[`${onboardingSteps[onboardingStep].step}`]:
|
||||
onboardingState[onboardingSteps[onboardingStep].step] + 1,
|
||||
};
|
||||
|
||||
setOnboardingState(newOnboardingState);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
}, [onboardingStep, router.nextRouter.pathname]);
|
||||
|
||||
// const ONBOARDING_STEP_NUM = steps.length;
|
||||
|
||||
// ********************************************************
|
||||
|
||||
return (
|
||||
|
@ -188,6 +257,12 @@ const UIProvider = ({ children }) => {
|
|||
currentTransaction,
|
||||
setCurrentTransaction,
|
||||
isEntryDetailView,
|
||||
onboardingStep,
|
||||
setOnboardingStep,
|
||||
isOnboardingComplete,
|
||||
setisOnboardingComplete,
|
||||
onboardingSteps,
|
||||
setOnboardingState,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
Ładowanie…
Reference in New Issue