Skip to content

Commit

Permalink
Settings - Toggle Universal Link (#678)
Browse files Browse the repository at this point in the history
setting page: toggle universal link, deactivating individual links
  • Loading branch information
manny-m authored Jan 19, 2022
1 parent d338d1a commit 7b3605d
Show file tree
Hide file tree
Showing 16 changed files with 510 additions and 68 deletions.
16 changes: 15 additions & 1 deletion src/components/settings/SettingsAccessTab/ActionsTableCell.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { ActionRow, Button } from '@edx/paragon';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform/config';
import { logError } from '@edx/frontend-platform/logging';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';

import LinkDeactivationAlertModal from './LinkDeactivationAlertModal';
import LinkCopiedToast from './LinkCopiedToast';
import { SETTINGS_ACCESS_EVENTS } from '../../../eventTracking';

const ActionsTableCell = ({ row, onDeactivateLink }) => {
const ActionsTableCell = ({ row, onDeactivateLink, enterpriseUUID }) => {
const [isLinkDeactivationModalOpen, setIsLinkDeactivationModalOpen] = useState(false);
const [isCopyLinkToastOpen, setIsCopyLinkToastOpen] = useState(false);
const { isValid, uuid: inviteKeyUUID } = row.original;
Expand All @@ -29,12 +31,22 @@ const ActionsTableCell = ({ row, onDeactivateLink }) => {
} catch (error) {
logError(error);
}
sendEnterpriseTrackEvent(
enterpriseUUID,
SETTINGS_ACCESS_EVENTS.UNIVERSAL_LINK_COPIED,
{ invite_key_uuid: inviteKeyUUID },
);
};
addToClipboard();
};

const handleDeactivateClick = () => {
setIsLinkDeactivationModalOpen(true);
sendEnterpriseTrackEvent(
enterpriseUUID,
SETTINGS_ACCESS_EVENTS.UNIVERSAL_LINK_DEACTIVATE,
{ invite_key_uuid: inviteKeyUUID },
);
};

const closeLinkDeactivationModal = () => {
Expand Down Expand Up @@ -66,6 +78,7 @@ const ActionsTableCell = ({ row, onDeactivateLink }) => {
isOpen={isLinkDeactivationModalOpen}
onClose={closeLinkDeactivationModal}
onDeactivateLink={handleLinkDeactivated}
inviteKeyUUID={inviteKeyUUID}
/>
<LinkCopiedToast show={isCopyLinkToastOpen} onClose={handleCloseLinkCopyToast} />
</>
Expand All @@ -80,6 +93,7 @@ ActionsTableCell.propTypes = {
}),
}).isRequired,
onDeactivateLink: PropTypes.func,
enterpriseUUID: PropTypes.string.isRequired,
};

ActionsTableCell.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
import React, { useState } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import {
ActionRow,
AlertModal,
Alert,
Button,
StatefulButton,
} from '@edx/paragon';
import { logError } from '@edx/frontend-platform/logging';
import { Info } from '@edx/paragon/icons';

const DisableLinkManagementAlertModal = ({
isOpen,
onClose,
onDisableLinkManagement,
onDisable,
isLoading,
error,
}) => {
const [modalDisableButtonState, setModalDisableButtonState] = useState('default');

const handleClose = () => {
if (onClose) {
onClose();
}
};

const handleDisableLinkManagement = () => {
const disableLinkManagement = async () => {
setModalDisableButtonState('pending');
try {
// TODO: make legit API request
await new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
});
if (onDisableLinkManagement) {
onDisableLinkManagement();
}
} catch (error) {
logError(error);
} finally {
setModalDisableButtonState('default');
}
};
disableLinkManagement();
};
const modalDisableButtonState = isLoading ? 'pending' : 'default';

const disableButtonProps = {
labels: {
Expand All @@ -48,21 +25,27 @@ const DisableLinkManagementAlertModal = ({
},
state: modalDisableButtonState,
variant: 'primary',
onClick: handleDisableLinkManagement,
onClick: onDisable,
};

return (
<AlertModal
title="Are you sure?"
isOpen={isOpen}
onClose={handleClose}
onClose={onClose}
footerNode={(
<ActionRow>
<Button variant="tertiary" onClick={handleClose}>Go back</Button>
<StatefulButton {...disableButtonProps}>Disable</StatefulButton>
<Button disabled={isLoading} variant="tertiary" onClick={onClose}>Go back</Button>
<StatefulButton disabled={isLoading} {...disableButtonProps}>Disable</StatefulButton>
</ActionRow>
)}
>
{error && (
<Alert icon={Info} variant="danger" dismissible>
<Alert.Heading>Something went wrong</Alert.Heading>
There was an issue with your request, please try again.
</Alert>
)}
<p>
If you disable access via link, all links will be deactivated and your
learners will no longer have access. Links cannot be reactivated.
Expand All @@ -72,15 +55,16 @@ const DisableLinkManagementAlertModal = ({
};

DisableLinkManagementAlertModal.propTypes = {
isOpen: PropTypes.bool,
onClose: PropTypes.func,
onDisableLinkManagement: PropTypes.func,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onDisable: PropTypes.func.isRequired,
isLoading: PropTypes.bool,
error: PropTypes.bool,
};

DisableLinkManagementAlertModal.defaultProps = {
isOpen: false,
onClose: undefined,
onDisableLinkManagement: undefined,
isLoading: false,
error: false,
};

export default DisableLinkManagementAlertModal;
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
StatefulButton,
} from '@edx/paragon';
import { logError } from '@edx/frontend-platform/logging';
import LmsApiService from '../../../data/services/LmsApiService';

const LinkDeactivationAlertModal = ({
isOpen,
onClose,
onDeactivateLink,
inviteKeyUUID,
}) => {
const [deactivationState, setDeactivationState] = useState('default');

Expand All @@ -25,17 +27,12 @@ const LinkDeactivationAlertModal = ({
const deactivateLink = async () => {
setDeactivationState('pending');
try {
// TODO: make legit API request
await new Promise((resolve) => {
setTimeout(() => resolve(), 2000);
});
await LmsApiService.disableEnterpriseCustomerLink(inviteKeyUUID);
if (onDeactivateLink) {
onDeactivateLink();
}
} catch (error) {
logError(error);
} finally {
setDeactivationState('default');
}
};
deactivateLink();
Expand Down Expand Up @@ -77,6 +74,7 @@ LinkDeactivationAlertModal.propTypes = {
isOpen: PropTypes.bool,
onClose: PropTypes.func,
onDeactivateLink: PropTypes.func,
inviteKeyUUID: PropTypes.string.isRequired,
};

LinkDeactivationAlertModal.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
} from '@edx/paragon';
import moment from 'moment';
import { logError } from '@edx/frontend-platform/logging';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';

import LmsApiService from '../../../data/services/LmsApiService';
import { SettingsContext } from '../SettingsContext';
import { SETTINGS_ACCESS_EVENTS } from '../../../eventTracking';

const BUTTON_PROPS = {
labels: {
Expand Down Expand Up @@ -44,6 +46,10 @@ const SettingsAccessGenerateLinkButton = ({
logError(error);
} finally {
setLoadingLinkCreation(false);
sendEnterpriseTrackEvent(
enterpriseId,
SETTINGS_ACCESS_EVENTS.UNIVERSAL_LINK_GENERATE,
);
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useState } from 'react';
import React, { useState, useContext } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
DataTable,
Alert,
} from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import moment from 'moment';
import { logError } from '@edx/frontend-platform/logging';
import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils';

import { useLinkManagement } from '../data/hooks';
import SettingsAccessTabSection from './SettingsAccessTabSection';
Expand All @@ -14,15 +19,59 @@ import LinkTableCell from './LinkTableCell';
import UsageTableCell from './UsageTableCell';
import ActionsTableCell from './ActionsTableCell';
import DisableLinkManagementAlertModal from './DisableLinkManagementAlertModal';
import { updatePortalConfigurationEvent } from '../../../data/actions/portalConfiguration';
import LmsApiService from '../../../data/services/LmsApiService';
import { SettingsContext } from '../SettingsContext';
import { SETTINGS_ACCESS_EVENTS } from '../../../eventTracking';

const SettingsAccessLinkManagement = ({ enterpriseUUID }) => {
const SettingsAccessLinkManagement = ({
enterpriseUUID,
isUniversalLinkEnabled,
dispatch,
}) => {
const {
links,
loadingLinks,
refreshLinks,
} = useLinkManagement(enterpriseUUID);
const [isLinkManagementEnabled, setIsLinkManagementEnabled] = useState(true);

const {
customerAgreement: { netDaysUntilExpiration },
} = useContext(SettingsContext);

const [isLinkManagementAlertModalOpen, setIsLinkManagementAlertModalOpen] = useState(false);
const [isLoadingLinkManagementEnabledChange, setIsLoadingLinkManagementEnabledChange] = useState(false);
const [hasLinkManagementEnabledChangeError, setHasLinkManagementEnabledChangeError] = useState(false);

const toggleUniversalLink = async (newEnableUniversalLink) => {
setIsLoadingLinkManagementEnabledChange(true);
const args = {
enterpriseUUID,
enableUniversalLink: newEnableUniversalLink,
};

if (newEnableUniversalLink) {
args.expirationDate = moment().add(netDaysUntilExpiration, 'days').startOf('day').format();
}

try {
await LmsApiService.toggleEnterpriseCustomerUniversalLink(args);
dispatch(updatePortalConfigurationEvent({ enableUniversalLink: newEnableUniversalLink }));
setIsLinkManagementAlertModalOpen(false);
setHasLinkManagementEnabledChangeError(false);
refreshLinks();
} catch (error) {
logError(error);
setHasLinkManagementEnabledChangeError(true);
} finally {
sendEnterpriseTrackEvent(
enterpriseUUID,
SETTINGS_ACCESS_EVENTS.UNIVERSAL_LINK_TOGGLE,
{ toggle_to: newEnableUniversalLink },
);
setIsLoadingLinkManagementEnabledChange(false);
}
};

const handleLinkManagementCollapsibleToggled = (isOpen) => {
if (isOpen) {
Expand All @@ -38,32 +87,30 @@ const SettingsAccessLinkManagement = ({ enterpriseUUID }) => {
refreshLinks();
};

const handleLinkManagementAlertModalClose = () => {
setIsLinkManagementAlertModalOpen(false);
};

const handleLinkManagementDisabledSuccess = () => {
refreshLinks();
setIsLinkManagementEnabled(false);
setIsLinkManagementAlertModalOpen(false);
};

const handleLinkManagementFormSwitchChanged = (e) => {
const isChecked = e.target.checked;
if (isChecked) {
setIsLinkManagementEnabled(isChecked);
toggleUniversalLink(isChecked);
} else {
setIsLinkManagementAlertModalOpen(true);
}
};

return (
<>
{hasLinkManagementEnabledChangeError && !isLinkManagementAlertModalOpen && (
<Alert icon={Info} variant="danger" dismissible>
<Alert.Heading>Something went wrong</Alert.Heading>
There was an issue with your request, please try again.
</Alert>
)}
<SettingsAccessTabSection
title="Access via Link"
checked={isLinkManagementEnabled}
checked={isUniversalLinkEnabled}
onFormSwitchChange={handleLinkManagementFormSwitchChanged}
onCollapsibleToggle={handleLinkManagementCollapsibleToggled}
loading={isLoadingLinkManagementEnabledChange}
disabled={isLoadingLinkManagementEnabledChange}
>
<p>Generate a link to share with your learners.</p>
<DataTable
Expand All @@ -73,7 +120,7 @@ const SettingsAccessLinkManagement = ({ enterpriseUUID }) => {
tableActions={() => (
<SettingsAccessGenerateLinkButton
onSuccess={handleGenerateLinkSuccess}
disabled={!isLinkManagementEnabled}
disabled={!isUniversalLinkEnabled}
/>
)}
columns={[
Expand Down Expand Up @@ -102,7 +149,13 @@ const SettingsAccessLinkManagement = ({ enterpriseUUID }) => {
{
id: 'action',
Header: '',
Cell: props => <ActionsTableCell {...props} onDeactivateLink={handleDeactivatedLink} />,
Cell: props => (
<ActionsTableCell
{...props}
enterpriseUUID={enterpriseUUID}
onDeactivateLink={handleDeactivatedLink}
/>
),
},
]}
>
Expand All @@ -113,19 +166,24 @@ const SettingsAccessLinkManagement = ({ enterpriseUUID }) => {
</SettingsAccessTabSection>
<DisableLinkManagementAlertModal
isOpen={isLinkManagementAlertModalOpen}
onDisableLinkManagement={handleLinkManagementDisabledSuccess}
onClose={handleLinkManagementAlertModalClose}
onClose={() => { setIsLinkManagementAlertModalOpen(false); }}
onDisable={() => (toggleUniversalLink(false))}
isLoadingDisable={isLoadingLinkManagementEnabledChange}
error={hasLinkManagementEnabledChangeError}
/>
</>
);
};

const mapStateToProps = (state) => ({
enterpriseUUID: state.portalConfiguration.enterpriseId,
isUniversalLinkEnabled: state.portalConfiguration.enableUniversalLink,
});

SettingsAccessLinkManagement.propTypes = {
enterpriseUUID: PropTypes.string.isRequired,
isUniversalLinkEnabled: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired,
};

export default connect(mapStateToProps)(SettingsAccessLinkManagement);
Loading

0 comments on commit 7b3605d

Please sign in to comment.