-
-
-
-
- Free trial, then upgrade course for full access to Xpert features.
-
+const Disclosure = ({ children }) => {
+ const { upgradeable, upgradeUrl, auditTrialLengthDays } = useCourseUpgrade();
+ const { track } = useTrackEvent();
+
+ const handleClick = () => track('edx.ui.lms.learning_assistant.message');
+ const freeDays = auditTrialLengthDays === 1 ? '1 day' : `${auditTrialLengthDays} days`;
+
+ return (
+
+
+ Xpert Learning Assistant
+
+
+
An AI-powered educational tool
+
+
+
+
+ Understand a concept
+ “How does photosynthesis work?”
+
+
+
+
+
+ Summarize your learning
+ “Can you help me review pivot tables?”
+
-
Upgrade now
+ {upgradeable ? (
+
+
+
+
+
+ Free for {freeDays}, then upgrade course for full access to Xpert features.
+
+
+
+ Upgrade now
+
+
+
+ ) : null}
+
+ Note: This chat is AI generated, mistakes are possible.
+ By using it you agree that edX may create a record of this chat.
+ Your personal data will be used as described in our
+
+ privacy policy
+
+ .
+
- ) : null}
-
- Note: This chat is AI generated, mistakes are possible.
- By using it you agree that edX may create a record of this chat.
- Your personal data will be used as described in our
-
- privacy policy
-
- .
-
- {children}
-
-);
+
+
+ );
+};
Disclosure.propTypes = {
- showTrial: PropTypes.bool,
children: PropTypes.node.isRequired,
};
-Disclosure.defaultProps = {
- showTrial: false,
-};
-
export default Disclosure;
diff --git a/src/components/MessageForm/index.jsx b/src/components/MessageForm/index.jsx
index b86353bd..69c3c23f 100644
--- a/src/components/MessageForm/index.jsx
+++ b/src/components/MessageForm/index.jsx
@@ -57,7 +57,7 @@ const MessageForm = ({ courseId, shouldAutofocus, unitId }) => {
);
return (
-
- {disclosureAcknowledged ? (getSidebar()) : ({getMessageForm()} )}
+ {disclosureAcknowledged
+ ? (getSidebar())
+ : ({getMessageForm()} )}
)
);
diff --git a/src/context/course-info-context.js b/src/context/course-info-context.js
new file mode 100644
index 00000000..107dd0df
--- /dev/null
+++ b/src/context/course-info-context.js
@@ -0,0 +1,9 @@
+import { createContext } from 'react';
+
+export const CourseInfoContext = createContext('course-info', {
+ courseId: null,
+ unitId: null,
+ isUpgradeEligible: false,
+});
+
+export const CourseInfoProvider = CourseInfoContext.Provider;
diff --git a/src/context/index.js b/src/context/index.js
new file mode 100644
index 00000000..dca05b8c
--- /dev/null
+++ b/src/context/index.js
@@ -0,0 +1 @@
+export * from './course-info-context';
diff --git a/src/data/slice.js b/src/data/slice.js
index 91982422..fc64c47e 100644
--- a/src/data/slice.js
+++ b/src/data/slice.js
@@ -12,6 +12,7 @@ export const initialState = {
sidebarIsOpen: false,
isEnabled: false,
auditTrial: {},
+ auditTrialLengthDays: null,
};
export const learningAssistantSlice = createSlice({
@@ -48,6 +49,9 @@ export const learningAssistantSlice = createSlice({
setAuditTrial: (state, { payload }) => {
state.auditTrial = payload;
},
+ setAuditTrialLengthDays: (state, { payload }) => {
+ state.auditTrialLengthDays = payload;
+ },
},
});
@@ -62,6 +66,7 @@ export const {
setSidebarIsOpen,
setIsEnabled,
setAuditTrial,
+ setAuditTrialLengthDays,
} = learningAssistantSlice.actions;
export const {
diff --git a/src/data/thunks.js b/src/data/thunks.js
index 2bf2528a..31d71844 100644
--- a/src/data/thunks.js
+++ b/src/data/thunks.js
@@ -17,6 +17,7 @@ import {
setSidebarIsOpen,
setIsEnabled,
setAuditTrial,
+ setAuditTrialLengthDays,
} from './slice';
import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY } from './optimizely';
@@ -134,6 +135,8 @@ export function getLearningAssistantChatSummary(courseId) {
if (Object.keys(auditTrial).length !== 0) {
dispatch(setAuditTrial(auditTrial));
}
+
+ if (data.audit_trial_length_days) { dispatch(setAuditTrialLengthDays(data.audit_trial_length_days)); }
} catch (error) {
dispatch(setApiError());
}
diff --git a/src/hooks/index.js b/src/hooks/index.js
new file mode 100644
index 00000000..22f1a63c
--- /dev/null
+++ b/src/hooks/index.js
@@ -0,0 +1,3 @@
+/* eslint-disable import/prefer-default-export */
+export { default as useCourseUpgrade } from './use-course-upgrade';
+export { default as useTrackEvent } from './use-track-event';
diff --git a/src/hooks/use-course-upgrade.js b/src/hooks/use-course-upgrade.js
new file mode 100644
index 00000000..f5544890
--- /dev/null
+++ b/src/hooks/use-course-upgrade.js
@@ -0,0 +1,39 @@
+import { useContext } from 'react';
+import { useModel } from '@src/generic/model-store'; // eslint-disable-line import/no-unresolved
+import { useSelector } from 'react-redux';
+import { CourseInfoContext } from '../context';
+
+const millisecondsInOneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
+
+export default function useCourseUpgrade() {
+ const { courseId, isUpgradeEligible } = useContext(CourseInfoContext);
+ const { offer } = useModel('coursewareMeta', courseId);
+ const { verifiedMode } = useModel('courseHomeMeta', courseId);
+ const {
+ auditTrialLengthDays,
+ auditTrial,
+ } = useSelector(state => state.learningAssistant);
+
+ const upgradeUrl = offer?.upgradeUrl || verifiedMode?.upgradeUrl;
+
+ if (!isUpgradeEligible || !upgradeUrl) { return { upgradeable: false }; }
+
+ let auditTrialExpired = false;
+ let auditTrialDaysRemaining;
+
+ if (auditTrial?.expirationDate) {
+ const auditTrialExpirationDate = new Date(auditTrial.expirationDate);
+ auditTrialDaysRemaining = Math.ceil((auditTrialExpirationDate - Date.now()) / millisecondsInOneDay);
+
+ auditTrialExpired = auditTrialDaysRemaining < 0;
+ }
+
+ return {
+ upgradeable: true,
+ auditTrialLengthDays,
+ auditTrialDaysRemaining,
+ auditTrialExpired,
+ auditTrial,
+ upgradeUrl,
+ };
+}
diff --git a/src/hooks/use-track-event.js b/src/hooks/use-track-event.js
new file mode 100644
index 00000000..7205326c
--- /dev/null
+++ b/src/hooks/use-track-event.js
@@ -0,0 +1,20 @@
+import { useContext } from 'react';
+import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
+import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+import { CourseInfoContext } from '../context';
+
+export default function useTrackEvent() {
+ const { courseId, moduleId } = useContext(CourseInfoContext);
+ const { userId } = getAuthenticatedUser();
+
+ const track = (event, details) => {
+ sendTrackEvent(event, {
+ course_id: courseId,
+ user_id: userId,
+ module_id: moduleId,
+ ...details,
+ });
+ };
+
+ return { track };
+}
diff --git a/src/widgets/Xpert.jsx b/src/widgets/Xpert.jsx
index a8a6148a..48cc8283 100644
--- a/src/widgets/Xpert.jsx
+++ b/src/widgets/Xpert.jsx
@@ -1,19 +1,24 @@
import PropTypes from 'prop-types';
-import { useEffect } from 'react';
+import { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { updateSidebarIsOpen, getLearningAssistantChatSummary } from '../data/thunks';
import ToggleXpert from '../components/ToggleXpertButton';
import Sidebar from '../components/Sidebar';
import { ExperimentsProvider } from '../experiments';
+import { CourseInfoProvider } from '../context';
const Xpert = ({
courseId,
contentToolsEnabled,
unitId,
- isUpgradeEligible, // eslint-disable-line no-unused-vars
+ isUpgradeEligible,
}) => {
const dispatch = useDispatch();
+ const courseInfo = useMemo(
+ () => ({ courseId, unitId, isUpgradeEligible }),
+ [courseId, unitId, isUpgradeEligible],
+ );
const {
isEnabled,
@@ -40,22 +45,24 @@ const Xpert = ({
};
return isEnabled ? (
-
- <>
-
-
- >
-
+
+
+ <>
+
+
+ >
+
+
) : null;
};