Skip to content

Commit

Permalink
feat: add settings page zero state (#656)
Browse files Browse the repository at this point in the history
  • Loading branch information
manny-m authored Dec 16, 2021
1 parent 997113c commit 79647fc
Show file tree
Hide file tree
Showing 22 changed files with 648 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ FEATURE_SUPPORT=true
FEATURE_SAML_CONFIGURATION=true
FEATURE_EXTERNAL_LMS_CONFIGURATION=true
FEATURE_BULK_ENROLLMENT=true
FEATURE_SETTINGS_PAGE=true
FEATURE_SETTINGS_UNIVERSAL_LINK=true
HOTJAR_APP_ID=''
HOTJAR_VERSION=6
HOTJAR_DEBUG=''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@edx/paragon';
import PropTypes from 'prop-types';

import { configuration } from '../../../config';
import { configuration } from '../../config';

const ContactCustomerSupportButton = (props) => (
<Button
Expand Down
1 change: 1 addition & 0 deletions src/components/EnterpriseApp/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
export const ROUTE_NAMES = {
bulkEnrollment: 'enrollment',
subscriptionManagement: 'subscriptions',
settings: 'settings',
};
7 changes: 7 additions & 0 deletions src/components/EnterpriseApp/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ReportingConfig from '../ReportingConfig';
import NotFoundPage from '../NotFoundPage';
import ErrorPage from '../ErrorPage';
import LoadingMessage from '../LoadingMessage';
import SettingsPage from '../settings';
import { SubscriptionManagementPage } from '../subscriptions';
import { AnalyticsPage } from '../analytics';
import { removeTrailingSlash } from '../../utils';
Expand Down Expand Up @@ -227,6 +228,12 @@ class EnterpriseApp extends React.Component {
render={routeProps => <LmsConfigurations {...routeProps} />}
/>
)}
{features.SETTINGS_PAGE && features.EXTERNAL_LMS_CONFIGURATION && enableLmsConfigurationsScreen && (
<Route
path={`${baseUrl}/admin/${ROUTE_NAMES.settings}`}
component={SettingsPage}
/>
)}
<Route path="" component={NotFoundPage} />
</Switch>
</div>
Expand Down
9 changes: 8 additions & 1 deletion src/components/Sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import classNames from 'classnames';

import { faFile, faIdCard, faLifeRing } from '@fortawesome/free-regular-svg-icons';
import {
faCreditCard, faTags, faChartLine, faChartBar, faUniversity,
faCreditCard, faTags, faChartLine, faChartBar, faUniversity, faCog,
} from '@fortawesome/free-solid-svg-icons';

import IconLink from './IconLink';

import { configuration, features } from '../../config';
import { ROUTE_NAMES } from '../EnterpriseApp/constants';

class Sidebar extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -96,6 +97,12 @@ class Sidebar extends React.Component {
hidden: !features.EXTERNAL_LMS_CONFIGURATION || !enableLmsConfigurationsScreen,
},
// NOTE: keep "Support" link the last nav item
{
title: 'Settings',
to: `${baseUrl}/admin/${ROUTE_NAMES.settings}/`,
icon: faCog,
hidden: !(features.SETTINGS_PAGE && features.SAML_CONFIGURATION && enableLmsConfigurationsScreen),
},
{
title: 'Support',
to: configuration.ENTERPRISE_SUPPORT_URL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React from 'react';
import {
DataTable,
StatefulButton,
Button,
} from '@edx/paragon';

import { useLinkManagement } from '../data/hooks';
import SettingsAccessTabSection from './SettingsAccessTabSection';

const SettingsAccessLinkManagement = () => {
const {
links,
loadingLinks,
refreshLinks,
} = useLinkManagement();

const handleLinkManagementToggleChanged = () => {
// console.log(e.target.checked);
};

const handleCopyLink = () => {
// TODO: Handle Copy link to clipboard
// console.log('handleCopyLink', rowData);
};

const handleDeactivateLink = () => {
// TODO: Handle deactivate link
// console.log('handleDeactivateLink', rowData);
};

/**
* When we successfully create a link we should refresh links
*/
const handleGenerateLinkSuccess = () => {
refreshLinks();
};

// TODO: consider moving button to separate component
const generateLinkButtonProps = {
labels: {
default: 'Generate Link',
pending: 'Generating Link...',
complete: 'Link Generated',
error: 'Error',
},
state: 'default',
variant: 'primary',
};

// TODO: Make sure our data is being mapped correctly
const data = links;

return (
<SettingsAccessTabSection
title="Access via Link"
checked
onChange={handleLinkManagementToggleChanged}

>
<p>Generate a link to share with your learners.</p>

<DataTable
data={data}
itemCount={data.length}
// eslint-disable-next-line no-unused-vars
tableActions={(i) => (
// TODO: Consider making this its own component with logic
<StatefulButton
{...generateLinkButtonProps}
onClick={handleGenerateLinkSuccess}
/>
)}
columns={[
{
Header: 'Link',
accessor: 'link',
},
{
Header: 'Status',
accessor: 'linkStatus',
},
{
Header: 'Date created',
accessor: 'dateCreated',
},
{
Header: 'Usage',
accessor: 'usage',
},
]}
additionalColumns={[
{
id: 'action',
Header: '',
/* eslint-disable react/prop-types */
Cell: ({ row }) => {
if (row.original.linkStatus === 'activated') {
return (
<div className="d-flex flex-row-reverse">
<Button onClick={() => handleCopyLink(row)} variant="link">Copy</Button>
<Button onClick={() => handleDeactivateLink(row)} variant="link">Deactivate</Button>
</div>
);
}
return null;
},
/* eslint-enable */
},
]}
>
<DataTable.TableControlBar />
<DataTable.Table />
<DataTable.EmptyTable content={loadingLinks ? 'Loading...' : 'No links found'} />
</DataTable>
</SettingsAccessTabSection>

);
};

export default SettingsAccessLinkManagement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

import SettingsAccessTabSection from './SettingsAccessTabSection';

const SettingsAccessSSOManagement = () => {
// TODO: fetch feature setting and handle toggle
const FAKE_SETTING = true;
const handleSSOAccessToggleChanged = () => {
// console.log(e.target.checked);
};

return (
<SettingsAccessTabSection
title="Access via Single Sign-on"
checked={FAKE_SETTING}
onChange={handleSSOAccessToggleChanged}
>
<p>Give learners with Single Sign-On access to the catalog.</p>
</SettingsAccessTabSection>
);
};

export default SettingsAccessSSOManagement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
Collapsible,
Form,
} from '@edx/paragon';

const SettingsAccessTabSection = ({
title,
checked,
onChange,
children,
}) => {
const [isExpanded, setExpanded] = useState(true);
return (
<div className="mb-4">
<div className="d-flex flex-row-reverse mb-3">
<Form.Switch onChange={onChange} checked={checked}>Enable</Form.Switch>
</div>
<div>
<Collapsible
open={isExpanded}
onToggle={isOpen => setExpanded(isOpen)}
styling="card"
title={<p><strong>{title}</strong></p>}
>
{children}
</Collapsible>
</div>
</div>
);
};

SettingsAccessTabSection.propTypes = {
title: PropTypes.string.isRequired,
checked: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired,
children: PropTypes.node.isRequired,
};

export default SettingsAccessTabSection;
55 changes: 55 additions & 0 deletions src/components/settings/SettingsAccessTab/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import {
Container,
Col,
Row,
} from '@edx/paragon';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import { features } from '../../../config';
import ContactCustomerSupportButton from '../../ContactCustomerSupportButton';
import LinkManagement from './SettingsAccessLinkManagement';
import SSOManagement from './SettingsAccessSSOManagement';

const SettingsAccessTab = ({ learnerPortalEnabled }) => (
<Container fluid className="pl-0">
<Row>
<Col>
<h2>Enable browsing on-demand</h2>
</Col>
</Row>
<Row className="mb-4">
<Col>
<p>
Allow learners without a subsidy to browse the catalog and request enrollment to courses.
Browsing on demand will expire at the end of your latest subscription.
</p>
</Col>
<Col md="auto">
<ContactCustomerSupportButton variant="outline-primary">
Contact support
</ContactCustomerSupportButton>
</Col>
</Row>
{features.SETTINGS_UNIVERSAL_LINK && learnerPortalEnabled
&& (
<div className="mb-4">
<LinkManagement />
</div>
)}
<div className="mb-4">
<SSOManagement />
</div>
</Container>
);

const mapStateToProps = state => ({
learnerPortalEnabled: state.portalConfiguration.enableLearnerPortal,
});

SettingsAccessTab.propTypes = {
learnerPortalEnabled: PropTypes.bool.isRequired,
};

export default connect(mapStateToProps)(SettingsAccessTab);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import {
screen,
render,
cleanup,
act,
waitForElementToBeRemoved,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import SettingsAccessTabSection from '../SettingsAccessTabSection';

const generateProps = (checked, onChange) => ({
title: 'toggle me',
children: 'hide me',
checked,
onChange,
});

describe('<SettingsAccessTabSection />', () => {
afterEach(() => {
cleanup();
jest.clearAllMocks();
});

it('Clicking title ', async () => {
const props = generateProps(true, () => ({}));
render(<SettingsAccessTabSection {...props} />);
// is open by default
expect(screen.queryByText(props.children)).toBeTruthy();
// click on title
const titleArea = screen.getByText(props.title);
await act(async () => { userEvent.click(titleArea); });
// wait till its gone and assert
await waitForElementToBeRemoved(() => screen.queryByText(props.children));
expect(screen.queryByText(props.children)).toBeFalsy();
});
});
6 changes: 6 additions & 0 deletions src/components/settings/SettingsLMSTab/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';

// TODO: LMS TAB
const SettingsLMSTab = () => (<div>LMS tab belongs here</div>);

export default SettingsLMSTab;
Loading

0 comments on commit 79647fc

Please sign in to comment.