diff --git a/i18n.js b/i18n.js
index c81e33453..bb5fb1bab 100644
--- a/i18n.js
+++ b/i18n.js
@@ -25,6 +25,7 @@ module.exports = {
'/syllabus/[cohortSlug]/[lesson]/[lessonSlug]': ['syllabus', 'dashboard', 'projects', 'assignments'],
'/survey/[surveyId]': ['survey'],
'/mentorship': ['mentorship'],
+ '/mentorship/schedule': ['mentorship', 'dashboard', 'signup', 'common'],
'/how-to': ['how-to'],
'/pricing': ['pricing', 'signup'],
'/how-to/[slug]': ['how-to'],
diff --git a/public/locales/en/alert-message.json b/public/locales/en/alert-message.json
index 3b2ef9f5d..5caceb88b 100644
--- a/public/locales/en/alert-message.json
+++ b/public/locales/en/alert-message.json
@@ -78,5 +78,6 @@
"event-access-error": "You dont have access to apply for this event",
"error-event-already-started": "You cannot reserve a spot for this event because it has already started.",
"success-event-reservation": "You have successfully reserved a spot for this event.",
- "error-creating-code-review": "Something went wrong creating the code review"
+ "error-creating-code-review": "Something went wrong creating the code review",
+ "error-ai-chat": "Something went wrong opening the AI Chat"
}
diff --git a/public/locales/en/assignments.json b/public/locales/en/assignments.json
index 9aa97c70d..91b2c6c7d 100644
--- a/public/locales/en/assignments.json
+++ b/public/locales/en/assignments.json
@@ -8,6 +8,12 @@
"no-upload-students": "The following students are still pending to upload their final project information.",
"see-students": "See students",
"no-information": "It is possible that the student never opened this assignment. No information was found about this student assignment",
+ "sync-cohort": "You can synchronize all the students tasks in the cohort",
+ "sync": "Synchronize",
+ "sync-cohort-title": "Synchronize cohort",
+ "sync-warning": "WARNING: This operation should only be performed on cohorts with serious synchronization problems",
+ "cancel": "Cancel",
+ "error-msg": "Something went wrong while synchronizing the cohort",
"educational-status": "Educational Status",
"delivered-percentage": "% delivered",
"last-deliver": "Last delivery: {{date}} ago",
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 350c61dd5..89b5547b3 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -135,6 +135,7 @@
"mark-as-not-done": "Mark as not done"
},
"learnpack": {
+ "title": "This is a Learnpack interactive exercise",
"description": "\"{{projectName}}\" uses LearnPack for an interactive and auto-graded experience; choose one of the following options to start working on the project:",
"description2": "This practice will reroute your browser to LearnPack - an interactive learning tool that runs integrated with VSCode.",
"new-exercise": "Start from the beginning",
@@ -147,6 +148,7 @@
"description": "We take care of setup and installations for all the required technologies to run this exercise or project.",
"type": "dropdown"
},
+ "clone-title": "How to clone a project?",
"cloneInstructions": "This exercise can be downloaded and run locally if you have node.js installed (installation steps ).\n\n Once you have node.js, it's time to install learnpack and clone this project into your computer by typing the following command on your terminal:\n\n``` bash\n$ npm i @learnpack/learnpack -g\n$ git clone {{urlToClone}}\n```\nNote: This will create a new folder \"{{repoName}}\" in your computer.\n\nIf you want to use VSCode: Make sure you have the LearnPack extension installed, open the folder in VSCode and type `learnpack start` on your vscode terminal.\n\nTo run without VSCode: Use your computer terminal to get inside your recently created folder and start learnpack:\n\n```bash\n$ cd {{repoName}}\n$ learnpack start\n```\nRead the README.md file and follow the rest of the instructions."
},
"upgrade-plan": {
@@ -234,6 +236,8 @@
"logout-and-switch-user": "Logout and switch user",
"free": "Free",
"login": "Login",
+ "terms-and-conditions": "Terms and conditions",
+ "terms-and-conditions-link": "/terms-and-conditions",
"privacy-policy": "Privacy policy",
"privacy-policy-link": "/privacy-policy",
"bootcamp": {
diff --git a/public/locales/en/dashboard.json b/public/locales/en/dashboard.json
index 2eba43f76..a5357150a 100644
--- a/public/locales/en/dashboard.json
+++ b/public/locales/en/dashboard.json
@@ -3,8 +3,8 @@
"title": "Dashboard"
},
"title": "Your News",
- "backToChooseProgram": "Back to choose program",
"moduleMap": "Module map",
+ "backToChooseProgram": "Back to choose program",
"progressText": "progress in the program",
"whiteLabeledText": "This course is brought to you thanks to our parnership with this university",
"free-trial-msg": "You are currently on a free trial, some features might be limited. Upgrade your plan to have unlimited access!",
@@ -14,9 +14,9 @@
"cta-description": "Your current plan does not include access to this cohort, please upgrade to access the content.",
"cta-cohort-not-found": "This cohort does not exist or you don't have access to join.",
"cta-button": "Review plan",
- "join-next-cohort":"Join next cohort",
- "start-course":"Start this course",
- "join-more":"Join more than 100 people taking this course right now",
+ "join-next-cohort": "Join next cohort",
+ "start-course": "Start this course",
+ "join-more": "Join more than 100 people taking this course right now",
"preview-description": "You are reviewing this cohort dashboard on \"preview mode\", in order to start learning and interact with the materials please join the cohort."
},
"already-have-this-cohort": "You are already a member of this cohort",
@@ -84,6 +84,8 @@
"get-more-mentorships": "Get more mentorships",
"schedule-button": "Schedule a mentoring session",
"mentors-available": "+{{count}} Mentors available",
+ "service-not-found": "couldn't find service",
+ "mentor-not-found": "couldn't find mentor",
"actionButtons": [
{
"name": "mentoring",
@@ -104,7 +106,15 @@
"get-unlimited-mentorship": "and get unlimited mentorship",
"you-have": "You have",
"available-sessions": "available sessions",
- "tooltip": "Mentorships reload every week"
+ "tooltip": "Mentorships reload every week",
+ "mentor-sessions-available": "mentoring sessions available",
+ "action": "Schedule session",
+ "no-available": "No services available"
+ },
+ "schedule-steps": {
+ "select-mentorship": "First select a type of mentorship",
+ "select-mentor": "Now search for a mentor",
+ "schedule": "Schedule the session"
},
"deliverProject": {
"title": "Deliver assignment",
@@ -194,4 +204,4 @@
"title": "Welcome to 4Geeks!",
"description": "Watch this short video that explains how to get the most out of 4Geeks and enhance your learning experience"
}
-}
+}
\ No newline at end of file
diff --git a/public/locales/en/signup.json b/public/locales/en/signup.json
index 0bcbaeecd..b5b074e0b 100644
--- a/public/locales/en/signup.json
+++ b/public/locales/en/signup.json
@@ -146,9 +146,10 @@
"email-required": "Email is required",
"confirm-email-required": "Confirm Email is required",
"confirm-email-not-match": "Emails don't match",
- "termns-and-conditions-required": "I agree to receive information in my email about coding workshops, events, courses, and other marketing materials. We'll never share your email,"
+ "receive-information": "I agree to receive information in my email, WhatsApp, and/or other channels about coding workshops, events, courses, and other marketing materials. We'll never share your contact information and you can easily opt out at any moment. "
},
"no-date-available": "We currently have no dates available for the location entered",
+ "agree-terms-and-conditions": "By signing up, you agree to the",
"alert-message": {
"title": "Already a member?",
"description": "It seems that you already have an account in 4geeks.com",
diff --git a/public/locales/en/syllabus.json b/public/locales/en/syllabus.json
index 26908a980..994eea724 100644
--- a/public/locales/en/syllabus.json
+++ b/public/locales/en/syllabus.json
@@ -3,13 +3,32 @@
"module-not-started": "You haven't started this module yet",
"no-modules-to-show": "No modules to show",
"edit-page": "Edit in GitHub",
+ "solution-video": "Solution video",
+ "watch-intro": "Watch intro",
+ "get-help": "Get help 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",
+ "teachers-feedback": "Teacher’s feedback",
+ "no-feedback": "You don't have any feedback yet",
+ "task-notification": "You will receive an email when your teacher reviews the task",
+ "code-reviews": "Code reviews",
+ "no-code-reviews": "You don't have any code review yet",
+ "rate-comment": "Rate this comment",
+ "start-review": "Start you review here...",
+ "like": "You sent a positive rating to this comment",
+ "dislike": "You sent a negative rating to this comment",
+ "you": "You",
+ "back": "Back",
+ "next-page": "Next",
+ "back-to-top": "Back to the top",
+ "start-next": "Start next module:",
"no-traduction-found": "No translation found",
"no-traduction-found-description": ">We are sorry, a translation for this content was not found. We are constantly working to offer our modules in various languages. We appreciate your patience and if you wish to contribute with translations, please visit our repository on GitHub. In the meantime, you can try accessing the content in another available language or check back later to see if the translation has been added.",
"no-content-found": "No content found",
"no-content-found-description": ">Unfortunately, the content you are looking for is not available at the specified path. We are constantly working on improvements and appreciate your understanding. If you need assistance or have additional questions, please do not hesitate to contact us. We are committed to providing you with the best service. Thank you for your patience and understanding.",
- "previous-page": "Previous page",
+ "previous-page": "Previous",
"ask-to-done": "Would you like to mark this \"{{taskType}}\" as done before moving on?",
"mark-later": "Mark as done later",
"blank-page": "This content cannot be visualized inside of 4Geeks.com , please Click here to open it on a new window",
@@ -31,5 +50,12 @@
"superseded-message": "This lesson belongs to the legacy archive, we recommend reading a more updated version:",
"solution-message": "This project includes a model solution that you can review if you need additional guidance.",
"open-solution": "Click here to review the model solution",
- "click-to-review": "Click here to review the solution."
+ "completion-percentage": "Completion percentage",
+ "total-steps": "Total steps completed",
+ "total-time": "Total time worked on this package",
+ "successful-compiles": "Successfull compiles",
+ "successful-tests": "Successfull tests",
+ "total-errors": "Total errors",
+ "click-to-review": "Click here to review the solution.",
+ "share-social-message": "I just finished coding {{title}} at 4geeks.com"
}
diff --git a/public/locales/en/workshops.json b/public/locales/en/workshops.json
index 6515cfd43..7960726b3 100644
--- a/public/locales/en/workshops.json
+++ b/public/locales/en/workshops.json
@@ -14,6 +14,11 @@
"in-person-confirm": "This is an in-person event that will take place in {{address}}. Do you still want to confirm your attendance?",
"confirm-attendance": "Yes, I will be there",
"deny-attendance" : "No, I will not attend",
+ "denny-access":{
+ "description": "This is a private event, only students with access to",
+ "button": "You don't have access to this event",
+ "can-join": "can join"
+ },
"form": {
"title": "Join this event",
"description": "Sign in to join other coders live solving technical or career challenges.",
diff --git a/public/locales/es/alert-message.json b/public/locales/es/alert-message.json
index 6896847b1..3a00b24e3 100644
--- a/public/locales/es/alert-message.json
+++ b/public/locales/es/alert-message.json
@@ -77,5 +77,6 @@
"event-access-error": "No tienes acceso para aplicar en este evento",
"error-event-already-started": "No puedes reservar cupo para este evento porque ya ha comenzado",
"success-event-reservation": "¡Has reservado con éxito un cupo para este evento.!",
- "error-creating-code-review": "Algo salió mal al crear la revisión de código"
+ "error-creating-code-review": "Algo salió mal al crear la revisión de código",
+ "error-ai-chat": "Algo salio mal abriendo el chat de IA"
}
diff --git a/public/locales/es/assignments.json b/public/locales/es/assignments.json
index 10d4e5e33..90126b4c5 100644
--- a/public/locales/es/assignments.json
+++ b/public/locales/es/assignments.json
@@ -8,6 +8,11 @@
"no-upload-students": "Los siguientes estudiantes aún están pendientes de subir la información de su proyecto final.",
"see-students": "Ver estudiantes",
"no-information": "Es posible que el estudiante nunca haya abierto esta tarea. No se encontró información sobre la tarea de este estudiante.",
+ "sync-cohort": "Puedes sincronizar todas las asignaciones de los estudiantes de la cohorte",
+ "sync": "Sincronizar",
+ "sync-cohort-title": "Sincronizar la cohorte",
+ "sync-warning": "ADVERTENCIA: Esta operación solo debe realizarse en cohortes con problemas graves de sincronización.",
+ "cancel": "Cancelar",
"educational-status": "Estatus eduacional",
"delivered-percentage": "% de entregado",
"last-deliver": "Última entrega: hace {{date}}",
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
index 16947e566..25615d4c6 100644
--- a/public/locales/es/common.json
+++ b/public/locales/es/common.json
@@ -135,6 +135,7 @@
"mark-as-not-done": "Marcar como no hecho"
},
"learnpack": {
+ "title": "Este es un ejercicio interactivo de Learnpack",
"description": "\"{{projectName}}\" utiliza LearnPack para una experiencia interactiva y autoevaluada; elige una de las siguientes opciones para comenzar a trabajar en el proyecto:",
"new-exercise": "Comenzar desde el principio",
"continue-exercise": "Continuar desde una sesión anterior",
@@ -146,6 +147,7 @@
"description": "Nosotros nos encargamos de la configuración e instalación de todas las tecnologías requeridas para realizar este ejercicio.",
"type": "dropdown"
},
+ "clone-title": "¿Cómo clonar un proyecto?",
"cloneInstructions": "Este ejercicio se puede descargar y ejecutar localmente si tienes node.js instalado (pasos de instalación ).\n\n Si ya tienes node, toca instalar learnpack y clonar el proyecto en tu computadora escribiendo el siguiente comando en tu terminal:\n\n```bash\n$ npm i @learnpack/learnpack -g\n$ git clone {{urlToClone}}\n```\nNota: Esto creará una nueva carpeta \"{{repoName}}\" en tu computadora con el código del proyecto dentro.\n\nSi quieres usar VSCode: asegúrate de tener el LearnPack extension instalado, abre la carpeta en VSCode y escribe `learnpack start` en tu terminal de vscode.\n\nPara realizar los ejercicios sin VSCode: abre tu terminal en la carpeta recién creada y comienza el programa learnpack:\n\n```bash\n$ cd {{repoName}}\n$ learnpack start\n```\nLee el archivo README.md y sigue el resto de las instrucciones."
},
"upgrade-plan": {
@@ -233,6 +235,8 @@
"logout-and-switch-user": "Cerrar sesión e iniciar como otro usuario",
"free": "Gratis",
"login": "Iniciar sesión",
+ "terms-and-conditions": "Términos y condiciones",
+ "terms-and-conditions-link": "/es/terminos-y-condiciones",
"privacy-policy": "Política de privacidad",
"privacy-policy-link": "/es/politicas-de-privacidad",
"bootcamp": {
diff --git a/public/locales/es/dashboard.json b/public/locales/es/dashboard.json
index 0a4b24808..e7897dc8f 100644
--- a/public/locales/es/dashboard.json
+++ b/public/locales/es/dashboard.json
@@ -15,9 +15,9 @@
"cta-description": "Tu plan actual no incluye acceso a esta cohorte, por favor actualiza para acceder al contenido.",
"cta-cohort-not-found": "Esta cohorte no existe o no tienes acceso para unirte.",
"cta-button": "Revisar plan",
- "join-next-cohort":"Únete a la cohorte",
- "start-course":"Empezar este curso",
- "join-more":"Únete a más de 100 personas que toman este curso ahora mismo",
+ "join-next-cohort": "Únete a la cohorte",
+ "start-course": "Empezar este curso",
+ "join-more": "Únete a más de 100 personas que toman este curso ahora mismo",
"preview-description": "Estás revisando esta cohorte en \"modo de vista previa\". Para comenzar a aprender e interactuar con el material, únete a la cohorte."
},
"modules": {
@@ -72,18 +72,21 @@
"no-mentors": "No hay mentores disponibles",
"no-mentor-link": "No hay enlace para crear sesión con este mentor",
"search-mentor": "Buscar mentor por nombre",
- "start-mentorship": "Para comenzar, seleccione un tipo de tutoría",
+ "start-mentorship": "Para comenzar, seleccione un tipo de mentoría",
"mentoring-label": "Mentoría",
- "mentoring": "Programe una sesión de tutoría 1-1.",
- "select-type": "Seleccione un tipo de tutoría",
- "mentoring-available": "Tutorías disponibles",
- "no-mentoring-available": "No tienes tutorías disponibles.",
+ "mentoring": "Programe una sesión de mentoría 1-1.",
+ "select-type": "Seleccione un tipo de mentoría",
+ "mentoring-available": "Mentorías disponibles",
+ "no-mentoring-available": "No tienes mentorías disponibles.",
"create-session-text": "Agendar ahora",
"learn-more": "Aprender mas.",
"learn-more-link": "https://4geeks.com/es/docs/knowledge-base-4geeks/sesiones-de-tutoria",
"get-more-mentorships": "Consigue más mentorías",
- "schedule-button": "Programar una sesión de tutoría",
+ "schedule-button": "Programar una sesión de mentoría",
"mentors-available": "+{{count}} Mentores disponibles",
+ "for": "para",
+ "service-not-found": "no se ha encontrado servicio",
+ "mentor-not-found": "no se ha encontrado el mentor",
"actionButtons": [
{
"name": "mentoring",
@@ -100,11 +103,19 @@
]
},
"mentorship": {
- "no-mentorship": "Se ha quedado sin tutorías, pero no se preocupe, puede obtener más con unos pocos clics.",
- "get-unlimited-mentorship": "y obtén tutoría ilimitada",
+ "no-mentorship": "Se ha quedado sin mentorías, pero no se preocupe, puede obtener más con unos pocos clics.",
+ "get-unlimited-mentorship": "y obtén mentorías ilimitadas",
"you-have": "Tienes",
"available-sessions": "sesiones disponibles",
- "tooltip": "Las tutorías se recargan cada semana"
+ "tooltip": "Las mentorías se recargan cada semana",
+ "mentor-sessions-available": "sesiones de mentoria disponibles",
+ "action": "Reservar mentoria",
+ "no-available": "No hay servicios disponibles"
+ },
+ "schedule-steps": {
+ "select-mentorship": "Primero selecciona un tipo de mentoría",
+ "select-mentor": "Ahora busca un mentor",
+ "schedule": "Agenda la sesión"
},
"deliverProject": {
"title": "Entregar tarea",
diff --git a/public/locales/es/exercises.json b/public/locales/es/exercises.json
index 8a87c1fb0..e85d2c5e4 100644
--- a/public/locales/es/exercises.json
+++ b/public/locales/es/exercises.json
@@ -50,7 +50,7 @@
"or": " o "
},
"clone-modal" : {
- "title": "¿Como clonar un proyecto?",
+ "title": "¿Cómo clonar un proyecto?",
"text-part-one": "No lo recomendamos, pero algunos ejercicios se pueden descargar y ejecutar localmente usando VSCode y el ",
"text-part-two": ". Si quieres hacerlo, debes comenzar por clonar el proyecto en tu computadora escribiendo el siguiente comando en tu terminal:",
"note": "Nota: Esto creará una nueva carpeta “{{folder}}” en su computadora con el código del proyecto dentro.",
diff --git a/public/locales/es/signup.json b/public/locales/es/signup.json
index 7732374cb..d60bee094 100644
--- a/public/locales/es/signup.json
+++ b/public/locales/es/signup.json
@@ -146,9 +146,10 @@
"email-required": "Correo electronico es requerido",
"confirm-email-required": "Confirmar Correo electrónico es requerido",
"confirm-email-not-match": "Los correos electrónicos no coinciden",
- "termns-and-conditions-required": "Acepto recibir información a mi correo electrónico sobre talleres de programación, eventos, cursos y otros materiales de marketing. Nunca compartiremos tu correo electrónico,"
+ "receive-information": "Acepto recibir información a través de mi correo electrónico, WhatsApp y/o otros canales sobre talleres de programación, eventos, cursos y otros materiales promocionales. Nunca compartiremos tu información de contacto y podrás darte de baja fácilmente en cualquier momento."
},
"no-date-available": "Actualmente no tenemos fechas disponibles para la ubicación ingresada",
+ "agree-terms-and-conditions": "Al registrarte estás aceptando nuestros",
"alert-message": {
"title": "¿Ya eres usuario?",
"description": "Parece que ya tienes una cuenta",
diff --git a/public/locales/es/syllabus.json b/public/locales/es/syllabus.json
index 16bb47cf6..fd2c92777 100644
--- a/public/locales/es/syllabus.json
+++ b/public/locales/es/syllabus.json
@@ -3,13 +3,32 @@
"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",
+ "solution-video": "Ver solución",
+ "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",
+ "teachers-feedback": "Comentario del profesor",
+ "no-feedback": "Aún no tienes ningún comentario",
+ "notification": "Recibirás un email cuando tu profesor revise la asignación",
+ "code-reviews": "Revisiones de código",
+ "no-code-reviews": "No tienes revisiones aún",
+ "rate-comment": "Califica este comentario",
+ "start-review": "Deja tu comentario aquí...",
+ "like": "Enviaste una calificación positiva a este comentario",
+ "dislike": "Enviaste una calificación negativa a este comentario",
+ "you": "Tu",
+ "back": "Volver",
+ "next-page": "Siguiente",
+ "back-to-top": "Volver arriba",
+ "start-next": "Comenzar siguiente module:",
"no-traduction-found": "No se encontró traducción",
"no-traduction-found-description": ">Lo sentimos, no se encontró una traducción para este contenido. Estamos trabajando constantemente para ofrecer nuestros módulos en varios idiomas. Apreciamos tu paciencia y si deseas contribuir con traducciones, por favor visita nuestro repositorio en Github. Mientras tanto, puedes intentar acceder al contenido en otro idioma disponible o volver más tarde para verificar si la traducción ha sido agregada.",
"no-content-found": "No se encontró contenido",
"no-content-found-description": ">Lamentablemente, el contenido que buscas no está disponible en la ruta especificada. Estamos trabajando en mejoras continuas y valoramos tu comprensión. Si necesitas asistencia o tienes preguntas adicionales, no dudes en comunicarte con nosotros. Estamos comprometidos a brindarte el mejor servicio. Gracias por tu paciencia y comprensión.",
- "previous-page": "Anterior página",
+ "previous-page": "Anterior",
"ask-to-done": "¿Te gustaría marcar este \"{{taskType}}\" como completado antes de continuar?",
"mark-later": "Marcar luego",
"blank-page": "Este contenido no se puede visualizar dentro de 4Geeks.com , please Haga clic aquí para abrirlo en una nueva ventana",
@@ -31,5 +50,12 @@
"superseded-message": "Esta lección pertenece al archivo de legado, recomendamos leer una versión más actualizada:",
"solution-message": "Este proyecto tiene una solución modelo que puedes revisar en caso de necesitar orientación adicional.",
"open-solution": "Haz clic aquí para revisar el modelo de solución",
- "click-to-review": "Pincha aquí para revisar la solución."
+ "completion-percentage": "Porcentaje de finalización",
+ "total-steps": "Pasos completados",
+ "total-time": "Tiempo total",
+ "successful-compiles": "Compilaciones exitosas",
+ "successful-tests": "Pasos exitosos",
+ "total-errors": "Total de errores",
+ "click-to-review": "Pincha aquí para revisar la solución.",
+ "share-social-message": "Acabo de terminar de programar {{title}} en 4geeks.com"
}
diff --git a/public/locales/es/workshops.json b/public/locales/es/workshops.json
index c0b1f1305..1c8ccb3a3 100644
--- a/public/locales/es/workshops.json
+++ b/public/locales/es/workshops.json
@@ -14,6 +14,11 @@
"in-person-confirm": "Este es un evento presencial que se llevará a cabo en {{address}}. ¿Aún quieres confirmar tu asistencia?",
"confirm-attendance": "Si, si asistiré",
"deny-attendance" : "No, no asistiré",
+ "denny-access":{
+ "description": "Esto es un evento privado, solo estudiantes con acceso a",
+ "button": "No tienes acceso a este evento.",
+ "can-join": "pueden entrar"
+ },
"form": {
"title": "Únete a este evento",
"description": "Inicia sesión para unirte a otros programadores en vivo resolviendo desafíos técnicos o profesionales.",
diff --git a/public/sitemap.xml b/public/sitemap.xml
index 52b3d365d..3390c017d 100644
--- a/public/sitemap.xml
+++ b/public/sitemap.xml
@@ -2,26 +2,26 @@
https://4geeks.com/pages-sitemap.xml
- 2024-08-06T16:07:22.641Z
+ 2024-09-18T15:38:55.380Z
https://4geeks.com/howto-sitemap.xml
- 2024-08-06T16:07:22.641Z
+ 2024-09-18T15:38:55.380Z
https://4geeks.com/lessons-sitemap.xml
- 2024-08-06T16:07:22.641Z
+ 2024-09-18T15:38:55.380Z
https://4geeks.com/projects-sitemap.xml
- 2024-08-06T16:07:22.641Z
+ 2024-09-18T15:38:55.380Z
https://4geeks.com/exercises-sitemap.xml
- 2024-08-06T16:07:22.641Z
+ 2024-09-18T15:38:55.380Z
https://4geeks.com/technologies-sitemap.xml
- 2024-08-06T16:07:22.641Z
+ 2024-09-18T15:38:55.380Z
\ No newline at end of file
diff --git a/src/common/components/AttendanceModal/index.jsx b/src/common/components/AttendanceModal/index.jsx
index b12600f86..4c0b28248 100644
--- a/src/common/components/AttendanceModal/index.jsx
+++ b/src/common/components/AttendanceModal/index.jsx
@@ -17,11 +17,11 @@ import useCohortHandler from '../../hooks/useCohortHandler';
import handlers from '../../handlers';
function AttendanceModal({
- title, message, isOpen, onClose, sortedAssignments, students,
+ title, message, isOpen, onClose, students,
}) {
const { t } = useTranslation('dashboard');
const { state, setCohortSession } = useCohortHandler();
- const { cohortSession } = state;
+ const { cohortSession, sortedAssignments } = state;
const [historyLog, setHistoryLog] = useState();
const [day, setDay] = useState(cohortSession.current_day);
const [attendanceTaken, setAttendanceTaken] = useState({});
@@ -440,7 +440,6 @@ CheckboxCard.propTypes = {
AttendanceModal.propTypes = {
title: PropTypes.string,
message: PropTypes.string,
- sortedAssignments: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)).isRequired,
students: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)).isRequired,
isOpen: PropTypes.bool,
onClose: PropTypes.func,
diff --git a/src/common/components/CallToAction.jsx b/src/common/components/CallToAction.jsx
index 201a405c8..c8c3a1154 100644
--- a/src/common/components/CallToAction.jsx
+++ b/src/common/components/CallToAction.jsx
@@ -16,7 +16,7 @@ import Icon from './Icon';
function CallToAction({
background, imageSrc, icon, href, styleContainer, isExternalLink, title, text,
buttonText, onClick, margin, buttonsData, buttonStyle, fontSizeOfTitle,
- isLoading, reverseButtons,
+ isLoading, reverseButtons, buttonsContainerStyles,
}) {
return (
{buttonText && !buttonsData?.length > 0 && (
@@ -203,6 +204,7 @@ CallToAction.propTypes = {
isLoading: PropTypes.bool,
icon: PropTypes.string,
reverseButtons: PropTypes.bool,
+ buttonsContainerStyles: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))),
};
CallToAction.defaultProps = {
@@ -222,6 +224,7 @@ CallToAction.defaultProps = {
isLoading: false,
icon: '',
reverseButtons: false,
+ buttonsContainerStyles: {},
};
export default CallToAction;
diff --git a/src/common/components/CodeViewer.jsx b/src/common/components/CodeViewer.jsx
index 9cb5be3f8..3f607c0f7 100644
--- a/src/common/components/CodeViewer.jsx
+++ b/src/common/components/CodeViewer.jsx
@@ -125,14 +125,14 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
let endpoint;
if (path) {
- endpoint = 'https://rigobot.herokuapp.com/v1/prompting/completion/code-compiler-with-context/';
+ endpoint = `${RIGOBOT_HOST}/v1/prompting/completion/code-compiler-with-context/`;
completionJob.inputs = {
main_file: `File path: ${path}\nFile content:\n${code}`,
language_and_version: language,
secondary_files: fileContext,
};
} else {
- endpoint = 'https://rigobot.herokuapp.com/v1/prompting/completion/code-compiler/';
+ endpoint = `${RIGOBOT_HOST}/v1/prompting/completion/code-compiler/`;
completionJob.inputs = {
code,
language_and_version: language,
@@ -241,8 +241,9 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
]);
}}
defaultLanguage={language}
- height="300px"
+ height="290px"
options={{
+ scrollBeyondLastLine: false,
borderRadius: '4px',
scrollbar: {
alwaysConsumeMouseWheel: false,
diff --git a/src/common/components/CohortSideBar.jsx b/src/common/components/CohortSideBar.jsx
index 736292741..22d7afe56 100644
--- a/src/common/components/CohortSideBar.jsx
+++ b/src/common/components/CohortSideBar.jsx
@@ -20,6 +20,7 @@ import AvatarUser from '../../js_modules/cohortSidebar/avatarUser';
import { AvatarSkeleton } from './Skeleton';
import useOnline from '../hooks/useOnline';
import useStyle from '../hooks/useStyle';
+import useCohortHandler from '../hooks/useCohortHandler';
import useProgramList from '../store/actions/programListAction';
import { isWindow } from '../../utils';
@@ -215,7 +216,7 @@ function ProfilesSection({
}
function CohortSideBar({
- title, teacherVersionActive, cohort, cohortCity, width, containerStyle,
+ title, teacherVersionActive, width, containerStyle,
studentAndTeachers, isDisabled,
}) {
const { t } = useTranslation('dashboard');
@@ -226,6 +227,9 @@ function CohortSideBar({
const [activeStudentsLoading, setActiveStudentsLoading] = useState(true);
const [graduatedStudentsLoading, setGraduatedStudentsLoading] = useState(true);
const { addTeacherProgramList } = useProgramList();
+ const { state } = useCohortHandler();
+ const { cohortSession: cohort } = state;
+ const cohortCity = cohort?.name;
const teacher = studentAndTeachers.filter((st) => st?.role === 'TEACHER');
const activeStudents = studentAndTeachers.filter(
(st) => st?.role === 'STUDENT' && ['ACTIVE', 'GRADUATED'].includes(st?.educational_status),
@@ -517,8 +521,6 @@ CohortSideBar.propTypes = {
teacherVersionActive: PropTypes.bool,
containerStyle: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
studentAndTeachers: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))),
- cohortCity: PropTypes.string,
- cohort: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
isDisabled: PropTypes.bool,
// handleStudySession: PropTypes.func,
};
@@ -528,8 +530,6 @@ CohortSideBar.defaultProps = {
teacherVersionActive: false,
containerStyle: {},
studentAndTeachers: [],
- cohortCity: 'Miami Downtown',
- cohort: {},
isDisabled: false,
// handleStudySession: () => {},
};
diff --git a/src/common/components/DynamicContentCard/index.jsx b/src/common/components/DynamicContentCard/index.jsx
index 982efd154..c57a7fa50 100644
--- a/src/common/components/DynamicContentCard/index.jsx
+++ b/src/common/components/DynamicContentCard/index.jsx
@@ -113,7 +113,7 @@ function DynamicContentCard({ data, type, technologies, usersWorkedHere, ...rest
padding={isWorkshop ? '10px 16px 0px' : '16px'}
gridGap="14px"
width={isWorkshop ? { base: '310px', md: '360px' } : 'auto'}
- minWidth={{ base: '280px', md: '310px' }}
+ minWidth="280px"
background={isWorkshopStarted ? featuredColor : backgroundColor}
color={fontColor}
borderRadius="10px"
diff --git a/src/common/components/FooterTC.jsx b/src/common/components/FooterTC.jsx
index 4f785dbf6..c72db4237 100644
--- a/src/common/components/FooterTC.jsx
+++ b/src/common/components/FooterTC.jsx
@@ -18,7 +18,11 @@ function FooterTC({ pageProps }) {
const copyrightName = pageProps?.existsWhiteLabel ? logoData.name : '4Geeks';
const actualYear = new Date().getFullYear();
const router = useRouter();
- const noFooterRoutes = ['/cohort/[cohortSlug]/[slug]/[version]', '/syllabus/[cohortSlug]/[lesson]/[lessonSlug]'];
+ const noFooterRoutes = [
+ '/cohort/[cohortSlug]/[slug]/[version]',
+ '/syllabus/[cohortSlug]/[lesson]/[lessonSlug]',
+ '/mentorship/schedule',
+ ];
if (noFooterRoutes.includes(router.pathname)) {
return null;
diff --git a/src/common/components/Forms/Register.jsx b/src/common/components/Forms/Register.jsx
index 0b57a78a1..58a1c5d55 100644
--- a/src/common/components/Forms/Register.jsx
+++ b/src/common/components/Forms/Register.jsx
@@ -314,115 +314,9 @@ function Register({ setIsLoggedFromRegister }) {
)}
-
- {/*
-
- Date of Birth
-
-
- */}
-
- {/*
- {({ field, form }) => (
-
-
- Password
-
-
-
-
- {showPSW ? (
-
- ) : (
-
- )}
-
-
- {form.errors.password}
-
- )}
-
-
- {({ field, form }) => (
-
-
- Repeat Password
-
-
-
-
- {showRepeatPSW ? (
-
- ) : (
-
- )}
-
-
- {form.errors.passwordConfirmation}
-
- )}
- */}
-
setIsChecked(!isChecked)}>
- {t('signup:validators.termns-and-conditions-required')}
+ {t('signup:validators.receive-information')}
{' '}
{t('common:privacy-policy')}
diff --git a/src/common/components/Forms/Signup.jsx b/src/common/components/Forms/Signup.jsx
index a7b2c0424..ce51ba63e 100644
--- a/src/common/components/Forms/Signup.jsx
+++ b/src/common/components/Forms/Signup.jsx
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import * as Yup from 'yup';
-import { Avatar, Box, Button, Checkbox, useToast,
+import {
+ Avatar, Box, Button, Checkbox, useToast,
Spinner,
InputGroup,
InputRightElement,
@@ -312,12 +313,7 @@ function SignupForm({
setIsChecked(!isChecked)}>
- {t('validators.termns-and-conditions-required')}
- {' '}
-
- {t('common:privacy-policy')}
-
- .
+ {t('validators.receive-information')}
{!invertHandlerPosition && showLoginLink && (
@@ -328,17 +324,33 @@ function SignupForm({
)}
-
- {t('create-account')}
-
+
+
+ {t('create-account')}
+
+
+ {t('agree-terms-and-conditions')}
+ {' '}
+
+ {t('common:terms-and-conditions')}
+
+ {' '}
+ {t('common:word-connector.and')}
+ {' '}
+
+ {t('common:privacy-policy')}
+
+ .
+
+
{invertHandlerPosition && showLoginLink && (
{t('already-have-account')}
@@ -449,7 +461,7 @@ SignupForm.propTypes = {
formContainerStyle: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
};
SignupForm.defaultProps = {
- onHandleSubmit: () => {},
+ onHandleSubmit: () => { },
planSlug: null,
courseChoosed: '',
showVerifyEmail: true,
diff --git a/src/common/components/Icon/set/graph-up.jsx b/src/common/components/Icon/set/graph-up.jsx
new file mode 100644
index 000000000..bf19a6a70
--- /dev/null
+++ b/src/common/components/Icon/set/graph-up.jsx
@@ -0,0 +1,23 @@
+const graphUp = ({
+ width, height, style, color,
+}) => (
+
+
+
+
+);
+
+export default graphUp;
diff --git a/src/common/components/Icon/set/layout.jsx b/src/common/components/Icon/set/layout.jsx
new file mode 100644
index 000000000..b139db959
--- /dev/null
+++ b/src/common/components/Icon/set/layout.jsx
@@ -0,0 +1,50 @@
+const layout = ({
+ width, height, color, style,
+}) => (
+
+
+
+
+
+
+
+);
+
+export default layout;
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/Icon/set/send.jsx b/src/common/components/Icon/set/send.jsx
new file mode 100644
index 000000000..b24a54fc2
--- /dev/null
+++ b/src/common/components/Icon/set/send.jsx
@@ -0,0 +1,19 @@
+const send = ({
+ width, height, style, color,
+}) => (
+
+
+
+);
+
+export default send;
diff --git a/src/common/components/Icon/set/sync-error.jsx b/src/common/components/Icon/set/sync-error.jsx
new file mode 100644
index 000000000..38b7da567
--- /dev/null
+++ b/src/common/components/Icon/set/sync-error.jsx
@@ -0,0 +1,27 @@
+const syncError = ({
+ width, height, style, color,
+}) => (
+
+
+
+
+
+);
+
+export default syncError;
diff --git a/src/common/components/Icon/set/sync-success.jsx b/src/common/components/Icon/set/sync-success.jsx
new file mode 100644
index 000000000..bc0608abf
--- /dev/null
+++ b/src/common/components/Icon/set/sync-success.jsx
@@ -0,0 +1,27 @@
+const syncSuccess = ({
+ width, height, style, color,
+}) => (
+
+
+
+
+
+);
+
+export default syncSuccess;
diff --git a/src/common/components/KPI.jsx b/src/common/components/KPI.jsx
index ff04ae295..b0ddda9b5 100644
--- a/src/common/components/KPI.jsx
+++ b/src/common/components/KPI.jsx
@@ -12,7 +12,7 @@ function KPI({
label, icon, value, unit, max,
variation, variationColor, style,
changeWithColor, valueUnit, unstyled, chart,
- fontSize, iconSize, labelSize,
+ fontSize, iconSize, labelSize, textProps, ...rest
}) {
const verifiVariation = () => {
if (variation.includes('+')) return 'up';
@@ -52,7 +52,7 @@ function KPI({
const numberColors = getNumberColor();
return (
-
+
{chart !== null ? (
{label && (
@@ -66,7 +66,7 @@ function KPI({
) : (
-
+
{label}
)}
@@ -76,7 +76,7 @@ function KPI({
)}
{chart === null && (
-
+
{unit}
{value}
{/* {value.toString().length >= 3
@@ -130,6 +130,7 @@ KPI.propTypes = {
fontSize: PropTypes.string,
iconSize: PropTypes.string,
labelSize: PropTypes.string,
+ textProps: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
// variationUnit: PropTypes.string.isRequired,
};
@@ -148,6 +149,7 @@ KPI.defaultProps = {
fontSize: 'l',
iconSize: '26px',
labelSize: '15px',
+ textProps: {},
// variationUnit: '',
};
diff --git a/src/common/components/MarkDownParser/ContentHeading.jsx b/src/common/components/MarkDownParser/ContentHeading.jsx
index 0b4d6015f..9ef9598d7 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',
@@ -15,16 +17,34 @@ function ContentHeading({
QUIZ: 'answer',
};
+ const guidedExperienceStyles = () => {
+ if (!isGuidedExperience) return {};
+
+ return {
+ background: backgroundColor4,
+ margin: { base: '0px -10px', md: '0px -2rem' },
+ borderRadius: '11px 11px 0 0',
+ padding: '15px',
+ borderBottom: '1px solid #BBE5FE',
+ };
+ };
+
return content && Object.keys(content).length !== 0 && (
-
+
-
+
{title}
@@ -47,11 +67,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/MDComponents/index.jsx b/src/common/components/MarkDownParser/MDComponents/index.jsx
index de8a3dd84..2338ac19e 100644
--- a/src/common/components/MarkDownParser/MDComponents/index.jsx
+++ b/src/common/components/MarkDownParser/MDComponents/index.jsx
@@ -441,7 +441,7 @@ export function DOMComponent({ children }) {
}
export function MDCheckbox({
- index, children, subTasks, subTasksLoaded, subTasksProps, setSubTasksProps, updateSubTask,
+ index, children, subTasks, subTasksLoaded, newSubTasks, setNewSubTasks, updateSubTask,
}) {
const childrenData = children[1]?.props?.children || children;
const [isChecked, setIsChecked] = useState(false);
@@ -463,15 +463,15 @@ export function MDCheckbox({
const text = renderToStringClient();
const slug = typeof text === 'string' && slugify(text);
- const currentSubTask = subTasks.length > 0 && subTasks.filter((task) => task?.id === slug);
- const taskChecked = subTasks && subTasks.filter((task) => task?.id === slug && task?.status !== 'PENDING').length > 0;
+ const currentSubTask = subTasks.find((task) => task?.id === slug);
useEffect(() => {
// load checked tasks
+ const taskChecked = subTasks.some((task) => task?.id === slug && task?.status !== 'PENDING');
if (taskChecked) {
setIsChecked(true);
}
- }, [taskChecked]);
+ }, [subTasks]);
const taskStatus = {
true: 'DONE',
@@ -481,35 +481,25 @@ export function MDCheckbox({
useEffect(() => {
if (subTasksLoaded) {
if (
- subTasksProps?.length > 0 && subTasksProps.find((l) => l?.id === slug)
+ newSubTasks?.length > 0 && newSubTasks.find((l) => l?.id === slug)
) { return () => {}; }
- if (currentSubTask.length > 0) {
- setSubTasksProps((prev) => {
- if (prev.length > 0) {
- return [...prev, currentSubTask[0]];
- }
- return [currentSubTask[0]];
+ if (currentSubTask) {
+ setNewSubTasks((prev) => {
+ const content = [...prev];
+ if (!content.some((subTask) => subTask.id === currentSubTask.id)) content.push(currentSubTask);
+ return content;
});
} else {
- setSubTasksProps((prev) => {
- if (prev?.length > 0) {
- return [
- ...prev,
- {
- id: slug,
- status: 'PENDING',
- label: text,
- },
- ];
- }
- return [
- {
- id: slug,
- status: 'PENDING',
- label: text,
- },
- ];
+ setNewSubTasks((prev) => {
+ const task = {
+ id: slug,
+ status: 'PENDING',
+ label: text,
+ };
+ const content = [...prev];
+ if (!content.some((subTask) => subTask.id === task.id)) content.push(task);
+ return content;
});
}
}
@@ -543,7 +533,7 @@ export function MDCheckbox({
}
export function OnlyForBanner({
- children, permission, include, exclude, cohortSession, profile,
+ children, permission, include, exclude, profile,
}) {
const allCapabilities = permission.split(',').concat(include.split(',').concat(exclude.split(',')));
log('md_permissions:', allCapabilities);
@@ -553,7 +543,6 @@ export function OnlyForBanner({
onlyMember
withBanner
profile={profile}
- cohortSession={cohortSession}
capabilities={allCapabilities}
>
{children}
@@ -602,16 +591,16 @@ MDCheckbox.propTypes = {
index: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
subTasks: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)),
subTasksLoaded: PropTypes.bool,
- subTasksProps: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)),
- setSubTasksProps: PropTypes.func,
+ newSubTasks: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.any)),
+ setNewSubTasks: PropTypes.func,
updateSubTask: PropTypes.func,
};
MDCheckbox.defaultProps = {
index: 0,
subTasks: [],
subTasksLoaded: false,
- subTasksProps: [],
- setSubTasksProps: () => {},
+ newSubTasks: [],
+ setNewSubTasks: () => {},
updateSubTask: () => {},
};
@@ -632,14 +621,12 @@ MDText.propTypes = {
OnlyForBanner.propTypes = {
children: PropTypes.node.isRequired,
permission: PropTypes.string,
- cohortSession: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
profile: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
include: PropTypes.string,
exclude: PropTypes.string,
};
OnlyForBanner.defaultProps = {
permission: '',
- cohortSession: {},
profile: {},
include: '',
exclude: '',
diff --git a/src/common/components/MarkDownParser/SubTasks.jsx b/src/common/components/MarkDownParser/SubTasks.jsx
index 09a20d07d..ee9651c28 100644
--- a/src/common/components/MarkDownParser/SubTasks.jsx
+++ b/src/common/components/MarkDownParser/SubTasks.jsx
@@ -7,7 +7,7 @@ import Text from '../Text';
import { toCapitalize } from '../../../utils';
function SubTasks({
- subTasks, title, description, assetType,
+ subTasks, title, description, assetType, variant, ...rest
}) {
const { t } = useTranslation('common');
@@ -15,14 +15,39 @@ function SubTasks({
const taskPercent = Math.round((tasksDone.length / subTasks.length) * 100);
const assetValue = t(`common:asset-type-pronouns.${assetType.toLowerCase()}`);
+ const variantContainerStyles = {
+ square: {
+ flexDirection: 'column',
+ maxWidth: { base: 'none', sm: '50%' },
+ },
+ };
+
+ const variantTextStyles = {
+ square: {
+ textAlign: 'center',
+ p: '0px',
+ },
+ };
+
return subTasks.length > 0 && (
-
+
-
+
{title || toCapitalize(t('subtasks.title', { count: subTasks.length, asset_type: assetValue }))}
-
+
{description || t('subtasks.description')}
@@ -35,6 +60,7 @@ SubTasks.propTypes = {
title: PropTypes.string,
description: PropTypes.string,
assetType: PropTypes.string,
+ variant: PropTypes.string,
};
SubTasks.defaultProps = {
@@ -42,6 +68,7 @@ SubTasks.defaultProps = {
title: '',
description: '',
assetType: 'lesson',
+ variant: '',
};
export default SubTasks;
diff --git a/src/common/components/MarkDownParser/index.jsx b/src/common/components/MarkDownParser/index.jsx
index bc7707f53..b32cb24a1 100644
--- a/src/common/components/MarkDownParser/index.jsx
+++ b/src/common/components/MarkDownParser/index.jsx
@@ -18,6 +18,7 @@ import {
} from './MDComponents';
import { usePersistent } from '../../hooks/usePersistent';
import useCohortHandler from '../../hooks/useCohortHandler';
+import useModuleHandler from '../../hooks/useModuleHandler';
import Toc from './toc';
import ContentHeading from './ContentHeading';
import CallToAction from '../CallToAction';
@@ -66,8 +67,8 @@ function HrComponent() {
function IframeComponent({ src, title, width, height }) {
return ();
}
-function OnlyForComponent({ cohortSession, profile, ...props }) {
- return ( );
+function OnlyForComponent({ profile, ...props }) {
+ return ( );
}
function CodeViewerComponent(props) {
@@ -118,7 +119,7 @@ function MdCallToAction({ assetData }) {
);
}
-function ListComponent({ subTasksLoaded, subTasksProps, setSubTasksProps, subTasks, updateSubTask, ...props }) {
+function ListComponent({ subTasksLoaded, newSubTasks, setNewSubTasks, subTasks, updateSubTask, ...props }) {
const childrenExists = props?.children?.length >= 0;
const type = childrenExists && props?.children[0]?.props && props.children[0].props.type;
const type2 = childrenExists && props?.children[1]?.props && props.children[1]?.props.node?.children[0]?.properties?.type;
@@ -127,8 +128,8 @@ function ListComponent({ subTasksLoaded, subTasksProps, setSubTasksProps, subTas
className="MDCheckbox"
{...props}
subTasksLoaded={subTasksLoaded}
- subTasksProps={subTasksProps}
- setSubTasksProps={setSubTasksProps}
+ newSubTasks={newSubTasks}
+ setNewSubTasks={setNewSubTasks}
subTasks={subTasks}
updateSubTask={updateSubTask}
/>
@@ -139,14 +140,14 @@ 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, showContentHeading,
}) {
const { t, lang } = useTranslation('common');
- const [subTasks, setSubTasks] = useState([]);
const [subTasksLoaded, setSubTasksLoaded] = useState(false);
- const [subTasksProps, setSubTasksProps] = useState([]);
+ const [newSubTasks, setNewSubTasks] = useState([]);
const [learnpackActions, setLearnpackActions] = useState([]);
const [fileContext, setFileContext] = useState('');
+ const { subTasks, setSubTasks } = useModuleHandler();
const { state } = useCohortHandler();
const { cohortSession } = state;
const [profile] = usePersistent('profile', {});
@@ -183,12 +184,12 @@ function MarkDownParser({
const createSubTasksIfNotExists = async () => {
// const cleanedSubTasks = subTasks.filter((task) => task.id !== currentTask.id);
- if (currentTask?.id && subTasksProps.length > 0) {
+ if (currentTask?.id && newSubTasks.length > 0) {
const resp = await bc.todo().subtask().update(
currentTask?.id,
[
// ...cleanedSubTasks,
- ...subTasksProps,
+ ...newSubTasks,
],
);
if (resp.status >= 200 && resp.status < 400) {
@@ -200,8 +201,10 @@ function MarkDownParser({
// Create subTasks if not exists
useEffect(() => {
- createSubTasksIfNotExists();
- }, [subTasksProps]);
+ if (subTasksLoaded && subTasks.length === 0) {
+ createSubTasksIfNotExists();
+ }
+ }, [subTasksLoaded, subTasks, newSubTasks]);
const {
token, assetSlug, gitpod, interactive,
@@ -286,7 +289,7 @@ function MarkDownParser({
<>
{
setShowCloneModal(false);
@@ -309,34 +312,37 @@ function MarkDownParser({
showLineNumbers={false}
/>
-
- )}
- content={frontMatter}
- >
- {withToc && (
-
- )}
- {alerMessage && alerMessage}
+ {showContentHeading && (
+
+ )}
+ content={frontMatter}
+ currentData={currentData}
+ >
+ {withToc && (
+
+ )}
+ {alerMessage && alerMessage}
- {Array.isArray(subTasks) && subTasks?.length > 0 && (
-
- )}
-
+ {Array.isArray(subTasks) && subTasks?.length > 0 && (
+
+ )}
+
+ )}
{isPublic && withToc && (
)}
@@ -364,12 +370,12 @@ function MarkDownParser({
// table: {
// component: MDTable,
// },
- onlyfor: ({ ...props }) => OnlyForComponent({ ...props, cohortSession, profile }),
+ onlyfor: ({ ...props }) => OnlyForComponent({ ...props, profile }),
codeviewer: ({ ...props }) => CodeViewerComponent({ ...props, preParsedContent, fileContext }),
calltoaction: ({ ...props }) => MdCallToAction({ ...props, assetData }),
// Component for list of checkbox
// children[1].props.node.children[0].properties.type
- li: ({ ...props }) => ListComponent({ subTasksLoaded, subTasksProps, setSubTasksProps, subTasks, updateSubTask, ...props }),
+ li: ({ ...props }) => ListComponent({ subTasksLoaded, newSubTasks, setNewSubTasks, subTasks, updateSubTask, ...props }),
quote: Quote,
}}
>
@@ -392,6 +398,8 @@ MarkDownParser.propTypes = {
showInlineLineNumbers: PropTypes.bool,
assetData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.object])),
alerMessage: PropTypes.node,
+ isGuidedExperience: PropTypes.bool,
+ showContentHeading: PropTypes.bool,
};
MarkDownParser.defaultProps = {
content: '',
@@ -406,6 +414,8 @@ MarkDownParser.defaultProps = {
showInlineLineNumbers: true,
assetData: null,
alerMessage: null,
+ isGuidedExperience: false,
+ showContentHeading: true,
};
export default MarkDownParser;
diff --git a/src/common/components/MktSideRecommendations.jsx b/src/common/components/MktSideRecommendations.jsx
index b9385b5d6..6b2d075e5 100644
--- a/src/common/components/MktSideRecommendations.jsx
+++ b/src/common/components/MktSideRecommendations.jsx
@@ -135,10 +135,7 @@ function MktSideRecommendations({ title, endpoint, technologies, containerPaddin
if (recom?.color) {
return recom.color;
}
- if (recom?.icon_url) {
- return 'green.400';
- }
- return 'gray.100';
+ return 'blue.50';
};
useEffect(() => {
@@ -155,7 +152,7 @@ function MktSideRecommendations({ title, endpoint, technologies, containerPaddin
return (
<>
-
+
{recom?.course_translation?.title || recom.title}
@@ -183,6 +180,8 @@ function MktSideRecommendations({ title, endpoint, technologies, containerPaddin
background="white"
gridGap="10px"
width="100%"
+ _hover="none"
+ marginTop="10px"
>
{t('learn-more')}
@@ -207,13 +206,13 @@ function MktSideRecommendations({ title, endpoint, technologies, containerPaddin
const tags = [];
return (
-
+
-
+
-
-
+
+
{recom?.course_translation?.title || recom.title}
@@ -237,7 +236,7 @@ function MktSideRecommendations({ title, endpoint, technologies, containerPaddin
href={link}
alignItems="center"
display="flex"
- colorScheme={{ base: 'default', md: 'success' }}
+ colorScheme={{ base: 'default', md: 'blue.400' }}
width="auto"
color={{ base: 'green.light', md: 'white' }}
gridGap="10px"
diff --git a/src/common/components/OnlyFor.jsx b/src/common/components/OnlyFor.jsx
index 96adf28b2..0fe403e1e 100644
--- a/src/common/components/OnlyFor.jsx
+++ b/src/common/components/OnlyFor.jsx
@@ -39,19 +39,21 @@ function Component({ withBanner, children }) {
}
function OnlyFor({
- cohortSession, academy, capabilities, children, onlyMember, onlyTeachers, withBanner, profile,
+ academy, capabilities, children, onlyMember, onlyTeachers, withBanner, profile, cohort,
}) {
const academyNumber = Math.floor(academy);
const teachers = ['TEACHER', 'ASSISTANT', 'REVIEWER'];
const commonUser = ['TEACHER', 'ASSISTANT', 'STUDENT', 'REVIEWER'];
const { state } = useCohortHandler();
- const { userCapabilities: cohortCapabilities } = state;
+ const { userCapabilities: cohortCapabilities, cohortSession } = state;
+
+ const currentCohort = cohort || cohortSession;
const profileCapabilities = profile?.permissionsSlug || [];
const userCapabilities = [...new Set([...cohortCapabilities, ...profileCapabilities])];
const profileRole = profile?.roles?.length > 0 && profile?.roles[0]?.role?.toUpperCase();
- const cohortRole = cohortSession?.cohort_role?.toUpperCase() || profileRole || 'NONE';
- const isCapableAcademy = cohortSession && cohortSession.academy?.id === academyNumber;
+ const cohortRole = currentCohort?.cohort_role?.toUpperCase() || profileRole || 'NONE';
+ const isCapableAcademy = currentCohort && currentCohort.academy?.id === academyNumber;
const isMember = commonUser.includes(cohortRole);
const isTeacher = teachers.includes(cohortRole);
const capabilitiesNotExists = capabilities.length <= 0 || capabilities.includes('');
@@ -60,7 +62,7 @@ function OnlyFor({
).includes(true);
const haveRequiredCapabilities = () => {
- if (!cohortSession) return false;
+ if (!currentCohort) return false;
if (onlyTeachers && isTeacher) {
if (isCapableRole) return true;
if (capabilitiesNotExists) return true;
@@ -85,13 +87,13 @@ function OnlyFor({
}
OnlyFor.propTypes = {
- cohortSession: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])).isRequired,
academy: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
capabilities: PropTypes.arrayOf(PropTypes.string),
children: PropTypes.node.isRequired,
onlyMember: PropTypes.bool,
onlyTeachers: PropTypes.bool,
profile: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+ cohort: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
withBanner: PropTypes.bool,
};
@@ -101,6 +103,7 @@ OnlyFor.defaultProps = {
onlyMember: false,
onlyTeachers: false,
profile: {},
+ cohort: null,
withBanner: false,
};
diff --git a/src/common/components/PopoverTaskHandler.jsx b/src/common/components/PopoverTaskHandler.jsx
index b5055be7b..fc99a1409 100644
--- a/src/common/components/PopoverTaskHandler.jsx
+++ b/src/common/components/PopoverTaskHandler.jsx
@@ -1,4 +1,6 @@
-import { Box, PopoverArrow, Text, PopoverBody, PopoverCloseButton, PopoverContent, PopoverHeader, Button, FormErrorMessage, FormControl, Input, useColorModeValue, useToast, Popover, PopoverTrigger } from '@chakra-ui/react';
+/* eslint-disable react/prop-types */
+/* eslint-disable no-unused-vars */
+import { Box, PopoverArrow, Text, PopoverBody, PopoverCloseButton, PopoverContent, PopoverHeader, Button, FormErrorMessage, FormControl, Input, useColorModeValue, useToast, Popover, PopoverTrigger, Tooltip } from '@chakra-ui/react';
import * as Yup from 'yup';
import { Field, Form, Formik } from 'formik';
import PropTypes from 'prop-types';
@@ -13,56 +15,72 @@ import useStyle from '../hooks/useStyle';
import useCohortHandler from '../hooks/useCohortHandler';
import { formatBytes } from '../../utils';
-export function TextByTaskStatus({ currentTask, t }) {
+export function textByTaskStatus(currentTask) {
+ const { t } = useTranslation('dashboard');
+ const { hexColor } = useStyle();
const taskIsApproved = currentTask?.revision_status === 'APPROVED';
// task project status
if (currentTask && currentTask.task_type === 'PROJECT' && currentTask.task_status) {
if (currentTask.task_status === 'DONE' && currentTask.revision_status === 'PENDING') {
- return (
- <>
-
- {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: 'longArrowRight',
+ color: 'white',
+ 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 }) {
@@ -90,17 +108,12 @@ export function IconByTaskStatus({ currentTask, noDeliveryFormat }) {
return ;
}
-function PopoverTaskHandler({
- isLoading,
+function PopoverCustomContent({
currentAssetData,
currentTask,
sendProject,
onClickHandler,
- settingsOpen,
- allowText,
closeSettings,
- toggleSettings,
- buttonChildren,
}) {
const { t } = useTranslation('dashboard');
const { state } = useCohortHandler();
@@ -116,7 +129,6 @@ function PopoverTaskHandler({
const commonInputActiveColor = useColorModeValue('gray.800', 'gray.350');
const toast = useToast();
- const taskIsApproved = allowText && currentTask?.revision_status === 'APPROVED';
const deliveryFormatExists = typeof currentAssetData?.delivery_formats === 'string';
const noDeliveryFormat = deliveryFormatExists && currentAssetData?.delivery_formats.includes('no_delivery');
const deliveryFormatIsUrl = deliveryFormatExists && currentAssetData?.delivery_formats.includes('url');
@@ -125,7 +137,6 @@ function PopoverTaskHandler({
const howToSendProjectUrl = 'https://4geeksacademy.notion.site/How-to-deliver-a-project-e1db0a8b1e2e4fbda361fc2f5457c0de';
const maxFileSize = 1048576 * 10; // 10mb
const fileErrorExists = fileProps.some((file) => file.formatError) || fileProps.some((file) => file.sizeError);
- const isButtonDisabled = currentTask === null || taskIsApproved;
const customUrlValidation = Yup.object().shape({
githubUrl: Yup.string().matches(
@@ -217,6 +228,288 @@ function PopoverTaskHandler({
closeSettings();
};
+ return (
+
+
+ {t('deliverProject.title')}
+
+
+ {noDeliveryFormat ? (
+
+
+ {t('deliverProject.no-delivery-needed')}
+
+
+ {
+ sendProject({ task: currentTask, taskStatus: 'DONE' });
+ }}
+ colorScheme="blue"
+ isLoading={isSubmitting}
+ type="submit"
+ >
+ {t('deliverProject.handler-text')}
+
+
+ ) : (
+ <>
+ {typeof currentAssetData === 'object' && deliveryFormatIsUrl ? (
+ {
+ setIsSubmitting(true);
+ if (githubUrl !== '') {
+ sendProject({ task: currentTask, githubUrl, taskStatus: 'DONE' });
+ setIsSubmitting(false);
+ onClickHandler();
+ }
+ }}
+ validationSchema={regexUrlExists ? customUrlValidation : githubUrlValidation}
+ >
+ {() => (
+
+ )}
+
+ ) : (
+
+ {currentAssetData?.delivery_instructions?.length > 2 ? (
+
+
+
+ ) : (
+
+ {t('deliverProject.file-upload')}
+ {currentAssetData?.delivery_formats?.replaceAll(',', ', ').replaceAll('.', '').toUpperCase()}
+
+ )}
+
+
+
+
+
+
+
+ setDragOver(true)}
+ onDragLeave={() => setDragOver(false)}
+ />
+
+
+ {fileProps.some((file) => typeof file?.type === 'string') && (
+ <>
+
+ {fileProps.map((file) => {
+ const errorExists = file.formatError || file.sizeError;
+ const extension = file.name.split('.').pop();
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg'];
+ const isImage = imageExtensions.includes(extension);
+ const icon = iconDict.includes(extension) ? extension : 'file';
+ return (
+
+
+
+
+ 20}>
+ {file.name}
+
+
+ {errorExists ? (
+ <>
+
+ {file.formatError
+ ? t('deliverProject.error-file-format')
+ : file.sizeError && t('deliverProject.error-file-size')}
+ >
+ ) : formatBytes(file.size)}
+
+
+
+ {
+ handleRemoveFileInList(file.name);
+ }}
+ cursor="pointer"
+ >
+
+
+
+ );
+ })}
+
+
+ {t('deliverProject.total-size', { size: formatBytes(fileSumSize) })}
+
+ >
+ )}
+
+ handleUploadFile()}
+ isLoading={isUploading}
+ isDisabled={isUploading || fileProps?.length === 0 || fileProps.some((file) => typeof file?.type !== 'string') || fileErrorExists || fileSumSize > maxFileSize}
+ textTransform="uppercase"
+ >
+ {t('common:upload')}
+
+ handleCloseFile()}>
+ {t('common:cancel')}
+
+
+
+ )}
+ >
+ )}
+
+
+ );
+}
+
+function PopoverTaskHandler({
+ isGuidedExperience,
+ isLoading,
+ currentAssetData,
+ currentTask,
+ sendProject,
+ onClickHandler,
+ settingsOpen,
+ allowText,
+ closeSettings,
+ toggleSettings,
+ buttonChildren,
+}) {
+ const { hexColor } = useStyle();
+ const taskIsApproved = allowText && currentTask?.revision_status === 'APPROVED';
+ const isButtonDisabled = currentTask === null || taskIsApproved;
+
+ const handleCloseFile = () => {
+ closeSettings();
+ };
+
+ const textAndIcon = textByTaskStatus(currentTask || {});
+
+ if (isGuidedExperience) {
+ return (
+
+
+
+ toggleSettings()}
+ >
+
+
+
+
+
+
+
+ );
+ }
+
return (
{allowText ? (
-
+ <>
+
+ {textAndIcon.text}
+ >
) : (
)}
@@ -255,221 +551,13 @@ function PopoverTaskHandler({
-
-
- {t('deliverProject.title')}
-
-
- {noDeliveryFormat ? (
-
-
- {t('deliverProject.no-delivery-needed')}
-
-
- {
- sendProject({ task: currentTask, taskStatus: 'DONE' });
- }}
- colorScheme="blue"
- isLoading={isSubmitting}
- type="submit"
- >
- {t('deliverProject.handler-text')}
-
-
- ) : (
- <>
- {typeof currentAssetData === 'object' && deliveryFormatIsUrl ? (
- {
- setIsSubmitting(true);
- if (githubUrl !== '') {
- sendProject({ task: currentTask, githubUrl, taskStatus: 'DONE' });
- setIsSubmitting(false);
- onClickHandler();
- }
- }}
- validationSchema={regexUrlExists ? customUrlValidation : githubUrlValidation}
- >
- {() => (
-
- )}
-
- ) : (
-
- {currentAssetData?.delivery_instructions?.length > 2 ? (
-
-
-
- ) : (
-
- {t('deliverProject.file-upload')}
- {currentAssetData?.delivery_formats?.replaceAll(',', ', ').replaceAll('.', '').toUpperCase()}
-
- )}
-
-
-
-
-
-
-
- setDragOver(true)}
- onDragLeave={() => setDragOver(false)}
- />
-
-
- {fileProps.some((file) => typeof file?.type === 'string') && (
- <>
-
- {fileProps.map((file) => {
- const errorExists = file.formatError || file.sizeError;
- const extension = file.name.split('.').pop();
- const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg'];
- const isImage = imageExtensions.includes(extension);
- const icon = iconDict.includes(extension) ? extension : 'file';
- return (
-
-
-
-
- 20}>
- {file.name}
-
-
- {errorExists ? (
- <>
-
- {file.formatError
- ? t('deliverProject.error-file-format')
- : file.sizeError && t('deliverProject.error-file-size')}
- >
- ) : formatBytes(file.size)}
-
-
-
- {
- handleRemoveFileInList(file.name);
- }}
- cursor="pointer"
- >
-
-
-
- );
- })}
-
-
- {t('deliverProject.total-size', { size: formatBytes(fileSumSize) })}
-
- >
- )}
-
- handleUploadFile()}
- isLoading={isUploading}
- isDisabled={isUploading || fileProps?.length === 0 || fileProps.some((file) => typeof file?.type !== 'string') || fileErrorExists || fileSumSize > maxFileSize}
- textTransform="uppercase"
- >
- {t('common:upload')}
-
- handleCloseFile()}>
- {t('common:cancel')}
-
-
-
- )}
- >
- )}
-
-
+
);
}
@@ -485,6 +573,7 @@ PopoverTaskHandler.propTypes = {
allowText: PropTypes.bool,
toggleSettings: PropTypes.func,
buttonChildren: PropTypes.node,
+ isGuidedExperience: PropTypes.bool,
};
PopoverTaskHandler.defaultProps = {
@@ -498,15 +587,15 @@ PopoverTaskHandler.defaultProps = {
allowText: false,
toggleSettings: () => {},
buttonChildren: null,
+ isGuidedExperience: false,
};
-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/ReactPlayerV2.jsx b/src/common/components/ReactPlayerV2.jsx
index c606e3f37..5404301f1 100644
--- a/src/common/components/ReactPlayerV2.jsx
+++ b/src/common/components/ReactPlayerV2.jsx
@@ -7,7 +7,7 @@ import Icon from './Icon';
import useStyle from '../hooks/useStyle';
function ReactPlayerV2({
- url, thumbnail, controls, closeOnOverlayClick, className, withThumbnail, iframeStyle, thumbnailStyle, title, withModal, ...rest
+ url, thumbnail, controls, closeOnOverlayClick, className, withThumbnail, iframeStyle, thumbnailStyle, title, withModal, containerStyle, ...rest
}) {
const { lang } = useTranslation('exercises');
const isVideoFromDrive = url && url.includes('drive.google.com');
@@ -105,7 +105,7 @@ function ReactPlayerV2({
) : (
<>
{url && !isExternalVideoProvider && (
-
+
{contentList.map((item) => {
- const isLesson = getAssetPath(item) === 'lesson';
- const isExercise = getAssetPath(item) === 'interactive-exercise';
- const isProject = getAssetPath(item) === 'interactive-coding-tutorial';
- const isHowTo = getAssetPath(item) === 'how-to';
+ const assetPath = getAssetPath(item);
+
const prefixLang = item?.lang === 'us' ? '' : `/${item?.lang}`;
const date = new Date(item?.published_at);
const dateCreated = isValidDate(item?.published_at) ? {
@@ -69,18 +74,7 @@ function RelatedContent({ slug, type, extraQuerys, technologies, pathWithDifficu
} : {};
const getLink = () => {
- if (isLesson) {
- return `${prefixLang}/lesson/${item.slug}`;
- }
- if (isExercise) {
- return `${prefixLang}/interactive-exercise/${item.slug}`;
- }
- if (isProject) {
- return `${prefixLang}/interactive-coding-tutorial/${item.slug}`;
- }
- if (isHowTo) {
- return `${prefixLang}/how-to/${item.slug}`;
- }
+ if (assetTypePaths.includes(assetPath)) return `${prefixLang}/${assetPath}/${item.slug}`;
return `/${projectPath}${checkIsPathDifficulty(item.difficulty)}/${item.slug}`;
};
return (
diff --git a/src/common/components/ReviewModal/CodeRevisionsList.jsx b/src/common/components/ReviewModal/CodeRevisionsList.jsx
new file mode 100644
index 000000000..97fb80759
--- /dev/null
+++ b/src/common/components/ReviewModal/CodeRevisionsList.jsx
@@ -0,0 +1,88 @@
+import { Box, Button, Flex } from '@chakra-ui/react';
+import PropTypes from 'prop-types';
+import useTranslation from 'next-translate/useTranslation';
+import useStyle from '../../hooks/useStyle';
+import Icon from '../Icon';
+import Text from '../Text';
+
+function CodeRevisionsList({ codeRevisions, revisionContent, selectCodeRevision, ...rest }) {
+ const { fontColor, borderColor, hexColor, featuredLight } = useStyle();
+ const { t } = useTranslation('assignments');
+
+ return (
+
+ {codeRevisions.map((commit) => {
+ const isSelected = revisionContent?.id === commit?.id;
+ const hasBeenReviewed = typeof commit?.is_good === 'boolean';
+ const dataStruct = {
+ ...commit,
+ revision_rating: commit?.revision_rating,
+ hasBeenReviewed,
+ };
+ return (
+ selectCodeRevision(dataStruct)} _hover={{ background: featuredLight }} border="1px solid" borderColor={isSelected ? 'blue.default' : borderColor} justifyContent="space-between" alignItems="center" height="48px" padding="4px 8px" borderRadius="8px">
+
+
+
+
+
+ {commit?.file?.name}
+
+
+ {`${commit?.file?.commit_hash?.slice(0, 10)}...`}
+
+
+ {commit?.committer?.github_username && (
+
+ {commit?.committer?.github_username}
+
+ )}
+
+
+
+
+ {hasBeenReviewed ? (
+
+
+
+ ) : (
+
+
+
+
+
+
+ )}
+
+ selectCodeRevision(dataStruct)}
+ display="flex"
+ width="fit-content"
+ justifyContent="flex-end"
+ alignItems="center"
+ padding="0 1rem 0 0"
+ gridGap="10px"
+ >
+ {t('code-review.review')}
+
+
+ );
+ })}
+
+ );
+}
+CodeRevisionsList.propTypes = {
+ selectCodeRevision: PropTypes.func,
+ revisionContent: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+ codeRevisions: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])),
+};
+CodeRevisionsList.defaultProps = {
+ selectCodeRevision: () => {},
+ revisionContent: null,
+ codeRevisions: [],
+};
+
+export default CodeRevisionsList;
diff --git a/src/common/components/ReviewModal/DeliverModalContent.jsx b/src/common/components/ReviewModal/DeliverModalContent.jsx
index c7a892f90..740376934 100644
--- a/src/common/components/ReviewModal/DeliverModalContent.jsx
+++ b/src/common/components/ReviewModal/DeliverModalContent.jsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/no-unstable-nested-components */
import { useEffect, useState, useRef } from 'react';
import {
Box,
@@ -24,7 +23,7 @@ import bc from '../../services/breathecode';
import useStyle from '../../hooks/useStyle';
import Icon from '../Icon';
-function DeliverModal({
+function DeliverModalContent({
isStudent,
currentTask,
projectLink,
@@ -215,7 +214,7 @@ function DeliverModal({
);
}
-DeliverModal.propTypes = {
+DeliverModalContent.propTypes = {
isStudent: PropTypes.bool,
currentTask: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])).isRequired,
projectLink: PropTypes.string.isRequired,
@@ -229,7 +228,7 @@ DeliverModal.propTypes = {
loaders: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
proceedToCommitFiles: PropTypes.func,
};
-DeliverModal.defaultProps = {
+DeliverModalContent.defaultProps = {
isStudent: false,
readOnly: false,
showCodeReviews: false,
@@ -239,4 +238,4 @@ DeliverModal.defaultProps = {
proceedToCommitFiles: () => {},
};
-export default DeliverModal;
+export default DeliverModalContent;
diff --git a/src/common/components/ReviewModal/ReviewCodeRevision.jsx b/src/common/components/ReviewModal/ReviewCodeRevision.jsx
index 40b70add2..8c3b4a0d7 100644
--- a/src/common/components/ReviewModal/ReviewCodeRevision.jsx
+++ b/src/common/components/ReviewModal/ReviewCodeRevision.jsx
@@ -2,6 +2,7 @@ import { Box, Button, Divider, Flex, Textarea } from '@chakra-ui/react';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import useTranslation from 'next-translate/useTranslation';
+import CodeRevisionsList from './CodeRevisionsList';
import Heading from '../Heading';
import useStyle from '../../hooks/useStyle';
import bc from '../../services/breathecode';
@@ -19,7 +20,7 @@ const defaultReviewRateData = {
revision_rating: null,
};
function ReviewCodeRevision({ contextData, setContextData, stages, setStage, disableRate }) {
- const { fontColor, borderColor, lightColor, hexColor, featuredLight } = useStyle();
+ const { lightColor, hexColor, featuredLight } = useStyle();
const [reviewRateData, setReviewRateData] = useState(defaultReviewRateData);
const { t } = useTranslation('assignments');
@@ -149,62 +150,7 @@ function ReviewCodeRevision({ contextData, setContextData, stages, setStage, dis
{t('code-review.filename')}
{t('code-review.feedback-status')}
-
- {codeRevisions?.length > 0 && codeRevisions.map((commit) => {
- const isSelected = revisionContent?.id === commit?.id;
- const hasBeenReviewed = typeof commit?.is_good === 'boolean';
- const dataStruct = {
- ...commit,
- revision_rating: commit?.revision_rating,
- hasBeenReviewed,
- };
- return (
- selectCodeRevision(dataStruct)} _hover={{ background: featuredLight }} border="1px solid" borderColor={isSelected ? 'blue.default' : borderColor} justifyContent="space-between" alignItems="center" height="48px" padding="4px 8px" borderRadius="8px">
-
-
-
-
-
- {commit?.file?.name}
-
-
- {`${commit?.file?.commit_hash?.slice(0, 10)}...`}
-
-
- {commit?.committer?.github_username && (
-
- {commit?.committer?.github_username}
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- selectCodeRevision(dataStruct)}
- display="flex"
- width="fit-content"
- justifyContent="flex-end"
- alignItems="center"
- padding="0 1rem 0 0"
- gridGap="10px"
- >
- {t('code-review.review')}
-
-
- );
- })}
-
+
{revisionContent?.id && (
diff --git a/src/common/components/ReviewModal/index.jsx b/src/common/components/ReviewModal/index.jsx
index 22eaed58c..417296764 100644
--- a/src/common/components/ReviewModal/index.jsx
+++ b/src/common/components/ReviewModal/index.jsx
@@ -18,8 +18,7 @@ import ReviewCodeRevision from './ReviewCodeRevision';
import { usePersistent } from '../../hooks/usePersistent';
import useCohortHandler from '../../hooks/useCohortHandler';
import PopoverTaskHandler from '../PopoverTaskHandler';
-import { updateAssignment } from '../../hooks/useModuleHandler';
-import useModuleMap from '../../store/actions/moduleMapAction';
+import useModuleHandler from '../../hooks/useModuleHandler';
import iconDict from '../../utils/iconDict.json';
import UndoApprovalModal from '../UndoApprovalModal';
import useAuth from '../../hooks/useAuth';
@@ -56,13 +55,13 @@ function ReviewModal({ isExternal, externalFiles, isOpen, isStudent, externalDat
});
const [profile] = usePersistent('profile', {});
const [comment, setComment] = useState('');
+ const { updateAssignment } = useModuleHandler();
const { state } = useCohortHandler();
const { cohortSession } = state;
const [currentAssetData, setCurrentAssetData] = useState(null);
const [settingsOpen, setSettingsOpen] = useState(false);
const [openUndoApproval, setOpenUndoApproval] = useState(false);
const [fileData, setFileData] = useState();
- const { contextState, setContextState } = useModuleMap();
const [reviewStatus, setReviewStatus] = useState('');
const [contextData, setContextData] = useState({
commitFiles: {
@@ -85,7 +84,7 @@ function ReviewModal({ isExternal, externalFiles, isOpen, isStudent, externalDat
const hasNotBeenReviewed = revisionStatus === PENDING;
const hasBeenApproved = revisionStatus === APPROVED;
const hasBeenRejected = revisionStatus === REJECTED;
- const noFilesToReview = !hasBeenApproved && contextData?.commitFiles?.fileList?.length === 0;
+ const noFilesToReview = !hasBeenApproved && (contextData?.commitFiles?.fileList?.length === 0 || !('commitFiles' in contextData));
const codeRevisionsNotExists = typeof contextData?.code_revisions === 'undefined';
const hasFilesToReview = contextData?.code_revisions?.length > 0 || !isStudent; // Used to show rigobot files content
const stage = stageHistory?.current;
@@ -427,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,
+ task, closeSettings, githubUrl, taskStatus: newTaskStatus,
});
};
@@ -627,7 +626,7 @@ function ReviewModal({ isExternal, externalFiles, isOpen, isStudent, externalDat
)}
- {(!isAuthenticatedWithRigobot || !noFilesToReview) && hasFilesToReview && !disableRate && (
+ {(!isAuthenticatedWithRigobot || !noFilesToReview) && hasFilesToReview && !disableRate && contextData?.commitFiles?.fileList?.length > 0 && (
diff --git a/src/common/components/SupportSidebar/Mentoring.jsx b/src/common/components/SupportSidebar/Mentoring.jsx
index a02592404..57fc097c0 100644
--- a/src/common/components/SupportSidebar/Mentoring.jsx
+++ b/src/common/components/SupportSidebar/Mentoring.jsx
@@ -7,12 +7,9 @@ import {
// useToast,
} from '@chakra-ui/react';
import PropTypes from 'prop-types';
-import { format } from 'date-fns';
-import { es } from 'date-fns/locale';
import { useRouter } from 'next/router';
import useTranslation from 'next-translate/useTranslation';
import bc from '../../services/breathecode';
-import MentoringFree from './MentoringFree';
import MentoringConsumables from './MentoringConsumables';
import useAuth from '../../hooks/useAuth';
import useCohortHandler from '../../hooks/useCohortHandler';
@@ -20,28 +17,19 @@ import useCohortHandler from '../../hooks/useCohortHandler';
function Mentoring({
width, allCohorts, allSyllabus, programServices, subscriptions, subscriptionData,
}) {
+ // const toast = useToast();
const { t } = useTranslation('dashboard');
- const [savedChanges, setSavedChanges] = useState({});
+ const router = useRouter();
+ const { isLoading, user } = useAuth();
+ const { slug } = router.query;
const { state } = useCohortHandler();
const { cohortSession } = state;
- const router = useRouter();
- const [consumables, setConsumables] = useState({});
+ const [consumables, setConsumables] = useState([]);
const [mentoryProps, setMentoryProps] = useState({});
const [allMentorsAvailable, setAllMentorsAvailable] = useState([]);
const [programMentors, setProgramMentors] = useState([]);
- const [isAvailableForConsumables, setIsAvailableForConsumables] = useState(true);
- const { isLoading, user } = useAuth();
- // const toast = useToast();
- const { slug } = router.query;
-
- const [searchProps, setSearchProps] = useState({
- serviceSearch: '',
- mentorSearch: '',
- });
-
- const servicesFiltered = programServices.list.filter(
- (l) => l.name.toLowerCase().includes(searchProps.serviceSearch),
- );
+ const [cohortSessionIsSaaS, setCohortSessionIsSaaS] = useState(true);
+ const [searchProps, setSearchProps] = useState({ serviceSearch: '', mentorSearch: '' });
const filterServices = () => {
if (subscriptionData?.selected_mentorship_service_set?.mentorship_services?.length > 0) {
@@ -62,38 +50,13 @@ function Mentoring({
const mentorsFiltered = programMentors.filter(
(mentor) => {
const fullName = `${mentor.user.first_name} ${mentor.user.last_name}`.toLowerCase();
- const mentorServices = fullName.includes(searchProps.mentorSearch) && mentor.services.some((sv) => sv.status === 'ACTIVE'
- && sv.slug === mentoryProps?.service?.slug);
- return mentorServices;
+ return (
+ fullName.includes(searchProps.mentorSearch)
+ && mentor.services.some((sv) => sv.status === 'ACTIVE' && sv.slug === mentoryProps?.service?.slug)
+ );
},
);
- const dateFormated = {
- en: mentoryProps?.date && format(new Date(mentoryProps.date), 'MMMM dd'),
- es: mentoryProps?.date && format(new Date(mentoryProps.date), "dd 'de' MMMM", { locale: es }),
- };
-
- const dateFormated2 = {
- en: mentoryProps?.date && format(new Date(mentoryProps.date), 'MMMM dd, yyyy'),
- es: mentoryProps?.date && format(new Date(mentoryProps.date), "dd 'de' MMMM, yyyy", { locale: es }),
- };
-
- useEffect(() => {
- if (mentoryProps?.time) {
- const [hours, minutes] = mentoryProps.time.split(':');
-
- const nDate = mentoryProps?.date
- && new Date(mentoryProps.date);
-
- nDate.setHours(+hours, +minutes, 0, 0); // set hours/minute;
- setMentoryProps({ ...mentoryProps, date: nDate });
- setSavedChanges({ ...mentoryProps, date: nDate });
- }
- }, [mentoryProps?.time]);
-
- const step1 = !mentoryProps?.service;
- const step2 = mentoryProps?.service && !mentoryProps?.date;
-
const getAllMentorsAvailable = async () => {
const servicesSlugs = programServices.list.map((service) => service?.slug);
@@ -106,7 +69,6 @@ function Mentoring({
academies[academy.id].services.push(restOfService);
});
- // Convert the object to an array of academies with their services
const academyData = Object.entries(academies).map(([academy, values]) => ({
id: Number(academy),
services: values.services,
@@ -130,6 +92,21 @@ function Mentoring({
return [];
};
+ const sortByConsumptionAvailability = (allConsumables) => allConsumables.sort((a, b) => {
+ const balanceA = a?.balance?.unit;
+ const balanceB = b?.balance?.unit;
+
+ if (balanceA === -1 && balanceB !== -1) return -1;
+ if (balanceA !== -1 && balanceB === -1) return 1;
+
+ if (balanceA > 0 && balanceB <= 0) return -1;
+ if (balanceA <= 0 && balanceB > 0) return 1;
+
+ if (balanceA > 0 && balanceB > 0) return balanceB - balanceA;
+
+ return 0;
+ });
+
const getMentorsAndConsumables = async () => {
const mentors = await getAllMentorsAvailable();
const reqConsumables = await bc.payment().service().consumable()
@@ -141,7 +118,8 @@ function Mentoring({
}))));
const allConsumables = await Promise.all(reqConsumables);
- setConsumables(allConsumables);
+ const sortedConsumables = sortByConsumptionAvailability(allConsumables);
+ setConsumables(sortedConsumables);
setAllMentorsAvailable(mentors);
};
@@ -153,73 +131,34 @@ function Mentoring({
useEffect(() => {
const existsCohortSession = typeof cohortSession?.available_as_saas === 'boolean';
-
if (existsCohortSession) {
- setIsAvailableForConsumables(cohortSession?.available_as_saas);
- }
- if (!existsCohortSession) {
- if (allCohorts.length > 0) {
- setIsAvailableForConsumables(allCohorts?.some((c) => c.cohort?.available_as_saas === true));
- }
+ setCohortSessionIsSaaS(cohortSession?.available_as_saas);
}
}, [allCohorts]);
- const mentorshipService = consumables?.mentorship_service_sets?.find(
- (c) => c?.slug.toLowerCase() === subscriptionData?.selected_mentorship_service_set?.slug.toLowerCase(),
- );
-
return !isLoading && user?.id && (
{t('supportSideBar.mentoring-label')}
- {isAvailableForConsumables ? (
- 0 ? programServices.list : subscriptionData?.selected_mentorship_service_set?.mentorship_services,
- servicesFiltered: suscriptionServicesFiltered,
- dateFormated,
- searchProps,
- setSearchProps,
- setProgramMentors,
- savedChanges,
- setSavedChanges,
- mentorsFiltered,
- step1,
- step2,
- dateFormated2,
- allMentorsAvailable,
- subscriptionData,
- allSubscriptions: subscriptions,
- }}
- />
- ) : (
-
- )}
+ 0 ? programServices.list : subscriptionData?.selected_mentorship_service_set?.mentorship_services,
+ servicesFiltered: suscriptionServicesFiltered,
+ searchProps,
+ setSearchProps,
+ setProgramMentors,
+ mentorsFiltered,
+ allMentorsAvailable,
+ subscriptionData,
+ cohortSessionIsSaaS,
+ allSubscriptions: subscriptions,
+ }}
+ />
);
}
diff --git a/src/common/components/SupportSidebar/MentoringConsumables.jsx b/src/common/components/SupportSidebar/MentoringConsumables.jsx
index 3f8eb7479..86667d45b 100644
--- a/src/common/components/SupportSidebar/MentoringConsumables.jsx
+++ b/src/common/components/SupportSidebar/MentoringConsumables.jsx
@@ -18,7 +18,7 @@ import Text from '../Text';
import { AvatarSkeletonWrapped } from '../Skeleton';
import modifyEnv from '../../../../modifyEnv';
import { validatePlanExistence } from '../../handlers/subscriptions';
-import { getStorageItem, isDevMode } from '../../../utils';
+import { getStorageItem } from '../../../utils';
import { reportDatalayer } from '../../../utils/requests';
function NoConsumablesCard({ t, setMentoryProps, handleGetMoreMentorships, mentoryProps, subscriptionData, disableBackButton = false, ...rest }) {
@@ -53,9 +53,9 @@ function NoConsumablesCard({ t, setMentoryProps, handleGetMoreMentorships, mento
{!disableBackButton && (
- setMentoryProps({})} letterSpacing="0.05em">
- {t('common:go-back')}
-
+ setMentoryProps({})} letterSpacing="0.05em">
+ {t('common:go-back')}
+
)}
);
@@ -90,13 +90,12 @@ function ProfilesSection({
}
function MentoringConsumables({
- mentoryProps, width, consumables, mentorshipService, setMentoryProps,
- programServices, dateFormated, servicesFiltered, searchProps,
- setSearchProps, setProgramMentors, savedChanges, setSavedChanges,
- mentorsFiltered, dateFormated2, allMentorsAvailable, subscriptionData, allSubscriptions,
+ mentoryProps, width, consumables, cohortSessionIsSaaS, setMentoryProps,
+ programServices, servicesFiltered, searchProps, setSearchProps, setProgramMentors,
+ mentorsFiltered, allMentorsAvailable, subscriptionData, allSubscriptions,
+ queryService, queryMentor, titleSize,
}) {
const { t } = useTranslation('dashboard');
-
const { user } = useAuth();
const BREATHECODE_HOST = modifyEnv({ queryString: 'host', env: process.env.BREATHECODE_HOST });
const commonBackground = useColorModeValue('white', 'rgba(255, 255, 255, 0.1)');
@@ -108,29 +107,32 @@ function MentoringConsumables({
const [isFetchingDataForModal, setIsFetchingDataForModal] = useState(false);
const [dataToGetAccessModal, setDataToGetAccessModal] = useState({});
const [consumableOfService, setConsumableOfService] = useState({});
+ const [servicesWithMentorsAvailable, setServicesWithMentorsAvailable] = useState([]);
+ const [hasReset, setHasReset] = useState(false);
+ const [notifyError, setNotifyError] = useState(true);
+ const [shouldHandleService, setShouldHandleService] = useState(true);
const router = useRouter();
- const toast = useToast();
const { slug } = router.query;
-
- const mentorshipBalance = mentorshipService?.balance?.unit || mentorshipService?.balance || consumableOfService?.balance?.unit;
+ const toast = useToast();
+ const mentorshipBalance = consumableOfService?.balance?.unit;
const currentBalance = Number(mentorshipBalance && mentorshipBalance);
const calculateExistenceOfConsumable = () => {
if (consumableOfService.available_as_saas === false) return true;
if (consumableOfService?.balance) return consumableOfService?.balance?.unit > 0 || consumableOfService?.balance?.unit === -1;
- return consumables?.mentorship_service_sets?.length > 0 && Object.values(mentorshipService).length > 0 && (currentBalance > 0 || currentBalance === -1);
+ return consumables?.mentorship_service_sets?.length > 0 && (currentBalance > 0 || currentBalance === -1);
};
const existConsumablesOnCurrentService = calculateExistenceOfConsumable();
const getMostRecentPaidAt = (invoices) => invoices.reduce((latest, invoice) => {
const paidAtDate = new Date(invoice.paid_at);
return paidAtDate > latest ? paidAtDate : latest;
- }, new Date(0)); // Initialize with a very old date
+ }, new Date(0));
const sortByMostRecentInvoice = (a, b) => {
const latestA = getMostRecentPaidAt(a.invoices);
const latestB = getMostRecentPaidAt(b.invoices);
- return latestB - latestA; // Descending order
+ return latestB - latestA;
};
const currentServiceSubscription = Array.isArray(allSubscriptions) && allSubscriptions.sort(sortByMostRecentInvoice).find((subscription) => subscription.selected_mentorship_service_set.mentorship_services.some((service) => service.slug === mentoryProps?.service?.slug));
@@ -144,7 +146,7 @@ function MentoringConsumables({
}
}, [allMentorsAvailable]);
- const manageMentorsData = (service, data) => {
+ const manageMentorsData = (service, mentors) => {
reportDatalayer({
dataLayer: {
event: 'select_mentorship_service',
@@ -153,18 +155,18 @@ function MentoringConsumables({
mentorship_service: service?.slug,
},
});
- const relatedConsumables = consumables.find((consumable) => consumable?.mentorship_services?.some((c) => c?.slug === service?.slug));
- setProgramMentors(data);
+ const relatedConsumable = consumables.find((consumable) => consumable?.mentorship_services?.some((c) => c?.slug === service?.slug));
+ setProgramMentors(mentors);
setConsumableOfService({
- ...relatedConsumables,
+ ...relatedConsumable,
balance: {
- unit: service?.academy?.available_as_saas === false ? -1 : relatedConsumables?.balance?.unit,
+ unit: (service?.academy?.available_as_saas === false || cohortSessionIsSaaS === false) ? -1 : relatedConsumable?.balance?.unit,
},
available_as_saas: service?.academy?.available_as_saas,
});
+
setTimeout(() => {
setMentoryProps({ ...mentoryProps, service });
- setSavedChanges({ ...savedChanges, service });
}, 50);
};
@@ -194,7 +196,71 @@ function MentoringConsumables({
}
};
- const servicesWithMentorsAvailable = servicesFiltered.filter((service) => allMentorsAvailable.some((mentor) => mentor.services.some((mentServ) => mentServ.slug === service.slug)));
+ useEffect(() => {
+ const getAllServicesWithMentors = () => servicesFiltered.filter((service) => allMentorsAvailable.some((ment) => ment.services.some((mentServ) => mentServ.slug === service.slug)));
+ const getServicesWithMentor = (mentor) => servicesFiltered.filter((service) => mentor.services.some((mentServ) => mentServ.slug === service.slug));
+
+ const showErrorToast = () => {
+ toast({
+ position: 'top',
+ title: 'Error',
+ description: `${t('supportSideBar.mentor-not-found')} "${queryMentor}"`,
+ status: 'error',
+ duration: 7000,
+ isClosable: true,
+ });
+ };
+
+ let servWithMentorsAvailable = getAllServicesWithMentors();
+
+ if (queryMentor && allMentorsAvailable.length > 0 && !hasReset) {
+ const mentorFound = allMentorsAvailable.find((ment) => ment.slug === queryMentor);
+
+ if (!mentorFound && notifyError) {
+ showErrorToast();
+ setNotifyError(false);
+ }
+ if (mentorFound) {
+ servWithMentorsAvailable = getServicesWithMentor(mentorFound);
+ setProgramMentors([mentorFound]);
+ }
+ }
+
+ setServicesWithMentorsAvailable(servWithMentorsAvailable);
+
+ if (!hasReset && queryMentor) {
+ setOpen(true);
+ }
+ }, [servicesFiltered, queryMentor, hasReset]);
+
+ useEffect(() => {
+ if (queryService && servicesWithMentorsAvailable?.length > 0 && shouldHandleService && !hasReset) {
+ const serviceFound = servicesWithMentorsAvailable.find((service) => service.slug === queryService);
+
+ if (!serviceFound && notifyError) {
+ toast({
+ position: 'top',
+ title: 'Error',
+ description: `${t('supportSideBar.service-not-found')} "${queryService}" ${queryMentor ? `${t('common:word-connector.for')} "${queryMentor}"` : ''}`,
+ status: 'error',
+ duration: 7000,
+ isClosable: true,
+ });
+ setNotifyError(false);
+ return;
+ }
+
+ handleService(serviceFound);
+ setOpen(true);
+ setShouldHandleService(false);
+ }
+ }, [hasReset, servicesWithMentorsAvailable]);
+
+ const reset = () => {
+ if (mentoryProps?.service) setMentoryProps({});
+ else setOpen(false);
+ setHasReset(true);
+ };
const handleGetMoreMentorships = () => {
setIsFetchingDataForModal(true);
@@ -212,16 +278,17 @@ function MentoringConsumables({
})
.finally(() => setIsFetchingDataForModal(false));
};
- const reportBookMentor = () => {
+
+ const reportBookMentor = (mentorSelected) => {
reportDatalayer({
dataLayer: {
event: 'book_mentorship_session',
path: router.pathname,
consumables_amount: currentBalance,
mentorship_service: mentoryProps?.service?.slug,
- mentor_name: `${mentoryProps.mentor.user.first_name} ${mentoryProps.mentor.user.last_name}`,
- mentor_id: mentoryProps.mentor.slug,
- mentor_booking_url: mentoryProps.mentor.booking_url,
+ mentor_name: `${mentorSelected.user.first_name} ${mentorSelected.user.last_name}`,
+ mentor_id: mentorSelected.slug,
+ mentor_booking_url: mentorSelected.booking_url,
},
});
};
@@ -236,12 +303,12 @@ function MentoringConsumables({
>
{open && mentoryProps?.service && (
- setMentoryProps({})} cursor="pointer">
+
)}
{open && !mentoryProps?.service && (
- setOpen(false)} cursor="pointer">
+
)}
@@ -249,7 +316,7 @@ function MentoringConsumables({
{!mentoryProps?.service && (consumables?.mentorship_service_sets?.length !== 0 || currentBalance !== 0) && (
<>
-
+
{t('supportSideBar.mentoring')}
@@ -285,11 +352,10 @@ function MentoringConsumables({
)}
>
)}
-
- {t('supportSideBar.mentors-available', { count: 3 })}
+
+ {t('supportSideBar.mentors-available', { count: allMentorsAvailable.length })}
- {/* Schedule event */}
)}
- {isDevMode && open && mentoryProps?.service && !mentoryProps?.mentor && existConsumablesOnCurrentService && (
-
+ {open && mentoryProps?.service && !mentoryProps?.mentor && existConsumablesOnCurrentService && (
+
{t('mentorship.you-have')}
@@ -326,178 +391,101 @@ function MentoringConsumables({
)}
+
{mentoryProps?.service && open && !mentoryProps?.mentor && !existConsumablesOnCurrentService ? (
- ) : open && (
+ ) : open
+ && (
<>
- {!mentoryProps?.time ? (
+ {mentoryProps?.service && (
+
+
+
+
+
+ {mentoryProps.service.name}
+
+
+ )}
+
+ {!mentoryProps?.service && programServices.length > 0 && (
<>
- {mentoryProps?.service && (
- 0 ? '22px' : '34px'} px="20px" py="15px" textAlign="center" w="100%" borderTopRadius="0.375rem">
-
-
-
-
- {mentoryProps.service.name}
-
-
- )}
- {mentoryProps?.mentor && (
-
-
-
-
- {`${mentoryProps.mentor.user.first_name} ${mentoryProps.mentor.user.last_name}`}
-
-
- {`${parseInt(mentoryProps.service.duration, 10) / 60} min Mentoring Session`}
-
+
+ setSearchProps({ ...searchProps, serviceSearch: e.target.value?.toLocaleLowerCase() })} background={commonBackground} borderBottomRadius="0" border="0" placeholder={t('supportSideBar.select-type')} />
+
+
+
+
+
+ {servicesWithMentorsAvailable.length > 0 ? servicesWithMentorsAvailable.map((service) => (
+ handleService(service)} borderColor={borderColor} py="14px" background={commonBackground} width="100%" px="22px" _hover={{ background: useColorModeValue('featuredLight', 'gray.700') }}>
+ {service.name}
-
- )}
- {mentoryProps?.date && (
-
- {dateFormated[router.locale]}
-
- )}
-
- {!mentoryProps?.service && programServices.length > 0 && (
- <>
-
- setSearchProps({ ...searchProps, serviceSearch: e.target.value?.toLocaleLowerCase() })} background={commonBackground} borderBottomRadius="0" border="0" placeholder={t('supportSideBar.select-type')} />
-
-
-
-
-
- {servicesWithMentorsAvailable.length > 0 ? servicesWithMentorsAvailable.map((service) => (
- handleService(service)} borderColor={borderColor} py="14px" background={commonBackground} width="100%" px="22px" _hover={{ background: useColorModeValue('featuredLight', 'gray.700') }}>
- {service.name}
-
- )) : (
-
- {t('common:search-not-found')}
-
- )}
+ )) : (
+
+ {t('common:search-not-found')}
- >
- )}
-
- {mentoryProps?.service && !mentoryProps?.mentor
- && (
- <>
-
- setSearchProps({ ...searchProps, mentorSearch: e.target.value?.toLowerCase() })} background={commonBackground} borderBottomRadius="0" border="0" placeholder={t('supportSideBar.search-mentor')} />
-
-
-
-
-
- {mentorsFiltered.length > 0 ? mentorsFiltered.map((mentor, i) => (
-
- {i !== 0 && (
-
- )}
-
- {/* onClick={() => { setMentoryProps({ ...mentoryProps, mentor }); setSavedChanges({ ...savedChanges, mentor }); }} */}
-
-
-
- {`${mentor.user.first_name} ${mentor.user.last_name}`}
-
-
-
- {(mentor.one_line_bio && mentor.one_line_bio !== '') ? `${mentor.one_line_bio} ` : ''}
- {mentor?.booking_url ? (
- reportBookMentor()}
- href={`${BREATHECODE_HOST}/mentor/${mentor?.slug}?utm_campaign=${mentoryProps?.service?.slug}&utm_source=4geeks&salesforce_uuid=${user?.id}&token=${accessToken}`}
- target="_blank"
- rel="noopener noreferrer"
- >
- {t('supportSideBar.create-session-text')}
-
- ) : (
-
- {t('supportSideBar.no-mentor-link')}
-
- )}
-
-
-
-
- )) : (
-
- {t('supportSideBar.no-mentors')}
-
- )}
-
- >
)}
+
>
- ) : (
- <>
- {mentoryProps?.mentor && mentoryProps?.date && mentoryProps?.time && (
-
- {!mentoryProps?.confirm ? (
-
- ) : (
-
- )}
-
-
- {`${mentoryProps.mentor.user.first_name} ${mentoryProps.mentor.user.last_name} - ${mentoryProps?.service?.name}`}
-
-
- {dateFormated2[router.locale]}
-
-
- {`${mentoryProps.time} hs.`}
-
+ )}
- {!mentoryProps?.confirm && (
- setMentoryProps({ ...mentoryProps, confirm: true })} textTransform="uppercase" margin="15px auto 10px auto">
- Confirm
-
- )}
- {mentoryProps?.confirm && (
- setOpen(false)} textTransform="uppercase" margin="15px auto 10px auto">
- Done
-
+ {mentoryProps?.service && !mentoryProps?.mentor && (
+ <>
+
+ setSearchProps({ ...searchProps, mentorSearch: e.target.value?.toLowerCase() })} background={commonBackground} borderBottomRadius="0" border="0" placeholder={t('supportSideBar.search-mentor')} />
+
+
+
+
+
+ {mentorsFiltered.length > 0 ? mentorsFiltered.map((mentor, i) => (
+
+ {i !== 0 && (
+
)}
- {!mentoryProps?.confirm && (
- setMentoryProps({ ...mentoryProps, time: null })} className="link" width="fit-content" margin="0 auto">
- Go back
+
+
+
+
+ {`${mentor.user.first_name} ${mentor.user.last_name}`}
+
+
+
+ {(mentor.one_line_bio && mentor.one_line_bio !== '') ? `${mentor.one_line_bio} ` : ''}
+ {mentor?.booking_url ? (
+ reportBookMentor(mentor)}
+ href={`${BREATHECODE_HOST}/mentor/${mentor?.slug}?utm_campaign=${mentoryProps?.service?.slug}&utm_source=4geeks&salesforce_uuid=${user?.id}&token=${accessToken}`}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {t('supportSideBar.create-session-text')}
+
+ ) : (
+
+ {t('supportSideBar.no-mentor-link')}
+
+ )}
+
- )}
+
+
+ )) : (
+
+ {t('supportSideBar.no-mentors')}
-
- )}
+ )}
+
>
)}
>
@@ -519,30 +507,30 @@ function MentoringConsumables({
MentoringConsumables.propTypes = {
mentoryProps: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])),
width: PropTypes.string,
+ titleSize: PropTypes.string,
+ queryService: PropTypes.string,
+ queryMentor: PropTypes.string,
consumables: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
- mentorshipService: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
setMentoryProps: PropTypes.func.isRequired,
programServices: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])),
- dateFormated: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])).isRequired,
servicesFiltered: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])).isRequired,
searchProps: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])).isRequired,
setSearchProps: PropTypes.func.isRequired,
- savedChanges: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any, PropTypes.string])).isRequired,
- setSavedChanges: PropTypes.func.isRequired,
setProgramMentors: PropTypes.func,
mentorsFiltered: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])).isRequired,
- dateFormated2: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])).isRequired,
subscriptionData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
allSubscriptions: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])),
};
MentoringConsumables.defaultProps = {
+ queryService: undefined,
+ queryMentor: undefined,
+ titleSize: undefined,
mentoryProps: [],
width: '100%',
consumables: {},
- mentorshipService: {},
programServices: [],
- setProgramMentors: () => {},
+ setProgramMentors: () => { },
subscriptionData: {},
allSubscriptions: [],
};
diff --git a/src/common/components/TeacherSidebar.jsx b/src/common/components/TeacherSidebar.jsx
index b32ee0475..ba980866d 100644
--- a/src/common/components/TeacherSidebar.jsx
+++ b/src/common/components/TeacherSidebar.jsx
@@ -11,6 +11,7 @@ import Icon from './Icon';
import Text from './Text';
import AttendanceModal from './AttendanceModal';
import useCohortHandler from '../hooks/useCohortHandler';
+import useAuth from '../hooks/useAuth';
import { isValidDate, isWindow } from '../../utils';
function ItemText({ text }) {
@@ -50,9 +51,10 @@ function ItemButton({
}
function TeacherSidebar({
- title, user, students, width, sortedAssignments,
+ title, students, width,
}) {
const { t } = useTranslation('dashboard');
+ const { user } = useAuth();
const { colorMode } = useColorMode();
const [openAttendance, setOpenAttendance] = useState(false);
const { state } = useCohortHandler();
@@ -163,7 +165,6 @@ function TeacherSidebar({
setOpenAttendance(false)}
title={t('attendance-modal.start-today-class')}
// title="Start your today's class"
@@ -178,18 +179,14 @@ function TeacherSidebar({
TeacherSidebar.propTypes = {
title: PropTypes.string,
- user: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
students: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])),
width: PropTypes.string,
- sortedAssignments: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))),
};
TeacherSidebar.defaultProps = {
title: 'Actions',
- user: {},
students: [],
width: '100%',
- sortedAssignments: [],
};
ItemText.propTypes = {
diff --git a/src/common/components/Timeline.jsx b/src/common/components/Timeline.jsx
index f5b287112..2d7a7c086 100644
--- a/src/common/components/Timeline.jsx
+++ b/src/common/components/Timeline.jsx
@@ -4,28 +4,26 @@ 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 useStyle from '../hooks/useStyle';
import Icon from './Icon';
import Text from './Text';
-const color = {
- light: 'blue.light',
- dark: 'featuredDark',
-};
-
function Timeline({
- title, assignments, technologies, width, onClickAssignment, showPendingTasks,
+ title, assignments, technologies, width, onClickAssignment, showPendingTasks, variant,
}) {
const { t, lang } = useTranslation('syllabus');
- const { colorMode } = useColorMode();
const router = useRouter();
+ const { hexColor, fontColor, backgroundColor, backgroundColor3 } = useStyle();
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) => {
@@ -38,29 +36,93 @@ function Timeline({
}
};
+ const scrollTop = () => {
+ const element = document.getElementById('main-container');
+ if (element) {
+ element.scrollTo({
+ behavior: 'smooth',
+ top: 0,
+ });
+ }
+ };
+
useEffect(() => {
if (assignments?.length > 0) {
const assignmentFound = assignments.find((item) => (
item?.translations?.us?.slug === lessonSlug
|| item?.translations?.es?.slug === lessonSlug
+ || item?.slug === lessonSlug
));
- const slug = currentAssignment?.translations?.us?.slug || currentAssignment?.translations?.en?.slug;
- setCurrentDefaultSlug(slug);
setCurrentAssignment(assignmentFound);
}
}, [lessonSlug, assignments]);
useEffect(() => {
- if (currentDefaultSlug) {
- scrollIntoView(currentDefaultSlug);
+ if (currentAssignment?.slug) {
+ scrollIntoView(currentAssignment.slug);
}
- }, [currentDefaultSlug]);
+ }, [currentAssignment]);
const handleClick = (e, item) => {
e.preventDefault();
e.stopPropagation();
onClickAssignment(e, item);
+ scrollTop();
};
+ 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);
+ };
+
+ if (variant === 'guided-experience') {
+ return (
+
+ {assignments.length > 0 ? assignments.map((item, index) => {
+ const mapIndex = index;
+ const muted = item?.slug !== currentAssignment?.slug;
+ const assignmentTitle = getAssignmentTitle(item);
+
+ return (
+ handleClick(e, item)}
+ width="100%"
+ borderRadius="0px 8px 8px 0px "
+ bg={!muted ? backgroundColor : 'none'}
+ borderLeft={!muted && `4px solid ${hexColor.blueDefault}`}
+ padding="16px"
+ display="flex"
+ alignItems="center"
+ justifyContent="space-between"
+ gap="5px"
+ _hover={{ background: backgroundColor3 }}
+ >
+
+
+ {index + 1}
+
+ {item.type}
+
+ {assignmentTitle}
+
+ {item.task_status === 'DONE' && (
+
+ )}
+
+ );
+ }) : (
+
+ {showPendingTasks ? t('no-modules-to-show') : t('module-not-started')}
+
+ )}
+
+ );
+ }
+
return (
<>
@@ -97,10 +159,8 @@ 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 muted = item?.slug !== currentAssignment?.slug;
+ 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">
@@ -147,6 +207,7 @@ Timeline.propTypes = {
width: PropTypes.string,
onClickAssignment: PropTypes.func,
showPendingTasks: PropTypes.bool,
+ variant: PropTypes.string,
};
Timeline.defaultProps = {
@@ -156,6 +217,7 @@ Timeline.defaultProps = {
width: '100%',
onClickAssignment: () => {},
showPendingTasks: false,
+ variant: '',
};
export default memo(Timeline);
diff --git a/src/common/context/AuthContext.jsx b/src/common/context/AuthContext.jsx
index 45a5f2b04..2dc5262ef 100644
--- a/src/common/context/AuthContext.jsx
+++ b/src/common/context/AuthContext.jsx
@@ -196,7 +196,7 @@ function AuthProvider({ children, pageProps }) {
method: 'native',
user_id: data.id,
email: data.email,
- // is_saas: data.roles.filter(r => r.role.toLowerCase() == "student" && r.)
+ is_academy_legacy: data.roles.some((r) => r.academy.id === 6),
first_name: data.first_name,
last_name: data.last_name,
avatar_url: data.profile?.avatar_url || data.github?.avatar_url,
diff --git a/src/common/handlers/cohorts.js b/src/common/handlers/cohorts.js
index 085f84104..203288b7b 100644
--- a/src/common/handlers/cohorts.js
+++ b/src/common/handlers/cohorts.js
@@ -121,18 +121,12 @@ export const processRelatedAssignments = (syllabusData = {}, taskTodo = []) => {
const content = [...updatedRead, ...updatedPractice, ...updatedProject, ...updatedAnswer];
- const includesDailyTask = (module) => {
- const getModules = taskTodo.some((task) => task.associated_slug === module.slug);
- return getModules;
- };
+ const includesDailyTask = (module) => taskTodo.some((task) => task.associated_slug === module.slug);
- const includesStatusPending = (module) => {
- const getModules = module.task_status === 'PENDING' && module.revision_status !== 'APPROVED';
- return getModules;
- };
+ const includesStatusPending = (module) => module.task_status === 'PENDING' && module.revision_status !== 'APPROVED';
- const filteredContent = content.filter((module) => includesDailyTask(module));
- const filteredContentByPending = content.filter((module) => includesStatusPending(module));
+ const filteredContent = content.filter(includesDailyTask);
+ const filteredContentByPending = content.filter(includesStatusPending);
return {
filteredContent,
@@ -195,3 +189,17 @@ export const generateCohortSyllabusModules = async (id) => {
return {};
}
};
+
+export const getTechonologies = (cohortDays) => {
+ let technologyTags = [];
+
+ for (let i = 0; i < cohortDays.length; i += 1) {
+ if (typeof cohortDays[i].technologies === 'string') technologyTags.push(cohortDays[i].technologies);
+ if (Array.isArray(cohortDays[i].technologies)) {
+ technologyTags = technologyTags.concat(cohortDays[i].technologies);
+ }
+ }
+ technologyTags = [...new Set(technologyTags)];
+
+ return technologyTags;
+};
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..931b78702 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 useCohortAction from '../store/actions/cohortAction';
+import useModuleHandler from './useModuleHandler';
+import { processRelatedAssignments } from '../handlers/cohorts';
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, setMyCohorts, state } = useCohortAction();
+ const { cohortProgram, taskTodo, setCohortProgram, setTaskTodo } = useModuleHandler();
const {
cohortSession,
@@ -49,29 +52,26 @@ function useCohortHandler() {
}
};
- const getCohortAssignments = ({
- setContextState, slug, cohort,
+ const getCohortAssignments = async ({
+ slug, cohort,
}) => {
if (user) {
- const academyId = cohort.academy.id;
- const { version } = cohort.syllabus_version;
- const syllabusSlug = cohort?.syllabus_version.slug || slug;
+ const academyId = cohort?.academy.id;
+ const version = cohort?.syllabus_version?.version;
+ 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)
- Promise.all([
- bc.todo({ cohort: cohort.id, limit: 1000 }).getTaskByStudent(), // Tasks with cohort id
- bc.syllabus().get(academyId, syllabusSlug, version), // cohortProgram
- bc.auth().getRoles(currentAcademy?.role), // Roles
- ]).then((
- [taskTodoData, programData, userRoles],
- ) => {
+ // Fetch cohortProgram and TaskTodo then apply to moduleMap store
+ try {
+ const [taskTodoData, programData, userRoles] = await Promise.all([
+ bc.todo({ cohort: cohort.id, limit: 1000 }).getTaskByStudent(), // Tasks with cohort id
+ bc.syllabus().get(academyId, syllabusSlug, version), // cohortProgram
+ bc.auth().getRoles(currentAcademy?.role), // Roles
+ ]);
setUserCapabilities(userRoles.data.capabilities);
- setContextState({
- taskTodo: taskTodoData.data.results,
- cohortProgram: programData.data,
- });
- }).catch((err) => {
+ setTaskTodo(taskTodoData.data.results);
+ setCohortProgram(programData.data);
+ } catch (err) {
console.log(err);
toast({
position: 'top',
@@ -82,98 +82,91 @@ function useCohortHandler() {
isClosable: true,
});
router.push('/choose-program');
- });
+ }
}
}
};
- const handleRedirectToPublicPage = () => {
- axios.get(`${BREATHECODE_HOST}/v1/registry/asset/${assetSlug}`)
- .then((response) => {
- if (response?.data?.asset_type) {
- redirectToPublicPage(response.data);
- }
- })
- .catch(() => {
- router.push('/404');
- });
+ const handleRedirectToPublicPage = async () => {
+ try {
+ const response = await axios.get(`${BREATHECODE_HOST}/v1/registry/asset/${assetSlug}`);
+ if (response?.data?.asset_type) {
+ redirectToPublicPage(response.data);
+ }
+ } catch (e) {
+ router.push('/404');
+ }
};
- const getCohortData = ({
+ const getCohortData = async ({
cohortSlug,
- }) => new Promise((resolve, reject) => {
- // Fetch cohort data with pathName structure
- if (cohortSlug && accessToken) {
- bc.admissions().me().then(({ data }) => {
+ }) => {
+ try {
+ // Fetch cohort data with pathName structure
+ if (cohortSlug && accessToken) {
+ const { data } = await bc.admissions().me();
if (!data) throw new Error('No data');
const { cohorts } = data;
+
+ const parsedCohorts = cohorts.map(((elem) => {
+ const { cohort, ...cohort_user } = elem;
+ const { syllabus_version } = cohort;
+ return {
+ ...cohort,
+ selectedProgramSlug: `/cohort/${cohort.slug}/${syllabus_version.slug}/v${syllabus_version.version}`,
+ cohort_role: elem.role,
+ cohort_user,
+ };
+ }));
+
// find cohort with current slug
- const findCohort = cohorts.find((c) => c.cohort.slug === cohortSlug);
- const currentCohort = findCohort?.cohort;
+ const currentCohort = parsedCohorts.find((c) => c.slug === cohortSlug);
- if (assetSlug && (!currentCohort)) {
- handleRedirectToPublicPage();
- }
- if (currentCohort) {
- const { syllabus_version } = currentCohort;
- setCohortSession({
- ...cohortSession,
- ...currentCohort,
- selectedProgramSlug: `/cohort/${currentCohort.slug}/${syllabus_version.slug}/v${syllabus_version.version}`,
- cohort_role: findCohort.role,
- cohort_user: {
- created_at: findCohort.created_at,
- educational_status: findCohort.educational_status,
- finantial_status: findCohort.finantial_status,
- role: findCohort.role,
- },
- });
- resolve(currentCohort);
+ if (!currentCohort) {
+ if (assetSlug) return handleRedirectToPublicPage();
+
+ return router.push('/choose-program');
}
- }).catch((error) => {
- handleRedirectToPublicPage();
- toast({
- position: 'top',
- title: t('alert-message:invalid-cohort-slug'),
- // title: 'Invalid cohort slug',
- status: 'error',
- duration: 7000,
- isClosable: true,
- });
- reject(error);
- setTimeout(() => {
- localStorage.removeItem('cohortSession');
- }, 4000);
- });
- } else {
+
+ setCohortSession(currentCohort);
+ setMyCohorts(parsedCohorts);
+ return currentCohort;
+ }
+
+ return handleRedirectToPublicPage();
+ } catch (error) {
handleRedirectToPublicPage();
+ toast({
+ position: 'top',
+ title: t('alert-message:invalid-cohort-slug'),
+ status: 'error',
+ duration: 7000,
+ isClosable: true,
+ });
+ return localStorage.removeItem('cohortSession');
}
- });
+ };
// Sort all data fetched in order of taskTodo
- const prepareTasks = ({
- cohortProgram, contextState, nestAssignments,
- }) => {
- const moduleData = cohortProgram.json?.days || cohortProgram.json?.modules;
- const cohort = cohortProgram.json ? moduleData : [];
+ const prepareTasks = () => {
+ const moduleData = cohortProgram?.json?.days || cohortProgram?.json?.modules || [];
const assignmentsRecopilated = [];
devLog('json.days:', moduleData);
- if (contextState.cohortProgram.json && contextState.taskTodo) {
- cohort.map((assignment) => {
+ if (cohortProgram?.json && taskTodo) {
+ moduleData.forEach((assignment) => {
const {
id, label, description, lessons, replits, assignments, quizzes,
} = assignment;
if (lessons && replits && assignments && quizzes) {
- const nestedAssignments = nestAssignments({
- id,
- read: lessons,
- practice: replits,
- project: assignments,
- answer: quizzes,
- taskTodo: contextState.taskTodo,
- });
- const { modules, filteredModules, filteredModulesByPending } = nestedAssignments;
+ const nestedAssignments = processRelatedAssignments(assignment, taskTodo);
+
+ // this properties name's reassignment is done to keep compatibility with deprecated functions
+ const {
+ content: modules,
+ filteredContent: filteredModules,
+ filteredContentByPending: filteredModulesByPending,
+ } = nestedAssignments;
// Data to be sent to [sortedAssignments] = state
const assignmentsStruct = {
@@ -201,14 +194,12 @@ function useCohortHandler() {
...assignmentsStruct,
});
}
-
- const filterNotEmptyModules = assignmentsRecopilated.filter(
- (l) => l.modules.length > 0,
- );
- return setSortedAssignments(filterNotEmptyModules);
}
- return null;
});
+ const filterNotEmptyModules = assignmentsRecopilated.filter(
+ (l) => l.modules.length > 0,
+ );
+ setSortedAssignments(filterNotEmptyModules);
}
};
diff --git a/src/common/hooks/useModuleHandler.js b/src/common/hooks/useModuleHandler.js
index 7419fb237..0537f0bbe 100644
--- a/src/common/hooks/useModuleHandler.js
+++ b/src/common/hooks/useModuleHandler.js
@@ -1,270 +1,166 @@
-import { differenceInDays } from 'date-fns';
+import { useToast } from '@chakra-ui/react';
+import useTranslation from 'next-translate/useTranslation';
+import useModuleMap from '../store/actions/moduleMapAction';
import bc from '../services/breathecode';
import { reportDatalayer } from '../../utils/requests';
-export const updateAssignment = async ({
- t, task, closeSettings, toast, githubUrl, contextState, setContextState, taskStatus,
-}) => {
- // Task case
- const toggleStatus = (task.task_status === undefined || task.task_status === 'PENDING') ? 'DONE' : 'PENDING';
- if (task.task_type && task.task_type !== 'PROJECT') {
- const taskToUpdate = {
- ...task,
- id: task.id,
- task_status: toggleStatus,
- };
-
- 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)
+function useModuleHandler() {
+ const { t } = useTranslation('alert-message');
+ const toast = useToast();
+ const { setTaskTodo, setCohortProgram, state, setCurrentTask, setSubTasks, setNextModule, setPrevModule } = useModuleMap();
+ const { taskTodo } = state;
+
+ const updateAssignment = async ({
+ task, closeSettings, githubUrl, taskStatus,
+ }) => {
+ // Task case
+ const toggleStatus = (task.task_status === undefined || task.task_status === 'PENDING') ? 'DONE' : 'PENDING';
+ if (task.task_type && task.task_type !== 'PROJECT') {
+ const taskToUpdate = {
+ ...task,
+ id: task.id,
+ task_status: toggleStatus,
+ };
+
+ try {
+ await bc.todo({}).update(taskToUpdate);
+ const keyIndex = taskTodo.findIndex((x) => x.id === task.id);
+ setTaskTodo([
+ ...taskTodo.slice(0, keyIndex), // before keyIndex (inclusive)
taskToUpdate, // key item (updated)
- ...contextState.taskTodo.slice(keyIndex + 1), // after keyIndex (exclusive)
- ],
- });
- toast({
- position: 'top',
- title: t('alert-message:assignment-updated'),
- status: 'success',
- duration: 6000,
- isClosable: true,
- });
-
- closeSettings();
- } catch (error) {
- console.log(error);
- toast({
- position: 'top',
- title: t('alert-message:assignment-update-error'),
- status: 'errror',
- duration: 5000,
- isClosable: true,
- });
- closeSettings();
- }
- } else {
- // Project case
- const getProjectUrl = () => {
- if (githubUrl) {
- return githubUrl;
- }
- return '';
- };
-
- const projectUrl = getProjectUrl();
-
- const isDelivering = projectUrl !== '';
- // const linkIsRemoved = task.task_type === 'PROJECT' && !isDelivering;
- const taskToUpdate = {
- ...task,
- task_status: taskStatus || toggleStatus,
- github_url: projectUrl,
- revision_status: 'PENDING',
- delivered_at: new Date(),
- };
-
- try {
- 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)
- ],
- });
- reportDatalayer({
- dataLayer: {
- event: 'assignment_status_updated',
- task_status: taskStatus,
- task_id: task.id,
- task_title: task.title,
- task_associated_slug: task.associated_slug,
- task_type: task.task_type,
- task_revision_status: task.revision_status,
- },
- });
+ ...taskTodo.slice(keyIndex + 1), // after keyIndex (exclusive)
+ ]);
toast({
position: 'top',
- // title: `"${res.data.title}" has been updated successfully`,
- title: isDelivering
- ? t('alert-message:delivery-success')
- : t('alert-message:delivery-removed'),
+ title: t('alert-message:assignment-updated'),
status: 'success',
duration: 6000,
isClosable: true,
});
closeSettings();
+ } catch (error) {
+ console.log(error);
+ toast({
+ position: 'top',
+ title: t('alert-message:assignment-update-error'),
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ });
+ closeSettings();
+ }
+ } else {
+ // Project case
+ const getProjectUrl = () => {
+ if (githubUrl) {
+ return githubUrl;
+ }
+ return '';
+ };
+
+ const projectUrl = getProjectUrl();
+
+ const isDelivering = projectUrl !== '';
+ // const linkIsRemoved = task.task_type === 'PROJECT' && !isDelivering;
+ const taskToUpdate = {
+ ...task,
+ task_status: taskStatus || toggleStatus,
+ github_url: projectUrl,
+ revision_status: 'PENDING',
+ delivered_at: new Date(),
+ };
+
+ try {
+ const response = await bc.todo({}).update(taskToUpdate);
+ // verify if form is equal to the response
+ if (response.data.github_url === projectUrl) {
+ 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',
+ task_status: taskStatus,
+ task_id: task.id,
+ task_title: task.title,
+ task_associated_slug: task.associated_slug,
+ task_type: task.task_type,
+ task_revision_status: task.revision_status,
+ },
+ });
+ toast({
+ position: 'top',
+ title: isDelivering
+ ? t('alert-message:delivery-success')
+ : t('alert-message:delivery-removed'),
+ status: 'success',
+ duration: 6000,
+ isClosable: true,
+ });
+ closeSettings();
+ }
+ } catch (error) {
+ console.log(error);
+ toast({
+ position: 'top',
+ title: t('alert-message:delivery-error'),
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ });
+ closeSettings();
}
- } catch (error) {
- console.log(error);
- toast({
- position: 'top',
- title: t('alert-message:delivery-error'),
- status: 'errror',
- duration: 5000,
- isClosable: true,
- });
- closeSettings();
}
- }
-};
+ };
-export const startDay = async ({
- t, newTasks, label, contextState, setContextState, toast, customHandler = () => {},
-}) => {
- try {
- const response = await bc.todo({}).add(newTasks);
+ const startDay = async ({
+ newTasks, label, customHandler = () => {},
+ }) => {
+ try {
+ const response = await bc.todo({}).add(newTasks);
- if (response.status < 400) {
+ if (response.status < 400) {
+ toast({
+ position: 'top',
+ title: label
+ ? t('alert-message:module-started', { title: label })
+ : t('alert-message:module-sync-success'),
+ status: 'success',
+ duration: 6000,
+ isClosable: true,
+ });
+ setTaskTodo([
+ ...taskTodo,
+ ...response.data,
+ ]);
+ customHandler();
+ }
+ } catch (err) {
+ console.log('error_ADD_TASK 🔴 ', err);
toast({
position: 'top',
- title: label
- ? t('alert-message:module-started', { title: label })
- : t('alert-message:module-sync-success'),
- // title: `Module ${label ? `${label}started` : 'synchronized'} successfully`,
- status: 'success',
+ title: t('alert-message:module-start-error'),
+ status: 'error',
duration: 6000,
isClosable: true,
});
- setContextState({
- ...contextState,
- taskTodo: [
- ...contextState.taskTodo,
- ...response.data,
- ],
- });
- customHandler();
}
- } catch (err) {
- console.log('error_ADD_TASK 🔴 ', err);
- toast({
- position: 'top',
- title: t('alert-message:module-start-error'),
- status: 'error',
- duration: 6000,
- isClosable: true,
- });
- }
-};
-
-export const nestAssignments = ({
- id, label = '', read, practice, project, answer, taskTodo = [],
-}) => {
- const getTaskProps = (slug) => taskTodo.find(
- (task) => task.associated_slug === slug,
- );
- const currentDate = new Date();
-
- const updatedRead = read?.map((el) => ({
- ...el,
- id,
- label,
- task_status: getTaskProps(el.slug)?.task_status || '',
- revision_status: getTaskProps(el.slug)?.revision_status || '',
- created_at: getTaskProps(el.slug)?.created_at || '',
- position: el.position,
- type: 'Read',
- icon: 'book',
- task_type: 'LESSON',
- })).sort((a, b) => b.position - a.position);
-
- const updatedPractice = practice?.map((el) => ({
- ...el,
- id,
- label,
- task_status: getTaskProps(el.slug)?.task_status || '',
- revision_status: getTaskProps(el.slug)?.revision_status || '',
- created_at: getTaskProps(el.slug)?.created_at || '',
- position: el.position,
- type: 'Practice',
- icon: 'strength',
- task_type: 'EXERCISE',
- })).sort((a, b) => b.position - a.position);
-
- const updatedProject = project?.map((el) => {
- const taskProps = getTaskProps(el?.slug?.slug || el?.slug);
-
- return ({
- ...el,
- id,
- label,
- slug: el?.slug?.slug || el?.slug,
- task_status: taskProps?.task_status || '',
- revision_status: taskProps?.revision_status || '',
- created_at: taskProps?.created_at || '',
- daysDiff: taskProps?.created_at ? differenceInDays(currentDate, new Date(taskProps?.created_at)) : '',
- position: el.position,
- mandatory: el.mandatory,
- type: 'Project',
- icon: 'code',
- task_type: 'PROJECT',
- });
- }).sort((a, b) => b.position - a.position);
-
- const updatedAnswer = answer?.map((el) => ({
- ...el,
- id,
- label,
- task_status: getTaskProps(el.slug)?.task_status || '',
- revision_status: getTaskProps(el.slug)?.revision_status || '',
- created_at: getTaskProps(el.slug)?.created_at || '',
- position: el.position,
- type: 'Answer',
- icon: 'answer',
- task_type: 'QUIZ',
- })).sort((a, b) => b.position - a.position);
-
- const modules = [...updatedRead, ...updatedPractice, ...updatedProject, ...updatedAnswer];
-
- const includesDailyTask = (module) => {
- const getModules = taskTodo.some((task) => task.associated_slug === module.slug);
- return getModules;
- };
-
- const includesStatusPending = (module) => {
- const getModules = module.task_status === 'PENDING' && module.revision_status !== 'APPROVED';
- return getModules;
};
- const filteredModules = modules.filter((module) => includesDailyTask(module));
- const filteredModulesByPending = modules.filter((module) => includesStatusPending(module));
-
return {
- filteredModules,
- modules,
- filteredModulesByPending,
+ updateAssignment,
+ startDay,
+ setTaskTodo,
+ setCohortProgram,
+ setCurrentTask,
+ setSubTasks,
+ setNextModule,
+ setPrevModule,
+ ...state,
};
- /*
- example:
- filteredModules: [{...}, {...}, {...}]
- filteredModules: [{...}, {...}]
- filteredModules: []
- filteredModules: []
- filteredModules: [{...}]
- --------------------------------------------------
- modules: [{...}, {...}, {...}]
- modules: [{...}, {...}]
- modules: [{...}, {...}, {...}, {...}, {...}]
- */
-};
-
-export const getTechonologies = (cohortDays) => {
- let technologyTags = [];
-
- for (let i = 0; i < cohortDays.length; i += 1) {
- if (typeof cohortDays[i].technologies === 'string') technologyTags.push(cohortDays[i].technologies);
- if (Array.isArray(cohortDays[i].technologies)) {
- technologyTags = technologyTags.concat(cohortDays[i].technologies);
- }
- }
- technologyTags = [...new Set(technologyTags)];
+}
- return technologyTags;
-};
+export default useModuleHandler;
diff --git a/src/common/hooks/useStyle.js b/src/common/hooks/useStyle.js
index b28983673..57d9a0183 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',
@@ -54,6 +56,7 @@ const useStyle = () => {
green: '#38A56A',
greenLight: '#25BF6C',
greenLight2: '#A4FFBD',
+ greenLight3: '#D7FFE2',
fontColor2: useColorModeValue('#3A3A3A', '#EBEBEB'),
successLight: useColorModeValue('#e9ffef', '#A4FFBD'),
};
@@ -72,6 +75,7 @@ const useStyle = () => {
backgroundColor,
backgroundColor2,
backgroundColor3,
+ backgroundColor4,
borderColor,
borderColor2,
borderColorStrong,
diff --git a/src/common/services/breathecode.js b/src/common/services/breathecode.js
index 2e2612ecd..20b22aebc 100644
--- a/src/common/services/breathecode.js
+++ b/src/common/services/breathecode.js
@@ -162,6 +162,7 @@ const breathecode = {
// getTaskByStudent: (cohortId) => axios.get(`${url}/user/me/task?cohort=${cohortId}`),
getTaskByStudent: () => axios.get(`${url}/user/me/task${qs}`),
add: (args) => axios.post(`${url}/user/me/task`, args),
+ postCompletionJob: (taskId) => axios.post(`${url}/completion_job/${taskId}`),
// delete: (id, args) => axios.delete(`${url}/user/${id}/task/${args.id}`, args),
update: (args) => axios.put(`${url}/task/${args.id}`, args),
updateBulk: (args) => axios.put(`${url}/user/me/task`, args),
@@ -259,6 +260,7 @@ const breathecode = {
personalFiles: (taskId) => breathecode.get(`${url}/me/task/${taskId}/commitfile${qs}`),
personalFile: (commitId) => breathecode.get(`${url}/me/commitfile/${commitId}${qs}`),
rateCodeRevision: (coderevisionId, data) => axios.post(`${url}/me/coderevision/${coderevisionId}/rate`, data),
+ syncCohort: (cohortId) => axios.get(`${url}/academy/cohort/${cohortId}/synctasks`),
};
},
feedback: () => {
@@ -286,6 +288,7 @@ const breathecode = {
const qs = parseQuerys(query);
return {
lead: (data) => axios.post(`${url}/lead${qs}`, data),
+ courses: () => axios.get(`${url}/course${qs}`),
};
},
@@ -324,7 +327,6 @@ const breathecode = {
return {
checking: (data) => axios.put(`${url}/checking${qs}`, data),
subscriptions: () => axios.get(`${url}/me/subscription${qs}`),
- courses: () => axios.get(`${host}/marketing/course${qs}`),
pay: (data) => breathecode.post(`${url}/pay${qs}`, data),
addCard: (data) => breathecode.post(`${url}/card${qs}`, data),
cancelSubscription: (id) => axios.put(`${url}/subscription/${id}/cancel${qs}`),
@@ -365,6 +367,7 @@ const breathecode = {
const qs = parseQuerys(query);
return {
completionJob: (data) => axios.post(`${url}/prompting/completion/43${qs}`, data),
+ meToken: (token) => axios.get(`${url}/auth/me/token?breathecode_token=${token}`),
};
},
};
diff --git a/src/common/store/actions/cohortAction.js b/src/common/store/actions/cohortAction.js
index 60352200e..1f188bcff 100644
--- a/src/common/store/actions/cohortAction.js
+++ b/src/common/store/actions/cohortAction.js
@@ -1,5 +1,6 @@
import { useDispatch, useSelector } from 'react-redux';
import {
+ SET_MY_COHORTS,
SET_COHORT_SESSION,
SET_SORTED_ASSIGNMENTS,
SET_TASK_COHORT_NULL,
@@ -7,11 +8,20 @@ import {
} from '../types';
import { usePersistent } from '../../hooks/usePersistent';
-const useCohort = () => {
+const useCohortAction = () => {
const dispatch = useDispatch();
const [, persistCohortSession] = usePersistent('cohortSession', {});
const state = useSelector((reducerState) => reducerState.cohortReducer);
+ const setMyCohorts = (payload) => {
+ dispatch({
+ type: SET_MY_COHORTS,
+ payload: {
+ myCohorts: payload,
+ },
+ });
+ };
+
const setCohortSession = (payload) => {
dispatch({
type: SET_COHORT_SESSION,
@@ -51,6 +61,7 @@ const useCohort = () => {
return {
state,
+ setMyCohorts,
setCohortSession,
setTaskCohortNull,
setSortedAssignments,
@@ -58,4 +69,4 @@ const useCohort = () => {
};
};
-export default useCohort;
+export default useCohortAction;
diff --git a/src/common/store/actions/moduleMapAction.js b/src/common/store/actions/moduleMapAction.js
index 45ee687dd..f89770730 100644
--- a/src/common/store/actions/moduleMapAction.js
+++ b/src/common/store/actions/moduleMapAction.js
@@ -2,42 +2,58 @@ 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 state = useSelector((reducerState) => reducerState.moduleMapReducer);
+
+ const setTaskTodo = (newState) => {
+ dispatch({
+ type: 'CHANGE_TASK_TO_DO',
+ payload: newState,
});
+ };
+
+ const setCohortProgram = (newState) => {
dispatch({
- type: 'CHANGE_STATUS',
- payload: changedModules,
+ type: 'CHANGE_COHORT_PROGRAM',
+ payload: newState,
});
};
- const setContextState = (newState) => {
+ const setCurrentTask = (newState) => {
dispatch({
- type: 'CHANGE_CONTEXT_STATE',
+ type: 'CHANGE_CURRENT_TASK',
payload: newState,
});
};
- // const changeSingleTask = (newState) => {
- // dispatch({
- // type: 'CHANGE_SINGLE_TASK_STATUS',
- // payload: newState,
- // });
- // };
+ const setSubTasks = (newState) => {
+ dispatch({
+ type: 'CHANGE_SUB_TASKS',
+ payload: newState,
+ });
+ };
+
+ const setNextModule = (payload) => {
+ dispatch({
+ type: 'CHANGE_NEXT_MODULE',
+ payload,
+ });
+ };
+
+ const setPrevModule = (payload) => {
+ dispatch({
+ type: 'CHANGE_PREV_MODULE',
+ payload,
+ });
+ };
+
return {
- modules,
- contextState,
- setContextState,
- updateModuleStatus,
- // changeSingleTask,
+ setTaskTodo,
+ setCohortProgram,
+ setCurrentTask,
+ setSubTasks,
+ setNextModule,
+ setPrevModule,
+ state,
};
};
diff --git a/src/common/store/reducers/cohortReducer.js b/src/common/store/reducers/cohortReducer.js
index 02a92cd34..05a430257 100644
--- a/src/common/store/reducers/cohortReducer.js
+++ b/src/common/store/reducers/cohortReducer.js
@@ -1,4 +1,5 @@
import {
+ SET_MY_COHORTS,
SET_COHORT_SESSION,
SET_SORTED_ASSIGNMENTS,
SET_TASK_COHORT_NULL,
@@ -6,6 +7,7 @@ import {
} from '../types';
const initialState = {
+ myCohorts: [],
cohortSession: {},
sortedAssignments: [],
taskCohortNull: [],
@@ -14,6 +16,13 @@ const initialState = {
const cohortHandlerReducer = (state = initialState, action) => {
switch (action.type) {
+ case SET_MY_COHORTS: {
+ const { myCohorts } = action.payload;
+ return {
+ ...state,
+ myCohorts,
+ };
+ }
case SET_COHORT_SESSION: {
const { cohortSession } = action.payload;
return {
diff --git a/src/common/store/reducers/moduleMapReducer.js b/src/common/store/reducers/moduleMapReducer.js
index 0b0b6d79f..5a036bc36 100644
--- a/src/common/store/reducers/moduleMapReducer.js
+++ b/src/common/store/reducers/moduleMapReducer.js
@@ -1,41 +1,43 @@
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: [],
+ currentTask: null,
+ subTasks: [],
+ nextModule: null,
+ prevModule: null,
};
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,
+ };
+ case 'CHANGE_CURRENT_TASK':
+ return {
+ ...state,
+ currentTask: action.payload,
+ };
+ case 'CHANGE_SUB_TASKS':
+ return {
+ ...state,
+ subTasks: action.payload,
+ };
+ case 'CHANGE_NEXT_MODULE':
+ return {
+ ...state,
+ nextModule: action.payload,
+ };
+ case 'CHANGE_PREV_MODULE':
+ return {
+ ...state,
+ prevModule: action.payload,
};
default:
return state;
diff --git a/src/common/store/types/index.js b/src/common/store/types/index.js
index 6dc448b9c..ddc6451a9 100644
--- a/src/common/store/types/index.js
+++ b/src/common/store/types/index.js
@@ -22,6 +22,7 @@ const SET_PAYMENT_STATUS = 'SET_PAYMENT_STATUS';
const SET_SUBMITTING_CARD = 'SET_SUBMITTING_CARD';
const SET_SUBMITTING_PAYMENT = 'SET_SUBMITTING_PAYMENT';
const SET_SELF_APPLIED_COUPON = 'SET_SELF_APPLIED_COUPON';
+const SET_MY_COHORTS = 'SET_MY_COHORTS';
const SET_COHORT_SESSION = 'SET_COHORT_SESSION';
const SET_SORTED_ASSIGNMENTS = 'SET_SORTED_ASSIGNMENTS';
const SET_TASK_COHORT_NULL = 'SET_TASK_COHORT_NULL';
@@ -55,6 +56,7 @@ export {
SET_SERVICE_PROPS,
SET_SELECTED_SERVICE,
SET_PAYMENT_METHODS,
+ SET_MY_COHORTS,
SET_COHORT_SESSION,
SET_SORTED_ASSIGNMENTS,
SET_TASK_COHORT_NULL,
diff --git a/src/common/views/StudentAssignments.jsx b/src/common/views/StudentAssignments.jsx
index 814b43fdd..2b691e97e 100644
--- a/src/common/views/StudentAssignments.jsx
+++ b/src/common/views/StudentAssignments.jsx
@@ -195,6 +195,7 @@ function StudentAssignments({ currentStudentList, updpateAssignment, syllabusDat
setCurrentTask(null)}
+ selectedCohort={selectedCohort}
/>
{
+ try {
+ const resp = await bc.assignments().syncCohort(selectedCohort.id);
+ if (resp.status >= 400) throw new Error('Sync error');
+
+ const { message } = resp.data;
+
+ toast({
+ position: 'top',
+ title: 'Success',
+ description: message,
+ status: 'success',
+ duration: 5000,
+ });
+ } catch (e) {
+ console.log(e);
+ toast({
+ position: 'top',
+ title: t('error-msg'),
+ status: 'error',
+ duration: 6000,
+ isClosable: true,
+ });
+ } finally {
+ setIsSyncOpen(false);
+ onClose();
+ }
+ };
+
return (
@@ -227,8 +260,33 @@ export function NoInfoModal({ isOpen, onClose }) {
-
- {t('no-information')}
+
+ {t('no-information')}
+ {selectedCohort && (
+ <>
+ {t('sync-cohort')}
+ setIsSyncOpen(true)}>
+ {t('sync')}
+
+ >
+ )}
+
+ {t('sync-warning')}
+
+ setIsSyncOpen(false)}>
+ {t('cancel')}
+
+
+ {t('sync')}
+
+
+
@@ -393,6 +451,11 @@ ReviewModal.defaultProps = {
NoInfoModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
+ selectedCohort: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+};
+
+NoInfoModal.defaultProps = {
+ selectedCohort: null,
};
DetailsModal.propTypes = {
diff --git a/src/js_modules/chooseProgram/Programs.jsx b/src/js_modules/chooseProgram/Programs.jsx
index 7ed69ff3d..69c084d28 100644
--- a/src/js_modules/chooseProgram/Programs.jsx
+++ b/src/js_modules/chooseProgram/Programs.jsx
@@ -8,11 +8,10 @@ import axios from '../../axios';
import useProgramList from '../../common/store/actions/programListAction';
function Programs({ item, onOpenModal, setLateModalProps }) {
- const { state, setCohortSession } = useCohortHandler();
- const { cohortSession } = state;
+ const { setCohortSession } = useCohortHandler();
const [isLoadingPageContent, setIsLoadingPageContent] = useState(false);
const { programsList } = useProgramList();
- const { cohort } = item;
+ const { cohort, ...cohortUser } = item;
const signInDate = item.created_at;
const { version, slug } = cohort.syllabus_version;
const currentCohortProps = programsList[cohort.slug];
@@ -44,7 +43,8 @@ function Programs({ item, onOpenModal, setLateModalProps }) {
axios.defaults.headers.common.Academy = cohort.academy.id;
setCohortSession({
...cohort,
- ...cohortSession,
+ cohort_role: cohortUser?.role,
+ cohort_user: cohortUser,
selectedProgramSlug: `/cohort/${cohort?.slug}/${slug}/v${version}`,
});
router.push(`/cohort/${cohort?.slug}/${slug}/v${version}`);
diff --git a/src/js_modules/chooseProgram/index.jsx b/src/js_modules/chooseProgram/index.jsx
index 0b04be4e0..4eba9cac1 100644
--- a/src/js_modules/chooseProgram/index.jsx
+++ b/src/js_modules/chooseProgram/index.jsx
@@ -23,13 +23,12 @@ function ChooseProgram({ chooseList, handleChoose, setLateModalProps }) {
const [marketingCursesList, setMarketingCursesList] = useState([]);
const [showFinished, setShowFinished] = useState(false);
const [upgradeModalIsOpen, setUpgradeModalIsOpen] = useState(false);
- const activeCohorts = handlers.getActiveCohorts(chooseList);
- const finishedCohorts = handlers.getCohortsFinished(chooseList);
const { featuredColor } = useStyle();
const router = useRouter();
-
const cardColumnSize = 'repeat(auto-fill, minmax(17rem, 1fr))';
- const activeSubscriptionCohorts = activeCohorts?.length > 0 ? activeCohorts.map((item) => {
+
+ const finishedCohorts = handlers.getCohortsFinished(chooseList);
+ const activeCohorts = handlers.getActiveCohorts(chooseList).map((item) => {
const cohort = item?.cohort;
const currentCohortProps = programsList[cohort.slug];
return ({
@@ -43,18 +42,14 @@ function ChooseProgram({ chooseList, handleChoose, setLateModalProps }) {
all_subscriptions: currentCohortProps?.all_subscriptions,
subscription_exists: currentCohortProps?.subscription !== null || currentCohortProps?.plan_financing !== null,
});
- }) : [];
+ });
- const marketingCourses = marketingCursesList?.length > 0 ? marketingCursesList.filter(
- (item) => !activeSubscriptionCohorts.some(
- (activeCohort) => activeCohort?.all_subscriptions?.some(
- (sb) => sb?.selected_cohort_set?.slug === item?.slug,
- ),
- ) && item?.course_translation?.title,
- ) : [];
+ const hasNonSaasCourse = chooseList.some(({ cohort }) => !cohort.available_as_saas);
- const isNotAvailableForMktCourses = activeSubscriptionCohorts.length > 0 && activeSubscriptionCohorts.some(
- (item) => item?.cohort?.available_as_saas === false,
+ const marketingCourses = marketingCursesList.filter(
+ (item) => !activeCohorts.some(
+ ({ cohort }) => cohort.slug === item?.cohort?.slug,
+ ) && item?.course_translation?.title,
);
useEffect(() => {
@@ -62,46 +57,52 @@ function ChooseProgram({ chooseList, handleChoose, setLateModalProps }) {
}, [router.locale]);
useEffect(() => {
- bc.payment({ academy: WHITE_LABEL_ACADEMY }).courses()
+ bc.marketing({ academy: WHITE_LABEL_ACADEMY }).courses()
.then(({ data }) => {
setMarketingCursesList(data);
});
}, [router?.locale]);
+ const filterForNonSaasStudents = (course) => {
+ if (!hasNonSaasCourse) return true;
+
+ return course.plan_slug === process.env.BASE_PLAN;
+ };
+
return (
<>
- {activeSubscriptionCohorts.length > 0 && (
-
-
- {t('your-active-programs')}
-
-
-
+ {activeCohorts.length > 0 && (
+ <>
+
+
+ {t('your-active-programs')}
+
+
+
+ 1 ? cardColumnSize : '', md: cardColumnSize }}
+ height="auto"
+ gridGap="4rem"
+ >
+ {activeCohorts.map((item) => (
+ setUpgradeModalIsOpen(true)}
+ setLateModalProps={setLateModalProps}
+ />
+ ))}
+
+ >
)}
setUpgradeModalIsOpen(false)}
/>
- {activeSubscriptionCohorts.length > 0 && (
- 1 ? cardColumnSize : '', md: cardColumnSize }}
- height="auto"
- gridGap="4rem"
- >
- {activeSubscriptionCohorts.map((item) => (
- setUpgradeModalIsOpen(true)}
- setLateModalProps={setLateModalProps}
- />
- ))}
-
- )}
- {!isNotAvailableForMktCourses && marketingCourses?.length > 0 && marketingCourses.some((l) => l?.course_translation?.title) && (
+ {marketingCourses?.length > 0 && (
<>
@@ -115,7 +116,7 @@ function ChooseProgram({ chooseList, handleChoose, setLateModalProps }) {
height="auto"
gridGap="4rem"
>
- {marketingCourses.map((item) => (
+ {marketingCourses.filter(filterForNonSaasStudents).map((item) => (
)}
- {
- finishedCohorts.length > 0 && (
- <>
- 0 && (
+ <>
+
+
-
- {isPlural(finishedCohorts)
- ? t('finished.plural', { finishedCohorts: finishedCohorts.length })
- : t('finished.singular', { finishedCohorts: finishedCohorts.length })}
-
- setShowFinished(!showFinished)}
- >
- {showFinished ? t('finished.hide') : t('finished.show')}
-
-
-
-
+ setShowFinished(!showFinished)}
>
- {showFinished && finishedCohorts.map((item) => (
- setUpgradeModalIsOpen(true)}
- />
- ))}
-
- >
- )
- }
+ {showFinished ? t('finished.hide') : t('finished.show')}
+
+
+
+
+ {showFinished && finishedCohorts.map((item) => (
+ setUpgradeModalIsOpen(true)}
+ />
+ ))}
+
+ >
+ )}
>
);
}
diff --git a/src/js_modules/moduleMap/taskHandler.jsx b/src/js_modules/moduleMap/ButtonHandlerByTaskStatus.jsx
similarity index 52%
rename from src/js_modules/moduleMap/taskHandler.jsx
rename to src/js_modules/moduleMap/ButtonHandlerByTaskStatus.jsx
index 740cf6464..c446a181b 100644
--- a/src/js_modules/moduleMap/taskHandler.jsx
+++ b/src/js_modules/moduleMap/ButtonHandlerByTaskStatus.jsx
@@ -1,20 +1,17 @@
-/* eslint-disable react/no-unstable-nested-components */
import {
- Button,
+ Button, Tooltip,
} 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 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, isGuidedExperience,
}) {
- const { t } = useTranslation('dashboard');
const { hexColor } = useStyle();
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false);
const [loaders, setLoaders] = useState({
@@ -29,47 +26,6 @@ export function ButtonHandlerByTaskStatus({
const noDeliveryFormat = deliveryFormatExists && currentAssetData?.delivery_formats.includes('no_delivery');
const isButtonDisabled = currentTask === null || taskIsApproved;
- function TaskButton() {
- return (
- {
- if (currentTask) {
- setLoaders((prevState) => ({
- ...prevState,
- isChangingTaskStatus: true,
- }));
- changeStatusAssignment(event, currentTask)
- .finally(() => {
- setLoaders((prevState) => ({
- ...prevState,
- isChangingTaskStatus: false,
- }));
- onClickHandler();
- });
- }
- }}
- isDisabled={isButtonDisabled}
- minWidth="26px"
- minHeight="26px"
- background={allowText ? 'blue.default' : 'none'}
- lineHeight={allowText ? '15px' : '0'}
- padding={allowText ? '12px 24px' : '0'}
- borderRadius={allowText ? '3px' : '30px'}
- variant={allowText ? 'default' : 'none'}
- textTransform={allowText ? 'uppercase' : 'none'}
- gridGap={allowText ? '12px' : '0'}
- >
- {allowText ? (
-
- ) : (
-
- )}
-
- );
- }
-
const openAssignmentFeedbackModal = () => {
setIsReviewModalOpen(true);
setLoaders((prevState) => ({
@@ -78,12 +34,48 @@ export function ButtonHandlerByTaskStatus({
}));
};
- function OpenModalButton() {
- return (
- <>
- {currentTask?.description && (
+ const handleTaskButton = (event) => {
+ if (currentTask) {
+ setLoaders((prevState) => ({
+ ...prevState,
+ isChangingTaskStatus: true,
+ }));
+ changeStatusAssignment(event, currentTask)
+ .finally(() => {
+ setLoaders((prevState) => ({
+ ...prevState,
+ isChangingTaskStatus: false,
+ }));
+ onClickHandler();
+ });
+ }
+ };
+
+ const textAndIcon = textByTaskStatus(currentTask || {});
+
+ // PRROJECT CASE
+ if (currentTask && currentTask.task_type === 'PROJECT' && currentTask.task_status) {
+ if ((currentTask.task_status === 'DONE' || taskIsApprovedOrRejected) && !onlyPopoverDialog && !isGuidedExperience) {
+ return (
+ <>
+ {currentTask?.description && (
+ {
+ if (currentTask) {
+ setLoaders((prevState) => ({
+ ...prevState,
+ isOpeningReviewModal: true,
+ }));
+ handleOpen(() => openAssignmentFeedbackModal());
+ }
+ }}
+ >
+
+
+ )}
{
if (currentTask) {
setLoaders((prevState) => ({
@@ -93,57 +85,34 @@ export function ButtonHandlerByTaskStatus({
handleOpen(() => openAssignmentFeedbackModal());
}
}}
+ isDisabled={isButtonDisabled}
+ display="flex"
+ minWidth="26px"
+ minHeight="26px"
+ height="fit-content"
+ background={allowText ? 'blue.default' : 'none'}
+ lineHeight={allowText ? '15px' : '0'}
+ padding={allowText ? '12px 24px' : '0'}
+ borderRadius={allowText ? '3px' : '30px'}
+ variant={allowText ? 'default' : 'none'}
+ textTransform={allowText ? 'uppercase' : 'none'}
+ gridGap={allowText ? '12px' : '0'}
>
-
+ {allowText ? (
+ <>
+
+ {textAndIcon.text}
+ >
+ ) : (
+
+ )}
- )}
- {
- if (currentTask) {
- setLoaders((prevState) => ({
- ...prevState,
- isOpeningReviewModal: true,
- }));
- handleOpen(() => openAssignmentFeedbackModal());
- }
- }}
- isDisabled={isButtonDisabled}
- display="flex"
- minWidth="26px"
- minHeight="26px"
- height="fit-content"
- background={allowText ? 'blue.default' : 'none'}
- lineHeight={allowText ? '15px' : '0'}
- padding={allowText ? '12px 24px' : '0'}
- borderRadius={allowText ? '3px' : '30px'}
- variant={allowText ? 'default' : 'none'}
- textTransform={allowText ? 'uppercase' : 'none'}
- gridGap={allowText ? '12px' : '0'}
- >
- {allowText ? (
-
- ) : (
-
- )}
-
- >
- );
- }
-
- // PRROJECT CASE
- if (currentTask && currentTask.task_type === 'PROJECT' && currentTask.task_status) {
- if ((currentTask.task_status === 'DONE' || taskIsApprovedOrRejected) && !onlyPopoverDialog) {
- return (
- <>
-
setIsReviewModalOpen(false)}
@@ -153,6 +122,7 @@ export function ButtonHandlerByTaskStatus({
}
return (
);
}
+
+ if (isGuidedExperience) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
-
+
+ {allowText ? (
+ <>
+
+ {textAndIcon.text}
+ >
+ ) : (
+
+ )}
+
);
}
@@ -182,6 +197,7 @@ ButtonHandlerByTaskStatus.propTypes = {
currentAssetData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
fileData: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
onlyPopoverDialog: PropTypes.bool,
+ isGuidedExperience: PropTypes.bool,
};
ButtonHandlerByTaskStatus.defaultProps = {
currentTask: null,
@@ -192,4 +208,5 @@ ButtonHandlerByTaskStatus.defaultProps = {
toggleSettings: () => {},
handleOpen: () => {},
onlyPopoverDialog: false,
+ isGuidedExperience: false,
};
diff --git a/src/js_modules/moduleMap/index.jsx b/src/js_modules/moduleMap/index.jsx
index 5477cde93..8038fa0f0 100644
--- a/src/js_modules/moduleMap/index.jsx
+++ b/src/js_modules/moduleMap/index.jsx
@@ -1,22 +1,25 @@
import { memo } from 'react';
import {
- Box, Button, Heading, useColorModeValue, useToast,
+ Box, Button, Heading, useColorModeValue,
} from '@chakra-ui/react';
import useTranslation from 'next-translate/useTranslation';
import PropTypes from 'prop-types';
import Text from '../../common/components/Text';
import Module from './module';
-import { startDay } from '../../common/hooks/useModuleHandler';
+import useModuleHandler from '../../common/hooks/useModuleHandler';
+import useCohortHandler from '../../common/hooks/useCohortHandler';
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, slug, modules, filteredModules,
+ title, description, cohortData, filteredModulesByPending,
showPendingTasks, searchValue, existsActivities,
}) {
const { t } = useTranslation('dashboard');
- const toast = useToast();
+ const { startDay } = useModuleHandler();
+ const { state } = useCohortHandler();
+ const { taskCohortNull } = state;
const commonBorderColor = useColorModeValue('gray.200', 'gray.900');
const currentModules = showPendingTasks ? filteredModulesByPending : filteredModules;
const cohortId = cohortData?.id || cohortData?.cohort_id;
@@ -37,12 +40,7 @@ function ModuleMap({
},
});
startDay({
- t,
- id: userId,
newTasks: updatedTasks,
- contextState,
- setContextState,
- toast,
});
};
@@ -115,7 +113,6 @@ function ModuleMap({
key={`${module.title}-${cheatedIndex}`}
currIndex={i}
data={module}
- taskTodo={taskTodo}
/>
);
}) : (
@@ -155,17 +152,12 @@ 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]))),
showPendingTasks: PropTypes.bool,
searchValue: PropTypes.string,
@@ -177,9 +169,7 @@ ModuleMap.defaultProps = {
title: 'HTML/CSS/Bootstrap',
slug: 'html-css-bootstrap',
description: '',
- taskTodo: [],
cohortData: {},
- taskCohortNull: [],
filteredModulesByPending: [],
showPendingTasks: false,
searchValue: '',
diff --git a/src/js_modules/moduleMap/module.jsx b/src/js_modules/moduleMap/module.jsx
index e94b2c8cd..ca7cd89fc 100644
--- a/src/js_modules/moduleMap/module.jsx
+++ b/src/js_modules/moduleMap/module.jsx
@@ -7,12 +7,11 @@ import {
import useTranslation from 'next-translate/useTranslation';
import PropTypes from 'prop-types';
import { useState, memo } from 'react';
-import { updateAssignment } from '../../common/hooks/useModuleHandler';
+import useModuleHandler 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 { ButtonHandlerByTaskStatus } from './ButtonHandlerByTaskStatus';
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 +19,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, updateAssignment } = useModuleHandler();
+ const { state } = useCohortHandler();
+ const { cohortSession } = state;
const [currentAssetData, setCurrentAssetData] = useState(null);
const [fileData, setFileData] = useState(null);
const [, setUpdatedTask] = useState(null);
@@ -79,8 +80,6 @@ function Module({
},
];
- const cohortSession = isWindow ? JSON.parse(localStorage.getItem('cohortSession') || '{}') : {};
-
const closeSettings = () => {
setSettingsOpen(false);
};
@@ -151,7 +150,7 @@ function Module({
...task,
});
await updateAssignment({
- t, task, taskStatus, closeSettings, toast, contextState, setContextState,
+ task, taskStatus, closeSettings,
});
}
};
@@ -161,7 +160,7 @@ function Module({
}) => {
setShowModal(true);
await updateAssignment({
- t, task, closeSettings, toast, githubUrl, taskStatus, contextState, setContextState,
+ task, closeSettings, githubUrl, taskStatus,
});
};
@@ -251,7 +250,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/navbar/MobileNav.jsx b/src/js_modules/navbar/MobileNav.jsx
index d38713eee..484c6d9f9 100644
--- a/src/js_modules/navbar/MobileNav.jsx
+++ b/src/js_modules/navbar/MobileNav.jsx
@@ -8,14 +8,13 @@ import {
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import useTranslation from 'next-translate/useTranslation';
+import { useRouter } from 'next/router';
import Icon from '../../common/components/Icon';
import MobileItem from './MobileItem';
import LanguageSelector from '../../common/components/LanguageSelector';
-// import syllabusList from '../../../public/syllabus.json';
import NextChakraLink from '../../common/components/NextChakraLink';
-// import UpgradeExperience from '../../common/components/UpgradeExperience';
import useStyle from '../../common/hooks/useStyle';
-// import UpgradeExperience from '../../common/components/UpgradeExperience';
+import { setStorageItem } from '../../utils';
function MobileNav({
// eslint-disable-next-line no-unused-vars
@@ -24,6 +23,7 @@ function MobileNav({
const [privateItems, setPrivateItems] = useState([]);
const { colorMode, toggleColorMode } = useColorMode();
const { t } = useTranslation('navbar');
+ const router = useRouter();
const commonColors = useColorModeValue('white', 'gray.800');
// const readSyllabus = JSON.parse(syllabusList);
const prismicRef = process.env.PRISMIC_REF;
@@ -100,6 +100,7 @@ function MobileNav({
setStorageItem('redirect', router?.asPath)}
fontSize="16px"
lineHeight="22px"
margin="0"
diff --git a/src/js_modules/projects/ProjectList.jsx b/src/js_modules/projects/ProjectList.jsx
index ce0b6e172..297711194 100644
--- a/src/js_modules/projects/ProjectList.jsx
+++ b/src/js_modules/projects/ProjectList.jsx
@@ -36,8 +36,8 @@ const ProjectList = forwardRef(({
const breakpointColumnsObj = {
default: 3,
1100: 3,
- 700: 2,
- 500: 1,
+ 880: 2,
+ 590: 1,
};
return (
diff --git a/src/js_modules/syllabus/ExerciseGuidedExperience.jsx b/src/js_modules/syllabus/ExerciseGuidedExperience.jsx
new file mode 100644
index 000000000..3cf489655
--- /dev/null
+++ b/src/js_modules/syllabus/ExerciseGuidedExperience.jsx
@@ -0,0 +1,218 @@
+import { useState, useEffect } from 'react';
+import {
+ Box, Button,
+} from '@chakra-ui/react';
+import useTranslation from 'next-translate/useTranslation';
+import PropTypes from 'prop-types';
+import { intervalToDuration } from 'date-fns';
+import modifyEnv from '../../../modifyEnv';
+import ModalToCloneProject from './ModalToCloneProject';
+import useCohortHandler from '../../common/hooks/useCohortHandler';
+import useStyle from '../../common/hooks/useStyle';
+import ReactPlayerV2 from '../../common/components/ReactPlayerV2';
+import KPI from '../../common/components/KPI';
+import NextChakraLink from '../../common/components/NextChakraLink';
+import Heading from '../../common/components/Heading';
+import Text from '../../common/components/Text';
+import Icon from '../../common/components/Icon';
+import { intervalToHours } from '../../utils';
+
+function ExerciseGuidedExperience({ currentTask, currentAsset }) {
+ const { t } = useTranslation('syllabus');
+ const { colorMode } = useStyle();
+ const { state } = useCohortHandler();
+ const { cohortSession } = state;
+ const [showCloneModal, setShowCloneModal] = useState(false);
+ const [telemetryReport, setTelemetryReport] = useState([]);
+ const BREATHECODE_HOST = modifyEnv({ queryString: 'host', env: process.env.BREATHECODE_HOST });
+
+ const isExerciseStated = !!currentTask?.assignment_telemetry;
+
+ useEffect(() => {
+ if (isExerciseStated) {
+ const { steps, workout_session: workoutSession, last_interaction_at: lastInteractionAt } = currentTask.assignment_telemetry;
+ const completedSteps = steps.reduce((acum, elem) => {
+ if (elem.completed_at) return acum + 1;
+ return acum;
+ }, 0);
+
+ const compilations = [];
+ const tests = [];
+ let successfulCompilations = 0;
+ let successfulTests = 0;
+ let errors = 0;
+
+ steps.forEach((step) => {
+ compilations.push(...step.compilations);
+ tests.push(...step.tests);
+ });
+
+ compilations.forEach((comp) => {
+ if (comp > 0) errors += 1;
+ else successfulCompilations += 1;
+ });
+
+ tests.forEach((comp) => {
+ if (comp > 0) errors += 1;
+ else successfulTests += 1;
+ });
+
+ const completionPercentage = (completedSteps * 100) / steps.length;
+ const roundedPercentage = Math.round((completionPercentage + Number.EPSILON) * 100) / 100;
+
+ const totalHours = workoutSession.reduce((acum, elem) => {
+ const startedAt = elem.started_at;
+ const endedAt = elem.ended_at || lastInteractionAt;
+
+ const duration = intervalToDuration({
+ start: new Date(startedAt),
+ end: new Date(endedAt),
+ });
+
+ const hours = intervalToHours(duration);
+
+ return acum + hours;
+ }, 0);
+
+ const roundedHours = Math.round((totalHours + Number.EPSILON) * 100) / 100;
+ setTelemetryReport([{
+ label: t('completion-percentage'),
+ icon: 'graph-up',
+ value: `${roundedPercentage}%`,
+ }, {
+ label: t('total-steps'),
+ icon: 'list',
+ value: `${completedSteps}/${steps.length}`,
+ }, {
+ label: t('total-time'),
+ icon: 'clock',
+ value: `${roundedHours} hs`,
+ }, {
+ label: t('successful-compiles'),
+ icon: 'documentVerified',
+ value: successfulCompilations,
+ }, {
+ label: t('successful-tests'),
+ icon: 'sync-success',
+ value: successfulTests,
+ }, {
+ label: t('total-errors'),
+ icon: 'sync-error',
+ value: errors,
+ }]);
+ }
+ }, [currentTask]);
+
+ const token = localStorage.getItem('accessToken');
+
+ const newWorkspace = `${BREATHECODE_HOST}/v1/provisioning/me/container/new?token=${token}&cohort=${cohortSession?.id}&repo=${currentAsset?.url}`;
+ const continueWorkSpace = `${BREATHECODE_HOST}/v1/provisioning/me/workspaces?token=${token}&cohort=${cohortSession?.id}&repo=${currentAsset?.url}`;
+
+ return (
+
+
+
+
+
+ {currentAsset?.title}
+
+
+ {currentAsset?.description}
+
+
+
+
+
+
+ {isExerciseStated && (
+
+ {telemetryReport.map((elem) => (
+
+ ))}
+
+ )}
+
+
+
+
+
+
+ {t('common:learnpack.title')}
+
+
+
+
+
+
+
+ {t('common:learnpack.open-in-learnpack-button.text')}
+
+ setShowCloneModal(true)}
+ fontSize="17px"
+ >
+ {t('common:learnpack.open-locally')}
+
+
+
+
+
+ );
+}
+
+ExerciseGuidedExperience.propTypes = {
+ currentTask: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+ currentAsset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+};
+
+ExerciseGuidedExperience.defaultProps = {
+ currentTask: null,
+ currentAsset: null,
+};
+
+export default ExerciseGuidedExperience;
diff --git a/src/js_modules/syllabus/GuidedExperienceSidebar.jsx b/src/js_modules/syllabus/GuidedExperienceSidebar.jsx
new file mode 100644
index 000000000..144f33641
--- /dev/null
+++ b/src/js_modules/syllabus/GuidedExperienceSidebar.jsx
@@ -0,0 +1,182 @@
+/* eslint-disable no-unused-vars */
+import { useState } from 'react';
+import {
+ Box,
+ Button,
+ Img,
+ Spinner,
+ useColorModeValue,
+ Divider,
+} 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 Timeline from '../../common/components/Timeline';
+import NextChakraLink from '../../common/components/NextChakraLink';
+import Text from '../../common/components/Text';
+import Icon from '../../common/components/Icon';
+import useCohortHandler from '../../common/hooks/useCohortHandler';
+import useStyle from '../../common/hooks/useStyle';
+
+function GuidedExperienceSidebar({ onClickAssignment, isOpen, onToggle, currentModuleIndex, handleStartDay }) {
+ const { t } = useTranslation('syllabus');
+ const [moduleLoading, setModuleLoading] = useState(false);
+ const { state } = useCohortHandler();
+ const { cohortSession, sortedAssignments } = state;
+ const background = useColorModeValue('#E4E8EE', '#283340');
+
+ const Open = !isOpen;
+ const { height, display, position, zIndex, ...slideStyles } = getSlideProps(Open);
+ const {
+ currentThemeValue,
+ } = Config();
+ const { hexColor } = useStyle();
+
+ const currentModule = sortedAssignments[currentModuleIndex];
+ const nextModule = sortedAssignments[currentModuleIndex + 1];
+
+ const openNextModule = async () => {
+ try {
+ const nextAssignments = nextModule.filteredModules;
+ if (nextAssignments.length === 0) {
+ setModuleLoading(true);
+ await handleStartDay(nextModule, true);
+ setModuleLoading(false);
+ }
+ const assignment = nextModule.modules[0];
+ onClickAssignment(null, assignment);
+ } catch (e) {
+ console.log(e);
+ setModuleLoading(false);
+ }
+ };
+
+ return (
+ <>
+
+
+ {cohortSession?.syllabus_version && (
+
+ {cohortSession?.syllabus_version?.logo && (
+
+ )}
+ {cohortSession.syllabus_version?.name}
+
+ )}
+
+
+ {t(Open ? 'hide-menu' : 'show-menu')}
+
+
+
+
+ {currentModule ? (
+ <>
+
+
+
+ {' '}
+ {currentModule.label && (
+
+ {currentModule.label.toUpperCase()}
+
+ )}
+
+
+
+
+ {nextModule && (
+
+
+
+ {t('start-next')}
+
+
+
+ {nextModule.label}
+
+
+
+
+ )}
+ >
+ ) : (
+
+
+
+ )}
+
+
+ >
+ );
+}
+
+GuidedExperienceSidebar.propTypes = {
+ onClickAssignment: PropTypes.func,
+ isOpen: PropTypes.bool,
+ onToggle: PropTypes.func,
+ currentModuleIndex: PropTypes.number,
+ handleStartDay: PropTypes.func.isRequired,
+};
+GuidedExperienceSidebar.defaultProps = {
+ onClickAssignment: () => {},
+ isOpen: false,
+ onToggle: () => {},
+ currentModuleIndex: null,
+};
+
+export default GuidedExperienceSidebar;
diff --git a/src/js_modules/syllabus/ModalToCloneProject.jsx b/src/js_modules/syllabus/ModalToCloneProject.jsx
new file mode 100644
index 000000000..1c303d436
--- /dev/null
+++ b/src/js_modules/syllabus/ModalToCloneProject.jsx
@@ -0,0 +1,51 @@
+import useTranslation from 'next-translate/useTranslation';
+import PropTypes from 'prop-types';
+import MarkDownParser from '../../common/components/MarkDownParser';
+import SimpleModal from '../../common/components/SimpleModal';
+
+function ModalToCloneProject({ isOpen, onClose, currentAsset }) {
+ const { t } = useTranslation('syllabus');
+
+ const urlToClone = currentAsset?.url || currentAsset?.readme_url?.split('/blob')?.[0];
+ const repoName = urlToClone?.split('/')?.pop();
+
+ return (
+
+
+
+ );
+}
+
+ModalToCloneProject.propTypes = {
+ isOpen: PropTypes.bool,
+ onClose: PropTypes.func,
+ currentAsset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+};
+
+ModalToCloneProject.defaultProps = {
+ isOpen: false,
+ onClose: () => {},
+ currentAsset: null,
+};
+
+export default ModalToCloneProject;
diff --git a/src/js_modules/syllabus/OpenWithLearnpackCTA.jsx b/src/js_modules/syllabus/OpenWithLearnpackCTA.jsx
new file mode 100644
index 000000000..922c6b558
--- /dev/null
+++ b/src/js_modules/syllabus/OpenWithLearnpackCTA.jsx
@@ -0,0 +1,78 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import useTranslation from 'next-translate/useTranslation';
+import useCohortHandler from '../../common/hooks/useCohortHandler';
+import CallToAction from '../../common/components/CallToAction';
+import modifyEnv from '../../../modifyEnv';
+import ModalToCloneProject from './ModalToCloneProject';
+
+function OpenWithLearnpackCTA({ currentAsset }) {
+ const { t, lang } = useTranslation('common');
+ const [learnpackActions, setLearnpackActions] = useState([]);
+ const { state } = useCohortHandler();
+ const { cohortSession } = state;
+ const [showCloneModal, setShowCloneModal] = useState(false);
+ const BREATHECODE_HOST = modifyEnv({ queryString: 'host', env: process.env.BREATHECODE_HOST });
+
+ const accessToken = localStorage.getItem('accessToken');
+
+ const provisioningLinks = [{
+ title: t('learnpack.new-exercise'),
+ link: `${BREATHECODE_HOST}/v1/provisioning/me/container/new?token=${accessToken}&cohort=${cohortSession?.id}&repo=${currentAsset?.url}`,
+ isExternalLink: true,
+ },
+ {
+ title: t('learnpack.continue-exercise'),
+ link: `${BREATHECODE_HOST}/v1/provisioning/me/workspaces?token=${accessToken}&cohort=${cohortSession?.id}&repo=${currentAsset?.url}`,
+ isExternalLink: true,
+ }];
+
+ useEffect(() => {
+ const openInLearnpackAction = t('learnpack.open-in-learnpack-button', {}, { returnObjects: true });
+ const localhostAction = {
+ text: `${t('learnpack.open-locally')}${cohortSession?.available_as_saas ? ` (${t('learnpack.recommended')})` : ''}`,
+ type: 'button',
+ onClick: () => {
+ setShowCloneModal(true);
+ },
+ };
+ const cloudActions = {
+ ...openInLearnpackAction,
+ text: `${openInLearnpackAction.text}${cohortSession?.available_as_saas === false ? ` (${t('learnpack.recommended')})` : ''}`,
+ links: provisioningLinks,
+ };
+ if (cohortSession?.id) {
+ if (!currentAsset?.gitpod) setLearnpackActions([localhostAction]);
+ else if (cohortSession.available_as_saas) setLearnpackActions([localhostAction, cloudActions]);
+ else setLearnpackActions([cloudActions, localhostAction]);
+ }
+ }, [lang, cohortSession?.id, currentAsset?.url]);
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+OpenWithLearnpackCTA.propTypes = {
+ currentAsset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.array])),
+};
+OpenWithLearnpackCTA.defaultProps = {
+ currentAsset: null,
+};
+
+export default OpenWithLearnpackCTA;
diff --git a/src/js_modules/syllabus/ProjectBoardGuidedExperience.jsx b/src/js_modules/syllabus/ProjectBoardGuidedExperience.jsx
new file mode 100644
index 000000000..5111a3331
--- /dev/null
+++ b/src/js_modules/syllabus/ProjectBoardGuidedExperience.jsx
@@ -0,0 +1,237 @@
+import React, { useState, useRef, useEffect } from 'react';
+import useTranslation from 'next-translate/useTranslation';
+import PropTypes from 'prop-types';
+import { Box, Button, useColorModeValue } from '@chakra-ui/react';
+import TaskCodeRevisions from './TaskCodeRevisions';
+import useModuleHandler from '../../common/hooks/useModuleHandler';
+import useStyle from '../../common/hooks/useStyle';
+import SubTasks from '../../common/components/MarkDownParser/SubTasks';
+import ReactPlayerV2 from '../../common/components/ReactPlayerV2';
+import Heading from '../../common/components/Heading';
+import Text from '../../common/components/Text';
+import Icon from '../../common/components/Icon';
+
+function ProjectHeading({ currentAsset, isDelivered }) {
+ const { backgroundColor4, hexColor } = useStyle();
+ const { subTasks } = useModuleHandler();
+
+ const title = currentAsset?.title;
+ const assetType = currentAsset?.asset_type;
+ const assetTypeIcons = {
+ LESSON: 'book',
+ EXERCISE: 'strength',
+ PROJECT: 'code',
+ QUIZ: 'answer',
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ {title}
+
+
+ {currentAsset?.description && (
+
+ {currentAsset.description}
+
+ )}
+
+
+ {Array.isArray(subTasks) && subTasks?.length > 0 && (
+
+ )}
+ {isDelivered && (
+
+
+
+ )}
+
+
+ {!isDelivered && (
+
+ )}
+
+ >
+ );
+}
+
+function ProjectBoardGuidedExperience({ currentAsset }) {
+ const { t } = useTranslation('syllabus');
+ const { currentTask } = useModuleHandler();
+ const headerRef = useRef(null);
+ const [isHeaderVisible, setIsHeaderVisible] = useState(true);
+ const { backgroundColor4, hexColor, backgroundColor, featuredLight } = useStyle();
+
+ const title = currentAsset?.title;
+ const assetType = currentAsset?.asset_type;
+
+ const isDelivered = currentTask?.task_status === 'DONE' && currentAsset?.delivery_formats !== 'no_delivery';
+
+ const assetTypeIcons = {
+ LESSON: 'book',
+ EXERCISE: 'strength',
+ PROJECT: 'code',
+ QUIZ: 'answer',
+ };
+
+ const scrollTop = () => {
+ const markdownBody = document.getElementById('main-container');
+ markdownBody.scroll({
+ top: 0,
+ behavior: 'smooth',
+ });
+ };
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ setIsHeaderVisible(entry.isIntersecting);
+ },
+ {
+ root: document.querySelector('.scrollable-container'),
+ threshold: 0,
+ },
+ );
+
+ if (headerRef.current) {
+ observer.observe(headerRef.current);
+ }
+
+ return () => {
+ if (headerRef.current) {
+ observer.unobserve(headerRef.current);
+ }
+ };
+ }, []);
+
+ return (
+ <>
+
+
+
+ {isDelivered && (
+
+
+ {t('teachers-feedback')}
+
+ {currentTask.description ? (
+
+
+ {currentTask.description}
+
+
+ ) : (
+ <>
+
+ {t('no-feedback')}
+
+
+ {t('task-notification')}
+
+ >
+ )}
+
+ )}
+
+ {isDelivered && (
+
+ )}
+
+
+
+
+
+ {title}
+
+
+
+
+ {t('back-to-top')}
+
+
+ >
+ );
+}
+
+ProjectBoardGuidedExperience.propTypes = {
+ currentAsset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+};
+ProjectBoardGuidedExperience.defaultProps = {
+ currentAsset: null,
+};
+
+ProjectHeading.propTypes = {
+ currentAsset: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
+ isDelivered: PropTypes.bool,
+};
+ProjectHeading.defaultProps = {
+ currentAsset: null,
+ isDelivered: false,
+};
+
+export default ProjectBoardGuidedExperience;
diff --git a/src/js_modules/syllabus/SyllabusMarkdownComponent.jsx b/src/js_modules/syllabus/SyllabusMarkdownComponent.jsx
index def1c4697..ddc40ac45 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,8 @@ function SyllabusMarkdownComponent({
content={readme.content}
callToActionProps={callToActionProps}
withToc={lesson?.toLowerCase() === 'read'}
+ showContentHeading={!(currentData.asset_type === 'PROJECT' && isGuidedExperience)}
+ isGuidedExperience={isGuidedExperience}
frontMatter={{
title: currentData.title,
// subtitle: currentData.description,
@@ -33,6 +35,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/TaskCodeRevisions.jsx b/src/js_modules/syllabus/TaskCodeRevisions.jsx
new file mode 100644
index 000000000..052e9ada3
--- /dev/null
+++ b/src/js_modules/syllabus/TaskCodeRevisions.jsx
@@ -0,0 +1,306 @@
+import { Box, Button, Divider, Flex, Textarea, useToast } from '@chakra-ui/react';
+import { useEffect, useState } from 'react';
+import useTranslation from 'next-translate/useTranslation';
+import useAuth from '../../common/hooks/useAuth';
+import useModuleHandler from '../../common/hooks/useModuleHandler';
+import useStyle from '../../common/hooks/useStyle';
+import bc from '../../common/services/breathecode';
+import CodeRevisionsList from '../../common/components/ReviewModal/CodeRevisionsList';
+import Icon from '../../common/components/Icon';
+import Heading from '../../common/components/Heading';
+import Text from '../../common/components/Text';
+import MarkDownParser from '../../common/components/MarkDownParser';
+import { error } from '../../utils/logging';
+
+const inputReviewRateCommentLimit = 100;
+const defaultReviewRateData = {
+ status: null,
+ comment: '',
+ isSubmitting: false,
+ submited: false,
+};
+function TaskCodeRevisions() {
+ const { t } = useTranslation('syllabus');
+ const { currentTask } = useModuleHandler();
+ const { featuredLight, hexColor, backgroundColor, backgroundColor4 } = useStyle();
+ const { isAuthenticatedWithRigobot } = useAuth();
+ const toast = useToast();
+ const [contextData, setContextData] = useState({
+ code_revisions: [],
+ revision_content: {},
+ });
+ const [reviewRateData, setReviewRateData] = useState(defaultReviewRateData);
+
+ const reviewRateStatus = reviewRateData?.status;
+ const codeRevisions = contextData?.code_revisions || [];
+ const revisionContent = contextData?.revision_content;
+ const hasRevision = revisionContent !== undefined;
+ const resetView = () => {
+ setReviewRateData({
+ status: null,
+ comment: '',
+ isSubmitting: false,
+ submited: false,
+ });
+ };
+
+ const selectCodeRevision = (revision) => {
+ const content = revision?.original_code;
+ const decodedReviewCodeContent = atob(content);
+
+ setContextData((prevState) => ({
+ ...prevState,
+ revision_content: {
+ path: revision?.file?.name,
+ ...revision,
+ code: decodedReviewCodeContent,
+ },
+ }));
+
+ if (revision.is_good || revision.revision_rating_comments) {
+ setReviewRateData((prev) => ({
+ ...prev,
+ submited: true,
+ status: revision.is_good ? 'like' : 'dislike',
+ comment: revision.revision_rating_comments,
+ }));
+ }
+ };
+
+ const getCodeRevisions = async () => {
+ try {
+ if (!isAuthenticatedWithRigobot || !currentTask.github_url) return;
+ const response = await bc.assignments().getPersonalCodeRevisionsByTask(currentTask.id);
+ const data = await response.json();
+
+ if (response.ok) {
+ const codeRevisionsSortedByDate = data.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
+ setContextData((prev) => ({
+ ...prev,
+ code_revisions: codeRevisionsSortedByDate,
+ }));
+ } else {
+ toast({
+ title: t('alert-message:something-went-wrong'),
+ description: `Cannot get code revisions: ${data?.detail}`,
+ status: 'error',
+ duration: 5000,
+ position: 'top',
+ isClosable: true,
+ });
+ }
+ } catch (errorMsg) {
+ error('Error fetching code revisions:', errorMsg);
+ }
+ };
+
+ useEffect(() => {
+ if (currentTask) {
+ getCodeRevisions();
+ }
+ }, [currentTask?.id]);
+
+ const handleSelectReviewRate = (status) => {
+ setReviewRateData((prev) => ({ ...prev, status }));
+ };
+
+ const onChangeRateComment = (e) => {
+ if (e.target.value.length <= inputReviewRateCommentLimit) {
+ setReviewRateData((prev) => ({ ...prev, comment: e.target.value }));
+ }
+ };
+
+ const submitReviewRate = async (type) => {
+ try {
+ setReviewRateData((prev) => ({ ...prev, isSubmitting: true }));
+ const argsData = {
+ send: {
+ is_good: reviewRateData.status === 'like',
+ comment: reviewRateData.comment,
+ },
+ skip: {
+ is_good: reviewRateData.status === 'like',
+ comment: null,
+ },
+ };
+ const { data } = await bc.assignments().rateCodeRevision(revisionContent?.id, argsData[type]);
+
+ setReviewRateData((prev) => ({ ...prev, submited: true }));
+ const updatedRevisionContent = {
+ ...data,
+ is_good: typeof data?.is_good === 'string' ? data?.is_good === 'True' : data?.is_good,
+ hasBeenReviewed: true,
+ };
+ const updateCodeRevisions = contextData.code_revisions.map((revision) => {
+ if (revision.id === revisionContent.id) {
+ return updatedRevisionContent;
+ }
+ return revision;
+ });
+ selectCodeRevision(updatedRevisionContent);
+ setContextData((prevState) => ({
+ ...prevState,
+ code_revisions: updateCodeRevisions,
+ }));
+ } finally {
+ setReviewRateData((prev) => ({ ...prev, isSubmitting: false }));
+ }
+ };
+
+ return (
+
+ {revisionContent?.id ? (
+
+ {
+ resetView();
+ setContextData((prev) => ({ ...prev, revision_content: {} }));
+ }}
+ >
+ ←
+ {' '}
+ {t('back')}
+
+
+ {revisionContent?.file?.name}
+
+
+ ) : (
+
+ {t('code-reviews')}
+
+ )}
+ {codeRevisions?.length > 0 ? (
+ <>
+ {!revisionContent?.id ? (
+
+ ) : (
+ <>
+
+
+ {hasRevision && (
+
+
+
+ {revisionContent?.comment}
+
+
+
+
+
+
+ {reviewRateStatus !== null && (
+ <>
+
+
+
+
+
+ {t(reviewRateStatus)}
+
+
+ >
+ )}
+
+ )}
+ {hasRevision && reviewRateData.submited && (
+
+
+
+ {`${t('you')}:`}
+
+
+ {reviewRateData?.comment}
+
+
+
+ )}
+
+
+ {!reviewRateStatus ? (
+ <>
+
+ {t('rate-comment')}
+
+
+ handleSelectReviewRate('like')}
+ variant="unstyled"
+ height="auto"
+ gridGap="10px"
+ aria-label="Mark as Useful"
+ _hover={{
+ opacity: 1,
+ }}
+ >
+
+
+ handleSelectReviewRate('dislike')}
+ variant="unstyled"
+ height="auto"
+ gridGap="10px"
+ aria-label="Mark as not useful"
+ _hover={{
+ opacity: 1,
+ }}
+ >
+
+
+
+ >
+ ) : (
+
+
+
+
+ {`${reviewRateData.comment.length}/ ${inputReviewRateCommentLimit}`}
+
+
+
+ submitReviewRate('send')}>
+
+
+ handleSelectReviewRate(null)}>
+
+
+
+
+ )}
+
+ >
+ )}
+ >
+ ) : (
+
+
+ {t('no-code-reviews')}
+
+
+ {t('task-notification')}
+
+
+ )}
+
+ );
+}
+
+export default TaskCodeRevisions;
diff --git a/src/js_modules/syllabus/TimelineSidebar.jsx b/src/js_modules/syllabus/TimelineSidebar.jsx
index 14c336263..cee3db0b9 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,
+ onClickAssignment, showPendingTasks, setShowPendingTasks,
isOpen, onToggle, isStudent, teacherInstructions,
}) {
const { t } = useTranslation('syllabus');
+ const { state } = useCohortHandler();
+ const { cohortSession, sortedAssignments } = state;
const Open = !isOpen;
const slide = getSlideProps(Open);
const {
@@ -46,7 +49,7 @@ function TimelineSidebar({
/>
)}
/>
-
+
)}
- {filterEmptyModules.length > 0 && filterEmptyModules.map((section) => {
+ {sortedAssignments.length > 0 && sortedAssignments.map((section) => {
const currentAssignments = showPendingTasks
? section.filteredModulesByPending
: section.filteredModules;
@@ -164,8 +167,6 @@ function TimelineSidebar({
}
TimelineSidebar.propTypes = {
- cohortSession: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
- filterEmptyModules: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.any])),
onClickAssignment: PropTypes.func,
showPendingTasks: PropTypes.bool,
isOpen: PropTypes.bool,
@@ -175,8 +176,6 @@ TimelineSidebar.propTypes = {
teacherInstructions: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any])),
};
TimelineSidebar.defaultProps = {
- cohortSession: null,
- filterEmptyModules: [],
onClickAssignment: () => {},
showPendingTasks: false,
isOpen: false,
diff --git a/src/js_modules/syllabus/config.jsx b/src/js_modules/syllabus/config.jsx
index b7d2f6cca..fe8ea26f7 100644
--- a/src/js_modules/syllabus/config.jsx
+++ b/src/js_modules/syllabus/config.jsx
@@ -30,7 +30,7 @@ export const getSlideProps = (open) => {
minWidth: '290px',
zIndex: 1200,
position: isBelowLaptop ? 'inherit' : 'sticky',
- backgroundColor: Config().bgColor,
+ background: Config().bgColor,
top: 0,
left: 0,
display: 'flex',
@@ -43,8 +43,6 @@ export const getSlideProps = (open) => {
outline: 0,
borderRight: 1,
borderStyle: 'solid',
- // overflowX: 'hidden',
- // overflowY: 'auto',
borderColor: Config().commonBorderColor,
transition: open ? 'transform 225ms cubic-bezier(0, 0, 0.2, 1) 0ms' : 'box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
transitionProperty: open ? 'transform' : 'box-shadow',
diff --git a/src/pages/accept-invite.jsx b/src/pages/accept-invite.jsx
index 24a2d8495..58e4cfd63 100644
--- a/src/pages/accept-invite.jsx
+++ b/src/pages/accept-invite.jsx
@@ -321,7 +321,7 @@ function AcceptInvite() {
onChange={() => setIsChecked(!isChecked)}
/>
- {t('signup:validators.termns-and-conditions-required')}
+ {t('signup:validators.receive-information')}
{' '}
{
@@ -124,6 +125,7 @@ function chooseProgram() {
const getServices = async (userRoles) => {
if (userRoles?.length > 0) {
+ delete axios.defaults.headers.common.Academy;
const mentorshipPromises = await userRoles.map((role) => bc.mentorship({ academy: role?.academy?.id }, true).getService()
.then((resp) => {
const data = resp?.data;
diff --git a/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx b/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx
index 134671429..29b99a4db 100644
--- a/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx
+++ b/src/pages/cohort/[cohortSlug]/[slug]/[version]/index.jsx
@@ -27,8 +27,6 @@ import asPrivate from '../../../../../common/context/PrivateRouteWrapper';
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,
@@ -46,6 +44,7 @@ import Text from '../../../../../common/components/Text';
import OnlyFor from '../../../../../common/components/OnlyFor';
import AlertMessage from '../../../../../common/components/AlertMessage';
import useCohortHandler from '../../../../../common/hooks/useCohortHandler';
+import useModuleHandler from '../../../../../common/hooks/useModuleHandler';
import modifyEnv from '../../../../../../modifyEnv';
import LiveEvent from '../../../../../common/components/LiveEvent';
import FinalProject from '../../../../../common/components/FinalProject';
@@ -58,9 +57,7 @@ function Dashboard() {
const toast = useToast();
const router = useRouter();
const { colorMode } = useColorMode();
- const { contextState, setContextState } = useModuleMap();
const [showWarningModal, setShowWarningModal] = useState(false);
- const { cohortProgram } = contextState;
const [studentAndTeachers, setSudentAndTeachers] = useState([]);
const [modalIsOpen, setModalIsOpen] = useState(false);
const [showSearch, setShowSearch] = useState(false);
@@ -71,19 +68,20 @@ function Dashboard() {
const [liveClasses, setLiveClasses] = useState([]);
const { featuredColor, hexColor, modal } = useStyle();
const [isLoadingAssigments, setIsLoadingAssigments] = useState(true);
- const { user, isAuthenticated } = useAuth();
+ const { user } = useAuth();
const isBelowTablet = getBrowserSize()?.width < 768;
const [subscriptionData, setSubscriptionData] = useState(null);
const [allSubscriptions, setAllSubscriptions] = useState(null);
const [isAvailableToShowWarningModal, setIsAvailableToShowModalMessage] = useState(false);
const [showMandatoryModal, setShowMandatoryModal] = useState(false);
+ const { cohortProgram, taskTodo, setTaskTodo } = useModuleHandler();
const {
state, getCohortAssignments, getCohortData, prepareTasks, getDailyModuleData,
getMandatoryProjects, getTasksWithoutCohort, setSortedAssignments, getLastDoneTaskModuleData,
} = useCohortHandler();
- const { cohortSession, sortedAssignments, taskCohortNull } = state;
+ const { cohortSession, sortedAssignments, taskCohortNull, myCohorts } = state;
const mainTechnologies = cohortProgram?.main_technologies
? cohortProgram?.main_technologies.split(',').map((el) => el.trim())
@@ -137,13 +135,10 @@ function Dashboard() {
}));
await bc.todo({}).updateBulk(tasksToUpdate)
.then(({ data }) => {
- setContextState({
- ...contextState,
- taskTodo: [
- ...contextState.taskTodo,
- ...data,
- ],
- });
+ setTaskTodo([
+ ...taskTodo,
+ ...data,
+ ]);
setModalIsOpen(false);
})
.catch(() => {
@@ -185,23 +180,17 @@ function Dashboard() {
});
};
useEffect(() => {
- if (isAuthenticated) {
- bc.admissions().me()
- .then((resp) => {
- const data = resp?.data;
- const cohorts = data?.cohorts;
- const currentCohort = cohorts?.find((l) => l?.cohort?.slug === cohortSlug);
- if (currentCohort?.finantial_status === 'LATE' || currentCohort?.educational_status === 'SUSPENDED') {
- router.push('/choose-program');
- } else {
- const isReadyToShowGithubMessage = cohorts?.some(
- (l) => l?.educational_status === 'ACTIVE' && l.cohort.available_as_saas === false,
- );
- setIsAvailableToShowModalMessage(isReadyToShowGithubMessage);
- }
- });
+ if (cohortSession?.cohort_user) {
+ if (cohortSession.cohort_user.finantial_status === 'LATE' || cohortSession.cohort_user.educational_status === 'SUSPENDED') {
+ router.push('/choose-program');
+ } else {
+ const isReadyToShowGithubMessage = myCohorts.some(
+ (l) => l.cohort_user.educational_status === 'ACTIVE' && l.available_as_saas === false,
+ );
+ setIsAvailableToShowModalMessage(isReadyToShowGithubMessage);
+ }
}
- }, [isAuthenticated]);
+ }, [cohortSession]);
useEffect(() => {
if (showGithubWarning === 'active') {
@@ -274,15 +263,17 @@ function Dashboard() {
getCohortData({
cohortSlug,
}).then((cohort) => {
- reportDatalayer({
- dataLayer: {
- current_cohort_id: cohort.id,
- current_cohort_slug: cohort.slug,
- },
- });
+ if (cohort) {
+ reportDatalayer({
+ dataLayer: {
+ current_cohort_id: cohort.id,
+ current_cohort_slug: cohort.slug,
+ },
+ });
+ }
// Fetch cohort assignments (lesson, exercise, project, quiz)
getCohortAssignments({
- user, setContextState, slug, cohort,
+ slug, cohort,
});
}).finally(() => {
setIsLoadingAssigments(false);
@@ -334,12 +325,8 @@ function Dashboard() {
// Sort all data fetched in order of taskTodo
useEffect(() => {
- if (contextState.cohortProgram && typeof contextState.cohortProgram === 'object' && contextState.taskTodo) {
- prepareTasks({
- cohortProgram, contextState, nestAssignments,
- });
- }
- }, [contextState.cohortProgram, contextState.taskTodo, router]);
+ prepareTasks();
+ }, [cohortProgram, taskTodo, router]);
const dailyModuleData = getDailyModuleData() || '';
const lastTaskDoneModuleData = getLastDoneTaskModuleData() || '';
@@ -464,9 +451,9 @@ function Dashboard() {
}}
>
- {(cohortSession?.syllabus_version?.name || cohortProgram.name) ? (
+ {(cohortSession?.syllabus_version?.name || cohortProgram?.name) ? (
- {cohortSession?.syllabus_version?.name || cohortProgram?.name}
+ {cohortSession?.syllabus_version?.name || cohortProgram.name}
) : (
-
+
@@ -538,9 +523,7 @@ function Dashboard() {
{cohortSession?.kickoff_date && (
)}
@@ -612,7 +595,7 @@ function Dashboard() {
@@ -689,18 +672,13 @@ function Dashboard() {
return (
-
+
{cohortSession?.stage === 'FINAL_PROJECT' && (
@@ -779,8 +755,6 @@ function Dashboard() {
)}
@@ -904,7 +878,7 @@ function Dashboard() {
key={`${module.title}-${i}`}
currIndex={i}
data={module}
- taskTodo={contextState.taskTodo}
+ taskTodo={taskTodo}
variant="open-only"
/>
))}
diff --git a/src/pages/cohort/[cohortSlug]/student/[studentId]/assignment/[assignmentId]/index.jsx b/src/pages/cohort/[cohortSlug]/student/[studentId]/assignment/[assignmentId]/index.jsx
index 87c4b7143..a95863264 100644
--- a/src/pages/cohort/[cohortSlug]/student/[studentId]/assignment/[assignmentId]/index.jsx
+++ b/src/pages/cohort/[cohortSlug]/student/[studentId]/assignment/[assignmentId]/index.jsx
@@ -20,6 +20,7 @@ import Heading from '../../../../../../../common/components/Heading';
import Text from '../../../../../../../common/components/Text';
import Icon from '../../../../../../../common/components/Icon';
import DottedTimeline from '../../../../../../../common/components/DottedTimeline';
+import { intervalToHours } from '../../../../../../../utils';
import axiosInstance from '../../../../../../../axios';
function AssignmentReport() {
@@ -68,16 +69,6 @@ function AssignmentReport() {
}
};
- const intervalToHours = (duration) => {
- const hours = duration.years * 24 * 365 // Hours from years (assuming 365 days per year)
- + duration.months * 24 * 30 // Hours from months (assuming 30 days per month)
- + duration.days * 24 // Hours from days
- + duration.hours
- + duration.minutes / 60 // Convert minutes to hours
- + duration.seconds / 3600;
- return hours;
- };
-
useEffect(() => {
fetchStudentAndTasks();
}, []);
diff --git a/src/pages/docs/[syllabusSlug]/[assetSlug]/index.jsx b/src/pages/docs/[syllabusSlug]/[assetSlug]/index.jsx
index e3e47039b..d29c65aba 100644
--- a/src/pages/docs/[syllabusSlug]/[assetSlug]/index.jsx
+++ b/src/pages/docs/[syllabusSlug]/[assetSlug]/index.jsx
@@ -16,7 +16,7 @@ import { ChevronRightIcon, ChevronDownIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import useTranslation from 'next-translate/useTranslation';
import Head from 'next/head';
-import { nestAssignments } from '../../../../common/hooks/useModuleHandler';
+import { processRelatedAssignments } from '../../../../common/handlers/cohorts';
import useStyle from '../../../../common/hooks/useStyle';
import bc from '../../../../common/services/breathecode';
import Heading from '../../../../common/components/Heading';
@@ -52,20 +52,14 @@ const formatSyllabus = (syllabus) => syllabus.json.days.filter((assignment) => {
return false;
}).map((assignment) => {
const {
- id, label, lessons, replits, assignments, quizzes,
+ id, label,
} = assignment;
- const nestedAssignments = nestAssignments({
- id,
- read: lessons,
- practice: replits,
- project: assignments,
- answer: quizzes,
- });
+ const nestedAssignments = processRelatedAssignments(assignment);
const myModule = {
id,
label,
- assets: nestedAssignments.modules,
+ assets: nestedAssignments.content,
};
return myModule;
});
diff --git a/src/pages/docs/[syllabusSlug]/index.jsx b/src/pages/docs/[syllabusSlug]/index.jsx
index daa223b50..0df2394c9 100644
--- a/src/pages/docs/[syllabusSlug]/index.jsx
+++ b/src/pages/docs/[syllabusSlug]/index.jsx
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import { useRouter } from 'next/router';
import useTranslation from 'next-translate/useTranslation';
import bc from '../../../common/services/breathecode';
-import { nestAssignments } from '../../../common/hooks/useModuleHandler';
+import { processRelatedAssignments } from '../../../common/handlers/cohorts';
import GridContainer from '../../../common/components/GridContainer';
import { MDSkeleton } from '../../../common/components/Skeleton';
import { WHITE_LABEL_ACADEMY } from '../../../utils/variables';
@@ -30,20 +30,14 @@ const formatSyllabus = (syllabus) => syllabus.json.days.filter((assignment) => {
return false;
}).map((assignment) => {
const {
- id, label, lessons, replits, assignments, quizzes,
+ id, label,
} = assignment;
- const nestedAssignments = nestAssignments({
- id,
- read: lessons,
- practice: replits,
- project: assignments,
- answer: quizzes,
- });
+ const nestedAssignments = processRelatedAssignments(assignment);
const myModule = {
id,
label,
- assets: nestedAssignments.modules,
+ assets: nestedAssignments.content,
};
return myModule;
});
diff --git a/src/pages/lesson/[slug].jsx b/src/pages/lesson/[slug].jsx
index 6ecd1fb23..01a375ca7 100644
--- a/src/pages/lesson/[slug].jsx
+++ b/src/pages/lesson/[slug].jsx
@@ -173,8 +173,6 @@ function LessonSlug({ lesson, markdown, ipynbHtml }) {
const exensionName = getExtensionName(lesson.readme_url);
const isIpynb = exensionName === 'ipynb' || ipynbHtml?.iframe;
- console.log(lesson);
-
return (
<>
{lesson?.structuredData?.name && (
diff --git a/src/pages/lessons/index.jsx b/src/pages/lessons/index.jsx
index 6d2cc6d5b..8b1fee0d4 100644
--- a/src/pages/lessons/index.jsx
+++ b/src/pages/lessons/index.jsx
@@ -229,6 +229,7 @@ function Projects({ lessons, technologyTags, difficulties, count }) {
withContainer
gridColumn="1 / span 10"
maxWidth="1280px"
+ padding="0 15px"
>
{
+ if (userRoles?.length > 0) {
+ const mentorshipPromises = await userRoles.map((role) => bc.mentorship({ academy: role?.academy?.id }, true).getService()
+ .then((resp) => {
+ const data = resp?.data;
+ if (data !== undefined && data.length > 0) {
+ return data.map((serv) => ({
+ ...serv,
+ academy: {
+ id: role?.academy.id,
+ available_as_saas: role?.academy?.available_as_saas,
+ },
+ }));
+ }
+ return [];
+ }));
+ const mentorshipResults = await Promise.all(mentorshipPromises);
+ const recopilatedServices = mentorshipResults.flat();
+
+ setMentorshipServices({
+ isLoading: false,
+ data: recopilatedServices,
+ });
+ }
+ };
+
+ const getAdmissionsData = async () => {
+ try {
+ const response = await bc.admissions().me();
+ const admissionsFromDB = response.data;
+ setAdmissions(response.data);
+ getServices(admissionsFromDB.roles);
+ } catch (error) {
+ console.error('Error fetching admissions data:', error);
+ }
+ };
+
+ useEffect(() => {
+ const checkAuthAndFetchData = async () => {
+ if (!isLoading && !isAuthenticated) {
+ router.push('/login');
+ } else if (isAuthenticated) {
+ await getAdmissionsData();
+ }
+ };
+
+ checkAuthAndFetchData();
+ }, [isLoading, isAuthenticated]);
+
+ const allSyllabus = useMemo(() => {
+ const allCohorts = admissions?.cohorts || [];
+ const syllabus = [...new Set(allCohorts.map(({ cohort }) => cohort.syllabus_version.slug))];
+ return syllabus;
+ }, [admissions]);
+
+ const getAllMentorsAvailable = async () => {
+ const servicesSlugs = mentorshipServices.data.map(({ slug }) => slug);
+
+ const academies = mentorshipServices.data.reduce((acc, { academy, ...restOfService }) => {
+ if (!acc[academy.id]) {
+ acc[academy.id] = { services: [] };
+ }
+ acc[academy.id].services.push(restOfService);
+ return acc;
+ }, {});
+
+ const academyData = Object.entries(academies).map(([id, { services }]) => ({
+ id: Number(id),
+ services,
+ }));
+
+ const getMentorsForAcademy = async (academy) => {
+ const res = await bc.mentorship({
+ services: academy.services.map((s) => s.slug).join(','),
+ status: 'ACTIVE',
+ syllabus: allSyllabus?.join(',') || undefined,
+ academy: academy.id,
+ }).getMentor();
+
+ return res?.data || [];
+ };
+
+ if (servicesSlugs.length > 0 || allSyllabus.length > 0) {
+ const mentorsPromises = academyData.map(getMentorsForAcademy);
+ const mentorsList = (await Promise.all(mentorsPromises)).flat();
+ return mentorsList;
+ }
+
+ return [];
+ };
+
+ const sortByConsumptionAvailability = (allConsumables) => allConsumables.sort((a, b) => {
+ const balanceA = a?.balance?.unit;
+ const balanceB = b?.balance?.unit;
+
+ if (balanceA === -1 && balanceB !== -1) return -1;
+ if (balanceA !== -1 && balanceB === -1) return 1;
+
+ if (balanceA > 0 && balanceB <= 0) return -1;
+ if (balanceA <= 0 && balanceB > 0) return 1;
+
+ if (balanceA > 0 && balanceB > 0) return balanceB - balanceA;
+
+ return 0;
+ });
+
+ const getMentorsAndConsumables = async () => {
+ const mentors = await getAllMentorsAvailable();
+ const reqConsumables = await bc.payment().service().consumable()
+ .then((res) => res?.data?.mentorship_service_sets.map((mentorshipServiceSet) => bc.mentorship()
+ .getServiceSet(mentorshipServiceSet?.id)
+ .then((rs) => ({
+ ...rs?.data,
+ ...mentorshipServiceSet,
+ }))));
+
+ const allConsumables = await Promise.all(reqConsumables);
+ const sortedConsumables = sortByConsumptionAvailability(allConsumables);
+ setConsumables(sortedConsumables);
+ setAllMentorsAvailable(mentors);
+ };
+
+ useEffect(() => {
+ if (!mentorshipServices.isLoading && mentorshipServices?.data.length > 0) {
+ getMentorsAndConsumables();
+ }
+ }, [mentorshipServices]);
+
+ const mentorsFiltered = mentorsByService.filter(
+ (ment) => {
+ const fullName = `${ment.user.first_name} ${ment.user.last_name}`.toLowerCase();
+ return (
+ fullName.includes(searchProps.mentorSearch)
+ && ment.services.some((sv) => sv.status === 'ACTIVE' && sv.slug === mentoryProps?.service?.slug)
+ );
+ },
+ );
+
+ const filterServices = () => {
+ if (subscriptionData?.selected_mentorship_service_set?.mentorship_services?.length > 0) {
+ return subscriptionData?.selected_mentorship_service_set?.mentorship_services?.filter(
+ (l) => l.name.toLowerCase().includes(searchProps.serviceSearch),
+ );
+ }
+ if (mentorshipServices.data.length > 0) {
+ return mentorshipServices.data?.filter(
+ (l) => l.name.toLowerCase().includes(searchProps.serviceSearch),
+ );
+ }
+
+ return [];
+ };
+ const suscriptionServicesFiltered = filterServices();
+
+ useEffect(() => {
+ if (mentoryProps.serviceSelected) {
+ fetchSubscriptions()
+ .then((data) => {
+ setSubscriptionData(data);
+ reportDatalayer({
+ dataLayer: {
+ event: 'subscriptions_load',
+ method: 'native',
+ plan_financings: data?.plan_financings?.filter((s) => s.status === 'ACTIVE').map((s) => s.plans.filter((p) => p.status === 'ACTIVE').map((p) => p.slug).join(',')).join(','),
+ subscriptions: data?.subscriptions?.filter((s) => s.status === 'ACTIVE').map((s) => s.plans.filter((p) => p.status === 'ACTIVE').map((p) => p.slug).join(',')).join(','),
+ },
+ });
+ });
+ }
+ }, [mentoryProps.serviceSelected]);
+
+ return !isLoading && user && !mentorshipServices.isLoading && (
+ useColorModeValue('#f9f9f9', '#171f2a')} overflow="hidden">
+
+
+
+
+ {`${t('consumables.back-to-dashboard')}`}
+
+
+
+ useColorModeValue('white', '#27333f')}>
+
+
+
+
+
+ );
+}
+
+export default MentorshipSchedule;
diff --git a/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx b/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx
index 1adf03b15..03a2a7115 100644
--- a/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx
+++ b/src/pages/syllabus/[cohortSlug]/[lesson]/[lessonSlug]/index.jsx
@@ -1,11 +1,8 @@
-/* eslint-disable no-dupe-else-if */
-/* eslint-disable no-unsafe-optional-chaining */
-/* eslint-disable no-extra-boolean-cast */
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import {
- Box, Flex, useDisclosure, Link, useToast,
- useColorModeValue, Modal, ModalOverlay,
+ Box, Flex, useDisclosure, Link,
+ useColorModeValue, Modal, ModalOverlay, useToast, Tooltip,
ModalContent, ModalHeader, ModalCloseButton, ModalBody, Button,
} from '@chakra-ui/react';
import useTranslation from 'next-translate/useTranslation';
@@ -14,8 +11,8 @@ 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 { ButtonHandlerByTaskStatus } from '../../../../../js_modules/moduleMap/taskHandler';
+import useModuleHandler from '../../../../../common/hooks/useModuleHandler';
+import { ButtonHandlerByTaskStatus } from '../../../../../js_modules/moduleMap/ButtonHandlerByTaskStatus';
import getMarkDownContent from '../../../../../common/components/MarkDownParser/markdown';
import MarkDownParser from '../../../../../common/components/MarkDownParser';
import Text from '../../../../../common/components/Text';
@@ -23,14 +20,17 @@ import useAuth from '../../../../../common/hooks/useAuth';
import StickySideBar from '../../../../../common/components/StickySideBar';
import Icon from '../../../../../common/components/Icon';
import AlertMessage from '../../../../../common/components/AlertMessage';
-import useModuleMap from '../../../../../common/store/actions/moduleMapAction';
import ShareButton from '../../../../../common/components/ShareButton';
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 bc from '../../../../../common/services/breathecode';
+import GuidedExperienceSidebar from '../../../../../js_modules/syllabus/GuidedExperienceSidebar';
+import ExerciseGuidedExperience from '../../../../../js_modules/syllabus/ExerciseGuidedExperience';
+import ProjectBoardGuidedExperience from '../../../../../js_modules/syllabus/ProjectBoardGuidedExperience';
+import OpenWithLearnpackCTA from '../../../../../js_modules/syllabus/OpenWithLearnpackCTA';
import SyllabusMarkdownComponent from '../../../../../js_modules/syllabus/SyllabusMarkdownComponent';
+import bc from '../../../../../common/services/breathecode';
import useCohortHandler from '../../../../../common/hooks/useCohortHandler';
import modifyEnv from '../../../../../../modifyEnv';
import SimpleModal from '../../../../../common/components/SimpleModal';
@@ -40,16 +40,31 @@ import { ORIGIN_HOST } from '../../../../../utils/variables';
import useSession from '../../../../../common/hooks/useSession';
import { log } from '../../../../../utils/logging';
-function Content() {
+function SyllabusContent() {
const { t, lang } = useTranslation('syllabus');
+ const router = useRouter();
+ const toast = useToast();
const BREATHECODE_HOST = modifyEnv({ queryString: 'host', env: process.env.BREATHECODE_HOST });
+ const { isOpen, onToggle } = useDisclosure();
const { user, isLoading } = useAuth();
- const { contextState, setContextState } = useModuleMap();
- const [currentTask, setCurrentTask] = useState(null);
+ const {
+ taskTodo,
+ cohortProgram,
+ setTaskTodo,
+ startDay,
+ updateAssignment,
+ setCurrentTask,
+ currentTask,
+ nextModule,
+ setNextModule,
+ prevModule,
+ setPrevModule,
+ } = useModuleHandler();
const { setUserSession } = useSession();
const [settingsOpen, setSettingsOpen] = useState(false);
const [modalSettingsOpen, setModalSettingsOpen] = useState(false);
- const { isOpen, onToggle } = useDisclosure();
+ const [modalIntroOpen, setModalIntroOpen] = useState(false);
+ const [solutionVideoOpen, setSolutionVideoOpen] = useState(false);
const [openNextPageModal, setOpenNextPageModal] = useState(false);
const [readme, setReadme] = useState(null);
const [ipynbHtmlUrl, setIpynbHtmlUrl] = useState(null);
@@ -57,8 +72,6 @@ function Content() {
const [extendedIsEnabled, setExtendedIsEnabled] = useState(false);
const [showPendingTasks, setShowPendingTasks] = useState(false);
const [currentSelectedModule, setCurrentSelectedModule] = useState(null);
- const [nextModule, setNextModule] = useState(null);
- const [prevModule, setPrevModule] = useState(null);
const [openNextModuleModal, setOpenNextModuleModal] = useState(false);
const [quizSlug, setQuizSlug] = useState(null);
const [showSolutionVideo, setShowSolutionVideo] = useState(false);
@@ -68,37 +81,31 @@ function Content() {
const [showModal, setShowModal] = useState(false);
const [readmeUrlPathname, setReadmeUrlPathname] = useState(null);
const [openTargetBlankModal, setOpenTargetBlankModal] = useState(null);
- const [currentAssetData, setCurrentAssetData] = useState(null);
const [currentBlankProps, setCurrentBlankProps] = useState(null);
const [fileData, setFileData] = useState(null);
const [clickedPage, setClickedPage] = useState({});
- const [currentData, setCurrentData] = useState({});
- const toast = useToast();
- const router = useRouter();
+ const [currentAsset, setCurrentAsset] = useState(null);
+ const [isLoadingRigobot, setIsLoadingRigobot] = useState(false);
const taskIsNotDone = currentTask && currentTask.task_status !== 'DONE';
const {
getCohortAssignments, getCohortData, prepareTasks, state,
} = useCohortHandler();
const { cohortSession, sortedAssignments } = state;
- const { featuredLight, fontColor, borderColor, featuredCard } = useStyle();
+ // const isAvailableAsSaas = false;
+ const isAvailableAsSaas = cohortSession?.available_as_saas;
- const profesionalRoles = ['TEACHER', 'ASSISTANT', 'REVIEWER'];
+ const { featuredLight, fontColor, borderColor, featuredCard, backgroundColor, backgroundColor4, hexColor, featuredColor, colorMode } = useStyle();
+
+ const professionalRoles = ['TEACHER', 'ASSISTANT', 'REVIEWER'];
const accessToken = isWindow ? localStorage.getItem('accessToken') : '';
- const commonBorderColor = useColorModeValue('#E2E8F0', '#718096');
- const commonFeaturedColors = useColorModeValue('featuredLight', 'featuredDark');
+ const commonBorderColor = useColorModeValue('gray.200', 'gray.500');
const Open = !isOpen;
const { label, teacherInstructions, keyConcepts } = selectedSyllabus;
- const filterEmptyModules = sortedAssignments.filter(
- (assignment) => assignment.modules.length > 0,
- );
-
- const currentTheme = useColorModeValue('light', 'dark');
-
const firstTask = nextModule?.modules[0];
- const lastPrevTask = prevModule?.modules[prevModule?.modules?.length - 1];
+ const lastPrevTask = prevModule?.modules && prevModule.modules[prevModule.modules.length - 1];
const cohortSlug = router?.query?.cohortSlug;
const lesson = router?.query?.lesson;
@@ -107,44 +114,37 @@ function Content() {
const language = router?.locale === 'en' ? 'us' : router?.locale;
const isQuiz = lesson === 'answer';
+ const isExercise = lesson === 'practice';
+ const isProject = lesson === 'project';
+ const isLesson = lesson === 'read';
- const filteredCurrentAssignments = filterEmptyModules.map((section) => {
- const currentAssignments = showPendingTasks
- ? section.filteredModulesByPending
- : section.filteredModules;
- return currentAssignments;
- });
+ const filteredCurrentAssignments = sortedAssignments.map((section) => (showPendingTasks
+ ? section.filteredModulesByPending
+ : section.filteredModules));
- const currentModuleIndex = filteredCurrentAssignments.findIndex((s) => {
- const currIndex = s?.some((l) => l.slug === lessonSlug);
- return currIndex;
- });
+ const currentModuleIndex = filteredCurrentAssignments.findIndex((s) => s?.some((l) => l.slug === lessonSlug || l.translations?.[language]?.slug === lessonSlug || (currentAsset?.id && l.translations?.[language]?.slug === currentAsset.slug)));
- const currentModule = filterEmptyModules[currentModuleIndex];
+ const currentModule = sortedAssignments[currentModuleIndex];
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
- const handleStartDay = () => {
- const updatedTasks = (nextModule.modules || [])?.map((l) => ({
+ const handleStartDay = async (module = null, avoidRedirect = false) => {
+ const moduleToUpdate = module?.modules || nextModule.modules;
+ const updatedTasks = moduleToUpdate?.map((l) => ({
...l,
associated_slug: l.slug,
cohort: cohortSession.id,
}));
const customHandler = () => {
- if (nextModule && cohortSlug && firstTask) {
+ if (moduleToUpdate && cohortSlug && firstTask && !avoidRedirect) {
router.push(`/syllabus/${cohortSlug}/${firstTask?.type?.toLowerCase()}/${firstTask?.slug}`);
}
};
if (user?.id) {
- startDay({
- t,
- id: user.id,
+ await startDay({
newTasks: updatedTasks,
- contextState,
- setContextState,
- toast,
customHandler,
});
}
@@ -155,7 +155,7 @@ function Content() {
cohortSlug,
});
getCohortAssignments({
- setContextState, cohort,
+ cohort,
});
};
@@ -170,82 +170,64 @@ 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));
}
}, [currentTask]);
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]
- && el.associated_slug === assetSlug));
+ const assetSlug = currentAsset?.translations?.us || currentAsset?.translations?.en || lessonSlug;
+ 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);
setModalSettingsOpen(false);
};
- const toggleSettings = async () => {
- const assetResp = await bc.lesson().getAsset(currentTask.associated_slug);
- if (assetResp.status < 400) {
- const assetData = await assetResp.data;
- setCurrentAssetData(assetData);
- if (openNextPageModal) {
- setModalSettingsOpen(!modalSettingsOpen);
- } else {
- setSettingsOpen(!settingsOpen);
- }
+ const toggleSettings = () => {
+ if (openNextPageModal) {
+ setModalSettingsOpen(!modalSettingsOpen);
+ } else {
+ setSettingsOpen(!settingsOpen);
}
};
const handleOpen = async (onOpen = () => { }) => {
if (currentTask && currentTask?.task_type === 'PROJECT' && currentTask.task_status === 'DONE') {
- const assetResp = await bc.lesson().getAsset(currentTask.associated_slug);
- if (assetResp?.status < 400) {
- const assetData = await assetResp.data;
- setCurrentAssetData(assetData);
-
- if (typeof assetData?.delivery_formats === 'string' && !assetData?.delivery_formats.includes('url')) {
- const fileResp = await bc.todo().getFile({ id: currentTask.id, academyId: cohortSession?.academy?.id });
- const respData = await fileResp.data;
- setFileData(respData);
- onOpen();
- } else {
- onOpen();
- }
- } else {
- onOpen();
+ if (typeof currentAsset?.delivery_formats === 'string' && !currentAsset?.delivery_formats.includes('url')) {
+ const fileResp = await bc.todo().getFile({ id: currentTask.id, academyId: cohortSession?.academy?.id });
+ const respData = await fileResp.data;
+ setFileData(respData);
}
+ onOpen();
}
};
const changeStatusAssignment = async (event, task, taskStatus) => {
event.preventDefault();
await updateAssignment({
- t, task, taskStatus, closeSettings, toast, contextState, setContextState,
+ task, taskStatus, closeSettings,
});
};
const sendProject = async ({ task, githubUrl, taskStatus }) => {
setShowModal(true);
await updateAssignment({
- t, task, closeSettings, toast, githubUrl, taskStatus, contextState, setContextState,
+ task, closeSettings, githubUrl, taskStatus,
});
};
const cleanCurrentData = () => {
setShowModal(false);
- setCurrentData({});
+ setCurrentAsset(null);
setCurrentSelectedModule(null);
setCallToActionProps({});
setReadme(null);
@@ -263,13 +245,13 @@ function Content() {
setReadme({
content: t('no-content-found-description'),
});
- setCurrentData({
+ setCurrentAsset({
title: t('no-content-found'),
});
};
useEffect(() => {
- const currTask = filterEmptyModules[currentModuleIndex]?.modules?.find((l) => l.slug === lessonSlug);
+ const currTask = sortedAssignments[currentModuleIndex]?.modules?.find((l) => l.slug === lessonSlug);
const englishTaskUrls = {
en: currTask?.translations?.en,
us: currTask?.translations?.us,
@@ -301,7 +283,7 @@ function Content() {
let currentTranslationSlug = data?.lang === language ? data?.slug : data.translations[language];
if (isIpynb) {
setIpynbHtmlUrl(`${BREATHECODE_HOST}/v1/registry/asset/preview/${currentSlug}?plain=true`);
- setCurrentData(data);
+ setCurrentAsset(data);
} else {
setIpynbHtmlUrl(null);
if (currentTranslationSlug === undefined) {
@@ -324,14 +306,14 @@ function Content() {
// Binary base64 decoding ⇢ UTF-8
const markdown = getMarkDownContent(markdownData);
setReadme(markdown);
- setCurrentData(currData);
+ setCurrentAsset(currData);
}
})
.catch(() => {
setReadme({
content: t('no-traduction-found-description'),
});
- setCurrentData({
+ setCurrentAsset({
...data,
title: t('no-traduction-found'),
});
@@ -377,13 +359,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',
@@ -394,7 +373,7 @@ function Content() {
},
] : [];
- const videoTutorial = currentData?.solution_video_url ? [{
+ const videoTutorial = currentAsset?.solution_video_url ? [{
icon: 'youtube',
slug: 'video-player',
title: 'Video tutorial',
@@ -406,9 +385,11 @@ function Content() {
const previousAssignment = filteredCurrentAssignments.map((section) => {
const currentIndex = section.findIndex((l) => l.slug === lessonSlug);
const prevIndex = currentIndex - 1;
+
if (prevIndex >= 0) {
return section[prevIndex];
}
+
return null;
})[currentModuleIndex];
@@ -444,9 +425,9 @@ function Content() {
},
});
}
- } else if (!!nextModule) {
+ } else if (nextModule) {
if (firstTask.target !== 'blank') {
- if (cohortSlug && !!firstTask && !!nextModule?.filteredModules[0]) {
+ if (cohortSlug && !!firstTask && nextModule?.filteredModules[0]) {
router.push({
query: {
cohortSlug,
@@ -492,7 +473,7 @@ function Content() {
},
});
}
- } else if (!!prevModule) {
+ } else if (prevModule) {
if (lastPrevTask.target !== 'blank') {
if (cohortSlug && !!lastPrevTask) {
router.push({
@@ -505,7 +486,7 @@ function Content() {
}
} else {
setCurrentBlankProps(lastPrevTask);
- setCurrentData(lastPrevTask);
+ setCurrentAsset(lastPrevTask);
router.push({
query: {
cohortSlug,
@@ -517,6 +498,45 @@ 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 (nextAssignment?.target === 'blank') {
+ setCurrentBlankProps(nextAssignment);
+ router.push({
+ query: {
+ cohortSlug,
+ lesson: nextAssignment?.type?.toLowerCase(),
+ lessonSlug: nextAssignment?.slug,
+ },
+ });
+ } else {
+ setCurrentBlankProps(null);
+ handleNextPage();
+ }
+ } 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`}`,
@@ -524,15 +544,12 @@ function Content() {
answer: 'https://assessment.4geeks.com/quiz',
};
const shareLink = currentTask ? `${pathConnector[lesson]}/${currentTask.associated_slug}` : '';
- const shareSocialMessage = {
- en: `I just finished coding ${currentTask?.title} at 4geeks.com`,
- es: `Acabo de terminar de programar ${currentTask?.title} en 4geeks.com`,
- };
+
const socials = [
{
name: 'twitter',
label: 'Twitter',
- href: `https://twitter.com/share?url=&text=${encodeURIComponent(shareSocialMessage[router.locale])} %23100DaysOfCode%0A%0A${shareLink}`,
+ href: `https://twitter.com/share?url=&text=${encodeURIComponent(t('share-social-message', { title: currentTask?.title }))} %23100DaysOfCode%0A%0A${shareLink}`,
color: '#1DA1F2',
},
{
@@ -544,505 +561,755 @@ function Content() {
},
];
- const url = currentData?.readme_url || currentData?.url;
+ const url = currentAsset?.readme_url || currentAsset?.url;
const repoUrl = (ipynbHtmlUrl && url) ? `${url.replace('.inpynb', `${router.locale === 'en' ? '' : `.${router.locale}`}.inpynb`)}` : url;
const inputModalLink = currentBlankProps && currentBlankProps.target === 'blank' ? currentBlankProps.url : `${ORIGIN_HOST}/syllabus/${cohortSlug}/${nextAssignment?.type?.toLowerCase()}/${nextAssignment?.slug}`;
const cohortModule = sortedAssignments.find((module) => module?.id === cohortSession?.current_module);
+ const projectStyles = {
+ DONE: {
+ borderRadius: currentAsset?.delivery_formats !== 'no_delivery' ? '11px' : '0 0 11px 11px',
+ pt: '2rem !important',
+ },
+ PENDING: {
+ borderRadius: '0 0 11px 11px',
+ pt: '3rem !important',
+ },
+ };
+
+ const assetTypeStyles = {
+ answer: { padding: '0px', height: '100%', mb: '0px' },
+ read: {},
+ practice: {},
+ project: { ...projectStyles[currentTask?.task_status] },
+ };
+
+ const getStyles = () => {
+ if (!isAvailableAsSaas) return {};
+
+ return {
+ padding: { base: '0px 10px 0 10px', md: '0px 2rem 0 2rem' },
+ ...assetTypeStyles[lesson],
+ };
+ };
+
+ const openAiChat = async () => {
+ try {
+ setIsLoadingRigobot(true);
+ const [completionResp, tokenResp] = await Promise.all([
+ bc.todo().postCompletionJob(currentTask.id),
+ bc.auth().temporalToken(),
+ ]);
+
+ const completionId = completionResp.data.id;
+ const temporalToken = tokenResp.data.token;
+
+ const { data } = await bc.rigobot().meToken(temporalToken);
+ const rigobotToken = data.key;
+
+ const aiChat = `https://ai.4geeks.com/?token=${rigobotToken}&purpose=14&completion=${completionId}&action=generate`;
+
+ window.open(aiChat, '_blank');
+ } catch (e) {
+ console.log(e);
+ toast({
+ position: 'top',
+ title: t('alert-message:error-ai-chat'),
+ status: 'error',
+ duration: 5000,
+ isClosable: true,
+ });
+ } finally {
+ setIsLoadingRigobot(false);
+ }
+ };
+
return (
<>
- {currentData?.title || '4Geeks'}
+ {currentAsset?.title || '4Geeks'}
-
- setOpenTargetBlankModal(false)}
- title={t('dashboard:modules.target-blank-title')}
- isReadonly
- description={t('dashboard:modules.target-blank-msg', { title: clickedPage?.title || currentBlankProps?.title })}
- link={inputModalLink}
- handlerText={t('common:open')}
- closeText={t('common:close')}
- closeButtonVariant="outline"
- actionHandler={() => {
- setOpenTargetBlankModal(false);
- if (currentBlankProps && currentBlankProps.target === 'blank') {
- window.open(currentBlankProps.url, '_blank');
- }
- }}
- />
-
+
+ {!isAvailableAsSaas && (
+
+ )}
- {
- console.log('click');
- setExtendedIsEnabled(!extendedIsEnabled);
- if (extendedIsEnabled === false) {
- scrollTop();
- }
- },
- actionState: extendedIsEnabled,
- }}
- />
+ {isAvailableAsSaas ? (
+
+ ) : (
+ {
+ setExtendedIsEnabled(!extendedIsEnabled);
+ if (extendedIsEnabled === false) {
+ scrollTop();
+ }
+ },
+ actionState: extendedIsEnabled,
+ }}
+ />
+ )}
-
- {!isQuiz && currentData?.intro_video_url && (
+
+ {!isAvailableAsSaas && !isQuiz && currentAsset?.intro_video_url && (
)}
- {extendedInstructions !== null && (
- setExtendedIsEnabled(false)} padding="2rem 0 2rem 0" style={{ margin: '3rem 0' }}>
-
-
- {`${t('teacherSidebar.instructions')}:`}
-
- {sortedAssignments.length > 0 && (
-
+
+
+ {t(Open ? 'hide-menu' : 'show-menu')}
+
+
+ {(previousAssignment || !!prevModule) && (
+ 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}`,
- }))}
- />
+ onClick={prevPage}
+ >
+
+
+
+ {t('previous-page')}
+
)}
-
-
- {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}
-
+ {(nextAssignment || !!nextModule) && (
+
+ {t('next-page')}
+
+
+
+
+ )}
-
-
- )}
-
- {!isQuiz && currentData?.solution_video_url && showSolutionVideo && (
-
-
- Video Tutorial
-
-
)}
-
-
- {repoUrl && !isQuiz && (
-
+ ) : (
+
+ {isProject && isAvailableAsSaas && currentAsset?.id && (
+
+ )}
+
-
- {t('edit-page')}
-
- )}
-
- {ipynbHtmlUrl && currentData?.readme_url && (
-
- )}
-
- {ipynbHtmlUrl && readmeUrlPathname && (
-
-
- {t('open-google-collab')}
-
- )}
-
- {ipynbHtmlUrl && (
-
- )}
+ {isAvailableAsSaas && currentAsset?.interactive && !isQuiz && (
+
+ )}
- {isQuiz ? (
-
-
-
- ) : (
-
- {currentData?.solution_url && (
-
-
- {t('solution-message')}
- {' '}
-
- {t('click-to-review')}
-
-
-
- )}
- {currentData?.superseded_by?.slug && (
-
+
+ Video Tutorial
+
+
+
+ )}
+
+ {(!isAvailableAsSaas || ipynbHtmlUrl) && (
+
+ {repoUrl && !isQuiz && !isAvailableAsSaas && (
+
+
+ {t('edit-page')}
+
+ )}
+
+ {ipynbHtmlUrl && currentAsset?.readme_url && (
+
+ )}
+
+ {ipynbHtmlUrl && readmeUrlPathname && (
+
+
+ {t('open-google-collab')}
+
+ )}
+
+ )}
+ {ipynbHtmlUrl && (
+
+ )}
+
+ {isQuiz ? (
+
+
-
+ )}
+ setModalIntroOpen(false)}
+ >
+
+
+
+
+ setSolutionVideoOpen(false)}
+ >
+
+
+
+
+ setOpenNextPageModal(false)}>
+
+
+
+ {assetTypeValues[lesson]}
+
+
+
+
+ {t('ask-to-done', { taskType: assetTypeValues[lesson]?.toLowerCase() })}
+
+
+ {
+ handleNextPage();
+ setOpenNextPageModal(false);
+ }}
+ textTransform="uppercase"
+ fontSize="13px"
+ >
+ {t('mark-later')}
+
+ {
+ setShowModal(false);
+ if (nextAssignment?.target === 'blank') {
+ setTimeout(() => {
+ setCurrentBlankProps(nextAssignment);
+ setOpenTargetBlankModal(true);
+ }, 1200);
+ } else {
+ setTimeout(() => {
+ handleNextPage();
+ }, 1200);
+ }
+ setOpenNextPageModal(false);
+ }}
+ />
+
+
+
+
+
+ setOpenNextModuleModal(false)}>
+
+
+
+
+
+ {t('reached-the-end-of-the-module', { label, nextModuleLabel: nextModule?.label })}
+
+
+ {
+ setOpenNextModuleModal(false);
+ }}
+ textTransform="uppercase"
+ fontSize="13px"
+ >
+ {t('common:cancel')}
+
+ {
+ handleStartDay();
+ setOpenNextModuleModal(false);
+ }}
+ textTransform="uppercase"
+ fontSize="13px"
+ >
+ {t('start-next-module')}
+
+
+
+
+
+ setOpenTargetBlankModal(false)}
+ title={t('dashboard:modules.target-blank-title')}
+ isReadonly
+ description={t('dashboard:modules.target-blank-msg', { title: clickedPage?.title || currentBlankProps?.title })}
+ link={inputModalLink}
+ handlerText={t('common:open')}
+ closeText={t('common:close')}
+ closeButtonVariant="outline"
+ actionHandler={() => {
+ setOpenTargetBlankModal(false);
+ if (currentBlankProps && currentBlankProps.target === 'blank') {
+ window.open(currentBlankProps.url, '_blank');
+ }
+ }}
+ />
+ {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 && (
+
+ )}
+ {selectedSyllabus && defaultSelectedSyllabus?.id !== selectedSyllabus?.id && (
+
+ )}
+
+
+
+ {`${label} - `}
+ {t('teacherSidebar.module-duration', { duration: selectedSyllabus?.duration_in_days || currentModule?.duration_in_days || 1 })}
+
+
+ {teacherInstructions}
+
+
+
+
+ )}
>
);
}
-export default asPrivate(Content);
+export default asPrivate(SyllabusContent);
diff --git a/src/pages/workshops/[event_slug].jsx b/src/pages/workshops/[event_slug].jsx
index d5da6fbc0..e910cf61f 100644
--- a/src/pages/workshops/[event_slug].jsx
+++ b/src/pages/workshops/[event_slug].jsx
@@ -166,6 +166,7 @@ function Page({ eventData, asset }) {
const [dataToGetAccessModal, setDataToGetAccessModal] = useState({});
const [isFetchingDataForModal, setIsFetchingDataForModal] = useState(false);
const [noConsumablesFound, setNoConsumablesFound] = useState(false);
+ const [denyAccessToEvent, setDenyAccessToEvent] = useState(false);
const router = useRouter();
const { locale } = router;
@@ -350,9 +351,36 @@ function Page({ eventData, asset }) {
});
};
+ const sortByConsumptionAvailability = (consum) => consum.sort((a, b) => {
+ const balanceA = a?.balance?.unit;
+ const balanceB = b?.balance?.unit;
+
+ if (balanceA === -1 && balanceB !== -1) return -1;
+ if (balanceA !== -1 && balanceB === -1) return 1;
+
+ if (balanceA > 0 && balanceB <= 0) return -1;
+ if (balanceA <= 0 && balanceB > 0) return 1;
+
+ return 0;
+ });
+
+ const getSubscriptionForCurrentEvent = () => {
+ if (!subscriptions || !event?.event_type?.slug) return [];
+ const currentEventSlug = event.event_type.slug;
+
+ const filteredSubscriptions = subscriptions.filter((subscription) => {
+ const eventTypes = subscription.selected_event_type_set?.event_types || [];
+ return eventTypes.some((eventType) => eventType.slug === currentEventSlug);
+ });
+
+ return filteredSubscriptions;
+ };
+
const consumableEventList = consumables?.data?.event_type_sets || [];
- const currentConsumable = consumableEventList?.length > 0 ? consumableEventList?.find(
- (c) => subscriptions.some(
+ const availableConsumables = sortByConsumptionAvailability(consumableEventList);
+ const subscriptionsForCurrentEvent = getSubscriptionForCurrentEvent();
+ const currentConsumable = availableConsumables?.length > 0 ? availableConsumables?.find(
+ (c) => subscriptionsForCurrentEvent.some(
(s) => c?.slug.toLowerCase() === s?.selected_event_type_set?.slug.toLowerCase(),
),
) : {};
@@ -362,6 +390,11 @@ function Page({ eventData, asset }) {
const existsNoAvailableAsSaas = myCohorts.some((c) => c?.cohort?.available_as_saas === false);
const isFreeForConsumables = event?.free_for_all || finishedEvent || (event?.free_for_bootcamps === true && existsNoAvailableAsSaas);
+ useEffect(() => {
+ if (subscriptionsForCurrentEvent.length === 0) setDenyAccessToEvent(true);
+ else setDenyAccessToEvent(false);
+ }, [subscriptionsForCurrentEvent]);
+
const dynamicFormInfo = () => {
if (finishedEvent) {
return ({
@@ -374,7 +407,7 @@ function Page({ eventData, asset }) {
title: '',
childrenDescription: (
- {t('no-consumables.description')}
+ {!denyAccessToEvent ? t('no-consumables.description') : `${t('denny-access.description')} '${event?.event_type?.name}' ${t('denny-access.can-join')}`}
),
});
@@ -876,7 +909,7 @@ function Page({ eventData, asset }) {
{hasFetchedAndNoConsumablesToUse && (
- {t('no-consumables.description')}
+ {!denyAccessToEvent ? t('no-consumables.description') : `${t('denny-access.description')} '${event?.event_type?.name}' ${t('denny-access.can-join')}`}
)}
- {t('no-consumables.get-more-workshops')}
-
+ {denyAccessToEvent ? t('no-consumables.get-more-workshops') : t('denny-access.button')}
+ {!denyAccessToEvent && }
)}
@@ -1028,10 +1062,11 @@ function Page({ eventData, asset }) {
alignItems="center"
gridGap="10px"
width="100%"
+ isDisabled={denyAccessToEvent}
background={hexColor.greenLight}
>
- {t('no-consumables.get-more-workshops')}
-
+ {!denyAccessToEvent ? t('no-consumables.get-more-workshops') : t('denny-access.button')}
+ {!denyAccessToEvent && }
) : (
diff --git a/src/stories/Icons.stories.jsx b/src/stories/Icons.stories.jsx
index 5b77582b3..a56fcb2a0 100644
--- a/src/stories/Icons.stories.jsx
+++ b/src/stories/Icons.stories.jsx
@@ -55,7 +55,9 @@ export default {
};
const Component = (args) => {
- const iconDictSearched = iconDict.filter((icon) => icon?.toLowerCase().includes(args.icon?.toLowerCase()))
+ const iconDictSearched = iconDict.sort(
+ (a, b) => a.localeCompare(b),
+ ).filter((icon) => icon?.toLowerCase().includes(args.icon?.toLowerCase()))
return (
<>
diff --git a/src/stories/Onlyfor.stories.jsx b/src/stories/Onlyfor.stories.jsx
index 992b5624e..9e4c90a7d 100644
--- a/src/stories/Onlyfor.stories.jsx
+++ b/src/stories/Onlyfor.stories.jsx
@@ -65,7 +65,7 @@ Default.args = {
profile: {
permissionsSlug: ['join_cohort', 'join_mentorship'],
},
- cohortSession: {
+ cohort: {
academy: {
id: 4,
},
diff --git a/src/utils/index.js b/src/utils/index.js
index 21a17920e..088479a7a 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -32,24 +32,24 @@ const slugify = (str) => (typeof str === 'string' ? str
.replace(/^-+|-+$/g, '')
: '');
- const unSlugify = (str, capitalize = false) => (typeof str === 'string'
- ? str
- .replace(/-/g, ' ')
- .replace(
- /\w\S*/g,
- (txt) => {
- const firstLetter = capitalize ? txt.charAt(0).toUpperCase() : txt.charAt(0);
- return firstLetter + txt.substring(1).toLowerCase();
- },
- )
- : '');
+const unSlugify = (str, capitalize = false) => (typeof str === 'string'
+ ? str
+ .replace(/-/g, ' ')
+ .replace(
+ /\w\S*/g,
+ (txt) => {
+ const firstLetter = capitalize ? txt.charAt(0).toUpperCase() : txt.charAt(0);
+ return firstLetter + txt.substring(1).toLowerCase();
+ },
+ )
+ : '');
const unSlugifyCapitalize = (str) => (typeof str === 'string' ? str
.replace(/-/g, ' ')
.replace(
-/\w\S*/g,
- (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase(),
-)
+ /\w\S*/g,
+ (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase(),
+ )
: '');
function slugToTitle(slug) {
@@ -60,7 +60,7 @@ function slugToTitle(slug) {
return word.charAt(0) + word.slice(1);
},
).join(' ').replace(/([A-Z])/g, ' $1')
- .trim();
+ .trim();
}
const cleanQueryStrings = (url) => url.split('?')[0];
@@ -116,8 +116,8 @@ const devLog = (msg, ...params) => { // Relevant logs only in dev mode
const devLogTable = (msg, array) => { // Relevant table logs with title only in dev mode
if (isDevMode) {
console.group();
- console.log(`%c🛠️${msg}`, 'font-size: 14px');
- console.table(array);
+ console.log(`%c🛠️${msg}`, 'font-size: 14px');
+ console.table(array);
console.groupEnd();
}
};
@@ -127,19 +127,19 @@ const objectAreNotEqual = (t1, t2) => Object.keys(t1).map((l) => t1[l] === t2[l]
function removeURLParameter(url, parameter) {
const urlparts = url.split('?');
if (urlparts.length >= 2) {
- const prefix = `${encodeURIComponent(parameter)}=`;
- const pars = urlparts[1].split(/[&;]/g);
-
- // reverse iteration as may be destructive
- // eslint-disable-next-line no-plusplus
- for (let i = pars.length; i-- > 0;) {
- // idiom for string.startsWith
- if (pars[i].lastIndexOf(prefix, 0) !== -1) {
- pars.splice(i, 1);
- }
+ const prefix = `${encodeURIComponent(parameter)}=`;
+ const pars = urlparts[1].split(/[&;]/g);
+
+ // reverse iteration as may be destructive
+ // eslint-disable-next-line no-plusplus
+ for (let i = pars.length; i-- > 0;) {
+ // idiom for string.startsWith
+ if (pars[i].lastIndexOf(prefix, 0) !== -1) {
+ pars.splice(i, 1);
}
+ }
- return urlparts[0] + (pars.length > 0 ? `?${pars.join('&')}` : '');
+ return urlparts[0] + (pars.length > 0 ? `?${pars.join('&')}` : '');
}
return url;
}
@@ -297,7 +297,7 @@ const getQueryString = (key, def) => {
const createArray = (length) => Array.from({ length }, (_, i) => i);
const lengthOfString = (string) => (typeof string === 'string' ? string?.replaceAll(/\s/g, '').length : 0);
-const syncInterval = (callback = () => {}) => {
+const syncInterval = (callback = () => { }) => {
const now = new Date();
const secondsToNextMinute = 60 - now.getSeconds();
@@ -333,6 +333,16 @@ function calculateDifferenceDays(date) {
};
}
+const intervalToHours = (duration) => {
+ const hours = duration.years * 24 * 365 // Hours from years (assuming 365 days per year)
+ + duration.months * 24 * 30 // Hours from months (assuming 30 days per month)
+ + duration.days * 24 // Hours from days
+ + duration.hours
+ + duration.minutes / 60 // Convert minutes to hours
+ + duration.seconds / 3600;
+ return hours;
+};
+
function adjustNumberBeetwenMinMax({ number = 1, min = 1, max = 10 }) {
const range = max - min;
const overflow = (number - max) % range;
@@ -348,7 +358,7 @@ function adjustNumberBeetwenMinMax({ number = 1, min = 1, max = 10 }) {
function getDiscountedPrice({ numItems, maxItems, discountRatio, bundleSize, pricePerUnit, startDiscountFrom = 0 }) {
if (numItems > maxItems) {
- console.log('numItems cannot be greater than maxItems');
+ console.log('numItems cannot be greater than maxItems');
}
let totalDiscountRatio = 0;
@@ -357,12 +367,12 @@ function getDiscountedPrice({ numItems, maxItems, discountRatio, bundleSize, pri
const maxDiscount = 0.2;
for (let i = startDiscountFrom; i < Math.floor(numItems / bundleSize); i += 1) {
- totalDiscountRatio += currentDiscountRatio;
- currentDiscountRatio -= currentDiscountRatio * discountNerf;
+ totalDiscountRatio += currentDiscountRatio;
+ currentDiscountRatio -= currentDiscountRatio * discountNerf;
}
if (totalDiscountRatio > maxDiscount) {
- totalDiscountRatio = maxDiscount;
+ totalDiscountRatio = maxDiscount;
}
const amount = pricePerUnit * numItems;
@@ -402,11 +412,11 @@ function cleanObject(obj) {
function decodeBase64(encoded) {
// Decode from base64 and convert to UTF-8 and remove � characters if they exist
- const decoded = new TextDecoder('utf-8')
- .decode(Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0)))
- .replace(/�/g, '');
+ const decoded = new TextDecoder('utf-8')
+ .decode(Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0)))
+ .replace(/�/g, '');
- return decoded;
+ return decoded;
}
export {
@@ -417,7 +427,7 @@ export {
setStorageItem, toCapitalize, tokenExists, getTimeProps, formatBytes,
resizeAllMasonryItems, calcSVGViewBox, number2DIgits, getNextDateInMonths,
sortToNearestTodayDate, isNumber, isDateMoreThanAnyDaysAgo, getQueryString, isValidDate,
- createArray, url, lengthOfString, syncInterval, getBrowserSize, calculateDifferenceDays, capitalizeFirstLetter,
+ createArray, url, lengthOfString, syncInterval, getBrowserSize, calculateDifferenceDays, intervalToHours, capitalizeFirstLetter,
adjustNumberBeetwenMinMax, getDiscountedPrice, formatPrice, cleanObject, slugToTitle, decodeBase64,
removeSessionStorageItem,
};
diff --git a/src/utils/requests.js b/src/utils/requests.js
index 2b78d7b85..e8d3116db 100644
--- a/src/utils/requests.js
+++ b/src/utils/requests.js
@@ -113,9 +113,9 @@ const getAsset = async (type = '', extraQuerys = {}, category = '', onlyFirstFet
};
const qsRequest = parseQuerys({
- ...baseQuery,
limit,
offset,
+ ...baseQuery,
});
// let response = fetchWithEncoding(`${BREATHECODE_HOST}/v1/registry/asset${qsRequest}`, ['br', 'gzip', 'deflate']);
diff --git a/styles/globals.css b/styles/globals.css
index e3efed718..c80115047 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -75,6 +75,18 @@ featured {
border-radius: 4px;
}
+.react-player-border-radius {
+ border-radius: 11px;
+}
+
+.react-player-border-radius iframe {
+ border-radius: 11px;
+}
+
+.react-player-border-radius .react-player__preview {
+ border-radius: 11px;
+}
+
@keyframes bounce {
0% {
opacity: 1;
diff --git a/styles/markdown.css b/styles/markdown.css
index d60fc797f..cffa18998 100644
--- a/styles/markdown.css
+++ b/styles/markdown.css
@@ -99,7 +99,7 @@
.markdown-body h4, .markdown-body h5, .markdown-body h6 {
margin: 20px 0 10px;
padding: 0;
- font-weight: bold !important;
+ font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative;
diff --git a/styles/theme.js b/styles/theme.js
index ab3bfed75..04b046517 100644
--- a/styles/theme.js
+++ b/styles/theme.js
@@ -68,6 +68,9 @@ const theme = extendTheme({
700: '#2B6CB0',
800: '#2C5282',
900: '#01455E',
+ 1000: '#008DD2',
+ 1100: '#00ABEB',
+ 1200: '#2EC7FF',
},
green: {