From ced6c85d5b7dc5d007328438b0fb0f064b36f1ed Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Fri, 31 Mar 2023 12:03:23 +0200 Subject: [PATCH 01/13] feat: display course topics status --- packages/gatsby-theme-docs/gatsby-node.mjs | 11 ++- .../src/hooks/use-course-pages.js | 25 +++++- .../src/layouts/internals/sidebar.js | 49 +++++++----- packages/gatsby-theme-learning/index.ts | 1 + .../src/components/page-topic-status.tsx | 80 +++++++++++++++++++ .../src/hooks/use-course-details.ts | 60 ++++++++++++++ .../src/hooks/use-submit-attempt.ts | 5 +- packages/gatsby-theme-learning/src/index.ts | 1 + websites/docs-smoke-test/gatsby-config.mjs | 3 + .../src/content/self-learning/2-quiz.mdx | 5 +- .../src/content/self-learning/overview.mdx | 3 +- .../src/content/self-learning/quiz.mdx | 5 +- 12 files changed, 218 insertions(+), 30 deletions(-) create mode 100644 packages/gatsby-theme-learning/src/components/page-topic-status.tsx create mode 100644 packages/gatsby-theme-learning/src/hooks/use-course-details.ts diff --git a/packages/gatsby-theme-docs/gatsby-node.mjs b/packages/gatsby-theme-docs/gatsby-node.mjs index 54281fa6f1..cb886e395b 100644 --- a/packages/gatsby-theme-docs/gatsby-node.mjs +++ b/packages/gatsby-theme-docs/gatsby-node.mjs @@ -218,6 +218,7 @@ export const createSchemaCustomization = ({ actions, schema }) => { }), }, courseId: { type: 'Int' }, + topicName: { type: 'String' }, }, interfaces: ['Node'], }), @@ -352,6 +353,9 @@ export const onCreateNode = async ( courseId: node.frontmatter.courseId ? Number(node.frontmatter.courseId) : null, + topicName: node.frontmatter.topicName + ? String(node.frontmatter.topicName) + : null, }; actions.createNode({ @@ -410,7 +414,8 @@ async function createContentPages( allContentPage { nodes { slug - courseId + courseId, + topicName, } } allReleaseNotePage(sort: { date: DESC }) { @@ -447,7 +452,7 @@ async function createContentPages( (pageLinks, node) => [...pageLinks, ...(node.pages || [])], [] ); - pages.forEach(({ slug, courseId }) => { + pages.forEach(({ slug, courseId, topicName }) => { const matchingNavigationPage = navigationPages.find( (page) => trimTrailingSlash(page.path) === trimTrailingSlash(slug) ); @@ -492,6 +497,8 @@ async function createContentPages( } if (courseId) { contentPageData = {...contentPageData, context: {...contentPageData.context, courseId}} + }if (topicName) { + contentPageData = {...contentPageData, context: {...contentPageData.context, topicName}} } actions.createPage(contentPageData); diff --git a/packages/gatsby-theme-docs/src/hooks/use-course-pages.js b/packages/gatsby-theme-docs/src/hooks/use-course-pages.js index 7302c6dd90..df36f921ce 100644 --- a/packages/gatsby-theme-docs/src/hooks/use-course-pages.js +++ b/packages/gatsby-theme-docs/src/hooks/use-course-pages.js @@ -8,6 +8,7 @@ export const useCoursePages = () => { nodes { slug courseId + topicName } } } @@ -25,17 +26,33 @@ var isIndexed = false; const courseMapToPages = (coursePagesData) => { if (!isIndexed) { coursePagesData.forEach((element) => { - coursePageMap.set(element.slug, { courseId: element.courseId }); + const { courseId, topicName } = element; + coursePageMap.set(element.slug, { courseId, topicName }); }); } isIndexed = true; }; /** - * Given a page slug, it returns the courseId where the page belongs + * Given an array of page slugs, it returns an array with course info * if exists, otherwise undefined */ -export const useCourseInfoByPageSlug = (pageSlug) => { +export const useCourseInfoByPageSlugs = (pageSlugs) => { courseMapToPages(useCoursePages()); - return coursePageMap.get(pageSlug); + const courseInfo = pageSlugs.reduce( + (prev, curr) => ({ ...prev, [curr]: coursePageMap.get(curr) }), + {} + ); + // sanity check: all the pages should belong to the same course, therefore have the same courseId + const isOk = Object.values(courseInfo)?.every( + (info, _, theArray) => info?.courseId === theArray[0]?.courseId + ); + if (!isOk) { + // TODO: decide if we want to make this blocker + console.warn( + 'pages belonging to the same course have different courseId metadata', + courseInfo + ); + } + return courseInfo; }; diff --git a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js index ef5edc02a5..1d78f20454 100644 --- a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js +++ b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js @@ -16,8 +16,11 @@ import SiteIcon from '../../overrides/site-icon'; import useScrollPosition from '../../hooks/use-scroll-position'; import { BetaFlag } from '../../components'; import LayoutHeaderLogo from './layout-header-logo'; -import { useCourseInfoByPageSlug } from '../../hooks/use-course-pages'; -import { PageCourseStatus } from '@commercetools-docs/gatsby-theme-learning'; +import { useCourseInfoByPageSlugs } from '../../hooks/use-course-pages'; +import { + PageCourseStatus, + PageTopicStatus, +} from '@commercetools-docs/gatsby-theme-learning'; const ReleaseNotesIcon = createStyledIcon(Icons.ReleaseNotesSvgIcon); @@ -234,7 +237,10 @@ SidebarLinkWrapper.propTypes = { }; const SidebarChapter = (props) => { - const courseInfo = useCourseInfoByPageSlug(props.chapter.pages[0].path); + const courseInfo = useCourseInfoByPageSlugs( + props.chapter.pages.map((page) => page.path) + ); + const courseId = Object.values(courseInfo)[0]?.courseId; const elemId = `sidebar-chapter-${props.index}`; const getChapterDOMElement = React.useCallback( () => document.getElementById(elemId), @@ -246,24 +252,31 @@ const SidebarChapter = (props) => { {props.chapter.chapterTitle} - {courseInfo?.courseId && ( - - )} + {courseId && } {props.chapter.pages && - props.chapter.pages.map((pageLink, pageIndex) => ( - - {pageLink.title} - - ))} + props.chapter.pages.map((pageLink, pageIndex) => { + const currTopicName = courseInfo[pageLink.path]?.topicName; + return ( + + {pageLink.title} + {courseId && currTopicName && ( + + )} + + ); + })} diff --git a/packages/gatsby-theme-learning/index.ts b/packages/gatsby-theme-learning/index.ts index 0502671a6d..f66b89a48e 100644 --- a/packages/gatsby-theme-learning/index.ts +++ b/packages/gatsby-theme-learning/index.ts @@ -3,3 +3,4 @@ export { useFetchCourses, getCourseStatusByCourseId, } from './src/hooks/use-course-status'; +export { useFetchCourseDetails } from './src/hooks/use-course-details'; diff --git a/packages/gatsby-theme-learning/src/components/page-topic-status.tsx b/packages/gatsby-theme-learning/src/components/page-topic-status.tsx new file mode 100644 index 0000000000..133116675b --- /dev/null +++ b/packages/gatsby-theme-learning/src/components/page-topic-status.tsx @@ -0,0 +1,80 @@ +import React, { useContext } from 'react'; +import styled from '@emotion/styled'; +import { keyframes } from '@emotion/react'; +import { useAuth0 } from '@auth0/auth0-react'; +import { + useFetchCourseDetails, + getTopicStatusByPageTitle, +} from '../hooks/use-course-details'; +import ConfigContext from './config-context'; + +const spin = keyframes` + to { + transform: rotate(360deg); + }`; + +const LoadingIcon = styled.div` + display: inline-block; + width: 10px; + height: 10px; + border: 3px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: #000; + animation: ${spin} 1s ease-in-out infinite; +`; + +const colorByStatus = (status?: string) => { + switch (status) { + case 'completed': + return '#7CFC00'; + case 'notCompleted': + return '#D3D3D3'; + default: + return '#000'; + } +}; + +type StatusIndicatorProps = { + status?: string; +}; + +const StatusIndicator = styled.span` + height: 10px; + width: 10px; + background-color: ${(props: StatusIndicatorProps) => + colorByStatus(props.status)}; + border-radius: 50%; + display: inline-block; +`; + +type PageTopicStatusProps = { + courseId: number; + pageTitle: string; +}; + +const PageTopicStatus = (props: PageTopicStatusProps) => { + const { isAuthenticated } = useAuth0(); + const { data, isLoading } = useFetchCourseDetails(props.courseId); + const { + features: { courseStatusIndicator }, + } = useContext(ConfigContext); + + // courseStatusIndicator feature flag + if (!courseStatusIndicator) { + return null; + } + + const topicStatus = data?.result?.topics + ? getTopicStatusByPageTitle(data.result.topics, props.pageTitle) + : undefined; + return ( + <> + {props.courseId && isAuthenticated && topicStatus && ( + + )} + {isLoading && } + + ); +}; + +export default PageTopicStatus; diff --git a/packages/gatsby-theme-learning/src/hooks/use-course-details.ts b/packages/gatsby-theme-learning/src/hooks/use-course-details.ts new file mode 100644 index 0000000000..5fd3755c2e --- /dev/null +++ b/packages/gatsby-theme-learning/src/hooks/use-course-details.ts @@ -0,0 +1,60 @@ +import useSWR from 'swr'; +import { useContext } from 'react'; +import ConfigContext from '../components/config-context'; +import { useAuth0 } from '@auth0/auth0-react'; +import type { + ApiCallResult, + CourseWithDetails, + CourseTopic, +} from '../external-types'; +import { fetcherWithToken } from './hooks.utils'; +import { useAuthToken } from './use-auth-token'; + +type UseFetchCoursesIdResponse = { + data: ApiCallResult | undefined; + error: string; + isLoading: boolean; +}; + +export const useFetchCourseDetails = ( + courseId: number +): { + data: ApiCallResult | undefined; + error: string | undefined; + isLoading: boolean; +} => { + const { + learnApiBaseUrl, + features: { courseStatusIndicator }, + } = useContext(ConfigContext); + const { isAuthenticated } = useAuth0(); + const { getAuthToken } = useAuthToken(); + const apiEndpoint = `/api/courses/${courseId}`; + + // fetch data only if course status feature flag is true and the user is logged in + const shouldFetchData = courseId && courseStatusIndicator && isAuthenticated; + + const { data, error, isLoading } = useSWR( + shouldFetchData ? apiEndpoint : null, + (url) => fetcherWithToken(url, getAuthToken, learnApiBaseUrl) + ) as UseFetchCoursesIdResponse; + return { + data, + error, + isLoading, + }; +}; + +export const getTopicStatusByPageTitle = ( + topics: CourseTopic[], + pageTitle: string +) => { + const matchingTopic = topics.find( + (topic) => + topic.name.trim().toLowerCase() === pageTitle.trim().toLowerCase() + ); + if (matchingTopic) { + return matchingTopic.completed ? 'completed' : 'notCompleted'; + } + return 'unknown'; +}; diff --git a/packages/gatsby-theme-learning/src/hooks/use-submit-attempt.ts b/packages/gatsby-theme-learning/src/hooks/use-submit-attempt.ts index 55cb37805f..7f457e6b1f 100644 --- a/packages/gatsby-theme-learning/src/hooks/use-submit-attempt.ts +++ b/packages/gatsby-theme-learning/src/hooks/use-submit-attempt.ts @@ -27,7 +27,10 @@ export const useSubmitAttempt = (submitAttemptParams: SubmitAttemptParams) => { attemptData: SubmissionAttempt, finish: boolean ) => { - const invalidateCache = () => mutate('/api/courses'); + const invalidateCache = () => { + mutate('/api/courses'); + mutate(`/api/courses/${courseId}`); + }; const apiEndpoint = `${learnApiBaseUrl}/api/courses/${courseId}/quizzes/${quizId}/attempts/${attemptId}?finish=${finish}`; const accessToken = await getAuthToken(); const data = await fetch(apiEndpoint, { diff --git a/packages/gatsby-theme-learning/src/index.ts b/packages/gatsby-theme-learning/src/index.ts index 4883038dbf..332acc6b8c 100644 --- a/packages/gatsby-theme-learning/src/index.ts +++ b/packages/gatsby-theme-learning/src/index.ts @@ -2,3 +2,4 @@ export { default as Quiz } from './components/quiz'; export * from './components/quiz'; export * from './components/quiz.types'; export { default as PageCourseStatus } from './components/page-course-status'; +export { default as PageTopicStatus } from './components/page-topic-status'; diff --git a/websites/docs-smoke-test/gatsby-config.mjs b/websites/docs-smoke-test/gatsby-config.mjs index b57d754104..29c46a14c0 100644 --- a/websites/docs-smoke-test/gatsby-config.mjs +++ b/websites/docs-smoke-test/gatsby-config.mjs @@ -32,6 +32,9 @@ const config = { options: { auth0Domain: 'auth.id.commercetools.com', learnApiBaseUrl: 'https://api.learn.commercetools.com', + features: { + courseStatusIndicator: true, + } }, }, { diff --git a/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx b/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx index afdb3a80e6..0248f8433c 100644 --- a/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx +++ b/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx @@ -1,8 +1,9 @@ --- title: Quiz component -courseId: 55 +courseId: 66 +topicName: 'Quiz 2' --- # Test your knowledge - + diff --git a/websites/docs-smoke-test/src/content/self-learning/overview.mdx b/websites/docs-smoke-test/src/content/self-learning/overview.mdx index dfb7f632b6..86844ba5c8 100644 --- a/websites/docs-smoke-test/src/content/self-learning/overview.mdx +++ b/websites/docs-smoke-test/src/content/self-learning/overview.mdx @@ -1,6 +1,7 @@ --- title: Course overview -courseId: 55 +courseId: 66 +topicName: 'Overview' --- The purpose of this page is to provide an introduction to the quiz component. diff --git a/websites/docs-smoke-test/src/content/self-learning/quiz.mdx b/websites/docs-smoke-test/src/content/self-learning/quiz.mdx index 80ddc42501..a5c8f7d52a 100644 --- a/websites/docs-smoke-test/src/content/self-learning/quiz.mdx +++ b/websites/docs-smoke-test/src/content/self-learning/quiz.mdx @@ -1,8 +1,9 @@ --- title: Quiz component -courseId: 55 +courseId: 66 +topicName: 'Quiz 1' --- # Test your knowledge - + From 9dbea5f4ddb0b93c4e83c8dc21e24ec6d9c5ebfb Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Mon, 3 Apr 2023 21:54:35 +0200 Subject: [PATCH 02/13] feat: initial design icon implementation --- .../gatsby-theme-docs/src/layouts/content.js | 4 - .../src/layouts/internals/sidebar.js | 38 ++++++--- .../src/components/page-topic-status.tsx | 80 ------------------- ...e-status.tsx => sidebar-course-status.tsx} | 36 +++------ .../src/components/sidebar-topic-status.tsx | 50 ++++++++++++ .../src/hooks/use-course-details.ts | 2 +- packages/gatsby-theme-learning/src/index.ts | 4 +- 7 files changed, 94 insertions(+), 120 deletions(-) delete mode 100644 packages/gatsby-theme-learning/src/components/page-topic-status.tsx rename packages/gatsby-theme-learning/src/components/{page-course-status.tsx => sidebar-course-status.tsx} (54%) create mode 100644 packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx diff --git a/packages/gatsby-theme-docs/src/layouts/content.js b/packages/gatsby-theme-docs/src/layouts/content.js index 8d8691a991..716010df77 100644 --- a/packages/gatsby-theme-docs/src/layouts/content.js +++ b/packages/gatsby-theme-docs/src/layouts/content.js @@ -22,7 +22,6 @@ import LayoutPageNavigation from './internals/layout-page-navigation'; import LayoutPageContent from './internals/layout-page-content'; import PageContentInset from './internals/page-content-inset'; import PageReadTime from './internals/page-read-time-estimation'; -import { PageCourseStatus } from '@commercetools-docs/gatsby-theme-learning'; const LayoutContent = (props) => { const { ref, inView, entry } = useInView(); @@ -82,9 +81,6 @@ const LayoutContent = (props) => { {props.pageData.showTimeToRead && ( )} - {props.pageContext.courseId && ( - - )} diff --git a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js index 1d78f20454..11ac0e62f2 100644 --- a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js +++ b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js @@ -18,8 +18,8 @@ import { BetaFlag } from '../../components'; import LayoutHeaderLogo from './layout-header-logo'; import { useCourseInfoByPageSlugs } from '../../hooks/use-course-pages'; import { - PageCourseStatus, - PageTopicStatus, + SidebarCourseStatus, + SidebarTopicStatus, } from '@commercetools-docs/gatsby-theme-learning'; const ReleaseNotesIcon = createStyledIcon(Icons.ReleaseNotesSvgIcon); @@ -124,6 +124,17 @@ const activeLinkStyles = css` color: ${designSystem.colors.light.linkNavigation} !important; `; +const LinkSubtitleWithIcon = (props) => ( + + {props.icon} + {props.children} + +); +LinkSubtitleWithIcon.propTypes = { + icon: PropTypes.element, + children: PropTypes.any, +}; + const SidebarLink = (props) => { const { locationPath, customStyles, customActiveStyles, ...forwardProps } = props; @@ -251,13 +262,20 @@ const SidebarChapter = (props) => {
+ {courseId && } {props.chapter.chapterTitle} - {courseId && } {props.chapter.pages && props.chapter.pages.map((pageLink, pageIndex) => { const currTopicName = courseInfo[pageLink.path]?.topicName; + const TopicIcon = + courseId && currTopicName ? ( + + ) : null; return ( { nextScrollPosition={props.nextScrollPosition} getChapterDOMElement={getChapterDOMElement} > - {pageLink.title} - {courseId && currTopicName && ( - - )} + {/* + + {pageLink.title} + */} + + {pageLink.title} + ); })} diff --git a/packages/gatsby-theme-learning/src/components/page-topic-status.tsx b/packages/gatsby-theme-learning/src/components/page-topic-status.tsx deleted file mode 100644 index 133116675b..0000000000 --- a/packages/gatsby-theme-learning/src/components/page-topic-status.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useContext } from 'react'; -import styled from '@emotion/styled'; -import { keyframes } from '@emotion/react'; -import { useAuth0 } from '@auth0/auth0-react'; -import { - useFetchCourseDetails, - getTopicStatusByPageTitle, -} from '../hooks/use-course-details'; -import ConfigContext from './config-context'; - -const spin = keyframes` - to { - transform: rotate(360deg); - }`; - -const LoadingIcon = styled.div` - display: inline-block; - width: 10px; - height: 10px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-radius: 50%; - border-top-color: #000; - animation: ${spin} 1s ease-in-out infinite; -`; - -const colorByStatus = (status?: string) => { - switch (status) { - case 'completed': - return '#7CFC00'; - case 'notCompleted': - return '#D3D3D3'; - default: - return '#000'; - } -}; - -type StatusIndicatorProps = { - status?: string; -}; - -const StatusIndicator = styled.span` - height: 10px; - width: 10px; - background-color: ${(props: StatusIndicatorProps) => - colorByStatus(props.status)}; - border-radius: 50%; - display: inline-block; -`; - -type PageTopicStatusProps = { - courseId: number; - pageTitle: string; -}; - -const PageTopicStatus = (props: PageTopicStatusProps) => { - const { isAuthenticated } = useAuth0(); - const { data, isLoading } = useFetchCourseDetails(props.courseId); - const { - features: { courseStatusIndicator }, - } = useContext(ConfigContext); - - // courseStatusIndicator feature flag - if (!courseStatusIndicator) { - return null; - } - - const topicStatus = data?.result?.topics - ? getTopicStatusByPageTitle(data.result.topics, props.pageTitle) - : undefined; - return ( - <> - {props.courseId && isAuthenticated && topicStatus && ( - - )} - {isLoading && } - - ); -}; - -export default PageTopicStatus; diff --git a/packages/gatsby-theme-learning/src/components/page-course-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx similarity index 54% rename from packages/gatsby-theme-learning/src/components/page-course-status.tsx rename to packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx index 137ca639f8..1828a14267 100644 --- a/packages/gatsby-theme-learning/src/components/page-course-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx @@ -1,33 +1,30 @@ import React, { useContext } from 'react'; import { useAuth0 } from '@auth0/auth0-react'; +import { CircleIcon, VerifiedIcon } from '@commercetools-uikit/icons'; import { getCourseStatusByCourseId, useFetchCourses, } from '../hooks/use-course-status'; import ConfigContext from './config-context'; -type CourseStatusProps = { - error?: string; +type StatusIndicatorProps = { status?: string; }; -const CourseStatus = (props: CourseStatusProps) => { - if (props.error) { - return unavailable; - } - if (props.status) { - return {props.status}; - } - return null; -}; +export const StatusIndicator = (props: StatusIndicatorProps) => + props.status && props.status === 'completed' ? ( + + ) : ( + + ); -type PageCourseStatusProps = { +type SidebarCourseStatusProps = { courseId: number; }; -const PageCourseStatus = (props: PageCourseStatusProps) => { +const SidebarCourseStatus = (props: SidebarCourseStatusProps) => { const { isAuthenticated } = useAuth0(); - const { data, isLoading, error } = useFetchCourses(); + const { data } = useFetchCourses(); const { features: { courseStatusIndicator }, } = useContext(ConfigContext); @@ -43,17 +40,10 @@ const PageCourseStatus = (props: PageCourseStatusProps) => { return ( <> {props.courseId && isAuthenticated && ( -
- Course status:{' '} - {isLoading ? ( - '...' - ) : ( - - )} -
+ )} ); }; -export default PageCourseStatus; +export default SidebarCourseStatus; diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx new file mode 100644 index 0000000000..98d83f8d6b --- /dev/null +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -0,0 +1,50 @@ +import React, { useContext } from 'react'; +import { CheckActiveIcon, CircleIcon } from '@commercetools-uikit/icons'; +import { useAuth0 } from '@auth0/auth0-react'; +import { + useFetchCourseDetails, + getTopicStatusByPageTitle, +} from '../hooks/use-course-details'; +import ConfigContext from './config-context'; + +type StatusIndicatorProps = { + status?: string; +}; + +export const StatusIndicator = (props: StatusIndicatorProps) => + props.status && props.status === 'completed' ? ( + + ) : ( + + ); + +type PageTopicStatusProps = { + courseId: number; + pageTitle: string; +}; + +const SidebarTopicStatus = (props: PageTopicStatusProps) => { + const { isAuthenticated } = useAuth0(); + const { data } = useFetchCourseDetails(props.courseId); + const { + features: { courseStatusIndicator }, + } = useContext(ConfigContext); + + // courseStatusIndicator feature flag + if (!courseStatusIndicator) { + return null; + } + + const topicStatus = data?.result?.topics + ? getTopicStatusByPageTitle(data.result.topics, props.pageTitle) + : undefined; + return ( + <> + {props.courseId && isAuthenticated && ( + + )} + + ); +}; + +export default SidebarTopicStatus; diff --git a/packages/gatsby-theme-learning/src/hooks/use-course-details.ts b/packages/gatsby-theme-learning/src/hooks/use-course-details.ts index 5fd3755c2e..554f59fe68 100644 --- a/packages/gatsby-theme-learning/src/hooks/use-course-details.ts +++ b/packages/gatsby-theme-learning/src/hooks/use-course-details.ts @@ -56,5 +56,5 @@ export const getTopicStatusByPageTitle = ( if (matchingTopic) { return matchingTopic.completed ? 'completed' : 'notCompleted'; } - return 'unknown'; + return 'notAvailable'; }; diff --git a/packages/gatsby-theme-learning/src/index.ts b/packages/gatsby-theme-learning/src/index.ts index 332acc6b8c..0264263aac 100644 --- a/packages/gatsby-theme-learning/src/index.ts +++ b/packages/gatsby-theme-learning/src/index.ts @@ -1,5 +1,5 @@ export { default as Quiz } from './components/quiz'; export * from './components/quiz'; export * from './components/quiz.types'; -export { default as PageCourseStatus } from './components/page-course-status'; -export { default as PageTopicStatus } from './components/page-topic-status'; +export { default as SidebarCourseStatus } from './components/sidebar-course-status'; +export { default as SidebarTopicStatus } from './components/sidebar-topic-status'; From cefb159b6bfc7a8950e4c046c6d1b293170084e8 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Mon, 3 Apr 2023 23:09:00 +0200 Subject: [PATCH 03/13] chore: refined UI --- .../src/layouts/internals/sidebar.js | 57 ++++++++++++++----- .../src/components/sidebar-topic-status.tsx | 7 +-- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js index 11ac0e62f2..172a58197a 100644 --- a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js +++ b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js @@ -94,6 +94,19 @@ const LinkItem = styled.div` display: flex; flex-direction: row; align-items: flex-end; + vertical-align: middle; +`; +const LinkItemWithIcon = styled.div` + padding: 0 0 0 ${designSystem.dimensions.spacings.m}; + display: flex; + flex-direction: row; + vertical-align: middle; + svg { + margin-right: 2px; + } + div { + line-height: ${designSystem.typography.lineHeights.cardSmallTitle}; + } `; const linkStyles = css` border-left: ${designSystem.dimensions.spacings.xs} solid @@ -124,10 +137,21 @@ const activeLinkStyles = css` color: ${designSystem.colors.light.linkNavigation} !important; `; +const StatusIconWrapper = styled.span` + display: flex; + vertical-align: middle; + padding-left: 10px; // change this setting to remove the indentation + svg { + margin-right: 6px; + } +`; + const LinkSubtitleWithIcon = (props) => ( - {props.icon} - {props.children} + + {props.icon} + {props.children} + ); LinkSubtitleWithIcon.propTypes = { @@ -261,10 +285,17 @@ const SidebarChapter = (props) => { return (
- - {courseId && } - {props.chapter.chapterTitle} - + {courseId ? ( + + + {props.chapter.chapterTitle} + + ) : ( + + {props.chapter.chapterTitle} + + )} + {props.chapter.pages && props.chapter.pages.map((pageLink, pageIndex) => { @@ -285,13 +316,13 @@ const SidebarChapter = (props) => { nextScrollPosition={props.nextScrollPosition} getChapterDOMElement={getChapterDOMElement} > - {/* - - {pageLink.title} - */} - - {pageLink.title} - + {TopicIcon ? ( + + {pageLink.title} + + ) : ( + {pageLink.title} + )} ); })} diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx index 98d83f8d6b..d1b47d685c 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -39,11 +39,8 @@ const SidebarTopicStatus = (props: PageTopicStatusProps) => { ? getTopicStatusByPageTitle(data.result.topics, props.pageTitle) : undefined; return ( - <> - {props.courseId && isAuthenticated && ( - - )} - + props.courseId && + isAuthenticated && ); }; From b684813ff9fd15d73507219ddde3367057c82964 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Tue, 4 Apr 2023 11:05:20 +0200 Subject: [PATCH 04/13] chore: don't display any icon if the user is not logged in --- .../src/layouts/internals/sidebar.js | 11 ++++------- packages/gatsby-theme-learning/gatsby-ssr.js | 18 ++++++++++++++++++ .../src/components/sidebar-topic-status.tsx | 1 + 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 packages/gatsby-theme-learning/gatsby-ssr.js diff --git a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js index 172a58197a..0b14edab1f 100644 --- a/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js +++ b/packages/gatsby-theme-docs/src/layouts/internals/sidebar.js @@ -142,20 +142,16 @@ const StatusIconWrapper = styled.span` vertical-align: middle; padding-left: 10px; // change this setting to remove the indentation svg { - margin-right: 6px; + margin-right: 5px; } `; const LinkSubtitleWithIcon = (props) => ( - - {props.icon} - {props.children} - + {props.children} ); LinkSubtitleWithIcon.propTypes = { - icon: PropTypes.element, children: PropTypes.any, }; @@ -317,7 +313,8 @@ const SidebarChapter = (props) => { getChapterDOMElement={getChapterDOMElement} > {TopicIcon ? ( - + + {TopicIcon} {pageLink.title} ) : ( diff --git a/packages/gatsby-theme-learning/gatsby-ssr.js b/packages/gatsby-theme-learning/gatsby-ssr.js new file mode 100644 index 0000000000..01f0db3729 --- /dev/null +++ b/packages/gatsby-theme-learning/gatsby-ssr.js @@ -0,0 +1,18 @@ +import ConfigContext from './src/components/config-context'; + +export const wrapRootElement = ({ element }, pluginOptions) => { + return ( + + {element} + + ); +}; diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx index d1b47d685c..c67afb4dc6 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -38,6 +38,7 @@ const SidebarTopicStatus = (props: PageTopicStatusProps) => { const topicStatus = data?.result?.topics ? getTopicStatusByPageTitle(data.result.topics, props.pageTitle) : undefined; + return ( props.courseId && isAuthenticated && From 8bf750b31c28349b215f024d726de21f39e5b10c Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Tue, 4 Apr 2023 11:47:49 +0200 Subject: [PATCH 05/13] chore: refactor feature flags --- packages/gatsby-theme-learning/README.md | 4 ++-- .../gatsby-theme-learning/gatsby-browser.js | 5 +---- packages/gatsby-theme-learning/gatsby-node.mjs | 4 +--- packages/gatsby-theme-learning/gatsby-ssr.js | 5 +---- .../src/components/config-context.ts | 17 +++++++++++------ .../src/components/sidebar-course-status.tsx | 13 +++++++------ .../src/components/sidebar-topic-status.tsx | 13 +++++++------ .../src/hooks/use-course-details.ts | 15 +++++++++------ .../src/hooks/use-course-status.ts | 13 +++++++------ websites/docs-smoke-test/gatsby-config.mjs | 3 --- .../src/content/writing/quizzes.mdx | 4 ++-- 11 files changed, 48 insertions(+), 48 deletions(-) diff --git a/packages/gatsby-theme-learning/README.md b/packages/gatsby-theme-learning/README.md index a18079e8f7..82132ee294 100644 --- a/packages/gatsby-theme-learning/README.md +++ b/packages/gatsby-theme-learning/README.md @@ -11,8 +11,8 @@ Works against the learning-api. - `auth0Domain`: the auth0 application domain url (it is defined in the auth0 management app) - `learnApiBaseUrl`: the learn API base url. It can be omitted if the host running the site matches the api host. -- `features`: a list of feature flags (boolean values) to enable/disable specific functionalities - - `courseStatusIndicator`: feature flag to toggle the course status indicator. +- `features`: an array of strings representing feature flags used to enable/disable specific functionalities. Expected values: + - `status-indicator`: feature flag to toggle the course and topics status indicator. In order to enable the plugin, at least the following configuration should be added to the `gatsby-config.js` plugin section: diff --git a/packages/gatsby-theme-learning/gatsby-browser.js b/packages/gatsby-theme-learning/gatsby-browser.js index 01f0db3729..8b7af7f273 100644 --- a/packages/gatsby-theme-learning/gatsby-browser.js +++ b/packages/gatsby-theme-learning/gatsby-browser.js @@ -6,10 +6,7 @@ export const wrapRootElement = ({ element }, pluginOptions) => { value={{ learnApiBaseUrl: pluginOptions.learnApiBaseUrl, auth0Domain: pluginOptions.auth0Domain, - features: { - courseStatusIndicator: - pluginOptions?.features?.courseStatusIndicator || false, - }, + features: pluginOptions?.features || [], }} > {element} diff --git a/packages/gatsby-theme-learning/gatsby-node.mjs b/packages/gatsby-theme-learning/gatsby-node.mjs index 1b71514f6e..6e34a74f07 100644 --- a/packages/gatsby-theme-learning/gatsby-node.mjs +++ b/packages/gatsby-theme-learning/gatsby-node.mjs @@ -5,8 +5,6 @@ export const pluginOptionsSchema = ({ Joi }) => { .allow('') .default('') .description(`Learn API base url`), - features: Joi.object({ - courseStatusIndicator: Joi.boolean().default(false), - }) + features: Joi.array().items(Joi.string()), }); }; diff --git a/packages/gatsby-theme-learning/gatsby-ssr.js b/packages/gatsby-theme-learning/gatsby-ssr.js index 01f0db3729..8b7af7f273 100644 --- a/packages/gatsby-theme-learning/gatsby-ssr.js +++ b/packages/gatsby-theme-learning/gatsby-ssr.js @@ -6,10 +6,7 @@ export const wrapRootElement = ({ element }, pluginOptions) => { value={{ learnApiBaseUrl: pluginOptions.learnApiBaseUrl, auth0Domain: pluginOptions.auth0Domain, - features: { - courseStatusIndicator: - pluginOptions?.features?.courseStatusIndicator || false, - }, + features: pluginOptions?.features || [], }} > {element} diff --git a/packages/gatsby-theme-learning/src/components/config-context.ts b/packages/gatsby-theme-learning/src/components/config-context.ts index 9b64feca65..b595eb6b16 100644 --- a/packages/gatsby-theme-learning/src/components/config-context.ts +++ b/packages/gatsby-theme-learning/src/components/config-context.ts @@ -1,19 +1,24 @@ import { createContext } from 'react'; +export enum EFeatureFlag { + CourseStatus = 'status-indicator', +} + export type Config = { learnApiBaseUrl: string; auth0Domain: string; - features: { - courseStatusIndicator: boolean; - }; + features: Array; }; const ConfigContext = createContext({ learnApiBaseUrl: '', auth0Domain: '', - features: { - courseStatusIndicator: false, - }, + features: [], }); +export const isFeatureEnabled = ( + feature: EFeatureFlag, + features: EFeatureFlag[] +) => features.includes(feature); + export default ConfigContext; diff --git a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx index 1828a14267..ad66e741cb 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx @@ -5,7 +5,10 @@ import { getCourseStatusByCourseId, useFetchCourses, } from '../hooks/use-course-status'; -import ConfigContext from './config-context'; +import ConfigContext, { + isFeatureEnabled, + EFeatureFlag, +} from './config-context'; type StatusIndicatorProps = { status?: string; @@ -25,12 +28,10 @@ type SidebarCourseStatusProps = { const SidebarCourseStatus = (props: SidebarCourseStatusProps) => { const { isAuthenticated } = useAuth0(); const { data } = useFetchCourses(); - const { - features: { courseStatusIndicator }, - } = useContext(ConfigContext); + const { features } = useContext(ConfigContext); - // courseStatusIndicator feature flag - if (!courseStatusIndicator) { + // CourseStatus feature flag + if (!isFeatureEnabled(EFeatureFlag.CourseStatus, features)) { return null; } diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx index c67afb4dc6..5149947fc1 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -5,7 +5,10 @@ import { useFetchCourseDetails, getTopicStatusByPageTitle, } from '../hooks/use-course-details'; -import ConfigContext from './config-context'; +import ConfigContext, { + isFeatureEnabled, + EFeatureFlag, +} from './config-context'; type StatusIndicatorProps = { status?: string; @@ -26,12 +29,10 @@ type PageTopicStatusProps = { const SidebarTopicStatus = (props: PageTopicStatusProps) => { const { isAuthenticated } = useAuth0(); const { data } = useFetchCourseDetails(props.courseId); - const { - features: { courseStatusIndicator }, - } = useContext(ConfigContext); + const { features } = useContext(ConfigContext); - // courseStatusIndicator feature flag - if (!courseStatusIndicator) { + // CourseStatus feature flag + if (!isFeatureEnabled(EFeatureFlag.CourseStatus, features)) { return null; } diff --git a/packages/gatsby-theme-learning/src/hooks/use-course-details.ts b/packages/gatsby-theme-learning/src/hooks/use-course-details.ts index 554f59fe68..98b7c022ab 100644 --- a/packages/gatsby-theme-learning/src/hooks/use-course-details.ts +++ b/packages/gatsby-theme-learning/src/hooks/use-course-details.ts @@ -1,6 +1,9 @@ import useSWR from 'swr'; import { useContext } from 'react'; -import ConfigContext from '../components/config-context'; +import ConfigContext, { + EFeatureFlag, + isFeatureEnabled, +} from '../components/config-context'; import { useAuth0 } from '@auth0/auth0-react'; import type { ApiCallResult, @@ -23,16 +26,16 @@ export const useFetchCourseDetails = ( error: string | undefined; isLoading: boolean; } => { - const { - learnApiBaseUrl, - features: { courseStatusIndicator }, - } = useContext(ConfigContext); + const { learnApiBaseUrl, features } = useContext(ConfigContext); const { isAuthenticated } = useAuth0(); const { getAuthToken } = useAuthToken(); const apiEndpoint = `/api/courses/${courseId}`; // fetch data only if course status feature flag is true and the user is logged in - const shouldFetchData = courseId && courseStatusIndicator && isAuthenticated; + const shouldFetchData = + courseId && + isFeatureEnabled(EFeatureFlag.CourseStatus, features) && + isAuthenticated; const { data, error, isLoading } = useSWR( shouldFetchData ? apiEndpoint : null, diff --git a/packages/gatsby-theme-learning/src/hooks/use-course-status.ts b/packages/gatsby-theme-learning/src/hooks/use-course-status.ts index 541cb1c682..f88cd70df5 100644 --- a/packages/gatsby-theme-learning/src/hooks/use-course-status.ts +++ b/packages/gatsby-theme-learning/src/hooks/use-course-status.ts @@ -1,6 +1,9 @@ import useSWR from 'swr'; import { useContext } from 'react'; -import ConfigContext from '../components/config-context'; +import ConfigContext, { + EFeatureFlag, + isFeatureEnabled, +} from '../components/config-context'; import { useAuth0 } from '@auth0/auth0-react'; import type { ApiCallResult, @@ -28,16 +31,14 @@ export const useFetchCourses = (): { error: string | undefined; isLoading: boolean; } => { - const { - learnApiBaseUrl, - features: { courseStatusIndicator }, - } = useContext(ConfigContext); + const { learnApiBaseUrl, features } = useContext(ConfigContext); const { isAuthenticated } = useAuth0(); const { getAuthToken } = useAuthToken(); const apiEndpoint = `/api/courses`; // fetch data only if course status feature flag is true and the user is logged in - const shouldFetchData = courseStatusIndicator && isAuthenticated; + const shouldFetchData = + isFeatureEnabled(EFeatureFlag.CourseStatus, features) && isAuthenticated; const { data, error, isLoading } = useSWR( shouldFetchData ? apiEndpoint : null, diff --git a/websites/docs-smoke-test/gatsby-config.mjs b/websites/docs-smoke-test/gatsby-config.mjs index 29c46a14c0..b57d754104 100644 --- a/websites/docs-smoke-test/gatsby-config.mjs +++ b/websites/docs-smoke-test/gatsby-config.mjs @@ -32,9 +32,6 @@ const config = { options: { auth0Domain: 'auth.id.commercetools.com', learnApiBaseUrl: 'https://api.learn.commercetools.com', - features: { - courseStatusIndicator: true, - } }, }, { diff --git a/websites/documentation/src/content/writing/quizzes.mdx b/websites/documentation/src/content/writing/quizzes.mdx index 0c04cecf8b..a10a8de9ff 100644 --- a/websites/documentation/src/content/writing/quizzes.mdx +++ b/websites/documentation/src/content/writing/quizzes.mdx @@ -14,8 +14,8 @@ Works against commercetools' learning-api, which is not a part of this open sour - `auth0Domain`: the auth0 application domain url (it is defined in the auth0 management app) - `learnApiBaseUrl`: the learn API base url. It can be omitted if the host running the site matches the api host. -- `features`: a list of feature flags (boolean values) to toggle specific functionalities. - - `courseStatusIndicator`: feature flag to toggle the course status indicator. +- `features`: an array of strings representing feature flags used to enable/disable specific functionalities. Expected values: + - `status-indicator`: feature flag to toggle the course status indicator. In order to enable the plugin, the following configuration should be added to the `gatsby-config.js` plugin section: From c816d9b69a5beabcf795d9068cf608a50fd7ee91 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Tue, 4 Apr 2023 11:48:18 +0200 Subject: [PATCH 06/13] chore: refactor feature flags --- websites/docs-smoke-test/gatsby-config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/websites/docs-smoke-test/gatsby-config.mjs b/websites/docs-smoke-test/gatsby-config.mjs index b57d754104..be93716363 100644 --- a/websites/docs-smoke-test/gatsby-config.mjs +++ b/websites/docs-smoke-test/gatsby-config.mjs @@ -32,6 +32,7 @@ const config = { options: { auth0Domain: 'auth.id.commercetools.com', learnApiBaseUrl: 'https://api.learn.commercetools.com', + features: ['status-indicator'] }, }, { From 76de1cb914dca7233e9dc9f48cd1385a4dec4ed4 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Tue, 4 Apr 2023 13:05:53 +0200 Subject: [PATCH 07/13] chore: changest --- .changeset/chilled-houses-study.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/chilled-houses-study.md diff --git a/.changeset/chilled-houses-study.md b/.changeset/chilled-houses-study.md new file mode 100644 index 0000000000..0fd37b8d5b --- /dev/null +++ b/.changeset/chilled-houses-study.md @@ -0,0 +1,8 @@ +--- +'@commercetools-docs/gatsby-theme-learning': minor +'@commercetools-docs/gatsby-theme-docs': minor +'@commercetools-website/docs-smoke-test': minor +'@commercetools-website/documentation': minor +--- + +Added status indicator for course and course topics From b0a286607a2befdad150bc0fbe54fd4f89b95048 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Tue, 4 Apr 2023 15:02:16 +0200 Subject: [PATCH 08/13] chore: style checker --- websites/documentation/src/content/writing/quizzes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websites/documentation/src/content/writing/quizzes.mdx b/websites/documentation/src/content/writing/quizzes.mdx index a10a8de9ff..cc4548b791 100644 --- a/websites/documentation/src/content/writing/quizzes.mdx +++ b/websites/documentation/src/content/writing/quizzes.mdx @@ -14,7 +14,7 @@ Works against commercetools' learning-api, which is not a part of this open sour - `auth0Domain`: the auth0 application domain url (it is defined in the auth0 management app) - `learnApiBaseUrl`: the learn API base url. It can be omitted if the host running the site matches the api host. -- `features`: an array of strings representing feature flags used to enable/disable specific functionalities. Expected values: +- `features`: an array of strings representing feature flags used to toggle specific functionalities. Expected values: - `status-indicator`: feature flag to toggle the course status indicator. In order to enable the plugin, the following configuration should be added to the `gatsby-config.js` plugin section: From 2d8a5a00ec45bcfcb4944c68ddaafd49d3b266b3 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Tue, 4 Apr 2023 15:31:05 +0200 Subject: [PATCH 09/13] chore: un-necessary string quotes --- websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx b/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx index 0248f8433c..be2ff9d01d 100644 --- a/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx +++ b/websites/docs-smoke-test/src/content/self-learning/2-quiz.mdx @@ -1,7 +1,7 @@ --- title: Quiz component courseId: 66 -topicName: 'Quiz 2' +topicName: Quiz 2 --- # Test your knowledge From 32003f758e4666e7679b25acaaef57576522d1fd Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Wed, 5 Apr 2023 16:11:56 +0200 Subject: [PATCH 10/13] chore: add build time sanity check for self-learning contents --- packages/gatsby-theme-docs/gatsby-node.mjs | 58 +++++++++++++++++-- .../src/hooks/use-course-pages.js | 11 ---- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/gatsby-theme-docs/gatsby-node.mjs b/packages/gatsby-theme-docs/gatsby-node.mjs index cb886e395b..0296194152 100644 --- a/packages/gatsby-theme-docs/gatsby-node.mjs +++ b/packages/gatsby-theme-docs/gatsby-node.mjs @@ -47,6 +47,40 @@ const debugMem = () => { } }; +/** + * Given navigation and content nodes, checks if all the content nodes + * belonging to a single self-learning chapter (course) have the same courseId. + */ +const validateSelfLearningContentStructure = ( + allNavigationNodes, + allContentNodes +) => { + // get only self learning content + const slugToCourseMap = new Map(); + allContentNodes + .filter((contentNode) => contentNode.courseId) + .forEach((contentNode) => + slugToCourseMap.set(contentNode.slug, contentNode.courseId) + ); + + allNavigationNodes.forEach((navigationNode) => { + let prevCourseId = undefined; + const chapterTitle = navigationNode.chapterTitle; + navigationNode.pages.forEach((page) => { + if (!slugToCourseMap.has(page.path)) { + return; // the path is not self-learning content + } + const courseId = slugToCourseMap.get(page.path); + if (prevCourseId !== undefined && prevCourseId !== courseId) { + const msg = `Mismatch self-learning courseId property (${courseId}) found in topic with slug ${page.path}. All topics within a single course should referece the same courseId, please check the frontmatter sections within the "${chapterTitle}" course`; + throw new Error(msg); + } else { + prevCourseId = courseId; + } + }); + }); +}; + // Ensure that certain directories exist. // https://www.gatsbyjs.org/tutorial/building-a-theme/#create-a-data-directory-using-the-onprebootstrap-lifecycle export const onPreBootstrap = async (gatsbyApi, themeOptions) => { @@ -414,8 +448,8 @@ async function createContentPages( allContentPage { nodes { slug - courseId, - topicName, + courseId + topicName } } allReleaseNotePage(sort: { date: DESC }) { @@ -446,6 +480,11 @@ async function createContentPages( if (navigationYamlResult.errors) { reporter.panicOnBuild('🚨 ERROR: Loading "allNavigationYaml" query'); } + // validate self-learning content structure + validateSelfLearningContentStructure( + navigationYamlResult.data.allNavigationYaml.nodes, + result.data.allContentPage.nodes + ); const pages = result.data.allContentPage.nodes; const navigationPages = navigationYamlResult.data.allNavigationYaml.nodes.reduce( @@ -494,11 +533,18 @@ async function createContentPages( let contentPageData = { ...pageData, component: require.resolve('./src/templates/page-content.js'), - } + }; if (courseId) { - contentPageData = {...contentPageData, context: {...contentPageData.context, courseId}} - }if (topicName) { - contentPageData = {...contentPageData, context: {...contentPageData.context, topicName}} + contentPageData = { + ...contentPageData, + context: { ...contentPageData.context, courseId }, + }; + } + if (topicName) { + contentPageData = { + ...contentPageData, + context: { ...contentPageData.context, topicName }, + }; } actions.createPage(contentPageData); diff --git a/packages/gatsby-theme-docs/src/hooks/use-course-pages.js b/packages/gatsby-theme-docs/src/hooks/use-course-pages.js index df36f921ce..67726f70c6 100644 --- a/packages/gatsby-theme-docs/src/hooks/use-course-pages.js +++ b/packages/gatsby-theme-docs/src/hooks/use-course-pages.js @@ -43,16 +43,5 @@ export const useCourseInfoByPageSlugs = (pageSlugs) => { (prev, curr) => ({ ...prev, [curr]: coursePageMap.get(curr) }), {} ); - // sanity check: all the pages should belong to the same course, therefore have the same courseId - const isOk = Object.values(courseInfo)?.every( - (info, _, theArray) => info?.courseId === theArray[0]?.courseId - ); - if (!isOk) { - // TODO: decide if we want to make this blocker - console.warn( - 'pages belonging to the same course have different courseId metadata', - courseInfo - ); - } return courseInfo; }; From c46458ac5d2fa5c10e8e9ce95dd3749518252116 Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Mon, 17 Apr 2023 12:08:12 +0200 Subject: [PATCH 11/13] chore: added unknown empty state spacer to avoid layout shifts during loading --- .../src/components/sidebar-course-status.tsx | 24 ++++++++++++++----- .../src/components/sidebar-topic-status.tsx | 23 +++++++++++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx index ad66e741cb..bdffe27c67 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx @@ -9,17 +9,29 @@ import ConfigContext, { isFeatureEnabled, EFeatureFlag, } from './config-context'; +import styled from '@emotion/styled'; +import { designSystem } from '@commercetools-docs/ui-kit'; + +const UnknownStateSpacer = styled.div` + width: ${designSystem.dimensions.spacings.l}; + margin-left: 2px; +`; type StatusIndicatorProps = { status?: string; }; -export const StatusIndicator = (props: StatusIndicatorProps) => - props.status && props.status === 'completed' ? ( - - ) : ( - - ); +export const StatusIndicator = (props: StatusIndicatorProps) => { + console.log(props.status); + switch (props.status) { + case 'completed': + return ; + case 'inProgress': + return ; + default: + return ; + } +}; type SidebarCourseStatusProps = { courseId: number; diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx index 5149947fc1..4f50c03636 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -9,17 +9,28 @@ import ConfigContext, { isFeatureEnabled, EFeatureFlag, } from './config-context'; +import { designSystem } from '@commercetools-docs/ui-kit'; +import styled from '@emotion/styled'; + +const UnknownStateSpacer = styled.div` + width: ${designSystem.dimensions.spacings.m}; + margin-left: 5px; +`; type StatusIndicatorProps = { status?: string; }; -export const StatusIndicator = (props: StatusIndicatorProps) => - props.status && props.status === 'completed' ? ( - - ) : ( - - ); +export const StatusIndicator = (props: StatusIndicatorProps) => { + switch (props.status) { + case 'completed': + return ; + case 'notCompleted': + return ; + default: + return ; + } +}; type PageTopicStatusProps = { courseId: number; From 9546a0b32903c724cf581b475c3d103c45dd12eb Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Mon, 17 Apr 2023 23:07:49 +0200 Subject: [PATCH 12/13] chore: fix layout shift --- .../src/components/sidebar-course-status.tsx | 12 ++---------- .../src/components/sidebar-topic-status.tsx | 7 +------ 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx index bdffe27c67..ee7fa25a2f 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx @@ -1,5 +1,4 @@ import React, { useContext } from 'react'; -import { useAuth0 } from '@auth0/auth0-react'; import { CircleIcon, VerifiedIcon } from '@commercetools-uikit/icons'; import { getCourseStatusByCourseId, @@ -14,7 +13,7 @@ import { designSystem } from '@commercetools-docs/ui-kit'; const UnknownStateSpacer = styled.div` width: ${designSystem.dimensions.spacings.l}; - margin-left: 2px; + margin-left: 5px; `; type StatusIndicatorProps = { @@ -38,7 +37,6 @@ type SidebarCourseStatusProps = { }; const SidebarCourseStatus = (props: SidebarCourseStatusProps) => { - const { isAuthenticated } = useAuth0(); const { data } = useFetchCourses(); const { features } = useContext(ConfigContext); @@ -50,13 +48,7 @@ const SidebarCourseStatus = (props: SidebarCourseStatusProps) => { const courseStatus = data?.result?.enrolledCourses ? getCourseStatusByCourseId(data.result.enrolledCourses, props.courseId) : undefined; - return ( - <> - {props.courseId && isAuthenticated && ( - - )} - - ); + return <>{props.courseId && }; }; export default SidebarCourseStatus; diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx index 4f50c03636..e0408217f4 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; import { CheckActiveIcon, CircleIcon } from '@commercetools-uikit/icons'; -import { useAuth0 } from '@auth0/auth0-react'; import { useFetchCourseDetails, getTopicStatusByPageTitle, @@ -38,7 +37,6 @@ type PageTopicStatusProps = { }; const SidebarTopicStatus = (props: PageTopicStatusProps) => { - const { isAuthenticated } = useAuth0(); const { data } = useFetchCourseDetails(props.courseId); const { features } = useContext(ConfigContext); @@ -51,10 +49,7 @@ const SidebarTopicStatus = (props: PageTopicStatusProps) => { ? getTopicStatusByPageTitle(data.result.topics, props.pageTitle) : undefined; - return ( - props.courseId && - isAuthenticated && - ); + return props.courseId && ; }; export default SidebarTopicStatus; From 33964e770bdeb01ef7d06ee8f2f2320f1f241dbe Mon Sep 17 00:00:00 2001 From: Gabriele Antonini Date: Mon, 17 Apr 2023 23:16:40 +0200 Subject: [PATCH 13/13] chore: fix layout shift --- .../src/components/sidebar-course-status.tsx | 2 +- .../src/components/sidebar-topic-status.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx index ee7fa25a2f..0cf3d1b088 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-course-status.tsx @@ -13,7 +13,7 @@ import { designSystem } from '@commercetools-docs/ui-kit'; const UnknownStateSpacer = styled.div` width: ${designSystem.dimensions.spacings.l}; - margin-left: 5px; + margin-right: 5px; `; type StatusIndicatorProps = { diff --git a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx index e0408217f4..af1ac2f161 100644 --- a/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx +++ b/packages/gatsby-theme-learning/src/components/sidebar-topic-status.tsx @@ -13,7 +13,7 @@ import styled from '@emotion/styled'; const UnknownStateSpacer = styled.div` width: ${designSystem.dimensions.spacings.m}; - margin-left: 5px; + margin-right: 5px; `; type StatusIndicatorProps = {