Skip to content

Commit eca6f40

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 eca6f40

File tree

2 files changed

+41
-3
lines changed

2 files changed

+41
-3
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: 19 additions & 3 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,31 @@ export function OrganizationSettingsForm({initialData, onSave}: Props) {
430432
onError: () => addErrorMessage(t('Unable to save change')),
431433
});
432434

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

435445
// Slug form — uses explicit Save button instead of auto-save
436446
const slugForm = useScrapsForm({
437447
...defaultFormOptions,
438448
defaultValues: {slug: initialData.slug},
439449
validators: {onDynamic: slugSchema},
440-
onSubmit: ({value}) =>
450+
onSubmit: ({value, formApi}) =>
441451
updateSlug({slug: value.slug})
442452
.then(() => slugForm.reset())
443-
.catch(() => {}),
453+
.catch(error => {
454+
if (error instanceof RequestError && error.responseJSON?.slug) {
455+
setFieldErrors(formApi, error);
456+
} else {
457+
addErrorMessage(t('Unable to save change'));
458+
}
459+
}),
444460
});
445461

446462
return (

0 commit comments

Comments
 (0)