From 4451386a3950f830b4153c916dbf55d856483e9a Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Wed, 7 Sep 2022 06:12:45 -0700 Subject: [PATCH 1/8] Add DOJO exercises nav --- pages/exercises/[lessonSlug].tsx | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/pages/exercises/[lessonSlug].tsx b/pages/exercises/[lessonSlug].tsx index 331d7006c..a93b1e923 100644 --- a/pages/exercises/[lessonSlug].tsx +++ b/pages/exercises/[lessonSlug].tsx @@ -9,6 +9,7 @@ import Error, { StatusCode } from '../../components/Error' import LoadingSpinner from '../../components/LoadingSpinner' import GET_APP from '../../graphql/queries/getApp' import AlertsDisplay from '../../components/AlertsDisplay' +import NavLink from '../../components/NavLink' const Exercises: React.FC> = ({ queryData }) => { const { lessons, alerts } = queryData @@ -25,12 +26,48 @@ const Exercises: React.FC> = ({ queryData }) => { return ( +

{currentLesson.title}

{alerts && }
) } +type ExercisesTitleCardProps = { + lessonDocUrl: string | null | undefined + lessonSlug: string +} + +const ExercisesTitleCard = ({ + lessonDocUrl, + lessonSlug +}: ExercisesTitleCardProps) => { + return ( +
+ {lessonDocUrl && ( + + LESSONS + + )} + + CHALLENGES + +
+ EXERCISES +
+
+ ) +} + export default withQueryLoader( { query: GET_APP From e8919493129cf437328d6fa27f69b1c46a34c27c Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Wed, 7 Sep 2022 06:50:56 -0700 Subject: [PATCH 2/8] Rename ExercisesTitleCard to ExercisesNavCard --- pages/exercises/[lessonSlug].tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pages/exercises/[lessonSlug].tsx b/pages/exercises/[lessonSlug].tsx index a93b1e923..08aff5e2a 100644 --- a/pages/exercises/[lessonSlug].tsx +++ b/pages/exercises/[lessonSlug].tsx @@ -26,25 +26,22 @@ const Exercises: React.FC> = ({ queryData }) => { return ( - +

{currentLesson.title}

{alerts && }
) } -type ExercisesTitleCardProps = { +type ExercisesNavCardProps = { lessonDocUrl: string | null | undefined lessonSlug: string } -const ExercisesTitleCard = ({ +const ExercisesNavCard = ({ lessonDocUrl, lessonSlug -}: ExercisesTitleCardProps) => { +}: ExercisesNavCardProps) => { return (
{lessonDocUrl && ( From f37293037c841a3cfd26b8b841f4b531b42f506b Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Thu, 8 Sep 2022 04:07:01 -0700 Subject: [PATCH 3/8] Make AdminLessonNav more generic --- .../__snapshots__/storyshots.test.js.snap | 78 +++++++++---------- .../[lessonSlug]/[pageName]/modules.test.js | 2 +- components/NavCard.tsx | 46 +++++++++++ .../lessons/AdminLessonSideNavLayout.tsx | 25 ------ .../lessons/[lessonSlug]/[pageName]/index.tsx | 33 ++------ ...sonNav.module.scss => navCard.module.scss} | 16 ++-- .../AdminLessonSideNavLayout.stories.tsx | 55 ------------- stories/components/NavCard.stories.tsx | 29 +++++++ 8 files changed, 125 insertions(+), 159 deletions(-) create mode 100644 components/NavCard.tsx delete mode 100644 components/admin/lessons/AdminLessonSideNavLayout.tsx rename scss/{adminLessonNav.module.scss => navCard.module.scss} (83%) delete mode 100644 stories/components/AdminLessonSideNavLayout.stories.tsx create mode 100644 stories/components/NavCard.stories.tsx diff --git a/__tests__/__snapshots__/storyshots.test.js.snap b/__tests__/__snapshots__/storyshots.test.js.snap index 56c73e1ae..bc233d808 100644 --- a/__tests__/__snapshots__/storyshots.test.js.snap +++ b/__tests__/__snapshots__/storyshots.test.js.snap @@ -866,46 +866,6 @@ Array [ ] `; -exports[`Storyshots Components/AdminLessonNav Basic 1`] = ` - -`; - exports[`Storyshots Components/AdminLessonSideNav Basic 1`] = `
`; +exports[`Storyshots Components/NavCard Basic 1`] = ` + +`; + exports[`Storyshots Components/NavLink Active Link 1`] = ` Array [ { await act(() => new Promise(res => setTimeout(res, 0))) expect(screen.getByText('MODULES').className).not.toEqual( - 'lessons_tabsNav__nav__item' + 'navCard__tabsNav__nav__item' ) }) }) diff --git a/components/NavCard.tsx b/components/NavCard.tsx new file mode 100644 index 000000000..1dd848fd8 --- /dev/null +++ b/components/NavCard.tsx @@ -0,0 +1,46 @@ +import Link from 'next/link' +import React from 'react' +import styles from '../scss/navCard.module.scss' + +type Tab = { + text: string + url: string + isSelected: boolean +} + +type NavCardProps = { + tabs: Tab[] +} + +const NavCard = ({ tabs }: NavCardProps) => { + return ( +
+
+ {tabs.map((tab, i) => ( + + ))} +
+
+ ) +} + +type NavCardItemProps = { + tab: Tab +} + +const NavCardItem = ({ tab }: NavCardItemProps) => { + const className = + styles[ + tab.isSelected + ? 'navCard__tabsNav__nav__item' + : 'navCard__tabsNav__nav__item--inactive' + ] + + return ( + +
{tab.text.toUpperCase()} + + ) +} + +export default NavCard diff --git a/components/admin/lessons/AdminLessonSideNavLayout.tsx b/components/admin/lessons/AdminLessonSideNavLayout.tsx deleted file mode 100644 index 3827fe4cb..000000000 --- a/components/admin/lessons/AdminLessonSideNavLayout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { FunctionComponent } from 'react' -import styles from '../../../scss/adminLessonNav.module.scss' - -type Tab = { tabName: string; urlPageName: string } - -type Props = { - tabs: Tab[] - Component: FunctionComponent<{ tab: Tab }> -} - -const AdminLessonNav: React.FC = ({ tabs, Component }: Props) => { - return ( -
-
-
- {tabs.map((tab, i) => ( - - ))} -
-
-
- ) -} - -export default AdminLessonNav diff --git a/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx b/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx index ed7b7283f..b98c978a6 100644 --- a/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx +++ b/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx @@ -1,18 +1,15 @@ import { gql, useQuery } from '@apollo/client' import { GetAppProps, withGetApp } from '../../../../../graphql' -import { toUpper } from 'lodash' import React, { useMemo, useState } from 'react' -import AdminLessonNav from '../../../../../components/admin/lessons/AdminLessonSideNavLayout' import AdminLessonSideNav from '../../../../../components/admin/lessons/AdminLessonSideNav' import AdminLessonInputs from '../../../../../components/admin/lessons/AdminLessonInputs' import { Props } from '../../../../../components/admin/lessons/AdminLessonInputs/AdminLessonInputs' import Breadcrumbs from '../../../../../components/Breadcrumbs' import styles from '../../../../../scss/modules.module.scss' -import navStyles from '../../../../../scss/adminLessonNav.module.scss' import { AdminLayout } from '../../../../../components/admin/AdminLayout' import { useRouter } from 'next/router' -import Link from 'next/link' import { compose, filter, get, sortBy } from 'lodash/fp' +import NavCard from '../../../../../components/NavCard' const MAIN_PATH = '/admin/lessons' @@ -114,26 +111,6 @@ const Lessons = ({ data }: GetAppProps) => { return sortModules(modulesData) }, [lesson.id, get('modules', modulesData)]) - const LessonNav = ({ - tab - }: { - tab: { tabName: string; urlPageName: string } - }) => { - const isSelected = tab.urlPageName === pageName - const className = - navStyles[ - isSelected - ? 'lessons_tabsNav__nav__item' - : 'lessons_tabsNav__nav__item--inactive' - ] - - return ( - - {toUpper(tab.tabName)} - - ) - } - return (
@@ -148,14 +125,14 @@ const Lessons = ({ data }: GetAppProps) => { />
-
diff --git a/scss/adminLessonNav.module.scss b/scss/navCard.module.scss similarity index 83% rename from scss/adminLessonNav.module.scss rename to scss/navCard.module.scss index bd05a1baa..7f2985c0c 100644 --- a/scss/adminLessonNav.module.scss +++ b/scss/navCard.module.scss @@ -6,7 +6,7 @@ $light: hsl(0, 0%, 98.4%); $dark_primary: color.adjust(variables.$primary, $lightness: -20%); $light_primary: color.adjust(variables.$primary, $lightness: 28%); -.lessons_tabsNav { +.navCard__tabsNav { font-family: 'PT Mono'; max-width: fit-content; padding: 8px 16px; @@ -20,7 +20,7 @@ $light_primary: color.adjust(variables.$primary, $lightness: 28%); border: 1px solid $light_primary; } -.lessons__tabsNav__nav { +.navCard__tabsNav__nav { display: flex; column-gap: 20px; justify-content: center; @@ -28,7 +28,7 @@ $light_primary: color.adjust(variables.$primary, $lightness: 28%); flex-wrap: nowrap; } -.lessons_tabsNav__nav__item { +.navCard__tabsNav__nav__item { padding: 7px 16px; border-radius: 4px; background-color: variables.$primary; @@ -39,11 +39,11 @@ $light_primary: color.adjust(variables.$primary, $lightness: 28%); font-size: 14px; } -.lessons_tabsNav__nav__item:hover { +.navCard__tabsNav__nav__item:hover { color: white; } -.lessons_tabsNav__nav__item--inactive { +.navCard__tabsNav__nav__item--inactive { padding: 7px 16px; border-radius: 4px; color: $dark_primary; @@ -55,12 +55,8 @@ $light_primary: color.adjust(variables.$primary, $lightness: 28%); font-size: 14px; } -.lessons_tabsNav__nav__item--inactive:hover { +.navCard__tabsNav__nav__item--inactive:hover { background-color: color.change(variables.$primary, $lightness: 95%); color: $dark_primary; transition: background-color 0.3s ease-out; } - -.lessons_tabs { - margin-top: 40px; -} diff --git a/stories/components/AdminLessonSideNavLayout.stories.tsx b/stories/components/AdminLessonSideNavLayout.stories.tsx deleted file mode 100644 index af9004995..000000000 --- a/stories/components/AdminLessonSideNavLayout.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { toUpper } from 'lodash' -import Link from 'next/link' -import { useRouter } from 'next/router' -import * as React from 'react' -import AdminLessonNav from '../../components/admin/lessons/AdminLessonSideNavLayout' -import styles from '../../scss/adminLessonNav.module.scss' - -export default { - component: AdminLessonNav, - title: 'Components/AdminLessonNav' -} - -export const Basic = () => { - const router = useRouter() - - const SideNavComponent = ({ - tab - }: { - tab: { tabName: string; urlPageName: string } - }) => { - const isSelected = router.asPath.split('/').slice(-1)[0] === tab.urlPageName - - const className = - styles[ - isSelected - ? 'lessons_tabsNav__nav__item' - : 'lessons_tabsNav__nav__item--inactive' - ] - - return ( - - {toUpper(tab.tabName)} - - ) - } - - return ( - - ) -} - -const parameters = { - nextRouter: { - asPath: 'c0d3.com/admin/lessons/1/introduction' - } -} - -Basic.parameters = parameters diff --git a/stories/components/NavCard.stories.tsx b/stories/components/NavCard.stories.tsx new file mode 100644 index 000000000..bc3a925b6 --- /dev/null +++ b/stories/components/NavCard.stories.tsx @@ -0,0 +1,29 @@ +import { useRouter } from 'next/router' +import * as React from 'react' +import NavCard from '../../components/NavCard' + +export default { + component: NavCard, + title: 'Components/NavCard' +} + +export const Basic = () => { + const router = useRouter() + const pageName = router.asPath.split('/').slice(-1)[0] + const tabTexts = ['introduction', 'modules', 'challenges'] + const textToTab = (text: string) => ({ + text, + url: `/admin/lessons/1/${text}`, + isSelected: text === pageName + }) + + return +} + +const parameters = { + nextRouter: { + asPath: 'c0d3.com/admin/lessons/1/introduction' + } +} + +Basic.parameters = parameters From de4a6ace6c5585f1442aad806da261625a03d7af Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Thu, 8 Sep 2022 23:55:54 -0700 Subject: [PATCH 4/8] Use tabSelected in NavCard to make invalid states impossible --- components/NavCard.tsx | 11 ++++++----- .../lessons/[lessonSlug]/[pageName]/index.tsx | 18 +++++++++--------- stories/components/NavCard.stories.tsx | 6 +++--- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/components/NavCard.tsx b/components/NavCard.tsx index 1dd848fd8..de01356c8 100644 --- a/components/NavCard.tsx +++ b/components/NavCard.tsx @@ -5,19 +5,19 @@ import styles from '../scss/navCard.module.scss' type Tab = { text: string url: string - isSelected: boolean } type NavCardProps = { + tabSelected: number tabs: Tab[] } -const NavCard = ({ tabs }: NavCardProps) => { +const NavCard = ({ tabSelected, tabs }: NavCardProps) => { return (
{tabs.map((tab, i) => ( - + ))}
@@ -25,13 +25,14 @@ const NavCard = ({ tabs }: NavCardProps) => { } type NavCardItemProps = { + isSelected: boolean tab: Tab } -const NavCardItem = ({ tab }: NavCardItemProps) => { +const NavCardItem = ({ isSelected, tab }: NavCardItemProps) => { const className = styles[ - tab.isSelected + isSelected ? 'navCard__tabsNav__nav__item' : 'navCard__tabsNav__nav__item--inactive' ] diff --git a/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx b/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx index b98c978a6..1a1bd9001 100644 --- a/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx +++ b/pages/admin/lessons/[lessonSlug]/[pageName]/index.tsx @@ -111,6 +111,14 @@ const Lessons = ({ data }: GetAppProps) => { return sortModules(modulesData) }, [lesson.id, get('modules', modulesData)]) + const tabs = [ + { + text: 'modules', + url: `${MAIN_PATH}/${lesson.slug}/modules` + } + ] + const tabSelected = tabs.findIndex(tab => tab.text === pageName) + return (
@@ -125,15 +133,7 @@ const Lessons = ({ data }: GetAppProps) => { />
- +
{ const router = useRouter() const pageName = router.asPath.split('/').slice(-1)[0] const tabTexts = ['introduction', 'modules', 'challenges'] + const tabSelected = tabTexts.findIndex(text => text === pageName) const textToTab = (text: string) => ({ text, - url: `/admin/lessons/1/${text}`, - isSelected: text === pageName + url: `/admin/lessons/1/${text}` }) - return + return } const parameters = { From d5270c7b1ddc710b37f85f5ea1a234a75464feb8 Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Fri, 9 Sep 2022 00:04:46 -0700 Subject: [PATCH 5/8] Refactor NavCard.stories.tsx --- stories/components/NavCard.stories.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/stories/components/NavCard.stories.tsx b/stories/components/NavCard.stories.tsx index 339d0b1b3..62b96ef9a 100644 --- a/stories/components/NavCard.stories.tsx +++ b/stories/components/NavCard.stories.tsx @@ -10,14 +10,13 @@ export default { export const Basic = () => { const router = useRouter() const pageName = router.asPath.split('/').slice(-1)[0] - const tabTexts = ['introduction', 'modules', 'challenges'] - const tabSelected = tabTexts.findIndex(text => text === pageName) - const textToTab = (text: string) => ({ + const tabs = ['introduction', 'modules', 'challenges'].map(text => ({ text, url: `/admin/lessons/1/${text}` - }) + })) + const tabSelected = tabs.findIndex(tab => tab.text === pageName) - return + return } const parameters = { From 0726e527f2a4d8b99ac4bb68550f1d8d2ce7cc18 Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Fri, 9 Sep 2022 05:39:13 -0700 Subject: [PATCH 6/8] Use NavCard on DOJO exercises page --- pages/exercises/[lessonSlug].tsx | 49 +++++++++----------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/pages/exercises/[lessonSlug].tsx b/pages/exercises/[lessonSlug].tsx index 08aff5e2a..f00718cb3 100644 --- a/pages/exercises/[lessonSlug].tsx +++ b/pages/exercises/[lessonSlug].tsx @@ -9,7 +9,7 @@ import Error, { StatusCode } from '../../components/Error' import LoadingSpinner from '../../components/LoadingSpinner' import GET_APP from '../../graphql/queries/getApp' import AlertsDisplay from '../../components/AlertsDisplay' -import NavLink from '../../components/NavLink' +import NavCard from '../../components/NavCard' const Exercises: React.FC> = ({ queryData }) => { const { lessons, alerts } = queryData @@ -24,47 +24,26 @@ const Exercises: React.FC> = ({ queryData }) => { if (!currentLesson) return + const tabs = [ + { text: 'lessons', url: `/curriculum/${currentLesson.slug}` }, + ...(currentLesson.docUrl + ? [{ text: 'challenges', url: currentLesson.docUrl }] + : []), + { text: 'exercises', url: `/exercises/${currentLesson.slug}` } + ] + return ( - -

{currentLesson.title}

+ tab.text === 'exercises')} + tabs={tabs} + /> +

{currentLesson.title}

{alerts && }
) } -type ExercisesNavCardProps = { - lessonDocUrl: string | null | undefined - lessonSlug: string -} - -const ExercisesNavCard = ({ - lessonDocUrl, - lessonSlug -}: ExercisesNavCardProps) => { - return ( -
- {lessonDocUrl && ( - - LESSONS - - )} - - CHALLENGES - -
- EXERCISES -
-
- ) -} - export default withQueryLoader( { query: GET_APP From cca5524b77c2f5ca47ad304c1099b44099696de3 Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Fri, 9 Sep 2022 06:25:12 -0700 Subject: [PATCH 7/8] Fix DOJO exercise NavCard links --- .../pages/exercises/[lessonSlug].test.js | 33 +++++++++++++++++++ pages/exercises/[lessonSlug].tsx | 4 +-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/__tests__/pages/exercises/[lessonSlug].test.js b/__tests__/pages/exercises/[lessonSlug].test.js index ac2fb5cac..36ee7e72a 100644 --- a/__tests__/pages/exercises/[lessonSlug].test.js +++ b/__tests__/pages/exercises/[lessonSlug].test.js @@ -18,6 +18,7 @@ const session = { describe('Exercises page', () => { const { query } = useRouter() query['lessonSlug'] = 'js0' + test('Should render correctly', async () => { const mocks = [ { @@ -41,6 +42,38 @@ describe('Exercises page', () => { await waitFor(() => screen.getByRole('heading', { name: /Foundations of JavaScript/i }) ) + + screen.getByRole('link', { name: 'CHALLENGES' }) + screen.getByRole('link', { name: 'EXERCISES' }) + screen.getByRole('link', { name: 'LESSONS' }) + }) + + test('Should not render lessons nav card tab if lesson docUrl is null', async () => { + const mocks = [ + { + request: { query: GET_APP }, + result: { + data: { + session, + lessons: dummyLessonData.map(lesson => ({ + ...lesson, + docUrl: null + })), + alerts: dummyAlertData + } + } + } + ] + + render( + + + + ) + + await waitFor(() => screen.getByRole('link', { name: 'CHALLENGES' })) + screen.getByRole('link', { name: 'EXERCISES' }) + expect(screen.queryByRole('link', { name: 'LESSONS' })).toBeNull() }) test('Should render a 500 error page if the lesson data is null', async () => { diff --git a/pages/exercises/[lessonSlug].tsx b/pages/exercises/[lessonSlug].tsx index f00718cb3..df79c6f19 100644 --- a/pages/exercises/[lessonSlug].tsx +++ b/pages/exercises/[lessonSlug].tsx @@ -25,10 +25,10 @@ const Exercises: React.FC> = ({ queryData }) => { return const tabs = [ - { text: 'lessons', url: `/curriculum/${currentLesson.slug}` }, ...(currentLesson.docUrl - ? [{ text: 'challenges', url: currentLesson.docUrl }] + ? [{ text: 'lessons', url: currentLesson.docUrl }] : []), + { text: 'challenges', url: `/curriculum/${currentLesson.slug}` }, { text: 'exercises', url: `/exercises/${currentLesson.slug}` } ] From 273fff46b7aaf3ac2e7d88766c9d7a50f94758f6 Mon Sep 17 00:00:00 2001 From: Bryan Jennings Date: Fri, 9 Sep 2022 20:31:21 -0700 Subject: [PATCH 8/8] Make DOJO exercises tests look more consistent --- __tests__/pages/exercises/[lessonSlug].test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/__tests__/pages/exercises/[lessonSlug].test.js b/__tests__/pages/exercises/[lessonSlug].test.js index 36ee7e72a..c309ff611 100644 --- a/__tests__/pages/exercises/[lessonSlug].test.js +++ b/__tests__/pages/exercises/[lessonSlug].test.js @@ -71,7 +71,11 @@ describe('Exercises page', () => { ) - await waitFor(() => screen.getByRole('link', { name: 'CHALLENGES' })) + await waitFor(() => + screen.getByRole('heading', { name: /Foundations of JavaScript/i }) + ) + + screen.getByRole('link', { name: 'CHALLENGES' }) screen.getByRole('link', { name: 'EXERCISES' }) expect(screen.queryByRole('link', { name: 'LESSONS' })).toBeNull() })