From 92e683e27f13fe7e2a0863847c9ed4a14a80e7c7 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Thu, 21 May 2026 14:30:43 +0200 Subject: [PATCH] ref(forms): Migrate CreateTeamForm to TanStack form system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the legacy Form/TextField with useScrapsForm and field.Input. The form now drives validation via a zod schema and the SubmitButton handles busy/disabled state through the form context. Simplify the onSubmit prop signature — the legacy (data, onSuccess, onError) callbacks were FormModel-internal hooks for state tracking, which the new form handles itself. The createTeam action creator already surfaces success/error toasts, so the modal just awaits the mutation and closes on success. The unused organization prop on CreateTeamForm is dropped along with it. Refs DE-1256 Co-Authored-By: Claude Opus 4.7 --- .../app/components/modals/createTeamModal.tsx | 21 ++---- .../app/components/teams/createTeamForm.tsx | 72 ++++++++++--------- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/static/app/components/modals/createTeamModal.tsx b/static/app/components/modals/createTeamModal.tsx index f225248a40cbde..3c7e72b451b86e 100644 --- a/static/app/components/modals/createTeamModal.tsx +++ b/static/app/components/modals/createTeamModal.tsx @@ -15,20 +15,13 @@ interface Props extends ModalRenderProps { function CreateTeamModal({Body, Header, organization, onClose, closeModal}: Props) { const api = useApi(); - const handleSubmit: React.ComponentProps['onSubmit'] = async ( - data, - onSuccess, - onError - ) => { - try { - const team: Team = await createTeam(api, data, {orgId: organization.slug}); + const handleSubmit: React.ComponentProps< + typeof CreateTeamForm + >['onSubmit'] = async data => { + const team: Team = await createTeam(api, data, {orgId: organization.slug}); - closeModal(); - onClose?.(team); - onSuccess(team); - } catch (err) { - onError(err as Team); - } + closeModal(); + onClose?.(team); }; return ( @@ -37,7 +30,7 @@ function CreateTeamModal({Body, Header, organization, onClose, closeModal}: Prop
{t('Create Team')}
- + ); diff --git a/static/app/components/teams/createTeamForm.tsx b/static/app/components/teams/createTeamForm.tsx index c9c670137224bd..ba29a2dda09644 100644 --- a/static/app/components/teams/createTeamForm.tsx +++ b/static/app/components/teams/createTeamForm.tsx @@ -1,9 +1,11 @@ import {Fragment} from 'react'; +import {z} from 'zod'; + +import {defaultFormOptions, useScrapsForm} from '@sentry/scraps/form'; +import {Flex} from '@sentry/scraps/layout'; -import {TextField} from 'sentry/components/forms/fields/textField'; -import {Form} from 'sentry/components/forms/form'; import {t} from 'sentry/locale'; -import type {Organization, Team} from 'sentry/types/organization'; +import type {Team} from 'sentry/types/organization'; import {slugify} from 'sentry/utils/slugify'; type Payload = { @@ -11,43 +13,47 @@ type Payload = { }; type Props = { - onSubmit: ( - data: Payload, - onSuccess: (team: Team) => void, - onError: (team: Team) => void - ) => void; - organization: Organization; + onSubmit: (data: Payload) => Promise | Team | void; }; -export function CreateTeamForm({organization, onSubmit}: Props) { +const schema = z.object({ + slug: z.string().min(1, t('Slug is required')), +}); + +export function CreateTeamForm({onSubmit}: Props) { + const form = useScrapsForm({ + ...defaultFormOptions, + defaultValues: {slug: ''}, + validators: {onDynamic: schema}, + onSubmit: ({value}) => Promise.resolve(onSubmit(value)).catch(() => {}), + }); + return (

{t('Teams group members for issue assignment, ownership, and notifications.')}

- -
- onSubmit(data as Payload, onSuccess, onError) - } - requireChanges - > - - + + + {field => ( + + field.handleChange(slugify(value))} + placeholder={t('e.g. operations, web-frontend, mobile-ios')} + autoFocus + /> + + )} + + + {t('Create Team')} + +
); }