@@ -74,7 +75,7 @@ function LiveSettings({
))}
{intl.formatMessage(messages.providerHelperText, { providerName: 'Zoom' })}
- {liveData.piiSharingEnable ? (
+ {values.piiSharingEnable ? (
<>
{intl.formatMessage(messages.formInstructions)}
-
>
) : (
{intl.formatMessage(messages.requestPiiSharingEnable)}
diff --git a/src/pages-and-resources/live/data/api.js b/src/pages-and-resources/live/data/api.js
new file mode 100644
index 000000000..143ddddaa
--- /dev/null
+++ b/src/pages-and-resources/live/data/api.js
@@ -0,0 +1,42 @@
+/* eslint-disable import/prefer-default-export */
+import { camelCaseObject, ensureConfig, getConfig } from '@edx/frontend-platform';
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+
+ensureConfig([
+ 'STUDIO_BASE_URL',
+], 'Course Apps API service');
+
+const apiBaseUrl = getConfig().STUDIO_BASE_URL;
+
+const providersApiUrl = `${apiBaseUrl}/api/course_live/providers`;
+const providerConfigurationApiUrl = `${apiBaseUrl}/api/course_live/course`;
+
+/**
+ * Fetches providers for provided course
+ * @param {string} courseId
+ * @returns {Promise<[{}]>}
+ */
+export async function getLiveProviders(courseId) {
+ const { data } = await getAuthenticatedHttpClient()
+ .get(`${providersApiUrl}/${courseId}/`);
+ return camelCaseObject(data);
+}
+
+/**
+ * Fetches provider settings for provided course
+ * @param {string} courseId
+ * @returns {Promise<[{}]>}
+ */
+export async function getLiveConfiguration(courseId) {
+ const { data } = await getAuthenticatedHttpClient()
+ .get(`${providerConfigurationApiUrl}/${courseId}/`);
+ return camelCaseObject(data);
+}
+
+export async function postLiveConfiguration(courseId, config) {
+ const data = await getAuthenticatedHttpClient().post(
+ `${providerConfigurationApiUrl}/${courseId}/`,
+ config,
+ );
+ return camelCaseObject(data);
+}
diff --git a/src/pages-and-resources/live/data/slice.js b/src/pages-and-resources/live/data/slice.js
new file mode 100644
index 000000000..8bc6a7013
--- /dev/null
+++ b/src/pages-and-resources/live/data/slice.js
@@ -0,0 +1,49 @@
+/* eslint-disable no-param-reassign */
+import { createSlice } from '@reduxjs/toolkit';
+import { RequestStatus } from '../../../data/constants';
+
+const slice = createSlice({
+ name: 'live',
+ initialState: {
+ providers: {
+ available: {},
+ selectedProvider: {},
+ },
+ appIds: [],
+ status: RequestStatus.IN_PROGRESS,
+ configuration: {},
+ saveStatus: RequestStatus.SUCCESSFUL,
+ },
+ reducers: {
+ updateProviders: (state, { payload }) => {
+ Object.assign(state.providers, payload);
+ },
+ updateConfiguration: (state, { payload }) => {
+ Object.assign(state.configuration, payload);
+ state.configuredProvider = payload.provider;
+ },
+ updateStatus: (state, { payload }) => {
+ const { status } = payload;
+ state.status = status;
+ },
+ updateAppIds: (state, { payload }) => {
+ state.appIds = payload;
+ },
+ updateSaveStatus: (state, { payload }) => {
+ const { status } = payload;
+ state.saveStatus = status;
+ },
+ },
+});
+
+export const {
+ updateProviders,
+ updateConfiguration,
+ updateStatus,
+ updateSaveStatus,
+ updateAppIds,
+} = slice.actions;
+
+export const {
+ reducer,
+} = slice;
diff --git a/src/pages-and-resources/live/data/thunks.js b/src/pages-and-resources/live/data/thunks.js
new file mode 100644
index 000000000..f57021d5e
--- /dev/null
+++ b/src/pages-and-resources/live/data/thunks.js
@@ -0,0 +1,102 @@
+import { history } from '@edx/frontend-platform';
+import { getLiveConfiguration, getLiveProviders, postLiveConfiguration } from './api';
+import {
+ updateStatus, updateSaveStatus, updateProviders,
+ updateAppIds, updateConfiguration,
+} from './slice';
+import { RequestStatus } from '../../../data/constants';
+
+function normalizeLiveConfig(config) {
+ const configuration = {};
+ configuration.courseKey = config?.courseKey || '';
+ configuration.enabled = config?.enabled || false;
+ configuration.consumerKey = config?.ltiConfiguration?.lti1P1ClientKey || '';
+ configuration.consumerSecret = config?.ltiConfiguration?.lti1P1ClientSecret || '';
+ configuration.launchUrl = config?.ltiConfiguration?.lti1P1LaunchUrl || '';
+ configuration.launchEmail = config?.ltiConfiguration?.ltiConfig?.additionalParameters?.customInstructorEmail || '';
+ configuration.provider = config?.providerType || 'zoom';
+ configuration.piiSharingEnable = config?.piiSharingAllowed || false;
+ return configuration;
+}
+
+function deNormalizeLiveConfig(config) {
+ const configuration = {};
+ configuration.course_key = config.courseKey;
+ configuration.provider_type = config?.provider || 'zoom';
+ configuration.enabled = config?.enabled || false;
+ configuration.lti_configuration = {
+ lti_1p1_client_key: config?.consumerKey || '',
+ lti_1p1_client_secret: config?.consumerSecret || '',
+ lti_1p1_launch_url: config?.launchUrl || '',
+ version: 'lti_1p1',
+ lti_config: {
+ additional_parameters: {
+ custom_instructor_email: config?.launchEmail || '',
+ },
+ },
+ };
+ configuration.pii_sharing_allowed = config?.piiSharingEnable || false;
+ return configuration;
+}
+
+export function fetchLiveProviders(courseId) {
+ return async (dispatch) => {
+ const providers = await getLiveProviders(courseId);
+ dispatch(updateProviders(providers.providers));
+ const availableProvidersInfo = [];
+ Object.keys(providers.providers.available).forEach((key) => { availableProvidersInfo.push({ id: key }); });
+ dispatch(updateAppIds(availableProvidersInfo));
+ };
+}
+
+function updateLiveConfigurationState(config) {
+ return async (dispatch) => {
+ const data = normalizeLiveConfig(config);
+ dispatch(updateConfiguration(data));
+ };
+}
+
+export function fetchLiveConfiguration(courseId) {
+ return async (dispatch) => {
+ const config = await getLiveConfiguration(courseId);
+ dispatch(updateLiveConfigurationState(config));
+ };
+}
+
+export function fetchLiveData(courseId) {
+ return async (dispatch) => {
+ dispatch(updateStatus({ status: RequestStatus.IN_PROGRESS }));
+ try {
+ await dispatch(fetchLiveConfiguration(courseId));
+ await dispatch(fetchLiveProviders(courseId));
+ dispatch(updateStatus({ status: RequestStatus.SUCCESSFUL }));
+ } catch (error) {
+ if (error.response && error.response.status === 403) {
+ dispatch(updateStatus({ status: RequestStatus.DENIED }));
+ } else {
+ dispatch(updateStatus({ status: RequestStatus.FAILED }));
+ }
+ }
+ };
+}
+
+export function saveLiveConfiguration(courseId, config) {
+ return async (dispatch) => {
+ dispatch(updateSaveStatus({ status: RequestStatus.IN_PROGRESS }));
+ try {
+ const configuration = normalizeLiveConfig(
+ (await postLiveConfiguration(courseId, deNormalizeLiveConfig(config))).data,
+ );
+ dispatch(updateConfiguration(configuration));
+ dispatch(updateSaveStatus({ status: RequestStatus.SUCCESSFUL }));
+ history.push(`/course/${courseId}/pages-and-resources/`);
+ } catch (error) {
+ if (error.response && error.response.status === 403) {
+ dispatch(updateSaveStatus({ status: RequestStatus.DENIED }));
+ dispatch(updateStatus({ status: RequestStatus.DENIED }));
+ } else {
+ dispatch(updateSaveStatus({ status: RequestStatus.FAILED }));
+ }
+ }
+ };
+}
diff --git a/src/store.js b/src/store.js
index e023afd31..a4a53b9dd 100644
--- a/src/store.js
+++ b/src/store.js
@@ -4,6 +4,7 @@ import { reducer as modelsReducer } from './generic/model-store';
import { reducer as courseDetailReducer } from './data/slice';
import { reducer as discussionsReducer } from './pages-and-resources/discussions';
import { reducer as pagesAndResourcesReducer } from './pages-and-resources/data/slice';
+import { reducer as liveReducer } from './pages-and-resources/live/data/slice';
export default function initializeStore(preloadedState = undefined) {
return configureStore({
@@ -12,6 +13,7 @@ export default function initializeStore(preloadedState = undefined) {
discussions: discussionsReducer,
pagesAndResources: pagesAndResourcesReducer,
models: modelsReducer,
+ live: liveReducer,
},
preloadedState,
});