diff --git a/frontend/global.d.ts b/frontend/global.d.ts index 79fa78d2b91e..99ba1f8d9a97 100644 --- a/frontend/global.d.ts +++ b/frontend/global.d.ts @@ -1,5 +1,14 @@ import { Component, FC, ReactNode } from 'react' import _Select from './web/components/Select' +export type OpenConfirm = { + title: ReactNode + body: ReactNode + onYes: () => void + onNo?: () => void + destructive?: boolean + yesText?: string + noText?: string +} import { TooltipProps } from './web/components/Tooltip' export declare const openModal: (name?: string) => Promise @@ -16,14 +25,7 @@ declare global { className?: string, onClose?: () => void, ) => void - const openConfirm: ( - header: ReactNode, - body: ReactNode, - onYes: () => void, - onNo?: () => void, - yesText?: string, - noText?: string, - ) => void + const openConfirm: (data: OpenConfirm) => void const Row: typeof Component const toast: (value: string) => void const Flex: typeof Component diff --git a/frontend/web/components/AdminAPIKeys.js b/frontend/web/components/AdminAPIKeys.js index 88b6c7a69ec6..66d13209cde9 100644 --- a/frontend/web/components/AdminAPIKeys.js +++ b/frontend/web/components/AdminAPIKeys.js @@ -383,13 +383,14 @@ export default class AdminAPIKeys extends PureComponent { } remove = (v) => { - openConfirm( - 'Are you sure?', -
- This will revoke the API key {v.name} ({v.prefix} - *****************). This change cannot be reversed. -
, - () => { + openConfirm({ + body: ( +
+ This will revoke the API key {v.name} ({v.prefix} + *****************). This change cannot be undone. +
+ ), + onYes: () => { data .delete( `${Project.api}organisations/${this.props.organisationId}/master-api-keys/${v.prefix}/`, @@ -398,7 +399,9 @@ export default class AdminAPIKeys extends PureComponent { this.fetch() }) }, - ) + title: 'Revoke the API key', + yesText: 'Confirm', + }) } render() { diff --git a/frontend/web/components/CollapsibleNestedRolePermissionsList.tsx b/frontend/web/components/CollapsibleNestedRolePermissionsList.tsx index 50630d3f6de1..21d2a89d686c 100644 --- a/frontend/web/components/CollapsibleNestedRolePermissionsList.tsx +++ b/frontend/web/components/CollapsibleNestedRolePermissionsList.tsx @@ -99,14 +99,14 @@ const CollapsibleNestedRolePermissionsList: React.FC 0) { return new Promise((resolve) => { - openConfirm( - 'Are you sure?', - 'Closing this will discard your unsaved changes.', - () => resolve(true), - () => resolve(false), - 'Ok', - 'Cancel', - ) + openConfirm({ + body: 'Closing this will discard your unsaved changes.', + noText: 'Cancel', + onNo: () => resolve(false), + onYes: () => resolve(true), + title: 'Discard changes', + yesText: 'Ok', + }) }) } else { return Promise.resolve(true) diff --git a/frontend/web/components/EditPermissions.tsx b/frontend/web/components/EditPermissions.tsx index 2816e99517ae..ec97e5011a7b 100644 --- a/frontend/web/components/EditPermissions.tsx +++ b/frontend/web/components/EditPermissions.tsx @@ -116,14 +116,14 @@ const _EditPermissionsModal: FC = forwardRef( setInterceptClose(() => { if (valueChanged) { return new Promise((resolve) => { - openConfirm( - 'Are you sure?', - 'Closing this will discard your unsaved changes.', - () => resolve(true), - () => resolve(false), - 'Ok', - 'Cancel', - ) + openConfirm({ + body: 'Closing this will discard your unsaved changes.', + noText: 'Cancel', + onNo: () => resolve(false), + onYes: () => resolve(true), + title: 'Discard changes', + yesText: 'Ok', + }) }) } else { return Promise.resolve(true) diff --git a/frontend/web/components/Feature.js b/frontend/web/components/Feature.js index 9ca431afd83a..4aecb868daaa 100644 --- a/frontend/web/components/Feature.js +++ b/frontend/web/components/Feature.js @@ -15,13 +15,15 @@ export default class Feature extends PureComponent { const idToRemove = this.props.multivariate_options[i].id if (idToRemove) { - openConfirm( - 'Please confirm', - 'This will remove the variation on your feature for all environments, if you wish to turn it off just for this environment you can set the % value to 0.', - () => { + openConfirm({ + body: 'This will remove the variation on your feature for all environments, if you wish to turn it off just for this environment you can set the % value to 0.', + destructive: true, + onYes: () => { this.props.removeVariation(i) }, - ) + title: 'Delete variation', + yesText: 'Confirm', + }) } else { this.props.removeVariation(i) } diff --git a/frontend/web/components/IntegrationList.js b/frontend/web/components/IntegrationList.js index 914ea66fe695..835e4b2a37fd 100644 --- a/frontend/web/components/IntegrationList.js +++ b/frontend/web/components/IntegrationList.js @@ -206,15 +206,17 @@ class IntegrationList extends Component { ? ProjectStore.getEnvironment(integration.flagsmithEnvironment) : '' const name = env && env.name - openConfirm( - 'Confirm remove integration', - - This will remove your integration from the{' '} - {integration.flagsmithEnvironment ? 'environment ' : 'project'} - {name ? {name} : ''}, it will no longer receive data. - Are you sure? - , - () => { + openConfirm({ + body: ( + + This will remove your integration from the{' '} + {integration.flagsmithEnvironment ? 'environment ' : 'project'} + {name ? {name} : ''}, it will no longer receive data. + Are you sure? + + ), + destructive: true, + onYes: () => { if (integration.flagsmithEnvironment) { _data .delete( @@ -231,7 +233,9 @@ class IntegrationList extends Component { .catch(this.onError) } }, - ) + title: 'Delete integration', + yesText: 'Confirm', + }) } addIntegration = (integration, id) => { diff --git a/frontend/web/components/SegmentOverrides.js b/frontend/web/components/SegmentOverrides.js index 6111027a86db..57d50e0bf4ea 100644 --- a/frontend/web/components/SegmentOverrides.js +++ b/frontend/web/components/SegmentOverrides.js @@ -499,19 +499,22 @@ class TheComponent extends Component { } return } - openConfirm( - 'Delete Segment Override', -
- { - 'Are you sure you want to delete this segment override? This will be applied when you click Update Segment Overrides.' - } -
, - () => { + openConfirm({ + body: ( +
+ { + 'Are you sure you want to delete this segment override? This will be applied when you click Update Segment Overrides and cannot be undone.' + } +
+ ), + destructive: true, + onYes: () => { this.props.value[i].toRemove = true this.setState({ isLoading: false }) }, - () => {}, - ) + title: 'Delete Segment Override', + yesText: 'Confirm', + }) } setValue = (i, value) => { diff --git a/frontend/web/components/ServerSideSDKKeys.js b/frontend/web/components/ServerSideSDKKeys.js index 35da267f3aa6..d044870ce815 100644 --- a/frontend/web/components/ServerSideSDKKeys.js +++ b/frontend/web/components/ServerSideSDKKeys.js @@ -112,13 +112,15 @@ class ServerSideSDKKeys extends Component { } remove = (id, name) => { - openConfirm( - 'Delete Server-side Environment Keys', -
- The key {name} will be permanently deleted, are you - sure? -
, - () => { + openConfirm({ + body: ( +
+ Are you sure you want to remove the SDK key {name}? + This action cannot be undone. +
+ ), + destructive: true, + onYes: () => { this.setState({ isSaving: true }) deleteServersideEnvironmentKeys(getStore(), { environmentId: this.props.environmentId, @@ -129,7 +131,9 @@ class ServerSideSDKKeys extends Component { this.setState({ isSaving: false }) }) }, - ) + title: 'Delete Server-side Environment Keys', + yesText: 'Confirm', + }) } fetch = (environmentId) => { diff --git a/frontend/web/components/UserGroupList.tsx b/frontend/web/components/UserGroupList.tsx index 2b172b9a9e71..03ddb536e5a6 100644 --- a/frontend/web/components/UserGroupList.tsx +++ b/frontend/web/components/UserGroupList.tsx @@ -37,15 +37,19 @@ const UserGroupsList: FC = ({ const isAdmin = AccountStore.isAdmin() const removeGroup = (id: number, name: string) => { - openConfirm( - 'Delete Group', -
- {'Are you sure you want to delete '} - {name} - {'?'} -
, - () => deleteGroup({ id, orgId }), - ) + openConfirm({ + body: ( +
+ {'Are you sure you want to delete '} + {name} + {'? This action cannot be undone.'} +
+ ), + destructive: true, + onYes: () => deleteGroup({ id, orgId }), + title: 'Delete Group', + yesText: 'Confirm', + }) } return ( diff --git a/frontend/web/components/import-export/ImportPage.tsx b/frontend/web/components/import-export/ImportPage.tsx index f21cfc09545a..03a0b8e9c356 100644 --- a/frontend/web/components/import-export/ImportPage.tsx +++ b/frontend/web/components/import-export/ImportPage.tsx @@ -173,19 +173,22 @@ const ImportPage: FC = ({ diff --git a/frontend/web/components/modals/ConfirmRemoveOrganisation.tsx b/frontend/web/components/modals/ConfirmRemoveOrganisation.tsx index 2ee819f4a432..7e6c4a37f649 100644 --- a/frontend/web/components/modals/ConfirmRemoveOrganisation.tsx +++ b/frontend/web/components/modals/ConfirmRemoveOrganisation.tsx @@ -33,7 +33,7 @@ const ConfirmRemoveOrganisation: FC = ({ This will remove {organisation.name} and{' '} all of it's projects. You should ensure that you do not contain any references to this organisation in your - applications before proceeding. + applications before proceeding. This action cannot be undone. = ({ diff --git a/frontend/web/components/modals/ConfirmRemoveProject.tsx b/frontend/web/components/modals/ConfirmRemoveProject.tsx index c31874e89031..99d86139da66 100644 --- a/frontend/web/components/modals/ConfirmRemoveProject.tsx +++ b/frontend/web/components/modals/ConfirmRemoveProject.tsx @@ -33,7 +33,7 @@ const ConfirmRemoveProject: FC = ({ This will remove {project.name} and{' '} all environments. You should ensure that you do not contain any references to this project in your applications - before proceeding. + before proceeding. This action cannot be undone.

= ({ - diff --git a/frontend/web/components/modals/ConfirmRemoveWebhook.tsx b/frontend/web/components/modals/ConfirmRemoveWebhook.tsx index 21436e9b8e45..ccc39d0c6028 100644 --- a/frontend/web/components/modals/ConfirmRemoveWebhook.tsx +++ b/frontend/web/components/modals/ConfirmRemoveWebhook.tsx @@ -36,7 +36,8 @@ const ConfirmRemoveWebhook: FC = ({

- This will remove {url} for the environment{' '} + This will remove webhooks to {url} for the + environment{' '} { find(project.environments, { @@ -44,8 +45,7 @@ const ConfirmRemoveWebhook: FC = ({ })?.name } - . You should ensure that you do not contain any references to this - webhook in your applications before proceeding. + . This action cannot be undone.

= ({
diff --git a/frontend/web/components/modals/ConfirmToggleEnvFeature.tsx b/frontend/web/components/modals/ConfirmToggleEnvFeature.tsx index 32b825bb5104..df4552a5775b 100644 --- a/frontend/web/components/modals/ConfirmToggleEnvFeature.tsx +++ b/frontend/web/components/modals/ConfirmToggleEnvFeature.tsx @@ -45,7 +45,7 @@ const ConfirmToggleEnvFeature: FC = ({ className='btn btn-primary' id='confirm-toggle-feature-btn' > - Confirm changes + Confirm diff --git a/frontend/web/components/modals/ConfirmToggleFeature.tsx b/frontend/web/components/modals/ConfirmToggleFeature.tsx index 1aabd712eb86..0b25688d7f5b 100644 --- a/frontend/web/components/modals/ConfirmToggleFeature.tsx +++ b/frontend/web/components/modals/ConfirmToggleFeature.tsx @@ -84,7 +84,7 @@ const ConfirmToggleFeature: FC = ({ className='btn btn-primary' id='confirm-toggle-feature-btn' > - Confirm changes + Confirm diff --git a/frontend/web/components/modals/CreateFlag.js b/frontend/web/components/modals/CreateFlag.js index 6d8ac6dfae57..725d553f812e 100644 --- a/frontend/web/components/modals/CreateFlag.js +++ b/frontend/web/components/modals/CreateFlag.js @@ -156,14 +156,14 @@ const CreateFlag = class extends Component { this.state.segmentsChanged || this.state.settingsChanged ) { - openConfirm( - 'Are you sure?', - 'Closing this will discard your unsaved changes.', - () => resolve(true), - () => resolve(false), - 'Ok', - 'Cancel', - ) + openConfirm({ + body: 'Closing this will discard your unsaved changes.', + noText: 'Cancel', + onNo: () => resolve(false), + onYes: () => resolve(true), + title: 'Discard changes', + yesText: 'Ok', + }) } else { resolve(true) } @@ -899,16 +899,15 @@ const CreateFlag = class extends Component { } else if ( document.getElementById('language-validation-error') ) { - openConfirm( - 'Validation error', - 'Your remote config value does not pass validation for the language you have selected. Are you sure you wish to save?', - () => { + openConfirm({ + body: 'Your remote config value does not pass validation for the language you have selected. Are you sure you wish to save?', + noText: 'Cancel', + onYes: () => { this.save(editFeatureValue, isSaving) }, - null, - 'Save', - 'Cancel', - ) + title: 'Validation error', + yesText: 'Save', + }) } else { this.save(editFeatureValue, isSaving) } diff --git a/frontend/web/components/modals/CreateGroup.js b/frontend/web/components/modals/CreateGroup.js index 4ac797ca12cd..bee90a4dbbf1 100644 --- a/frontend/web/components/modals/CreateGroup.js +++ b/frontend/web/components/modals/CreateGroup.js @@ -78,14 +78,14 @@ const CreateGroup = class extends Component { this.state.userRemoved ) { return new Promise((resolve) => { - openConfirm( - 'Are you sure?', - 'Closing this will discard your unsaved changes.', - () => resolve(true), - () => resolve(false), - 'Ok', - 'Cancel', - ) + openConfirm({ + body: 'Closing this will discard your unsaved changes.', + noText: 'Cancel', + onNo: () => resolve(false), + onYes: () => resolve(true), + title: 'Discard changes', + yesText: 'Ok', + }) }) } else { return Promise.resolve(true) diff --git a/frontend/web/components/modals/CreateRole.tsx b/frontend/web/components/modals/CreateRole.tsx index 914e87048e6c..7449e2f4d919 100644 --- a/frontend/web/components/modals/CreateRole.tsx +++ b/frontend/web/components/modals/CreateRole.tsx @@ -264,14 +264,14 @@ const CreateRole: FC = ({ onClosing() { if (roleNameChanged || roleDescChanged) { return new Promise((resolve) => { - openConfirm( - 'Are you sure?', - 'Closing this will discard your unsaved changes.', - () => resolve(true), - () => resolve(false), - 'Ok', - 'Cancel', - ) + openConfirm({ + body: 'Closing this will discard your unsaved changes.', + noText: 'Cancel', + onNo: () => resolve(false), + onYes: () => resolve(true), + title: 'Discard changes', + yesText: 'Ok', + }) }) } else { return Promise.resolve(true) @@ -389,16 +389,16 @@ const CreateRole: FC = ({ const changed = ref.current.tabChanged() if (changed && newTab !== tab) { return new Promise((resolve) => { - openConfirm( - 'Are you sure?', - 'Changing this tab will discard your unsaved changes.', - () => { + openConfirm({ + body: 'Changing this tab will discard your unsaved changes.', + noText: 'Cancel', + onNo: () => resolve(false), + onYes: () => { resolve(true), setTab(newTab) }, - () => resolve(false), - 'Ok', - 'Cancel', - ) + title: 'Discard changes', + yesText: 'Ok', + }) }) } else { setTab(newTab) diff --git a/frontend/web/components/modals/base/Modal.tsx b/frontend/web/components/modals/base/Modal.tsx index 10b395a005b9..72c1d0dfb6a4 100644 --- a/frontend/web/components/modals/base/Modal.tsx +++ b/frontend/web/components/modals/base/Modal.tsx @@ -11,6 +11,7 @@ import Confirm from './ModalConfirm' import ModalDefault, { interceptClose, setInterceptClose } from './ModalDefault' import { getStore } from 'common/store' import { Provider } from 'react-redux' +import { OpenConfirm } from '../../../../global' export const ModalHeader = _ModalHeader export const ModalFooter = _ModalFooter @@ -52,17 +53,28 @@ const _Confirm = withModal(Confirm) const _ModalDefault2 = withModal(ModalDefault, { closePointer: 'closeModal2' }) const _ModalDefault = withModal(ModalDefault, { shouldInterceptClose: true }) -export const openConfirm = (global.openConfirm = ( - title: string, - body: ReactNode, - onYes?: () => void, - onNo?: () => void, -) => { +export const openConfirm = (global.openConfirm = ({ + body, + destructive, + noText, + onNo, + onYes, + title, + yesText, +}: OpenConfirm) => { document.getElementById('confirm') && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion unmountComponentAtNode(document.getElementById('confirm')!) render( - <_Confirm isOpen onNo={onNo} onYes={onYes} title={title}> + <_Confirm + isOpen + isDanger={destructive} + onNo={onNo} + onYes={onYes} + title={title} + yesText={yesText} + noText={noText} + > {body} , document.getElementById('confirm'), diff --git a/frontend/web/components/modals/base/ModalConfirm.tsx b/frontend/web/components/modals/base/ModalConfirm.tsx index 57dd133a9077..e29504df97c1 100644 --- a/frontend/web/components/modals/base/ModalConfirm.tsx +++ b/frontend/web/components/modals/base/ModalConfirm.tsx @@ -12,6 +12,7 @@ interface Confirm { onNo?: () => void noText?: string disabled?: boolean + destructive?: boolean disabledYes?: boolean yesText?: string toggle: () => void diff --git a/frontend/web/components/pages/AccountSettingsPage.js b/frontend/web/components/pages/AccountSettingsPage.js index cc9edb68854a..ba3306bd3dc4 100644 --- a/frontend/web/components/pages/AccountSettingsPage.js +++ b/frontend/web/components/pages/AccountSettingsPage.js @@ -68,22 +68,26 @@ class TheComponent extends Component { } invalidateToken = () => { - openConfirm( - 'Invalidate Token', -
- Invalidating your token will generate a new token to use with our API,{' '} - - your current token will no longer work - - . Performing this action will also log you out, are you sure you wish to - do this? -
, - () => { + openConfirm({ + body: ( +
+ Invalidating your token will generate a new token to use with our API,{' '} + + your current token will no longer work + + . Performing this action will also log you out, are you sure you wish + to do this? +
+ ), + destructive: true, + onYes: () => { _data.delete(`${Project.api}auth/token/`).then(() => { AppActions.logout() }) }, - ) + title: 'Invalidate Token', + yesText: 'Confirm', + }) } savePassword = (e) => { diff --git a/frontend/web/components/pages/ChangeRequestPage.js b/frontend/web/components/pages/ChangeRequestPage.js index e6357f933b79..0823f3043c81 100644 --- a/frontend/web/components/pages/ChangeRequestPage.js +++ b/frontend/web/components/pages/ChangeRequestPage.js @@ -100,17 +100,24 @@ const ChangeRequestsPage = class extends Component { componentDidMount = () => {} deleteChangeRequest = () => { - openConfirm( - 'Delete Change Request', -
Are you sure you want to delete this change request?
, - () => { + openConfirm({ + body: ( +
+ Are you sure you want to delete this change request? This action + cannot be undone. +
+ ), + destructive: true, + onYes: () => { AppActions.deleteChangeRequest(this.props.match.params.id, () => { this.context.router.history.replace( `/project/${this.props.match.params.projectId}/environment/${this.props.match.params.environmentId}/change-requests`, ) }) }, - ) + title: 'Delete Change Request', + yesText: 'Confirm', + }) } editChangeRequest = (projectFlag, environmentFlag) => { @@ -152,17 +159,18 @@ const ChangeRequestsPage = class extends Component { new Date().valueOf() const scheduledDate = moment(changeRequest.feature_states[0].live_from) - openConfirm( - `${isScheduled ? 'Schedule' : 'Publish'} Change Request`, -
- Are you sure you want to {isScheduled ? 'schedule' : 'publish'} this - change request - {isScheduled - ? ` for ${scheduledDate.format('Do MMM YYYY hh:mma')}` - : ''} - ? This will adjust the feature for your environment. -
, - () => { + openConfirm({ + body: ( +
+ Are you sure you want to {isScheduled ? 'schedule' : 'publish'} this + change request + {isScheduled + ? ` for ${scheduledDate.format('Do MMM YYYY hh:mma')}` + : ''} + ? This will adjust the feature for your environment. +
+ ), + onYes: () => { AppActions.actionChangeRequest( this.props.match.params.id, 'commit', @@ -175,7 +183,8 @@ const ChangeRequestsPage = class extends Component { }, ) }, - ) + title: `${isScheduled ? 'Schedule' : 'Publish'} Change Request`, + }) } fetchFeature = (featureId) => { diff --git a/frontend/web/components/pages/OrganisationSettingsPage.js b/frontend/web/components/pages/OrganisationSettingsPage.js index 4a0202b54f0b..165e045786c9 100644 --- a/frontend/web/components/pages/OrganisationSettingsPage.js +++ b/frontend/web/components/pages/OrganisationSettingsPage.js @@ -112,22 +112,34 @@ const OrganisationSettingsPage = class extends Component { } deleteInvite = (id) => { - openConfirm( - 'Delete Invite', -
Are you sure you want to delete this invite?
, - () => AppActions.deleteInvite(id), - ) + openConfirm({ + body: ( +
+ Are you sure you want to delete this invite? This action cannot be + undone. +
+ ), + destructive: true, + onYes: () => AppActions.deleteInvite(id), + title: 'Delete Invite', + yesText: 'Confirm', + }) } deleteUser = (id, userDisplayName) => { - openConfirm( - 'Remove User', -
- Are you sure you want to remove the user{' '} - {userDisplayName} from the organisation? -
, - () => AppActions.deleteUser(id), - ) + openConfirm({ + body: ( +
+ Are you sure you want to remove the user{' '} + {userDisplayName} from the organisation? This action + cannot be undone. +
+ ), + destructive: true, + onYes: () => AppActions.deleteUser(id), + title: 'Delete User', + yesText: 'Confirm', + }) } save = (e) => { @@ -1029,21 +1041,27 @@ const OrganisationSettingsPage = class extends Component { size='small' type='button' onClick={() => { - openConfirm( - 'Regenerate Invite Link', - 'This will generate a new invite link for the selected role, users will no longer be able to use the existing one. Are you sure?', - () => { - invalidateInviteLink( - inviteLinks.find( - (f) => - f.role === - this - .state - .role, - ), - ) - }, - ) + openConfirm({ + body: 'This will generate a new invite link for the selected role, users will no longer be able to use the existing one. Are you sure?', + onYes: + () => { + invalidateInviteLink( + inviteLinks.find( + ( + f, + ) => + f.role === + this + .state + .role, + ), + ) + }, + title: + 'Regenerate Invite Link', + yesText: + 'Confirm', + }) }} > Regenerate diff --git a/frontend/web/components/pages/ProjectSettingsPage.js b/frontend/web/components/pages/ProjectSettingsPage.js index 9a9755f7074f..735f2b95d2cf 100644 --- a/frontend/web/components/pages/ProjectSettingsPage.js +++ b/frontend/web/components/pages/ProjectSettingsPage.js @@ -379,13 +379,13 @@ const ProjectSettingsPage = class extends Component {