Skip to content

Commit

Permalink
feat: add approve/decline subsidy request modals
Browse files Browse the repository at this point in the history
  • Loading branch information
long74100 committed Mar 2, 2022
1 parent 269eadb commit a2186fa
Show file tree
Hide file tree
Showing 16 changed files with 1,331 additions and 1 deletion.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NODE_ENV='production'
USE_API_CACHE=true
4 changes: 3 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ DATA_API_BASE_URL='http://localhost:8000'
ECOMMERCE_BASE_URL='http://localhost:18130'
LICENSE_MANAGER_BASE_URL='http://localhost:18170'
DISCOVERY_BASE_URL='http://localhost:18381'
ENTERPRISE_CATALOG_BASE_URL='http://localhost:18160'
ENTERPRISE_ACCESS_BASE_URL='http://localhost:18270'
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:8734'
ENTERPRISE_SUPPORT_URL='https://edx.org'
ENTERPRISE_SUPPORT_REVOKE_LICENSE_URL='https://edx.org'
Expand Down Expand Up @@ -42,4 +44,4 @@ IS_MAINTENANCE_ALERT_ENABLED=''
MAINTENANCE_ALERT_MESSAGE=''
MAINTENANCE_ALERT_START_TIMESTAMP=''
TABLEAU_URL='https://enterprise-tableau.edx.org'

USE_API_CACHE=true
2 changes: 2 additions & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ ECOMMERCE_BASE_URL='http://localhost:8000'
LICENSE_MANAGER_BASE_URL='http://localhost:18170'
ENTERPRISE_LEARNER_PORTAL_URL='http://localhost:8734'
DISCOVERY_BASE_URL='http://localhost:18381'
ENTERPRISE_CATALOG_BASE_URL='http://localhost:18160'
ENTERPRISE_ACCESS_BASE_URL='http://localhost:18270'
LOGO_URL='https://edx-cdn.org/v3/prod/logo.svg'
LOGO_TRADEMARK_URL='https://edx-cdn.org/v3/default/logo-trademark.svg'
LOGO_WHITE_URL='https://edx-cdn.org/v3/default/logo-white.svg'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import React, {
useState, useMemo, useCallback, useEffect,
} from 'react';
import PropTypes from 'prop-types';
import {
ModalDialog, ActionRow, Button,
Alert,
Form,
StatefulButton,
} from '@edx/paragon';
import { connect } from 'react-redux';
import { Info } from '@edx/paragon/icons';
import { logError } from '@edx/frontend-platform/logging';
import Skeleton from 'react-loading-skeleton';
import { useApplicableCoupons } from './data/hooks';
import EnterpriseAccessApiService from '../../data/services/EnterpriseAccessApiService';
import { formatTimestamp } from '../../utils';

export const ApproveCouponCodeRequestModal = ({
enterpriseId,
couponCodeRequest: {
uuid,
courseId,
},
coupons,
isOpen,
onSuccess,
onClose,
}) => {
const {
applicableCoupons,
isLoading: isLoadingApplicableCoupons,
error: loadApplicableCouponsError,
} = useApplicableCoupons({
enterpriseId,
courseRunIds: [courseId],
coupons,
});
const [selectedCouponId, setSelectedCouponId] = useState();
const [isApprovingRequest, setIsApprovingRequest] = useState(false);
const [approveRequestError, setApproveRequestError] = useState(undefined);

const hasError = loadApplicableCouponsError || approveRequestError;
const isApprovalButtonDisabled = !applicableCoupons.length > 0
|| !selectedCouponId || isLoadingApplicableCoupons || isApprovingRequest;

const buttonState = useMemo(() => {
if (approveRequestError) {
return 'errored';
}

if (isApprovingRequest) {
return 'pending';
}

return 'default';
}, [isApprovingRequest, approveRequestError]);

// If there is only one choice, automatically select it
useEffect(() => {
if (applicableCoupons.length === 1) {
setSelectedCouponId(applicableCoupons[0].id);
}
}, [applicableCoupons.length]);

const approveCouponCodeRequest = useCallback(async () => {
setIsApprovingRequest(true);
try {
await EnterpriseAccessApiService.approveCouponCodeRequest({
couponCodeRequestUUIDs: [uuid],
couponId: selectedCouponId,
});
onSuccess();
} catch (err) {
logError(err);
setApproveRequestError(err);
} finally {
setIsApprovingRequest(false);
}
}, [onSuccess, selectedCouponId]);

return (
<ModalDialog
className="subsidy-request-modal"
title="Approve Coupon Code Request"
isOpen={isOpen}
hasCloseButton
onClose={onClose}
>
<Form>
<ModalDialog.Header>
<ModalDialog.Title>
Code Assignment
</ModalDialog.Title>
{hasError && (
<Alert
className="mt-3"
icon={Info}
variant="danger"
data-testid="approve-coupon-code-request-modal-error-alert"
>
<Alert.Heading>Something went wrong</Alert.Heading>
Please try again later.
</Alert>
)}
</ModalDialog.Header>
<ModalDialog.Body>
{isLoadingApplicableCoupons && (
<div data-testid="approve-coupon-code-request-modal-skeleton">
<Skeleton count={2} />
<span className="sr-only">Loading coupon choices...</span>
</div>
)}
{applicableCoupons.length > 1 && (
<>
<p>
Please choose a coupon from which to allocate a code.
</p>
<Form.Group>
<Form.RadioSet
name="coupon-choices"
onChange={(e) => setSelectedCouponId(e.target.value)}
>
{applicableCoupons.map((coupon, index) => (
<Form.Radio
className="mb-1"
value={coupon.id}
data-testid={`approve-coupon-code-request-modal-coupon-${index}`}
key={coupon.id}
description={`Expires on ${formatTimestamp({ timestamp: coupon.endDate })}`}
>
<strong>
{coupon.title}{' '}
({coupon.numUnassigned} of {coupon.maxUses} remaining)
</strong>
</Form.Radio>
))}
</Form.RadioSet>
</Form.Group>
</>
)}
{applicableCoupons.length > 0 && (
<p>
<strong>Please note:</strong>{' '}
Learners can apply this code to any course, not just the one they requested.
</p>
)}
{!isLoadingApplicableCoupons && applicableCoupons.length === 0 && (
<Alert
icon={Info}
variant="danger"
data-testid="approve-coupon-code-request-modal-no-coupons-alert"
>
<Alert.Heading>No applicable coupons</Alert.Heading>
You do not have a coupon that can be allocated for this request.
</Alert>
)}
</ModalDialog.Body>
<ModalDialog.Footer>
<ActionRow>
<Button variant="tertiary" onClick={onClose}>
Close
</Button>
<StatefulButton
state={buttonState}
variant="primary"
labels={{
default: 'Approve',
pending: 'Approving...',
errored: 'Try again',
}}
onClick={approveCouponCodeRequest}
disabled={isApprovalButtonDisabled}
data-testid="approve-coupon-code-request-modal-approve-btn"
/>
</ActionRow>
</ModalDialog.Footer>
</Form>
</ModalDialog>
);
};

ApproveCouponCodeRequestModal.propTypes = {
enterpriseId: PropTypes.string.isRequired,
couponCodeRequest: PropTypes.shape({
uuid: PropTypes.string.isRequired,
courseId: PropTypes.string.isRequired,
}).isRequired,
isOpen: PropTypes.bool.isRequired,
onSuccess: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
coupons: PropTypes.shape({
results: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number,
endDate: PropTypes.string,
title: PropTypes.string,
numUnassigned: PropTypes.number,
maxUses: PropTypes.number,
})),
}).isRequired,
};

const mapStateToProps = state => ({
enterpriseId: state.portalConfiguration.enterpriseId,
coupons: state.coupons.data,
});

export default connect(mapStateToProps)(ApproveCouponCodeRequestModal);
Loading

0 comments on commit a2186fa

Please sign in to comment.