Make api name editable and add expiry (#6473)
Fixes #6302 --------- Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
@ -0,0 +1,70 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledComboInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
> * + * {
|
||||
margin-left: ${({ theme }) => theme.spacing(4)};
|
||||
}
|
||||
`;
|
||||
|
||||
type ApiKeyNameInputProps = {
|
||||
apiKeyName: string;
|
||||
apiKeyId: string;
|
||||
disabled: boolean;
|
||||
onNameUpdate?: (name: string) => void;
|
||||
};
|
||||
|
||||
export const ApiKeyNameInput = ({
|
||||
apiKeyName,
|
||||
apiKeyId,
|
||||
disabled,
|
||||
onNameUpdate,
|
||||
}: ApiKeyNameInputProps) => {
|
||||
const { updateOneRecord: updateApiKey } = useUpdateOneRecord<ApiKey>({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
});
|
||||
|
||||
// TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedUpdate = useCallback(
|
||||
useDebouncedCallback(async (name: string) => {
|
||||
if (isDefined(onNameUpdate)) {
|
||||
onNameUpdate(apiKeyName);
|
||||
}
|
||||
if (!apiKeyName) {
|
||||
return;
|
||||
}
|
||||
await updateApiKey({
|
||||
idToUpdate: apiKeyId,
|
||||
updateOneRecordInput: { name },
|
||||
});
|
||||
}, 500),
|
||||
[updateApiKey, onNameUpdate],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedUpdate(apiKeyName);
|
||||
return debouncedUpdate.cancel;
|
||||
}, [debouncedUpdate, apiKeyName]);
|
||||
|
||||
return (
|
||||
<StyledComboInputContainer>
|
||||
<TextInput
|
||||
placeholder="E.g. backoffice integration"
|
||||
onChange={onNameUpdate}
|
||||
fullWidth
|
||||
value={apiKeyName}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledComboInputContainer>
|
||||
);
|
||||
};
|
||||
@ -1,27 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, RecoilState } from 'recoil';
|
||||
|
||||
import { generatedApiKeyFamilyState } from '@/settings/developers/states/generatedApiKeyFamilyState';
|
||||
|
||||
import { useGeneratedApiKeys } from '../useGeneratedApiKeys';
|
||||
|
||||
describe('useGeneratedApiKeys', () => {
|
||||
test('should set generatedApiKeyFamilyState correctly', () => {
|
||||
const { result } = renderHook(() => useGeneratedApiKeys(), {
|
||||
wrapper: RecoilRoot,
|
||||
});
|
||||
|
||||
const apiKeyId = 'someId';
|
||||
const apiKey = 'someKey';
|
||||
|
||||
act(() => {
|
||||
result.current(apiKeyId, apiKey);
|
||||
});
|
||||
|
||||
const recoilState: RecoilState<string | null | undefined> =
|
||||
generatedApiKeyFamilyState(apiKeyId);
|
||||
|
||||
const stateValue = recoilState.key;
|
||||
expect(stateValue).toContain(apiKeyId);
|
||||
});
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { generatedApiKeyFamilyState } from '@/settings/developers/states/generatedApiKeyFamilyState';
|
||||
|
||||
export const useGeneratedApiKeys = () => {
|
||||
return useRecoilCallback(
|
||||
({ set }) =>
|
||||
(apiKeyId: string, apiKey: string | null) => {
|
||||
set(generatedApiKeyFamilyState(apiKeyId), apiKey);
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
|
||||
|
||||
export const generatedApiKeyFamilyState = createFamilyState<
|
||||
string | null | undefined,
|
||||
string
|
||||
>({
|
||||
key: 'generatedApiKeyFamilyState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
export const apiKeyTokenState = createState<string | null>({
|
||||
key: 'apiKeyTokenState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -21,8 +21,10 @@ export enum AppPath {
|
||||
RecordIndexPage = '/objects/:objectNamePlural',
|
||||
RecordShowPage = '/object/:objectNameSingular/:objectRecordId',
|
||||
|
||||
SettingsCatchAll = `/settings/*`,
|
||||
DevelopersCatchAll = `/developers/*`,
|
||||
Settings = `settings`,
|
||||
SettingsCatchAll = `/${Settings}/*`,
|
||||
Developers = `developers`,
|
||||
DevelopersCatchAll = `/${Developers}/*`,
|
||||
|
||||
// Impersonate
|
||||
Impersonate = '/impersonate/:userId',
|
||||
|
||||
@ -9,6 +9,7 @@ import { isDefaultLayoutAuthModalVisibleState } from '@/ui/layout/states/isDefau
|
||||
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
|
||||
import { OnboardingStatus, SubscriptionStatus } from '~/generated/graphql';
|
||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||
import { UNTESTED_APP_PATHS } from '~/testing/constants/UntestedAppPaths';
|
||||
|
||||
jest.mock('@/onboarding/hooks/useOnboardingStatus');
|
||||
const setupMockOnboardingStatus = (
|
||||
@ -331,7 +332,7 @@ describe('useShowAuthModal', () => {
|
||||
SubscriptionStatus.Trialing,
|
||||
];
|
||||
expect(testCases.length).toEqual(
|
||||
Object.keys(AppPath).length *
|
||||
(Object.keys(AppPath).length - UNTESTED_APP_PATHS.length) *
|
||||
(Object.keys(OnboardingStatus).length +
|
||||
(Object.keys(SubscriptionStatus).length -
|
||||
untestedSubscriptionStatus.length)),
|
||||
|
||||
Reference in New Issue
Block a user