Files
twenty/packages/twenty-front/src/modules/settings/playground/components/PlaygroundSetupForm.tsx
Félix Malfait 8694840b92 Rename webhook and open api urls (#11684)
We want to have fewer base path for routing.

We will have:
- /files
- /webhooks
- /graphql
- /metadata
- /rest
- /auth
- /healthz

I'm moving /open-api under /rest, and centralizing the webhooks
(removing /stripe and /cloudflare)
2025-04-22 22:24:26 +02:00

182 lines
5.3 KiB
TypeScript

import { SETTINGS_PLAYGROUND_FORM_SCHEMA_SELECT_OPTIONS } from '@/settings/playground/constants/SettingsPlaygroundFormSchemaSelectOptions';
import { playgroundApiKeyState } from '@/settings/playground/states/playgroundApiKeyState';
import { PlaygroundSchemas } from '@/settings/playground/types/PlaygroundSchemas';
import { PlaygroundTypes } from '@/settings/playground/types/PlaygroundTypes';
import { SettingsPath } from '@/types/SettingsPath';
import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput';
import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
import { Controller, useForm } from 'react-hook-form';
import { useRecoilState } from 'recoil';
import { IconApi, IconBrandGraphql } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { z } from 'zod';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
const playgroundSetupFormSchema = z.object({
apiKeyForPlayground: z.string(),
schema: z.nativeEnum(PlaygroundSchemas),
playgroundType: z.nativeEnum(PlaygroundTypes),
});
type PlaygroundSetupFormValues = z.infer<typeof playgroundSetupFormSchema>;
const StyledForm = styled.form`
display: grid;
grid-template-columns: 1.5fr 1fr 1fr 0.5fr;
align-items: end;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export const PlaygroundSetupForm = () => {
const { t } = useLingui();
const navigateSettings = useNavigateSettings();
const [playgroundApiKey, setPlaygroundApiKey] = useRecoilState(
playgroundApiKeyState,
);
const {
control,
handleSubmit,
formState: { isSubmitting },
setError,
} = useForm<PlaygroundSetupFormValues>({
mode: 'onTouched',
resolver: zodResolver(playgroundSetupFormSchema),
defaultValues: {
schema: PlaygroundSchemas.CORE,
playgroundType: PlaygroundTypes.REST,
apiKeyForPlayground: playgroundApiKey || '',
},
});
const validateApiKey = async (values: PlaygroundSetupFormValues) => {
try {
// Validate by fetching the schema (but not storing it)
const response = await fetch(
`${REACT_APP_SERVER_BASE_URL}/rest/open-api/${values.schema}`,
{
headers: { Authorization: `Bearer ${values.apiKeyForPlayground}` },
},
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const openAPIReference = await response.json();
if (!openAPIReference.tags) {
throw new Error('Invalid API Key');
}
return true;
} catch (error) {
throw new Error(t`Invalid API key`);
}
};
const onSubmit = async (values: PlaygroundSetupFormValues) => {
try {
await validateApiKey(values);
setPlaygroundApiKey(values.apiKeyForPlayground);
const path =
values.playgroundType === PlaygroundTypes.GRAPHQL
? SettingsPath.GraphQLPlayground
: SettingsPath.RestPlayground;
navigateSettings(path, {
schema: values.schema.toLowerCase(),
});
} catch (error) {
setError('apiKeyForPlayground', {
type: 'manual',
message:
error instanceof Error
? error.message
: t`An unexpected error occurred`,
});
}
};
return (
<StyledForm onSubmit={handleSubmit(onSubmit)}>
<Controller
name="apiKeyForPlayground"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<TextInput
label={t`API Key`}
placeholder="Enter your API key"
value={value}
onChange={(newValue) => {
onChange(newValue);
setPlaygroundApiKey(newValue);
}}
error={error?.message}
required
/>
)}
/>
<Controller
name="schema"
control={control}
defaultValue={PlaygroundSchemas.CORE}
render={({ field: { onChange, value } }) => (
<Select
dropdownId="schema"
label={t`Schema`}
options={SETTINGS_PLAYGROUND_FORM_SCHEMA_SELECT_OPTIONS.map(
(option) => ({
...option,
label: t(option.label),
}),
)}
value={value}
onChange={onChange}
/>
)}
/>
<Controller
name="playgroundType"
control={control}
defaultValue={PlaygroundTypes.REST}
render={({ field: { onChange, value } }) => (
<Select
dropdownId="apiPlaygroundType"
label={t`API`}
options={[
{
value: PlaygroundTypes.REST,
label: t`REST`,
Icon: IconApi,
},
{
value: PlaygroundTypes.GRAPHQL,
label: t`GraphQL`,
Icon: IconBrandGraphql,
},
]}
value={value}
onChange={onChange}
/>
)}
/>
<Button
title={t`Launch`}
variant="primary"
accent="blue"
type="submit"
disabled={isSubmitting}
/>
</StyledForm>
);
};