From e8320a3fd5cf8029367ecdc4af2b57dd814e2e38 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 21 Sep 2023 13:44:40 +0300 Subject: [PATCH] fix(clerk-js): Improve error message feedback during Organization invitation --- .changeset/four-brooms-live.md | 6 +++ .../OrganizationProfile/InviteMembersForm.tsx | 46 +++++++++---------- .../__tests__/InviteMembersPage.test.tsx | 7 ++- packages/clerk-js/src/ui/elements/Alert.tsx | 2 +- packages/localizations/src/en-US.ts | 3 +- 5 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 .changeset/four-brooms-live.md diff --git a/.changeset/four-brooms-live.md b/.changeset/four-brooms-live.md new file mode 100644 index 00000000000..363cb18d6e3 --- /dev/null +++ b/.changeset/four-brooms-live.md @@ -0,0 +1,6 @@ +--- +'@clerk/localizations': patch +'@clerk/clerk-js': patch +--- + +Fix: localized key for invalid email addreses in InviteMembers form. diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx index 274a46ca90d..035ee83edd7 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx @@ -1,4 +1,4 @@ -import { ClerkAPIResponseError } from '@clerk/shared'; +import { isClerkAPIResponseError } from '@clerk/shared'; import type { MembershipRole, OrganizationResource } from '@clerk/types'; import React from 'react'; @@ -16,7 +16,7 @@ import { import type { LocalizationKey } from '../../localization'; import { localizationKeys, useLocalizations } from '../../localization'; import { useRouter } from '../../router'; -import { handleError, roleLocalizationKey, useFormControl } from '../../utils'; +import { createListFormat, handleError, roleLocalizationKey, useFormControl } from '../../utils'; const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str); @@ -32,9 +32,9 @@ export const InviteMembersForm = (props: InviteMembersFormProps) => { const { navigate } = useRouter(); const { onSuccess, onReset = () => navigate('..'), resetButtonLabel, organization } = props; const card = useCardState(); - const { t } = useLocalizations(); + const { t, locale } = useLocalizations(); const [isValidUnsubmittedEmail, setIsValidUnsubmittedEmail] = React.useState(false); - const [invalidEmails, setInvalidEmails] = React.useState([]); + const [localizedEmails, setLocalizedEmails] = React.useState(null); if (!organization) { return null; @@ -81,15 +81,6 @@ export const InviteMembersForm = (props: InviteMembersFormProps) => { placeholder: '', }); - React.useEffect(() => { - // Remove invalid emails from the tag input - if (invalidEmails.length) { - const invalids = new Set(invalidEmails); - const emails = emailAddressField.value.split(','); - emailAddressField.setValue(emails.filter(e => !invalids.has(e)).join(',')); - } - }, [invalidEmails]); - const canSubmit = !!emailAddressField.value.length || isValidUnsubmittedEmail; const onSubmit = async (e: React.FormEvent) => { @@ -98,26 +89,33 @@ export const InviteMembersForm = (props: InviteMembersFormProps) => { .inviteMembers({ emailAddresses: emailAddressField.value.split(','), role: roleField.value as MembershipRole }) .then(onSuccess) .catch(err => { - if (err instanceof ClerkAPIResponseError) { - const invalids = err.errors[0].meta?.emailAddresses || []; - if (invalids.length) { - setInvalidEmails(invalids); - } else { - setInvalidEmails([]); - handleError(err, [], card.setError); - } + if (isClerkAPIResponseError(err) && err.errors?.[0]?.code === 'duplicate_record') { + const unlocalizedEmailsList = err.errors[0].meta?.emailAddresses || []; + + // Create a localized list of email addresses + const localizedList = createListFormat(unlocalizedEmailsList, locale); + setLocalizedEmails(localizedList); + + // Remove any invalid email address + const invalids = new Set(unlocalizedEmailsList); + const emails = emailAddressField.value.split(','); + emailAddressField.setValue(emails.filter(e => !invalids.has(e)).join(',')); + } else { + setLocalizedEmails(null); + handleError(err, [], card.setError); } }); }; return ( <> - {!!invalidEmails.length && ( + {localizedEmails && ( )} diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx index fbffe0ce4ff..d3c73689864 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx @@ -126,9 +126,12 @@ describe('InviteMembersPage', () => { await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.dev,'); await userEvent.click(getByRole('button', { name: 'Send invitations' })); await waitFor(() => - expect(getByText('The invitations could not be sent. Fix the following and try again:')).toBeDefined(), + expect( + getByText( + 'The invitations could not be sent. There are already pending invitations for the following email addresses: test+5@clerk.dev, test+6@clerk.dev, and test+7@clerk.dev.', + ), + ).toBeInTheDocument(), ); - await waitFor(() => expect(getByText('test+5@clerk.dev, test+6@clerk.dev, test+7@clerk.dev')).toBeDefined()); }); }); diff --git a/packages/clerk-js/src/ui/elements/Alert.tsx b/packages/clerk-js/src/ui/elements/Alert.tsx index a0a0c8bc546..80e46b02c3a 100644 --- a/packages/clerk-js/src/ui/elements/Alert.tsx +++ b/packages/clerk-js/src/ui/elements/Alert.tsx @@ -16,7 +16,7 @@ type AlertProps = Omit, keyof _AlertProps> & export const Alert = (props: AlertProps): JSX.Element | null => { const { children, title, subtitle, variant = 'warning', ...rest } = props; - if (!children && (!title || !subtitle)) { + if (!children && !title && !subtitle) { return null; } diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 20101a2ae65..f6eb3ddf20b 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -630,7 +630,8 @@ export const enUS: LocalizationResource = { title: 'Invite members', subtitle: 'Invite new members to this organization', successMessage: 'Invitations successfully sent', - detailsTitle__inviteFailed: 'The invitations could not be sent. Fix the following and try again:', + detailsTitle__inviteFailed: + 'The invitations could not be sent. There are already pending invitations for the following email addresses: {{email_addresses}}.', formButtonPrimary__continue: 'Send invitations', }, removeDomainPage: {