diff --git a/lib/course.js b/lib/course.js index 3085a185..94073917 100644 --- a/lib/course.js +++ b/lib/course.js @@ -6,9 +6,9 @@ import { useTranslation } from 'react-i18next' const fetch = require('node-fetch') -async function getPage(course, section, page) { +export async function getPage(course, section, lesson, language) { const commit = await lastCommitId(user, repo, branch) - const url = `https://cdn.rawgit.com/${user}/${repo}/${commit}/${course.id}/${course.repository_language}/${section}/${page}` + const url = `https://cdn.rawgit.com/${user}/${repo}/${commit}/${course}/${language}/${section}/${lesson}` const result = await fetch(url) const text = await result.text() return text @@ -28,11 +28,7 @@ async function getCourseLessons(course) { }) .flat() - return await Promise.all( - lessons.map(async (l) => { - return { ...l, markdown: await getPage(course, l.section, l.lesson) } - }) - ) + return lessons } export async function getStudyGroup(groupSlug) { diff --git a/pages/courses/[id].js b/pages/courses/[id].js index 75ec3caf..1ccdd2f5 100644 --- a/pages/courses/[id].js +++ b/pages/courses/[id].js @@ -33,6 +33,7 @@ function Course({ course, currentDate }) { const [lessonsSubmitted, setLessonsSubmitted] = useState() const [loading, setLoading] = useState(true) const { t, i18n } = useTranslation() + const language = i18n.resolvedLanguage let counter = 0 useEffect(async () => { @@ -408,7 +409,7 @@ function Course({ course, currentDate }) {
1 ? 'pointer-events-none' : ''}>

{lesson.title}

diff --git a/pages/courses/[id]/[section]/[lesson].js b/pages/courses/[id]/[section]/[lesson].js new file mode 100644 index 00000000..fdbbb120 --- /dev/null +++ b/pages/courses/[id]/[section]/[lesson].js @@ -0,0 +1,308 @@ +import Head from 'next/head' +import ReactMarkdown from 'react-markdown' +import { Button, Text } from '@nextui-org/react' +import Modal from '../../../../components/Modal' +import { withProtected } from '../../../../hooks/route' +import { getCourse, getPage } from '../../../../lib/course' +import React, { useState, useEffect } from 'react' +import { getLessonsSubmissions } from '../../../../lib/lessons' +import Tabs from '../../../../components/Tabs' +import { getAllCohorts, getCurrentCohort } from '../../../../lib/cohorts' +import { useRouter } from 'next/router' +import { toast } from 'react-toastify' +import rehypeRaw from 'rehype-raw' +import rehypePrism from 'rehype-prism-plus' +import remarkGfm from 'remark-gfm' +import TwitterModal from '../../../../components/TwitterModal.js' +import { getUserFromFirestore } from '../../../../lib/user' +import { auth } from '../../../../firebase/initFirebase' +import { MdAdsClick } from 'react-icons/md' +import { Container } from '@nextui-org/react' +import { useTranslation } from 'react-i18next' + +function Lessons({ course, lesson, content, currentDate }) { + const [open, setOpen] = useState(false) + const [lessonSent, setLessonSent] = useState(false) + const [userSubmission, setUserSubmission] = useState() + const [sortedLessons, setSortedLessons] = useState([]) + const [url, setUrl] = useState() + const [cohorts, setCohorts] = useState() + const [cohort, setCohort] = useState() + const [submissionType, setSubmissionType] = useState() + const [submissionTitle, setSubmissionTitle] = useState() + const [submissionText, setSubmissionText] = useState() + const [lessonsSubmitted, setLessonsSubmitted] = useState([]) + const [twitterShare, setTwitterShare] = useState(null) + const [twitterModal, setTwitterModal] = useState(false) + const [user, setUser] = useState() + const ref = React.createRef() + const router = useRouter() + const { t, i18n } = useTranslation() + let testUrl + + useEffect(async () => { + if (auth.currentUser) { + const userSession = await getUserFromFirestore(auth.currentUser) + setUser(userSession) + } + }, [auth.currentUser]) + + useEffect(async () => { + setCohorts(await getAllCohorts()) + getSubmissionData() + }, []) + + useEffect(async () => { + if (cohorts) { + setCohort(getCurrentCohort(user, cohorts, course, currentDate)) + } + }, [cohorts, user]) + + useEffect(async () => { + setLessonsSubmitted(await getLessonsSubmissions(user?.uid)) + }, [user, open]) + + useEffect(() => { + lessonsSubmitted.map((item) => { + if (item?.lesson === lesson && item?.user == user?.uid && item?.cohort_id == cohort?.id) { + setUserSubmission(item.content.value) + validateUserSubmission(item.content.value) + setLessonSent(true) + } + }) + }, [lessonsSubmitted]) + + useEffect(() => { + setSortedLessons(course.lessons.sort((a, b) => (a.section > b.section ? 1 : -1))) + }, [sortedLessons]) + + useEffect(() => { + const handleLanguageChange = () => { + const newLanguage = i18n.resolvedLanguage + const { pathname, query } = router + if (query.lang !== newLanguage) { + query.lang = newLanguage + router.replace({ pathname, query }, undefined, { lang: newLanguage }) + } + } + + i18n.on('languageChanged', handleLanguageChange) + + return () => { + i18n.off('languageChanged', handleLanguageChange) + } + }, [i18n, router]) + + const nextLesson = () => { + const currentLessonIndex = sortedLessons.map((item) => item.lesson === lesson).indexOf(true) + const nextLesson = sortedLessons[currentLessonIndex + 1] + if (lessonSent && !nextLesson) return toast.success(t('messages.lesson_completed_congrats')) + if (lessonSent) + return (window.location.href = `/courses/${course.id}/lessons/${nextLesson?.lesson}`) + return toast.error(t('messages.exercise_not_submitted')) + } + const previousLesson = () => { + const currentLessonIndex = sortedLessons.map((item) => item.lesson === lesson).indexOf(true) + const previousLesson = sortedLessons[currentLessonIndex - 1] + if (previousLesson) + return (window.location.href = `/courses/${course.id}/lessons/${previousLesson?.lesson}`) + return toast.error(t('messages.already_on_first_lesson')) + } + const validateUserSubmission = (submission) => { + try { + testUrl = new URL(submission) + } catch (_) { + return submission + } + if (testUrl?.hostname.includes('firebasestorage')) return setUrl(testUrl.href) + if (testUrl) return submission + } + const getSection = () => { + return Object.entries(course.sections) + .map((section) => + section[1].map((item) => { + if (item.file.includes(lesson)) return section[0] + }) + ) + .flat() + .find(Boolean) + } + + const fixMarkdown = (markdown) => { + let result = markdown.replace( + /\[Loom]\(+[a-z]+:\/\/[a-z]+[.][a-z]+[.][a-z]+\/[a-z]+\/(\w+)\)/, + '
' + ) + result = result.replace( + /\[Youtube]\(https:\/\/www\.youtube\.com\/watch\?v=([^)]*)\)/, + '