Skip to content

Commit

Permalink
fix(console,phrases): check duplicated emails when inviting members
Browse files Browse the repository at this point in the history
  • Loading branch information
charIeszhao committed Mar 28, 2024
1 parent 961fd8e commit 29aa2b7
Show file tree
Hide file tree
Showing 19 changed files with 118 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { emailRegEx } from '@logto/core-kit';
import { conditional, conditionalArray, conditionalString } from '@silverhand/essentials';
import { useCallback, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';

import { useAuthedCloudApi } from '@/cloud/hooks/use-cloud-api';
import { type TenantMemberResponse } from '@/cloud/types/router';
import { TenantsContext } from '@/contexts/TenantsProvider';
import { type RequestError } from '@/hooks/use-api';

import { type InviteeEmailItem } from '../types';

const useEmailInputUtils = () => {
const cloudApi = useAuthedCloudApi();
const { currentTenantId } = useContext(TenantsContext);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });

const { data: existingMembers = [] } = useSWR<TenantMemberResponse[], RequestError>(
`api/tenant/${currentTenantId}/members`,
async () =>
cloudApi.get('/api/tenants/:tenantId/members', { params: { tenantId: currentTenantId } })
);

/**
* Find duplicated and invalid formatted email addresses.
*
* @param emails Array of email emails.
* @returns
*/
const findDuplicatedOrInvalidEmails = useCallback(
(emails: string[] = []) => {
const duplicatedEmails = new Set<string>();
const invalidEmails = new Set<string>();
const validEmails = new Set<string>(
existingMembers.map(({ primaryEmail }) => primaryEmail ?? '').filter(Boolean)
);

for (const email of emails) {
if (!emailRegEx.test(email)) {
invalidEmails.add(email);
}

if (validEmails.has(email)) {
duplicatedEmails.add(email);
} else {
validEmails.add(email);
}
}

return {
duplicatedEmails,
invalidEmails,
};
},
[existingMembers]
);

const parseEmailOptions = useCallback(
(
inputValues: InviteeEmailItem[]
): {
values: InviteeEmailItem[];
errorMessage?: string;
} => {
const { duplicatedEmails, invalidEmails } = findDuplicatedOrInvalidEmails(
inputValues.map((email) => email.value)
);
// Show error message and update the inputs' status for error display.
if (duplicatedEmails.size > 0 || invalidEmails.size > 0) {
return {
values: inputValues.map(({ status, ...rest }) => ({
...rest,
...conditional(
(duplicatedEmails.has(rest.value) || invalidEmails.has(rest.value)) && {
status: 'info',
}
),
})),
errorMessage: conditionalArray(
conditionalString(duplicatedEmails.size > 0 && t('tenant_members.errors.user_exists')),
conditionalString(invalidEmails.size > 0 && t('tenant_members.errors.invalid_email'))
).join(' '),
};
}

return { values: inputValues };
},
[findDuplicatedOrInvalidEmails]

Check warning on line 89 in packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/hooks.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/console/src/pages/TenantSettings/TenantMembers/InviteEmailsInput/hooks.ts#L89

[react-hooks/exhaustive-deps] React Hook useCallback has a missing dependency: 't'. Either include it or remove the dependency array.
);

return {
parseEmailOptions,
};
};

export default useEmailInputUtils;
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { onKeyDownHandler } from '@/utils/a11y';

import type { InviteeEmailItem, InviteMemberForm } from '../types';

import useEmailInputUtils from './hooks';
import * as styles from './index.module.scss';
import { emailOptionsParser } from './utils';

type Props = {
className?: string;
Expand All @@ -34,9 +34,10 @@ function InviteEmailsInput({
const [focusedValueId, setFocusedValueId] = useState<Nullable<string>>(null);
const [currentValue, setCurrentValue] = useState('');
const { setError, clearErrors } = useFormContext<InviteMemberForm>();
const { parseEmailOptions } = useEmailInputUtils();

const onChange = (values: InviteeEmailItem[]) => {
const { values: parsedValues, errorMessage } = emailOptionsParser(values);
const { values: parsedValues, errorMessage } = parseEmailOptions(values);
if (errorMessage) {
setError('emails', { type: 'custom', message: errorMessage });
} else {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Select, { type Option } from '@/ds-components/Select';
import * as modalStyles from '@/scss/modal.module.scss';

import InviteEmailsInput from '../InviteEmailsInput';
import { emailOptionsParser } from '../InviteEmailsInput/utils';
import useEmailInputUtils from '../InviteEmailsInput/hooks';
import { type InviteMemberForm } from '../types';

type Props = {
Expand All @@ -37,6 +37,7 @@ function InviteMemberModal({ isOpen, onClose }: Props) {

const [isLoading, setIsLoading] = useState(false);
const cloudApi = useAuthedCloudApi();
const { parseEmailOptions } = useEmailInputUtils();

const formMethods = useForm<InviteMemberForm>({
defaultValues: {
Expand Down Expand Up @@ -123,7 +124,7 @@ function InviteMemberModal({ isOpen, onClose }: Props) {
if (value.length === 0) {
return t('errors.email_required');
}
const { errorMessage } = emailOptionsParser(value);
const { errorMessage } = parseEmailOptions(value);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return errorMessage || true;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const tenant_members = {
},
errors: {
email_required: 'Invitee email is required.',
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
max_member_limit: 'You have reached the maximum number of members ({{limit}}) for this tenant.',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const tenant_members = {
/** UNTRANSLATED */
email_required: 'Invitee email is required.',
/** UNTRANSLATED */
user_exists: 'This user is already in this organization.',
user_exists: 'This user is already invited to this organization.',
/** UNTRANSLATED */
invalid_email: 'Email address is invalid. Please make sure it is in the right format.',
/** UNTRANSLATED */
Expand Down

0 comments on commit 29aa2b7

Please sign in to comment.