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 LMS
  • quizId: Id of the quiz defined in the LMS

Example

you write:md
<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 page
  • courseId: The courseId
  • duration: A string giving information about course duration

Example

you write:md
<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 duration
  • productName: A string representing the product the learning path is referring to

Example

you write:md
<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 to true while auth0 is still loading the logged in/logged out user state, the component will render the content regardless.

Examples

you write:md
<IfUserLoggedIn assumeTrue>
## Welcome back!
</IfUserLoggedIn>
you write:md
<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

you write:md
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)

you write:md
<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

you write:md
<IfLearningPathComplete>
Congratulations, you completed learning path
</IfLearningPathComplete>
you write:md
<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 accessToken
const accessToken = await getAuthToken();
// ...then performs fetch passing the token as Authorization header
const 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 Auth0 User 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 (if true).
  • 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 a X-Correlation-ID value, this property gets its value.
  • updatedUser: If the update operation completes successfully, this property will contain the full updated User 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 ID
quizId: '13' // the Moodle quiz ID
}

It exposes the following properties:

  • fetchAttempt: The main function, it takes an optional boolean forceNew parameter. If that is passed true, 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 (if true).
  • 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 a X-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 with QuizAttempt type.

Example:

const {
fetchAttempt,
attempt: quizData,
isLoading,
error,
correlationId,
} = useAttempt({courseId: '516', quizId: '13'});
fetchAttempt(false); // do not force new attempt
useEffect(() => {
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 ID
quizId: '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 type SubmissionAttempt 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 (if true).
  • 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 a X-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 with QuizAttempt 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 (if true).
  • 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 with CourseWithDetails 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 (if true).
  • 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.

SidebarCourseStatus

This component purpose is displaying an icon next to the course title in the navigation bar. The icon displayed depends on the course status and will only be displayed if the user is logged in.

The component expects the following props:

  • courseId: A number representing the id of the Moodle course.

Example:

<LinkItemWithIcon>
<SidebarCourseStatus courseId={516} />
<LinkTitle>The course title</LinkTitle>
</LinkItemWithIcon>

SidebarTopicStatus

This component purpose is displaying an icon next to the course's topics titles in the navigation bar. The icon displayed depends on the topic status and will only be displayed if the user is logged in.

The component expects the following props:

  • courseId: A number representing the id of the Moodle course.
  • pageTitle: The title of the topic (used to find the topic status within the CourseDetails object).

Example:

<SidebarTopicStatus
courseId={courseId}
pageTitle={currTopicName}
/>

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 title
openProfileModal({
title: 'Tell us a bit about yourself.',
isDismissable: false,
});
// it closes the modal if it's open
closeProfileModal();

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 configuration
aiAssistantModal: 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 profile
openProfileModal: (cfg: ProfileModalConfig) => void; // opens a profile modal with the specified configuration
closeProfileModal: () => void; // closes any open profile modal
openAiAssistantModal: (cfg: AiModalConfig) => void; // open the ai assistant chat
closeAiAssistantModal: () => 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 url
auth0Domain: string; // auth0 domain
auth0ClientId: string; // auth0 clientId
hideLogin: boolean; // per-site setting to display or hide the login area
selfLearningFeatures: 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 1
path: /course-1/overview
pages:
- title: Test your knowledge with long title
path: /course-1/quiz
- title: Test your knowledge 2
path: /course-1/2-quiz
- chapter-title: Self-learning course 2
path: /course-2/overview
pages:
- title: Test your knowledge with long title
path: /course-2/quiz
- title: Test your knowledge 2
path: /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.