From f4be84296f0c08fba026e45b6f758591e64a1242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20M=C3=A1rquez?= <47191295+gustavomm19@users.noreply.github.com> Date: Tue, 13 Aug 2024 18:37:07 +0000 Subject: [PATCH 001/122] guided experience for lesson and remove unnecesary contextState variable --- public/locales/en/dashboard.json | 1 + public/locales/en/syllabus.json | 5 + public/locales/es/dashboard.json | 1 + public/locales/es/syllabus.json | 5 + .../Icon/set/rigobot-avatar-tiny.jsx | 4 +- .../MarkDownParser/ContentHeading.jsx | 17 +- .../components/MarkDownParser/index.jsx | 5 +- src/common/components/PopoverTaskHandler.jsx | 111 +- src/common/components/ReviewModal/index.jsx | 4 +- src/common/components/Timeline.jsx | 24 +- src/common/handlers/index.js | 62 +- src/common/hooks/useCohortHandler.js | 25 +- src/common/hooks/useModuleHandler.js | 45 +- src/common/hooks/useStyle.js | 3 + src/common/store/actions/moduleMapAction.js | 38 +- src/common/store/reducers/moduleMapReducer.js | 34 +- src/js_modules/moduleMap/index.jsx | 15 +- src/js_modules/moduleMap/module.jsx | 15 +- src/js_modules/moduleMap/taskHandler.jsx | 130 ++- .../syllabus/GuidedExperienceSidebar.jsx | 139 +++ .../syllabus/SyllabusMarkdownComponent.jsx | 4 +- src/js_modules/syllabus/TimelineSidebar.jsx | 13 +- .../[cohortSlug]/[slug]/[version]/index.jsx | 32 +- .../[lesson]/[lessonSlug]/index.jsx | 1038 ++++++++++------- 24 files changed, 1067 insertions(+), 703 deletions(-) create mode 100644 src/js_modules/syllabus/GuidedExperienceSidebar.jsx diff --git a/public/locales/en/dashboard.json b/public/locales/en/dashboard.json index 2eba43f76..8b8a5453d 100644 --- a/public/locales/en/dashboard.json +++ b/public/locales/en/dashboard.json @@ -3,6 +3,7 @@ "title": "Dashboard" }, "title": "Your News", + "back-to-dashboard": "Back to dashboard", "backToChooseProgram": "Back to choose program", "moduleMap": "Module map", "progressText": "progress in the program", diff --git a/public/locales/en/syllabus.json b/public/locales/en/syllabus.json index ac55f6ed6..20403eca2 100644 --- a/public/locales/en/syllabus.json +++ b/public/locales/en/syllabus.json @@ -3,6 +3,11 @@ "module-not-started": "You haven't started this module yet", "no-modules-to-show": "No modules to show", "edit-page": "Edit in GitHub", + "watch-intro": "Watch intro", + "get-help": "Get gelp from Rigobot", + "contribute": "Contribute to this lesson", + "show-menu": "Show menu", + "hide-menu": "Hide menu", "open-google-collab": "Open in Colab", "next-page": "Next page", "no-traduction-found": "No translation found", diff --git a/public/locales/es/dashboard.json b/public/locales/es/dashboard.json index 0a4b24808..d113d546e 100644 --- a/public/locales/es/dashboard.json +++ b/public/locales/es/dashboard.json @@ -4,6 +4,7 @@ }, "title": "Tus noticias", "moduleMap": "Mapa de módulos", + "back-to-dashboard": "Volver a dashboard", "backToChooseProgram": "Volver a elegir programa", "progressText": "Progreso en el programa", "whiteLabeledText": "Este curso es traído a ti gracias a nuestra alianza con esta universidad.", diff --git a/public/locales/es/syllabus.json b/public/locales/es/syllabus.json index 5e8653ea6..bd8a1edb5 100644 --- a/public/locales/es/syllabus.json +++ b/public/locales/es/syllabus.json @@ -3,6 +3,11 @@ "module-not-started": "Aún no has iniciado este módulo", "edit-page": "Editar en Github", "no-modules-to-show": "No hay módulos para mostrar", + "watch-intro": "Ver introducción", + "get-help": "Pide ayuda a Rigobot", + "contribute": "Contribuye a esta lección", + "show-menu": "Abrir menú", + "hide-menu": "Cerrar menú", "open-google-collab": "Abrir en Colab", "next-page": "Siguiente página", "no-traduction-found": "No se encontró traducción", diff --git a/src/common/components/Icon/set/rigobot-avatar-tiny.jsx b/src/common/components/Icon/set/rigobot-avatar-tiny.jsx index fb30c7deb..0df3a2445 100644 --- a/src/common/components/Icon/set/rigobot-avatar-tiny.jsx +++ b/src/common/components/Icon/set/rigobot-avatar-tiny.jsx @@ -1,5 +1,5 @@ -const rigobotAvatarTiny = ({ width, height }) => ( - +const rigobotAvatarTiny = ({ width, height, style }) => ( + diff --git a/src/common/components/MarkDownParser/ContentHeading.jsx b/src/common/components/MarkDownParser/ContentHeading.jsx index 0b4d6015f..cff04fdb1 100644 --- a/src/common/components/MarkDownParser/ContentHeading.jsx +++ b/src/common/components/MarkDownParser/ContentHeading.jsx @@ -1,12 +1,14 @@ import PropTypes from 'prop-types'; import { Box, useColorModeValue } from '@chakra-ui/react'; +import useStyle from '../../hooks/useStyle'; import Heading from '../Heading'; import Text from '../Text'; import Icon from '../Icon'; function ContentHeading({ - content, children, callToAction, titleRightSide, + content, children, callToAction, titleRightSide, isGuidedExperience, }) { + const { backgroundColor4 } = useStyle(); const { title, subtitle, assetType } = content; const assetTypeIcons = { LESSON: 'book', @@ -21,7 +23,16 @@ function ContentHeading({ borderColor={useColorModeValue('gray.200', 'gray.900')} > - + @@ -47,11 +58,13 @@ ContentHeading.propTypes = { children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, callToAction: PropTypes.node, titleRightSide: PropTypes.node, + isGuidedExperience: PropTypes.bool, }; ContentHeading.defaultProps = { content: {}, callToAction: null, titleRightSide: null, + isGuidedExperience: false, }; export default ContentHeading; diff --git a/src/common/components/MarkDownParser/index.jsx b/src/common/components/MarkDownParser/index.jsx index bc7707f53..2580545de 100644 --- a/src/common/components/MarkDownParser/index.jsx +++ b/src/common/components/MarkDownParser/index.jsx @@ -139,7 +139,7 @@ function ListComponent({ subTasksLoaded, subTasksProps, setSubTasksProps, subTas function MarkDownParser({ content, callToActionProps, withToc, frontMatter, titleRightSide, currentTask, isPublic, currentData, - showLineNumbers, showInlineLineNumbers, assetData, alerMessage, + showLineNumbers, showInlineLineNumbers, assetData, alerMessage, isGuidedExperience, }) { const { t, lang } = useTranslation('common'); const [subTasks, setSubTasks] = useState([]); @@ -311,6 +311,7 @@ function MarkDownParser({ - - {t('common:taskStatus.update-project-delivery')} - - ); + return { + icon: { + icon: 'checked', + color: '#FFB718', + width: '20px', + height: '20px', + }, + text: t('common:taskStatus.update-project-delivery'), + }; } if (currentTask.revision_status === 'APPROVED') { - return ( - <> - - {t('common:taskStatus.project-approved')} - - ); + return { + icon: { + icon: 'verified', + color: '#606060', + width: '20px', + }, + text: t('common:taskStatus.project-approved'), + }; } if (currentTask.revision_status === 'REJECTED') { - return ( - <> - - {t('common:taskStatus.update-project-delivery')} - - ); + return { + icon: { + icon: 'checked', + color: '#FF4433', + width: '20px', + }, + text: t('common:taskStatus.update-project-delivery'), + }; } - return ( - <> - - {t('common:taskStatus.send-project')} - - ); + return { + icon: { + icon: 'unchecked', + color: '#C4C4C4', + width: '20px', + }, + text: t('common:taskStatus.send-project'), + }; } // common task status if (currentTask && currentTask.task_type !== 'PROJECT' && currentTask.task_status === 'DONE') { - return ( - <> - - {t('common:taskStatus.mark-as-not-done')} - - ); + return { + icon: { + icon: 'close', + color: '#FFFFFF', + width: '12px', + }, + text: t('common:taskStatus.mark-as-not-done'), + }; } - return ( - <> - - {t('common:taskStatus.mark-as-done')} - - ); + + return { + icon: { + icon: 'checked2', + color: taskIsApproved ? '#606060' : '#FFFFFF', + width: '14px', + }, + text: t('common:taskStatus.mark-as-done'), + }; } export function IconByTaskStatus({ currentTask, noDeliveryFormat }) { @@ -217,6 +234,8 @@ function PopoverTaskHandler({ closeSettings(); }; + const textAndIcon = textByTaskStatus(currentTask || {}); + return ( {allowText ? ( - + <> + + {textAndIcon.text} + ) : ( )} @@ -500,13 +522,12 @@ PopoverTaskHandler.defaultProps = { buttonChildren: null, }; -TextByTaskStatus.propTypes = { - currentTask: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), - t: PropTypes.func.isRequired, -}; -TextByTaskStatus.defaultProps = { - currentTask: {}, -}; +// TextByTaskStatus.propTypes = { +// currentTask: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), +// }; +// TextByTaskStatus.defaultProps = { +// currentTask: {}, +// }; IconByTaskStatus.propTypes = { currentTask: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), noDeliveryFormat: PropTypes.bool, diff --git a/src/common/components/ReviewModal/index.jsx b/src/common/components/ReviewModal/index.jsx index cbd3c2a45..82c55fca9 100644 --- a/src/common/components/ReviewModal/index.jsx +++ b/src/common/components/ReviewModal/index.jsx @@ -61,7 +61,7 @@ function ReviewModal({ isExternal, externalFiles, isOpen, isStudent, externalDat const [settingsOpen, setSettingsOpen] = useState(false); const [openUndoApproval, setOpenUndoApproval] = useState(false); const [fileData, setFileData] = useState(); - const { contextState, setContextState } = useModuleMap(); + const { taskTodo, setTaskTodo } = useModuleMap(); const [reviewStatus, setReviewStatus] = useState(''); const [contextData, setContextData] = useState({ commitFiles: { @@ -426,7 +426,7 @@ function ReviewModal({ isExternal, externalFiles, isOpen, isStudent, externalDat }; const sendProject = async ({ task, githubUrl, taskStatus: newTaskStatus }) => { await updateAssignment({ - t, task, closeSettings, toast, githubUrl, taskStatus: newTaskStatus, contextState, setContextState, + t, task, closeSettings, toast, githubUrl, taskStatus: newTaskStatus, taskTodo, setTaskTodo, }); }; diff --git a/src/common/components/Timeline.jsx b/src/common/components/Timeline.jsx index f5b287112..b5f200c48 100644 --- a/src/common/components/Timeline.jsx +++ b/src/common/components/Timeline.jsx @@ -4,28 +4,25 @@ import React, { import PropTypes from 'prop-types'; import { useRouter } from 'next/router'; import { - Box, Flex, useColorMode, useColorModeValue, + Box, + Flex, + useColorModeValue, } from '@chakra-ui/react'; import useTranslation from 'next-translate/useTranslation'; import Icon from './Icon'; import Text from './Text'; -const color = { - light: 'blue.light', - dark: 'featuredDark', -}; - function Timeline({ title, assignments, technologies, width, onClickAssignment, showPendingTasks, }) { const { t, lang } = useTranslation('syllabus'); - const { colorMode } = useColorMode(); const router = useRouter(); const { lessonSlug } = router.query; const [currentAssignment, setCurrentAssignment] = useState(null); const [currentDefaultSlug, setCurrentDefaultSlug] = useState(null); const fontColor1 = useColorModeValue('gray.dark', 'white'); const fontColor2 = useColorModeValue('gray.dark', 'gray.light'); + const bgColor = useColorModeValue('blue.light', 'featuredDark'); // scroll scrollIntoView for id when lessonSlug changes const scrollIntoView = (id) => { @@ -61,6 +58,13 @@ function Timeline({ onClickAssignment(e, item); }; + const getAssignmentTitle = (item) => { + if (!item?.translations) return item?.title; + + return lang === 'en' ? (item?.translations?.en?.title || item?.translations?.us?.title) + : (item?.translations?.[lang]?.title || item?.title); + }; + return ( <> @@ -98,9 +102,7 @@ function Timeline({ {assignments.length > 0 ? assignments.map((item, index) => { const mapIndex = index; const muted = item?.slug !== currentDefaultSlug; - const assignmentTitle = lang === 'en' - ? (item?.translations?.en?.title || item?.translations?.us?.title) - : (item?.translations?.[lang]?.title || item?.title); + const assignmentTitle = getAssignmentTitle(item); return ( - handleClick(e, item)} width="100%" borderRadius="17px" bg={!muted ? color[colorMode] : 'none'} paddingY="10.5px" paddingX="12px"> + handleClick(e, item)} width="100%" borderRadius="17px" bg={!muted ? bgColor : 'none'} paddingY="10.5px" paddingX="12px"> diff --git a/src/common/handlers/index.js b/src/common/handlers/index.js index d26e40b85..d9cf65fa5 100644 --- a/src/common/handlers/index.js +++ b/src/common/handlers/index.js @@ -279,36 +279,38 @@ const handlers = { quiz: [], }; - modules?.forEach((module) => { - const { - assignments = [], - lessons = [], - replits = [], - quizzes = [], - } = module; - - const exercisesCount = replits.length; - const lessonsCount = lessons.length; - const projectCount = assignments.length; - const quizzesCount = quizzes.length; - - const assignmentsRecopilatedObj = { - exercisesCount, - lessonsCount, - projectCount, - quizzesCount, - }; - const replitsCompletedFromTask = getCompletedTasksFromModule(replits, taskTodo); - const quizzesCompletedFromTask = getCompletedTasksFromModule(quizzes, taskTodo); - const lessonsCompletedFromTask = getCompletedTasksFromModule(lessons, taskTodo); - const assignmentsCompletedFromTask = getCompletedTasksFromModule(assignments, taskTodo); - assetsCompleted.exercise.push(...replitsCompletedFromTask); - assetsCompleted.lesson.push(...lessonsCompletedFromTask); - assetsCompleted.project.push(...assignmentsCompletedFromTask); - assetsCompleted.quiz.push(...quizzesCompletedFromTask); - - assignmentsRecopilated.push(assignmentsRecopilatedObj); - }); + if (Array.isArray(modules)) { + modules?.forEach((module) => { + const { + assignments = [], + lessons = [], + replits = [], + quizzes = [], + } = module; + + const exercisesCount = replits.length; + const lessonsCount = lessons.length; + const projectCount = assignments.length; + const quizzesCount = quizzes.length; + + const assignmentsRecopilatedObj = { + exercisesCount, + lessonsCount, + projectCount, + quizzesCount, + }; + const replitsCompletedFromTask = getCompletedTasksFromModule(replits, taskTodo); + const quizzesCompletedFromTask = getCompletedTasksFromModule(quizzes, taskTodo); + const lessonsCompletedFromTask = getCompletedTasksFromModule(lessons, taskTodo); + const assignmentsCompletedFromTask = getCompletedTasksFromModule(assignments, taskTodo); + assetsCompleted.exercise.push(...replitsCompletedFromTask); + assetsCompleted.lesson.push(...lessonsCompletedFromTask); + assetsCompleted.project.push(...assignmentsCompletedFromTask); + assetsCompleted.quiz.push(...quizzesCompletedFromTask); + + assignmentsRecopilated.push(assignmentsRecopilatedObj); + }); + } const assignmentsRecopilatedObj = { exercise: 0, diff --git a/src/common/hooks/useCohortHandler.js b/src/common/hooks/useCohortHandler.js index 35417c396..84ef7e399 100644 --- a/src/common/hooks/useCohortHandler.js +++ b/src/common/hooks/useCohortHandler.js @@ -5,7 +5,9 @@ import useTranslation from 'next-translate/useTranslation'; import { useRouter } from 'next/router'; import useAuth from './useAuth'; import { devLog, getStorageItem } from '../../utils'; -import useAssignments from '../store/actions/cohortAction'; +import useCohort from '../store/actions/cohortAction'; +import useModuleMap from '../store/actions/moduleMapAction'; +import { nestAssignments } from './useModuleHandler'; import bc from '../services/breathecode'; import { BREATHECODE_HOST, DOMAIN_NAME } from '../../utils/variables'; @@ -13,7 +15,8 @@ function useCohortHandler() { const router = useRouter(); const { user } = useAuth(); const { t, lang } = useTranslation('dashboard'); - const { setCohortSession, setTaskCohortNull, setSortedAssignments, setUserCapabilities, state } = useAssignments(); + const { setCohortSession, setTaskCohortNull, setSortedAssignments, setUserCapabilities, state } = useCohort(); + const { cohortProgram, taskTodo, setCohortProgram, setTaskTodo } = useModuleMap(); const { cohortSession, @@ -50,7 +53,7 @@ function useCohortHandler() { }; const getCohortAssignments = ({ - setContextState, slug, cohort, + slug, cohort, }) => { if (user) { const academyId = cohort.academy.id; @@ -58,7 +61,7 @@ function useCohortHandler() { const syllabusSlug = cohort?.syllabus_version.slug || slug; const currentAcademy = user.roles.find((role) => role.academy.id === academyId); if (currentAcademy) { - // Fetch cohortProgram and TaskTodo then apply to contextState (useModuleMap - action) + // Fetch cohortProgram and TaskTodo then apply to moduleMap store Promise.all([ bc.todo({ cohort: cohort.id, limit: 1000 }).getTaskByStudent(), // Tasks with cohort id bc.syllabus().get(academyId, syllabusSlug, version), // cohortProgram @@ -67,10 +70,8 @@ function useCohortHandler() { [taskTodoData, programData, userRoles], ) => { setUserCapabilities(userRoles.data.capabilities); - setContextState({ - taskTodo: taskTodoData.data.results, - cohortProgram: programData.data, - }); + setTaskTodo(taskTodoData.data.results); + setCohortProgram(programData.data); }).catch((err) => { console.log(err); toast({ @@ -151,15 +152,13 @@ function useCohortHandler() { }); // Sort all data fetched in order of taskTodo - const prepareTasks = ({ - cohortProgram, contextState, nestAssignments, - }) => { + const prepareTasks = () => { const moduleData = cohortProgram.json?.days || cohortProgram.json?.modules; const cohort = cohortProgram.json ? moduleData : []; const assignmentsRecopilated = []; devLog('json.days:', moduleData); - if (contextState.cohortProgram.json && contextState.taskTodo) { + if (cohortProgram.json && taskTodo) { cohort.map((assignment) => { const { id, label, description, lessons, replits, assignments, quizzes, @@ -171,7 +170,7 @@ function useCohortHandler() { practice: replits, project: assignments, answer: quizzes, - taskTodo: contextState.taskTodo, + taskTodo, }); const { modules, filteredModules, filteredModulesByPending } = nestedAssignments; diff --git a/src/common/hooks/useModuleHandler.js b/src/common/hooks/useModuleHandler.js index 7419fb237..60538fa8a 100644 --- a/src/common/hooks/useModuleHandler.js +++ b/src/common/hooks/useModuleHandler.js @@ -3,7 +3,7 @@ import bc from '../services/breathecode'; import { reportDatalayer } from '../../utils/requests'; export const updateAssignment = async ({ - t, task, closeSettings, toast, githubUrl, contextState, setContextState, taskStatus, + t, task, closeSettings, toast, githubUrl, taskTodo, setTaskTodo, taskStatus, }) => { // Task case const toggleStatus = (task.task_status === undefined || task.task_status === 'PENDING') ? 'DONE' : 'PENDING'; @@ -16,15 +16,12 @@ export const updateAssignment = async ({ try { await bc.todo({}).update(taskToUpdate); - const keyIndex = contextState.taskTodo.findIndex((x) => x.id === task.id); - setContextState({ - ...contextState, - taskTodo: [ - ...contextState.taskTodo.slice(0, keyIndex), // before keyIndex (inclusive) - taskToUpdate, // key item (updated) - ...contextState.taskTodo.slice(keyIndex + 1), // after keyIndex (exclusive) - ], - }); + const keyIndex = taskTodo.findIndex((x) => x.id === task.id); + setTaskTodo([ + ...taskTodo.slice(0, keyIndex), // before keyIndex (inclusive) + taskToUpdate, // key item (updated) + ...taskTodo.slice(keyIndex + 1), // after keyIndex (exclusive) + ]); toast({ position: 'top', title: t('alert-message:assignment-updated'), @@ -70,15 +67,12 @@ export const updateAssignment = async ({ const response = await bc.todo({}).update(taskToUpdate); // verify if form is equal to the response if (response.data.github_url === projectUrl) { - const keyIndex = contextState.taskTodo.findIndex((x) => x.id === task.id); - setContextState({ - ...contextState, - taskTodo: [ - ...contextState.taskTodo.slice(0, keyIndex), // before keyIndex (inclusive) - taskToUpdate, // key item (updated) - ...contextState.taskTodo.slice(keyIndex + 1), // after keyIndex (exclusive) - ], - }); + const keyIndex = taskTodo.findIndex((x) => x.id === task.id); + setTaskTodo([ + ...taskTodo.slice(0, keyIndex), // before keyIndex (inclusive) + taskToUpdate, // key item (updated) + ...taskTodo.slice(keyIndex + 1), // after keyIndex (exclusive) + ]); reportDatalayer({ dataLayer: { event: 'assignment_status_updated', @@ -117,7 +111,7 @@ export const updateAssignment = async ({ }; export const startDay = async ({ - t, newTasks, label, contextState, setContextState, toast, customHandler = () => {}, + t, newTasks, label, taskTodo, setTaskTodo, toast, customHandler = () => {}, }) => { try { const response = await bc.todo({}).add(newTasks); @@ -133,13 +127,10 @@ export const startDay = async ({ duration: 6000, isClosable: true, }); - setContextState({ - ...contextState, - taskTodo: [ - ...contextState.taskTodo, - ...response.data, - ], - }); + setTaskTodo([ + ...taskTodo, + ...response.data, + ]); customHandler(); } } catch (err) { diff --git a/src/common/hooks/useStyle.js b/src/common/hooks/useStyle.js index b28983673..d0185c8f6 100644 --- a/src/common/hooks/useStyle.js +++ b/src/common/hooks/useStyle.js @@ -5,6 +5,7 @@ const useStyle = () => { const backgroundColor = useColorModeValue('white', 'darkTheme'); const backgroundColor2 = useColorModeValue('white', 'gray.700'); const backgroundColor3 = useColorModeValue('gray.light2', 'gray.800'); + const backgroundColor4 = useColorModeValue('#F4FAFF', 'gray.800'); const borderColor = useColorModeValue('gray.200', 'gray.700'); const borderColor2 = useColorModeValue('gray.200', 'featuredDark'); const borderColorStrong = useColorModeValue('gray.400', 'gray.500'); @@ -47,6 +48,7 @@ const useStyle = () => { lightColor: useColorModeValue('#F5F5F5', '#4A5568'), lightColor2: useColorModeValue('#F5F5F5', '#283340'), lightColor3: useColorModeValue('#F5F5F5', '#17202A'), + lightColor4: useColorModeValue('#F0F2F5', '#4A5568'), white2: useColorModeValue('#ffffff', '#283340'), danger: useColorModeValue('#CD0000', '#e26161'), blueDefault: '#0097CD', @@ -72,6 +74,7 @@ const useStyle = () => { backgroundColor, backgroundColor2, backgroundColor3, + backgroundColor4, borderColor, borderColor2, borderColorStrong, diff --git a/src/common/store/actions/moduleMapAction.js b/src/common/store/actions/moduleMapAction.js index 45ee687dd..fce496025 100644 --- a/src/common/store/actions/moduleMapAction.js +++ b/src/common/store/actions/moduleMapAction.js @@ -2,42 +2,28 @@ import { useDispatch, useSelector } from 'react-redux'; const useModuleMap = () => { const dispatch = useDispatch(); - const modules = useSelector((state) => state.moduleMapReducer.modules); - const contextState = useSelector((state) => state.moduleMapReducer.contextState); - const updateModuleStatus = (module) => { - const changedModules = modules.map((m, index) => { - if (index === module.index) { - return { - ...m, status: module.status, - }; - } - return m; - }); + const taskTodo = useSelector((state) => state.moduleMapReducer.taskTodo); + const cohortProgram = useSelector((state) => state.moduleMapReducer.cohortProgram); + + const setTaskTodo = (newState) => { dispatch({ - type: 'CHANGE_STATUS', - payload: changedModules, + type: 'CHANGE_TASK_TO_DO', + payload: newState, }); }; - const setContextState = (newState) => { + const setCohortProgram = (newState) => { dispatch({ - type: 'CHANGE_CONTEXT_STATE', + type: 'CHANGE_COHORT_PROGRAM', payload: newState, }); }; - // const changeSingleTask = (newState) => { - // dispatch({ - // type: 'CHANGE_SINGLE_TASK_STATUS', - // payload: newState, - // }); - // }; return { - modules, - contextState, - setContextState, - updateModuleStatus, - // changeSingleTask, + cohortProgram, + taskTodo, + setTaskTodo, + setCohortProgram, }; }; diff --git a/src/common/store/reducers/moduleMapReducer.js b/src/common/store/reducers/moduleMapReducer.js index 0b0b6d79f..63120b0c7 100644 --- a/src/common/store/reducers/moduleMapReducer.js +++ b/src/common/store/reducers/moduleMapReducer.js @@ -1,41 +1,19 @@ const initialState = { - modules: [ - { - title: 'Read', - text: 'Introduction to the pre-work', - icon: 'verified', - status: 'inactive', - }, - { - title: 'Practice', - text: 'Practice pre-work', - icon: 'book', - status: 'active', - }, - { - title: 'Practice', - text: 'Star wars', - icon: 'verified', - status: 'finished', - }, - ], - contextState: { - cohortProgram: [], - taskTodo: [], - }, + cohortProgram: {}, + taskTodo: [], }; const moduleMapReducer = (state = initialState, action) => { switch (action.type) { - case 'CHANGE_STATUS': + case 'CHANGE_TASK_TO_DO': return { ...state, - modules: action.payload, + taskTodo: action.payload, }; - case 'CHANGE_CONTEXT_STATE': + case 'CHANGE_COHORT_PROGRAM': return { ...state, - contextState: action.payload, + cohortProgram: action.payload, }; default: return state; diff --git a/src/js_modules/moduleMap/index.jsx b/src/js_modules/moduleMap/index.jsx index 5477cde93..e8676e849 100644 --- a/src/js_modules/moduleMap/index.jsx +++ b/src/js_modules/moduleMap/index.jsx @@ -4,6 +4,7 @@ import { } from '@chakra-ui/react'; import useTranslation from 'next-translate/useTranslation'; import PropTypes from 'prop-types'; +import useModuleMap from '../../common/store/actions/moduleMapAction'; import Text from '../../common/components/Text'; import Module from './module'; import { startDay } from '../../common/hooks/useModuleHandler'; @@ -11,12 +12,13 @@ import Icon from '../../common/components/Icon'; import { reportDatalayer } from '../../utils/requests'; function ModuleMap({ - index, userId, contextState, setContextState, slug, modules, filteredModules, - title, description, taskTodo, cohortData, taskCohortNull, filteredModulesByPending, + index, userId, slug, modules, filteredModules, + title, description, cohortData, taskCohortNull, filteredModulesByPending, showPendingTasks, searchValue, existsActivities, }) { const { t } = useTranslation('dashboard'); const toast = useToast(); + const { taskTodo, setTaskTodo } = useModuleMap(); const commonBorderColor = useColorModeValue('gray.200', 'gray.900'); const currentModules = showPendingTasks ? filteredModulesByPending : filteredModules; const cohortId = cohortData?.id || cohortData?.cohort_id; @@ -40,8 +42,8 @@ function ModuleMap({ t, id: userId, newTasks: updatedTasks, - contextState, - setContextState, + taskTodo, + setTaskTodo, toast, }); }; @@ -115,7 +117,6 @@ function ModuleMap({ key={`${module.title}-${cheatedIndex}`} currIndex={i} data={module} - taskTodo={taskTodo} /> ); }) : ( @@ -156,14 +157,11 @@ function ModuleMap({ ModuleMap.propTypes = { index: PropTypes.number.isRequired, userId: PropTypes.number.isRequired, - contextState: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])).isRequired, - setContextState: PropTypes.func.isRequired, title: PropTypes.string, slug: PropTypes.string, modules: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))), filteredModules: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))), description: PropTypes.string, - taskTodo: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))), cohortData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), taskCohortNull: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))), filteredModulesByPending: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))), @@ -177,7 +175,6 @@ ModuleMap.defaultProps = { title: 'HTML/CSS/Bootstrap', slug: 'html-css-bootstrap', description: '', - taskTodo: [], cohortData: {}, taskCohortNull: [], filteredModulesByPending: [], diff --git a/src/js_modules/moduleMap/module.jsx b/src/js_modules/moduleMap/module.jsx index e94b2c8cd..a9ad48f9f 100644 --- a/src/js_modules/moduleMap/module.jsx +++ b/src/js_modules/moduleMap/module.jsx @@ -8,11 +8,11 @@ import useTranslation from 'next-translate/useTranslation'; import PropTypes from 'prop-types'; import { useState, memo } from 'react'; import { updateAssignment } from '../../common/hooks/useModuleHandler'; +import useCohortHandler from '../../common/hooks/useCohortHandler'; import useStyle from '../../common/hooks/useStyle'; import useModuleMap from '../../common/store/actions/moduleMapAction'; import { ButtonHandlerByTaskStatus } from './taskHandler'; import ModuleComponent from '../../common/components/Module'; -import { isWindow } from '../../utils/index'; import bc from '../../common/services/breathecode'; import ShareButton from '../../common/components/ShareButton'; import Icon from '../../common/components/Icon'; @@ -20,11 +20,13 @@ import { reportDatalayer } from '../../utils/requests'; // import { usePersistent } from '../../common/hooks/usePersistent'; function Module({ - data, taskTodo, currIndex, isDisabled, onDisabledClick, variant, + data, currIndex, isDisabled, onDisabledClick, variant, }) { const { t, lang } = useTranslation('dashboard'); const [settingsOpen, setSettingsOpen] = useState(false); - const { contextState, setContextState } = useModuleMap(); + const { taskTodo, setTaskTodo } = useModuleMap(); + const { state } = useCohortHandler(); + const { cohortSession } = state; const [currentAssetData, setCurrentAssetData] = useState(null); const [fileData, setFileData] = useState(null); const [, setUpdatedTask] = useState(null); @@ -79,8 +81,6 @@ function Module({ }, ]; - const cohortSession = isWindow ? JSON.parse(localStorage.getItem('cohortSession') || '{}') : {}; - const closeSettings = () => { setSettingsOpen(false); }; @@ -151,7 +151,7 @@ function Module({ ...task, }); await updateAssignment({ - t, task, taskStatus, closeSettings, toast, contextState, setContextState, + t, task, taskStatus, closeSettings, toast, taskTodo, setTaskTodo, }); } }; @@ -161,7 +161,7 @@ function Module({ }) => { setShowModal(true); await updateAssignment({ - t, task, closeSettings, toast, githubUrl, taskStatus, contextState, setContextState, + t, task, closeSettings, toast, githubUrl, taskStatus, taskTodo, setTaskTodo, }); }; @@ -251,7 +251,6 @@ function Module({ Module.propTypes = { data: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), currIndex: PropTypes.number, - taskTodo: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))).isRequired, isDisabled: PropTypes.bool, onDisabledClick: PropTypes.func, variant: PropTypes.string, diff --git a/src/js_modules/moduleMap/taskHandler.jsx b/src/js_modules/moduleMap/taskHandler.jsx index 740cf6464..ccdae8757 100644 --- a/src/js_modules/moduleMap/taskHandler.jsx +++ b/src/js_modules/moduleMap/taskHandler.jsx @@ -1,21 +1,22 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable react/self-closing-comp */ /* eslint-disable react/no-unstable-nested-components */ import { - Button, + Button, Box, } from '@chakra-ui/react'; -import useTranslation from 'next-translate/useTranslation'; import PropTypes from 'prop-types'; import { useState } from 'react'; import useStyle from '../../common/hooks/useStyle'; import ReviewModal from '../../common/components/ReviewModal'; import Icon from '../../common/components/Icon'; -import PopoverTaskHandler, { IconByTaskStatus, TextByTaskStatus } from '../../common/components/PopoverTaskHandler'; +import Text from '../../common/components/Text'; +import PopoverTaskHandler, { IconByTaskStatus, textByTaskStatus } from '../../common/components/PopoverTaskHandler'; export function ButtonHandlerByTaskStatus({ onlyPopoverDialog, currentTask, sendProject, changeStatusAssignment, toggleSettings, closeSettings, - settingsOpen, allowText, onClickHandler, currentAssetData, fileData, handleOpen, + settingsOpen, allowText, onClickHandler, currentAssetData, fileData, handleOpen, variant, }) { - const { t } = useTranslation('dashboard'); - const { hexColor } = useStyle(); + const { hexColor, backgroundColor } = useStyle(); const [isReviewModalOpen, setIsReviewModalOpen] = useState(false); const [loaders, setLoaders] = useState({ isFetchingCommitFiles: false, @@ -29,47 +30,6 @@ export function ButtonHandlerByTaskStatus({ const noDeliveryFormat = deliveryFormatExists && currentAssetData?.delivery_formats.includes('no_delivery'); const isButtonDisabled = currentTask === null || taskIsApproved; - function TaskButton() { - return ( - - ); - } - const openAssignmentFeedbackModal = () => { setIsReviewModalOpen(true); setLoaders((prevState) => ({ @@ -78,6 +38,25 @@ export function ButtonHandlerByTaskStatus({ })); }; + const handleTaskButton = (event) => { + if (currentTask) { + setLoaders((prevState) => ({ + ...prevState, + isChangingTaskStatus: true, + })); + changeStatusAssignment(event, currentTask) + .finally(() => { + setLoaders((prevState) => ({ + ...prevState, + isChangingTaskStatus: false, + })); + onClickHandler(); + }); + } + }; + + const textAndIcon = textByTaskStatus(currentTask || {}); + function OpenModalButton() { return ( <> @@ -122,7 +101,10 @@ export function ButtonHandlerByTaskStatus({ gridGap={allowText ? '12px' : '0'} > {allowText ? ( - + <> + + {textAndIcon.text} + ) : ( )} @@ -164,8 +146,56 @@ export function ButtonHandlerByTaskStatus({ /> ); } + + if (variant === 'rounded') { + return ( + + + + {textAndIcon.text} + + + ); + } + return ( - + ); } @@ -182,6 +212,7 @@ ButtonHandlerByTaskStatus.propTypes = { currentAssetData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), fileData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), onlyPopoverDialog: PropTypes.bool, + variant: PropTypes.string, }; ButtonHandlerByTaskStatus.defaultProps = { currentTask: null, @@ -192,4 +223,5 @@ ButtonHandlerByTaskStatus.defaultProps = { toggleSettings: () => {}, handleOpen: () => {}, onlyPopoverDialog: false, + variant: '', }; diff --git a/src/js_modules/syllabus/GuidedExperienceSidebar.jsx b/src/js_modules/syllabus/GuidedExperienceSidebar.jsx new file mode 100644 index 000000000..2cff73037 --- /dev/null +++ b/src/js_modules/syllabus/GuidedExperienceSidebar.jsx @@ -0,0 +1,139 @@ +/* eslint-disable no-unused-vars */ +import { + Box, + Button, + Img, + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, +} from '@chakra-ui/react'; +import PropTypes from 'prop-types'; +import useTranslation from 'next-translate/useTranslation'; +import Heading from '../../common/components/Heading'; +import { Config, getSlideProps } from './config'; +import NextChakraLink from '../../common/components/NextChakraLink'; +import Timeline from '../../common/components/Timeline'; +import Icon from '../../common/components/Icon'; +import Text from '../../common/components/Text'; +import useCohortHandler from '../../common/hooks/useCohortHandler'; +import useStyle from '../../common/hooks/useStyle'; + +function GuidedExperienceSidebar({ filteredEmptyModules, onClickAssignment, isOpen, onToggle, currentModuleIndex }) { + console.log('currentModuleIndex'); + console.log(currentModuleIndex); + const { t } = useTranslation('syllabus'); + const { state } = useCohortHandler(); + const { cohortSession } = state; + const Open = !isOpen; + const slide = getSlideProps(Open); + const { + themeColor, commonBorderColor, currentThemeValue, + } = Config(); + const { hexColor } = useStyle(); + + return ( + <> + + + + + + {t('dashboard:back-to-dashboard')} + + + + {cohortSession?.syllabus_version && ( + + {cohortSession?.syllabus_version?.logo && ( + + )} + {cohortSession.syllabus_version?.name} + + )} + + + + + {filteredEmptyModules.length > 0 && ( + + {filteredEmptyModules.map((section) => { + const currentAssignments = section.filteredModules; + return ( + + + + + {section.label} + + + + + + + + ); + })} + + )} + + + + + ); +} + +GuidedExperienceSidebar.propTypes = { + filteredEmptyModules: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])), + onClickAssignment: PropTypes.func, + isOpen: PropTypes.bool, + onToggle: PropTypes.func, + currentModuleIndex: PropTypes.number, +}; +GuidedExperienceSidebar.defaultProps = { + filteredEmptyModules: [], + onClickAssignment: () => {}, + isOpen: false, + onToggle: () => {}, + currentModuleIndex: null, +}; + +export default GuidedExperienceSidebar; diff --git a/src/js_modules/syllabus/SyllabusMarkdownComponent.jsx b/src/js_modules/syllabus/SyllabusMarkdownComponent.jsx index def1c4697..eba1251e1 100644 --- a/src/js_modules/syllabus/SyllabusMarkdownComponent.jsx +++ b/src/js_modules/syllabus/SyllabusMarkdownComponent.jsx @@ -5,7 +5,7 @@ import { MDSkeleton } from '../../common/components/Skeleton'; function SyllabusMarkdownComponent({ ipynbHtmlUrl, readme, currentBlankProps, callToActionProps, currentData, lesson, - quizSlug, lessonSlug, currentTask, alerMessage, + quizSlug, lessonSlug, currentTask, alerMessage, isGuidedExperience, }) { const { t } = useTranslation('syllabus'); const blankText = t('blank-page', { url: currentBlankProps?.url }); @@ -16,6 +16,7 @@ function SyllabusMarkdownComponent({ content={readme.content} callToActionProps={callToActionProps} withToc={lesson?.toLowerCase() === 'read'} + isGuidedExperience={isGuidedExperience} frontMatter={{ title: currentData.title, // subtitle: currentData.description, @@ -33,6 +34,7 @@ function SyllabusMarkdownComponent({ content={blankText} callToActionProps={callToActionProps} withToc={lesson?.toLowerCase() === 'read'} + isGuidedExperience={isGuidedExperience} frontMatter={{ title: currentBlankProps?.title, // subtitle: currentBlankProps.description, diff --git a/src/js_modules/syllabus/TimelineSidebar.jsx b/src/js_modules/syllabus/TimelineSidebar.jsx index 14c336263..a89db7037 100644 --- a/src/js_modules/syllabus/TimelineSidebar.jsx +++ b/src/js_modules/syllabus/TimelineSidebar.jsx @@ -9,13 +9,16 @@ import { Config, getSlideProps } from './config'; import Timeline from '../../common/components/Timeline'; import Icon from '../../common/components/Icon'; import Text from '../../common/components/Text'; +import useCohortHandler from '../../common/hooks/useCohortHandler'; import useStyle from '../../common/hooks/useStyle'; function TimelineSidebar({ - cohortSession, filterEmptyModules, onClickAssignment, showPendingTasks, setShowPendingTasks, + filteredEmptyModules, onClickAssignment, showPendingTasks, setShowPendingTasks, isOpen, onToggle, isStudent, teacherInstructions, }) { const { t } = useTranslation('syllabus'); + const { state } = useCohortHandler(); + const { cohortSession } = state; const Open = !isOpen; const slide = getSlideProps(Open); const { @@ -133,7 +136,7 @@ function TimelineSidebar({ )} - {filterEmptyModules.length > 0 && filterEmptyModules.map((section) => { + {filteredEmptyModules.length > 0 && filteredEmptyModules.map((section) => { const currentAssignments = showPendingTasks ? section.filteredModulesByPending : section.filteredModules; @@ -164,8 +167,7 @@ function TimelineSidebar({ } TimelineSidebar.propTypes = { - cohortSession: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), - filterEmptyModules: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])), + filteredEmptyModules: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])), onClickAssignment: PropTypes.func, showPendingTasks: PropTypes.bool, isOpen: PropTypes.bool, @@ -175,8 +177,7 @@ TimelineSidebar.propTypes = { teacherInstructions: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])), }; TimelineSidebar.defaultProps = { - cohortSession: null, - filterEmptyModules: [], + filteredEmptyModules: [], onClickAssignment: () => {}, showPendingTasks: false, isOpen: false, diff --git a/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx b/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx index 7bcb146a9..4ffa36f53 100644 --- a/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx +++ b/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx @@ -28,7 +28,6 @@ import useAuth from '../../../../../common/hooks/useAuth'; import { ModuleMapSkeleton, SimpleSkeleton } from '../../../../../common/components/Skeleton'; import bc from '../../../../../common/services/breathecode'; import useModuleMap from '../../../../../common/store/actions/moduleMapAction'; -import { nestAssignments } from '../../../../../common/hooks/useModuleHandler'; import axios from '../../../../../axios'; import { slugify, @@ -59,9 +58,8 @@ function Dashboard() { const toast = useToast(); const router = useRouter(); const { colorMode } = useColorMode(); - const { contextState, setContextState } = useModuleMap(); + const { cohortProgram, taskTodo, setTaskTodo } = useModuleMap(); const [showWarningModal, setShowWarningModal] = useState(false); - const { cohortProgram } = contextState; const [studentAndTeachers, setSudentAndTeachers] = useState([]); const [modalIsOpen, setModalIsOpen] = useState(false); const [showSearch, setShowSearch] = useState(false); @@ -138,13 +136,10 @@ function Dashboard() { })); await bc.todo({}).updateBulk(tasksToUpdate) .then(({ data }) => { - setContextState({ - ...contextState, - taskTodo: [ - ...contextState.taskTodo, - ...data, - ], - }); + setTaskTodo([ + ...taskTodo, + ...data, + ]); setModalIsOpen(false); }) .catch(() => { @@ -291,7 +286,7 @@ function Dashboard() { }); // Fetch cohort assignments (lesson, exercise, project, quiz) getCohortAssignments({ - user, setContextState, slug, cohort, + user, slug, cohort, }); }); } @@ -341,10 +336,8 @@ function Dashboard() { // Sort all data fetched in order of taskTodo useEffect(() => { - prepareTasks({ - cohortProgram, contextState, nestAssignments, - }); - }, [contextState.cohortProgram, contextState.taskTodo, router]); + prepareTasks(); + }, [cohortProgram, taskTodo, router]); const dailyModuleData = getDailyModuleData() || ''; const lastTaskDoneModuleData = getLastDoneTaskModuleData() || ''; @@ -617,7 +610,7 @@ function Dashboard() { @@ -698,14 +691,11 @@ function Dashboard() { existsActivities={existsActivities} cohortData={cohortSession} taskCohortNull={taskCohortNull} - contextState={contextState} - setContextState={setContextState} index={index} title={label} slug={slugify(label)} searchValue={searchValue} description={description} - taskTodo={contextState.taskTodo} modules={modules} filteredModules={filteredModulesSearched} showPendingTasks={showPendingTasks} @@ -745,7 +735,7 @@ function Dashboard() { {cohortSession?.stage === 'FINAL_PROJECT' && ( @@ -909,7 +899,7 @@ function Dashboard() { key={`${module.title}-${i}`} currIndex={i} data={module} - taskTodo={contextState.taskTodo} + taskTodo={taskTodo} variant="open-only" /> ))} diff --git a/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx b/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx index 24f944290..bf3b7d932 100644 --- a/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx +++ b/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx @@ -14,7 +14,7 @@ import Head from 'next/head'; import { isWindow, assetTypeValues, getExtensionName } from '../../../../../utils'; import asPrivate from '../../../../../common/context/PrivateRouteWrapper'; import Heading from '../../../../../common/components/Heading'; -import { updateAssignment, startDay, nestAssignments } from '../../../../../common/hooks/useModuleHandler'; +import { updateAssignment, startDay } from '../../../../../common/hooks/useModuleHandler'; import { ButtonHandlerByTaskStatus } from '../../../../../js_modules/moduleMap/taskHandler'; import getMarkDownContent from '../../../../../common/components/MarkDownParser/markdown'; import MarkDownParser from '../../../../../common/components/MarkDownParser'; @@ -29,6 +29,7 @@ import ModalInfo from '../../../../../js_modules/moduleMap/modalInfo'; import ReactPlayerV2 from '../../../../../common/components/ReactPlayerV2'; import ScrollTop from '../../../../../common/components/scrollTop'; import TimelineSidebar from '../../../../../js_modules/syllabus/TimelineSidebar'; +import GuidedExperienceSidebar from '../../../../../js_modules/syllabus/GuidedExperienceSidebar'; import bc from '../../../../../common/services/breathecode'; import SyllabusMarkdownComponent from '../../../../../js_modules/syllabus/SyllabusMarkdownComponent'; import useCohortHandler from '../../../../../common/hooks/useCohortHandler'; @@ -44,11 +45,12 @@ function Content() { const { t, lang } = useTranslation('syllabus'); const BREATHECODE_HOST = modifyEnv({ queryString: 'host', env: process.env.BREATHECODE_HOST }); const { user, isLoading } = useAuth(); - const { contextState, setContextState } = useModuleMap(); + const { taskTodo, cohortProgram, setTaskTodo } = useModuleMap(); const [currentTask, setCurrentTask] = useState(null); const { setUserSession } = useSession(); const [settingsOpen, setSettingsOpen] = useState(false); const [modalSettingsOpen, setModalSettingsOpen] = useState(false); + const [modalIntroOpen, setModalIntroOpen] = useState(false); const { isOpen, onToggle } = useDisclosure(); const [openNextPageModal, setOpenNextPageModal] = useState(false); const [readme, setReadme] = useState(null); @@ -80,9 +82,11 @@ function Content() { getCohortAssignments, getCohortData, prepareTasks, state, } = useCohortHandler(); const { cohortSession, sortedAssignments } = state; - const { featuredLight, fontColor, borderColor, featuredCard } = useStyle(); + const isAvailableAsSaas = cohortSession?.available_as_saas; - const profesionalRoles = ['TEACHER', 'ASSISTANT', 'REVIEWER']; + const { featuredLight, fontColor, borderColor, featuredCard, backgroundColor, hexColor } = useStyle(); + + const professionalRoles = ['TEACHER', 'ASSISTANT', 'REVIEWER']; const accessToken = isWindow ? localStorage.getItem('accessToken') : ''; const commonBorderColor = useColorModeValue('#E2E8F0', '#718096'); @@ -91,7 +95,7 @@ function Content() { const Open = !isOpen; const { label, teacherInstructions, keyConcepts } = selectedSyllabus; - const filterEmptyModules = sortedAssignments.filter( + const filteredEmptyModules = sortedAssignments.filter( (assignment) => assignment.modules.length > 0, ); @@ -108,7 +112,7 @@ function Content() { const isQuiz = lesson === 'answer'; - const filteredCurrentAssignments = filterEmptyModules.map((section) => { + const filteredCurrentAssignments = filteredEmptyModules.map((section) => { const currentAssignments = showPendingTasks ? section.filteredModulesByPending : section.filteredModules; @@ -120,7 +124,7 @@ function Content() { return currIndex; }); - const currentModule = filterEmptyModules[currentModuleIndex]; + const currentModule = filteredEmptyModules[currentModuleIndex]; const scrollTop = () => { window.scrollTo({ top: 0, behavior: 'smooth' }); @@ -142,8 +146,8 @@ function Content() { t, id: user.id, newTasks: updatedTasks, - contextState, - setContextState, + taskTodo, + setTaskTodo, toast, customHandler, }); @@ -155,7 +159,7 @@ function Content() { cohortSlug, }); getCohortAssignments({ - setContextState, cohort, + cohort, }); }; @@ -170,13 +174,10 @@ function Content() { bc.todo().update({ ...currentTask, opened_at: new Date() }) .then((result) => { if (result.data) { - const updateTasks = contextState.taskTodo.map((task) => ({ ...task })); + const updateTasks = taskTodo.map((task) => ({ ...task })); const index = updateTasks.findIndex((el) => el.task_type === assetTypeValues[lesson] && el.associated_slug === lessonSlug); updateTasks[index].opened_at = result.data.opened_at; - setContextState({ - ...contextState, - taskTodo: [...updateTasks], - }); + setTaskTodo([...updateTasks]); } }).catch((e) => log('update_task_error:', e)); } @@ -184,11 +185,11 @@ function Content() { useEffect(() => { const assetSlug = currentData?.translations?.us || currentData?.translations?.en || lessonSlug; - if (contextState.taskTodo.length > 0) { - setCurrentTask(contextState.taskTodo.find((el) => el.task_type === assetTypeValues[lesson] + if (taskTodo.length > 0) { + setCurrentTask(taskTodo.find((el) => el.task_type === assetTypeValues[lesson] && el.associated_slug === assetSlug)); } - }, [contextState.taskTodo, lessonSlug, lesson]); + }, [taskTodo, lessonSlug, lesson]); const closeSettings = () => { setSettingsOpen(false); @@ -232,14 +233,14 @@ function Content() { const changeStatusAssignment = async (event, task, taskStatus) => { event.preventDefault(); await updateAssignment({ - t, task, taskStatus, closeSettings, toast, contextState, setContextState, + t, task, taskStatus, closeSettings, toast, taskTodo, setTaskTodo, }); }; const sendProject = async ({ task, githubUrl, taskStatus }) => { setShowModal(true); await updateAssignment({ - t, task, closeSettings, toast, githubUrl, taskStatus, contextState, setContextState, + t, task, closeSettings, toast, githubUrl, taskStatus, taskTodo, setTaskTodo, }); }; @@ -269,7 +270,7 @@ function Content() { }; useEffect(() => { - const currTask = filterEmptyModules[currentModuleIndex]?.modules?.find((l) => l.slug === lessonSlug); + const currTask = filteredEmptyModules[currentModuleIndex]?.modules?.find((l) => l.slug === lessonSlug); const englishTaskUrls = { en: currTask?.translations?.en, us: currTask?.translations?.us, @@ -377,13 +378,10 @@ function Content() { }, [selectedSyllabus]); useEffect(() => { - const cohortProgram = contextState?.cohortProgram; - prepareTasks({ - cohortProgram, contextState, nestAssignments, - }); - }, [contextState.cohortProgram, contextState.taskTodo, router]); + prepareTasks(); + }, [cohortProgram, taskTodo, router]); - const teacherActions = profesionalRoles.includes(cohortSession.cohort_role) + const teacherActions = professionalRoles.includes(cohortSession.cohort_role) ? [ { icon: 'key', @@ -517,6 +515,55 @@ function Content() { } }; + const prevPage = () => { + setClickedPage(previousAssignment); + if (previousAssignment?.target === 'blank') { + setCurrentBlankProps(previousAssignment); + router.push({ + query: { + cohortSlug, + lesson: previousAssignment?.type?.toLowerCase(), + lessonSlug: previousAssignment?.slug, + }, + }); + } else { + handlePrevPage(); + } + }; + + const nextPage = () => { + if (taskIsNotDone) { + setOpenNextPageModal(true); + } else if (nextAssignment !== null || !!firstTask) { + setClickedPage(nextAssignment); + if (!taskIsNotDone) { + if (nextAssignment?.target === 'blank') { + setCurrentBlankProps(nextAssignment); + router.push({ + query: { + cohortSlug, + lesson: nextAssignment?.type?.toLowerCase(), + lessonSlug: nextAssignment?.slug, + }, + }); + } else { + setCurrentBlankProps(null); + handleNextPage(); + } + } + } else if (nextModule && cohortSlug && !!firstTask) { + router.push({ + query: { + cohortSlug, + lesson: firstTask?.type?.toLowerCase(), + lessonSlug: firstTask?.slug, + }, + }); + } else { + setOpenNextModuleModal(true); + } + }; + const pathConnector = { read: `${router.locale === 'en' ? '4geeks.com/lesson' : `4geeks.com/${router.locale}/lesson`}`, practice: `${router.locale === 'en' ? '4geeks.com/interactive-exercise' : `4geeks.com/${router.locale}/interactive-exercise`}`, @@ -555,7 +602,7 @@ function Content() { {currentData?.title || '4Geeks'} - + setOpenTargetBlankModal(false)} @@ -578,262 +625,491 @@ function Content() { menu={[ ...teacherActions, ...videoTutorial, - // { - // icon: 'youtube', - // slug: 'video-player', - // title: 'Video tutorial', - // content: '#923jmi2m', - // id: 3, - // }, ]} /> - { - console.log('click'); - setExtendedIsEnabled(!extendedIsEnabled); - if (extendedIsEnabled === false) { - scrollTop(); - } - }, - actionState: extendedIsEnabled, - }} - /> + {isAvailableAsSaas ? ( + + ) : ( + { + console.log('click'); + setExtendedIsEnabled(!extendedIsEnabled); + if (extendedIsEnabled === false) { + scrollTop(); + } + }, + actionState: extendedIsEnabled, + }} + /> + )} - {!isQuiz && currentData?.intro_video_url && ( + {!isAvailableAsSaas && !isQuiz && currentData?.intro_video_url && ( )} - {extendedInstructions !== null && ( - setExtendedIsEnabled(false)} padding="2rem 0 2rem 0" style={{ margin: '3rem 0' }}> - - - {`${t('teacherSidebar.instructions')}:`} - - {sortedAssignments.length > 0 && ( - + + + {(previousAssignment || !!prevModule) && ( + + + + + {t('previous-page')} + + )} + + {(nextAssignment || !!nextModule) && ( + t('common:no-options-message')} - defaultValue={{ - value: selectedSyllabus?.id || defaultSelectedSyllabus?.id, - slug: selectedSyllabus?.slug || defaultSelectedSyllabus?.slug, - label: selectedSyllabus?.id - ? `#${selectedSyllabus?.id} - ${selectedSyllabus?.label}` - : `#${defaultSelectedSyllabus?.id} - ${defaultSelectedSyllabus?.label}`, + onClick={nextPage} + > + {t('next-page')} + + + + + )} + + + )} + + + {extendedInstructions !== null && ( + setExtendedIsEnabled(false)} padding="2rem 0 2rem 0" style={{ margin: '3rem 0' }}> + + + {`${t('teacherSidebar.instructions')}:`} + + {sortedAssignments.length > 0 && ( + t('common:no-options-message')} + defaultValue={{ + value: selectedSyllabus?.id || defaultSelectedSyllabus?.id, + slug: selectedSyllabus?.slug || defaultSelectedSyllabus?.slug, + label: selectedSyllabus?.id + ? `#${selectedSyllabus?.id} - ${selectedSyllabus?.label}` + : `#${defaultSelectedSyllabus?.id} - ${defaultSelectedSyllabus?.label}`, + }} + onChange={({ value }) => { + setCurrentSelectedModule(parseInt(value, 10)); + }} + options={sortedAssignments.map((module) => ({ + value: module?.id, + slug: module.slug, + label: `#${module?.id} - ${module?.label}`, + }))} + /> + )} + + + {selectedSyllabus && cohortModule?.id && cohortModule?.id !== selectedSyllabus?.id && ( + { - setCurrentSelectedModule(parseInt(value, 10)); + dangerouslySetInnerHTML + title={t('teacherSidebar.no-need-to-teach-today.title')} + message={t('teacherSidebar.no-need-to-teach-today.description', { module_name: `#${cohortModule?.id} - ${cohortModule?.label}` })} + /> + )} + {selectedSyllabus && defaultSelectedSyllabus?.id !== selectedSyllabus?.id && ( + ({ - value: module?.id, - slug: module.slug, - label: `#${module?.id} - ${module?.label}`, - }))} + message={t('teacherSidebar.alert-updated-module-instructions')} /> )} - - {selectedSyllabus && cohortModule?.id && cohortModule?.id !== selectedSyllabus?.id && ( - - )} - {selectedSyllabus && defaultSelectedSyllabus?.id !== selectedSyllabus?.id && ( - - )} + + + {`${label} - `} + {t('teacherSidebar.module-duration', { duration: selectedSyllabus?.duration_in_days || currentModule?.duration_in_days || 1 })} + + + {teacherInstructions} + + + + + )} - - - {`${label} - `} - {t('teacherSidebar.module-duration', { duration: selectedSyllabus?.duration_in_days || currentModule?.duration_in_days || 1 })} + {!isQuiz && currentData?.solution_video_url && showSolutionVideo && ( + + + Video Tutorial - - {teacherInstructions} - + - - - )} - - {!isQuiz && currentData?.solution_video_url && showSolutionVideo && ( - - - Video Tutorial - - - - )} - - - {repoUrl && !isQuiz && ( - - - {t('edit-page')} - )} - {ipynbHtmlUrl && currentData?.readme_url && ( - - )} + + {repoUrl && !isQuiz && !isAvailableAsSaas && ( + + + {t('edit-page')} + + )} - {ipynbHtmlUrl && readmeUrlPathname && ( - - - {t('open-google-collab')} - - )} - - {ipynbHtmlUrl && ( -