From de3211ff22ed336264c1a1ed3e836144a74eb52e Mon Sep 17 00:00:00 2001 From: panteliselef Date: Thu, 24 Aug 2023 12:38:42 +0300 Subject: [PATCH 1/3] feat(clerk-js): Delete pending invitations when updating enrollment mode of domain --- .../VerifiedDomainPage.tsx | 91 ++++++++++--------- .../clerk-js/src/ui/elements/ContentPage.tsx | 2 +- packages/localizations/src/en-US.ts | 32 ++++--- packages/types/src/appearance.ts | 3 +- packages/types/src/localization.ts | 33 ++++--- packages/types/src/organizationDomain.ts | 2 +- 6 files changed, 95 insertions(+), 68 deletions(-) diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/VerifiedDomainPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/VerifiedDomainPage.tsx index 8a9e95807da..90e89b86519 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/VerifiedDomainPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/VerifiedDomainPage.tsx @@ -1,29 +1,27 @@ import type { OrganizationEnrollmentMode } from '@clerk/types'; import { useCoreOrganization, useEnvironment } from '../../contexts'; -import { Col, descriptors, Flex, localizationKeys, Spinner } from '../../customizables'; -import { - BlockWithAction, - ContentPage, - Form, - FormButtons, - Header, - useCardState, - withCardStateProvider, -} from '../../elements'; -import { useFetch } from '../../hooks'; +import { Col, Flex, localizationKeys, Spinner } from '../../customizables'; +import { ContentPage, Form, FormButtons, Header, useCardState, withCardStateProvider } from '../../elements'; +import { useFetch, useNavigateToFlowStart } from '../../hooks'; import { useRouter } from '../../router'; import { handleError, useFormControl } from '../../utils'; -import { EnrollmentBadge } from './EnrollmentBadge'; import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar'; export const VerifiedDomainPage = withCardStateProvider(() => { const card = useCardState(); const { organizationSettings } = useEnvironment(); const { organization } = useCoreOrganization(); - const { params, navigate } = useRouter(); + const { domains } = useCoreOrganization({ + domains: { + infinite: true, + }, + }); + const { navigateToFlowStart } = useNavigateToFlowStart(); + const { params, navigate, queryParams } = useRouter(); + const mode = (queryParams.mode ?? 'edit') as 'select' | 'edit'; - const title = localizationKeys('organizationProfile.verifiedDomainPage.title'); + const allowsEdit = mode === 'edit'; const enrollmentMode = useFormControl('enrollmentMode', '', { type: 'radio', @@ -32,9 +30,11 @@ export const VerifiedDomainPage = withCardStateProvider(() => { ? [ { value: 'manual_invitation', - label: localizationKeys('organizationProfile.verifiedDomainPage.manualInvitationOption__label'), + label: localizationKeys( + 'organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__label', + ), description: localizationKeys( - 'organizationProfile.verifiedDomainPage.manualInvitationOption__description', + 'organizationProfile.verifiedDomainPage.enrollmentTab.manualInvitationOption__description', ), }, ] @@ -43,9 +43,11 @@ export const VerifiedDomainPage = withCardStateProvider(() => { ? [ { value: 'automatic_invitation', - label: localizationKeys('organizationProfile.verifiedDomainPage.automaticInvitationOption__label'), + label: localizationKeys( + 'organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__label', + ), description: localizationKeys( - 'organizationProfile.verifiedDomainPage.automaticInvitationOption__description', + 'organizationProfile.verifiedDomainPage.enrollmentTab.automaticInvitationOption__description', ), }, ] @@ -54,9 +56,11 @@ export const VerifiedDomainPage = withCardStateProvider(() => { ? [ { value: 'automatic_suggestion', - label: localizationKeys('organizationProfile.verifiedDomainPage.automaticSuggestionOption__label'), + label: localizationKeys( + 'organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__label', + ), description: localizationKeys( - 'organizationProfile.verifiedDomainPage.automaticSuggestionOption__description', + 'organizationProfile.verifiedDomainPage.enrollmentTab.automaticSuggestionOption__description', ), }, ] @@ -64,6 +68,11 @@ export const VerifiedDomainPage = withCardStateProvider(() => { ], }); + const deletePending = useFormControl('deleteExistingInvitationsSuggestions', '', { + label: localizationKeys('formFieldLabel__organizationDomainDeletePending'), + type: 'checkbox', + }); + const { data: domain, status: domainStatus } = useFetch( organization?.getDomain, { @@ -76,6 +85,7 @@ export const VerifiedDomainPage = withCardStateProvider(() => { }, ); + const isFormDirty = deletePending.checked || domain?.enrollmentMode !== enrollmentMode.value; const updateEnrollmentMode = async () => { if (!domain || !organization) { return; @@ -84,8 +94,11 @@ export const VerifiedDomainPage = withCardStateProvider(() => { try { await domain.updateEnrollmentMode({ enrollmentMode: enrollmentMode.value as OrganizationEnrollmentMode, + deletePending: deletePending.checked, }); + await (domains as any).unstable__mutate(); + await navigate('../../'); } catch (e) { handleError(e, [enrollmentMode], card.setError); @@ -115,36 +128,23 @@ export const VerifiedDomainPage = withCardStateProvider(() => { ); } + if (!(domain.verification && domain.verification.status === 'verified')) { + void navigateToFlowStart(); + } return ( - } - sx={t => ({ - backgroundColor: t.colors.$blackAlpha50, - padding: `${t.space.$3} ${t.space.$4}`, - minHeight: t.sizes.$10, - })} - actionSx={t => ({ - color: t.colors.$danger500, - })} - actionLabel={localizationKeys('organizationProfile.verifiedDomainPage.actionLabel__remove')} - onActionClick={() => navigate(`../../domain/${domain.id}/remove`)} - > - {domain.name} - - @@ -154,7 +154,16 @@ export const VerifiedDomainPage = withCardStateProvider(() => { - + {allowsEdit && ( + + + + )} + + diff --git a/packages/clerk-js/src/ui/elements/ContentPage.tsx b/packages/clerk-js/src/ui/elements/ContentPage.tsx index 25ef2ecb93a..e33280e5291 100644 --- a/packages/clerk-js/src/ui/elements/ContentPage.tsx +++ b/packages/clerk-js/src/ui/elements/ContentPage.tsx @@ -6,7 +6,7 @@ import type { PropsOfComponent } from '../styledSystem'; import { CardAlert, Header, NavbarMenuButtonRow, useCardState } from './index'; type PageProps = PropsOfComponent & { - headerTitle: LocalizationKey; + headerTitle: LocalizationKey | string; Breadcrumbs?: React.ComponentType | null; headerSubtitle?: LocalizationKey; }; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 1e96414404b..558aa8bc08c 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -39,6 +39,7 @@ export const enUS: LocalizationResource = { formFieldLabel__organizationEmailDomainEmailAddress: 'Verification email address', formFieldLabel__organizationEmailDomainEmailAddressDescription: 'Enter an email address under this domain to receive a code and verify this domain.', + formFieldLabel__organizationDomainDeletePending: 'Delete pending invitations and suggestions', formFieldLabel__confirmDeletion: 'Confirmation', formFieldLabel__role: 'Role', formFieldInputPlaceholder__emailAddress: '', @@ -592,25 +593,30 @@ export const enUS: LocalizationResource = { }, verifyDomainPage: { title: 'Verify domain', - actionLabel__remove: 'Remove unverified domain', subtitle: 'The domain {{domainName}} needs to be verified via email.', + subtitleVerificationCodeScreen: 'A verification code was sent to {{emailAddress}}. Enter the code to continue.', formTitle: 'Verification code', formSubtitle: 'Enter the verification code sent to your email address', resendButton: "Didn't receive a code? Resend", }, verifiedDomainPage: { - title: 'Enrollment options', - actionLabel__remove: 'Remove email domain', - formTitle: 'Enrollment mode', - formSubtitle: 'Choose how users from this domain can join the organization.', - manualInvitationOption__label: 'No automatic enrollment', - manualInvitationOption__description: 'Users can only be invited manually to the organization.', - automaticInvitationOption__label: 'Automatic invitations', - automaticInvitationOption__description: - 'Users are automatically invited to join the organization when they sign-up and can join anytime.', - automaticSuggestionOption__label: 'Automatic suggestions', - automaticSuggestionOption__description: - 'Users receive a suggestion to request to join, but must be approved by an admin before they are able to join the organization.', + subtitle: 'The domain {{domain}} is now verified. Continue by selecting enrollment mode.', + start: { + headerTitle__enrollment: 'Enrollment options', + headerTitle__danger: 'Danger', + }, + enrollmentTab: { + subtitle: 'Choose how users from this domain can join the organization.', + manualInvitationOption__label: 'No automatic enrollment', + manualInvitationOption__description: 'Users can only be invited manually to the organization.', + automaticInvitationOption__label: 'Automatic invitations', + automaticInvitationOption__description: + 'Users are automatically invited to join the organization when they sign-up and can join anytime.', + automaticSuggestionOption__label: 'Automatic suggestions', + automaticSuggestionOption__description: + 'Users receive a suggestion to request to join, but must be approved by an admin before they are able to join the organization.', + formButton__save: 'Save', + }, }, invitePage: { title: 'Invite members', diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index e043a97daca..d3a1074e659 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -75,7 +75,8 @@ export type FieldId = | 'deleteConfirmation' | 'deleteOrganizationConfirmation' | 'enrollmentMode' - | 'affiliationEmailAddress'; + | 'affiliationEmailAddress' + | 'deleteExistingInvitationsSuggestions'; export type ProfileSectionId = | 'profile' | 'username' diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 982a26413d4..305a58ef8a1 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -48,6 +48,7 @@ type _LocalizationResource = { formFieldLabel__organizationEmailDomain: LocalizationValue; formFieldLabel__organizationEmailDomainEmailAddress: LocalizationValue; formFieldLabel__organizationEmailDomainEmailAddressDescription: LocalizationValue; + formFieldLabel__organizationDomainDeletePending: LocalizationValue; formFieldLabel__confirmDeletion: LocalizationValue; formFieldLabel__role: LocalizationValue; formFieldInputPlaceholder__emailAddress: LocalizationValue; @@ -609,23 +610,33 @@ type _LocalizationResource = { }; verifyDomainPage: { title: LocalizationValue; - actionLabel__remove: LocalizationValue; subtitle: LocalizationValue; + subtitleVerificationCodeScreen: LocalizationValue; formTitle: LocalizationValue; formSubtitle: LocalizationValue; resendButton: LocalizationValue; }; verifiedDomainPage: { - title: LocalizationValue; - actionLabel__remove: LocalizationValue; - formTitle: LocalizationValue; - formSubtitle: LocalizationValue; - manualInvitationOption__label: LocalizationValue; - manualInvitationOption__description: LocalizationValue; - automaticInvitationOption__label: LocalizationValue; - automaticInvitationOption__description: LocalizationValue; - automaticSuggestionOption__label: LocalizationValue; - automaticSuggestionOption__description: LocalizationValue; + subtitle: LocalizationValue; + start: { + headerTitle__enrollment: LocalizationValue; + headerTitle__danger: LocalizationValue; + }; + enrollmentTab: { + subtitle: LocalizationValue; + manualInvitationOption__label: LocalizationValue; + manualInvitationOption__description: LocalizationValue; + automaticInvitationOption__label: LocalizationValue; + automaticInvitationOption__description: LocalizationValue; + automaticSuggestionOption__label: LocalizationValue; + automaticSuggestionOption__description: LocalizationValue; + formButton__save: LocalizationValue; + }; + dangerTab: { + removeDomainTitle: LocalizationValue; + removeDomainSubtitle: LocalizationValue; + removeDomainActionLabel__remove: LocalizationValue; + }; }; removeDomainPage: { title: LocalizationValue; diff --git a/packages/types/src/organizationDomain.ts b/packages/types/src/organizationDomain.ts index f8413d0a82b..924cf3b516a 100644 --- a/packages/types/src/organizationDomain.ts +++ b/packages/types/src/organizationDomain.ts @@ -36,5 +36,5 @@ export type AttemptAffiliationVerificationParams = { }; export type UpdateEnrollmentModeParams = Pick & { - deleteExisting?: boolean; + deletePending?: boolean; }; From c1de06bdf5cd1918ad2564ac0bb9431ae183c0bf Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 25 Aug 2023 01:38:42 +0300 Subject: [PATCH 2/3] chore(clerk-js): Remove BlockWithAction as it unused --- .../OrganizationProfile/VerifyDomainPage.tsx | 21 +-- .../src/ui/elements/BlockWithAction.tsx | 164 ------------------ .../elements/BlockWithTrailingComponent.tsx | 70 ++++++++ packages/clerk-js/src/ui/elements/index.ts | 2 +- 4 files changed, 72 insertions(+), 185 deletions(-) delete mode 100644 packages/clerk-js/src/ui/elements/BlockWithAction.tsx create mode 100644 packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/VerifyDomainPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/VerifyDomainPage.tsx index b08e5c64829..b6f0a577d6b 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/VerifyDomainPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/VerifyDomainPage.tsx @@ -5,7 +5,6 @@ import { useCoreOrganization, useEnvironment } from '../../contexts'; import { Button, descriptors, Flex, localizationKeys, Spinner } from '../../customizables'; import type { VerificationCodeCardProps } from '../../elements'; import { - BlockWithAction, ContentPage, Form, FormButtonContainer, @@ -18,14 +17,13 @@ import { CodeForm } from '../../elements/CodeForm'; import { useFetch, useLoadingStatus, useNavigateToFlowStart } from '../../hooks'; import { useRouter } from '../../router'; import { handleError, sleep, useFormControl } from '../../utils'; -import { EnrollmentBadge } from './EnrollmentBadge'; import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar'; export const VerifyDomainPage = withCardStateProvider(() => { const card = useCardState(); const { organizationSettings } = useEnvironment(); const { organization } = useCoreOrganization(); - const { params, navigate } = useRouter(); + const { params } = useRouter(); const { navigateToFlowStart } = useNavigateToFlowStart(); const [success, setSuccess] = React.useState(false); @@ -134,23 +132,6 @@ export const VerifyDomainPage = withCardStateProvider(() => { headerSubtitle={subtitle} Breadcrumbs={OrganizationProfileBreadcrumbs} > - } - sx={t => ({ - backgroundColor: t.colors.$blackAlpha50, - padding: `${t.space.$3} ${t.space.$4}`, - minHeight: t.sizes.$10, - })} - actionLabel={localizationKeys('organizationProfile.verifyDomainPage.actionLabel__remove')} - onActionClick={() => navigate(`../../../domain/${domain.id}/remove`)} - actionSx={t => ({ - color: t.colors.$danger500, - })} - > - {domain.name} - - & { - actionSx?: ThemableCssProp; - badge?: React.ReactElement; - textElementDescriptor?: ElementDescriptor; - textElementId?: ElementId; - textLocalizationKey?: LocalizationKey; - actionLabel?: LocalizationKey; - onActionClick?: (e: MouseEvent) => void | Promise; -}; - -/** - * Similar to ArrowBlockButton but just a container - * - An action is expected instead of an icon - */ -export const BlockWithAction = (props: ArrowBlockButtonProps) => { - const { - actionSx, - isLoading, - children, - textElementDescriptor, - textElementId, - textLocalizationKey, - badge, - onActionClick, - actionLabel, - ...rest - } = props; - - return ( - [ - { - borderRadius: theme.radii.$md, - display: 'inline-flex', - alignItems: 'center', - gap: theme.space.$4, - position: 'relative', - justifyContent: 'flex-start', - borderColor: theme.colors.$blackAlpha200, - '--action-opacity': '0', - '--action-transform': `translateX(-${theme.space.$2});`, - '&:hover,&:focus ': { - '--action-opacity': '1', - '--action-transform': 'translateX(0px);', - }, - }, - props.sx, - ]} - > - - - {children} - - {badge} - -