Skip to content

Commit b59ed09

Browse files
cvxluoclaude
andcommitted
fix(settings): Surface slug validation errors on org settings form
When changing the org slug to one already in use, the backend returns a field-level error but the frontend was showing a generic 'Unable to save change' toast. This was a regression from the form migration to TanStack which landed after the backend fix. Use setFieldErrors to display the API validation message inline on the slug field instead of swallowing it. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8b28ee1 commit b59ed09

File tree

2 files changed

+39
-5
lines changed

2 files changed

+39
-5
lines changed

static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,28 @@ describe('OrganizationSettingsForm', () => {
156156
expect(screen.queryByRole('button', {name: 'Cancel'})).not.toBeInTheDocument();
157157
});
158158

159+
it('shows field error when slug is already taken', async () => {
160+
MockApiClient.addMockResponse({
161+
url: `/organizations/${organization.slug}/`,
162+
method: 'PUT',
163+
statusCode: 400,
164+
body: {slug: ['The slug "taken" is in use by another organization.']},
165+
});
166+
167+
render(
168+
<OrganizationSettingsForm initialData={OrganizationFixture()} onSave={onSave} />
169+
);
170+
171+
const input = screen.getByRole('textbox', {name: 'Organization Slug'});
172+
await userEvent.clear(input);
173+
await userEvent.type(input, 'taken');
174+
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
175+
176+
expect(
177+
await screen.findByText('The slug "taken" is in use by another organization.')
178+
).toBeInTheDocument();
179+
});
180+
159181
it('can enable codecov', async () => {
160182
putMock = MockApiClient.addMockResponse({
161183
url: `/organizations/${organization.slug}/`,

static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
defaultFormOptions,
1212
FieldGroup,
1313
FormSearch,
14+
setFieldErrors,
1415
useScrapsForm,
1516
} from '@sentry/scraps/form';
1617
import {Container, Flex} from '@sentry/scraps/layout';
@@ -29,6 +30,7 @@ import {ConfigStore} from 'sentry/stores/configStore';
2930
import type {MembershipSettingsProps} from 'sentry/types/hooks';
3031
import type {Organization} from 'sentry/types/organization';
3132
import {fetchMutation, useMutation} from 'sentry/utils/queryClient';
33+
import {RequestError} from 'sentry/utils/requestError/requestError';
3234
import {slugify} from 'sentry/utils/slugify';
3335
import {useMembers} from 'sentry/utils/useMembers';
3436
import {useOrganization} from 'sentry/utils/useOrganization';
@@ -430,17 +432,27 @@ export function OrganizationSettingsForm({initialData, onSave}: Props) {
430432
onError: () => addErrorMessage(t('Unable to save change')),
431433
});
432434

433-
const {mutateAsync: updateSlug} = useMutation(orgMutationOptions);
435+
const slugMutation = useMutation(
436+
mutationOptions({
437+
mutationFn: (data: Partial<SlugSchema>) =>
438+
fetchMutation<Organization>({method: 'PUT', url: endpoint, data}),
439+
onSuccess: updated => onSave(initialData, updated),
440+
})
441+
);
434442

435443
// Slug form — uses explicit Save button instead of auto-save
436444
const slugForm = useScrapsForm({
437445
...defaultFormOptions,
438446
defaultValues: {slug: initialData.slug},
439447
validators: {onDynamic: slugSchema},
440-
onSubmit: ({value}) =>
441-
updateSlug({slug: value.slug})
442-
.then(() => slugForm.reset())
443-
.catch(() => {}),
448+
onSubmit: ({value, formApi}) =>
449+
slugMutation
450+
.mutateAsync({slug: value.slug}, {onSuccess: () => formApi.reset()})
451+
.catch(error => {
452+
if (error instanceof RequestError) {
453+
setFieldErrors(formApi, error);
454+
}
455+
}),
444456
});
445457

446458
return (

0 commit comments

Comments
 (0)