Commit cb9ff16a authored by tom's avatar tom

create, update and delete

parent e6cd85d8
import { ChakraProvider } from '@chakra-ui/react'; import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import React, { useState } from 'react'; import React, { useState } from 'react';
...@@ -19,6 +20,7 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -19,6 +20,7 @@ function MyApp({ Component, pageProps }: AppProps) {
<ChakraProvider theme={ theme }> <ChakraProvider theme={ theme }>
<Component { ...pageProps }/> <Component { ...pageProps }/>
</ChakraProvider> </ChakraProvider>
<ReactQueryDevtools/>
</QueryClientProvider> </QueryClientProvider>
); );
} }
......
import type { NextApiRequest } from 'next';
import handler from 'pages/api/utils/handler';
const getUrl = (req: NextApiRequest) => {
return `/account/v1/user/api_keys/${ req.query.id }`;
};
const apiKeysHandler = handler(getUrl, [ 'DELETE', 'PUT' ]);
export default apiKeysHandler;
...@@ -22,6 +22,7 @@ export default function handler<TRes>(getUrl: (_req: NextApiRequest) => string, ...@@ -22,6 +22,7 @@ export default function handler<TRes>(getUrl: (_req: NextApiRequest) => string,
} else if (allowedMethods.includes('PUT') && _req.method === 'PUT') { } else if (allowedMethods.includes('PUT') && _req.method === 'PUT') {
const response = await fetch(getUrl(_req), { const response = await fetch(getUrl(_req), {
method: 'PUT', method: 'PUT',
body: _req.body,
}); });
const data = await response.json() as TRes; const data = await response.json() as TRes;
......
...@@ -5,14 +5,16 @@ import { ...@@ -5,14 +5,16 @@ import {
FormLabel, FormLabel,
Input, Input,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import type { ApiKey } from 'pages/api/types/account'; import type { ApiKey, ApiKeys } from 'pages/api/types/account';
type Props = { type Props = {
data?: ApiKey; data?: ApiKey;
onClose: () => void;
} }
type Inputs = { type Inputs = {
...@@ -23,16 +25,54 @@ type Inputs = { ...@@ -23,16 +25,54 @@ type Inputs = {
// idk, maybe there is no limit // idk, maybe there is no limit
const NAME_MAX_LENGTH = 100; const NAME_MAX_LENGTH = 100;
const ApiKeyForm: React.FC<Props> = ({ data }) => { const ApiKeyForm: React.FC<Props> = ({ data, onClose }) => {
const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>(); const { control, handleSubmit, formState: { errors }, setValue } = useForm<Inputs>();
const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
setValue('token', data?.api_key || ''); setValue('token', data?.api_key || '');
setValue('name', data?.name || ''); setValue('name', data?.name || '');
}, [ setValue, data ]); }, [ setValue, data ]);
// eslint-disable-next-line no-console const updateApiKey = (data: Inputs) => {
const onSubmit: SubmitHandler<Inputs> = data => console.log(data); const body = JSON.stringify({ name: data.name });
if (!data.token) {
return fetch('/api/account/api-keys', { method: 'POST', body });
}
return fetch(`/api/account/api-keys/${ data.token }`, { method: 'PUT', body });
};
const mutation = useMutation(updateApiKey, {
onSuccess: async(data) => {
const response: ApiKey = await data.json();
queryClient.setQueryData([ 'api-keys' ], (prevData: ApiKeys | undefined) => {
const isExisting = prevData && prevData.some((item) => item.api_key === response.api_key);
if (isExisting) {
return prevData.map((item) => {
if (item.api_key === response.api_key) {
return response;
}
return item;
});
}
return [ ...(prevData || []), response ];
});
onClose();
},
// eslint-disable-next-line no-console
onError: console.error,
});
const onSubmit: SubmitHandler<Inputs> = useCallback((data) => {
mutation.mutate(data);
}, [ mutation ]);
const renderTokenInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'token'>}) => { const renderTokenInput = useCallback(({ field }: {field: ControllerRenderProps<Inputs, 'token'>}) => {
return ( return (
...@@ -86,6 +126,7 @@ const ApiKeyForm: React.FC<Props> = ({ data }) => { ...@@ -86,6 +126,7 @@ const ApiKeyForm: React.FC<Props> = ({ data }) => {
variant="primary" variant="primary"
onClick={ handleSubmit(onSubmit) } onClick={ handleSubmit(onSubmit) }
disabled={ Object.keys(errors).length > 0 } disabled={ Object.keys(errors).length > 0 }
isLoading={ mutation.isLoading }
> >
{ data ? 'Save' : 'Generate API key' } { data ? 'Save' : 'Generate API key' }
</Button> </Button>
......
...@@ -17,8 +17,8 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { ...@@ -17,8 +17,8 @@ const AddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const text = 'Add an application name to identify your API key. Click the button below to auto-generate the associated 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(() => { const renderForm = useCallback(() => {
return <ApiKeyForm data={ data }/>; return <ApiKeyForm data={ data } onClose={ onClose }/>;
}, [ data ]); }, [ data, onClose ]);
return ( return (
<FormModal<ApiKey> <FormModal<ApiKey>
isOpen={ isOpen } isOpen={ isOpen }
......
import { Text } from '@chakra-ui/react'; import { Text } from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { ApiKey, ApiKeys } from 'pages/api/types/account';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
name?: string; data: ApiKey;
} }
const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, name }) => { const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const onDelete = useCallback(() => {
const queryClient = useQueryClient();
const deleteApiKey = () => {
return fetch(`/api/account/api-keys/${ data.api_key }`, { method: 'DELETE' });
};
const mutation = useMutation(deleteApiKey, {
onSuccess: async() => {
queryClient.setQueryData([ 'api-keys' ], (prevData: ApiKeys | undefined) => {
return prevData?.filter((item) => item.api_key !== data.api_key);
});
onClose();
},
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('delete', name); onError: console.error,
}, [ name ]); });
const onDelete = useCallback(() => {
mutation.mutate(data);
}, [ data, mutation ]);
const renderText = useCallback(() => { const renderText = useCallback(() => {
return ( return (
<Text display="flex">API key for<Text fontWeight="600" whiteSpace="pre">{ ` "${ name || 'name' }" ` }</Text>will be deleted</Text> <Text display="flex">API key for<Text fontWeight="600" whiteSpace="pre">{ ` "${ data.name || 'name' }" ` }</Text>will be deleted</Text>
); );
}, [ name ]); }, [ data.name ]);
return ( return (
<DeleteModal <DeleteModal
isOpen={ isOpen } isOpen={ isOpen }
...@@ -27,6 +49,7 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, name }) => { ...@@ -27,6 +49,7 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, name }) => {
onDelete={ onDelete } onDelete={ onDelete }
title="Remove API key" title="Remove API key"
renderContent={ renderText } renderContent={ renderText }
pending={ mutation.isLoading }
/> />
); );
}; };
......
...@@ -18,7 +18,7 @@ const ApiKeysPage: React.FC = () => { ...@@ -18,7 +18,7 @@ const ApiKeysPage: React.FC = () => {
const deleteModalProps = useDisclosure(); const deleteModalProps = useDisclosure();
const [ apiKeyModalData, setApiKeyModalData ] = useState<ApiKey>(); const [ apiKeyModalData, setApiKeyModalData ] = useState<ApiKey>();
const [ deleteModalData, setDeleteModalData ] = useState<string>(); const [ deleteModalData, setDeleteModalData ] = useState<ApiKey>();
const { data, isLoading, isError } = useQuery<unknown, unknown, ApiKeys>([ 'api-keys' ], async() => { const { data, isLoading, isError } = useQuery<unknown, unknown, ApiKeys>([ 'api-keys' ], async() => {
const response = await fetch('/api/account/api-keys'); const response = await fetch('/api/account/api-keys');
...@@ -39,7 +39,7 @@ const ApiKeysPage: React.FC = () => { ...@@ -39,7 +39,7 @@ const ApiKeysPage: React.FC = () => {
}, [ apiKeyModalProps ]); }, [ apiKeyModalProps ]);
const onDeleteClick = useCallback((data: ApiKey) => { const onDeleteClick = useCallback((data: ApiKey) => {
setDeleteModalData(data.name); setDeleteModalData(data);
deleteModalProps.onOpen(); deleteModalProps.onOpen();
}, [ deleteModalProps ]); }, [ deleteModalProps ]);
...@@ -94,7 +94,7 @@ const ApiKeysPage: React.FC = () => { ...@@ -94,7 +94,7 @@ const ApiKeysPage: React.FC = () => {
{ content } { content }
</Box> </Box>
<ApiKeyModal { ...apiKeyModalProps } onClose={ onApiKeyModalClose } data={ apiKeyModalData }/> <ApiKeyModal { ...apiKeyModalProps } onClose={ onApiKeyModalClose } data={ apiKeyModalData }/>
<DeleteApiKeyModal { ...deleteModalProps } onClose={ onDeleteModalClose } name={ deleteModalData }/> { deleteModalData && <DeleteApiKeyModal { ...deleteModalProps } onClose={ onDeleteModalClose } data={ deleteModalData }/> }
</Page> </Page>
); );
}; };
......
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