diff --git a/.changeset/shy-readers-visit.md b/.changeset/shy-readers-visit.md
new file mode 100644
index 0000000000..da1fb5ffb3
--- /dev/null
+++ b/.changeset/shy-readers-visit.md
@@ -0,0 +1,7 @@
+---
+'@clerk/localizations': patch
+'@clerk/clerk-js': patch
+'@clerk/types': patch
+---
+
+When updating enrollment mode of a domain uses can now delete any pending invitations or suggestions.
diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/VerifiedDomainPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/VerifiedDomainPage.tsx
index 8a9e95807d..90e89b8651 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/components/OrganizationProfile/VerifyDomainPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/VerifyDomainPage.tsx
index b08e5c6482..b6f0a577d6 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}
-
-
- );
-};
-
-type BlockWithTrailingComponentProps = PropsOfComponent & {
- badge?: React.ReactElement;
- trailingComponent?: React.ReactElement;
- textElementDescriptor?: ElementDescriptor;
- textElementId?: ElementId;
- textLocalizationKey?: LocalizationKey;
-};
-
-export const BlockWithTrailingComponent = (props: BlockWithTrailingComponentProps) => {
- const {
- isLoading,
- children,
- trailingComponent,
- textElementDescriptor,
- textElementId,
- textLocalizationKey,
- badge,
- ...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,
- },
- props.sx,
- ]}
- >
-
-
- {children}
-
- {badge}
-
- {trailingComponent}
-
- );
-};
diff --git a/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx b/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx
new file mode 100644
index 0000000000..f7895c445f
--- /dev/null
+++ b/packages/clerk-js/src/ui/elements/BlockWithTrailingComponent.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+
+import type { LocalizationKey } from '../customizables';
+import { Box, Flex, Text } from '../customizables';
+import type { ElementDescriptor, ElementId } from '../customizables/elementDescriptors';
+import type { PropsOfComponent } from '../styledSystem';
+
+type BlockWithTrailingComponentProps = PropsOfComponent & {
+ badge?: React.ReactElement;
+ trailingComponent?: React.ReactElement;
+ textElementDescriptor?: ElementDescriptor;
+ textElementId?: ElementId;
+ textLocalizationKey?: LocalizationKey;
+};
+
+export const BlockWithTrailingComponent = (props: BlockWithTrailingComponentProps) => {
+ const {
+ isLoading,
+ children,
+ trailingComponent,
+ textElementDescriptor,
+ textElementId,
+ textLocalizationKey,
+ badge,
+ ...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,
+ },
+ props.sx,
+ ]}
+ >
+
+
+ {children}
+
+ {badge}
+
+ {trailingComponent}
+
+ );
+};
diff --git a/packages/clerk-js/src/ui/elements/ContentPage.tsx b/packages/clerk-js/src/ui/elements/ContentPage.tsx
index 25ef2ecb93..e33280e529 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/clerk-js/src/ui/elements/index.ts b/packages/clerk-js/src/ui/elements/index.ts
index 5e796da202..bf16d8e4a0 100644
--- a/packages/clerk-js/src/ui/elements/index.ts
+++ b/packages/clerk-js/src/ui/elements/index.ts
@@ -3,7 +3,7 @@ export * from './Header';
export * from './Footer';
export * from './Alert';
export * from './Form';
-export * from './BlockWithAction';
+export * from './BlockWithTrailingComponent';
export * from './BackLink';
export * from './IdentityPreview';
export * from './Avatar';
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 1e96414404..558aa8bc08 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 e043a97dac..d3a1074e65 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 982a26413d..305a58ef8a 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 f8413d0a82..924cf3b516 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;
};