Files
twenty/packages/twenty-front/src/modules/settings/playground/components/PlaygroundSetupForm.tsx
StormNinja17 8bd7b78825 Moved Select Options to External Files (#11400)
This is a minor rework of PR #10738.

I noticed an inconsistency with how Select options are passed as props.
Many files use constants stored in external files to pass options props
to Select objects. This allows for code reusability. Some files are not
passing options in this format.

I modified more files so that they use this method of passing options
props. I made changes to:
- WorkerQueueMetricsSection.tsx 
- SettingsDataModelFieldBooleanForm.tsx 
- SettingsDataModelFieldTextForm.tsx 
- SettingsDataModelFieldNumberForm.tsx 
- PlaygroundSetupForm.tsx 
- ViewPickerContentCreateMode.tsx 

I also noticed that some of these files were incorrectly using
useLingui(), so I fixed the import and usage where needed.

---------

Co-authored-by: Beau Smith <bsmith26@iastate.edu>
Co-authored-by: Charles Bochet <charles@twenty.com>
2025-04-15 18:31:17 +02:00

180 lines
5.2 KiB
TypeScript

import { PLAYGROUND_SETUP_SELECT_OPTIONS } from '@/settings/playground/constants/PlaygroundSetupSelectOptions';
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}/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={PLAYGROUND_SETUP_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>
);
};