From 9c6f8546cf1ec4abdcef34f26ecd672f144317d3 Mon Sep 17 00:00:00 2001 From: Awais Ansari <79941147+awais-ansari@users.noreply.github.com> Date: Wed, 16 Mar 2022 11:48:58 +0500 Subject: [PATCH] feat: live app configuration's mockups (#261) * feat: implement new mockups for live card --- src/generic/FieldFeedback.jsx | 9 +- src/generic/FormikControl.jsx | 12 +- src/pages-and-resources/data/thunks.js | 1 + .../apps/lti/LtiConfigForm.jsx | 6 +- .../apps/shared/AppExternalLinks.jsx | 18 ++- .../discussions/app-config-form/utils.js | 7 +- src/pages-and-resources/live/Settings.jsx | 128 +++++++++++++++ src/pages-and-resources/live/constants.js | 9 ++ src/pages-and-resources/live/messages.js | 146 ++++++++++++++++++ 9 files changed, 322 insertions(+), 14 deletions(-) create mode 100644 src/pages-and-resources/live/Settings.jsx create mode 100644 src/pages-and-resources/live/constants.js create mode 100644 src/pages-and-resources/live/messages.js diff --git a/src/generic/FieldFeedback.jsx b/src/generic/FieldFeedback.jsx index 131814e6e..9776d8212 100644 --- a/src/generic/FieldFeedback.jsx +++ b/src/generic/FieldFeedback.jsx @@ -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; diff --git a/src/generic/FormikControl.jsx b/src/generic/FormikControl.jsx index 606f1989b..5fd33b5a0 100644 --- a/src/generic/FormikControl.jsx +++ b/src/generic/FormikControl.jsx @@ -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; diff --git a/src/pages-and-resources/data/thunks.js b/src/pages-and-resources/data/thunks.js index 34c2436b9..b6fa27404 100644 --- a/src/pages-and-resources/data/thunks.js +++ b/src/pages-and-resources/data/thunks.js @@ -21,6 +21,7 @@ const COURSE_APPS_ORDER = [ 'wiki', 'calculator', 'proctoring', + 'live', 'textbooks', 'custom_pages', ]; diff --git a/src/pages-and-resources/discussions/app-config-form/apps/lti/LtiConfigForm.jsx b/src/pages-and-resources/discussions/app-config-form/apps/lti/LtiConfigForm.jsx index 35ca6038e..fb5cee6d9 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/lti/LtiConfigForm.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/lti/LtiConfigForm.jsx @@ -153,7 +153,11 @@ function LtiConfigForm({ onSubmit, intl, formRef }) { )} - + ); } diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/AppExternalLinks.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/AppExternalLinks.jsx index 2074a8420..855224511 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/AppExternalLinks.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/AppExternalLinks.jsx @@ -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({

{intl.formatMessage(messages.linkTextHeading)}

{linkTypes.map((type) => ( -
+
{ intl.formatMessage(messages[type], { providerName }) } @@ -38,7 +41,7 @@ function AppExternalLinks({ ) : null} {contactEmail && ( -
+

- { contactEmail } + { contactEmail } ), }} @@ -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); diff --git a/src/pages-and-resources/discussions/app-config-form/utils.js b/src/pages-and-resources/discussions/app-config-form/utils.js index e521aab98..6e33efdf4 100644 --- a/src/pages-and-resources/discussions/app-config-form/utils.js +++ b/src/pages-and-resources/discussions/app-config-form/utils.js @@ -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}`); diff --git a/src/pages-and-resources/live/Settings.jsx b/src/pages-and-resources/live/Settings.jsx new file mode 100644 index 000000000..986fa59d5 --- /dev/null +++ b/src/pages-and-resources/live/Settings.jsx @@ -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 ( + + { + ({ values, setFieldValue }) => ( + <> +

{intl.formatMessage(messages.selectProvider)}

+ handleProviderChange(event.target.value, setFieldValue)} + name="provider" + columns={3} + className="mb-3" + > + {[{ id: 'zoom' }, { id: 'google meet' }, { id: 'microsoft teams' }].map((app) => ( + +
+ + {intl.formatMessage(messages[`appName-${camelCase(app.id)}`])} +
+
+ ))} +
+

{intl.formatMessage(messages.providerHelperText, { providerName: 'Zoom' })}

+ {liveData.piiSharingEnable ? ( + <> +

{intl.formatMessage(messages.formInstructions)}

+ + + + + + + ) : ( +

{intl.formatMessage(messages.requestPiiSharingEnable)}

+ )} + + ) + } +
+ ); +} + +LiveSettings.propTypes = { + intl: intlShape.isRequired, + onClose: PropTypes.func.isRequired, +}; + +export default injectIntl(LiveSettings); diff --git a/src/pages-and-resources/live/constants.js b/src/pages-and-resources/live/constants.js new file mode 100644 index 000000000..cf8a2094b --- /dev/null +++ b/src/pages-and-resources/live/constants.js @@ -0,0 +1,9 @@ +import { GoogleMeet, MicrosoftTeams, Zoom } from '@edx/paragon/icons'; + +const iconsSrc = { + googleMeet: GoogleMeet, + microsoftTeams: MicrosoftTeams, + zoom: Zoom, +}; + +export { iconsSrc as default }; diff --git a/src/pages-and-resources/live/messages.js b/src/pages-and-resources/live/messages.js new file mode 100644 index 000000000..636a8e7de --- /dev/null +++ b/src/pages-and-resources/live/messages.js @@ -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;