From 9470cc7be68bc319b25c22d85de386edd7975817 Mon Sep 17 00:00:00 2001 From: ArielCalisaya Date: Tue, 26 Sep 2023 17:31:27 -0300 Subject: [PATCH 1/5] fix modal --- i18n.js | 2 +- src/pages/choose-program/index.jsx | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/i18n.js b/i18n.js index 0ac7cf45e..c5999548c 100644 --- a/i18n.js +++ b/i18n.js @@ -21,7 +21,7 @@ module.exports = { '/interactive-coding-tutorials': ['projects'], '/interactive-coding-tutorials/technology/[technology]': ['projects'], '/interactive-exercise/[slug]': ['exercises', 'workshops'], - '/choose-program': ['choose-program', 'profile'], + '/choose-program': ['choose-program', 'dashboard', 'profile'], '/syllabus/[cohortSlug]/[lesson]/[lessonSlug]': ['syllabus', 'dashboard', 'projects'], '/survey/[surveyId]': ['survey'], '/mentorship': ['mentorship'], diff --git a/src/pages/choose-program/index.jsx b/src/pages/choose-program/index.jsx index 09419a300..50ca2596a 100644 --- a/src/pages/choose-program/index.jsx +++ b/src/pages/choose-program/index.jsx @@ -237,6 +237,14 @@ function chooseProgram() { }); }, []); + useEffect(() => { + if (dataQuery?.date_joined) { + const cohortUserDaysCalculated = calculateDifferenceDays(dataQuery?.date_joined); + if (cohortUserDaysCalculated?.isRemainingToExpire === false && cohortUserDaysCalculated?.result <= 2) { + setWelcomeModal(true); + } + } + }, [dataQuery]); useEffect(() => { if (userID !== undefined) { setCohortSession({ @@ -244,13 +252,7 @@ function chooseProgram() { bc_id: userID, }); } - if (user?.id && !userLoading) { - const cohortUserDaysCalculated = calculateDifferenceDays(user?.date_joined); - if (cohortUserDaysCalculated?.isRemainingToExpire === false && cohortUserDaysCalculated?.result <= 2) { - setWelcomeModal(true); - } - ldClient?.identify({ kind: 'user', key: user?.id, @@ -340,13 +342,13 @@ function chooseProgram() { maxWidth="45rem" borderRadius="13px" headerStyles={{ textAlign: 'center' }} - title={t('welcome-modal.title')} + title={t('dashboard:welcome-modal.title')} bodyStyles={{ padding: 0 }} closeOnOverlayClick={false} > - {t('welcome-modal.description')} + {t('dashboard:welcome-modal.description')} From 25dd5a01f38456cfabffedc9d1adfb59e2376f5f Mon Sep 17 00:00:00 2001 From: Alejandro Sanchez Date: Tue, 3 Oct 2023 19:30:02 -0400 Subject: [PATCH 2/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbb920e0c..26a15e69b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 4Geeks Developers Community -## Getting Started +## Getting Started. 1. Install Bun: From 396fdda7d5675cd5f78cf8e77546b92dd97a8ad6 Mon Sep 17 00:00:00 2001 From: ArielCalisaya Date: Wed, 4 Oct 2023 17:47:21 -0300 Subject: [PATCH 3/5] updated redirect link for private pages --- src/common/context/PrivateRouteWrapper.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/context/PrivateRouteWrapper.jsx b/src/common/context/PrivateRouteWrapper.jsx index 7c3ae3518..b5c57896c 100644 --- a/src/common/context/PrivateRouteWrapper.jsx +++ b/src/common/context/PrivateRouteWrapper.jsx @@ -7,6 +7,7 @@ export const withGuard = (PassedComponent) => { const { isAuthenticated, isLoading } = useAuth(); const isNotAuthenticated = !isLoading && isWindow && !isAuthenticated; const tokenExists = isWindow && localStorage.getItem('accessToken'); + const pageToRedirect = '/checkout'; const query = isWindow && new URLSearchParams(window.location.search || ''); const queryToken = isWindow && query.get('token')?.split('?')[0]; @@ -20,7 +21,7 @@ export const withGuard = (PassedComponent) => { if (typeof window !== 'undefined') { setStorageItem('redirect', window.location.pathname); } - window.location.href = '/login'; + window.location.href = pageToRedirect; }, 150); }; @@ -31,7 +32,7 @@ export const withGuard = (PassedComponent) => { } else { localStorage.setItem('redirect', pathname); } - window.location.href = '/login'; + window.location.href = pageToRedirect; } if (queryTokenExists && isWindow) { localStorage.setItem('accessToken', queryToken); From 0c7761155aa471ab58ce8b7fd1a61ce4db112941 Mon Sep 17 00:00:00 2001 From: ArielCalisaya Date: Thu, 5 Oct 2023 12:30:58 -0300 Subject: [PATCH 4/5] updates for /join/cohort/ page --- public/locales/en/dashboard.json | 10 ++- public/locales/es/dashboard.json | 10 ++- src/common/components/CallToAction.jsx | 12 ++- src/common/components/LiveEvent/index.jsx | 2 +- src/common/handlers/cohorts.js | 2 +- src/common/handlers/subscriptions.js | 29 ++++++ src/js_modules/moduleMap/module.jsx | 25 +----- src/pages/join/cohort/[id].jsx | 103 ++++++++++++++++++---- 8 files changed, 148 insertions(+), 45 deletions(-) diff --git a/public/locales/en/dashboard.json b/public/locales/en/dashboard.json index 856686082..39c13e1f6 100644 --- a/public/locales/en/dashboard.json +++ b/public/locales/en/dashboard.json @@ -9,8 +9,14 @@ "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!", "intro-video-title": "Welcome to coding introduction", - "join-next-cohort": "Join next cohort", - "join-cohort": "Join to {{cohortTitle}}", + "join-cohort-page": { + "seo-title": "Join to {{cohortTitle}}", + "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 it with your current plan.", + "cta-button": "Review plan", + "join-next-cohort":"Join next cohort" + }, + "already-have-this-cohort": "Already have this cohort", "modules": { "read": "Read", "start": "Start", diff --git a/public/locales/es/dashboard.json b/public/locales/es/dashboard.json index c49d582a9..4198cb1a1 100644 --- a/public/locales/es/dashboard.json +++ b/public/locales/es/dashboard.json @@ -9,8 +9,14 @@ "whiteLabeledText": "Este curso es traído a ti gracias a nuestra alianza con esta universidad.", "free-trial-msg": "Actualmente se encuentra en una prueba gratuita, algunas funciones pueden ser limitadas. ¡Actualiza tu plan para tener acceso ilimitado!", "intro-video-title": "Bienvenido a la introducción de codificación", - "join-next-cohort": "Únete a la cohorte", - "join-cohort": "Únete a {{cohortTitle}}", + "already-have-this-cohort": "Ya perteneces a esta cohorte", + "join-cohort-page": { + "seo-title": "Únete a {{cohortTitle}}", + "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 con tu plan actual.", + "cta-button": "Revisar plan", + "join-next-cohort":"Únete a la cohorte" + }, "modules": { "read": "Leer", "start": "Iniciar", diff --git a/src/common/components/CallToAction.jsx b/src/common/components/CallToAction.jsx index 5f7afaf03..1bf68db65 100644 --- a/src/common/components/CallToAction.jsx +++ b/src/common/components/CallToAction.jsx @@ -7,7 +7,8 @@ import Text from './Text'; function CallToAction({ background, imageSrc, href, styleContainer, isExternalLink, title, text, - buttonText, width, onClick, margin, buttonsData, buttonStyle, + buttonText, width, onClick, margin, buttonsData, buttonStyle, fontSizeOfTitle, + isLoading, }) { return ( {title && ( - + {title} )} @@ -70,7 +71,7 @@ function CallToAction({ gridTemplateColumns={{ base: 'repeat(auto-fill, minmax(10rem, 1fr))', md: '' }} > {buttonText && ( - )} @@ -81,6 +82,7 @@ function CallToAction({ key={element.text} as="a" href={element.href} + isLoading={isLoading} target={element.isExternalLink ? '_blank' : '_self'} onClick={onClick} marginY="auto" @@ -113,6 +115,8 @@ CallToAction.propTypes = { margin: PropTypes.string, onClick: PropTypes.func, buttonsData: PropTypes.arrayOf(PropTypes.objectOf(PropTypes.oneOfType([PropTypes.any]))), + fontSizeOfTitle: PropTypes.string, + isLoading: PropTypes.bool, }; CallToAction.defaultProps = { @@ -129,6 +133,8 @@ CallToAction.defaultProps = { margin: '0 auto', onClick: () => {}, buttonsData: [], + fontSizeOfTitle: 'var(--heading-xsm)', + isLoading: false, }; export default CallToAction; diff --git a/src/common/components/LiveEvent/index.jsx b/src/common/components/LiveEvent/index.jsx index 3380a2f58..ddac1fe0d 100644 --- a/src/common/components/LiveEvent/index.jsx +++ b/src/common/components/LiveEvent/index.jsx @@ -225,7 +225,7 @@ function LiveEvent({ > {existsWhiteLabel ? ( } Returns a cohort found */ -export const getCohort = (id) => bc.admissions().cohorts() +export const getCohort = (id) => bc.admissions({ id }).cohorts() .then((resp) => { const cohortFinded = resp.data.find((cohort) => cohort?.id === id); return cohortFinded; diff --git a/src/common/handlers/subscriptions.js b/src/common/handlers/subscriptions.js index 4919b2991..002222309 100644 --- a/src/common/handlers/subscriptions.js +++ b/src/common/handlers/subscriptions.js @@ -2,6 +2,14 @@ import { slugToTitle, unSlugifyCapitalize } from '../../utils'; import { BASE_PLAN } from '../../utils/variables'; import bc from '../services/breathecode'; +export const SUBS_STATUS = { + ACTIVE: 'ACTIVE', + FREE_TRIAL: 'FREE_TRIAL', + FULLY_PAID: 'FULLY_PAID', + CANCELLED: 'CANCELLED', + PAYMENT_ISSUE: 'PAYMENT_ISSUE', +}; + /** * Get translations for plan content. * @@ -354,3 +362,24 @@ export const getSubscriptions = () => bc.payment({ return allPlans; }); + +/** + * This function requires the user to be logged in. + * + * @returns {Promise} // List of subscriptions (Plan financing and subscriptions) + */ +export const getAllMySubscriptions = async () => { + try { + const resp = await bc.payment().subscriptions(); + const data = resp?.data; + + const planFinancings = data?.plan_financings?.length > 0 ? data?.plan_financings : []; + const subscriptions = data?.subscriptions?.length > 0 ? data?.subscriptions : []; + const allSubscriptions = [...planFinancings, ...subscriptions]; + + return allSubscriptions; + } catch (error) { + console.error(error); + return []; + } +}; diff --git a/src/js_modules/moduleMap/module.jsx b/src/js_modules/moduleMap/module.jsx index c78e074b6..dbb3b9645 100644 --- a/src/js_modules/moduleMap/module.jsx +++ b/src/js_modules/moduleMap/module.jsx @@ -161,27 +161,7 @@ function Module({ const langLink = lang !== 'en' ? `/${lang}` : ''; const taskTranslations = lang === 'en' ? (data?.translations?.en || data?.translations?.us) : (data?.translations?.[lang] || {}); - const generatePublicLinks = () => { - const taskSlug = taskTranslations?.slug || undefined; - - if (data?.translations && taskSlug) { - if (data.task_type === 'LESSON') { - return `${langLink}/lesson/${taskSlug}`; - } - if (data.task_type === 'EXERCISE') { - return `${langLink}/interactive-exercise/${taskSlug}`; - } - if (data.task_type === 'PROJECT') { - return `${langLink}/interactive-coding-tutorial/${taskSlug}`; - } - if (data.task_type === 'QUIZ') { - return '#'; - } - } - return '#'; - }; - - const link = isDisabled ? generatePublicLinks() : `${langLink}/syllabus/${cohortSession.slug}/${data.type.toLowerCase()}/${taskTranslations?.slug || currentTask?.associated_slug}`; + const link = isDisabled ? '#' : `${langLink}/syllabus/${cohortSession.slug}/${data.type.toLowerCase()}/${taskTranslations?.slug || currentTask?.associated_slug}`; return ( <> { const t = await getT(locale, 'dashboard'); @@ -33,7 +37,7 @@ export const getServerSideProps = async ({ locale, query }) => { return { props: { seo: { - title: t('join-cohort', { cohortTitle: data.cohort?.name }), + title: data.cohort?.name ? t('join-cohort-page.seo-title', { cohortTitle: data.cohort?.name }) : '', }, id: idInt, syllabus: data.syllabus || null, @@ -45,11 +49,15 @@ export const getServerSideProps = async ({ locale, query }) => { function Page({ id, syllabus, cohort }) { const { disabledColor2, hexColor } = useStyle(); const { t, lang } = useTranslation('dashboard'); + const qsPlan = getQueryString('plan'); const { isAuthenticated, choose } = useAuth(); const [isFetching, setIsFetching] = useState(false); const [, setCohortSession] = usePersistent('cohortSession', {}); + const [relatedSubscription, setRelatedSubscription] = useState(null); + const [alreadyHaveCohort, setAlreadyHaveCohort] = useState(false); const toast = useToast(); const router = useRouter(); + const qsForPricing = parseQuerys({ plan: encodeURIComponent(qsPlan) }); const redirectTocohort = () => { const langLink = lang !== 'en' ? `/${lang}` : ''; @@ -70,7 +78,6 @@ function Page({ id, syllabus, cohort }) { }); router.push(cohortDashboardLink); }; - const joinCohort = () => { if (isAuthenticated) { setIsFetching(true); @@ -100,6 +107,7 @@ function Page({ id, syllabus, cohort }) { duration: 5000, isClosable: true, }); + router.push(`/pricing${qsForPricing}`); } }) .catch(() => {}) @@ -111,9 +119,55 @@ function Page({ id, syllabus, cohort }) { } }; + useEffect(() => { + if (isAuthenticated) { + getAllMySubscriptions().then((subscriptions) => { + const subscriptionRelatedToThisCohort = subscriptions?.length > 0 ? subscriptions?.find((sbs) => { + const isRelated = sbs?.selected_cohort_set?.cohorts.some((elmnt) => elmnt?.cohort?.id === cohort?.id); + return isRelated; + }) : null; + + setRelatedSubscription(subscriptionRelatedToThisCohort); + }); + + bc.admissions().me().then((resp) => { + const data = resp?.data; + const alreadyHaveThisCohort = data?.cohorts?.some((elmnt) => elmnt?.cohort?.id === cohort?.id); + + if (alreadyHaveThisCohort) { + toast({ + position: 'top', + title: t('already-have-this-cohort'), + status: 'info', + duration: 5000, + }); + setAlreadyHaveCohort(true); + redirectTocohort(); + } + }); + } + }, [isAuthenticated]); + + useEffect(() => { + if (cohort?.id === null || cohort?.id === undefined) { + toast({ + position: 'top', + title: t('join-cohort-page.cta-cohort-not-found'), + status: 'error', + duration: 5000, + }); + router.push(`/pricing${qsForPricing}`); + } + }, [cohort?.id]); + const techs = syllabus?.main_technologies?.split(',') || []; + const handleClick = (e) => { + if (alreadyHaveCohort) { + e.preventDefault(); + } + }; - return ( + return cohort?.id && ( )} - + + {relatedSubscription?.status === SUBS_STATUS.ACTIVE ? ( + + ) : ( + + )} {syllabus?.modules?.length > 0 && syllabus.modules.map((module) => ( From 82d3285267cf0c15c69771016c50b0f8e209cf70 Mon Sep 17 00:00:00 2001 From: ArielCalisaya Date: Thu, 5 Oct 2023 12:52:38 -0300 Subject: [PATCH 5/5] update about-us link in spanish version of footer --- public/locales/es/footer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/es/footer.json b/public/locales/es/footer.json index 6741b12bc..6dbc8fc25 100644 --- a/public/locales/es/footer.json +++ b/public/locales/es/footer.json @@ -16,7 +16,7 @@ "items":[ { "label": "NOSOTROS", - "href": "/about-us" + "href": "/es/sobre-nosotros" }, { "label":"CONTACTO",