Skip to content

Commit a229ced

Browse files
pmossmansuisuixia42
andcommitted
feat: add delete and reset buttons to Domain Verification UI (#18371)
Co-authored-by: suisuixia42 <[email protected]>
1 parent 61a6c45 commit a229ced

File tree

7 files changed

+262
-7
lines changed

7 files changed

+262
-7
lines changed

airbyte-webapp/src/core/api/hooks/domainVerification.ts

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
22

33
import { useCurrentOrganizationId } from "area/organization/utils/useCurrentOrganizationId";
44

5-
import { checkDomainVerification, createDomainVerification, listDomainVerifications } from "../generated/AirbyteClient";
5+
import {
6+
checkDomainVerification,
7+
createDomainVerification,
8+
deleteDomainVerification,
9+
listDomainVerifications,
10+
resetDomainVerification,
11+
} from "../generated/AirbyteClient";
612
import { SCOPE_ORGANIZATION } from "../scopes";
713
import { useRequestOptions } from "../useRequestOptions";
814

@@ -113,7 +119,68 @@ export const useCheckDomainVerification = () => {
113119
return useMutation({
114120
mutationFn: (domainVerificationId: string) => checkDomainVerification({ domainVerificationId }, requestOptions),
115121
onSuccess: () => {
116-
// Invalidate the list query to refresh the domain list with updated status
122+
queryClient.invalidateQueries({ queryKey: domainVerificationKeys.list(organizationId) });
123+
},
124+
});
125+
};
126+
127+
/**
128+
* Hook to delete a domain verification.
129+
*
130+
* Permanently removes a domain verification from the organization.
131+
* If the domain is verified and being used for SSO login enforcement,
132+
* those enforcements will be removed.
133+
*
134+
* @returns React Query mutation result
135+
*
136+
* @example
137+
* const { mutate: deleteDomain, isLoading } = useDeleteDomainVerification();
138+
*
139+
* deleteDomain(domainId, {
140+
* onSuccess: () => {
141+
* console.log("Domain deleted successfully");
142+
* }
143+
* });
144+
*/
145+
export const useDeleteDomainVerification = () => {
146+
const requestOptions = useRequestOptions();
147+
const queryClient = useQueryClient();
148+
const organizationId = useCurrentOrganizationId();
149+
150+
return useMutation({
151+
mutationFn: (domainVerificationId: string) => deleteDomainVerification({ domainVerificationId }, requestOptions),
152+
onSuccess: () => {
153+
queryClient.invalidateQueries({ queryKey: domainVerificationKeys.list(organizationId) });
154+
},
155+
});
156+
};
157+
158+
/**
159+
* Hook to reset a failed or expired domain verification.
160+
*
161+
* Resets the verification status back to PENDING and resumes checking for
162+
* the DNS TXT record. This is useful when a user wants to retry verification
163+
* after a failure or expiration.
164+
*
165+
* @returns React Query mutation result
166+
*
167+
* @example
168+
* const { mutate: resetDomain, isLoading } = useResetDomainVerification();
169+
*
170+
* resetDomain(domainId, {
171+
* onSuccess: () => {
172+
* console.log("Domain verification reset successfully");
173+
* }
174+
* });
175+
*/
176+
export const useResetDomainVerification = () => {
177+
const requestOptions = useRequestOptions();
178+
const queryClient = useQueryClient();
179+
const organizationId = useCurrentOrganizationId();
180+
181+
return useMutation({
182+
mutationFn: (domainVerificationId: string) => resetDomainVerification({ domainVerificationId }, requestOptions),
183+
onSuccess: () => {
117184
queryClient.invalidateQueries({ queryKey: domainVerificationKeys.list(organizationId) });
118185
},
119186
});

airbyte-webapp/src/locales/en.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,19 @@
14321432
"settings.organizationSettings.domainVerification.checkNow": "Check Status",
14331433
"settings.organizationSettings.domainVerification.checkError": "Failed to check domain verification status. Please try again.",
14341434
"settings.organizationSettings.domainVerification.viewDnsInfo": "View DNS Info",
1435+
"settings.organizationSettings.domainVerification.delete": "Delete",
1436+
"settings.organizationSettings.domainVerification.reset": "Reset",
1437+
"settings.organizationSettings.domainVerification.deleteDomain": "Delete Domain",
1438+
"settings.organizationSettings.domainVerification.deleteConfirmation": "Are you sure you want to delete the domain {domain}? This action cannot be undone.",
1439+
"settings.organizationSettings.domainVerification.deleteButton": "Delete Domain",
1440+
"settings.organizationSettings.domainVerification.deleteSsoWarning": "Warning: This domain is verified and being used for SSO login enforcement. Deleting it will remove SSO login requirements for users with email addresses on this domain.",
1441+
"settings.organizationSettings.domainVerification.deleted": "Domain deleted successfully.",
1442+
"settings.organizationSettings.domainVerification.deleteError": "Failed to delete domain. Please try again.",
1443+
"settings.organizationSettings.domainVerification.resetDomain": "Reset Domain Verification",
1444+
"settings.organizationSettings.domainVerification.resetConfirmation": "Are you sure you want to reset the verification for {domain}? The domain status will be changed to PENDING and the system will resume checking for the DNS record.",
1445+
"settings.organizationSettings.domainVerification.resetButton": "Reset Verification",
1446+
"settings.organizationSettings.domainVerification.resetSuccess": "Domain verification reset successfully. The system will now check for the DNS record.",
1447+
"settings.organizationSettings.domainVerification.resetError": "Failed to reset domain verification. Please try again.",
14351448
"settings.accountSettings": "Account Settings",
14361449
"settings.userId": "User ID: {id}",
14371450
"settings.userId.copy": "Copy user ID",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { FormattedMessage } from "react-intl";
2+
3+
import { ConfirmationModal } from "components/ConfirmationModal";
4+
import { Box } from "components/ui/Box";
5+
import { Text } from "components/ui/Text";
6+
7+
import { useDeleteDomainVerification } from "core/api";
8+
import { DomainVerificationResponse, SSOConfigRead } from "core/api/types/AirbyteClient";
9+
import { useNotificationService } from "hooks/services/Notification";
10+
11+
interface DeleteDomainConfirmationModalProps {
12+
domain: DomainVerificationResponse;
13+
ssoConfig: SSOConfigRead | null;
14+
onClose: () => void;
15+
}
16+
17+
export const DeleteDomainConfirmationModal: React.FC<DeleteDomainConfirmationModalProps> = ({
18+
domain,
19+
ssoConfig,
20+
onClose,
21+
}) => {
22+
const { registerNotification } = useNotificationService();
23+
const { mutateAsync: deleteDomain } = useDeleteDomainVerification();
24+
25+
const hasActiveSso = ssoConfig?.status === "active";
26+
const isVerifiedDomain = domain.status === "VERIFIED";
27+
const showSsoWarning = hasActiveSso && isVerifiedDomain;
28+
29+
const handleDelete = async () => {
30+
try {
31+
await deleteDomain(domain.id);
32+
33+
registerNotification({
34+
id: "domain-verification-deleted",
35+
text: <FormattedMessage id="settings.organizationSettings.domainVerification.deleted" />,
36+
type: "success",
37+
});
38+
39+
onClose();
40+
} catch (error) {
41+
registerNotification({
42+
id: "domain-verification-delete-error",
43+
text: <FormattedMessage id="settings.organizationSettings.domainVerification.deleteError" />,
44+
type: "error",
45+
});
46+
}
47+
};
48+
49+
return (
50+
<ConfirmationModal
51+
title="settings.organizationSettings.domainVerification.deleteDomain"
52+
text="settings.organizationSettings.domainVerification.deleteConfirmation"
53+
textValues={{ domain: domain.domain }}
54+
additionalContent={
55+
showSsoWarning && (
56+
<Box pt="lg">
57+
<Text color="red">
58+
<FormattedMessage id="settings.organizationSettings.domainVerification.deleteSsoWarning" />
59+
</Text>
60+
</Box>
61+
)
62+
}
63+
onCancel={onClose}
64+
onSubmit={handleDelete}
65+
submitButtonText="settings.organizationSettings.domainVerification.deleteButton"
66+
submitButtonVariant="danger"
67+
/>
68+
);
69+
};

airbyte-webapp/src/pages/SettingsPage/components/DomainVerification/DomainVerificationItem.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import styles from "./DomainVerification.module.scss";
1515
interface DomainVerificationItemProps {
1616
domain: DomainVerificationResponse;
1717
onViewDnsInfo: (domain: DomainVerificationResponse) => void;
18+
onDelete: (domain: DomainVerificationResponse) => void;
19+
onReset: (domain: DomainVerificationResponse) => void;
1820
}
1921

2022
const getStatusBadgeVariant = (status: DomainVerificationResponseStatus): "green" | "yellow" | "red" | "grey" => {
@@ -34,7 +36,12 @@ const getStatusBadgeVariant = (status: DomainVerificationResponseStatus): "green
3436

3537
const MINIMUM_LOADING_DELAY = 800; // milliseconds
3638

37-
export const DomainVerificationItem: React.FC<DomainVerificationItemProps> = ({ domain, onViewDnsInfo }) => {
39+
export const DomainVerificationItem: React.FC<DomainVerificationItemProps> = ({
40+
domain,
41+
onViewDnsInfo,
42+
onDelete,
43+
onReset,
44+
}) => {
3845
const { mutateAsync: checkNow } = useCheckDomainVerification();
3946
const { registerNotification } = useNotificationService();
4047
const [isChecking, setIsChecking] = useState(false);
@@ -50,7 +57,6 @@ export const DomainVerificationItem: React.FC<DomainVerificationItemProps> = ({
5057
const handleCheckNow = async () => {
5158
setIsChecking(true);
5259
try {
53-
// Ensure a minimum loading time to prevent jarring UI flicker
5460
await Promise.all([checkNow(domain.id), new Promise((resolve) => setTimeout(resolve, MINIMUM_LOADING_DELAY))]);
5561
} catch (error) {
5662
registerNotification({
@@ -63,6 +69,8 @@ export const DomainVerificationItem: React.FC<DomainVerificationItemProps> = ({
6369
}
6470
};
6571

72+
const canReset = domain.status === "FAILED" || domain.status === "EXPIRED";
73+
6674
return (
6775
<div className={styles.domainItem}>
6876
<FlexContainer justifyContent="space-between" alignItems="center">
@@ -99,6 +107,14 @@ export const DomainVerificationItem: React.FC<DomainVerificationItemProps> = ({
99107
<FormattedMessage id="settings.organizationSettings.domainVerification.checkNow" />
100108
</Button>
101109
)}
110+
{canReset && (
111+
<Button variant="secondary" size="sm" icon="reset" onClick={() => onReset(domain)}>
112+
<FormattedMessage id="settings.organizationSettings.domainVerification.reset" />
113+
</Button>
114+
)}
115+
<Button variant="danger" size="sm" icon="trash" onClick={() => onDelete(domain)}>
116+
<FormattedMessage id="settings.organizationSettings.domainVerification.delete" />
117+
</Button>
102118
</FlexContainer>
103119
</FlexContainer>
104120
</div>

airbyte-webapp/src/pages/SettingsPage/components/DomainVerification/DomainVerificationList.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ interface DomainVerificationListProps {
1111
domains: DomainVerificationResponse[];
1212
isLoading: boolean;
1313
onViewDnsInfo: (domain: DomainVerificationResponse) => void;
14+
onDelete: (domain: DomainVerificationResponse) => void;
15+
onReset: (domain: DomainVerificationResponse) => void;
1416
}
1517

1618
export const DomainVerificationList: React.FC<DomainVerificationListProps> = ({
1719
domains,
1820
isLoading,
1921
onViewDnsInfo,
22+
onDelete,
23+
onReset,
2024
}) => {
2125
if (isLoading) {
2226
return (
@@ -41,7 +45,13 @@ export const DomainVerificationList: React.FC<DomainVerificationListProps> = ({
4145
return (
4246
<FlexContainer direction="column" gap="md">
4347
{domains.map((domain) => (
44-
<DomainVerificationItem key={domain.id} domain={domain} onViewDnsInfo={onViewDnsInfo} />
48+
<DomainVerificationItem
49+
key={domain.id}
50+
domain={domain}
51+
onViewDnsInfo={onViewDnsInfo}
52+
onDelete={onDelete}
53+
onReset={onReset}
54+
/>
4555
))}
4656
</FlexContainer>
4757
);

airbyte-webapp/src/pages/SettingsPage/components/DomainVerification/DomainVerificationSection.tsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,26 @@ import { Heading } from "components/ui/Heading";
77
import { Text } from "components/ui/Text";
88

99
import { useCurrentOrganizationId } from "area/organization/utils/useCurrentOrganizationId";
10-
import { useListDomainVerifications } from "core/api";
10+
import { useListDomainVerifications, useSSOConfigManagement } from "core/api";
1111
import { DomainVerificationResponse } from "core/api/types/AirbyteClient";
1212
import { useIntent } from "core/utils/rbac";
1313

14+
import { DeleteDomainConfirmationModal } from "./DeleteDomainConfirmationModal";
1415
import styles from "./DomainVerification.module.scss";
1516
import { DomainVerificationList } from "./DomainVerificationList";
1617
import { DomainVerificationModal } from "./DomainVerificationModal";
18+
import { ResetDomainConfirmationModal } from "./ResetDomainConfirmationModal";
1719

1820
export const DomainVerificationSection: React.FC = () => {
1921
const organizationId = useCurrentOrganizationId();
2022
const canUpdateOrganization = useIntent("UpdateOrganization", { organizationId });
2123
const { data, isLoading } = useListDomainVerifications();
24+
const { ssoConfig } = useSSOConfigManagement();
2225

2326
const [isModalOpen, setIsModalOpen] = useState(false);
2427
const [selectedDomain, setSelectedDomain] = useState<DomainVerificationResponse | undefined>(undefined);
28+
const [deleteModalDomain, setDeleteModalDomain] = useState<DomainVerificationResponse | undefined>(undefined);
29+
const [resetModalDomain, setResetModalDomain] = useState<DomainVerificationResponse | undefined>(undefined);
2530

2631
const domains = data?.domainVerifications || [];
2732

@@ -40,6 +45,14 @@ export const DomainVerificationSection: React.FC = () => {
4045
setSelectedDomain(undefined);
4146
};
4247

48+
const handleDelete = (domain: DomainVerificationResponse) => {
49+
setDeleteModalDomain(domain);
50+
};
51+
52+
const handleReset = (domain: DomainVerificationResponse) => {
53+
setResetModalDomain(domain);
54+
};
55+
4356
return (
4457
<div className={styles.section}>
4558
<FlexContainer direction="column" gap="lg">
@@ -64,9 +77,27 @@ export const DomainVerificationSection: React.FC = () => {
6477
<FormattedMessage id="settings.organizationSettings.domainVerification.description" />
6578
</Text>
6679

67-
<DomainVerificationList domains={domains} isLoading={isLoading} onViewDnsInfo={handleViewDnsInfo} />
80+
<DomainVerificationList
81+
domains={domains}
82+
isLoading={isLoading}
83+
onViewDnsInfo={handleViewDnsInfo}
84+
onDelete={handleDelete}
85+
onReset={handleReset}
86+
/>
6887

6988
{isModalOpen && <DomainVerificationModal onClose={handleCloseModal} existingDomain={selectedDomain} />}
89+
90+
{deleteModalDomain && (
91+
<DeleteDomainConfirmationModal
92+
domain={deleteModalDomain}
93+
ssoConfig={ssoConfig}
94+
onClose={() => setDeleteModalDomain(undefined)}
95+
/>
96+
)}
97+
98+
{resetModalDomain && (
99+
<ResetDomainConfirmationModal domain={resetModalDomain} onClose={() => setResetModalDomain(undefined)} />
100+
)}
70101
</FlexContainer>
71102
</div>
72103
);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { FormattedMessage } from "react-intl";
2+
3+
import { ConfirmationModal } from "components/ConfirmationModal";
4+
5+
import { useResetDomainVerification } from "core/api";
6+
import { DomainVerificationResponse } from "core/api/types/AirbyteClient";
7+
import { useNotificationService } from "hooks/services/Notification";
8+
9+
interface ResetDomainConfirmationModalProps {
10+
domain: DomainVerificationResponse;
11+
onClose: () => void;
12+
}
13+
14+
export const ResetDomainConfirmationModal: React.FC<ResetDomainConfirmationModalProps> = ({ domain, onClose }) => {
15+
const { registerNotification } = useNotificationService();
16+
const { mutateAsync: resetDomain } = useResetDomainVerification();
17+
18+
const handleReset = async () => {
19+
try {
20+
await resetDomain(domain.id);
21+
22+
registerNotification({
23+
id: "domain-verification-reset",
24+
text: <FormattedMessage id="settings.organizationSettings.domainVerification.resetSuccess" />,
25+
type: "success",
26+
});
27+
28+
onClose();
29+
} catch (error) {
30+
registerNotification({
31+
id: "domain-verification-reset-error",
32+
text: <FormattedMessage id="settings.organizationSettings.domainVerification.resetError" />,
33+
type: "error",
34+
});
35+
}
36+
};
37+
38+
return (
39+
<ConfirmationModal
40+
title="settings.organizationSettings.domainVerification.resetDomain"
41+
text="settings.organizationSettings.domainVerification.resetConfirmation"
42+
textValues={{ domain: domain.domain }}
43+
onCancel={onClose}
44+
onSubmit={handleReset}
45+
submitButtonText="settings.organizationSettings.domainVerification.resetButton"
46+
submitButtonVariant="primary"
47+
/>
48+
);
49+
};

0 commit comments

Comments
 (0)