Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consume code viewer #1796

Open
wants to merge 11 commits into
base: development
Choose a base branch
from
9 changes: 9 additions & 0 deletions public/locales/en/signup.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,14 @@
"resend": "Resend",
"mentorship-bundle-title": "Mentorship sessions bundle",
"event-bundle-title": "Event bundle",
"compilation-bundle-title": "Compilation bundle",
"upgrade-a-plan": "Upgrade a plan",
"take-me-there": "Take me there",
"consumables": {
"already-have-it": "Already have it",
"consumable-event-not-available": "This event is not available for your subscription",
"consumable-mentorship-not-available": "This mentorship is not available for your subscription",
"consumable-compilations-not-available": "Compilations are not available for your subscription",
"full-plan": "Full plan",
"basic-plan": "Basic plan",
"ran-out-of-events": "You ran out of events",
Expand All @@ -190,19 +192,26 @@
"ran-out-of-mentorships-text": "You can purchase a mentoring session bundle or purchase a plan here:",
"and-get-mentorship-access": "And get more mentorships",
"purchase-one-or-more-sessions": "Purchase one or more sessions",
"ran-out-of-compilations": "You ran out of compilations",
"ran-out-of-compilations-text": "You can purchase a compilation bundle or purchase a plan here:",
"and-get-compilations-access": "And get more compilations",
"purchase-one-or-more-compilations": "Purchase one or compilations",
"avoid-monthly-commitment": "Avoid monthly commitment",
"select-bundle": "Select your bundle:",
"change-my-selection": "Change my selection",
"purchase-completed-title": "You have successfully completed the purchase",
"you-have-received": "You have received",
"events": "events",
"sessions": "sessions",
"compilations": "compilations",
"back-to-dashboard": "Back to dashboard",
"confirm-purchase-connector": "You are about to buy:",
"qty-events-to-consume": "{{qty}} events to apply",
"qty-mentorship-sessions": "{{qty}} mentorship sessions",
"qty-compilations-to-consume": "{{qty}} compilations",
"price-event-per-qty": "{{price}} per event",
"price-mentorship-per-qty": "{{price}} per mentorship",
"price-compilation-per-qty": "{{price}} per compilation",
"confirm-purchase": "Confirm purchase"
},
"select-service-of-plan": {
Expand Down
9 changes: 9 additions & 0 deletions public/locales/es/signup.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,14 @@
"resend": "Reenviar",
"mentorship-bundle-title": "Paquete de sesiones de tutoría",
"event-bundle-title": "Paquete de acceso a eventos",
"compilation-bundle-title": "Paquete de acceso a compilaciones",
"upgrade-a-plan": "Mejora un plan",
"take-me-there": "Comprar plan",
"consumables": {
"already-have-it": "Ya lo tienes",
"consumable-event-not-available": "Este evento no está disponible para tu suscripción",
"consumable-mentorship-not-available": "Esta mentoría no está disponible para tu suscripción",
"consumable-compilations-not-available": "Las compilaciones no están disponibles para tu suscripción",
"full-plan": "Plan completo",
"basic-plan": "Plan básico",
"ran-out-of-events": "Te has quedado sin acceso a eventos",
Expand All @@ -190,19 +192,26 @@
"ran-out-of-mentorships-text": "Puedes comprar un paquete de sesiones de tutoría o comprar un plan aquí:",
"and-get-mentorship-access": "Y obtén mas tutorías",
"purchase-one-or-more-sessions": "Compra una o más sesiones",
"ran-out-of-compilations": "Te has quedado sin compilaciones",
"ran-out-of-compilations-text": "Puedes comprar un paquete de compilaciones o comprar un plan aquí:",
"and-get-compilations-access": "Y obtén mas compilaciones",
"purchase-one-or-more-compilations": "Compra una o más compilaciones",
"avoid-monthly-commitment": "Evita el compromiso mensual",
"select-bundle": "Selecciona un paquete:",
"change-my-selection": "Cambiar mi selección",
"purchase-completed-title": "Haz completado con éxito la compra",
"you-have-received": "Has recibido",
"events": "eventos",
"sessions": "sesiones",
"compilations": "compilaciones",
"back-to-dashboard": "Volver al dashboard",
"confirm-purchase-connector": "Estás a punto de comprar:",
"qty-events-to-consume": "{{qty}} eventos para aplicar",
"qty-mentorship-sessions": "{{qty}} sesiones de tutoría",
"qty-compilations-to-consume": "{{qty}} compilaciones",
"price-event-per-qty": "{{price}} por evento",
"price-mentorship-per-qty": "{{price}} por mentoría",
"price-compilation-per-qty": "{{price}} por compilación",
"confirm-purchase": "Confirmar compra"
},
"select-service-of-plan": {
Expand Down
48 changes: 47 additions & 1 deletion src/common/components/CodeViewer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ import Editor from '@monaco-editor/react';
import { setStorageItem, getStorageItem, isWindow } from '../../utils';
import { RIGOBOT_HOST, BREATHECODE_HOST } from '../../utils/variables';
import ModalInfo from '../../js_modules/moduleMap/modalInfo';
import useSubscriptionsHandler from '../store/actions/subscriptionAction';
import bc from '../services/breathecode';
import useAuth from '../hooks/useAuth';
import useStyle from '../hooks/useStyle';
import Text from './Text';
import Icon from './Icon';
import { validatePlanExistence } from '../handlers/subscriptions';
import ModalToGetAccess, { stageType } from './ModalToGetAccess';

const notExecutables = ['css', 'shell', 'windows', 'mac', 'linux'];

Expand Down Expand Up @@ -70,9 +74,18 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
const [initialTouchY, setInitialTouchY] = useState(null);
const [tabIndex, setTabIndex] = useState(0);
const [showModal, setShowModal] = useState(false);
const [showConsumablesModal, setShowConsumablesModal] = useState(false);
const [planData, setPlanData] = useState(null);
const [languages, setLanguages] = useState(languagesData);
const defaultPlan = process.env.BASE_PLAN || 'basic';

const { state } = useSubscriptionsHandler();
const { subscriptions } = state;

const allSubscriptions = (subscriptions?.subscriptions
&& subscriptions?.plan_financings
&& [...subscriptions.subscriptions, ...subscriptions.plan_financings]) || [];

const isCodeForPreview = languages.some(({ language }) => language.toLowerCase() === 'html');
const isNotExecutable = notExecutables.includes(languages[tabIndex]?.language);

Expand Down Expand Up @@ -130,6 +143,7 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
setLanguages(updatedLanguages);
};

// eslint-disable-next-line consistent-return
const run = async () => {
if (isCodeForPreview) showCodePreview();
else if (isAuthenticated || allowNotLogged) {
Expand All @@ -140,6 +154,30 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
currLanguage,
...languages.slice(tabIndex + 1),
]);
const res = await bc.payment().service().consumable();
const { data } = res;
const { voids } = data;
const aiConsumables = voids.find(({ slug }) => slug === 'ai-compilation');

if (!aiConsumables || aiConsumables.balance.unit <= 0) {
const planSlug = allSubscriptions[0].plans[0].slug;

const result = await validatePlanExistence(allSubscriptions, planSlug);

setPlanData({
...result,
consumableType: 'aiCompilation',
});

setLanguages([
...languages.slice(0, tabIndex),
{ ...currLanguage, running: false },
...languages.slice(tabIndex + 1),
]);

return setShowConsumablesModal(true);
}

const { code, language, path } = languages[tabIndex];

const rigobotToken = await getRigobotToken();
Expand Down Expand Up @@ -321,6 +359,14 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
))}
</TabPanels>
</Tabs>
<ModalToGetAccess
isOpen={showConsumablesModal}
stage={stageType.outOfConsumables}
externalData={planData}
onClose={() => {
setShowConsumablesModal(false);
}}
/>
<ModalInfo
isOpen={showModal}
onClose={() => setShowModal(false)}
Expand All @@ -346,7 +392,7 @@ function CodeViewer({ languagesData, allowNotLogged, fileContext, ...rest }) {
router.push('/login');
}}
actionHandler={() => {
setStorageItem('redirect', router?.asPath);
setStorageItem('redirected-from', router?.asPath);
router.push(`/checkout?internal_cta_placement=codeviewer&plan=${defaultPlan}`);
}}
handlerText={t('log-in-modal.signup')}
Expand Down
13 changes: 12 additions & 1 deletion src/common/components/MarkDownParser/ArticleMarkdown.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import useAuth from '../../hooks/useAuth';
import useModuleHandler from '../../hooks/useModuleHandler';
import useSubscriptionsHandler from '../../store/actions/subscriptionAction';
import Toc from './toc';
import ContentHeading from './ContentHeading';
import SubTasks from './SubTasks';
Expand All @@ -11,10 +13,19 @@ function ArticleMarkdown({
content, withToc, frontMatter, titleRightSide, currentTask, currentData,
showLineNumbers, showInlineLineNumbers, assetData, alerMessage, isGuidedExperience,
}) {
const { isAuthenticated } = useAuth();
const { subTasks } = useModuleHandler();

const assetType = currentData?.asset_type;

const { fetchSubscriptions } = useSubscriptionsHandler();

useEffect(() => {
if (isAuthenticated) {
fetchSubscriptions();
}
}, [isAuthenticated]);

return (
<>
{!isGuidedExperience && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ function MentoringConsumables({
setDataToGetAccessModal({
...data,
event: '',
consumableType: 'mentorship',
academyService,
});
setIsModalToGetAccessOpen(true);
Expand Down
120 changes: 52 additions & 68 deletions src/common/components/UpgradeForConsumableView.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
import PropTypes from 'prop-types';
import { Button, Flex } from '@chakra-ui/react';
import { useState } from 'react';
Expand All @@ -22,43 +23,48 @@ function UpgradeForConsumableView({ externalData }) {
const allSubscriptions = externalData?.allSubscriptions || [];
const event = externalData?.event || {};
const academyService = externalData?.academyService || {};
const isEventConsumable = event?.event_type?.slug;
const type = externalData?.consumableType;

const coincidencesOfServiceWithOtherSubscriptions = allSubscriptions.length > 0 ? allSubscriptions?.filter(
(s) => s?.selected_mentorship_service_set?.mentorship_services.some(
(service) => service.slug === academyService?.slug,
),
) : [];
const mentoryProps = coincidencesOfServiceWithOtherSubscriptions.map(
(subscription) => ({
mentorship_service_set_slug: subscription?.selected_mentorship_service_set.slug,
plan_slug: subscription?.plans?.[0]?.slug,
}),
);
const mentoryPropsToQueryString = {
mentorship_service_set: mentoryProps.map((p) => p.mentorship_service_set_slug).join(','),
plans: mentoryProps.map((p) => p.plan_slug).join(','),
selected_service: mentoryProps?.service?.slug,
};

const findedEventTypeOfPlanCoincidences = allSubscriptions.length > 0 ? allSubscriptions.filter(
(s) => s.selected_event_type_set?.event_types.some(
(ev) => ev?.slug === event?.event_type?.slug,
),
) : [];
const eventRelevantProps = findedEventTypeOfPlanCoincidences.map(
(subscription) => ({
event_type_set_slug: subscription?.selected_event_type_set.slug,
plan_slug: subscription?.plans?.[0]?.slug,
}),
);
const eventPropsToQueryString = {
event_type_set: eventRelevantProps.map((p) => p.event_type_set_slug).join(','),
plans: eventRelevantProps.map((p) => p.plan_slug).join(','),
const consumablesDictionary = {
event: {
title: t('consumables.ran-out-of-events'),
description: t('consumables.ran-out-of-events-text'),
access: t('consumables.and-get-event-access'),
notAvailable: t('consumables.consumable-event-not-available'),
purchase: t('consumables.purchase-one-or-more-events'),
subscriptionMatch: allSubscriptions.filter(
(s) => s.selected_event_type_set?.event_types.some(
(ev) => ev?.slug === event?.event_type?.slug,
),
),
getPath: (suscriptions) => `/checkout/event/${suscriptions.map((subscription) => subscription?.selected_event_type_set.slug).join(',')}`,
},
mentorship: {
title: t('consumables.ran-out-of-mentorships'),
description: t('consumables.ran-out-of-mentorships-text'),
access: t('consumables.and-get-mentorship-access'),
notAvailable: t('consumables.consumable-mentorship-not-available'),
purchase: t('consumables.purchase-one-or-more-sessions'),
subscriptionMatch: allSubscriptions?.filter(
(s) => s?.selected_mentorship_service_set?.mentorship_services.some(
(service) => service.slug === academyService?.slug,
),
),
getPath: (suscriptions) => `/checkout/mentorship/${suscriptions.map((subscription) => subscription?.selected_mentorship_service_set.slug).join(',')}`,
},
aiCompilation: {
title: t('consumables.ran-out-of-compilations'),
description: t('consumables.ran-out-of-compilations-text'),
access: t('consumables.and-get-compilations-access'),
notAvailable: t('consumables.consumable-compilations-not-available'),
purchase: t('consumables.purchase-one-or-more-compilations'),
subscriptionMatch: allSubscriptions,
getPath: () => '/checkout/compilation/ai-compilation',
},
};

const alreadySubscribedToAll = hasBasePlan && hasASuggestedPlan;
const noExistsConsumablesForUserSubscriptions = isEventConsumable ? findedEventTypeOfPlanCoincidences?.length === 0 : coincidencesOfServiceWithOtherSubscriptions?.length === 0;
const noExistsConsumablesForUserSubscriptions = consumablesDictionary[type]?.subscriptionMatch?.length === 0;

const handleGetConsumables = () => {
setIsValidating(true);
Expand All @@ -72,32 +78,22 @@ function UpgradeForConsumableView({ externalData }) {
}

if (selectedIndex === 1) {
if (isEventConsumable && findedEventTypeOfPlanCoincidences?.length > 0) {
setStorageItem('redirected-from', router?.asPath);
router.push({
pathname: `/checkout/event/${eventPropsToQueryString.event_type_set}`,
});
}
if (!isEventConsumable && coincidencesOfServiceWithOtherSubscriptions?.length > 0) {
setStorageItem('redirected-from', router?.asPath);
router.push({
pathname: `/checkout/mentorship/${mentoryPropsToQueryString.mentorship_service_set}`,
});
}
setStorageItem('redirected-from', router?.asPath);
const subscriptions = consumablesDictionary[type].subscriptionMatch;
const pathname = consumablesDictionary[type].getPath(subscriptions);
router.push({
pathname,
});
}
};

return (
<Flex flexDirection="column" gridGap="16px" padding="15px">
<Heading size="21px">
{isEventConsumable
? t('consumables.ran-out-of-events')
: t('consumables.ran-out-of-mentorships')}
{consumablesDictionary[type].title}
</Heading>
<Text size="14px" fontWeight={700}>
{isEventConsumable
? t('consumables.ran-out-of-events-text')
: t('consumables.ran-out-of-mentorships-text')}
{consumablesDictionary[type].description}
</Text>
<Flex flexDirection="column" gridGap="16px">
{!alreadySubscribedToAll && (basePlan || suggestedPlan) && (
Expand All @@ -118,9 +114,7 @@ function UpgradeForConsumableView({ externalData }) {
{t('upgrade-a-plan')}
</Text>
<Text size="12px" fontWeight={400} color="black">
{isEventConsumable
? t('consumables.and-get-event-access')
: t('consumables.and-get-mentorship-access')}
{consumablesDictionary[type].access}
</Text>
</Button>
)}
Expand All @@ -131,23 +125,13 @@ function UpgradeForConsumableView({ externalData }) {
Bundle
</Text>
{noExistsConsumablesForUserSubscriptions && (
<>
{isEventConsumable ? (
<Text size="11px">
{`(${t('consumables.consumable-event-not-available')})`}
</Text>
) : (
<Text size="11px">
{`(${t('consumables.consumable-mentorship-not-available')})`}
</Text>
)}
</>
<Text size="11px">
{`(${consumablesDictionary[type].notAvailable})`}
</Text>
)}
</Flex>
<Text size="12px" fontWeight={700} textTransform="uppercase" color={fontColor}>
{isEventConsumable
? t('consumables.purchase-one-or-more-events')
: t('consumables.purchase-one-or-more-sessions')}
{consumablesDictionary[type].purchase}
</Text>
<Text size="12px" fontWeight={400} color={fontColor}>
{t('consumables.avoid-monthly-commitment')}
Expand Down
4 changes: 2 additions & 2 deletions src/common/services/breathecode.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,9 @@ const breathecode = {
verifyCoupon: () => axios.get(`${url}/coupon${qs}`),
service: () => ({
consumable: () => axios.get(`${url}/me/service/consumable${qs}`),
// getAcademyService: (serviceSlug) => axios.get(`${url}/academy/academyservice/${serviceSlug}${qs}`),
getAcademyServiceBySlug: (serviceSlug) => breathecode.get(`${url}/academy/academyservice/${serviceSlug}${qs}`),
getAcademyService: () => breathecode.get(`${url}/academy/academyservice${qs}`),
payConsumable: (data) => axios.post(`${url}/consumable/checkout${qs}`, data),
payConsumable: (data) => breathecode.post(`${url}/consumable/checkout${qs}`, data),
}),
getEvent: (eventId) => axios.get(`${host}/events/academy/event/${eventId}${qs}`),
getAllEventTypeSets: () => axios.get(`${host}/payments/eventtypeset`),
Expand Down
Loading