diff --git a/src/api/lessons.ts b/src/api/lessons.ts index f7156bc..e7d414f 100644 --- a/src/api/lessons.ts +++ b/src/api/lessons.ts @@ -114,6 +114,7 @@ interface SavedLessonProgress { interface DisplayedLessonProgress { progress: number; + locked: boolean; } export interface LessonListItem @@ -161,6 +162,7 @@ export async function listLessons( return { ...lesson, progress: computeLessonProgress(progressData, lesson.id), + locked: false, // Temporary, will be updated below }; }); @@ -178,7 +180,24 @@ export async function listLessons( return { name, displayName, - lessons: sectionLessons, + lessons: sectionLessons.map((lesson, index) => { + let locked; + + if (index === 0) { + // First lesson is always unlocked + locked = false; + } else { + // Previous lesson isn't completed + locked = + computeLessonProgress( + progressData, + sectionLessons[index - 1].id, + ) < 100; + } + + lesson.locked = locked; + return lesson; + }), }; }); } catch (error) { diff --git a/src/components/I18N/I18N.tsx b/src/components/I18N/I18N.tsx index 32db743..06932a7 100644 --- a/src/components/I18N/I18N.tsx +++ b/src/components/I18N/I18N.tsx @@ -45,6 +45,10 @@ const dictionary: Record< en: 'Lessons', ru: 'Уроки', }, + 'lesson-list-locked-message': { + en: 'Complete the previous lesson to unlock this one. Fill the progress bar.', + ru: 'Сначала нужно полностью пройти предыдущий урок. Заполните весь прогресс-бар.', + }, 'lesson-submit-button': { en: 'Check', ru: 'Проверить', @@ -85,6 +89,7 @@ const dictionary: Record< en: 'See other lessons', ru: 'Посмотреть другие уроки', }, + 'conversation-list-heading': { en: 'Conversations', ru: 'Разговоры', diff --git a/src/routes/LessonList.tsx b/src/routes/LessonList.tsx index 502689d..760a924 100644 --- a/src/routes/LessonList.tsx +++ b/src/routes/LessonList.tsx @@ -1,34 +1,35 @@ import { styled } from 'styled-components'; import { Link } from 'react-router-dom'; -import { CircularProgress, LinearProgress } from '@mui/material'; +import { + CircularProgress, + LinearProgress, + linearProgressClasses, +} from '@mui/material'; import { useQuery } from '@tanstack/react-query'; +import LockIcon from '@mui/icons-material/Lock'; +import Tooltip from '@mui/material/Tooltip'; import { listLessons, LessonListItem } from '@api/lessons'; import { CardListItem, CardList } from '@components/CardList'; -import { EllipsisTypography } from '@components/EllispsisTypography'; import { Text } from '@components/Text/Text'; import { Heading } from '@components/Heading'; +import { I18N, I18NLangs } from '@components/I18N/I18N'; // TODO: scroll to current active lesson on mobile and current active section on desktop +// TODO current active section is the first section with a locked lesson -const LessonProgress = styled(LinearProgress)` - && { - position: absolute; - bottom: 0; - left: 0; - right: 0; - - height: 0.5rem; - } -`; +interface LessonCardProps { + locked: boolean; +} -const CardLink = styled(Link)` +const LessonLink = styled(Link)` + ${(props) => props.locked && 'pointer-events: none;'} display: block; height: 100%; text-decoration: none; `; -const CustomCard = styled.div` +const LessonCard = styled.div` position: relative; width: 100%; @@ -44,28 +45,95 @@ const CustomCard = styled.div` 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12); border-radius: 0.25rem; - background-color: rgba(217, 175, 207, 0.2); + color: var(--text-color); + background-color: ${(props) => + props.locked ? 'rgba(208,208,208, 0.3)' : 'rgba(217, 175, 207, 0.2)'}; overflow: hidden; `; +const LessonHeading = styled(Heading)` + && { + display: flex; + align-items: center; + white-space: nowrap; + margin-bottom: 0.5rem; + } +`; + +const LessonHeadingIcon = styled.span` + &:not(:empty) { + pointer-events: auto; + margin-bottom: -6px; + margin-right: 0.25rem; + } +`; + +const LessonHeadingText = styled.span` + flex-shrink: 1; + overflow: hidden; + text-overflow: ellipsis; +`; + +const LessonProgress = styled(LinearProgress)` + && { + position: absolute; + bottom: 0; + left: 0; + right: 0; + + height: 0.5rem; + } + + &.${linearProgressClasses.colorPrimary} { + background-color: ${(props) => + props.locked ? 'rgb(205, 205, 205)' : 'rgb(217, 175, 206)'}; + } +`; + +// TODO tooltip for locked and completed lessons when hovering over the icon function renderLessonList(lessons: LessonListItem[]) { return lessons.map((lesson) => ( - - - - {lesson.displayTopic} · {lesson.displayName} - + + + + + } + enterTouchDelay={0} + enterDelay={100} + leaveDelay={100} + arrow + > + + {lesson.locked && ( + + )} + + + + + {lesson.displayTopic} · {lesson.displayName} + + {lesson.description} - - + + )); } @@ -103,6 +171,8 @@ export const LessonList = () => { ); } + console.log(sections); + return sections.map((section) => ( {section.displayName}