kopia lustrzana https://github.com/bugout-dev/moonstream
Initial commit with something working
rodzic
0e8fc39824
commit
66f7510ff1
|
@ -0,0 +1,2 @@
|
|||
.vscode/
|
||||
.DS_store
|
|
@ -17,14 +17,18 @@
|
|||
"axios": "^0.21.1",
|
||||
"framer-motion": "^4.1.17",
|
||||
"mixpanel-browser": "^2.41.0",
|
||||
"moment": "^2.29.1",
|
||||
"next": "11.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-calendly": "^2.2.1",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hook-form": "^6.9.2",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-pro-sidebar": "^0.6.0",
|
||||
"react-query": "^3.18.1",
|
||||
"react-showdown": "^2.3.0",
|
||||
"react-split-pane": "^0.1.92",
|
||||
"showdown": "^1.9.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
|
@ -35,6 +39,7 @@
|
|||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@testing-library/dom": "^7.31.2",
|
||||
"@types/react": "17.0.13",
|
||||
"axios-mock-adapter": "^1.19.0",
|
||||
"eslint": "7.29.0",
|
||||
"eslint-config-next": "11.0.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useChangePassword, useRouter } from "../../src/core/hooks";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
InputGroup,
|
||||
Stack,
|
||||
Center,
|
||||
Button,
|
||||
Input,
|
||||
InputRightElement,
|
||||
ScaleFade,
|
||||
Heading,
|
||||
} from "@chakra-ui/react";
|
||||
import { Icon } from "../../src/Theme";
|
||||
|
||||
import { getLayout } from "../../src/layouts/AccountLayout";
|
||||
const Security = () => {
|
||||
const headingStyle = {
|
||||
as: "h2",
|
||||
pt: 2,
|
||||
mb: 4,
|
||||
borderBottom: "solid",
|
||||
borderColor: "primary.50",
|
||||
borderBottomWidth: "2px",
|
||||
};
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { handleSubmit, errors, register, setError } = useForm();
|
||||
const { changePassword, data, isLoading } = useChangePassword();
|
||||
const [showPassword, setShowPassword] = useState({
|
||||
password: "password",
|
||||
newPassword: "password",
|
||||
confirmPassword: "password",
|
||||
});
|
||||
|
||||
const togglePassword = (key) => {
|
||||
if (showPassword[key] === "password") {
|
||||
setShowPassword({ ...showPassword, [key]: "text" });
|
||||
} else {
|
||||
setShowPassword({ ...showPassword, [key]: "password" });
|
||||
}
|
||||
};
|
||||
const change = (data) => {
|
||||
if (data.newPassword !== data.confirmPassword) {
|
||||
return setError("confirmPassword", {
|
||||
type: "manual",
|
||||
message: "New password and confirm password does not match",
|
||||
});
|
||||
} else {
|
||||
changePassword({
|
||||
newPassword: data.newPassword,
|
||||
currentPassword: data.currentPassword,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) router.push("/");
|
||||
}, [data, router]);
|
||||
|
||||
return (
|
||||
<ScaleFade in>
|
||||
<Heading {...headingStyle}> Change password </Heading>
|
||||
|
||||
<Box alignSelf="flex-start" width="100%">
|
||||
<Center>
|
||||
<form className="form" onSubmit={handleSubmit(change)}>
|
||||
<Stack width="100%" pt={4} spacing={3}>
|
||||
<FormControl isInvalid={errors.newPassword}>
|
||||
<InputGroup>
|
||||
<Input
|
||||
placeholder="Current password"
|
||||
autoComplete="current-password"
|
||||
name="currentPassword"
|
||||
type={showPassword.password}
|
||||
ref={register({
|
||||
required: "Current password is required!",
|
||||
})}
|
||||
/>
|
||||
<InputRightElement onClick={() => togglePassword("password")}>
|
||||
<Icon icon="password" />
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.newPassword && errors.newPassword.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={errors.newPassword}>
|
||||
<InputGroup>
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
placeholder="New password"
|
||||
name="newPassword"
|
||||
type={showPassword.newPassword}
|
||||
ref={register({ required: "Password is required!" })}
|
||||
/>
|
||||
<InputRightElement
|
||||
onClick={() => togglePassword("newPassword")}
|
||||
>
|
||||
<Icon icon="password" />
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.newPassword && errors.newPassword.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<FormControl isInvalid={errors.confirmPassword}>
|
||||
<InputGroup>
|
||||
<Input
|
||||
autoComplete="new-password"
|
||||
placeholder="Confirm password"
|
||||
name="confirmPassword"
|
||||
type={showPassword.confirmPassword}
|
||||
ref={register({ required: "Password is required!" })}
|
||||
/>
|
||||
<InputRightElement
|
||||
onClick={() => togglePassword("confirmPassword")}
|
||||
>
|
||||
<Icon icon="password" />
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.confirmPassword && errors.confirmPassword.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Stack></Stack>
|
||||
<Center>
|
||||
<Button
|
||||
my={8}
|
||||
variant="solid"
|
||||
colorScheme="primary"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Center>
|
||||
</form>
|
||||
</Center>
|
||||
</Box>
|
||||
</ScaleFade>
|
||||
);
|
||||
};
|
||||
|
||||
Security.getLayout = getLayout;
|
||||
|
||||
export default Security;
|
|
@ -0,0 +1,597 @@
|
|||
import React, { useState, useEffect, Suspense } from "react";
|
||||
import {
|
||||
Flex,
|
||||
Heading,
|
||||
Text,
|
||||
Box,
|
||||
Image,
|
||||
ListItem,
|
||||
Button,
|
||||
Link,
|
||||
ListIcon,
|
||||
List,
|
||||
useBreakpointValue,
|
||||
Center,
|
||||
Fade,
|
||||
} from "@chakra-ui/react";
|
||||
import { Grid, GridItem } from "@chakra-ui/react";
|
||||
import { useUser, useAnalytics, useModals, useRouter } from "../src/core/hooks";
|
||||
import { openPopupWidget, InlineWidget } from "react-calendly";
|
||||
import TrustedBadge from "../src/components/TrustedBadge"
|
||||
import { getLayout } from "../src/layouts";
|
||||
|
||||
const TEXT_PROPS = {
|
||||
fontSize: ["lg", null, "xl"],
|
||||
fontWeight: "600",
|
||||
};
|
||||
|
||||
const HEADING_PROPS = {
|
||||
fontWeight: "700",
|
||||
fontSize: ["4xl", "5xl", "4xl", "5xl", "6xl", "7xl"],
|
||||
};
|
||||
|
||||
const TITLE_PROPS = {
|
||||
fontWeight: "700",
|
||||
fontSize: ["4xl", "5xl", "5xl", "5xl", "6xl", "120px"],
|
||||
};
|
||||
|
||||
const TRIPLE_PICS_PROPS = {
|
||||
fontSize: ["2xl", "3xl", "3xl", "3xl", "4xl", "4xl"],
|
||||
textAlign: "center",
|
||||
fontWeight: "500",
|
||||
py: 4,
|
||||
};
|
||||
|
||||
const TRIPLE_PICS_TEXT = {
|
||||
fontSize: ["lg", "xl", "xl", "xl", "2xl", "3xl"],
|
||||
textAlign: "center",
|
||||
fontWeight: "400",
|
||||
mb: ["2rem", "2rem", "0", null, "0"],
|
||||
};
|
||||
|
||||
const CARD_CONTAINER = {
|
||||
className: "CardContainer",
|
||||
w: "100%",
|
||||
mx: [0, 0, "2rem", null, "4rem"],
|
||||
|
||||
alignSelf: ["center", null, "flex-start"],
|
||||
};
|
||||
|
||||
const IMAGE_CONTAINER = {
|
||||
className: "ImageContainer",
|
||||
h: ["10rem", "14rem", "14rem", "15rem", "18rem", "20rem"],
|
||||
justifyContent: "center",
|
||||
};
|
||||
|
||||
const AWS_PATH = "https://s3.amazonaws.com/static.simiotics.com/landing";
|
||||
|
||||
const assets = {
|
||||
background: `${AWS_PATH}/landing-background-2.png`,
|
||||
aviator: `${AWS_PATH}/aviator-2.svg`,
|
||||
icon1: `${AWS_PATH}/v2/Icon+1.svg`,
|
||||
icon2: `${AWS_PATH}/v2/Icon+2.svg`,
|
||||
icon3: `${AWS_PATH}/v2/Icon+3.svg`,
|
||||
icon4: `${AWS_PATH}/v2/Icon+4.svg`,
|
||||
icon5: `${AWS_PATH}/v2/Icon+5.svg`,
|
||||
icon6: `${AWS_PATH}/v2/Icon+6.svg`,
|
||||
activeloopLogo: `${AWS_PATH}/activeloop.svg`,
|
||||
aiIncubeLogo: `${AWS_PATH}/ai incube.svg`,
|
||||
b612Logo: `${AWS_PATH}/b612.svg`,
|
||||
harvardLogo: `${AWS_PATH}/harvard.svg`,
|
||||
mattermarkLogo: `${AWS_PATH}/mattermark.svg`,
|
||||
mixrankLogo: `${AWS_PATH}/mixrank.svg`,
|
||||
toolchainLogo: `${AWS_PATH}/toolchain.svg`,
|
||||
};
|
||||
const Homepage = () => {
|
||||
const router = useRouter();
|
||||
const buttonSize = useBreakpointValue({
|
||||
base: "md",
|
||||
sm: "md",
|
||||
md: "md",
|
||||
lg: "lg",
|
||||
xl: "xl",
|
||||
"2xl": "xl",
|
||||
});
|
||||
|
||||
const ButtonRadius = "2xl";
|
||||
const buttonWidth = ["100%", "100%", "40%", "45%", "45%", "45%"];
|
||||
const buttonMinWidth = "10rem";
|
||||
const { isInit } = useUser();
|
||||
const { withTracking, MIXPANEL_EVENTS } = useAnalytics();
|
||||
|
||||
const { toggleModal } = useModals();
|
||||
const [scrollDepth, setScrollDepth] = useState(0);
|
||||
|
||||
const getScrollPrecent = ({ currentTarget }) => {
|
||||
const scroll_level =
|
||||
(100 * (currentTarget.scrollTop + currentTarget.clientHeight)) /
|
||||
currentTarget.scrollHeight;
|
||||
return scroll_level;
|
||||
};
|
||||
|
||||
const handleScroll = (e) => {
|
||||
const currentScroll = Math.ceil(getScrollPrecent(e) / 10);
|
||||
|
||||
if (currentScroll > scrollDepth) {
|
||||
withTracking(
|
||||
setScrollDepth(currentScroll),
|
||||
MIXPANEL_EVENTS.HOMEPAGE_SCROLL_DEPTH,
|
||||
scrollDepth
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const DoubleCTAButton = () => (
|
||||
<Flex
|
||||
justifyContent="flex-start"
|
||||
// mt={20}
|
||||
flexWrap="wrap"
|
||||
width="100%"
|
||||
>
|
||||
<Button
|
||||
variant="solid"
|
||||
w={buttonWidth}
|
||||
minW={buttonMinWidth}
|
||||
borderRadius={ButtonRadius}
|
||||
colorScheme="secondary"
|
||||
size={buttonSize}
|
||||
onClick={() => toggleModal("register")}
|
||||
mr="1.25rem"
|
||||
color="white"
|
||||
border="2px solid #D35725"
|
||||
fontWeight="400"
|
||||
>
|
||||
Sign up for free
|
||||
</Button>
|
||||
|
||||
<Suspense fallback={""}>
|
||||
<Button
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
color="black"
|
||||
borderRadius={ButtonRadius}
|
||||
w={buttonWidth}
|
||||
minW={buttonMinWidth}
|
||||
mr="1.25rem"
|
||||
size={buttonSize}
|
||||
fontWeight="400"
|
||||
onClick={() => {
|
||||
openPopupWidget({
|
||||
url: "https://calendly.com/neeraj-simiotics/bugout-30",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Book office hours
|
||||
</Button>
|
||||
</Suspense>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
router.nextRouter.asPath !== "/" &&
|
||||
router.nextRouter.asPath.slice(0, 2) !== "/?"
|
||||
) {
|
||||
router.replace(router.nextRouter.asPath, undefined, {
|
||||
shallow: true,
|
||||
});
|
||||
}
|
||||
}, [isInit, router]);
|
||||
|
||||
return (
|
||||
<Fade in>
|
||||
<Box
|
||||
width="100%"
|
||||
flexDirection="column"
|
||||
onScroll={(e) => handleScroll(e)}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
h="auto"
|
||||
position="relative"
|
||||
w="100%"
|
||||
overflow="initial"
|
||||
>
|
||||
<Suspense fallback={""}></Suspense>
|
||||
|
||||
<Grid templateColumns="repeat(12,1fr)">
|
||||
<GridItem px="7%" colSpan="12" pb={[1, 2, null, 8]}>
|
||||
<Flex w="100%" wrap="wrap">
|
||||
<Flex
|
||||
direction="column"
|
||||
alignItems="left"
|
||||
pr={[0, null, 24]}
|
||||
flexBasis="300px"
|
||||
flexGrow={1}
|
||||
>
|
||||
<Heading
|
||||
pt={["2rem", "3rem", "3rem", "10rem", "12rem", "14rem"]}
|
||||
{...TITLE_PROPS}
|
||||
mb={3}
|
||||
fontWeight="700"
|
||||
>
|
||||
Measure the success of your dev tool
|
||||
</Heading>
|
||||
<Text
|
||||
fontSize={["3xl", "4xl", "3xl", "4xl", "5xl", "6xl"]}
|
||||
pb={[0, 0, 0, 0, 0, "3rem"]}
|
||||
mb={20}
|
||||
fontWeight="600"
|
||||
color="primary.1000"
|
||||
lineHeight="100%"
|
||||
>
|
||||
Get usage metrics and crash reports <br />{" "}
|
||||
{`Improve your
|
||||
users' experience`}
|
||||
</Text>
|
||||
<DoubleCTAButton />
|
||||
</Flex>
|
||||
<Flex flexBasis="200px" flexGrow={1} flexShrink={1}>
|
||||
<Image
|
||||
rel="preconnect"
|
||||
src={assets["aviator"]}
|
||||
alt="Bugout is on the fly to report your crashes"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
px="7%"
|
||||
colSpan="12"
|
||||
pt={["20px", "20px", "100px", null, "120px"]}
|
||||
pb={["20px", "56px", null, "184px"]}
|
||||
bgSize="cover"
|
||||
bgImage={`url(${assets["background"]})`}
|
||||
>
|
||||
<Heading
|
||||
{...HEADING_PROPS}
|
||||
textAlign="center"
|
||||
pb={[12, 12, 12, null, 48]}
|
||||
>
|
||||
See what your users are experiencing with your library, API, or
|
||||
command line tool
|
||||
</Heading>
|
||||
|
||||
<Flex
|
||||
direction={["column", null, "row"]}
|
||||
flexWrap="nowrap"
|
||||
justifyContent={["center", null, "space-evenly"]}
|
||||
>
|
||||
<Box {...CARD_CONTAINER}>
|
||||
<Flex {...IMAGE_CONTAINER}>
|
||||
<Image
|
||||
w="100%"
|
||||
src={assets["icon2"]}
|
||||
alt="privacy is our prioriy"
|
||||
/>
|
||||
</Flex>
|
||||
<Heading {...TRIPLE_PICS_PROPS}>
|
||||
Catch and fix bugs faster
|
||||
</Heading>
|
||||
<Text {...TRIPLE_PICS_TEXT}>
|
||||
Learn about errors as they occur, with full stack traces.
|
||||
</Text>
|
||||
</Box>
|
||||
<Box {...CARD_CONTAINER}>
|
||||
<Flex {...IMAGE_CONTAINER}>
|
||||
<Image w="100%" src={assets["icon1"]} alt="live metrics" />
|
||||
</Flex>
|
||||
<Heading {...TRIPLE_PICS_PROPS}>
|
||||
Understand your user engagement and retention
|
||||
</Heading>
|
||||
<Text {...TRIPLE_PICS_TEXT}>
|
||||
Learn how users are using your tool, and how frequently.
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box {...CARD_CONTAINER}>
|
||||
<Flex {...IMAGE_CONTAINER}>
|
||||
<Image
|
||||
w="100%"
|
||||
src={assets["icon3"]}
|
||||
alt="we make it simple for user"
|
||||
/>
|
||||
</Flex>
|
||||
<Heading {...TRIPLE_PICS_PROPS}>
|
||||
Inform your product roadmap
|
||||
</Heading>
|
||||
<Text {...TRIPLE_PICS_TEXT}>
|
||||
Understand which features people are actually using.
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Text
|
||||
textAlign="center"
|
||||
fontSize={["xl", "2xl", "2xl", "3xl", "4xl", "5xl"]}
|
||||
fontWeight="600"
|
||||
pt={[4, null, 12]}
|
||||
>
|
||||
We currently support Python, Javascript and Go!
|
||||
<br />
|
||||
Want us to support other programming languages?{" "}
|
||||
<Button
|
||||
size="2xl"
|
||||
colorScheme="primary"
|
||||
variant="link"
|
||||
onClick={() => toggleModal("Integration")}
|
||||
>
|
||||
Let us know
|
||||
</Button>
|
||||
</Text>
|
||||
</GridItem>
|
||||
<GridItem px="7%" colSpan="12" pt="5.125rem" pb="66px">
|
||||
<Heading {...HEADING_PROPS} textAlign="center" pb={12}>
|
||||
Engage with your users on a deeper level
|
||||
</Heading>
|
||||
|
||||
<Flex
|
||||
direction={["column", null, "row"]}
|
||||
flexWrap="nowrap"
|
||||
justifyContent={["center", null, "space-evenly"]}
|
||||
>
|
||||
<Box {...CARD_CONTAINER}>
|
||||
<Flex {...IMAGE_CONTAINER}>
|
||||
<Image w="100%" src={assets["icon4"]} alt="live metrics" />
|
||||
</Flex>
|
||||
<Heading {...TRIPLE_PICS_PROPS}>Live dashboards</Heading>
|
||||
<Text {...TRIPLE_PICS_TEXT}>
|
||||
See your users’ journeys as they happen
|
||||
</Text>
|
||||
</Box>
|
||||
<Box {...CARD_CONTAINER}>
|
||||
<Flex {...IMAGE_CONTAINER}>
|
||||
<Image
|
||||
w="100%"
|
||||
src={assets["icon5"]}
|
||||
alt="privacy is our prioriy"
|
||||
/>
|
||||
</Flex>
|
||||
<Heading {...TRIPLE_PICS_PROPS}>GDPR compliance</Heading>
|
||||
<Text {...TRIPLE_PICS_TEXT}>
|
||||
Automatically handle GDPR-related user requests
|
||||
</Text>
|
||||
</Box>
|
||||
<Box {...CARD_CONTAINER}>
|
||||
<Flex {...IMAGE_CONTAINER}>
|
||||
<Image
|
||||
w="100%"
|
||||
src={assets["icon6"]}
|
||||
alt="we make it simple for user"
|
||||
/>
|
||||
</Flex>
|
||||
<Heading {...TRIPLE_PICS_PROPS}>
|
||||
Simple user consent flows
|
||||
</Heading>
|
||||
<Text {...TRIPLE_PICS_TEXT}>
|
||||
Define principled user consent flows in only a few lines of
|
||||
code.
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Center>
|
||||
<Flex
|
||||
m={0}
|
||||
mt="120px"
|
||||
flexWrap="wrap"
|
||||
width="100%"
|
||||
w={["360px", "360px", "500px", null, "800px"]}
|
||||
>
|
||||
<Button
|
||||
variant="solid"
|
||||
w={buttonWidth}
|
||||
minW="14rem"
|
||||
borderRadius={ButtonRadius}
|
||||
colorScheme="secondary"
|
||||
size={buttonSize}
|
||||
onClick={() => toggleModal("register")}
|
||||
mr="1.25rem"
|
||||
color="white"
|
||||
border="2px solid #D35725"
|
||||
fontWeight="400"
|
||||
>
|
||||
Sign up for free
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
colorScheme="gray"
|
||||
color="black"
|
||||
borderRadius={ButtonRadius}
|
||||
w={buttonWidth}
|
||||
minW="14rem"
|
||||
mr="1.25rem"
|
||||
size={buttonSize}
|
||||
fontWeight="400"
|
||||
onClick={() => {
|
||||
openPopupWidget({
|
||||
url: "https://calendly.com/neeraj-simiotics/bugout-30",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Book office hours
|
||||
</Button>
|
||||
</Flex>
|
||||
</Center>
|
||||
</GridItem>
|
||||
<GridItem
|
||||
px="7%"
|
||||
colSpan="12"
|
||||
pt="66px"
|
||||
bgColor="primary.50"
|
||||
pb={["20px", "30px", "92px", null, "92px", "196px"]}
|
||||
>
|
||||
<Heading {...HEADING_PROPS} textAlign="center" pb={14} pt={0}>
|
||||
Loved by proactive teams{" "}
|
||||
<span role="img" aria-label="heart">
|
||||
💙
|
||||
</span>
|
||||
</Heading>
|
||||
<Flex wrap="wrap" direction="row" justifyContent="center">
|
||||
<Suspense fallback={""}>
|
||||
<TrustedBadge
|
||||
name="activeloop"
|
||||
caseURL="/case-studies/activeloop"
|
||||
ImgURL={assets["activeloopLogo"]}
|
||||
/>
|
||||
<TrustedBadge
|
||||
name="ai incube"
|
||||
ImgURL={assets["aiIncubeLogo"]}
|
||||
/>
|
||||
<TrustedBadge name="b612" ImgURL={assets["b612Logo"]} />
|
||||
<TrustedBadge name="harvard" ImgURL={assets["harvardLogo"]} />
|
||||
<TrustedBadge
|
||||
name="mattermark"
|
||||
ImgURL={assets["mattermarkLogo"]}
|
||||
/>
|
||||
<TrustedBadge name="mixrank" ImgURL={assets["mixrankLogo"]} />
|
||||
<TrustedBadge
|
||||
name="toolchain"
|
||||
ImgURL={assets["toolchainLogo"]}
|
||||
/>
|
||||
</Suspense>
|
||||
</Flex>
|
||||
</GridItem>
|
||||
<GridItem px="7%" colSpan="12" py={24}>
|
||||
<Heading textAlign="center" {...HEADING_PROPS} pb={8}>
|
||||
Ready to learn more?
|
||||
</Heading>
|
||||
<Text textAlign="center" {...TEXT_PROPS}>
|
||||
Book office hours with Neeraj Kashyap (
|
||||
<Link
|
||||
color="primary.500"
|
||||
isExternal
|
||||
href="https://github.com/zomglings"
|
||||
>
|
||||
@zomglings
|
||||
</Link>
|
||||
), CEO of Bugout
|
||||
</Text>
|
||||
</GridItem>
|
||||
|
||||
<GridItem px="7%" colSpan="12"></GridItem>
|
||||
</Grid>
|
||||
<Flex
|
||||
bg="primary.1200"
|
||||
direction={["column", null, "row"]}
|
||||
w="100%"
|
||||
py="2rem"
|
||||
px="7%"
|
||||
h="100%"
|
||||
>
|
||||
<Flex
|
||||
w={["100%", null, "50%"]}
|
||||
textColor="white.200"
|
||||
direction="column"
|
||||
pr={[0, 0, 12, null, 12]}
|
||||
>
|
||||
<Heading
|
||||
fontSize={["xl", "3xl", null, "3xl", "5xl", "5xl"]}
|
||||
fontWeight="500"
|
||||
alignSelf="left"
|
||||
pt={[4, 4, 24, null, 24]}
|
||||
pb={[6, 6, 12, null, 16]}
|
||||
>
|
||||
{`Let's talk about:`}
|
||||
</Heading>
|
||||
<List
|
||||
fontWeight="400"
|
||||
fontSize={["md", null, "2xl", "2xl", "3xl", "4xl"]}
|
||||
alignSelf="center"
|
||||
spacing={[4, 4, 8, null, 8]}
|
||||
>
|
||||
<ListItem>
|
||||
<ListIcon as={() => "- "} />
|
||||
How to measure and improve the quality of your users’
|
||||
experience
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListIcon as={() => "- "} />
|
||||
Ethical data collection
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListIcon as={() => "- "} />
|
||||
Developer tools best practices, pulling from our experience at
|
||||
OpenAI and Google (TensorFlow, Kubeflow, Google Cloud)
|
||||
</ListItem>
|
||||
</List>
|
||||
<Heading
|
||||
fontSize={["xl", "3xl", null, "3xl", "5xl", "5xl"]}
|
||||
fontWeight="500"
|
||||
pt={[4, null, 8, 16, 24]}
|
||||
>
|
||||
Book now →
|
||||
</Heading>
|
||||
</Flex>
|
||||
<Flex className="CalendlyWrapper" w={["100%", null, "45%"]}>
|
||||
<InlineWidget
|
||||
styles={{
|
||||
width: "100%",
|
||||
height: "720px",
|
||||
}}
|
||||
hid
|
||||
url="https://calendly.com/neeraj-simiotics/bugout-30?hide_event_type_details=1"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Grid px="7%" templateColumns="repeat(12,1fr)">
|
||||
<GridItem colSpan="4" py={2}>
|
||||
|
||||
</GridItem>
|
||||
<GridItem colSpan="4" py={2}>
|
||||
<Center>
|
||||
<iframe
|
||||
title="substack"
|
||||
src="https://bugout.substack.com/embed"
|
||||
width="480"
|
||||
height="320"
|
||||
frameBorder="0"
|
||||
scrolling="no"
|
||||
></iframe>
|
||||
</Center>
|
||||
</GridItem>
|
||||
<GridItem colSpan="4" py={2}>
|
||||
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps() {
|
||||
const metaTags = {
|
||||
title: "Bugout: Measure the success of your dev tool",
|
||||
description:
|
||||
"Get usage metrics and crash reports. Improve your users' experience",
|
||||
keywords:
|
||||
"bugout, bugout-dev, bugout.dev, usage-metrics, analytics, dev-tool ,knowledge, docs, journal, entry, find-anything",
|
||||
url: "https://bugout.dev",
|
||||
image:
|
||||
"https://s3.amazonaws.com/static.simiotics.com/landing/aviator-2.svg",
|
||||
};
|
||||
|
||||
const assetPreload = Object.keys(assets).map((key) => {
|
||||
return {
|
||||
rel: "preload",
|
||||
href: assets[key],
|
||||
as: "image",
|
||||
};
|
||||
});
|
||||
const preconnects = [
|
||||
{ rel: "preconnect", href: "https://s3.amazonaws.com" },
|
||||
{ rel: "preconnect", href: "https://assets.calendly.com/" },
|
||||
];
|
||||
|
||||
const preloads = assetPreload.concat(preconnects);
|
||||
|
||||
return {
|
||||
props: { metaTags, preloads },
|
||||
};
|
||||
}
|
||||
|
||||
Homepage.layout = "default";
|
||||
Homepage.getLayout = getLayout;
|
||||
|
||||
export default Homepage;
|
|
@ -1,8 +0,0 @@
|
|||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>hello world!</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
import React from "react";
|
||||
import { Flex, Link, HStack, Skeleton, Box, Title } from "@chakra-ui/react";
|
||||
import { ExternalLinkIcon } from "@chakra-ui/icons";
|
||||
import { useJournalEntry, useRouter } from "../../src/core/hooks";
|
||||
import FourOFour from "../../src/components/FourOFour";
|
||||
import FourOThree from "../../src/components/FourOThree";
|
||||
import Tags from "../../src/components/Tags";
|
||||
import CustomIcon from "../../src/components/CustomIcon";
|
||||
import { getLayout } from "../../src/layouts/EntriesLayout";
|
||||
import MarkdownView from "react-showdown";
|
||||
|
||||
const Entry = () => {
|
||||
const router = useRouter();
|
||||
const { entryId } = router.params;
|
||||
const journalId = `9b0d7567-4634-4bf7-946d-60ef4414aa93`;
|
||||
const {
|
||||
data: entry,
|
||||
isFetchedAfterMount,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useJournalEntry(journalId, entryId);
|
||||
|
||||
const contextUrl = () => {
|
||||
if (entry?.context_url) {
|
||||
switch (entry.context_type) {
|
||||
case "slack":
|
||||
return (
|
||||
<Link href={entry.context_url} isExternal>
|
||||
<CustomIcon width="28px" icon="slack" />
|
||||
</Link>
|
||||
);
|
||||
case "github":
|
||||
return (
|
||||
<Link href={entry.context_url} isExternal>
|
||||
<CustomIcon width="28px" icon="github" />
|
||||
</Link>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Link href={entry.context_url} isExternal>
|
||||
<ExternalLinkIcon bg="none" boxSize="18px" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
} else return "";
|
||||
};
|
||||
|
||||
if (isError && error.response.status === 404) return <FourOFour />;
|
||||
if (isError && error.response.status === 403) return <FourOThree />;
|
||||
// if (!entry || isLoading) return "";
|
||||
|
||||
return (
|
||||
<Flex
|
||||
id="Entry"
|
||||
height="100%"
|
||||
flexGrow="1"
|
||||
flexDirection="column"
|
||||
key={entryId}
|
||||
>
|
||||
<Skeleton
|
||||
id="EntryNameSkeleton"
|
||||
mx={2}
|
||||
mt={2}
|
||||
overflow="initial"
|
||||
isLoaded={!isLoading}
|
||||
>
|
||||
<HStack id="EntryHeader" width="100%" m={0}>
|
||||
<Box
|
||||
id="ContextURL"
|
||||
transition="0.3s"
|
||||
_hover={{ transform: "scale(1.2)" }}
|
||||
pl={2}
|
||||
pr={entry?.context_url ? 2 : 0}
|
||||
>
|
||||
{contextUrl()}
|
||||
</Box>
|
||||
<Title
|
||||
overflow="hidden"
|
||||
width={entry?.context_url ? "calc(100% - 28px)" : "100%"}
|
||||
// height="auto"
|
||||
minH="36px"
|
||||
style={{ marginLeft: "0" }}
|
||||
m={0}
|
||||
p={0}
|
||||
fontWeight="600"
|
||||
fontSize="1.5rem"
|
||||
textAlign="left"
|
||||
>
|
||||
{entry?.title}
|
||||
</Title>
|
||||
</HStack>
|
||||
</Skeleton>
|
||||
<Skeleton
|
||||
id="TagsSkeleton"
|
||||
mx={2}
|
||||
overflow="initial"
|
||||
mt={1}
|
||||
isLoaded={isFetchedAfterMount || entry}
|
||||
>
|
||||
<Tags entry={entry} />
|
||||
</Skeleton>
|
||||
<Skeleton
|
||||
height="10px"
|
||||
flexGrow={1}
|
||||
id="EditorSkeleton"
|
||||
mx={2}
|
||||
mt={1}
|
||||
isLoaded={isFetchedAfterMount || entry}
|
||||
>
|
||||
<MarkdownView
|
||||
markdown={entry?.content}
|
||||
options={{ tables: true, emoji: true }}
|
||||
/>
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
Entry.getLayout = getLayout;
|
||||
export default Entry;
|
|
@ -0,0 +1,6 @@
|
|||
import { getLayout } from "../../src/layouts/EntriesLayout";
|
||||
const Entry = () => {
|
||||
return "";
|
||||
};
|
||||
Entry.getLayout = getLayout;
|
||||
export default Entry;
|
|
@ -0,0 +1,112 @@
|
|||
import { getLayout } from "../src/layouts/AppLayout";
|
||||
import React, { useState } from "react";
|
||||
import SubscriptionsList from "../src/components/SubscriptionsList";
|
||||
import { useSubscriptions } from "../src/core/hooks";
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Spinner,
|
||||
ScaleFade,
|
||||
Heading,
|
||||
Flex,
|
||||
Button,
|
||||
Modal,
|
||||
useDisclosure,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
} from "@chakra-ui/react";
|
||||
import { headingStyle } from "./index";
|
||||
import NewSubscription from "../src/components/NewSubscription";
|
||||
import { AiOutlinePlusCircle } from "react-icons/ai";
|
||||
|
||||
const Subscriptions = () => {
|
||||
const { subscriptionsCache } = useSubscriptions();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [isAddingFreeSubscription, setIsAddingFreeSubscription] = useState();
|
||||
|
||||
document.title = `My Subscriptions`;
|
||||
|
||||
const newSubscriptionClicked = (isForFree) => {
|
||||
setIsAddingFreeSubscription(isForFree);
|
||||
onOpen();
|
||||
};
|
||||
return (
|
||||
<Box w="100%" px="7%" pt={2}>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size="2xl"
|
||||
scrollBehavior="outside"
|
||||
>
|
||||
<ModalOverlay />
|
||||
|
||||
<ModalContent>
|
||||
<NewSubscription
|
||||
isFreeOption={isAddingFreeSubscription}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{subscriptionsCache.isLoading ? (
|
||||
<Center>
|
||||
<Spinner
|
||||
hidden={false}
|
||||
// ref={loadMoreButtonRef}
|
||||
my={8}
|
||||
size="lg"
|
||||
color="primary.500"
|
||||
thickness="4px"
|
||||
speed="1.5s"
|
||||
/>
|
||||
</Center>
|
||||
) : (
|
||||
<ScaleFade in>
|
||||
<Heading {...headingStyle}> My Subscriptions </Heading>
|
||||
<Flex
|
||||
mt={4}
|
||||
overflow="initial"
|
||||
maxH="unset"
|
||||
height="100%"
|
||||
direction="column"
|
||||
>
|
||||
<Flex
|
||||
h="3rem"
|
||||
w="100%"
|
||||
bgColor="primary.50"
|
||||
borderTopRadius="xl"
|
||||
justifyContent="flex-end"
|
||||
alignItems="center"
|
||||
>
|
||||
{subscriptionsCache.data?.is_free_subscription_availible && (
|
||||
<Button
|
||||
onClick={() => newSubscriptionClicked(true)}
|
||||
mr={8}
|
||||
colorScheme="suggested"
|
||||
variant="solid"
|
||||
size="sm"
|
||||
rightIcon={<AiOutlinePlusCircle />}
|
||||
>
|
||||
Add for free
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => newSubscriptionClicked(false)}
|
||||
mr={8}
|
||||
colorScheme="primary"
|
||||
variant="solid"
|
||||
size="sm"
|
||||
rightIcon={<AiOutlinePlusCircle />}
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
</Flex>
|
||||
<SubscriptionsList />
|
||||
</Flex>
|
||||
</ScaleFade>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Subscriptions.getLayout = getLayout;
|
||||
export default Subscriptions;
|
|
@ -1,4 +1,4 @@
|
|||
import { jsx } from "@emotion/react";
|
||||
import React from "react";
|
||||
import { ChakraProvider } from "@chakra-ui/react";
|
||||
import theme from "./Theme/theme";
|
||||
import {
|
||||
|
|
|
@ -24,7 +24,7 @@ const AccountIconButton = (props) => {
|
|||
{...props}
|
||||
as={IconButton}
|
||||
aria-label="Account menu"
|
||||
icon={<RiAccountCircleLine />}
|
||||
icon={<RiAccountCircleLine size="26px"/>}
|
||||
// variant="outline"
|
||||
color="gray.100"
|
||||
/>
|
||||
|
@ -35,12 +35,6 @@ const AccountIconButton = (props) => {
|
|||
m={0}
|
||||
>
|
||||
<MenuGroup>
|
||||
<RouterLink href="/account/teams" passHref>
|
||||
<MenuItem>Teams</MenuItem>
|
||||
</RouterLink>
|
||||
<RouterLink href="/account/tokens" passHref>
|
||||
<MenuItem>Tokens</MenuItem>
|
||||
</RouterLink>
|
||||
<RouterLink href="/account/security" passHref>
|
||||
<MenuItem>Security</MenuItem>
|
||||
</RouterLink>
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { useState, useContext, useEffect } from "react";
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import RouterLink from "next/link";
|
||||
import {
|
||||
Flex,
|
||||
|
@ -27,9 +25,8 @@ import {
|
|||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import { IoIosJournal } from "react-icons/io";
|
||||
import { MdTimeline } from "react-icons/md";
|
||||
import useRouter from "../core/hooks/useRouter";
|
||||
import SearchBar from "./SearchBar";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
import AccountIconButton from "./AccountIconButton";
|
||||
|
||||
|
@ -101,48 +98,15 @@ const AppNavbar = () => {
|
|||
{!ui.isMobileView && (
|
||||
<>
|
||||
<Flex
|
||||
minW={["100%", "50%", "60%", "80%", null, "80%"]}
|
||||
width="100%"
|
||||
id="SearchBarwButtons"
|
||||
position="relative"
|
||||
alignItems="baseline"
|
||||
// bgColor="unsafe.100"
|
||||
justifyContent="flex-end"
|
||||
>
|
||||
<IconButton
|
||||
ml={4}
|
||||
colorScheme="blue"
|
||||
aria-label="App navigation"
|
||||
icon={<IoIosJournal />}
|
||||
onClick={() => {
|
||||
ui.isMobileView
|
||||
? ui.setSidebarToggled(!ui.sidebarToggled)
|
||||
: ui.setSidebarVisible(!ui.sidebarVisible);
|
||||
}}
|
||||
/>
|
||||
<SearchBar
|
||||
pl={4}
|
||||
position="absolute"
|
||||
left="64px"
|
||||
w={
|
||||
isSearchBarActive
|
||||
? [
|
||||
"100%",
|
||||
"calc(100% - 80px)",
|
||||
"calc(100% - 0px)",
|
||||
"calc(100% - 0px)",
|
||||
null,
|
||||
"100%",
|
||||
]
|
||||
: ["64px", "64px", "50%", "55%", null, "60%"]
|
||||
}
|
||||
h="2rem"
|
||||
bgColor="primary.1200"
|
||||
alignSelf="center"
|
||||
transition="0.5s"
|
||||
/>
|
||||
{/* <Fade in={!ui.searchBarActive}> */}
|
||||
{!ui.isMobileView && (
|
||||
<ButtonGroup
|
||||
position="relative"
|
||||
// position="relative"
|
||||
left={
|
||||
isSearchBarActive
|
||||
? "100%"
|
||||
|
@ -154,8 +118,6 @@ const AppNavbar = () => {
|
|||
colorScheme="secondary"
|
||||
spacing={4}
|
||||
px={2}
|
||||
justifyContent="space-between"
|
||||
width={["64px", "70%", "60%", "45%", null, "40%"]}
|
||||
zIndex={ui.searchBarActive ? -10 : 0}
|
||||
size={["xs", "xs", "xs", "lg", null, "lg"]}
|
||||
>
|
||||
|
@ -169,13 +131,9 @@ const AppNavbar = () => {
|
|||
Product
|
||||
</Button>
|
||||
</RouterLink>
|
||||
{/* <Button>Explore</Button> */}
|
||||
{/* <Button>Docs</Button> */}
|
||||
</ButtonGroup>
|
||||
)}
|
||||
{/* </Fade> */}
|
||||
</Flex>
|
||||
{/* <Spacer /> */}
|
||||
|
||||
<Flex justifyContent="flex-end" width="30%" pr={2}>
|
||||
<IconButton
|
||||
|
@ -211,8 +169,6 @@ const AppNavbar = () => {
|
|||
size="lg"
|
||||
h="32px"
|
||||
/>
|
||||
{/* <IconButton>Explore</IconButton>
|
||||
<IconButton>Docs</IconButton> */}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
@ -227,7 +183,6 @@ const AppNavbar = () => {
|
|||
h="32px"
|
||||
m={0}
|
||||
size={iconSize}
|
||||
// size={["md", "lg", null, "md"]}
|
||||
colorScheme="gray"
|
||||
aria-label="App navigation"
|
||||
icon={<HamburgerIcon />}
|
||||
|
@ -238,28 +193,19 @@ const AppNavbar = () => {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SearchBar
|
||||
pl={4}
|
||||
// position="absolute"
|
||||
w={
|
||||
isSearchBarActive
|
||||
? [
|
||||
"100%",
|
||||
"calc(100%)",
|
||||
"calc(100% - 64px)",
|
||||
"calc(100% - 164px)",
|
||||
null,
|
||||
"100%",
|
||||
]
|
||||
: ["32px", "32px", "40%", "55%", null, "60%"]
|
||||
}
|
||||
h="2rem"
|
||||
px={isSearchBarActive ? 2 : 0}
|
||||
bgColor="primary.1200"
|
||||
alignSelf="center"
|
||||
transition="0.5s"
|
||||
/>
|
||||
<RouterLink href="/stream" passHref>
|
||||
<IconButton
|
||||
m={0}
|
||||
variant="link"
|
||||
justifyContent="space-evenly"
|
||||
alignContent="center"
|
||||
h="32px"
|
||||
size={iconSize}
|
||||
colorScheme="gray"
|
||||
aria-label="go to ticker"
|
||||
icon={<MdTimeline />}
|
||||
/>
|
||||
</RouterLink>
|
||||
{!isSearchBarActive && (
|
||||
<IconButton
|
||||
m={0}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { jsx } from "@emotion/react";
|
||||
import { Box, Flex} from "@chakra-ui/react";
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import LoadingDots from "./LoadingDots"
|
||||
|
||||
const AppSidebar = () => {
|
||||
if (false) {
|
||||
return (
|
||||
<LoadingDots
|
||||
fontSize="md"
|
||||
textColor="white"
|
||||
py={4}
|
||||
px={4}
|
||||
isActive={true}
|
||||
>
|
||||
Reticulating splines
|
||||
</LoadingDots>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
|
||||
<Flex
|
||||
className="ScrollableWrapper"
|
||||
flexGrow={1}
|
||||
overflow="hidden"
|
||||
direction="column"
|
||||
pb={8}
|
||||
>
|
||||
<Box className="Scrollable" id="JournalList" overflowY="scroll">
|
||||
</Box>
|
||||
</Flex>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppSidebar;
|
|
@ -1,30 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { Circle, Text } from "@chakra-ui/react";
|
||||
|
||||
const CircleButton = ({ sign, onClick }) => {
|
||||
return (
|
||||
<Circle
|
||||
overflow="initial"
|
||||
transition="0.5s"
|
||||
_hover={{ bgColor: "primary.100", transform: "scale(1.2)" }}
|
||||
boxSize="28px"
|
||||
m="0 0.5rem"
|
||||
cursor="pointer"
|
||||
background="primary.800"
|
||||
boxShadow="0 4px 14px 0 rgba(33, 41, 144, 0.3)"
|
||||
// box-shadow: 0 4px 14px 0 rgba(33, 41, 144, 0.3);
|
||||
onClick={onClick}
|
||||
>
|
||||
<Text
|
||||
transition="1s"
|
||||
_hover={{ transform: "scale(1.5)" }}
|
||||
color="white.100"
|
||||
>
|
||||
{sign}
|
||||
</Text>
|
||||
</Circle>
|
||||
);
|
||||
};
|
||||
|
||||
export default CircleButton;
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { Fragment } from "react";
|
||||
import React, { Fragment } from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { Fragment, useCallback } from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverCloseButton,
|
||||
PopoverHeader,
|
||||
PopoverFooter,
|
||||
PopoverBody,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { useDeleteEntry, useRouter } from "../core/hooks";
|
||||
|
||||
const DeleteEntryButton = ({ id, journalId, appScope }) => {
|
||||
const router = useRouter();
|
||||
const deleteEntry = useDeleteEntry({
|
||||
entryId: id,
|
||||
journalId,
|
||||
});
|
||||
|
||||
const onConfirm = useCallback(
|
||||
(onClose) => () => {
|
||||
deleteEntry();
|
||||
onClose();
|
||||
|
||||
setTimeout(() => {
|
||||
router.push({
|
||||
pathname: `/app/${appScope}/${journalId}/entries`,
|
||||
query: router.query,
|
||||
});
|
||||
}, 1000);
|
||||
},
|
||||
[deleteEntry, journalId, appScope, router]
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover usePortal>
|
||||
{({ onClose }) => (
|
||||
<Fragment>
|
||||
<PopoverTrigger>
|
||||
<Button size="xs" variant="link" colorScheme="primary" ml={1}>
|
||||
Delete
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent zIndex={100} bg="White">
|
||||
<PopoverCloseButton />
|
||||
<PopoverHeader fontWeight="bold">Please confirm!</PopoverHeader>
|
||||
<PopoverBody fontSize="md">
|
||||
Are you sure you want to delete the entry ?
|
||||
</PopoverBody>
|
||||
<PopoverFooter>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
colorScheme="primary"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
No
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onConfirm(onClose)}
|
||||
colorScheme="unsafe"
|
||||
variant="solid"
|
||||
size="sm"
|
||||
>
|
||||
Yes
|
||||
</Button>
|
||||
</PopoverFooter>
|
||||
</PopoverContent>
|
||||
</Fragment>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteEntryButton;
|
|
@ -1,65 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import {
|
||||
Button,
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogContent,
|
||||
AlertDialogOverlay,
|
||||
} from "@chakra-ui/react";
|
||||
import { useRouter, useDeleteJournal } from "../core/hooks";
|
||||
|
||||
const DeleteJournalAlert = ({ isOpen, toggleSelf, cancelRef }) => {
|
||||
const router = useRouter();
|
||||
const { id } = router.params;
|
||||
const { deleteJournal } = useDeleteJournal(id);
|
||||
|
||||
const deleteJournalConfirm = () => {
|
||||
deleteJournal(id);
|
||||
toggleSelf(false);
|
||||
router.replace("/app/personal/");
|
||||
};
|
||||
|
||||
return (
|
||||
<AlertDialog
|
||||
isOpen={isOpen}
|
||||
leastDestructiveRef={cancelRef}
|
||||
onClose={() => toggleSelf(false)}
|
||||
>
|
||||
<AlertDialogOverlay backgroundColor="white.50">
|
||||
<AlertDialogContent bg="solid" backgroundColor="white.100">
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
Delete this journal
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody>
|
||||
{`Are you sure? You can't undo this action afterwards.`}
|
||||
</AlertDialogBody>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<Button
|
||||
ref={cancelRef}
|
||||
onClick={() => toggleSelf(false)}
|
||||
variant="outline"
|
||||
colorScheme="primary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="unsafe"
|
||||
onClick={() => deleteJournalConfirm()}
|
||||
ml={3}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogOverlay>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteJournalAlert;
|
|
@ -1,21 +1,7 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { useState, useRef, useEffect, useContext } from "react";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Spinner,
|
||||
Container,
|
||||
Button,
|
||||
VStack,
|
||||
Center,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
useJournalEntries,
|
||||
useRouter,
|
||||
useJournalPermissions,
|
||||
} from "../core/hooks";
|
||||
import { EntryList, NewEntryModal } from ".";
|
||||
import React, { useRef, useEffect, useContext } from "react";
|
||||
import { Box, Flex, Spinner, Button, Center } from "@chakra-ui/react";
|
||||
import { useJournalEntries, useJournalPermissions } from "../core/hooks";
|
||||
import EntryList from "./EntryList";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
|
||||
const pageSize = 25;
|
||||
|
@ -23,15 +9,16 @@ const isContent = false;
|
|||
|
||||
const EntriesNavigation = () => {
|
||||
const ui = useContext(UIContext);
|
||||
const router = useRouter();
|
||||
|
||||
const { currentUserPermissions: permissions } = useJournalPermissions(
|
||||
router.params.id,
|
||||
router.params.appScope
|
||||
`9b0d7567-4634-4bf7-946d-60ef4414aa93`,
|
||||
`personal`
|
||||
);
|
||||
|
||||
const loadMoreButtonRef = useRef(null);
|
||||
const { id: journalId, appScope } = router.params;
|
||||
|
||||
const journalId = `9b0d7567-4634-4bf7-946d-60ef4414aa93`;
|
||||
const appScope = `personal`;
|
||||
|
||||
const {
|
||||
fetchMore,
|
||||
|
@ -48,7 +35,6 @@ const EntriesNavigation = () => {
|
|||
isContent,
|
||||
searchQuery: ui.searchTerm,
|
||||
});
|
||||
const [newEntryModal, toggleNewEntryModal] = useState();
|
||||
|
||||
const handleScroll = ({ currentTarget }) => {
|
||||
if (
|
||||
|
@ -68,7 +54,7 @@ const EntriesNavigation = () => {
|
|||
}, [journalId, ui.searchTerm, refetch, setSearchTerm]);
|
||||
|
||||
const entriesPagesData = EntriesPages
|
||||
? EntriesPages.map((page) => {
|
||||
? EntriesPages.pages.map((page) => {
|
||||
return page.data;
|
||||
})
|
||||
: [""];
|
||||
|
@ -90,23 +76,6 @@ const EntriesNavigation = () => {
|
|||
direction="column"
|
||||
flexGrow={1}
|
||||
>
|
||||
<Flex align="center" height={12} borderColor="white.300">
|
||||
{canCreate && (
|
||||
<Button
|
||||
width="100%"
|
||||
variant="solid"
|
||||
colorScheme="secondary"
|
||||
alignSelf="center"
|
||||
height={12}
|
||||
m={0}
|
||||
borderRadius={0}
|
||||
// mb={2}
|
||||
onClick={() => toggleNewEntryModal(true)}
|
||||
>
|
||||
New Entry
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
{entries && !isLoading ? (
|
||||
<Flex
|
||||
className="ScrollableWrapper"
|
||||
|
@ -133,22 +102,6 @@ const EntriesNavigation = () => {
|
|||
disableCopy={!canCreate}
|
||||
/>
|
||||
))}
|
||||
{entries.length === 0 && (
|
||||
<Center>
|
||||
<VStack>
|
||||
<Container pt={8}>
|
||||
This journal has no entries so far.{" "}
|
||||
</Container>
|
||||
<Button
|
||||
onClick={() => toggleNewEntryModal(true)}
|
||||
variant="outline"
|
||||
colorScheme="suggested"
|
||||
>
|
||||
Create one
|
||||
</Button>
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
{canFetchMore && !isFetchingMore && (
|
||||
<Center>
|
||||
<Button
|
||||
|
@ -186,13 +139,6 @@ const EntriesNavigation = () => {
|
|||
/>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
{newEntryModal && (
|
||||
<NewEntryModal
|
||||
toggleModal={toggleNewEntryModal}
|
||||
journalId={journalId}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import React, { useContext } from "react";
|
||||
import { Flex, Link, HStack, Skeleton, Box, Title } from "@chakra-ui/react";
|
||||
import { ExternalLinkIcon } from "@chakra-ui/icons";
|
||||
import { useJournalEntry } from "../../src/core/hooks";
|
||||
import FourOFour from "./FourOFour";
|
||||
import FourOThree from "./FourOThree";
|
||||
import Tags from "./Tags";
|
||||
import CustomIcon from "./CustomIcon";
|
||||
import MarkdownView from "react-showdown";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
const Entry = () => {
|
||||
const ui = useContext(UIContext);
|
||||
const entryId = ui.entryId;
|
||||
const journalId = `9b0d7567-4634-4bf7-946d-60ef4414aa93`;
|
||||
const appScope = `personal`;
|
||||
const {
|
||||
data: entry,
|
||||
isFetchedAfterMount,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useJournalEntry(journalId, entryId, appScope);
|
||||
|
||||
const contextUrl = () => {
|
||||
if (entry?.context_url) {
|
||||
switch (entry.context_type) {
|
||||
case "slack":
|
||||
return (
|
||||
<Link href={entry.context_url} isExternal>
|
||||
<CustomIcon width="28px" icon="slack" />
|
||||
</Link>
|
||||
);
|
||||
case "github":
|
||||
return (
|
||||
<Link href={entry.context_url} isExternal>
|
||||
<CustomIcon width="28px" icon="github" />
|
||||
</Link>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Link href={entry.context_url} isExternal>
|
||||
<ExternalLinkIcon bg="none" boxSize="18px" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
} else return "";
|
||||
};
|
||||
|
||||
if (isError && error.response.status === 404) return <FourOFour />;
|
||||
if (isError && error.response.status === 403) return <FourOThree />;
|
||||
// if (!entry || isLoading) return "";
|
||||
|
||||
return (
|
||||
<Flex
|
||||
id="Entry"
|
||||
height="520px"
|
||||
flexGrow="1"
|
||||
flexDirection="column"
|
||||
key={entryId}
|
||||
bgColor="white"
|
||||
p={2}
|
||||
overflowY="scroll"
|
||||
>
|
||||
<Skeleton
|
||||
id="EntryNameSkeleton"
|
||||
mx={2}
|
||||
mt={2}
|
||||
overflow="initial"
|
||||
isLoaded={!isLoading}
|
||||
>
|
||||
<HStack id="EntryHeader" width="100%" m={0}>
|
||||
<Box
|
||||
id="ContextURL"
|
||||
transition="0.3s"
|
||||
_hover={{ transform: "scale(1.2)" }}
|
||||
pl={2}
|
||||
pr={entry?.context_url ? 2 : 0}
|
||||
>
|
||||
{contextUrl()}
|
||||
</Box>
|
||||
<Title
|
||||
overflow="hidden"
|
||||
width={entry?.context_url ? "calc(100% - 28px)" : "100%"}
|
||||
// height="auto"
|
||||
minH="36px"
|
||||
style={{ marginLeft: "0" }}
|
||||
m={0}
|
||||
p={0}
|
||||
fontWeight="600"
|
||||
fontSize="1.5rem"
|
||||
textAlign="left"
|
||||
>
|
||||
{entry?.title}
|
||||
</Title>
|
||||
</HStack>
|
||||
</Skeleton>
|
||||
<Skeleton
|
||||
id="TagsSkeleton"
|
||||
mx={2}
|
||||
overflow="initial"
|
||||
mt={1}
|
||||
isLoaded={isFetchedAfterMount || entry}
|
||||
>
|
||||
<Tags entry={entry} />
|
||||
</Skeleton>
|
||||
<Skeleton
|
||||
height="10px"
|
||||
flexGrow={1}
|
||||
id="EditorSkeleton"
|
||||
mx={2}
|
||||
mt={1}
|
||||
isLoaded={isFetchedAfterMount || entry}
|
||||
>
|
||||
<MarkdownView
|
||||
markdown={entry?.content}
|
||||
options={{ tables: true, emoji: true }}
|
||||
/>
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Entry;
|
|
@ -1,67 +1,52 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { Fragment, useContext } from "react";
|
||||
import { Flex, Heading, Text, LinkBox } from "@chakra-ui/react";
|
||||
import React, { useContext } from "react";
|
||||
import { Flex, Heading, Text, IconButton } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { EntryCard, TagsList, DeleteEntryButton, CopyEntryButton } from ".";
|
||||
import { ViewIcon } from "@chakra-ui/icons";
|
||||
import { useRouter } from "../core/hooks";
|
||||
import RouterLink from "next/link";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
|
||||
const EntryList = ({ entry, disableDelete, disableCopy }) => {
|
||||
const EntryList = ({ entry }) => {
|
||||
const ui = useContext(UIContext);
|
||||
const router = useRouter();
|
||||
const { id: journalId, entryId, appScope } = router.params;
|
||||
|
||||
const handleViewClicked = (entryId) => {
|
||||
ui.setEntryId(entryId);
|
||||
ui.setEntriesViewMode("entry");
|
||||
router.push({
|
||||
pathname: `/stream/${entry.id}`,
|
||||
query: router.query,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<RouterLink
|
||||
href={{
|
||||
pathname: `/app/${appScope}/${journalId}/entries/${entry.id}`,
|
||||
query: router.query,
|
||||
}}
|
||||
passHref
|
||||
<Flex
|
||||
px={6}
|
||||
borderTop="1px"
|
||||
borderColor="white.300"
|
||||
transition="0.1s"
|
||||
_hover={{ bg: "secondary.200" }}
|
||||
width="100%"
|
||||
direction="row"
|
||||
justifyContent="normal"
|
||||
alignItems="baseline"
|
||||
>
|
||||
<LinkBox
|
||||
onClick={() =>
|
||||
ui.setEntriesViewMode(ui.isMobileView ? "entry" : "split")
|
||||
}
|
||||
>
|
||||
<EntryCard isActive={entryId === entry.id ? true : false}>
|
||||
<Heading as="h3" fontWeight="500" fontSize="md">
|
||||
{entry.title}
|
||||
</Heading>
|
||||
<Flex alignItems="baseline">
|
||||
<Text opacity="0.5" fontSize="xs">
|
||||
{moment(entry.created_at).format("DD MMM, YYYY")}{" "}
|
||||
</Text>
|
||||
{entryId === entry.id && (
|
||||
<Fragment>
|
||||
{!disableDelete && (
|
||||
<DeleteEntryButton
|
||||
id={entryId}
|
||||
journalId={journalId}
|
||||
appScope={appScope}
|
||||
/>
|
||||
)}
|
||||
{!disableCopy && (
|
||||
<CopyEntryButton id={entryId} journalId={journalId} />
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex align="start">
|
||||
<Flex
|
||||
width="100%"
|
||||
mt={2}
|
||||
align="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<TagsList tags={entry.tags} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</EntryCard>
|
||||
</LinkBox>
|
||||
</RouterLink>
|
||||
<Flex flexGrow={1}>
|
||||
<Heading as="h3" fontWeight="500" fontSize="md">
|
||||
{entry.title}
|
||||
</Heading>
|
||||
</Flex>
|
||||
|
||||
<Text opacity="0.5" fontSize="xs" alignSelf="baseline">
|
||||
{moment(entry.created_at).format("DD MMM, YYYY, h:mm:ss")}{" "}
|
||||
</Text>
|
||||
<IconButton
|
||||
p={0}
|
||||
variant="ghost"
|
||||
boxSize="32px"
|
||||
colorScheme="primary"
|
||||
icon={<ViewIcon />}
|
||||
onClick={() => handleViewClicked(entry.id)}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { Fragment, useContext } from "react";
|
||||
import React, { Fragment, useContext } from "react";
|
||||
import RouterLink from "next/link";
|
||||
import {
|
||||
Flex,
|
||||
|
@ -14,12 +12,12 @@ import {
|
|||
MenuItem,
|
||||
MenuGroup,
|
||||
MenuDivider,
|
||||
IconButton,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import { ChevronDownIcon, HamburgerIcon } from "@chakra-ui/icons";
|
||||
import { ChevronDownIcon } from "@chakra-ui/icons";
|
||||
import useModals from "../core/hooks/useModals";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
import ChakraAccountIconButton from "./AccountIconButton";
|
||||
|
||||
const LandingNavbar = () => {
|
||||
const ui = useContext(UIContext);
|
||||
|
@ -50,7 +48,7 @@ const LandingNavbar = () => {
|
|||
>
|
||||
<Spacer />
|
||||
{ui.isLoggedIn && (
|
||||
<RouterLink href="/app" passHref>
|
||||
<RouterLink href="/stream" passHref>
|
||||
<Button
|
||||
as={Link}
|
||||
colorScheme="secondary"
|
||||
|
@ -84,41 +82,7 @@ const LandingNavbar = () => {
|
|||
Log in
|
||||
</Button>
|
||||
)}
|
||||
<RouterLink href="/pricing" passHref>
|
||||
<Button color="white" fontWeight="400">
|
||||
Pricing
|
||||
</Button>
|
||||
</RouterLink>
|
||||
<RouterLink href="/case-studies/activeloop" passHref>
|
||||
<Button color="white" fontWeight="400" as={Link}>
|
||||
Case study
|
||||
</Button>
|
||||
</RouterLink>
|
||||
|
||||
<Menu>
|
||||
<MenuButton
|
||||
size="xl"
|
||||
aria-label="menu"
|
||||
as={IconButton}
|
||||
color="white"
|
||||
icon={<HamburgerIcon />}
|
||||
>
|
||||
About us
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
w={["100%", "16rem", "18rem", "20rem", "22rem", "24rem"]}
|
||||
>
|
||||
<RouterLink href="/team" passHref>
|
||||
<MenuItem>Team and careers</MenuItem>
|
||||
</RouterLink>
|
||||
<RouterLink href="/events" passHref>
|
||||
<MenuItem>Bugout events</MenuItem>
|
||||
</RouterLink>
|
||||
<MenuItem as="a" href="http://blog.bugout.dev">
|
||||
Blog
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
{ui.isLoggedIn && <ChakraAccountIconButton />}
|
||||
</ButtonGroup>
|
||||
</Fragment>
|
||||
)}
|
||||
|
@ -149,7 +113,7 @@ const LandingNavbar = () => {
|
|||
>
|
||||
<MenuGroup>
|
||||
{ui.isLoggedIn && (
|
||||
<RouterLink href="/app" passHref>
|
||||
<RouterLink href="/stream" passHref>
|
||||
<MenuItem bgColor="secondary.600">Open App</MenuItem>
|
||||
</RouterLink>
|
||||
)}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
Heading,
|
||||
Box,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
InputGroup,
|
||||
Input,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { Modal } from ".";
|
||||
import { useCreateEntry, useRouter } from "../core/hooks";
|
||||
|
||||
const NewEntryModal = ({ toggleModal, journalId }) => {
|
||||
const router = useRouter();
|
||||
const { handleSubmit, errors, register } = useForm();
|
||||
const { createEntry, isLoading, data } = useCreateEntry(journalId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.data?.id) {
|
||||
router.push({
|
||||
pathname: `/app/personal/${journalId}/entries/${data.data.id}`,
|
||||
query: { ...router.query, mode: "write" },
|
||||
});
|
||||
}
|
||||
toggleModal(null);
|
||||
}, [data, toggleModal, journalId, router]);
|
||||
|
||||
return (
|
||||
<Modal onClose={() => toggleModal(null)}>
|
||||
<Heading mt={2} as="h2" fontSize={["lg", "2xl"]}>
|
||||
Create Entry
|
||||
</Heading>
|
||||
<form onSubmit={handleSubmit(createEntry)}>
|
||||
<FormControl position="relative" isInvalid={errors.title}>
|
||||
<InputGroup pt={4} width="100%">
|
||||
<Input
|
||||
colorScheme="primary"
|
||||
variant="filled"
|
||||
placeholder="Entry Title"
|
||||
name="title"
|
||||
ref={register({ required: "title is required!" })}
|
||||
/>
|
||||
</InputGroup>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.title && errors.title.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Box height="1px" width="100%" background="#eaebf8" mb="1.875rem" />
|
||||
<Button
|
||||
mt={8}
|
||||
variant="solid"
|
||||
colorScheme="primary"
|
||||
type="submit"
|
||||
size="lg"
|
||||
width="100%"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewEntryModal;
|
|
@ -0,0 +1,113 @@
|
|||
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,
|
||||
} from "@chakra-ui/react";
|
||||
import RadioCard from "./RadioCard";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
const NewSubscription = ({ isFreeOption, onClose }) => {
|
||||
const { typesCache, createSubscription } = useSubscriptions();
|
||||
const { handleSubmit, errors, register } = useForm();
|
||||
const [radioState, setRadioState] = useState("ethereum_blockchain");
|
||||
let { getRootProps, getRadioProps, ref } = 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,
|
||||
type: isFreeOption ? "free" : radioState,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<form onSubmit={handleSubmit(createSubscriptionWrap)}>
|
||||
<ModalHeader>Subscribe to a new address</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<FormControl isInvalid={errors.address}>
|
||||
<Input
|
||||
placeholder="new 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.type}>
|
||||
<HStack {...group} alignItems="stretch">
|
||||
{typesCache.data.map((type) => {
|
||||
const radio = getRadioProps({
|
||||
value: type.subscription_type,
|
||||
isDisabled:
|
||||
!type.active ||
|
||||
(isFreeOption &&
|
||||
type.subscription_type !== "ethereum_blockchain"),
|
||||
});
|
||||
if (!type.subscription_plan_id) return "";
|
||||
return (
|
||||
<RadioCard
|
||||
onClick={() => console.log("hello")}
|
||||
key={`subscription-type-${type.id}`}
|
||||
{...radio}
|
||||
>
|
||||
{type.name}
|
||||
</RadioCard>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Input
|
||||
placeholder="Add some notes"
|
||||
name="note"
|
||||
ref={register()}
|
||||
></Input>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
colorScheme="suggested"
|
||||
isLoading={createSubscription.isLoading}
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
<Button colorScheme="gray">Cancel</Button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewSubscription;
|
|
@ -1,483 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Checkbox,
|
||||
VStack,
|
||||
Table,
|
||||
Tr,
|
||||
Td,
|
||||
Th,
|
||||
Tbody,
|
||||
Thead,
|
||||
HStack,
|
||||
Select,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Button,
|
||||
Box,
|
||||
Text,
|
||||
Flex,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import { IconButton } from ".";
|
||||
import { CloseIcon, DeleteIcon } from "@chakra-ui/icons";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { TiGroupOutline } from "react-icons/ti";
|
||||
import { IoIosJournal } from "react-icons/io";
|
||||
import { useRouter, useGroups, useJournalPermissions } from "../core/hooks";
|
||||
import RouterLink from "next/link";
|
||||
|
||||
const PermissionTable = ({ id, user, LoadingSpinner, setDeleteAlert }) => {
|
||||
const router = useRouter();
|
||||
const { handleSubmit, errors, register } = useForm();
|
||||
const [showNewHolderForm, toggleNewHolder] = useState(false);
|
||||
const { data: groups } = useGroups();
|
||||
const { appScope } = router.params;
|
||||
|
||||
const {
|
||||
holders,
|
||||
error,
|
||||
setJournalPermissionMutation,
|
||||
removeJournalPermissionMutation,
|
||||
currentUserPermissions,
|
||||
} = useJournalPermissions(id, "personal");
|
||||
|
||||
const checkboxToggled = (holder, value) => {
|
||||
return holder.permissions.includes(value)
|
||||
? removePermissions({
|
||||
holder_id: holder.holder_id,
|
||||
holder_type: holder.holder_type,
|
||||
permission_list: [value],
|
||||
})
|
||||
: addPermissions({
|
||||
holder_id: holder.holder_id,
|
||||
holder_type: holder.holder_type,
|
||||
permission_list: [value],
|
||||
});
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Box>
|
||||
It seems you have no permissions to open this page
|
||||
<RouterLink
|
||||
href={{
|
||||
pathname: `/app/${appScope}/${id}`,
|
||||
query: router.query,
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<Button
|
||||
as={Link}
|
||||
m={8}
|
||||
variant="solid"
|
||||
colorScheme="primary"
|
||||
leftIcon={<IoIosJournal />}
|
||||
>
|
||||
Back to the journal
|
||||
</Button>
|
||||
</RouterLink>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentUserPermissions || !holders || !groups) return <LoadingSpinner />;
|
||||
|
||||
const removePermissions = (formData) => {
|
||||
formData.permission_list = formData.permission_list.filter(Boolean);
|
||||
removeJournalPermissionMutation.removeJournalPermission({
|
||||
holder_type: "group",
|
||||
...formData,
|
||||
});
|
||||
};
|
||||
|
||||
const addPermissions = (formData) => {
|
||||
formData.permission_list = formData.permission_list.filter(Boolean);
|
||||
toggleNewHolder(false);
|
||||
|
||||
setJournalPermissionMutation.setJournalPermission({
|
||||
holder_type: "group",
|
||||
...formData,
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
!currentUserPermissions.includes("journals.update") &&
|
||||
showNewHolderForm
|
||||
) {
|
||||
toggleNewHolder(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack>
|
||||
<form onSubmit={handleSubmit(addPermissions)}>
|
||||
<Table
|
||||
width="100%"
|
||||
variant="simple"
|
||||
colorScheme="primary"
|
||||
p={0}
|
||||
m={0}
|
||||
textColor="inherit"
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th
|
||||
textColor="inherit"
|
||||
borderRightWidth="1px"
|
||||
textAlign="center"
|
||||
colSpan="2"
|
||||
>
|
||||
Holder
|
||||
</Th>
|
||||
<Th borderRightWidth="1px" textAlign="center" colSpan="3">
|
||||
Journals
|
||||
</Th>
|
||||
<Th borderRightWidth="1px" textAlign="center" colSpan="4">
|
||||
Entries
|
||||
</Th>
|
||||
<Th>Actions</Th>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Th textAlign="center" width="200px" px={1}>
|
||||
name
|
||||
</Th>
|
||||
<Th textAlign="center" px={1} borderRightWidth="1px">
|
||||
type
|
||||
</Th>
|
||||
<Th textAlign="center" px={1}>
|
||||
Read
|
||||
</Th>
|
||||
<Th textAlign="center" px={1}>
|
||||
Update
|
||||
</Th>
|
||||
<Th textAlign="center" px={1} borderRightWidth="1px">
|
||||
Delete
|
||||
</Th>
|
||||
<Th textAlign="center" px={1}>
|
||||
Read
|
||||
</Th>
|
||||
<Th textAlign="center" px={1}>
|
||||
Create
|
||||
</Th>
|
||||
<Th textAlign="center" px={1}>
|
||||
Update
|
||||
</Th>
|
||||
<Th textAlign="center" px={1}>
|
||||
Delete
|
||||
</Th>
|
||||
<Th textAlign="center" width="140px" px={1}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{holders.map((holder, idx) => {
|
||||
const index = groups.findIndex(
|
||||
(item) => item.group_id === holder.holder_id
|
||||
);
|
||||
const name =
|
||||
index !== -1
|
||||
? groups[index]?.group_name
|
||||
: holder.holder_id === user?.user_id
|
||||
? user?.username
|
||||
: null;
|
||||
return (
|
||||
<Tr
|
||||
bgColor={name ? "white.100" : "gray.50"}
|
||||
key={`row-${idx}`}
|
||||
borderColor="white.200"
|
||||
>
|
||||
<Td textAlign="center">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Text fontSize="xx-small">{holder.holder_id}</Text>
|
||||
)}
|
||||
</Td>
|
||||
<Td textAlign="center">{holder.holder_type}</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
onChange={() => checkboxToggled(holder, "journals.read")}
|
||||
isChecked={holder.permissions.includes("journals.read")}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
isChecked={holder.permissions.includes("journals.update")}
|
||||
onChange={() =>
|
||||
checkboxToggled(holder, "journals.update")
|
||||
}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
isChecked={holder.permissions.includes("journals.delete")}
|
||||
onChange={() =>
|
||||
checkboxToggled(holder, "journals.delete")
|
||||
}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
isChecked={holder.permissions.includes(
|
||||
"journals.entries.read"
|
||||
)}
|
||||
onChange={() =>
|
||||
checkboxToggled(holder, "journals.entries.read")
|
||||
}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
isChecked={holder.permissions.includes(
|
||||
"journals.entries.create"
|
||||
)}
|
||||
onChange={() =>
|
||||
checkboxToggled(holder, "journals.entries.create")
|
||||
}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
isChecked={holder.permissions.includes(
|
||||
"journals.entries.update"
|
||||
)}
|
||||
onChange={() =>
|
||||
checkboxToggled(holder, "journals.entries.update")
|
||||
}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
isDisabled={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
isChecked={holder.permissions.includes(
|
||||
"journals.entries.delete"
|
||||
)}
|
||||
onChange={() =>
|
||||
checkboxToggled(holder, "journals.entries.delete")
|
||||
}
|
||||
></Checkbox>
|
||||
</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
hidden={
|
||||
!name ||
|
||||
!currentUserPermissions.includes("journals.update")
|
||||
}
|
||||
onClick={() =>
|
||||
removePermissions({
|
||||
holder_id: holder.holder_id,
|
||||
holder_typ: holder.holder_type,
|
||||
permission_list: [
|
||||
"journals.read",
|
||||
"journals.delete",
|
||||
"journals.entries.read",
|
||||
"journals.entries.create",
|
||||
"journals.entries.update",
|
||||
"journals.entries.delete",
|
||||
"journals.update",
|
||||
],
|
||||
})
|
||||
}
|
||||
icon={<CloseIcon />}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
{showNewHolderForm && (
|
||||
<Tr width="100%">
|
||||
<Td>
|
||||
<FormControl isInvalid={errors.groupName}>
|
||||
<Select
|
||||
_focus={{
|
||||
outline: "solid 1px",
|
||||
outlineColor: "primary.500",
|
||||
}}
|
||||
fontSize="sm"
|
||||
border="none"
|
||||
placeholder="Select a team"
|
||||
name="holder_id"
|
||||
// width="200px"
|
||||
height="fit-content"
|
||||
bgColor="white.200"
|
||||
ref={(e) => {
|
||||
register(e, {
|
||||
required: "Please select a group",
|
||||
});
|
||||
}}
|
||||
>
|
||||
{groups.map((group, idx) => {
|
||||
if (
|
||||
!holders.some((i) => i.holder_id === group.group_id)
|
||||
) {
|
||||
return (
|
||||
<option key={idx} value={group.group_id}>
|
||||
{group.group_name}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Select>
|
||||
<FormErrorMessage color="unsafe.400" pl="1">
|
||||
{errors.groupName && errors.groupName.message}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
</Td>
|
||||
<Td>{/* <Checkbox /> */}</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[0]"
|
||||
value="journals.read"
|
||||
defaultValue={null}
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[1]"
|
||||
value="journals.update"
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[2]"
|
||||
value="journals.delete"
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[3]"
|
||||
value="journals.entries.read"
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[4]"
|
||||
value="journals.entries.create"
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[5]"
|
||||
value="journals.entries.update"
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
name="permission_list[6]"
|
||||
value="journals.entries.delete"
|
||||
defaultChecked={false}
|
||||
ref={register}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<HStack>
|
||||
<IconButton
|
||||
onClick={() => toggleNewHolder(false)}
|
||||
icon={<CloseIcon />}
|
||||
/>
|
||||
<IconButton type="submit" />
|
||||
</HStack>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</form>
|
||||
<Flex
|
||||
direction="row"
|
||||
py={4}
|
||||
alignContent="baseline"
|
||||
width="100%"
|
||||
justifyContent="center"
|
||||
>
|
||||
<RouterLink
|
||||
href={{
|
||||
pathname: `/app/${appScope}/${id}`,
|
||||
query: router.query,
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<Button
|
||||
as={Link}
|
||||
variant="solid"
|
||||
colorScheme="primary"
|
||||
leftIcon={<IoIosJournal />}
|
||||
>
|
||||
Back to the journal
|
||||
</Button>
|
||||
</RouterLink>
|
||||
{currentUserPermissions.includes("journals.update") && (
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="primary"
|
||||
disabled={showNewHolderForm}
|
||||
hidden={!currentUserPermissions.includes("journals.update")}
|
||||
onClick={() => toggleNewHolder(true)}
|
||||
leftIcon={<TiGroupOutline />}
|
||||
>
|
||||
Add a team
|
||||
</Button>
|
||||
)}
|
||||
{currentUserPermissions.includes("journals.delete") && (
|
||||
<Button
|
||||
variant="solid"
|
||||
colorScheme="unsafe"
|
||||
hidden={!currentUserPermissions.includes("journals.delete")}
|
||||
onClick={() => setDeleteAlert(true)}
|
||||
leftIcon={<DeleteIcon />}
|
||||
>
|
||||
Delete this journal
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
export default PermissionTable;
|
|
@ -0,0 +1,45 @@
|
|||
import React from "react";
|
||||
import { useRadio, Box, Flex } from "@chakra-ui/react";
|
||||
|
||||
const RadioCard = (props) => {
|
||||
const { getInputProps, getCheckboxProps } = useRadio(props);
|
||||
|
||||
const input = getInputProps();
|
||||
const checkbox = getCheckboxProps();
|
||||
|
||||
return (
|
||||
<Flex as="label" h="fill-availible" onClick={() => console.log('hello2')}>
|
||||
<input {...input} />
|
||||
<Box
|
||||
justifyContent="left"
|
||||
alignContent="center"
|
||||
{...checkbox}
|
||||
cursor="pointer"
|
||||
borderWidth="1px"
|
||||
borderRadius="md"
|
||||
boxShadow="md"
|
||||
_disabled={{
|
||||
bg: "gray.300",
|
||||
color: "gray.900",
|
||||
borderColor: "gray.300",
|
||||
}}
|
||||
_checked={{
|
||||
bg: "secondary.900",
|
||||
color: "white",
|
||||
borderColor: "secondary.900",
|
||||
}}
|
||||
_focus={{
|
||||
boxShadow: "outline",
|
||||
}}
|
||||
px={5}
|
||||
py={3}
|
||||
fontWeight="600"
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
// const RadioCard = chakra(RadioCard_);
|
||||
export default RadioCard;
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import {
|
||||
useState,
|
||||
|
@ -69,11 +68,9 @@ const SearchBar = (props) => {
|
|||
|
||||
delete newQuery.entryId;
|
||||
ui.setSearchTerm(InputFieldValue);
|
||||
router.push(
|
||||
{ pathname: "/app/[appScope]/[id]/entries", query: newQuery },
|
||||
undefined,
|
||||
{ shallow: false }
|
||||
);
|
||||
router.push({ pathname: "/stream/", query: newQuery }, undefined, {
|
||||
shallow: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setShowError(true);
|
||||
|
@ -86,7 +83,9 @@ const SearchBar = (props) => {
|
|||
|
||||
useEffect(() => {
|
||||
const cache =
|
||||
router.params.appScope === "public" ? publicJournalsCache : journalsCache;
|
||||
router.params.appScope === "personal"
|
||||
? publicJournalsCache
|
||||
: journalsCache;
|
||||
if (router.params.id && !cache.isLoading) {
|
||||
const newJournal = cache.data.find(
|
||||
(journal) => journal.id === router.params.id
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import {
|
||||
ProSidebar,
|
||||
Menu,
|
||||
MenuItem,
|
||||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { ProSidebar } from "react-pro-sidebar";
|
||||
import AppSidebar from "./AppSidebar";
|
||||
SidebarHeader,
|
||||
SidebarFooter,
|
||||
SidebarContent,
|
||||
} from "react-pro-sidebar";
|
||||
import { useContext } from "react";
|
||||
import RouterLink from "next/link";
|
||||
import {
|
||||
Button,
|
||||
Image,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
MenuGroup,
|
||||
MenuDivider,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import { ChevronDownIcon } from "@chakra-ui/icons";
|
||||
import { Flex, Image, IconButton } from "@chakra-ui/react";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
import React from "react";
|
||||
import { HamburgerIcon } from "@chakra-ui/icons";
|
||||
import { MdTimeline, MdSettings } from "react-icons/md";
|
||||
// import RouterLink from "next/link";
|
||||
// import RouterLink from "next/link";
|
||||
|
||||
const Sidebar = () => {
|
||||
|
@ -29,8 +27,20 @@ const Sidebar = () => {
|
|||
collapsed={ui.sidebarCollapsed}
|
||||
hidden={!ui.sidebarVisible}
|
||||
>
|
||||
{!ui.isMobileView && (
|
||||
<Link href="/" bgColor="primary.1200">
|
||||
<SidebarHeader>
|
||||
<Flex>
|
||||
<IconButton
|
||||
ml={4}
|
||||
justifySelf="flex-start"
|
||||
colorScheme="primary"
|
||||
aria-label="App navigation"
|
||||
icon={<HamburgerIcon />}
|
||||
onClick={() => {
|
||||
ui.isMobileView
|
||||
? ui.setSidebarToggled(!ui.sidebarToggled)
|
||||
: ui.setSidebarCollapsed(!ui.sidebarCollapsed);
|
||||
}}
|
||||
/>
|
||||
<Image
|
||||
// as={Link}
|
||||
// to="/"
|
||||
|
@ -40,64 +50,27 @@ const Sidebar = () => {
|
|||
src="/icons/bugout-dev-white.svg"
|
||||
alt="bugout.dev"
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
{ui.isMobileView && (
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
m={0}
|
||||
variant="solid"
|
||||
w={["100%", "100%", "18rem", "20rem", "22rem", "24rem"]}
|
||||
p={5}
|
||||
colorScheme="primary"
|
||||
h="3rem"
|
||||
borderRadius={0}
|
||||
// bgColor="primary.900"
|
||||
rightIcon={<ChevronDownIcon boxSize="1.5rem" />}
|
||||
>
|
||||
</Flex>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<Menu iconShape="square">
|
||||
<MenuItem icon={<MdTimeline />}>
|
||||
{" "}
|
||||
<Image
|
||||
h="3rem"
|
||||
py="0.75rem"
|
||||
pl={5}
|
||||
src="/icons/bugout-dev-white.svg"
|
||||
alt="bugout.dev"
|
||||
/>
|
||||
</MenuButton>
|
||||
|
||||
<MenuList
|
||||
zIndex="dropdown"
|
||||
width={["100vw", "100vw", "18rem", "20rem", "22rem", "24rem"]}
|
||||
borderRadius={0}
|
||||
m={0}
|
||||
>
|
||||
<MenuGroup>
|
||||
<RouterLink href="/case-studies/activeloop" passHref>
|
||||
<MenuItem>case studies</MenuItem>
|
||||
</RouterLink>
|
||||
</MenuGroup>
|
||||
<MenuDivider />
|
||||
<RouterLink href="/events" passHref>
|
||||
<MenuItem>events</MenuItem>
|
||||
</RouterLink>
|
||||
<RouterLink href="/team" passHref>
|
||||
<MenuItem>team</MenuItem>
|
||||
</RouterLink>
|
||||
<RouterLink href="/pricing" passHref>
|
||||
<MenuItem>pricing</MenuItem>
|
||||
</RouterLink>
|
||||
</MenuList>
|
||||
<RouterLink href="/stream">Stream</RouterLink>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
)}
|
||||
{ui.isAppView && ui.isAppReady && ui.isLoggedIn && <AppSidebar />}
|
||||
{/* <Menu iconShape="square">
|
||||
<MenuItem>Dashboard</MenuItem>
|
||||
<SubMenu title="Components">
|
||||
<MenuItem>Component 1</MenuItem>
|
||||
<MenuItem>Component 2</MenuItem>
|
||||
</SubMenu>
|
||||
</Menu> */}
|
||||
<Menu iconShape="square">
|
||||
<MenuItem icon={<MdSettings />}>
|
||||
{" "}
|
||||
<RouterLink href="/subscriptions">Subscriptions </RouterLink>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
{/**
|
||||
* You can add a footer for the sidebar ex: copyright
|
||||
*/}
|
||||
</SidebarFooter>
|
||||
</ProSidebar>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import React from "react";
|
||||
import { Skeleton, IconButton } from "@chakra-ui/react";
|
||||
import {
|
||||
Table,
|
||||
Th,
|
||||
Td,
|
||||
Tr,
|
||||
Thead,
|
||||
Tbody,
|
||||
Editable,
|
||||
EditableInput,
|
||||
Image,
|
||||
EditablePreview,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import moment from "moment";
|
||||
import CopyButton from "./CopyButton";
|
||||
import { useSubscriptions } from "../core/hooks";
|
||||
import ConfirmationRequest from "./ConfirmationRequest";
|
||||
|
||||
const List = () => {
|
||||
const { subscriptionsCache, changeNote, deleteSubscription } =
|
||||
useSubscriptions();
|
||||
|
||||
const updateCallback = ({ id, note }) => {
|
||||
changeNote.mutate({ id, note });
|
||||
};
|
||||
|
||||
if (subscriptionsCache.data) {
|
||||
return (
|
||||
<Table
|
||||
borderColor="gray.200"
|
||||
borderWidth="1px"
|
||||
variant="simple"
|
||||
colorScheme="primary"
|
||||
justifyContent="center"
|
||||
borderBottomRadius="xl"
|
||||
alignItems="baseline"
|
||||
h="auto"
|
||||
size="sm"
|
||||
mt={0}
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Token</Th>
|
||||
<Th>Address</Th>
|
||||
<Th>Date Created</Th>
|
||||
<Th>Note</Th>
|
||||
<Th>Actions</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{subscriptionsCache.data.subscriptions.map((subscription) => {
|
||||
return (
|
||||
<Tr key={`token-row-${subscription.address}`}>
|
||||
<Td>
|
||||
<Image
|
||||
h="32px"
|
||||
src="https://ethereum.org/static/c48a5f760c34dfadcf05a208dab137cc/31987/eth-diamond-rainbow.png"
|
||||
/>
|
||||
</Td>
|
||||
<Td mr={4} p={0}>
|
||||
<CopyButton>{subscription.address}</CopyButton>
|
||||
</Td>
|
||||
<Td py={0}>{moment(subscription.created_at).format("L")}</Td>
|
||||
<Td py={0}>
|
||||
<Editable
|
||||
colorScheme="primary"
|
||||
placeholder="enter note here"
|
||||
defaultValue={subscription.note}
|
||||
onSubmit={(nextValue) =>
|
||||
updateCallback({
|
||||
id: subscription.id,
|
||||
note: nextValue,
|
||||
})
|
||||
}
|
||||
>
|
||||
<EditablePreview
|
||||
maxW="40rem"
|
||||
_placeholder={{ color: "black" }}
|
||||
/>
|
||||
<EditableInput maxW="40rem" />
|
||||
</Editable>
|
||||
</Td>
|
||||
<Td py={0}>
|
||||
<ConfirmationRequest
|
||||
bodyMessage={"please confirm"}
|
||||
header={"Delete subscription"}
|
||||
onConfirm={() => deleteSubscription.mutate(subscription.id)}
|
||||
>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorScheme="primary"
|
||||
icon={<DeleteIcon />}
|
||||
/>
|
||||
</ConfirmationRequest>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
} else if (subscriptionsCache.isLoading) {
|
||||
return <Skeleton />;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
export default List;
|
|
@ -0,0 +1,31 @@
|
|||
import React from "react";
|
||||
import { Tag, TagLabel, Flex } from "@chakra-ui/react";
|
||||
const Tags = ({ tags }) => {
|
||||
const displayTags = tags?.filter(
|
||||
(tag) =>
|
||||
tag.startsWith("from") ||
|
||||
tag.startsWith("client") ||
|
||||
tag.startsWith("network") ||
|
||||
tag.startsWith("to") ||
|
||||
tag.startsWith("source") ||
|
||||
tag.startsWith("node")
|
||||
);
|
||||
return (
|
||||
<Flex alignSelf="flex-start">
|
||||
<Flex flexWrap="wrap" pl={2} pr={2} spacing={2} alignItems="center">
|
||||
{displayTags?.map((tag, index) => (
|
||||
<Tag
|
||||
variant="subtle"
|
||||
colorScheme="primary"
|
||||
key={`${tag}-${index}`}
|
||||
zIndex={1}
|
||||
>
|
||||
<TagLabel>{tag}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tags;
|
|
@ -1,82 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { useCallback, useState, useEffect } from "react";
|
||||
import { Tag, TagLabel, TagCloseButton, Flex, Input } from "@chakra-ui/react";
|
||||
import { useRouter, useUpdateTag } from "../core/hooks";
|
||||
import { useQueryCache } from "react-query";
|
||||
const TagsEditor = ({ entry }) => {
|
||||
const router = useRouter();
|
||||
const [canEditTags, setEditTags] = useState(false);
|
||||
const cache = useQueryCache();
|
||||
const { id: journalId, entryId, appScope } = router.params;
|
||||
|
||||
const [tag, setTag] = useState("");
|
||||
const updateTag = useUpdateTag(journalId, entryId);
|
||||
const onTagAdd = useCallback(
|
||||
(e) => {
|
||||
if (e.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.tags.includes(e.target.value)) {
|
||||
updateTag({ tag: e.target.value, action: "add" });
|
||||
}
|
||||
setTag("");
|
||||
},
|
||||
[entry, setTag, updateTag]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const userPermissions = cache.getQueryData([
|
||||
"journal-permissions-current-user",
|
||||
{ journalId },
|
||||
]);
|
||||
if (userPermissions && appScope !== "public") {
|
||||
if (userPermissions.includes("journals.entries.update")) {
|
||||
setEditTags(true);
|
||||
} else {
|
||||
setEditTags(false);
|
||||
}
|
||||
} else {
|
||||
setEditTags(false);
|
||||
}
|
||||
}, [cache, appScope, journalId]);
|
||||
const onTagDelete = useCallback(
|
||||
(index) => {
|
||||
updateTag({ tag: entry.tags[index], action: "delete" });
|
||||
},
|
||||
[entry, updateTag]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex alignSelf="flex-start">
|
||||
<Flex flexWrap="wrap" pl={2} pr={2} spacing={2} alignItems="center">
|
||||
{entry?.tags?.map((tag, index) => (
|
||||
<Tag
|
||||
variant="subtle"
|
||||
colorScheme="primary"
|
||||
// size="sm"
|
||||
key={`${tag}-${index}`}
|
||||
zIndex={1}
|
||||
>
|
||||
<TagLabel>{tag}</TagLabel>
|
||||
{canEditTags && (
|
||||
<TagCloseButton onClick={() => onTagDelete(index)} />
|
||||
)}
|
||||
</Tag>
|
||||
))}
|
||||
{canEditTags && (
|
||||
<Input
|
||||
variant="newTag"
|
||||
placeholder="+ Add tag"
|
||||
onKeyUp={onTagAdd}
|
||||
value={tag}
|
||||
onChange={(e) => setTag(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagsEditor;
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import React from "react";
|
||||
import { IconButton } from "@chakra-ui/react";
|
||||
import {
|
||||
Table,
|
||||
|
@ -13,7 +12,9 @@ import {
|
|||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import { CopyButton, ConfirmationRequest, NewTokenTr } from ".";
|
||||
import CopyButton from "./CopyEntryButton";
|
||||
import ConfirmationRequest from "./ConfirmationRequest";
|
||||
import NewTokenTr from "./NewTokenTr";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
const TokenList = ({
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import { Skeleton, IconButton } from "@chakra-ui/react";
|
||||
import {
|
||||
Table,
|
||||
Th,
|
||||
Td,
|
||||
Tr,
|
||||
Thead,
|
||||
Tbody,
|
||||
Editable,
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
} from "@chakra-ui/react";
|
||||
import { DeleteIcon } from "@chakra-ui/icons";
|
||||
import moment from "moment";
|
||||
import { CopyButton } from ".";
|
||||
|
||||
const List = ({ data, revoke, isLoading, updateCallback }) => {
|
||||
const userToken = localStorage.getItem("BUGOUT_ACCESS_TOKEN");
|
||||
|
||||
if (data) {
|
||||
return (
|
||||
<Table
|
||||
variant="simple"
|
||||
colorScheme="primary"
|
||||
justifyContent="center"
|
||||
alignItems="baseline"
|
||||
h="auto"
|
||||
size="sm"
|
||||
>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Token</Th>
|
||||
<Th>Date Created</Th>
|
||||
<Th>Note</Th>
|
||||
<Th>Actions</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{data.data.token.map((token) => {
|
||||
if (token.active) {
|
||||
if (userToken !== token.id) {
|
||||
return (
|
||||
<Tr key={`token-row-${token.id}`}>
|
||||
<Td mr={4} p={0}>
|
||||
<CopyButton>{token.id}</CopyButton>
|
||||
</Td>
|
||||
<Td py={0}>{moment(token.created_at).format("L")}</Td>
|
||||
<Td py={0}>
|
||||
<Editable
|
||||
colorScheme="primary"
|
||||
placeholder="enter note here"
|
||||
defaultValue={token.note}
|
||||
onSubmit={(nextValue) =>
|
||||
updateCallback({ token: token.id, note: nextValue })
|
||||
}
|
||||
>
|
||||
<EditablePreview
|
||||
maxW="40rem"
|
||||
_placeholder={{ color: "black" }}
|
||||
/>
|
||||
<EditableInput maxW="40rem" />
|
||||
</Editable>
|
||||
</Td>
|
||||
<Td py={0}>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorScheme="primary"
|
||||
onClick={() => revoke(token.id)}
|
||||
icon={<DeleteIcon />}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
} else return null;
|
||||
} else return null;
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
);
|
||||
} else if (isLoading) {
|
||||
return <Skeleton />;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
export default List;
|
|
@ -5,17 +5,18 @@ import { AuthService } from "../../core/services";
|
|||
const useChangePassword = () => {
|
||||
const toast = useToast();
|
||||
|
||||
const [changePassword, { isLoading, data }] = useMutation(
|
||||
AuthService.changePassword,
|
||||
{
|
||||
onError: (error) => {
|
||||
toast(error, "error");
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast("Your password has been successfully changed", "success");
|
||||
},
|
||||
}
|
||||
);
|
||||
const {
|
||||
mutate: changePassword,
|
||||
isLoading,
|
||||
data,
|
||||
} = useMutation(AuthService.changePassword, {
|
||||
onError: (error) => {
|
||||
toast(error, "error");
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast("Your password has been successfully changed", "success");
|
||||
},
|
||||
});
|
||||
|
||||
return { changePassword, isLoading, data };
|
||||
};
|
||||
|
|
|
@ -5,7 +5,12 @@ import { useToast } from ".";
|
|||
const useEntriesSearch = ({ journalId }) => {
|
||||
const toast = useToast();
|
||||
|
||||
const [entriesSearch, { isLoading, error, data }] = useMutation(
|
||||
const {
|
||||
mutateAsync: entriesSearch,
|
||||
isLoading,
|
||||
error,
|
||||
data,
|
||||
} = useMutation(
|
||||
JournalService.searchEntries({ journalId }),
|
||||
|
||||
{
|
||||
|
|
|
@ -15,39 +15,42 @@ const useJournalEntries = ({
|
|||
journalId,
|
||||
});
|
||||
|
||||
const getEntries = (searchTerm) => async (key, jorunalId, nextPage = 0) => {
|
||||
if (!nextPage) {
|
||||
nextPage = 0;
|
||||
}
|
||||
|
||||
const searchTags = searchTerm.split(" ").filter(function (n) {
|
||||
if (n.startsWith("#")) return n;
|
||||
else {
|
||||
return null;
|
||||
const getEntries =
|
||||
(searchTerm) =>
|
||||
async ({ pageParam = 0 }) => {
|
||||
if (!pageParam) {
|
||||
pageParam = 0;
|
||||
}
|
||||
});
|
||||
|
||||
const data = await entriesSearch({
|
||||
searchTerm,
|
||||
journalType,
|
||||
isContent,
|
||||
limit,
|
||||
offset: nextPage,
|
||||
searchTags,
|
||||
});
|
||||
const searchTags = searchTerm.split(" ").filter(function (n) {
|
||||
if (n.startsWith("#")) return n;
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const newEntryList = data.data.results.map((entry) => ({
|
||||
...entry,
|
||||
id: entry.entry_url.split("/").pop(),
|
||||
}));
|
||||
return {
|
||||
data: [...newEntryList],
|
||||
nextPage: nextPage + 1,
|
||||
next_offset: data.data.next_offset,
|
||||
total_results: data.data.total_results,
|
||||
offset: data.data.offset,
|
||||
const data = await entriesSearch({
|
||||
searchTerm,
|
||||
journalType,
|
||||
isContent,
|
||||
limit,
|
||||
offset: pageParam,
|
||||
searchTags,
|
||||
});
|
||||
const newEntryList = data.data.results.map((entry) => ({
|
||||
...entry,
|
||||
id: entry.entry_url.split("/").pop(),
|
||||
}));
|
||||
return {
|
||||
data: [...newEntryList],
|
||||
pageParams: {
|
||||
pageParam: pageParam + 1,
|
||||
next_offset: data.data.next_offset,
|
||||
total_results: data.data.total_results,
|
||||
offset: data.data.offset,
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const {
|
||||
data: EntriesPages,
|
||||
|
@ -60,9 +63,10 @@ const useJournalEntries = ({
|
|||
["journal-entries", { journalId }],
|
||||
getEntries(searchQuery),
|
||||
{
|
||||
refetchInterval: 1000,
|
||||
...queryCacheProps,
|
||||
getNextPageParam: (lastPage) => lastPage.next_offset ?? false,
|
||||
getFetchMore: (lastGroup) => {
|
||||
// getNextPageParam: (lastPage) => lastPage.next_offset ?? false,
|
||||
getNextPageParam: (lastGroup) => {
|
||||
return lastGroup.next_offset === null ? false : lastGroup.next_offset;
|
||||
},
|
||||
enabled: !!journalId,
|
||||
|
|
|
@ -16,17 +16,11 @@ const useJournalEntry = (journalId, entryId, journalScope) => {
|
|||
return entry;
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetchedAfterMount,
|
||||
refetch,
|
||||
isError,
|
||||
error,
|
||||
} = useQuery(["journal-entry", { journalId, entryId }], getEntry, {
|
||||
...queryCacheProps,
|
||||
onError: (error) => toast(error, "error"),
|
||||
});
|
||||
const { data, isLoading, isFetchedAfterMount, refetch, isError, error } =
|
||||
useQuery(["journal-entry", { journalId, entryId }], getEntry, {
|
||||
...queryCacheProps,
|
||||
onError: (error) => toast(error, "error"),
|
||||
});
|
||||
|
||||
return { data, isFetchedAfterMount, isLoading, refetch, isError, error };
|
||||
};
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import { useQuery, useMutation, useQueryCache } from "react-query";
|
||||
import { useQuery, useMutation, useQueryClient } from "react-query";
|
||||
import { JournalService } from "../services";
|
||||
import { useToast } from ".";
|
||||
import { queryCacheProps } from "./hookCommon";
|
||||
|
||||
const useJournalPermissions = (journalId, journalScope) => {
|
||||
const cache = useQueryCache();
|
||||
const cache = useQueryClient();
|
||||
const toast = useToast();
|
||||
const { data, isLoading, refetch: getPermissions, error } = useQuery(
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
refetch: getPermissions,
|
||||
error,
|
||||
} = useQuery(
|
||||
["journal-permissions", { journalId }],
|
||||
async () => {
|
||||
if (journalId) {
|
||||
|
@ -64,7 +69,7 @@ const useJournalPermissions = (journalId, journalScope) => {
|
|||
}
|
||||
);
|
||||
|
||||
const [setJournalPermission, setJournalPermissionStatus] = useMutation(
|
||||
const setJournalPermissionMutation = useMutation(
|
||||
JournalService.setJournalPermission(journalId),
|
||||
{
|
||||
onMutate: (data) => {
|
||||
|
@ -111,7 +116,7 @@ const useJournalPermissions = (journalId, journalScope) => {
|
|||
}
|
||||
);
|
||||
|
||||
const [removeJournalPermission, removeJournalPermissionStatus] = useMutation(
|
||||
const removeJournalPermissionMutation = useMutation(
|
||||
JournalService.deleteJournalPermission(journalId),
|
||||
{
|
||||
onMutate: (data) => {
|
||||
|
@ -125,11 +130,10 @@ const useJournalPermissions = (journalId, journalScope) => {
|
|||
const index = previousJournalPermissionResponse.findIndex(
|
||||
(i) => i.holder_id === data.holder_id
|
||||
);
|
||||
newJournalPermissionResponse[
|
||||
index
|
||||
].permissions = newJournalPermissionResponse[index].permissions.filter(
|
||||
(value) => !data.permission_list.includes(value)
|
||||
);
|
||||
newJournalPermissionResponse[index].permissions =
|
||||
newJournalPermissionResponse[index].permissions.filter(
|
||||
(value) => !data.permission_list.includes(value)
|
||||
);
|
||||
|
||||
if (newJournalPermissionResponse[index].permissions.length < 1) {
|
||||
newJournalPermissionResponse.splice(index, 1);
|
||||
|
@ -154,17 +158,9 @@ const useJournalPermissions = (journalId, journalScope) => {
|
|||
}
|
||||
);
|
||||
|
||||
//ToDo: const addUserMutation = useMutation(.. when upgrading to React Query 3
|
||||
const setJournalPermissionMutation = {
|
||||
setJournalPermission,
|
||||
setJournalPermissionStatus,
|
||||
};
|
||||
|
||||
//ToDo: const removeJournalPermissionMutation = useMutation(.. when upgrading to React Query 3
|
||||
const removeJournalPermissionMutation = {
|
||||
removeJournalPermission,
|
||||
removeJournalPermissionStatus,
|
||||
};
|
||||
|
||||
|
||||
|
||||
const holders = data;
|
||||
return {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useCallback, useContext } from "react";
|
||||
import { useMutation, useQueryCache } from "react-query";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { useUser, useRouter, useAnalytics } from ".";
|
||||
import UIContext from "../providers/UIProvider/context";
|
||||
import { AuthService } from "../services";
|
||||
|
@ -8,7 +8,7 @@ const useLogout = () => {
|
|||
const { setLoggingOut } = useContext(UIContext);
|
||||
const router = useRouter();
|
||||
const analytics = useAnalytics();
|
||||
const [revoke, { data }] = useMutation(AuthService.revoke, {
|
||||
const {mutate: revoke, data } = useMutation(AuthService.revoke, {
|
||||
onSuccess: () => {
|
||||
if (analytics.isLoaded) {
|
||||
analytics.mixpanel.track(
|
||||
|
@ -19,7 +19,7 @@ const useLogout = () => {
|
|||
},
|
||||
});
|
||||
const { setUser } = useUser();
|
||||
const cache = useQueryCache();
|
||||
const cache = useQueryClient();
|
||||
|
||||
const logout = useCallback(() => {
|
||||
setLoggingOut(true);
|
||||
|
|
|
@ -43,7 +43,7 @@ const useSignUp = (source) => {
|
|||
const requested_pricing = window.sessionStorage.getItem(
|
||||
"requested_pricing_plan"
|
||||
);
|
||||
const redirectURL = requested_pricing ? "/account/teams" : "/app";
|
||||
const redirectURL = requested_pricing ? "/subscriptions" : "/stream";
|
||||
|
||||
router.push(redirectURL);
|
||||
window.sessionStorage.clear("requested_pricing");
|
||||
|
|
|
@ -5,19 +5,58 @@ import { queryCacheProps } from "./hookCommon";
|
|||
import useStripe from "./useStripe";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
const useSubscriptions = (groupId) => {
|
||||
const useSubscriptions = () => {
|
||||
const toast = useToast();
|
||||
const stripe = useStripe();
|
||||
|
||||
const [manageSubscription, mangeSeatsStatus] = useMutation(
|
||||
SubscriptionsService.manageSubscription(),
|
||||
// const manageSubscription = useMutation(
|
||||
// SubscriptionsService.manageSubscription(),
|
||||
// {
|
||||
// onError: (error) => toast(error, "error"),
|
||||
// onSuccess: (response) => {
|
||||
// const { session_id: sessionId, session_url: sessionUrl } =
|
||||
// response.data;
|
||||
// if (sessionId) {
|
||||
// stripe.redirectToCheckout({ sessionId });
|
||||
// } else if (sessionUrl) {
|
||||
// window.location = sessionUrl;
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
|
||||
const getSubscriptions = async () => {
|
||||
const response = await SubscriptionsService.getSubscriptions();
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
const subscriptionsCache = useQuery(["subscriptions"], getSubscriptions, {
|
||||
...queryCacheProps,
|
||||
onError: (error) => {
|
||||
toast(error, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const getSubscriptionTypes = async () => {
|
||||
const response = await SubscriptionsService.getTypes();
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
const typesCache = useQuery(["subscription_types"], getSubscriptionTypes, {
|
||||
...queryCacheProps,
|
||||
onError: (error) => {
|
||||
toast(error, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const createSubscription = useMutation(
|
||||
SubscriptionsService.createSubscription(),
|
||||
{
|
||||
onError: (error) => toast(error, "error"),
|
||||
onSuccess: (response) => {
|
||||
const {
|
||||
session_id: sessionId,
|
||||
session_url: sessionUrl,
|
||||
} = response.data;
|
||||
subscriptionsCache.refetch();
|
||||
const { session_id: sessionId, session_url: sessionUrl } =
|
||||
response.data;
|
||||
if (sessionId) {
|
||||
stripe.redirectToCheckout({ sessionId });
|
||||
} else if (sessionUrl) {
|
||||
|
@ -27,28 +66,21 @@ const useSubscriptions = (groupId) => {
|
|||
}
|
||||
);
|
||||
|
||||
const manageSubscriptionMutation = {
|
||||
manageSubscription,
|
||||
isLoading: mangeSeatsStatus.isLoading,
|
||||
};
|
||||
const changeNote = useMutation(SubscriptionsService.modifySubscription(), {
|
||||
onError: (error) => toast(error, "error"),
|
||||
onSuccess: (response) => {
|
||||
subscriptionsCache.refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const getSubscriptions = async () => {
|
||||
const response = await SubscriptionsService.getSubscriptions(groupId);
|
||||
return response.data.subscriptions;
|
||||
};
|
||||
const deleteSubscription = useMutation(SubscriptionsService.deleteSubscription(), {
|
||||
onError: (error) => toast(error, "error"),
|
||||
onSuccess: (response) => {
|
||||
subscriptionsCache.refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const subscriptionsCache = useQuery(
|
||||
["subscriptions", groupId],
|
||||
getSubscriptions,
|
||||
{
|
||||
...queryCacheProps,
|
||||
onError: (error) => {
|
||||
toast(error, "error");
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return { manageSubscriptionMutation, subscriptionsCache };
|
||||
return { createSubscription, subscriptionsCache, typesCache, changeNote, deleteSubscription };
|
||||
};
|
||||
|
||||
export default useSubscriptions;
|
||||
|
|
|
@ -2,16 +2,19 @@ import { useMutation } from "react-query";
|
|||
import { AuthService } from "../services";
|
||||
|
||||
const useTokens = () => {
|
||||
const [list, { isLoading, error, data }] = useMutation(
|
||||
AuthService.getTokenList
|
||||
);
|
||||
const [revoke] = useMutation(AuthService.revokeToken, {
|
||||
const {
|
||||
mutate: list,
|
||||
isLoading,
|
||||
error,
|
||||
data,
|
||||
} = useMutation(AuthService.getTokenList);
|
||||
const { mutate: revoke } = useMutation(AuthService.revokeToken, {
|
||||
onSuccess: () => {
|
||||
list();
|
||||
},
|
||||
});
|
||||
|
||||
const [update] = useMutation(AuthService.updateToken, {
|
||||
const { mutate: update } = useMutation(AuthService.updateToken, {
|
||||
onSuccess: () => {
|
||||
list();
|
||||
},
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { useMutation, useQueryCache } from "react-query";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { EntryService } from "../services";
|
||||
import { useToast } from ".";
|
||||
|
||||
const useUpdateEntry = (journalId, entryId) => {
|
||||
const entriesCache = useQueryCache();
|
||||
const entryCache = useQueryCache();
|
||||
const entriesCache = useQueryClient();
|
||||
const entryCache = useQueryClient();
|
||||
const toast = useToast();
|
||||
|
||||
const handleError = (error, variables, context) => {
|
||||
|
@ -20,49 +20,52 @@ const useUpdateEntry = (journalId, entryId) => {
|
|||
toast(error, "error");
|
||||
};
|
||||
|
||||
const [updateEntry] = useMutation(EntryService.update(journalId, entryId), {
|
||||
onMutate: (newData) => {
|
||||
const prevEntriesPages = entriesCache.getQueryData([
|
||||
"journal-entries",
|
||||
{ journalId },
|
||||
]);
|
||||
const { mutate: updateEntry } = useMutation(
|
||||
EntryService.update(journalId, entryId),
|
||||
{
|
||||
onMutate: (newData) => {
|
||||
const prevEntriesPages = entriesCache.getQueryData([
|
||||
"journal-entries",
|
||||
{ journalId },
|
||||
]);
|
||||
|
||||
const newEntriesPages = JSON.parse(JSON.stringify(prevEntriesPages));
|
||||
const prevEntry = entryCache.getQueryData([
|
||||
"journal-entry",
|
||||
{ journalId, entryId },
|
||||
]);
|
||||
const newEntriesPages = JSON.parse(JSON.stringify(prevEntriesPages));
|
||||
const prevEntry = entryCache.getQueryData([
|
||||
"journal-entry",
|
||||
{ journalId, entryId },
|
||||
]);
|
||||
|
||||
const newEntry = { ...prevEntry, ...newData };
|
||||
const newEntry = { ...prevEntry, ...newData };
|
||||
|
||||
newEntriesPages.map((page) => {
|
||||
page.data = page.data.map((entry) => {
|
||||
if (entry.id === entryId) {
|
||||
return {
|
||||
...entry,
|
||||
...newData,
|
||||
// for tags useUpdateTag instead
|
||||
};
|
||||
}
|
||||
return entry;
|
||||
newEntriesPages.map((page) => {
|
||||
page.data = page.data.map((entry) => {
|
||||
if (entry.id === entryId) {
|
||||
return {
|
||||
...entry,
|
||||
...newData,
|
||||
// for tags useUpdateTag instead
|
||||
};
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
return page;
|
||||
});
|
||||
return page;
|
||||
});
|
||||
|
||||
entriesCache.setQueryData(
|
||||
["journal-entries", { journalId }],
|
||||
newEntriesPages
|
||||
);
|
||||
entryCache.setQueryData(
|
||||
["journal-entry", { journalId, entryId }],
|
||||
newEntry
|
||||
);
|
||||
entriesCache.setQueryData(
|
||||
["journal-entries", { journalId }],
|
||||
newEntriesPages
|
||||
);
|
||||
entryCache.setQueryData(
|
||||
["journal-entry", { journalId, entryId }],
|
||||
newEntry
|
||||
);
|
||||
|
||||
return { prevEntriesPages, prevEntry };
|
||||
},
|
||||
onError: (error, variables, context) =>
|
||||
handleError(error, variables, context),
|
||||
});
|
||||
return { prevEntriesPages, prevEntry };
|
||||
},
|
||||
onError: (error, variables, context) =>
|
||||
handleError(error, variables, context),
|
||||
}
|
||||
);
|
||||
|
||||
return updateEntry;
|
||||
};
|
||||
|
|
|
@ -19,6 +19,9 @@ const UIProvider = ({ children }) => {
|
|||
|
||||
const { modal, toggleModal } = useContext(ModalContext);
|
||||
const [searchTerm, setSearchTerm] = useQuery("q", " ", true, false);
|
||||
|
||||
const [entryId, setEntryId] = useState();
|
||||
|
||||
const [searchBarActive, setSearchBarActive] = useState(false);
|
||||
|
||||
// ******* APP state ********
|
||||
|
@ -26,8 +29,9 @@ const UIProvider = ({ children }) => {
|
|||
const [isLoggingOut, setLoggingOut] = useState(false);
|
||||
const [isAppReady, setAppReady] = useState(false);
|
||||
const [isAppView, setAppView] = useState(
|
||||
router.nextRouter.asPath.includes("/app") ||
|
||||
router.nextRouter.asPath.includes("/account")
|
||||
router.nextRouter.asPath.includes("/stream") ||
|
||||
router.nextRouter.asPath.includes("/account") ||
|
||||
router.nextRouter.asPath.includes("/subscriptions")
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -61,8 +65,9 @@ const UIProvider = ({ children }) => {
|
|||
|
||||
useEffect(() => {
|
||||
setAppView(
|
||||
router.nextRouter.asPath.includes("/app") ||
|
||||
router.nextRouter.asPath.includes("/account")
|
||||
router.nextRouter.asPath.includes("/stream") ||
|
||||
router.nextRouter.asPath.includes("/account") ||
|
||||
router.nextRouter.asPath.includes("/subscriptions")
|
||||
);
|
||||
}, [router.nextRouter.asPath, user]);
|
||||
|
||||
|
@ -72,13 +77,13 @@ const UIProvider = ({ children }) => {
|
|||
const [sidebarVisible, setSidebarVisible] = useStorage(
|
||||
window.sessionStorage,
|
||||
"sidebarVisible",
|
||||
false
|
||||
true
|
||||
);
|
||||
// Whether sidebar should be smaller state
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useStorage(
|
||||
window.sessionStorage,
|
||||
"sidebarCollapsed",
|
||||
false
|
||||
true
|
||||
);
|
||||
|
||||
// Whether sidebar should be toggled in mobile view
|
||||
|
@ -96,14 +101,6 @@ const UIProvider = ({ children }) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isMobileView]);
|
||||
|
||||
// //When entrering appView - start with showing sidebar at all times
|
||||
useEffect(() => {
|
||||
if (isAppView) {
|
||||
setSidebarVisible(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isAppView]);
|
||||
|
||||
// *********** Entries layout states **********************
|
||||
|
||||
/**
|
||||
|
@ -111,18 +108,11 @@ const UIProvider = ({ children }) => {
|
|||
* Default true in mobile mode and false in desktop mode
|
||||
*/
|
||||
const [entriesViewMode, setEntriesViewMode] = useState(
|
||||
isMobileView ? (router.params?.entryId ? "entry" : "list") : "split"
|
||||
router.params?.entryId ? "entry" : "list"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setEntriesViewMode(
|
||||
isMobileView ? (router.params?.entryId ? "entry" : "list") : "split"
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isMobileView]);
|
||||
|
||||
useEffect(() => {
|
||||
setEntriesViewMode(isMobileView ? "list" : "split");
|
||||
setEntriesViewMode(router.params?.entryId ? "entry" : "list");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.params?.id]);
|
||||
|
||||
|
@ -151,6 +141,8 @@ const UIProvider = ({ children }) => {
|
|||
setEntriesViewMode,
|
||||
modal,
|
||||
toggleModal,
|
||||
entryId,
|
||||
setEntryId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { http } from "../utils";
|
|||
const AUTH_URL = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;
|
||||
|
||||
export const login = ({ username, password }) => {
|
||||
console.log('login',username, password)
|
||||
const data = new FormData();
|
||||
data.append("username", username);
|
||||
data.append("password", password);
|
||||
|
|
|
@ -1,26 +1,78 @@
|
|||
import { http } from "../utils";
|
||||
// import axios from "axios";
|
||||
|
||||
const AUTH_URL = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;
|
||||
const API = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;
|
||||
|
||||
export const manageSubscription = () => ({
|
||||
groupId,
|
||||
desiredUnits,
|
||||
planType,
|
||||
}) => {
|
||||
export const getTypes = () =>
|
||||
http({
|
||||
method: "GET",
|
||||
url: `${API}/subscription_types/`,
|
||||
});
|
||||
|
||||
export const getSubscriptions = () =>
|
||||
http({
|
||||
method: "GET",
|
||||
url: `${API}/subscriptions/`,
|
||||
});
|
||||
|
||||
export const create = ({ address, note, blockchain }) => {
|
||||
const data = new FormData();
|
||||
data.append("group_id", groupId);
|
||||
data.append("units_required", desiredUnits);
|
||||
data.append("plan_type", planType);
|
||||
return http({
|
||||
data.append("address", address);
|
||||
data.append("note", note);
|
||||
data.append("blockchain", blockchain);
|
||||
http({
|
||||
method: "POST",
|
||||
url: `${AUTH_URL}/subscription/manage`,
|
||||
url: `${API}/subscriptions/`,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const getSubscriptions = (groupId) => {
|
||||
export const deleteJournal = (id) => () =>
|
||||
http({
|
||||
method: "DELETE",
|
||||
url: `${API}/journals/${id}`,
|
||||
});
|
||||
|
||||
export const createSubscription =
|
||||
() =>
|
||||
({ address, type, note }) => {
|
||||
console.log("createSubscription: ", address, type, note);
|
||||
const data = new FormData();
|
||||
data.append("address", address);
|
||||
data.append("subscription_type", type);
|
||||
data.append("note", note);
|
||||
return http({
|
||||
method: "POST",
|
||||
url: `${API}/subscriptions/`,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const modifySubscription =
|
||||
() =>
|
||||
({ id, note }) => {
|
||||
console.log("modifySubscription: ", note, id);
|
||||
const data = new FormData();
|
||||
data.append("note", note);
|
||||
data.append("id", id);
|
||||
return http({
|
||||
method: "POST",
|
||||
url: `${API}/subscription/${id}`,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteSubscription = () => (id) => {
|
||||
console.log("deleteSubscription: ", id);
|
||||
return http({
|
||||
method: "GET",
|
||||
url: `${AUTH_URL}/groups/${groupId}/subscriptions`,
|
||||
method: "DELETE",
|
||||
url: `${API}/subscription/${id}`,
|
||||
});
|
||||
};
|
||||
|
||||
// export const getSubscriptions = (groupId) => {
|
||||
// return http({
|
||||
// method: "GET",
|
||||
// url: `${API}/groups/${groupId}/subscriptions`,
|
||||
// });
|
||||
// };
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import axios from "axios";
|
||||
import enableMockupRequests from "./mockupRequests";
|
||||
let axios = require("axios");
|
||||
|
||||
enableMockupRequests(axios);
|
||||
|
||||
const http = (config) => {
|
||||
const token = localStorage.getItem("BUGOUT_ACCESS_TOKEN");
|
||||
|
@ -15,4 +18,5 @@ const http = (config) => {
|
|||
return axios(options);
|
||||
};
|
||||
|
||||
export { axios };
|
||||
export default http;
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
import moment from "moment";
|
||||
const MOCK_API = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;
|
||||
var MockAdapter = require("axios-mock-adapter");
|
||||
const makeid = (length) => {
|
||||
var result = "";
|
||||
var characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var charactersLength = characters.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const randDate = () => {
|
||||
return moment(
|
||||
new Date(+new Date() - Math.floor(Math.random() * 10000000000))
|
||||
).format("MM/DD/YYYY");
|
||||
};
|
||||
let MockSubscriptions = [
|
||||
{
|
||||
address: makeid(24),
|
||||
id: makeid(24),
|
||||
notes: "lorem", //ToDo: rename in to label
|
||||
created_at: randDate(),
|
||||
subscription_type: "ethereum_blockchain",
|
||||
},
|
||||
{
|
||||
address: makeid(24),
|
||||
id: makeid(24),
|
||||
notes: "lorem",
|
||||
created_at: randDate(),
|
||||
subscription_type: "ethereum_txpool",
|
||||
},
|
||||
{
|
||||
address: makeid(24),
|
||||
id: makeid(24),
|
||||
notes: "lorem",
|
||||
created_at: randDate(),
|
||||
subscription_type: "algorand_blockchain",
|
||||
},
|
||||
{
|
||||
address: makeid(24),
|
||||
id: makeid(24),
|
||||
notes: "lorem",
|
||||
created_at: randDate(),
|
||||
subscription_type: "ethereum_blockchain",
|
||||
},
|
||||
{
|
||||
address: makeid(24),
|
||||
id: makeid(24),
|
||||
notes: "lorem",
|
||||
created_at: randDate(),
|
||||
subscription_type: "ethereum_blockchain",
|
||||
},
|
||||
];
|
||||
const enableMockupRequests = (axiosInstance) => {
|
||||
let mock = new MockAdapter(axiosInstance, { onNoMatch: "passthrough" });
|
||||
|
||||
mock.onGet(`${MOCK_API}/subscription_types/`).reply(200, {
|
||||
data: [
|
||||
{
|
||||
subscription_type: "ethereum_blockchain",
|
||||
id: makeid(24),
|
||||
name: "Ethereum",
|
||||
active: true,
|
||||
subscription_plan_id: makeid(24),
|
||||
},
|
||||
{
|
||||
subscription_type: "ethereum_txpool",
|
||||
id: makeid(24),
|
||||
name: "Ethereum Transaction Pool",
|
||||
active: true,
|
||||
subscription_plan_id: makeid(24),
|
||||
},
|
||||
{
|
||||
subscription_type: "algorand_blockchain",
|
||||
id: makeid(24),
|
||||
name: "Algorand",
|
||||
active: false,
|
||||
subscription_plan_id: makeid(24),
|
||||
},
|
||||
{
|
||||
subscription_type: "free",
|
||||
id: makeid(24),
|
||||
name: "Free subscription",
|
||||
active: true,
|
||||
subscription_plan_id: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
mock.onGet(`${MOCK_API}/subscriptions/`).reply(200, {
|
||||
data: {
|
||||
is_free_subscription_availible: true,
|
||||
subscriptions: MockSubscriptions,
|
||||
},
|
||||
});
|
||||
|
||||
mock.onPost(`${MOCK_API}/subscriptions/`).reply((config) => {
|
||||
const params = config.data; // FormData of {name: ..., file: ...}
|
||||
const id = params.get("id");
|
||||
const note = params.get("note");
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(function () {
|
||||
const data = { id, note };
|
||||
console.log("mock", id, note);
|
||||
MockSubscriptions.push({ ...data });
|
||||
resolve([200, { message: "OK", result: true }]);
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
};
|
||||
export default enableMockupRequests;
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
import { jsx } from "@emotion/react";
|
||||
import React from "react";
|
||||
import { useBreakpointValue, Flex } from "@chakra-ui/react";
|
||||
import SplitPane, { Pane } from "react-split-pane";
|
||||
import { getLayout as getSiteLayout } from "./AppLayout";
|
||||
import { EntriesNavigation } from "../components";
|
||||
import EntriesNavigation from "../components/EntriesNavigation";
|
||||
import { useContext } from "react";
|
||||
import UIContext from "../core/providers/UIProvider/context";
|
||||
const EntriesLayout = (props) => {
|
||||
|
|
|
@ -65,13 +65,13 @@
|
|||
z-index: 101;
|
||||
}
|
||||
.pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout .pro-sidebar-header {
|
||||
border-bottom: 1px solid rgba(173, 173, 173, 0.2);
|
||||
border-bottom: 1px solid #212990;
|
||||
}
|
||||
.pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout .pro-sidebar-content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout .pro-sidebar-footer {
|
||||
border-top: 1px solid rgba(173, 173, 173, 0.2);
|
||||
border-top: 1px solid #212990;
|
||||
}
|
||||
.pro-sidebar > .pro-sidebar-inner > .pro-sidebar-layout ul {
|
||||
list-style-type: none;
|
||||
|
@ -84,7 +84,7 @@
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
background-color: white;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
}
|
||||
|
@ -262,7 +262,7 @@
|
|||
}
|
||||
.pro-sidebar .pro-menu > ul > .pro-sub-menu > .pro-inner-list-item {
|
||||
position: relative;
|
||||
background-color: #2b2b2b;
|
||||
background-color: #212990;
|
||||
}
|
||||
.pro-sidebar .pro-menu > ul > .pro-sub-menu > .pro-inner-list-item > div > ul {
|
||||
padding-top: 15px;
|
||||
|
@ -331,6 +331,7 @@
|
|||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white
|
||||
}
|
||||
.pro-sidebar .pro-menu .pro-menu-item > .pro-inner-item > .pro-item-content {
|
||||
flex-grow: 1;
|
||||
|
@ -346,6 +347,7 @@
|
|||
.pro-icon-wrapper
|
||||
.pro-icon {
|
||||
animation: swing ease-in-out 0.5s 1 alternate;
|
||||
color: white
|
||||
}
|
||||
.pro-sidebar .pro-menu .pro-menu-item.pro-sub-menu > .pro-inner-item:before {
|
||||
background: #adadad;
|
||||
|
@ -420,7 +422,7 @@
|
|||
.pro-menu-item
|
||||
> .pro-inner-item
|
||||
> .pro-icon-wrapper {
|
||||
background-color: #2b2b2b;
|
||||
background-color: #212990;
|
||||
}
|
||||
.pro-sidebar
|
||||
.pro-menu.square
|
||||
|
@ -429,6 +431,9 @@
|
|||
> .pro-icon-wrapper {
|
||||
border-radius: 0;
|
||||
}
|
||||
.pro-menu-item {
|
||||
color: white;
|
||||
}
|
||||
.pro-sidebar
|
||||
.pro-menu.round
|
||||
.pro-menu-item
|
||||
|
@ -462,7 +467,7 @@
|
|||
opacity: 0;
|
||||
}
|
||||
.pro-sidebar.collapsed .pro-menu > ul > .pro-menu-item > .pro-inner-list-item {
|
||||
background-color: #2b2b2b;
|
||||
background-color: white;
|
||||
z-index: 111;
|
||||
}
|
||||
.pro-sidebar.collapsed .pro-menu > ul > .pro-menu-item::before {
|
||||
|
|
|
@ -179,3 +179,8 @@
|
|||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
code {
|
||||
white-space: pre-line !important;
|
||||
}
|
Ładowanie…
Reference in New Issue