Self-learning Module
self-learning module
A module providing UI components and helper utilities to integrate self-learning functionality.
Works against commercetools' learning-api, which is not a part of this open source project.
Public Components
The following components are the ones available for editor use directly in mdx files.
Quiz
Quiz component can be used in any mdx
page. It's responsible for fetching, rendering and handling all the interaction logic of a quiz answer submission.
The component simply renders a login CTA if the user is not logged in, otherwise the quiz is rendered.
The component expects 2 mandatory props:
courseId
: Id of the course defined in the LMSquizId
: Id of the quiz defined in the LMS
Example
<Quiz courseId="5565" quizId="85065"/>
CourseCard
Quiz card is a card component displaying information about a specific course. It supports logged in and logged out state. When the user is logged in, the course status is displayed.
The component has the following mandatory props:
title
: The course title.href
: Relative path to the first course pagecourseId
: The courseIdduration
: A string giving information about course duration
Example
<CourseCard duration="30 min" title="Course name" href="self-learning/overview" courseId="66">Course description. Introduction to extensibility possibilities available in Composable Commerce.</CourseCard>
LearningPathCard
Learning path card is a card component displaying information about a learning path.
The component has the following mandatory props:
title
: The learning path title.duration
: A string giving information about learning path durationproductName
: A string representing the product the learning path is referring to
Example
<LearningPathCard title="Overview" duration="1 hour 25 minutes | 7 courses" productName="Composable Commerce">This learning path is great for Composable Commerce administrators who create/maintain e-commerce data points, primarily work with a user interface, and have some familiarity with APIs.</LearningPathCard>
IfUserLoggedIn / IfUserLoggedOut
These components are used to wrap content that should only be displayed if the user is logged in or logged out respectively.
The components have 1 optional prop.
assumeTrue
: boolean, if set totrue
while auth0 is still loading the logged in/logged out user state, the component will render the content regardless.
Examples
<IfUserLoggedIn assumeTrue>## Welcome back!</IfUserLoggedIn>
<IfUserLoggedOut>## Please log in</IfUserLoggedOut>
FirstName
This component simply render the first name of the logged in user, if the user is logged out, nothing is rendered.
Example
Hello <FirstName />, welcome back
Learing path card with image on the side
In order to add an image on the side of a learning path card use the following pattern (the image must have been previously added to the /content/files directory)
<Cards><LearningPathCard title="Overview" duration="1 hour 25 minutes | 7 courses" productName="Composable Commerce"> Description here</LearningPathCard><ImageCard>![learning path](/content/files/file-name.svg)</ImageCard></Cards>
IfLearningPathComplete / IfLearningPathNotComplete
These components are used to wrap content that should only be/not be displayed if the user completed the learning path
Examples
<IfLearningPathComplete>Congratulations, you completed learning path</IfLearningPathComplete>
<IfLearningPathNotComplete>Keep learning!</IfLearningPathNotComplete>
Internal Components
The following components are internal, used by docs-kit developers to provide the functionalities needed in the public components described above. The purpose of this section is to create an internal reference for new developers involved in the self-learning project.
useAuthToken (hook)
This hook is a wrapper aroung Auth0 sdk and it's used to get a valid Auth0 authentication token for the current logged in user.
It exposes getAuthToken
function which can be used in the way described in the following example.
const { getAuthToken } = useAuthToken();// obtain the accessTokenconst accessToken = await getAuthToken();// ...then performs fetch passing the token as Authorization headerconst response = await fetch(`http://exampleapi.com/user`, {'POST',headers: {Accept: 'application/json',Authorization: `Bearer ${accessToken}`,},}
A typical use of this hook is to get a valid authorization token just before performing an API call against the learning api. The token value is usually passed as
Authorizaton
header to the API.
useUpdateUser (hook)
This hook can be used to update user profile data such as first name, family name or company. It needs to be initialized with an object containing Auth0 userId of the user we want to update.
It exposes the following properties:
performUpdateUser
: The main function to be invoked when we want to update user profile. It recieves a mandatory Auth0User
parameter that represents the new user profile for the user. Only the properties specified in the user object will be updated, the others will remain untouched.isLoading
: A boolean value indicating if the update operation is still in progress (iftrue
).error
: In case of error, this property will contain error information. The format of error might depend on its type.correlationId
: If the API returns a response including aX-Correlation-ID
value, this property gets its value.updatedUser
: If the update operation completes successfully, this property will contain the full updatedUser
object.
Example:
const { performUpdateUser, isLoading, updatedUser, error } = useUpdateUser({userId: profile?.user_id || '',});onSubmit: async (formikValues: TProfileFormValues) => {const updatedUserBody = {family_name: 'Updated name',given_name: 'Updated family name',company: 'Updated company',};performUpdateUser(updatedUserBody);},
useAttempt (hook)
This hook interacts with Moodle LMS through the learning API. In particular the purpose of this hook is to be able to retrieve an existing in progress quiz attempt for the currently logged in user with the specified course and quiz or create a new one in case no in progress attempts exist. The hook must be initialized with an object with the following structure:
{courseId: '516' // the Moodle course IDquizId: '13' // the Moodle quiz ID}
It exposes the following properties:
fetchAttempt
: The main function, it takes an optional booleanforceNew
parameter. If that is passedtrue
, the function will try to create and fetch a new attempt regardless of the fact that an existing in progress attempt might exist.isLoading
: A boolean value indicating if the fetch operation is still in progress (iftrue
).error
: In case of error, this property will contain error information. The format of error might depend on its type.correlationId
: If the API returns a response including aX-Correlation-ID
value, this property gets its value.attempt
: If the update operation completes successfully, this property will contain the value of the requested attempt withQuizAttempt
type.
Example:
const {fetchAttempt,attempt: quizData,isLoading,error,correlationId,} = useAttempt({courseId: '516', quizId: '13'});fetchAttempt(false); // do not force new attemptuseEffect(() => {handleAttempt(attempt); // perform operation as soon as attempt data is available}, [attempt]);
useSubmitAttempt (hook)
This hook interacts with Moodle LMS through the learning API. This hook is used to submit a quiz attempt for the current logged in user and the specified courseId and quizId. The hook must be initialized with an object with the following structure:
{courseId: '516' // the Moodle course IDquizId: '13' // the Moodle quiz ID}
It exposes the following properties:
submitAttempt
: The main function, it takes three parameters:attemptId
: The integer representing the id of the Moodle attempt.attemptData
: An object of typeSubmissionAttempt
containing the information about the attempt itself.finish
: A boolean which dictates if the attempt should be considered finished after submission.
isLoading
: A boolean value indicating if the submit operation is still in progress (iftrue
).error
: In case of error, this property will contain error information. The format of error might depend on its type.correlationId
: If the API returns a response including aX-Correlation-ID
value, this property gets its value.attempt
: If the update operation completes successfully, this property will contain the value of the submited attempt withQuizAttempt
type.
Example:
const {submitAttempt,attempt: submitQuizData,isLoading: isSubmitting,error: submitError,correlationId: submitCorrelationId,} = useSubmitAttempt({courseId: '516', quizId: '13'});const onQuizSubmit = useCallback((attemptData: SubmissionAttempt) => {submitAttempt(1, attemptData, true);},[quizData, submitAttempt]);
useCourseInfoByPageSlugs (hook)
This hook takes an array of string (page slugs) as parameter and returns, for each of the page slugs passed as input, it will return an obect containing course info. The course info object has the following structure:
{courseId: number;pages: NavigationPageNode[];status: string;}
Example:
const courseInfo = useCourseInfoByPageSlugs(['course/course-1', 'course/course-2']);courseInfo.map((course) => {const {courseId, pages, status} = course;// do something})
useOrderedCoursesInfo (hook)
Returns an array of objects matching the course order defined in the navigation. Each object has 2 properties: courseId and pages (the list of topics pages with the same order as defined in navigation.yaml).
Note: This hook has been implemented mainly as a hack for the lack of a server side endpoint specifying if a given learning path is completed or not. Therefore this specific hook is mainly used internally to determine if a learning path is completed or not. Once the backend will expose a dedicated learning path status endpoint, this hook will probably disappear.
useFetchCourseDetails (hook)
This hook, initialized with a Moodle courseId, fetches the course information and returns them in form of a CourseWithDetails
type object.
In details, the object contains, course id, course status and for each topic name, visibility, completion status.
It exposes the following properties:
isLoading
: A boolean value indicating if the fetch operation is still in progress (iftrue
).error
: In case of error, this property will contain error information. The format of error might depend on its type.data
: If the fetch operation completes successfully, this property will contain the value of the submited attempt withCourseWithDetails
type.
The hook can be used in conjunction with the getTopicStatusByPageTitle
helper to retrieve a specific topic status.
Example:
const { data, isLoading, error } = useFetchCourseDetails('516');useEffect(() => {if (!isLoading && data) {// do something with the data}}, [data, isLoading, error]);
Note: This hook uses SWR library to interact with the underlying API. This means it gets all the automatic features that comes with SWR.
useFetchCourses
This hook fetches the list of the courses in which the user is enrolled. The Course objects returned contain the course id and the course status.
It exposes the following properties:
isLoading
: A boolean value indicating if the fetch operation is still in progress (iftrue
).error
: In case of error, this property will contain error information. The format of error might depend on its type.data
: If the fetch operation completes successfully, this property will contain an array of all the courses in which the user is erolled in with the status.
The hook can be used in conjunction with the getCourseStatusByCourseId
helper to retrieve a specific course status.
Example:
const { data, isLoading } = useFetchCourses();useEffect(() => {if (!isLoading && data) {getCourseStatusByCourseId(data?.result?.enrolledCourses, props.courseId)}}, [data, isLoading, error]);
Note: This hook uses SWR library to interact with the underlying API. This means it gets all the automatic features that comes with SWR.
UserProfileInit
This is a component that doesn't render any html, in a standard SPA it would be a hook.
Its purpose is to monitor the user profile, either coming from Auth0 at the initial login, or stored in AuthenticatedContextState
, and check if at any point the profile becomes incomplete,
meaning that some defined fields are missing. When this situation happens, the complete profile modal is triggered.
Another function of this component is to keep in sync the user profile with the version of user profile stored into the AuthenticatedContextState.
The component doesn't recieve any props and it's just injected at the root of the application.
ProfileModal
When triggered, this component renders a modal with a form containing some user profile fields. The user is able to modify their values and
submit the form once the changes are completed.
The profile modal visibility is bound to a value in the AuthenticatedContextState and the correct way to open it or close it is by
the openProfileModal
and closeProfileModal
methods exposes from the AuthenticatedContextApi
.
Once loaded, the form field values are automatically pre-filled with the values coming from the user profile.
The <ProfileModal />
component is injected in the root of the application, its default state is closed.
Example:
// it opens a non dismissable profile modal with the specified titleopenProfileModal({title: 'Tell us a bit about yourself.',isDismissable: false,});// it closes the modal if it's opencloseProfileModal();
CourseCompleteModal
It's a modal notification modal that displays a different message depending on if the user completes a course or a learning path. The modal contains a "Continue" button that closes the modal itself and redirects the user to the next page (depending on a defined logic).
The component expects the following props:
courseId
: the Moodle id of the course completed.
Contexts
AuthenticatedContextState and AuthenticatedContextApi
AuthenticatedContextState
holds some state used when the user is logged in. It's used in various parts of the app, not only in self-learning. It's the single source of truth for user profile and
other aspects and can be manipulated using AuthenticatedContextApi
methods.
It's available to all the site components and has the following state structure:
user: {profile: User | undefined; // the user profile object};ui: {profileModal: ProfileModalConfig | undefined; // if a modal is displayed, this property contains the modal configurationaiAssistantModal: ProfileModalConfig | undefined; // if an ai assistant modal is displayed, this property contains the modal configuration};
The AuthenticatedContext also exposes an api (AuthenticatedContextApi
) to manipulate the state via a reducer function. This is the interface it exposes:
{updateProfile: (userProfile: User) => void; // function invoked update the user profileopenProfileModal: (cfg: ProfileModalConfig) => void; // opens a profile modal with the specified configurationcloseProfileModal: () => void; // closes any open profile modalopenAiAssistantModal: (cfg: AiModalConfig) => void; // open the ai assistant chatcloseAiAssistantModal: () => void; // closes the ai assistant chat}
The separation between state and API allows the memoization of the API with performance improvements. In gerneral, when possible, import only what is
strictly needed by the component. For example, if a component uses only the context to display data, then it's enough to just import AuthenticatedContextState
.
Similarly, if a component only updates the state, then only import AuthenticatedContextApi
.
ConfigContext
It holds the self-learning configuration properties and makes them available to all the application.
The structure:
{learnApiBaseUrl: string; // learn API base urlauth0Domain: string; // auth0 domainauth0ClientId: string; // auth0 clientIdhideLogin: boolean; // per-site setting to display or hide the login areaselfLearningFeatures: Array<EFeatureFlag>; // self-learning feature flags};
Clickable chapters
Side navigation chapters can become clickable and link to any page, typically this functionality is used to link to overview pages directly from the chapter title.
To enable this feature it's enough to edit navigation.yaml
file and add a path
attribute just below the chapter-title
like in the following example:
- chapter-title: Self-learning course 1path: /course-1/overviewpages:- title: Test your knowledge with long titlepath: /course-1/quiz- title: Test your knowledge 2path: /course-1/2-quiz- chapter-title: Self-learning course 2path: /course-2/overviewpages:- title: Test your knowledge with long titlepath: /course-2/quiz- title: Test your knowledge 2path: /course-2/2-quiz
Self-learning course 1 will link to /course-1/overview
and Self-learning course 2 will link to /course-2/overview
page.