Merge pull request #37 from zomglings/frontend-api-auth

Frontend now authenticates with Moonstream API.
pull/39/head
Tim Pechersky 2021-08-02 19:45:42 +08:00 zatwierdzone przez GitHub
commit 5946abb75b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
79 zmienionych plików z 192 dodań i 2098 usunięć

Wyświetl plik

@ -56,8 +56,8 @@ whitelist_paths.update(
{
"/users": "POST",
"/users/token": "POST",
"/users/password/restore": "POST",
"/users/password/reset": "POST",
"/users/password/reset_initiate": "POST",
"/users/password/reset_complete": "POST",
}
)
app.add_middleware(BroodAuthMiddleware, whitelist=whitelist_paths)
@ -87,11 +87,10 @@ async def get_user_handler(request: Request) -> BugoutUser:
return user
@app.post("/password/restore", tags=["users"], response_model=Dict[str, Any])
async def restore_password_handler(request: Request) -> Dict[str, Any]:
user = request.state.user
@app.post("/password/reset_initiate", tags=["users"], response_model=Dict[str, Any])
async def restore_password_handler(email: str = Form(...)) -> Dict[str, Any]:
try:
response = bc.restore_password(email=user.email)
response = bc.restore_password(email=email)
except BugoutResponseException as e:
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
@ -99,7 +98,7 @@ async def restore_password_handler(request: Request) -> Dict[str, Any]:
return response
@app.post("/password/reset", tags=["users"], response_model=BugoutUser)
@app.post("/password/reset_complete", tags=["users"], response_model=BugoutUser)
async def reset_password_handler(
reset_id: str = Form(...), new_password: str = Form(...)
) -> BugoutUser:

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import FourOThree from "../src/components/FourOThree";
const Page403 = () => {

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import FourOFour from "../src/components/FourOFour";
const Page404 = () => {

Wyświetl plik

@ -1,6 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class MyDocument extends Document {

Wyświetl plik

@ -1,13 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
type Data = {
name: string
}
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
res.status(200).json({ name: 'John Doe' })
}

Wyświetl plik

@ -17,7 +17,7 @@ import {
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 TrustedBadge from "../src/components/TrustedBadge";
import { getLayout } from "../src/layouts";
const TEXT_PROPS = {

Wyświetl plik

@ -30,7 +30,7 @@ function PriceWrapper({ children }) {
);
}
const Pricing = (props) => {
const Pricing = () => {
return (
<Box py={12} minH="100vh">
<VStack spacing={2} textAlign="center">
@ -225,20 +225,6 @@ export async function getStaticProps() {
"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 },
};

Wyświetl plik

@ -15,7 +15,6 @@ import {
ModalOverlay,
ModalContent,
} from "@chakra-ui/react";
import { headingStyle } from "./index";
import NewSubscription from "../src/components/NewSubscription";
import { AiOutlinePlusCircle } from "react-icons/ai";
@ -26,6 +25,18 @@ const Subscriptions = () => {
document.title = `My Subscriptions`;
// TODO(zomglings): This should be imported from some common location. For now, copied from
// pages/account/security.js. It was attempting to get imported from "./index", but is not defined
// there.
const headingStyle = {
as: "h2",
pt: 2,
mb: 4,
borderBottom: "solid",
borderColor: "primary.50",
borderBottomWidth: "2px",
};
const newSubscriptionClicked = (isForFree) => {
setIsAddingFreeSubscription(isForFree);
onOpen();

Wyświetl plik

@ -1,9 +1,3 @@
export NEXT_PUBLIC_SIMIOTICS_SEARCH_URL=http://localhost:5000
export NEXT_PUBLIC_MIXPANEL_TOKEN="<YOUR MIXPANEL TOKEN HERE>"
export NEXT_PUBLIC_SIMIOTICS_AUTH_URL=http://localhost:7474
export NEXT_PUBLIC_SIMIOTICS_JOURNALS_URL=http://localhost:7475
export NEXT_PUBLIC_BUGOUT_CONTACTUS_TOKEN="<Brood token for contact user>"
export NEXT_PUBLIC_BUGOUT_CONTACTUS_JOURNAL_ID="<journal ID for contact journal>"
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="<stripe publishable key>"
export NEXT_PUBLIC_MOONSTREAM_API_URL=http://localhost:7481
export NEXT_PUBLIC_MOONSTREAM_API_URL=http://localhost:7481

Wyświetl plik

@ -24,7 +24,7 @@ const AccountIconButton = (props) => {
{...props}
as={IconButton}
aria-label="Account menu"
icon={<RiAccountCircleLine size="26px"/>}
icon={<RiAccountCircleLine size="26px" />}
// variant="outline"
color="gray.100"
/>

Wyświetl plik

@ -1,7 +1,5 @@
import { jsx } from "@emotion/react";
import { useUser, useRouter } from "../core/hooks";
import { useEffect, Fragment, useState } from "react";
import { React, useEffect, Fragment, useState } from "react";
import { Heading, Center, Spinner, Link, Button } from "@chakra-ui/react";
import RouterLink from "next/link";
const ACCOUNT_SCREEN_WIDGETS = {

Wyświetl plik

@ -21,9 +21,7 @@ import {
} from "@chakra-ui/react";
import {
HamburgerIcon,
PlusSquareIcon,
QuestionOutlineIcon,
BellIcon,
ArrowLeftIcon,
ArrowRightIcon,
} from "@chakra-ui/icons";

Wyświetl plik

@ -1,6 +1,4 @@
import { jsx } from "@emotion/react";
import { useState, useEffect } from "react";
import { React, useState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { useChangePassword, useRouter } from "../core/hooks";
import {

Wyświetl plik

@ -1,67 +0,0 @@
import { jsx } from "@emotion/react";
import { GridItem } from "@chakra-ui/react";
import { Heading, Text, Image } from "@chakra-ui/react";
import { Fragment } from "react";
const Block = (content) => {
const TitleRef = content.PrevTitle
? `#${content.PrevTitle}-${content.title}`
: `#${content.title}`;
var HeaderStyle = content.PrevTitle
? { as: "h2", fontSize: "3xl" }
: { as: "h1", fontSize: "4xl" };
return (
<Fragment>
{content.title && (
<GridItem colSpan="12" px="8.3%" py={1} textAlign="center">
<Heading
id={TitleRef}
pt={16}
pb={4}
fontWeight="200"
{...HeaderStyle}
>
{content.title}
</Heading>
</GridItem>
)}
{content.body.map((element, idx) => {
if ("text" in element) {
return (
<GridItem key={idx} colSpan="10" px="8.3%" py={1}>
{element.text.map((paragraph, idx) => {
return (
<Text key={idx} py={2} fontSize="xl">
{paragraph}
</Text>
);
})}
</GridItem>
);
}
if ("image" in element) {
return (
<GridItem key={idx} colSpan="10" py={1} justifySelf="center">
<Image
justifySelf="center"
key={idx}
maxHeight="48rem"
src={element.image.path}
alt={element.image.annotation}
/>
</GridItem>
);
}
if ("title" in element) {
element.PrevTitle = content.title;
return <Block key={idx} {...element} />;
}
return "";
})}
</Fragment>
);
};
export default Block;

Wyświetl plik

@ -1,6 +1,4 @@
import { Fragment } from "react";
import { jsx } from "@emotion/react";
import { Fragment, React } from "react";
import {
useClipboard,
IconButton,

Wyświetl plik

@ -1,95 +0,0 @@
import { jsx } from "@emotion/react";
import {
Button,
Menu,
MenuButton,
MenuList,
MenuItem,
MenuGroup,
MenuDivider,
} from "@chakra-ui/react";
import {
useJournalEntry,
useJournals,
useRouter,
useToast,
} from "../core/hooks";
import { EntryService } from "../core/services";
import { useQueryCache } from "react-query";
const CopyEntryButton = ({ id, journalId }) => {
const router = useRouter();
const cache = useQueryCache();
const { appScope } = router.params;
const { data: entryToCopy, isLoading: sourceIsLoading } = useJournalEntry(
journalId,
id,
appScope
);
const toast = useToast();
const { journalsCache } = useJournals();
const copyEntry = async (targetJournalId) => {
try {
const newEntry = { ...entryToCopy };
newEntry.title = "Copy of " + newEntry.title;
await EntryService.create(targetJournalId)(newEntry).then((response) => {
journalsCache.refetch();
setTimeout(
() => cache.refetchQueries(["journal-entries", { journalId }]),
500
);
if (response.status === 200) {
toast("Copied!", "success");
}
});
} catch (e) {
console.error(
"Error copying entry. Please email engineering@bugout.dev if you encounter this issue.",
e
);
}
};
if (journalsCache.isLoading || sourceIsLoading) return "";
return (
<Menu>
<MenuButton
as={Button}
size="xs"
variant="link"
colorScheme="primary"
ml={1}
>
Copy
</MenuButton>
<MenuList maxH="sm" overflow="scroll">
<MenuGroup title="Destination:">
<MenuItem value={journalId} onClick={() => copyEntry(journalId)}>
{
journalsCache?.data?.data?.journals?.filter(
(journal) => journal.id === journalId
)[0]?.name
}
</MenuItem>
<MenuDivider />
{journalsCache?.data?.data?.journals?.map((journal) => {
if (journal.id === journalId) return "";
return (
<MenuItem
key={`option-${journal.id}`}
value={journal.id}
onClick={() => copyEntry(journal.id)}
>
{journal.name}
</MenuItem>
);
})}
</MenuGroup>
</MenuList>
</Menu>
);
};
export default CopyEntryButton;

Wyświetl plik

@ -1,4 +1,10 @@
import React, { useRef, useEffect, useContext, useState } from "react";
import React, {
useRef,
useEffect,
useContext,
useState,
useCallback,
} from "react";
import {
Flex,
Spinner,
@ -67,14 +73,17 @@ const EntriesNavigation = () => {
]);
const [filterState, setFilterState] = useState([]);
const setNewFilterState = (props) => {
console.log(
"setNewFilterState",
props,
subscriptionsCache.data.subscriptions[0].id
);
_setNewFilterState(props);
};
const setNewFilterState = useCallback(
(props) => {
console.log(
"setNewFilterState",
props,
subscriptionsCache.data.subscriptions[0].id
);
_setNewFilterState(props);
},
[subscriptionsCache?.data?.subscriptions]
);
const loadMoreButtonRef = useRef(null);
const { fetchMore, isFetchingMore, canFetchMore, EntriesPages, isLoading } =
@ -97,11 +106,14 @@ const EntriesNavigation = () => {
}
};
const setFilterProps = (filterIdx, props) => {
const newFilterProps = [...newFilterState];
newFilterProps[filterIdx] = { ...newFilterProps[filterIdx], ...props };
setNewFilterState(newFilterProps);
};
const setFilterProps = useCallback(
(filterIdx, props) => {
const newFilterProps = [...newFilterState];
newFilterProps[filterIdx] = { ...newFilterProps[filterIdx], ...props };
setNewFilterState(newFilterProps);
},
[newFilterState, setNewFilterState]
);
useEffect(() => {
if (
@ -112,7 +124,7 @@ const EntriesNavigation = () => {
value: subscriptionsCache.data.subscriptions[0].address,
});
}
}, [subscriptionsCache.isLoading]);
}, [subscriptionsCache, newFilterState, setFilterProps]);
const entriesPagesData = EntriesPages
? EntriesPages.pages.map((page) => {

Wyświetl plik

@ -2,7 +2,7 @@
/** @jsx jsx */
import { jsx } from "@emotion/react";
import { Flex, Heading, Text, Link } from "@chakra-ui/react";
import CustomIcon from "../components/CustomIcon"
import CustomIcon from "../components/CustomIcon";
import RouterLink from "next/link";
const ICONS = [
@ -14,8 +14,7 @@ const ICONS = [
{ social: "twit", link: "https://twitter.com/Bugout_dev" },
{
social: "slack",
link:
"https://join.slack.com/t/bugout-dev/shared_invite/zt-fhepyt87-5XcJLy0iu702SO_hMFKNhQ",
link: "https://join.slack.com/t/bugout-dev/shared_invite/zt-fhepyt87-5XcJLy0iu702SO_hMFKNhQ",
},
];

Wyświetl plik

@ -13,8 +13,8 @@ import {
Input,
InputRightElement,
} from "@chakra-ui/react";
import CustomIcon from "./CustomIcon"
import Modal from "./Modal"
import CustomIcon from "./CustomIcon";
import Modal from "./Modal";
const ForgotPassword = ({ toggleModal }) => {
const toast = useToast();

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import { Heading, Box, Text, Center, VStack } from "@chakra-ui/react";
const Page404 = () => (
<Box pt={8} w="100%" h="100%">

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import { Heading, Box, Text, VStack, Center } from "@chakra-ui/react";
const Page403 = ({ location }) => (
<Box pt={8} w="100%" h="100%">

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import Head from "next/head";
import propTypes from "prop-types";

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import Head from "next/head";
import propTypes from "prop-types";

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import { IconButton as IconButtonChakra } from "@chakra-ui/react";
import { CheckIcon } from "@chakra-ui/icons";

Wyświetl plik

@ -1,6 +1,4 @@
import { jsx } from "@emotion/react";
import { useState, useEffect } from "react";
import { React, useState, useEffect } from "react";
import { Text } from "@chakra-ui/react";
const LoadingDots = (props) => {

Wyświetl plik

@ -2,7 +2,7 @@
/** @jsx jsx */
import { jsx } from "@emotion/react";
import { Flex } from "@chakra-ui/react";
import CustomIcon from "../CustomIcon"
import CustomIcon from "../CustomIcon";
import styles from "./styles";
const Modal = ({ children, onClose }) => (

Wyświetl plik

@ -13,7 +13,6 @@ const AppNavbar = React.lazy(() => import("./AppNavbar"));
const Navbar = () => {
const { modal, toggleModal, isAppView, isLoggedIn } = useContext(UIContext);
return (
<Flex
boxShadow={["sm", "md"]}

Wyświetl plik

@ -22,7 +22,7 @@ const NewSubscription = ({ isFreeOption, onClose }) => {
const { typesCache, createSubscription } = useSubscriptions();
const { handleSubmit, errors, register } = useForm();
const [radioState, setRadioState] = useState("ethereum_blockchain");
let { getRootProps, getRadioProps, ref } = useRadioGroup({
let { getRootProps, getRadioProps } = useRadioGroup({
name: "type",
defaultValue: radioState,
onChange: setRadioState,

Wyświetl plik

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { InputGroup, InputRightElement, Input } from "@chakra-ui/react";
import CustomIcon from "./CustomIcon"
import CustomIcon from "./CustomIcon";
const PasswordInput = ({ placeholder, name }, ref) => {
const [showPassword, togglePassword] = useState(false);

Wyświetl plik

@ -8,7 +8,7 @@ const RadioCard = (props) => {
const checkbox = getCheckboxProps();
return (
<Flex as="label" h="fill-availible" onClick={() => console.log('hello2')}>
<Flex as="label" h="fill-availible" onClick={() => console.log("hello2")}>
<input {...input} />
<Box
justifyContent="left"

Wyświetl plik

@ -1,5 +1,5 @@
import { jsx } from "@emotion/react";
import {
React,
useState,
useContext,
useRef,

Wyświetl plik

@ -15,7 +15,7 @@ import {
Input,
InputRightElement,
} from "@chakra-ui/react";
import CustomIcon from "./CustomIcon"
import CustomIcon from "./CustomIcon";
import { useLogin } from "../core/hooks";
import PasswordInput from "./PasswordInput";
import Modal from "./Modal";
@ -51,7 +51,7 @@ const SignIn = ({ toggleModal }) => {
colorScheme="primary"
placeholder="Your Bugout username"
name="username"
{...register('username', { required: true })}
{...register("username", { required: true })}
ref={register({ required: "Username is required!" })}
/>
<InputRightElement>

Wyświetl plik

@ -26,12 +26,11 @@ const SignUp = ({ toggleModal }) => {
const { signUp, isLoading, isSuccess } = useSignUp();
const ui = useContext(UIContext);
useEffect(() => {
if (isSuccess) {
ui.toggleModal(null);
}
}, [isSuccess, toggleModal]);
}, [isSuccess, toggleModal, ui]);
return (
<Modal onClose={() => ui.toggleModal(null)}>

Wyświetl plik

@ -18,7 +18,7 @@ import CopyButton from "./CopyButton";
import { useSubscriptions } from "../core/hooks";
import ConfirmationRequest from "./ConfirmationRequest";
const List = (data) => {
const SubscriptionsList = () => {
const { subscriptionsCache, changeNote, deleteSubscription } =
useSubscriptions();
@ -127,4 +127,4 @@ const List = (data) => {
return "";
}
};
export default List;
export default SubscriptionsList;

Wyświetl plik

@ -1,46 +0,0 @@
import { jsx } from "@emotion/react";
import { Tag, TagLabel, Flex, Button } from "@chakra-ui/react";
import { useState } from "react";
const TAGS_DISPLAY_NUM_DEF = 5;
const TagsList = ({ tags }) => {
const [showAllTags, toggleAllTags] = useState(false);
const tagButtonText = showAllTags ? "Show less" : "Show all";
const TagsToShow = () =>
tags
.filter((tag, i) => (showAllTags ? true : i < TAGS_DISPLAY_NUM_DEF))
.map((tag, index) => {
return (
<Tag
variant="subtle"
colorScheme="primary"
size="sm"
key={`${tag}-${index}`}
>
<TagLabel>{tag}</TagLabel>
</Tag>
);
});
return (
<Flex display="flex" flexWrap="wrap" align="center" spacing={1}>
<TagsToShow />
{tags.length > TAGS_DISPLAY_NUM_DEF && (
<Button
m={1}
onClick={() => toggleAllTags(!showAllTags)}
size="xs"
variant="link"
color="primary.600"
ml={1}
style={{ transform: "translateY(-1px)" }}
>
{tagButtonText}
</Button>
)}
</Flex>
);
};
export default TagsList;

Wyświetl plik

@ -1,103 +0,0 @@
import React from "react";
import { IconButton } from "@chakra-ui/react";
import {
Table,
Th,
Td,
Tr,
Thead,
Tbody,
Text,
Center,
Spinner,
} from "@chakra-ui/react";
import { DeleteIcon } from "@chakra-ui/icons";
import CopyButton from "./CopyEntryButton";
import ConfirmationRequest from "./ConfirmationRequest";
import NewTokenTr from "./NewTokenTr";
import { useForm } from "react-hook-form";
const TokenList = ({
tokens,
revoke,
isLoading,
isNewTokenOpen,
toggleNewToken,
createToken,
journalName,
}) => {
const { register, handleSubmit, errors } = useForm();
if (isLoading)
return (
<Center>
<Spinner />
</Center>
);
const handleTokenSubmit = ({ appName, appVersion }) => {
createToken({ appName, appVersion }).then(() => toggleNewToken(false));
};
return (
<form onSubmit={handleSubmit(handleTokenSubmit)}>
<Table
variant="simple"
colorScheme="primary"
justifyContent="center"
alignItems="baseline"
h="auto"
size="sm"
>
<Thead>
<Tr>
<Th>Token</Th>
<Th>App Name</Th>
<Th>App version</Th>
<Th>Action</Th>
</Tr>
</Thead>
<Tbody>
{tokens.map((token, idx) => {
return (
<Tr key={`RestrictedToken-row-${idx}`}>
<Td mr={4} p={0}>
<CopyButton>{token.restricted_token_id}</CopyButton>
</Td>
<Td py={0}>{token.app_name}</Td>
<Td py={0}>{token.app_version}</Td>
<Td py={0}>
<ConfirmationRequest
bodyMessage={"please confirm"}
header={"Delete token"}
onConfirm={() => revoke(token.restricted_token_id)}
>
<IconButton
size="sm"
variant="ghost"
colorScheme="primary"
icon={<DeleteIcon />}
/>
</ConfirmationRequest>
</Td>
</Tr>
);
})}
<NewTokenTr
isOpen={isNewTokenOpen}
toggleSelf={toggleNewToken}
errors={errors}
register={register}
journalName={journalName}
/>
</Tbody>
</Table>
{tokens.length < 1 && (
<Center>
<Text my={4}>Create Usage report tokens here</Text>
</Center>
)}
</form>
);
};
export default TokenList;

Wyświetl plik

@ -1,109 +0,0 @@
import { jsx } from "@emotion/react";
import {
Box,
InputGroup,
InputLeftElement,
FormControl,
FormErrorMessage,
HStack,
Button,
InputRightElement,
Input,
} from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";
import { useEffect, useState, useRef } from "react";
import CustomIcon from "./CustomIcon"
import { useForm } from "react-hook-form";
import { useUser, useLogin } from "../core/hooks";
const TokenRequest = ({ newToken, toggle }) => {
const { user } = useUser();
const { login, isLoading, data } = useLogin("SendLoginProp");
const { handleSubmit, errors, register } = useForm();
const [showPassword, setShowPassword] = useState("password");
const togglePassword = () => {
if (showPassword === "password") {
setShowPassword("text");
} else {
setShowPassword("password");
}
};
const PasswordRef = useRef();
useEffect(() => {
if (PasswordRef.current) {
PasswordRef.current.focus();
}
}, [PasswordRef]);
useEffect(() => {
if (data) {
newToken(data.data.access_token);
toggle(null);
}
}, [data, newToken, toggle]);
const formStyle = {
display: "flex",
flexWrap: "wrap",
minWidth: "100px",
flexFlow: "row wrap-reverse",
aligntContent: "flex-end",
};
if (!user) return ""; //loading...
return (
<Box>
<form onSubmit={handleSubmit(login)} style={formStyle}>
<HStack>
<Button
variant="solid"
colorScheme="primary"
type="submit"
isLoading={isLoading}
>
Submit
</Button>
<FormControl isInvalid={errors.password}>
<InputGroup minWidth="300px">
<InputLeftElement onClick={togglePassword}>
<CustomIcon icon="password" />
</InputLeftElement>
<Input
colorScheme="primary"
variant="filled"
isDisabled={isLoading}
autoComplete="on"
placeholder="Your Bugout password"
name="password"
type={showPassword}
ref={(e) => {
register(e, { required: "Password is required!" });
PasswordRef.current = e;
}}
/>
<InputRightElement onClick={() => toggle(null)}>
<CloseIcon />
</InputRightElement>
</InputGroup>
<FormErrorMessage color="unsafe.400" pl="1" justifyContent="Center">
{errors.password && errors.password.message}
</FormErrorMessage>
</FormControl>
<Input
type="hidden"
ref={register}
name="username"
defaultValue={user?.username}
/>
</HStack>
</form>
</Box>
);
};
export default TokenRequest;

Wyświetl plik

@ -1,5 +1,4 @@
import { jsx } from "@emotion/react";
import { React } from "react";
import { Flex, Image, Link } from "@chakra-ui/react";
const TrustedBadge = ({ name, caseURL, ImgURL }) => {
@ -20,7 +19,6 @@ const TrustedBadge = ({ name, caseURL, ImgURL }) => {
alt={name}
></Image>
{caseURL && (
// <RouterLink href={caseURL} passHref scroll={true}>
<Link
fontSize={["sm", null, "md", "lg"]}
textColor="secondary.900"
@ -28,7 +26,6 @@ const TrustedBadge = ({ name, caseURL, ImgURL }) => {
>
{`Read case study >`}
</Link>
// </RouterLink>
)}
</Flex>
);

Wyświetl plik

@ -1,32 +1,15 @@
export { default as hookCommon } from "./hookCommon";
export { queryCacheProps as hookCommon } from "./hookCommon";
export { default as useAnalytics } from "./useAnalytics";
export { default as useAuthResultHandler } from "./useAuthResultHandler";
export { default as useChangePassword } from "./useChangePassword";
export { default as useClientID } from "./useClientID";
export { default as useCodeVerification } from "./useCodeVerification";
export { default as useCreateEntry } from "./useCreateEntry";
export { default as useCreateJournal } from "./useCreateJournal";
export { default as useDeleteEntry } from "./useDeleteEntry";
export { default as useDeleteJournal } from "./useDeleteJournal";
export { default as useEntriesSearch } from "./useEntriesSearch";
export { default as useForgotPassword } from "./useForgotPassword";
export { default as useGroup } from "./useGroup";
export { default as useGroups } from "./useGroups";
export { default as useHumbug } from "./useHumbug";
export { default as useHumbugTokens } from "./useHumbugTokens";
export { default as useHumbugs } from "./useHumbugs";
export { default as useInviteAccept } from "./useInviteAccept";
export { default as useJournal } from "./useJournal";
export { default as useJournalEntries } from "./useJournalEntries";
export { default as useJournalEntry } from "./useJournalEntry";
export { default as useJournalPermissions } from "./useJournalPermissions";
export { default as useJournalStats } from "./useJournalStats";
export { default as useJournals } from "./useJournals";
export { default as useJournalsScopes } from "./useJournalsScopes";
export { default as useLogin } from "./useLogin";
export { default as useLogout } from "./useLogout";
export { default as useModals } from "./useModals";
export { default as usePreferences } from "./usePreferences";
export { default as usePresignedURL } from "./usePresignedURL";
export { default as useQuery } from "./useQuery";
export { default as useResetPassword } from "./useResetPassword";
@ -36,7 +19,4 @@ export { default as useStorage } from "./useStorage";
export { default as useStripe } from "./useStripe";
export { default as useSubscriptions } from "./useSubscriptions";
export { default as useToast } from "./useToast";
export { default as useTokens } from "./useTokens";
export { default as useUpdateEntry } from "./useUpdateEntry";
export { default as useUpdateTag } from "./useUpdateTag";
export { default as useUser } from "./useUser";

Wyświetl plik

@ -2,9 +2,8 @@ import AnalyticsContext from "../providers/AnalyticsProvider/context";
import { useContext } from "react";
import { useState, useEffect, useCallback } from "react";
const useAnalytics = () => {
const { mixpanel, isLoaded, MIXPANEL_EVENTS, MIXPANEL_PROPS } = useContext(
AnalyticsContext
);
const { mixpanel, isLoaded, MIXPANEL_EVENTS, MIXPANEL_PROPS } =
useContext(AnalyticsContext);
const [trackProps, setTrackProps] = useState({
event: null,
props: null,

Wyświetl plik

@ -1,36 +0,0 @@
import { useMutation, useQueryCache } from "react-query";
import { EntryService } from "../services";
import { useToast } from ".";
const useCreateEntry = (journalId) => {
const cache = useQueryCache();
const toast = useToast();
const [createEntry, { isLoading, data }] = useMutation(
EntryService.create(journalId),
{
onSuccess: (newEntry) => {
const EntriesPages = cache.getQueryData([
"journal-entries",
{ journalId },
]);
EntriesPages[0].data.unshift(newEntry.data);
EntriesPages.map((page, idx) => {
if (idx + 1 < EntriesPages.length) {
const ShiftedEntry = EntriesPages[idx].data.pop();
EntriesPages[idx + 1].data.unshift(ShiftedEntry);
}
return page;
});
cache.setQueryData(["journal-entries", { journalId }], EntriesPages);
},
onError: (error) => {
toast(error, "error");
},
}
);
return { createEntry, isLoading, data };
};
export default useCreateEntry;

Wyświetl plik

@ -1,27 +0,0 @@
import { useMutation, useQueryCache } from "react-query";
import { JournalService } from "../services";
const useCreateJournal = () => {
const cache = useQueryCache();
const createJournal = useMutation(JournalService.create, {
onSuccess: (newJournal) => {
const previousJournals = cache.getQueryData(["journals-list"]);
const newJournals = [...previousJournals];
newJournals.push(newJournal.data);
newJournals.sort(function (a, b) {
var aName = a.name.toUpperCase();
var bName = b.name.toUpperCase();
return aName < bName ? -1 : aName < bName ? 1 : 0;
});
cache.setQueryData(["journals-list"], newJournals);
return () => cache.setQueryData(["journals-list"], previousJournals);
},
});
return createJournal;
};
export default useCreateJournal;

Wyświetl plik

@ -1,33 +0,0 @@
import { useMutation, useQueryCache } from "react-query";
import { EntryService } from "../services";
import { useToast } from ".";
const useDeleteEntry = ({ entryId, journalId }) => {
const cache = useQueryCache();
const toast = useToast();
const [deleteEntry] = useMutation(
EntryService.deleteEntry(journalId, entryId),
{
onSuccess: () => {
const previousEntriesPages = cache.getQueryData([
"journal-entries",
{ journalId },
]);
const newEntriesPages = [...previousEntriesPages];
newEntriesPages.map((page) => {
page.data = page.data.filter((entry) => entry.id !== entryId);
return page;
});
cache.setQueryData(["journal-entries", { journalId }], newEntriesPages);
},
onError: (error) => {
toast(error, "error");
},
}
);
return deleteEntry;
};
export default useDeleteEntry;

Wyświetl plik

@ -1,29 +0,0 @@
import { useMutation, useQueryCache } from "react-query";
import { JournalService } from "../services";
import { useToast } from ".";
const useDeleteJournal = (id) => {
const cache = useQueryCache();
const toast = useToast();
const [deleteJournal] = useMutation(JournalService.deleteJournal(id), {
onMutate: () => {
const previousJournals = [...cache.getQueryData(["journals-list"])];
const newJournals = previousJournals.filter(
(journal) => journal.id !== id
);
cache.setQueryData(["journals-list"], newJournals);
return { previousJournals };
},
onError: (error, variables, context) => {
cache.setQueryData(["journals-list"], context.previousJournals);
toast("Not enough permisions to delete this Journal", "error");
},
});
return { deleteJournal };
};
export default useDeleteJournal;

Wyświetl plik

@ -1,31 +0,0 @@
import { useMutation } from "react-query";
import { JournalService } from "../services";
import { useToast } from ".";
const useEntriesSearch = ({ journalId }) => {
const toast = useToast();
const {
mutateAsync: entriesSearch,
isLoading,
error,
data,
} = useMutation(
JournalService.searchEntries({ journalId }),
{
onError: (error) => {
toast(error, "error");
},
}
);
return {
entriesSearch,
data,
isLoading,
error,
};
};
export default useEntriesSearch;

Wyświetl plik

@ -1,223 +0,0 @@
import { useQuery, useQueryCache, useMutation } from "react-query";
import { GroupService, UserService } from "../services";
import { useToast, useUser } from ".";
import { queryCacheProps } from "./hookCommon";
const useGroup = (groupId) => {
const { user } = useUser();
const cache = useQueryCache();
const toast = useToast();
const { data: GroupUsersResponse, isLoading, refetch: getUsers } = useQuery(
["group-users", groupId],
GroupService.getGroupUsers,
queryCacheProps
);
const getInvites = async (key, groupId) => {
var data;
data = await GroupService.getInvites(groupId);
const newInvites = data.data.invites;
return [...newInvites];
};
const invitesQueryCache = useQuery(
["group-invites", groupId],
getInvites,
queryCacheProps
);
const [addExistingUser, addUserStatus] = useMutation(
GroupService.setGroupUser(groupId),
{
onMutate: (newUser) => {
const NewGroupResponse = cache.getQueryData(["group-users", groupId]);
const previousGroupResponse = JSON.parse(
JSON.stringify(NewGroupResponse)
);
NewGroupResponse.data.users = [
...NewGroupResponse.data.users,
{ email: newUser.email, user_type: newUser.role },
];
cache.setQueryData(["group-users", groupId], {
...NewGroupResponse,
});
return previousGroupResponse;
},
onError: (error, variables, context) => {
cache.setQueryData(["group-users", groupId], context);
toast(error, "error");
},
//fetch data from backend again to fill missing fields of newly added
//user
onSuccess: () => {
getUsers();
},
}
);
const [sendInvite, sendInviteStatus] = useMutation(
GroupService.sendInvite(groupId),
{
onSuccess: () => {
invitesQueryCache.refetch();
},
onError: (error) => {
toast(error, "error");
},
}
);
/**
* addToGroup adds to group.
*
* If `email` is specified and found our users DB - add user.
*
* If `email` is specified and not found in users DB - send invite link
*
* If `email` not specified - return public invite code
*/
const addToGroup = async (invitee) => {
if (invitee?.email) {
const query = `email=${invitee.email}`;
await UserService.findUser(query).then(
(response) => {
if (response.data.user_id) {
addExistingUser(invitee);
}
},
() => {
if (invitee.email) {
sendInvite(invitee);
} else {
toast("user not found", "error");
}
}
);
} else {
sendInvite();
}
};
//ToDo: const addUserMutation = useMutation(.. when upgrading to React Query 3
const addUserMutation = {
addUser: addToGroup,
isLoading: addUserStatus.isLoading,
};
const [removeUser, removeUserStatus] = useMutation(
GroupService.deleteGroupUser(groupId),
{
onMutate: (removedUsername) => {
const NewGroupResponse = cache.getQueryData(["group-users", groupId]);
const previousGroupResponse = JSON.parse(
JSON.stringify(NewGroupResponse)
);
NewGroupResponse.data.users = NewGroupResponse.data.users.filter(
(user) => user.username !== removedUsername
);
cache.setQueryData(["group-users", groupId], {
...NewGroupResponse,
});
if (user.username === removedUsername) {
const NewGroupsResponse = cache.getQueryData(["groups"]);
const previousGroupsResponse = JSON.parse(
JSON.stringify(NewGroupsResponse)
);
NewGroupsResponse.data[groupId].user_type = "none";
cache.setQueryData(["groups"], {
...NewGroupsResponse,
});
return { previousGroupResponse, previousGroupsResponse };
} else {
return { previousGroupResponse };
}
},
onError: (error, variables, context) => {
cache.setQueryData(
["group-users", groupId],
context.previousGroupResponse
);
if (context.previousGroupsResponse) {
cache.setQueryData(["groups"], context.previousGroupsResponse);
}
toast(error, "error");
},
onSuccess: (response, username) => {
if (user.username === username) {
const NewGroupsResponse = cache.getQueryData(["groups"]);
delete NewGroupsResponse.data[groupId];
cache.setQueryData(["groups"], {
...NewGroupsResponse,
});
}
},
}
);
//ToDo: const removeUserMutation = useMutation(.. when upgrading to React Query 3
const removeUserMutation = {
removeUser,
isLoading: removeUserStatus.isLoading,
};
const [revokeInvite, activatePublicInviteStatus] = useMutation(
GroupService.deleteInvite(groupId),
{
onSuccess: () => {
invitesQueryCache.refetch();
},
}
);
const users = {
isLoading: isLoading,
data: GroupUsersResponse?.data?.users,
refetch: getUsers,
};
const readInvites = ({ isPublic, isPersonal }) => {
const allInvites = cache.getQueryData(["group-invites", groupId]);
if (allInvites) {
if (isPublic && isPersonal) {
return allInvites;
} else if (isPersonal) {
return allInvites.filter((item) => item.email && item.active);
} else {
return allInvites.filter((item) => !item.email && item.active);
}
}
};
const invites = {
personal: readInvites({ isPublic: false, isPersonal: true }),
public: readInvites({ isPublic: true, isPersonal: false }),
all: readInvites({ isPublic: true, isPersonal: true }),
isLoading: invitesQueryCache.isLoading,
get: () => invitesQueryCache.refetch(),
createPersonal: addToGroup,
createPublic: () => addToGroup(),
isLoadingCreate: sendInviteStatus.isLoading,
revokeInvite: revokeInvite,
isLoadingRevoke: activatePublicInviteStatus.isLoading,
};
return {
users,
addUserMutation,
removeUserMutation,
invites,
};
};
export default useGroup;

Wyświetl plik

@ -1,118 +0,0 @@
import { useQuery, useQueryCache, useMutation } from "react-query";
import { GroupService } from "../services";
import { useToast } from ".";
import { queryCacheProps } from "./hookCommon";
import { useUser } from "../hooks";
const useGroups = () => {
const cache = useQueryCache();
const toast = useToast();
const { user } = useUser();
const fetchGroups = async (query) => {
const response = await GroupService.getGroups(query);
return response?.data?.groups;
};
const { data, isLoading, refetch: getGroups } = useQuery(
"groups",
fetchGroups,
queryCacheProps
);
const [createGroup, createStatus] = useMutation(
(groupName) => GroupService.createGroup(groupName),
{
onSuccess: (response) => {
const currentGroups = cache.getQueryData(["groups"]);
currentGroups.push({
autogenerated: false,
group_id: response.data.id,
group_name: response.data.group_name,
user_id: user.user_id,
user_type: "owner",
});
cache.setQueryData(["groups"], currentGroups);
cache.refetchQueries(["groups"]);
},
onError: (error) => {
toast(error, "error");
},
}
);
//ToDo: const createGroupMutation = useMutation(.. when upgrading to React Query 3
const createGroupMutation = {
createGroup,
isLoading: createStatus.isLoading,
};
const [deleteGroup, deleteStatus] = useMutation(GroupService.deleteGroup, {
onMutate: (groupId) => {
const previousGroups = cache.getQueryData(["groups"]);
const newGroups = previousGroups.filter(
(group) => group.group_id !== groupId
);
cache.setQueryData(["groups"], [...newGroups]);
return previousGroups;
},
onError: (error, variables, context) => {
cache.setQueryData(["groups"], context);
toast(error, "error");
},
});
//ToDo: const createGroupMutation = useMutation(.. when upgrading to React Query 3
const deleteGroupMutation = {
deleteGroup,
isLoading: deleteStatus.isLoading,
};
const [renameGroup, renameStatus] = useMutation(GroupService.setGroupName, {
onMutate: (data) => {
const newGroups = cache.getQueryData(["groups"]);
const previousGroups = [...newGroups];
newGroups.forEach((group) => {
if (group.group_id === data.groupId) {
group.group_name = data.name;
}
});
cache.setQueryData(["groups"], [...newGroups]);
return previousGroups;
},
onError: (error, variables, context) => {
cache.setQueryData(["groups"], context);
toast(error, "error");
},
onSuccess: () => {
getGroups();
},
});
//ToDo: const renameGroupMutation = useMutation(.. when upgrading to React Query 3
const renameGroupMutation = {
renameGroup,
isLoading: renameStatus.isLoading,
status: { ...renameStatus },
};
return {
data,
isLoading,
getGroups,
createGroupMutation,
deleteGroupMutation,
renameGroupMutation,
};
};
export default useGroups;

Wyświetl plik

@ -1,26 +0,0 @@
import { HumbugService } from "../services";
import { useToast } from ".";
import { useQuery } from "react-query";
import { queryCacheProps } from "./hookCommon";
const useHumbug = (humbugId) => {
const toast = useToast();
const { data, isLoading, refetch } = useQuery(
["humbug", { humbugId }],
HumbugService.getHumbug,
{
...queryCacheProps,
onError: (error) => {
toast(error, "error");
},
}
);
return {
data,
isLoading,
refetch,
};
};
export default useHumbug;

Wyświetl plik

@ -1,90 +0,0 @@
import { HumbugService } from "../services";
import { useToast } from ".";
import { useMutation, useQuery, useQueryCache } from "react-query";
import { queryCacheProps } from "./hookCommon";
const useHumbugTokens = (humbugId) => {
const toast = useToast();
const cache = useQueryCache();
const getTokens = async (key, { humbugId }) => {
var data;
data = await HumbugService.getTokens(humbugId);
const newHumbugTokens = data.data.tokens;
return [...newHumbugTokens];
};
const humbugTokensCache = useQuery(
[`Humbug-Tokens`, { humbugId }],
getTokens,
queryCacheProps
);
const [
createRestrictedToken,
{
isLoading: isLoadingRestrictedToken,
error: errorRestrictedToken,
data: dataRestrictedToken,
},
] = useMutation(HumbugService.createRestrictedToken(humbugId), {
onMutate: () => {},
onError: (error) => {
toast(error, "error");
},
onSuccess: (response) => {
const oldData = cache.getQueryData([`Humbug-Tokens`, { humbugId }]);
const newData = [...oldData, ...response.data.tokens];
cache.setQueryData([`Humbug-Tokens`, { humbugId }], newData);
},
});
const createRestrictedTokenMutation = {
createRestrictedToken,
isLoading: isLoadingRestrictedToken,
error: errorRestrictedToken,
data: dataRestrictedToken,
};
const [
deleteRestrictedToken,
{
isLoading: isLoadingDeleteRestrictedToken,
error: errorDeleteRestrictedToken,
data: dataDeleteRestrictedToken,
},
] = useMutation(HumbugService.deleteRestrictedToken(humbugId), {
onMutate: (tokenId) => {
var newTokens = cache.getQueryData([`Humbug-Tokens`, { humbugId }]);
const previousTokens = [...newTokens];
newTokens = newTokens.filter(
(token) => token.restricted_token_id !== tokenId
);
cache.setQueryData([`Humbug-Tokens`, { humbugId }], newTokens);
return previousTokens;
},
onError: (error, variables, context) => {
cache.setQueryData([`Humbug-Tokens`, { humbugId }], context);
toast(error, "error");
},
});
const deleteRestrictedTokenMutation = {
deleteRestrictedToken,
isLoading: isLoadingDeleteRestrictedToken,
error: errorDeleteRestrictedToken,
data: dataDeleteRestrictedToken,
};
return {
humbugTokensCache,
createRestrictedTokenMutation,
deleteRestrictedTokenMutation,
};
};
export default useHumbugTokens;

Wyświetl plik

@ -1,99 +0,0 @@
import { HumbugService } from "../services";
import { useToast } from ".";
import { useMutation, useQuery, useQueryCache } from "react-query";
import { queryCacheProps } from "./hookCommon";
const useHumbugs = (query) => {
const toast = useToast();
const cache = useQueryCache();
const getHumbugItegrations = async (key, { query }) => {
var data;
if (!query) {
data = await HumbugService.getHumbugItegrations();
} else {
data = await HumbugService.getHumbugItegrations(query);
}
const newHumbugIntegrations = data.data.integrations;
return [...newHumbugIntegrations];
};
const { data: humbugList } = useQuery(
["humbugs", { query }],
getHumbugItegrations,
queryCacheProps
);
const [
createHumbug,
{
isLoading: isLoadingCreateHumbug,
error: errorCreateHumbug,
data: dataCreateHumbug,
},
] = useMutation(HumbugService.createHumbug, {
onSuccess: (response) => {
var oldData = cache.getQueryData(["humbugs", { query }]);
var newData = oldData ? [...oldData, response.data] : [response.data];
cache.setQueryData(["humbugs", { query }], newData);
cache.refetchQueries(["journals-list"]);
cache.refetchQueries(["humbugs"], newData);
},
onError: (error) => {
toast(error, "error");
},
});
const [
deleteHumbug,
{
isLoading: isLoadingDeleteHumbug,
error: errorDeleteHumbug,
data: dataDeleteHumbug,
},
] = useMutation(HumbugService.deleteHumbug, {
onMutate: (humbugId) => {
var newHumbugs = cache.getQueryData(["humbugs", { query }]);
const previousHumbugs = [...newHumbugs];
newHumbugs = newHumbugs.filter((humbug) => humbug.id !== humbugId);
cache.setQueryData(["humbugs", { query }], newHumbugs);
var newHumbugsAll = cache.getQueryData(["humbugs", {}]);
const prevHumbugsAll = [...newHumbugsAll];
newHumbugsAll = newHumbugs.filter((humbug) => humbug.id !== humbugId);
cache.setQueryData(["humbugs", {}], newHumbugsAll);
return { previousHumbugs, prevHumbugsAll };
},
onError: (error, variables, context) => {
cache.setQueryData(["humbugs", { query }], context.previousHumbugs);
cache.setQueryData(["humbugs"], context.prevHumbugsAll);
toast(error, "error");
},
});
const createHumbugMutation = {
createHumbug,
isLoading: isLoadingCreateHumbug,
errorCreateHumbug,
dataCreateHumbug,
};
const deleteHumbugMutation = {
deleteHumbug,
isLoading: isLoadingDeleteHumbug,
errorDeleteHumbug,
dataDeleteHumbug,
};
return {
humbugList,
createHumbugMutation,
deleteHumbugMutation,
};
};
export default useHumbugs;

Wyświetl plik

@ -1,34 +0,0 @@
import { useQuery } from "react-query";
import { JournalService } from "../services";
import { queryCacheProps } from "./hookCommon";
import { useToast } from ".";
const useJournal = (journalId, journalScope) => {
const toast = useToast();
const getJournal = async (query, key) => {
const journalEndpoint =
journalScope === "public"
? JournalService.getPublicJournal
: JournalService.getJournal;
const data = await journalEndpoint(key, { journalId });
const entry = data.data;
return entry;
};
const { data, isLoading, refetch } = useQuery("journal", getJournal, {
...queryCacheProps,
onError: (error) => {
toast(error, "error");
},
});
return {
data,
isLoading,
refetch,
};
};
export default useJournal;

Wyświetl plik

@ -1,86 +0,0 @@
import { useInfiniteQuery } from "react-query";
import { useEntriesSearch } from ".";
import { queryCacheProps } from "./hookCommon";
const useJournalEntries = ({
journalId,
journalType,
isContent,
pageSize,
searchQuery,
}) => {
const limit = pageSize ? pageSize : 25;
const { entriesSearch } = useEntriesSearch({
journalId,
});
const getEntries =
(searchTerm) =>
async ({ pageParam = 0 }) => {
if (!pageParam) {
pageParam = 0;
}
const searchTags = searchTerm.split(" ").filter(function (n) {
if (n.startsWith("#")) return n;
else {
return null;
}
});
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,
isFetchingMore,
isLoading,
canFetchMore,
fetchMore,
refetch,
} = useInfiniteQuery(
["journal-entries", { journalId }],
getEntries(searchQuery),
{
refetchInterval: 1000,
...queryCacheProps,
// getNextPageParam: (lastPage) => lastPage.next_offset ?? false,
getNextPageParam: (lastGroup) => {
return lastGroup.next_offset === null ? false : lastGroup.next_offset;
},
enabled: !!journalId,
}
);
return {
EntriesPages,
fetchMore,
isFetchingMore,
canFetchMore,
refetch,
isLoading,
};
};
export default useJournalEntries;

Wyświetl plik

@ -1,178 +0,0 @@
import { useQuery, useMutation, useQueryClient } from "react-query";
import { JournalService } from "../services";
import { useToast } from ".";
import { queryCacheProps } from "./hookCommon";
const useJournalPermissions = (journalId, journalScope) => {
const cache = useQueryClient();
const toast = useToast();
const {
data,
isLoading,
refetch: getPermissions,
error,
} = useQuery(
["journal-permissions", { journalId }],
async () => {
if (journalId) {
if (journalScope === "personal") {
const response = await JournalService.getJournalPermissions(
journalId
);
if (!response.data || !response.data.permissions) {
return [];
}
return response.data.permissions;
} else {
return [];
}
} else {
const response = { data: { scopes: ["public"] } };
return response;
}
},
queryCacheProps
);
const {
data: currentUserPermissions,
refetch: getCurrentUserPermissions,
isLoading: currentUserPermissionsIsLoading,
} = useQuery(
["journal-permissions-current-user", { journalId }],
async () => {
if (journalId) {
if (journalScope === "personal") {
let response = { data: {} };
try {
response = await JournalService.getCurrentUserJournalPermissions(
journalId
);
} catch (error) {
console.warn("error retrieving scopes:", error);
}
if (!response.data || !response.data.scopes) {
return [];
}
return response.data.scopes.map((scope) => scope.permission);
} else {
return [];
}
} else {
const response = { data: { scopes: ["public"] } };
return response;
}
},
{
...queryCacheProps,
staleTime: 720000, // 12 hours
}
);
const setJournalPermissionMutation = useMutation(
JournalService.setJournalPermission(journalId),
{
onMutate: (data) => {
const newJournalPermissionResponse = cache.getQueryData([
"journal-permissions",
{ journalId },
]);
const previousJournalPermissionResponse = JSON.parse(
JSON.stringify(newJournalPermissionResponse)
);
const index = previousJournalPermissionResponse.findIndex(
(i) => i.holder_id === data.holder_id
);
if (index === -1) {
newJournalPermissionResponse.push({
permissions: [...data.permission_list],
holder_id: data.holder_id,
holder_type: data.holder_type,
});
} else {
newJournalPermissionResponse[index].permissions = [
...newJournalPermissionResponse[index].permissions,
...data.permission_list,
];
}
cache.setQueryData(
["journal-permissions", { journalId }],
newJournalPermissionResponse
);
return previousJournalPermissionResponse;
},
onError: (error, value, context) => {
cache.setQueryData(["journal-permissions", { journalId }], context);
toast(error, "error");
},
onSuccess: () => {
getCurrentUserPermissions();
},
}
);
const removeJournalPermissionMutation = useMutation(
JournalService.deleteJournalPermission(journalId),
{
onMutate: (data) => {
const newJournalPermissionResponse = cache.getQueryData([
"journal-permissions",
{ journalId },
]);
const previousJournalPermissionResponse = JSON.parse(
JSON.stringify(newJournalPermissionResponse)
);
const index = previousJournalPermissionResponse.findIndex(
(i) => i.holder_id === data.holder_id
);
newJournalPermissionResponse[index].permissions =
newJournalPermissionResponse[index].permissions.filter(
(value) => !data.permission_list.includes(value)
);
if (newJournalPermissionResponse[index].permissions.length < 1) {
newJournalPermissionResponse.splice(index, 1);
}
cache.setQueryData(
["journal-permissions", { journalId }],
newJournalPermissionResponse
);
return previousJournalPermissionResponse;
},
onError: (error, value, context) => {
cache.setQueryData(["journal-permissions", { journalId }], context);
toast(error, "error");
},
onSuccess: () => {
getCurrentUserPermissions();
},
}
);
const holders = data;
return {
holders,
isLoading,
getPermissions,
error,
setJournalPermissionMutation,
removeJournalPermissionMutation,
currentUserPermissions,
currentUserPermissionsIsLoading,
};
};
export default useJournalPermissions;

Wyświetl plik

@ -1,36 +0,0 @@
import { useQuery } from "react-query";
import { JournalService } from "../services";
import { queryCacheProps } from "./hookCommon";
import { useToast } from ".";
const useJournalStats = (journalId) => {
const toast = useToast();
const getStats = async (query, key) => {
const response = await JournalService.getJournalStats(key, { journalId })(
query
);
return response.data;
};
const { data, isLoading, refetch } = useQuery(
["journal-stats", { journalId }],
getStats,
{
...queryCacheProps,
onError: (error) => {
toast(error, "error");
},
onSuccess: () => {},
}
);
return {
data,
isLoading,
refetch,
};
};
export default useJournalStats;

Wyświetl plik

@ -1,58 +0,0 @@
import { useQuery } from "react-query";
import { JournalService } from "../services";
import { queryCacheProps } from "./hookCommon";
import { useToast, useUser } from ".";
const useJournals = () => {
const toast = useToast();
const { user } = useUser();
const getAllJournals = async () => {
const response = await JournalService.getAll();
const newAllJournals = [...response.data.journals];
newAllJournals.sort(function (a, b) {
var aName = a.name.toUpperCase();
var bName = b.name.toUpperCase();
return aName < bName ? -1 : aName < bName ? 1 : 0;
});
return [...newAllJournals];
};
const journalsCache = useQuery("journals-list", getAllJournals, {
...queryCacheProps,
placeholderData: [],
enabled: !!user,
onError: (error) => {
toast(error, "error");
},
});
const getPublicJournals = async () => {
const response = await JournalService.getPublicJournals();
const newPublicJournals = [...response.data.journals];
newPublicJournals.sort(function (a, b) {
var aName = a.name.toUpperCase();
var bName = b.name.toUpperCase();
return aName < bName ? -1 : aName < bName ? 1 : 0;
});
return [...newPublicJournals];
};
const publicJournalsCache = useQuery(["journals-public"], getPublicJournals, {
placeholderData: [],
...queryCacheProps,
onError: (error) => {
toast(error, "error");
},
});
return {
journalsCache,
publicJournalsCache,
};
};
export default useJournals;

Wyświetl plik

@ -1,25 +0,0 @@
import { useQuery } from "react-query";
import { JournalService } from "../services";
import { queryCacheProps } from "./hookCommon";
const useJournals = () => {
const getJournalsScopes = async () => {
var data;
data = await JournalService.getJournalsScopes();
const scopes = data.data.scopes;
return [...scopes];
};
const scopesCache = useQuery("journals-scopes", getJournalsScopes, {
...queryCacheProps,
enabled: true,
});
return {
scopesCache,
};
};
export default useJournals;

Wyświetl plik

@ -28,7 +28,7 @@ const useLogin = (loginType) => {
if (!data) {
return;
}
localStorage.setItem("BUGOUT_ACCESS_TOKEN", data.data.access_token);
localStorage.setItem("MOONSTREAM_ACCESS_TOKEN", data.data.id);
const invite_code = window.sessionStorage.getItem("invite_code");
if (invite_code) {
inviteAccept(invite_code);
@ -36,10 +36,12 @@ const useLogin = (loginType) => {
getUser();
if (analytics.isLoaded) {
analytics.mixpanel.people.set_once({
[`${analytics.MIXPANEL_EVENTS.FIRST_LOGIN_DATE}`]: new Date().toISOString(),
[`${analytics.MIXPANEL_EVENTS.FIRST_LOGIN_DATE}`]:
new Date().toISOString(),
});
analytics.mixpanel.people.set({
[`${analytics.MIXPANEL_EVENTS.LAST_LOGIN_DATE}`]: new Date().toISOString(),
[`${analytics.MIXPANEL_EVENTS.LAST_LOGIN_DATE}`]:
new Date().toISOString(),
});
analytics.mixpanel.track(
`${analytics.MIXPANEL_EVENTS.USER_LOGS_IN}`,

Wyświetl plik

@ -8,7 +8,7 @@ const useLogout = () => {
const { setLoggingOut } = useContext(UIContext);
const router = useRouter();
const analytics = useAnalytics();
const {mutate: revoke, data } = useMutation(AuthService.revoke, {
const { mutate: revoke, data } = useMutation(AuthService.revoke, {
onSuccess: () => {
if (analytics.isLoaded) {
analytics.mixpanel.track(
@ -34,7 +34,7 @@ const useLogout = () => {
return;
}
localStorage.removeItem("BUGOUT_ACCESS_TOKEN");
localStorage.removeItem("MOONSTREAM_ACCESS_TOKEN");
cache.clear();
}, [data, cache]);

Wyświetl plik

@ -1,40 +0,0 @@
import { useQuery, useQueryCache } from "react-query";
import { PreferencesService } from "../services";
import { queryCacheProps } from "./hookCommon";
const getPreferences = async () => {
let preferences = {};
try {
const defaultJournalResponse = await PreferencesService.getDefaultJournal();
preferences.defaultJournal = defaultJournalResponse.data?.id;
} catch {
preferences.defaultJournal = null;
}
return preferences;
};
const usePreferences = () => {
const preferencesKey = "preferences-default-journal";
const cache = useQueryCache();
const { data, refetch } = useQuery(preferencesKey, getPreferences, {
...queryCacheProps,
staleTime: 300000,
});
const invalidateAfter = (modifierFn) => {
return async function (...args) {
await modifierFn(...args);
cache.invalidateQueries(preferencesKey);
};
};
const setPreference = {
defaultJournal: invalidateAfter(PreferencesService.setDefaultJournal),
};
const unsetPreference = {
defaultJournal: invalidateAfter(PreferencesService.unsetDefaultJournal),
};
return { data, refetch, setPreference, unsetPreference };
};
export default usePreferences;

Wyświetl plik

@ -15,10 +15,13 @@ const useSignUp = (source) => {
isLoading,
error,
data,
isSuccess
isSuccess,
} = useMutation(AuthService.register(), {
onSuccess: (response) => {
localStorage.setItem("BUGOUT_ACCESS_TOKEN", response.data.access_token);
localStorage.setItem(
"MOONSTREAM_ACCESS_TOKEN",
response.data.access_token
);
const invite_code = window.sessionStorage.getItem("invite_code");
if (invite_code) {
inviteAccept(invite_code);

Wyświetl plik

@ -58,8 +58,7 @@ const useJournalEntries = ({
getNextPageParam: (lastGroup) => {
return lastGroup.next_offset === null ? false : lastGroup.next_offset;
},
onSuccess: (data) => {
},
onSuccess: (data) => {},
enabled: !!enabled,
});

Wyświetl plik

@ -1,4 +1,3 @@
import { jsx } from "@emotion/react";
import { useToast as useChakraToast, Box } from "@chakra-ui/react";
import { useCallback } from "react";

Wyświetl plik

@ -1,33 +0,0 @@
import { useMutation } from "react-query";
import { AuthService } from "../services";
const useTokens = () => {
const {
mutate: list,
isLoading,
error,
data,
} = useMutation(AuthService.getTokenList);
const { mutate: revoke } = useMutation(AuthService.revokeToken, {
onSuccess: () => {
list();
},
});
const { mutate: update } = useMutation(AuthService.updateToken, {
onSuccess: () => {
list();
},
});
return {
list,
update,
revoke,
isLoading,
data,
error,
};
};
export default useTokens;

Wyświetl plik

@ -1,73 +0,0 @@
import { useMutation, useQueryClient } from "react-query";
import { EntryService } from "../services";
import { useToast } from ".";
const useUpdateEntry = (journalId, entryId) => {
const entriesCache = useQueryClient();
const entryCache = useQueryClient();
const toast = useToast();
const handleError = (error, variables, context) => {
entriesCache.setQueryData(
["journal-entries", { journalId }],
context.prevEntriesPages
);
entryCache.setQueryData(
["journal-entry", { journalId, entryId }],
context.prevEntry
);
toast(error, "error");
};
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 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;
});
return page;
});
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 updateEntry;
};
export default useUpdateEntry;

Wyświetl plik

@ -1,100 +0,0 @@
import { useCallback } from "react";
import { useMutation, useQueryCache } from "react-query";
import { TagService } from "../services";
import { useToast } from ".";
const useUpdateTag = (journalId, entryId) => {
const cache = useQueryCache();
const entryCache = useQueryCache();
const toast = useToast();
const updateCache = (tagUpdate) => {
const prevEntriesPages = cache.getQueryData([
"journal-entries",
{ journalId },
]);
const newEntriesPages = JSON.parse(JSON.stringify(prevEntriesPages));
const prevEntry = entryCache.getQueryData([
"journal-entry",
{ journalId, entryId },
]);
const newEntry = JSON.parse(JSON.stringify(prevEntry));
newEntriesPages.map((page) => {
page.data = page.data.map((entry) => {
if (entry.id === entryId) {
var newTags;
if (tagUpdate.action === "add") {
newTags = [...entry.tags, tagUpdate.tag];
entry.tags = newTags;
} else {
newTags = entry.tags.filter((item) => item !== tagUpdate.tag);
entry.tags = newTags;
}
newEntry.tags = newTags;
entryCache.setQueryData(
["journal-entry", { journalId, entryId }],
newEntry
);
}
return entry;
});
return page;
});
cache.setQueryData(["journal-entries", { journalId }], newEntriesPages);
return { prevEntriesPages, prevEntry };
};
const handleError = (error, variables, context) => {
if (context) {
cache.setQueryData(
["journal-entries", { journalId }],
context.prevEntriesPages
);
entryCache.setQueryData(
["journal-entry", { journalId, entryId }],
context.prevEntry
);
}
toast(error, "error");
};
const [addTag] = useMutation(TagService.createTag(journalId, entryId), {
onMutate: (data) => {
let retval = updateCache({ tag: data.tags[0], action: "add" });
return retval;
},
onError: (error, variables, context) =>
handleError(error, variables, context),
});
const [deleteTag] = useMutation(TagService.deleteTag(journalId, entryId), {
onMutate: (data) => {
let retval = updateCache(data);
return retval;
},
onError: (error, variables, context) =>
handleError(error, variables, context),
});
const updateTag = useCallback(
(tagUpdate) => {
switch (tagUpdate.action) {
case "add":
addTag({ tags: [tagUpdate.tag] });
break;
case "delete":
deleteTag({ tag: tagUpdate.tag });
break;
default:
return "";
}
},
[addTag, deleteTag]
);
return updateTag;
};
export default useUpdateTag;

Wyświetl plik

@ -1 +0,0 @@
export const AUTH_URL = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;

Wyświetl plik

@ -1,14 +1,14 @@
import React, { useState, useEffect, useCallback } from "react";
import http from "axios";
import { AUTH_URL } from "./constants";
import UserContext from "./context";
import { AUTH_URL } from "../../services/auth.service";
const UserProvider = ({ children }) => {
const [user, setUser] = useState();
const [isInit, setInit] = useState(false);
const getUser = useCallback(() => {
const token = localStorage.getItem("BUGOUT_ACCESS_TOKEN");
const token = localStorage.getItem("MOONSTREAM_ACCESS_TOKEN");
if (!token) {
setInit(true);
return setUser(null);
@ -16,7 +16,7 @@ const UserProvider = ({ children }) => {
const headers = { Authorization: `Bearer ${token}` };
http
.get(`${AUTH_URL}/user`, { headers })
.get(`${AUTH_URL}/`, { headers })
.then((response) => {
setUser(response.data);
})

Wyświetl plik

@ -1,9 +1,9 @@
import { http } from "../utils";
const AUTH_URL = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;
const API_URL = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
export const AUTH_URL = `${API_URL}/users`;
export const login = ({ username, password }) => {
console.log('login',username, password)
const data = new FormData();
data.append("username", username);
data.append("password", password);
@ -17,66 +17,38 @@ export const login = ({ username, password }) => {
export const revoke = () => {
return http({
method: "POST",
url: `${AUTH_URL}/revoke/${localStorage.getItem("BUGOUT_ACCESS_TOKEN")}`,
});
};
export const register = () => ({ username, email, password }) => {
const data = new FormData();
data.append("username", username);
data.append("email", email);
data.append("password", password);
return http({
method: "POST",
url: `${AUTH_URL}/user`,
data,
}).then(() =>
http({
method: "POST",
url: `${AUTH_URL}/token`,
data,
})
);
};
export const verify = ({ code }) => {
const data = new FormData();
data.append("verification_code", code);
return http({
method: "POST",
url: `${AUTH_URL}/confirm`,
data,
});
};
export const getTokenList = () => {
const data = new FormData();
return http({
method: "GET",
url: `${AUTH_URL}/tokens`,
data,
});
};
export const updateToken = ({ note, token }) => {
const data = new FormData();
data.append("token_note", note);
data.append("access_token", token);
return http({
method: "PUT",
method: "DELETE",
url: `${AUTH_URL}/token`,
data,
});
};
export const register =
() =>
({ username, email, password }) => {
const data = new FormData();
data.append("username", username);
data.append("email", email);
data.append("password", password);
return http({
method: "POST",
url: `${AUTH_URL}/`,
data,
}).then(() =>
http({
method: "POST",
url: `${AUTH_URL}/token`,
data,
})
);
};
export const forgotPassword = ({ email }) => {
const data = new FormData();
data.append("email", email);
return http({
method: "POST",
url: `${AUTH_URL}/reset`,
url: `${AUTH_URL}/password/reset_initiate`,
data,
});
};
@ -87,18 +59,11 @@ export const resetPassword = ({ newPassword, resetId }) => {
data.append("new_password", newPassword);
return http({
method: "POST",
url: `${AUTH_URL}/password/reset`,
url: `${AUTH_URL}/password/reset_complete`,
data,
});
};
export const revokeToken = (token) => {
return http({
method: "POST",
url: `${AUTH_URL}/revoke/${token}`,
});
};
export const changePassword = ({ currentPassword, newPassword }) => {
const data = new FormData();
data.append("current_password", currentPassword);

Wyświetl plik

@ -50,20 +50,19 @@ export const getTokens = (humbugId) => {
});
};
export const createRestrictedToken = (humbugId) => ({
appName,
appVersion,
}) => {
const data = new FormData();
data.append("app_name", appName);
data.append("app_version", appVersion);
export const createRestrictedToken =
(humbugId) =>
({ appName, appVersion }) => {
const data = new FormData();
data.append("app_name", appName);
data.append("app_version", appVersion);
return http({
method: "POST",
url: `${API}/humbug/${humbugId}/tokens`,
data,
});
};
return http({
method: "POST",
url: `${API}/humbug/${humbugId}/tokens`,
data,
});
};
export const deleteRestrictedToken = (humbugId) => (tokenId) => {
const data = new FormData();

Wyświetl plik

@ -7,7 +7,6 @@ import * as GroupService from "./group.service";
import * as PreferencesService from "./preferences.service";
import * as HumbugService from "./humbug.service";
import * as InvitesService from "./invites.service";
import * as UserService from "./user.service";
import * as SubscriptionsService from "./subscriptions.service";
export {
@ -20,6 +19,5 @@ export {
PreferencesService,
HumbugService,
InvitesService,
UserService,
SubscriptionsService,
};

Wyświetl plik

@ -62,35 +62,31 @@ export const getJournalsScopes = () => {
});
};
export const setJournalPermission = (journalId) => ({
holder_type,
holder_id,
permission_list,
}) => {
const data = new FormData();
data.append("holder_type", holder_type);
data.append("holder_id", holder_id);
data.append("permission_list", permission_list);
export const setJournalPermission =
(journalId) =>
({ holder_type, holder_id, permission_list }) => {
const data = new FormData();
data.append("holder_type", holder_type);
data.append("holder_id", holder_id);
data.append("permission_list", permission_list);
return http({
method: "POST",
url: `${API}/journals/${journalId}/scopes`,
data: { holder_type, holder_id, permission_list },
});
};
return http({
method: "POST",
url: `${API}/journals/${journalId}/scopes`,
data: { holder_type, holder_id, permission_list },
});
};
export const deleteJournalPermission = (journalId) => ({
holder_type,
holder_id,
permission_list,
}) => {
return http({
method: "DELETE",
url: `${API}/journals/${journalId}/scopes`,
data: { holder_type, holder_id, permission_list },
// permission_list: ["read"]
});
};
export const deleteJournalPermission =
(journalId) =>
({ holder_type, holder_id, permission_list }) => {
return http({
method: "DELETE",
url: `${API}/journals/${journalId}/scopes`,
data: { holder_type, holder_id, permission_list },
// permission_list: ["read"]
});
};
export const getPublicJournals = () =>
http({
@ -98,32 +94,30 @@ export const getPublicJournals = () =>
url: `${API}/public/`,
});
export const searchEntries = ({ journalId }) => ({
searchTerm,
limit,
offset,
isContent,
journalType,
}) => {
const journalScope = journalType === "personal" ? "journals" : "public";
return http({
method: "GET",
url: `${API}/${journalScope}/${journalId}/search`,
params: {
// filters: searchTags,
q: searchTerm,
limit: encodeURIComponent(limit),
offset: encodeURIComponent(offset),
content: encodeURIComponent(isContent),
},
});
};
export const searchEntries =
({ journalId }) =>
({ searchTerm, limit, offset, isContent, journalType }) => {
const journalScope = journalType === "personal" ? "journals" : "public";
return http({
method: "GET",
url: `${API}/${journalScope}/${journalId}/search`,
params: {
// filters: searchTags,
q: searchTerm,
limit: encodeURIComponent(limit),
offset: encodeURIComponent(offset),
content: encodeURIComponent(isContent),
},
});
};
export const publicSearchEntries = ({ journalId }) => (query) =>
http({
method: "GET",
url: `${API}/public/${journalId}/search?q=${query}`,
});
export const publicSearchEntries =
({ journalId }) =>
(query) =>
http({
method: "GET",
url: `${API}/public/${journalId}/search?q=${query}`,
});
export const getPublicJournal = (key, { journalId }) =>
http({
@ -131,9 +125,11 @@ export const getPublicJournal = (key, { journalId }) =>
url: `${API}/public/${journalId}`,
});
export const getJournalStats = (key, { journalId }) => () =>
http({
method: "GET",
url: `${API}/journals/${journalId}/stats`,
params: { stats_version: 5 },
});
export const getJournalStats =
(key, { journalId }) =>
() =>
http({
method: "GET",
url: `${API}/journals/${journalId}/stats`,
params: { stats_version: 5 },
});

Wyświetl plik

@ -22,9 +22,9 @@ export const getResultsByEndpoint = async (query, endpoint, clientID) => {
// myself, I would have to implement the logic to handle multiple origins (since the
// Access-Control-Allow-Origins only takes one origin).
// At that point, uncomment the following:
// const token = localStorage.getItem('BUGOUT_ACCESS_TOKEN')
// const token = localStorage.getItem('MOONSTREAM_ACCESS_TOKEN')
// if (token) {
// headers.Authorization = `Bearer ${localStorage.getItem('BUGOUT_ACCESS_TOKEN')}`
// headers.Authorization = `Bearer ${localStorage.getItem('MOONSTREAM_ACCESS_TOKEN')}`
// }
const response = await fetch(requestURL, { method, headers });

Wyświetl plik

@ -1,10 +0,0 @@
import { http } from "../utils";
const AUTH_URL = process.env.NEXT_PUBLIC_SIMIOTICS_AUTH_URL;
export const findUser = (query) => {
return http({
method: "GET",
url: `${AUTH_URL}/user/find?${query}`,
});
};

Wyświetl plik

@ -4,7 +4,7 @@ let axios = require("axios");
enableMockupRequests(axios);
const http = (config) => {
const token = localStorage.getItem("BUGOUT_ACCESS_TOKEN");
const token = localStorage.getItem("MOONSTREAM_ACCESS_TOKEN");
const authorization = token ? { Authorization: `Bearer ${token}` } : {};
const defaultHeaders = config.headers ?? {};
const options = {

Wyświetl plik

@ -1,4 +1,3 @@
import { jsx } from "@emotion/react";
import { Box } from "@chakra-ui/react";
import { getLayout as getSiteLayout } from "./AppLayout";

Wyświetl plik

@ -1,4 +1,3 @@
import { Flex } from "@chakra-ui/react";
import { getLayout as getSiteLayout } from "./RootLayout";
import React, { useContext } from "react";

Wyświetl plik

@ -1,4 +1,3 @@
import { jsx } from "@emotion/react";
import { Scrollable, Footer } from "../components";
import { getLayout as getSiteLayout } from "./index";

Wyświetl plik

@ -1,4 +1,3 @@
import { jsx } from "@emotion/react";
import { Flex, Spinner } from "@chakra-ui/react";
import React, { Suspense, useContext, useState, useEffect } from "react";

Wyświetl plik

@ -1,5 +1,5 @@
import Footer from "../components/Footer"
import Scrollable from "../components/Scrollable"
import Footer from "../components/Footer";
import Scrollable from "../components/Scrollable";
import RootLayout from "./RootLayout";
const LayoutWrapper = ({ children }) => {