Commit f3fc1267 authored by isstuev's avatar isstuev

api keys page

parent dcc3e4f0
export const apiKey = [
{
token: '6fd12fe0-841c-4abf-ac2a-8c1b08dadf8e',
name: 'zapper.fi',
},
{
token: '057085a1-d2eb-4d8d-8b89-1dd9fba32071',
name: 'TenderlyBlaBlaName',
},
{
token: '057085a1-d2eb-4d8d-8b89-1dd9fba32071',
name: 'Application name',
},
];
export type TApiKey = Array<TApiKeyItem>
export type TApiKeyItem = {
token: string;
name: string;
}
import React from 'react';
import type { NextPage } from 'next';
import Head from 'next/head'
import ApiKeys from '../ui/pages/ApiKeys';
const ApiKeysPage: NextPage = () => {
return (
<>
<Head><title>API keys</title></Head>
<ApiKeys/>
</>
);
}
export default ApiKeysPage
...@@ -32,7 +32,9 @@ const variantOutline: PartsStyleFunction<typeof parts> = (props) => { ...@@ -32,7 +32,9 @@ const variantOutline: PartsStyleFunction<typeof parts> = (props) => {
userSelect: 'all', userSelect: 'all',
}, },
_disabled: { _disabled: {
opacity: 0.4, opacity: 1,
background: mode('gray.200', 'whiteAlpha.400')(props),
border: 'none',
cursor: 'not-allowed', cursor: 'not-allowed',
}, },
_invalid: { _invalid: {
......
import React, { useCallback, useEffect } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import {
Box,
Button,
FormControl,
FormLabel,
Input,
} from '@chakra-ui/react';
import type { TApiKeyItem } from '../../../data/apiKey';
type Props = {
data?: TApiKeyItem;
}
type Inputs = {
token: string;
name: string;
}
// idk, maybe there is no limit
const NAME_MAX_LENGTH = 100;
const ApiKeyForm: React.FC<Props> = ({ data }) => {
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
useEffect(() => {
setValue('token', data?.token || '');
setValue('name', data?.name || '');
}, [ setValue, data ]);
// eslint-disable-next-line no-console
const onSubmit: SubmitHandler<Inputs> = data => console.log(data);
const renderTokenInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'token'>}) => {
return (
<FormControl variant="floating" id="address" isRequired>
<Input
{ ...field }
placeholder=" "
disabled={ true }
/>
<FormLabel>Auto-generated API key token</FormLabel>
</FormControl>
)
}, []);
const renderNameInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'name'>}) => {
return (
<FormControl variant="floating" id="name" isRequired>
<Input
{ ...field }
placeholder=" "
isInvalid={ Boolean(errors.name) }
maxLength={ NAME_MAX_LENGTH }
/>
<FormLabel>Application name for API key (e.g Web3 project)</FormLabel>
</FormControl>
)
}, [ errors ]);
return (
<>
{ data && (
<Box marginBottom={ 5 }>
<Controller
name="token"
control={ control }
render={ renderTokenInput }
/>
</Box>
) }
<Box marginBottom={ 8 }>
<Controller
name="name"
control={ control }
rules={{
maxLength: NAME_MAX_LENGTH,
}}
render={ renderNameInput }
/>
</Box>
<Box marginTop={ 8 }>
<Button
size="lg"
variant="primary"
onClick={ handleSubmit(onSubmit) }
disabled={ Object.keys(errors).length > 0 }
>
{ data ? 'Save' : 'Generate API key' }
</Button>
</Box>
</>
)
}
export default ApiKeyForm;
import React, { useCallback } from 'react';
import type { TApiKeyItem } from '../../../data/apiKey';
import ApiKeyForm from './ApiKeyForm';
import FormModal from '../../shared/FormModal';
type Props = {
isOpen: boolean;
onClose: () => void;
data?: TApiKeyItem;
}
const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const title = data ? 'Edit API key' : 'New API key';
const text = 'Add an application name to identify your API key. Click the button below to auto-generate the associated key.'
const renderForm = useCallback(() => {
return <ApiKeyForm data={ data }/>
}, [ data ]);
return (
<FormModal<TApiKeyItem>
isOpen={ isOpen }
onClose={ onClose }
title={ title }
text={ text }
data={ data }
renderForm={ renderForm }
/>
)
}
export default AddressModal;
import React from 'react';
import {
Table,
Thead,
Tbody,
Tr,
Th,
TableContainer,
} from '@chakra-ui/react'
import type { TApiKey, TApiKeyItem } from '../../../data/apiKey';
import ApiKeyTableItem from './ApiKeyTableItem';
interface Props {
data: TApiKey;
onEditClick: (data: TApiKeyItem) => void;
onDeleteClick: (data: TApiKeyItem) => void;
limit: number;
}
const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => {
return (
<TableContainer width="100%">
<Table variant="simple" minWidth="600px">
<Thead>
<Tr>
<Th>{ `API key token (limit ${ limit } keys)` }</Th>
<Th width="108px"></Th>
</Tr>
</Thead>
<Tbody>
{ data.map((item) => (
<ApiKeyTableItem
item={ item }
key={ item.token }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
/>
)) }
</Tbody>
</Table>
</TableContainer>
);
};
export default ApiKeyTable;
import React, { useCallback } from 'react';
import {
Tr,
Td,
HStack,
Text,
useColorModeValue,
} from '@chakra-ui/react'
import EditButton from '../../shared/EditButton';
import DeleteButton from '../../shared/DeleteButton';
import type { TApiKeyItem } from '../../../data/apiKey';
import CopyToClipboard from '../../shared/CopyToClipboard';
interface Props {
item: TApiKeyItem;
onEditClick: (data: TApiKeyItem) => void;
onDeleteClick: (data: TApiKeyItem) => void;
}
const WatchlistTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
const onItemEditClick = useCallback(() => {
return onEditClick(item);
}, [ item, onEditClick ]);
const onItemDeleteClick = useCallback(() => {
return onDeleteClick(item);
}, [ item, onDeleteClick ]);
const secondaryColor = useColorModeValue('gray.500', 'gray.400');
return (
<Tr alignItems="top" key={ item.token }>
<Td>
<HStack>
<Text fontSize="md" fontWeight={ 600 }>{ item.token }</Text>
<CopyToClipboard text={ item.token }/>
</HStack>
<Text fontSize="sm" marginTop={ 0.5 } color={ secondaryColor }>{ item.name }</Text>
</Td>
<Td>
<HStack spacing={ 6 }>
icons
<EditButton onClick={ onItemEditClick }/>
<DeleteButton onClick={ onItemDeleteClick }/>
</HStack>
</Td>
</Tr>
)
};
export default WatchlistTableItem;
import React, { useCallback } from 'react';
import DeleteModal from '../shared/DeleteModal'
type Props = {
isOpen: boolean;
onClose: () => void;
name?: string;
}
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, name }) => {
const onDelete = useCallback(() => {
// eslint-disable-next-line no-console
console.log('delete', name);
}, [ name ]);
return (
<DeleteModal
isOpen={ isOpen }
onClose={ onClose }
onDelete={ onDelete }
title="Remove API key"
text={ `API key for "${ name || 'name' }" will be deleted` }
/>
)
}
export default DeleteAddressModal;
import React, { useCallback, useState } from 'react';
import { Box, Button, HStack, Link, Text, useColorModeValue, useDisclosure } from '@chakra-ui/react';
import Page from '../Page/Page';
import ApiKeyTable from '../apiKey/ApiKeyTable/ApiKeyTable';
import ApiKeyModal from '../apiKey/ApiKeyModal/ApiKeyModal';
import DeleteApiKeyModal from '../apiKey/DeleteApiKeyModal';
import type { TApiKeyItem } from '../../data/apiKey';
import { apiKey } from '../../data/apiKey';
import { space } from '../../lib/html-entities';
const DATA_LIMIT = 3;
const ApiKeys: React.FC = () => {
const apiKeyModalProps = useDisclosure();
const deleteModalProps = useDisclosure();
const [ apiKeyModalData, setApiKeyModalData ] = useState<TApiKeyItem>();
const [ deleteModalData, setDeleteModalData ] = useState<string>();
const onEditClick = useCallback((data: TApiKeyItem) => {
setApiKeyModalData(data);
apiKeyModalProps.onOpen();
}, [ apiKeyModalProps ])
const onApiKeyModalClose = useCallback(() => {
setApiKeyModalData(undefined);
apiKeyModalProps.onClose();
}, [ apiKeyModalProps ]);
const onDeleteClick = useCallback((data: TApiKeyItem) => {
setDeleteModalData(data.name);
deleteModalProps.onOpen();
}, [ deleteModalProps ])
const onDeleteModalClose = useCallback(() => {
setDeleteModalData(undefined);
deleteModalProps.onClose();
}, [ deleteModalProps ]);
const captionColor = useColorModeValue('gray.500', 'gray.400');
const canAdd = apiKey.length < DATA_LIMIT
return (
<Page>
<Box h="100%">
<Box as="h1" textStyle="h2" marginBottom={ 8 }>API keys</Box>
<Text marginBottom={ 12 }>
Create API keys to use for your RPC and EthRPC API requests. For more information, see { space }
<Link href="#">“How to use a Blockscout API key”</Link>.
</Text>
{ Boolean(apiKey.length) && (
<ApiKeyTable
data={ apiKey }
onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick }
limit={ DATA_LIMIT }
/>
) }
<HStack marginTop={ 8 } spacing={ 5 }>
<Button
variant="primary"
size="lg"
onClick={ apiKeyModalProps.onOpen }
disabled={ !canAdd }
>
Add API key
</Button>
{ !canAdd && (
<Text fontSize="sm" color={ captionColor }>
{ `You have added the maximum number of API keys (${ DATA_LIMIT }). Contact us to request additional keys.` }
</Text>
) }
</HStack>
</Box>
<ApiKeyModal { ...apiKeyModalProps } onClose={ onApiKeyModalClose } data={ apiKeyModalData }/>
<DeleteApiKeyModal { ...deleteModalProps } onClose={ onDeleteModalClose } name={ deleteModalData }/>
</Page>
);
};
export default ApiKeys
...@@ -24,7 +24,7 @@ type Inputs = { ...@@ -24,7 +24,7 @@ type Inputs = {
tag: string; tag: string;
} }
const AddressModal: React.FC<Props> = ({ data }) => { const AddressForm: React.FC<Props> = ({ data }) => {
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>(); const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
useEffect(() => { useEffect(() => {
...@@ -80,4 +80,4 @@ const AddressModal: React.FC<Props> = ({ data }) => { ...@@ -80,4 +80,4 @@ const AddressModal: React.FC<Props> = ({ data }) => {
) )
} }
export default AddressModal; export default AddressForm;
...@@ -25,7 +25,7 @@ const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => { ...@@ -25,7 +25,7 @@ const AddressTagTable = ({ data, onDeleteClick, onEditClick }: Props) => {
<Table variant="simple" minWidth="600px"> <Table variant="simple" minWidth="600px">
<Thead> <Thead>
<Tr> <Tr>
<Th width="75%">Address</Th> <Th width="75%">Transaction</Th>
<Th width="25%">Private tag</Th> <Th width="25%">Private tag</Th>
<Th width="108px"></Th> <Th width="108px"></Th>
</Tr> </Tr>
......
...@@ -30,7 +30,7 @@ type Inputs = { ...@@ -30,7 +30,7 @@ type Inputs = {
notification: boolean; notification: boolean;
} }
const AddressModal: React.FC<Props> = ({ data }) => { const AddressForm: React.FC<Props> = ({ data }) => {
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>(); const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
useEffect(() => { useEffect(() => {
...@@ -122,4 +122,4 @@ const AddressModal: React.FC<Props> = ({ data }) => { ...@@ -122,4 +122,4 @@ const AddressModal: React.FC<Props> = ({ data }) => {
) )
} }
export default AddressModal; export default AddressForm;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment