feat: live app configuration's mockups (#261)

* feat: implement new mockups for live card
This commit is contained in:
Awais Ansari
2022-03-16 11:48:58 +05:00
committed by GitHub
parent abc7e018fc
commit 9c6f8546cf
9 changed files with 322 additions and 14 deletions

View File

@@ -36,10 +36,10 @@ function FieldFeedback({
}
FieldFeedback.propTypes = {
errorMessage: PropTypes.string.isRequired,
feedbackMessage: PropTypes.string.isRequired,
errorCondition: PropTypes.bool.isRequired,
feedbackCondition: PropTypes.bool.isRequired,
errorMessage: PropTypes.string,
feedbackMessage: PropTypes.string,
feedbackCondition: PropTypes.bool,
feedbackClasses: PropTypes.string,
transitionClasses: PropTypes.string,
};
@@ -47,6 +47,9 @@ FieldFeedback.propTypes = {
FieldFeedback.defaultProps = {
feedbackClasses: '',
transitionClasses: '',
feedbackMessage: '',
feedbackCondition: false,
errorMessage: '',
};
export default FieldFeedback;

View File

@@ -39,13 +39,19 @@ function FormikControl({
FormikControl.propTypes = {
name: PropTypes.element.isRequired,
label: PropTypes.element.isRequired,
help: PropTypes.element.isRequired,
className: PropTypes.string.isRequired,
label: PropTypes.element,
help: PropTypes.element,
className: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]).isRequired,
};
FormikControl.defaultProps = {
help: <></>,
label: <></>,
className: '',
};
export default FormikControl;

View File

@@ -21,6 +21,7 @@ const COURSE_APPS_ORDER = [
'wiki',
'calculator',
'proctoring',
'live',
'textbooks',
'custom_pages',
];

View File

@@ -153,7 +153,11 @@ function LtiConfigForm({ onSubmit, intl, formRef }) {
</div>
)}
</Form>
<AppExternalLinks externalLinks={externalLinks} providerName={providerName} />
<AppExternalLinks
externalLinks={externalLinks}
providerName={providerName}
customClasses="small text-muted"
/>
</Card>
);
}

View File

@@ -13,6 +13,8 @@ function AppExternalLinks({
externalLinks,
intl,
providerName,
showLaunchIcon,
customClasses,
}) {
const { contactEmail, ...links } = externalLinks;
const linkTypes = Object.keys(links).filter(key => links[key]);
@@ -24,12 +26,13 @@ function AppExternalLinks({
<AppConfigFormDivider />
<h4 className="pt-4">{intl.formatMessage(messages.linkTextHeading)}</h4>
{linkTypes.map((type) => (
<div key={type} className="small text-muted">
<div key={type}>
<Hyperlink
destination={externalLinks[type]}
target="_blank"
rel="noopener noreferrer"
showLaunchIcon={false}
showLaunchIcon={showLaunchIcon}
className={customClasses}
>
{ intl.formatMessage(messages[type], { providerName }) }
</Hyperlink>
@@ -38,7 +41,7 @@ function AppExternalLinks({
</>
) : null}
{contactEmail && (
<div className="small text-muted">
<div className={customClasses}>
<hr />
<FormattedMessage
{...messages.contact}
@@ -48,7 +51,7 @@ function AppExternalLinks({
to={contactEmail}
rel="noopener noreferrer"
>
{ contactEmail }
<span className={customClasses}>{ contactEmail }</span>
</MailtoLink>
),
}}
@@ -69,6 +72,13 @@ AppExternalLinks.propTypes = {
}).isRequired,
providerName: PropTypes.string.isRequired,
intl: intlShape.isRequired,
showLaunchIcon: PropTypes.bool,
customClasses: PropTypes.string,
};
AppExternalLinks.defaultProps = {
showLaunchIcon: false,
customClasses: '',
};
export default injectIntl(AppExternalLinks);

View File

@@ -7,9 +7,10 @@ export const filterItemFromObject = (array, key, value) => (
array.filter(item => item[key] !== value)
);
export const checkFieldErrors = (touched, errors, fieldPath, propertyName) => Boolean(
getIn(errors, `${fieldPath}.${propertyName}`) && getIn(touched, `${fieldPath}.${propertyName}`),
);
export const checkFieldErrors = (touched, errors, fieldPath, propertyName) => {
const path = fieldPath ? `${fieldPath}.${propertyName}` : propertyName;
return Boolean(getIn(errors, path) && getIn(touched, path));
};
export const errorExists = (errors, fieldPath, propertyName) => getIn(errors, `${fieldPath}.${propertyName}`);

View File

@@ -0,0 +1,128 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as Yup from 'yup';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { SelectableBox, Icon } from '@edx/paragon';
import { camelCase } from 'lodash';
import FormikControl from '../../generic/FormikControl';
import { useAppSetting } from '../../utils';
import AppExternalLinks from '../discussions/app-config-form/apps/shared/AppExternalLinks';
import AppSettingsModal from '../app-settings-modal/AppSettingsModal';
import iconsSrc from './constants';
import messages from './messages';
function LiveSettings({
intl,
onClose,
}) {
const [liveConfiguration, saveSettings] = useAppSetting('liveConfiguration');
const liveData = {
consumerKey: liveConfiguration?.consumerKey || '',
consumerSecret: liveConfiguration?.consumerSecret || '',
launchUrl: liveConfiguration?.launchUrl || '',
launchEmail: liveConfiguration?.launchEmail || '',
provider: liveConfiguration?.provider || 'zoom',
piiSharingEnable: liveConfiguration?.piiSharing
? liveConfiguration.piiShareUsername && liveConfiguration.piiShareEmail
: false,
};
const validationSchema = {
enabled: Yup.boolean(),
consumerKey: Yup.string().required(intl.formatMessage(messages.consumerKeyRequired)),
consumerSecret: Yup.string().required(intl.formatMessage(messages.consumerSecretRequired)),
launchUrl: Yup.string().required(intl.formatMessage(messages.launchUrlRequired)),
launchEmail: Yup.string().required(intl.formatMessage(messages.launchEmailRequired)),
};
const handleSettingsSave = async (values) => saveSettings(values);
const handleProviderChange = (selectedProvider, setFieldValue) => {
setFieldValue('provider', selectedProvider);
};
return (
<AppSettingsModal
appId="live"
title={intl.formatMessage(messages.heading)}
enableAppHelp={intl.formatMessage(messages.enableLiveHelp)}
enableAppLabel={intl.formatMessage(messages.enableLiveLabel)}
learnMoreText={intl.formatMessage(messages.enableLiveLink)}
onClose={onClose}
initialValues={liveData}
validationSchema={validationSchema}
onSettingsSave={handleSettingsSave}
configureBeforeEnable
>
{
({ values, setFieldValue }) => (
<>
<h4 className="my-3">{intl.formatMessage(messages.selectProvider)}</h4>
<SelectableBox.Set
type="checkbox"
value={values.provider}
onChange={(event) => handleProviderChange(event.target.value, setFieldValue)}
name="provider"
columns={3}
className="mb-3"
>
{[{ id: 'zoom' }, { id: 'google meet' }, { id: 'microsoft teams' }].map((app) => (
<SelectableBox value={app.id} type="checkbox" key={app.id}>
<div className="d-flex flex-column align-items-center">
<Icon src={iconsSrc[`${camelCase(app.id)}`]} alt={app.id} />
<span>{intl.formatMessage(messages[`appName-${camelCase(app.id)}`])}</span>
</div>
</SelectableBox>
))}
</SelectableBox.Set>
<p>{intl.formatMessage(messages.providerHelperText, { providerName: 'Zoom' })}</p>
{liveData.piiSharingEnable ? (
<>
<p className="pb-2">{intl.formatMessage(messages.formInstructions)}</p>
<FormikControl
name="consumerKey"
value={values.consumerKey}
floatingLabel={intl.formatMessage(messages.consumerKey)}
className="pb-1"
type="input"
/>
<FormikControl
name="consumerSecret"
value={values.consumerSecret}
floatingLabel={intl.formatMessage(messages.consumerSecret)}
className="pb-1"
type="input"
/>
<FormikControl
name="launchUrl"
value={values.launchUrl}
floatingLabel={intl.formatMessage(messages.launchUrl)}
className="pb-1"
type="input"
/>
<FormikControl
name="launchEmail"
value={values.launchEmail}
floatingLabel={intl.formatMessage(messages.launchEmail)}
type="input"
/>
<AppExternalLinks
externalLinks={liveConfiguration?.documentationLinks}
providerName="live"
showLaunchIcon
/>
</>
) : (
<p>{intl.formatMessage(messages.requestPiiSharingEnable)}</p>
)}
</>
)
}
</AppSettingsModal>
);
}
LiveSettings.propTypes = {
intl: intlShape.isRequired,
onClose: PropTypes.func.isRequired,
};
export default injectIntl(LiveSettings);

View File

@@ -0,0 +1,9 @@
import { GoogleMeet, MicrosoftTeams, Zoom } from '@edx/paragon/icons';
const iconsSrc = {
googleMeet: GoogleMeet,
microsoftTeams: MicrosoftTeams,
zoom: Zoom,
};
export { iconsSrc as default };

View File

@@ -0,0 +1,146 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
heading: {
id: 'authoring.pagesAndResources.live.enableLive.heading',
defaultMessage: 'Configure Live',
description: 'Heading for live configuration',
},
enableLiveLabel: {
id: 'authoring.pagesAndResources.live.enableLive.label',
defaultMessage: 'Live',
description: 'Title for configuration',
},
enableLiveHelp: {
id: 'authoring.pagesAndResources.live.enableLive.help',
defaultMessage: 'Schedule meetings and conduct live course sessions with learners.',
description: 'Tells the purpose of live configuration',
},
enableLiveLink: {
id: 'authoring.pagesAndResources.live.enableLive.link',
defaultMessage: 'Learn more about live',
description: 'Link text that tells the user to learn about the live',
},
saveButton: {
id: 'authoring.discussions.saveButton',
defaultMessage: 'Save',
description: 'Button allowing the user to submit their discussion configuration.',
},
savingButton: {
id: 'authoring.discussions.savingButton',
defaultMessage: 'Saving',
description: 'Button label when the discussion configuration is being submitted.',
},
savedButton: {
id: 'authoring.discussions.savedButton',
defaultMessage: 'Saved',
description: 'Button label when the discussion configuration has been successfully submitted.',
},
selectProvider: {
id: 'authoring.live.selectProvider',
defaultMessage: 'Select a video conferencing tool',
description: '',
},
formInstructions: {
id: 'authoring.live.formInstructions',
defaultMessage: 'Complete the fields below to set up your video conferencing tool.',
description: 'Instruction for configure the video conferencing tool.',
},
consumerKey: {
id: 'authoring.live.consumerKey',
defaultMessage: 'Consumer Key',
description: 'Label for the Consumer Key field.',
},
consumerKeyRequired: {
id: 'authoring.live.consumerKey.required',
defaultMessage: 'Consumer key is a required field',
description: 'Tells the user that the Consumer Key field is required and must have a value.',
},
consumerSecret: {
id: 'authoring.live.consumerSecret',
defaultMessage: 'Consumer Secret',
description: 'Label for the Consumer Secret field.',
},
consumerSecretRequired: {
id: 'authoring.live.consumerSecret.required',
defaultMessage: 'Consumer secret is a required field',
description: 'Tells the user that the Consumer Secret field is required and must have a value.',
},
launchUrl: {
id: 'authoring.live.launchUrl',
defaultMessage: 'Launch URL',
description: 'Label for the Launch URL field.',
},
launchUrlRequired: {
id: 'authoring.live.launchUrl.required',
defaultMessage: 'Launch URL is a required field',
description: 'Tells the user that the Launch URL field is required and must have a value.',
},
launchEmail: {
id: 'authoring.live.launchEmail',
defaultMessage: 'Launch Email',
description: 'Label for the Launch Email field.',
},
launchEmailRequired: {
id: 'authoring.live.launchEmail.required',
defaultMessage: 'Launch Email is a required field',
description: 'Tells the user that the Launch Email field is required and must have a value.',
},
providerHelperText: {
id: 'authoring.live.provider.helpText',
defaultMessage: 'This configuration will require sharing username and emails of learners and the course team with {providerName}',
description: 'Tells the user that sharing username and email is required for configuration',
},
requestPiiSharingEnable: {
id: 'authoring.live.requestPiiSharingEnable',
defaultMessage: 'Request your edX support team to enable the PII sharing for this course, in order to access the LTI configurations for a provider',
description: 'Tells the user that request edx support team to enable the PII sharing to access the LTO configuration for a provider.',
},
general: {
id: 'authoring.live.appDocInstructions.documentationLink',
defaultMessage: 'General documentation',
description: 'Application Document Instructions message for documentation link',
},
accessibility: {
id: 'authoring.live.appDocInstructions.accessibilityDocumentationLink',
defaultMessage: 'Accessibility documentation',
description: 'Application Document Instructions message for accessibility link',
},
configuration: {
id: 'authoring.live.appDocInstructions.configurationLink',
defaultMessage: 'Configuration documentation',
description: 'Application Document Instructions message for configurations link',
},
learnMore: {
id: 'authoring.live.appDocInstructions.learnMoreLink',
defaultMessage: 'Learn more about {providerName}',
description: 'Application Document Instructions message for learn more links',
},
linkTextHeading: {
id: 'authoring.live.appDocInstructions.linkTextHeading',
defaultMessage: 'External help and documentation',
description: 'External help and documentation heading',
},
linkText: {
id: 'authoring.live.appDocInstructions.linkText',
defaultMessage: '{link}',
description: 'link',
},
'appName-zoom': {
id: 'authoring.live.appName-yellowdig',
defaultMessage: 'Zoom',
description: 'The name of the Zoom app.',
},
'appName-googleMeet': {
id: 'authoring.live.appName-googleMeet',
defaultMessage: 'Google Meet',
description: 'The name of the Google Meet app.',
},
'appName-microsoftTeams': {
id: 'authoring.live.appName-microsoftTeams',
defaultMessage: 'Microsoft Teams',
description: 'The name of the Microsoft Teams app.',
},
});
export default messages;