frontend changes

pull/271/head
Tim Pechersky 2021-09-21 16:58:13 +02:00
rodzic f68b8f9503
commit 0da4b9561b
11 zmienionych plików z 610 dodań i 1 usunięć

Wyświetl plik

@ -0,0 +1,97 @@
import React, { useState, useEffect, useLayoutEffect } from "react";
import TokensList from "../../src/components/TokensList";
import TokenRequest from "../../src/components/TokenRequest";
import { useTokens } from "../../src/core/hooks";
import {
VStack,
Box,
Center,
Spinner,
ScaleFade,
Button,
Heading,
} from "@chakra-ui/react";
import { getLayout } from "../../src/layouts/AccountLayout";
const Tokens = () => {
const [modal, toggleModal] = useState(null);
const [newToken, setNewToken] = useState(null);
const [tokens, setTokens] = useState();
const { list, update, revoke, isLoading, data } = useTokens();
useEffect(() => {
list();
//eslint-disable-next-line
}, []);
useLayoutEffect(() => {
if (newToken) {
const newData = { ...tokens };
newData.token.push(newToken);
setTokens(newData);
setNewToken(null);
}
}, [newToken, list, data, tokens]);
useLayoutEffect(() => {
if (data?.data?.user_id) {
setTokens(data.data);
}
}, [data?.data, isLoading]);
useEffect(() => {
document.title = `Tokens`;
}, []);
return (
<Box>
{isLoading && !tokens ? (
<Center>
<Spinner
hidden={false}
my={8}
size="lg"
color="primary.500"
thickness="4px"
speed="1.5s"
/>
</Center>
) : (
<ScaleFade in>
<Heading variant="tokensScreen"> My access tokens </Heading>
<VStack overflow="initial" maxH="unset" height="100%">
<Center>
<Box h="3rem">
{!modal ? (
<ScaleFade in={!modal}>
<Button
onClick={toggleModal}
colorScheme="primary"
variant="solid"
borderRadius="50%"
>
+
</Button>
</ScaleFade>
) : (
<ScaleFade in={modal} unmountOnExit>
<TokenRequest toggle={toggleModal} newToken={setNewToken} />
</ScaleFade>
)}
</Box>
</Center>
<TokensList
data={tokens}
revoke={revoke}
isLoading={isLoading}
updateCallback={update}
/>
</VStack>
</ScaleFade>
)}
</Box>
);
};
Tokens.getLayout = getLayout;
export default Tokens;

Wyświetl plik

@ -0,0 +1,57 @@
const baseStyle = {
fontFamily: "heading",
fontWeight: "bold",
};
const sizes = {
"4xl": {
fontSize: ["6xl", null, "7xl"],
lineHeight: 1,
},
"3xl": {
fontSize: ["5xl", null, "6xl"],
lineHeight: 1,
},
"2xl": {
fontSize: ["4xl", null, "5xl"],
lineHeight: [1.2, null, 1],
},
xl: {
fontSize: ["3xl", null, "4xl"],
lineHeight: [1.33, null, 1.2],
},
lg: {
fontSize: ["2xl", null, "3xl"],
lineHeight: [1.33, null, 1.2],
},
md: { fontSize: "xl", lineHeight: 1.2 },
sm: { fontSize: "md", lineHeight: 1.2 },
xs: { fontSize: "sm", lineHeight: 1.2 },
};
const defaultProps = {
size: "xl",
};
const variantTokensScreen = (props) => {
const { colorScheme: c } = props;
return {
as: "h2",
pt: 2,
mb: 4,
borderBottom: "solid",
borderColor: `${c}.50`,
borderBottomWidth: "2px",
};
};
const variants = {
tokensScreen: variantTokensScreen,
};
export default {
baseStyle,
sizes,
defaultProps,
variants,
};

Wyświetl plik

@ -10,6 +10,7 @@ import Checkbox from "./Checkbox";
import Table from "./Table";
import Tooltip from "./Tooltip";
import Spinner from "./Spinner";
import Heading from "./Heading";
import { createBreakpoints } from "@chakra-ui/theme-tools";
const breakpointsCustom = createBreakpoints({
@ -57,6 +58,7 @@ const theme = extendTheme({
Table,
Spinner,
Tooltip,
Heading,
},
fonts: {

Wyświetl plik

@ -35,6 +35,9 @@ const AccountIconButton = (props) => {
<RouterLink href="/account/security" passHref>
<MenuItem>Security</MenuItem>
</RouterLink>
<RouterLink href="/account/tokens" passHref>
<MenuItem>Access tokens</MenuItem>
</RouterLink>
</MenuGroup>
<MenuDivider />
<MenuItem

Wyświetl plik

@ -0,0 +1,85 @@
import React, { useEffect, useRef, Fragment } from "react";
import {
FormControl,
FormErrorMessage,
InputGroup,
Input,
Td,
Tr,
} from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";
import IconButton from "./IconButton";
const NewTokenTr = ({ isOpen, toggleSelf, errors, register, journalName }) => {
const inputRef = useRef(null);
useEffect(() => {
if (isOpen) {
//without timeout input is not catching focus on chrome and firefox..
//probably because it is hidden within accordion
setTimeout(() => {
inputRef.current.focus();
}, 100);
}
}, [inputRef, isOpen]);
return (
<Fragment>
{isOpen && (
<Tr transition="0.3s" _hover={{ bg: "white.200" }}>
<Td>New Token:</Td>
<Td>
<FormControl isInvalid={errors.appName}>
<InputGroup>
<Input
fontSize="sm"
border="none"
width="60%"
defaultValue={journalName}
height="fit-content"
placeholder="App name"
name="appName"
ref={(e) => {
register(e, { required: "app name is required" });
inputRef.current = e;
}}
/>
</InputGroup>
<FormErrorMessage color="unsafe.400" pl="1">
{errors.appName && errors.appName.message}
</FormErrorMessage>
</FormControl>
</Td>
<Td>
<FormControl isInvalid={errors.appVersion}>
<InputGroup>
<Input
fontSize="sm"
border="none"
width="60%"
height="fit-content"
placeholder="App Version"
name="appVersion"
ref={(e) => {
register(e, { required: "app name is required" });
}}
/>
</InputGroup>
<FormErrorMessage color="unsafe.400" pl="1">
{errors.appVersion && errors.appVersion.message}
</FormErrorMessage>
</FormControl>
</Td>
<Td>
<IconButton type="submit" />
<IconButton
onClick={() => toggleSelf(false)}
icon={<CloseIcon />}
/>
</Td>
</Tr>
)}
</Fragment>
);
};
export default NewTokenTr;

Wyświetl plik

@ -0,0 +1,101 @@
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, ConfirmationRequest, NewTokenTr } from ".";
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

@ -0,0 +1,109 @@
import React from "react";
import {
Box,
InputGroup,
InputLeftElement,
FormControl,
FormErrorMessage,
HStack,
Button,
InputRightElement,
Input,
Icon,
} from "@chakra-ui/react";
import { CloseIcon } from "@chakra-ui/icons";
import { useEffect, useState, useRef } from "react";
import { useForm } from "react-hook-form";
import { useUser, useTokens } from "../core/hooks";
const TokenRequest = ({ newToken, toggle }) => {
const { user } = useUser();
const { createToken } = useTokens();
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 (createToken.data?.data) {
newToken(createToken.data.data);
toggle(null);
}
}, [createToken.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(createToken.mutate)} style={formStyle}>
<HStack>
<Button
variant="solid"
colorScheme="primary"
type="submit"
isLoading={createToken.isLoading}
>
Submit
</Button>
<FormControl isInvalid={errors.password}>
<InputGroup minWidth="300px">
<InputLeftElement onClick={togglePassword}>
<Icon icon="password" />
</InputLeftElement>
<Input
colorScheme="primary"
variant="filled"
isDisabled={createToken.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

@ -0,0 +1,88 @@
import React from "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 "./CopyButton";
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.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;

Wyświetl plik

@ -1,4 +1,4 @@
export { queryCacheProps as hookCommon } from "./hookCommon";
export { default as hookCommon } from "./hookCommon";
export { default as useAuthResultHandler } from "./useAuthResultHandler";
export { default as useChangePassword } from "./useChangePassword";
export { default as useClientID } from "./useClientID";
@ -15,8 +15,10 @@ export { default as useResetPassword } from "./useResetPassword";
export { default as useRouter } from "./useRouter";
export { default as useSignUp } from "./useSignUp";
export { default as useStorage } from "./useStorage";
export { default as useStream } from "./useStream";
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 useTxInfo } from "./useTxInfo";
export { default as useUser } from "./useUser";

Wyświetl plik

@ -0,0 +1,40 @@
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();
},
});
const createToken = useMutation(AuthService.login, {
onSuccess: () => {
list();
},
});
return {
createToken,
list,
update,
revoke,
isLoading,
data,
error,
};
};
export default useTokens;

Wyświetl plik

@ -74,3 +74,28 @@ export const changePassword = ({ currentPassword, newPassword }) => {
data,
});
};
export const getTokenList = () => {
return http({
method: "GET",
url: `${AUTH_URL}/tokens`,
});
};
export const updateToken = ({ note, token }) => {
const data = new FormData();
data.append("token_note", note);
data.append("access_token", token);
return http({
method: "PUT",
url: `${AUTH_URL}/token`,
data,
});
};
export const revokeToken = (token) => {
return http({
method: "POST",
url: `${AUTH_URL}/revoke/${token}`,
});
};