Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b5ebadb
add conutry code when fetching the courses on the bootcamp page
gustavomm19 May 12, 2025
b446415
add country code to marketing courses endpoints
gustavomm19 May 12, 2025
2384457
Enhance phone number validation for Argentina and update regex for ph…
tommygonzaleza May 14, 2025
a268e02
Update PaymentInfo component to include academy ID when adding a card…
tommygonzaleza May 14, 2025
a691548
Refactor ShowPrices and CoursePage components to remove Spain-specifi…
tommygonzaleza May 15, 2025
9559adc
fix conflict
gustavomm19 May 15, 2025
28532d7
add academy parameter to card post
gustavomm19 May 15, 2025
df00d15
Update course.json localization for English and Spanish. Revised cour…
tommygonzaleza May 15, 2025
8a69190
Update course.json localization for English and Spanish. Revised cour…
tommygonzaleza May 15, 2025
a7c0a87
Update index.jsx
Charlytoc May 15, 2025
3c418ff
Create MermaidRenderer.jsx
Charlytoc May 15, 2025
d8f9ab6
Update package.json
Charlytoc May 15, 2025
2d72d61
Merge pull request #2018 from Charlytoc/main
tommygonzaleza May 15, 2025
5bf65bf
Update course.json localization for English and Spanish to reflect ne…
tommygonzaleza May 16, 2025
b5c03a0
Add course description rendering to CoursePage component
tommygonzaleza May 16, 2025
7cfd3a5
Refactor CourseContent component to accept course content text and de…
tommygonzaleza May 16, 2025
540e1ca
Remove quantity fields from course.json localization for Spanish, str…
tommygonzaleza May 16, 2025
4e9f5d6
Merge branch 'main' into country-code
tommygonzaleza May 16, 2025
c05257e
Merge pull request #2009 from gustavomm19/country-code
tommygonzaleza May 16, 2025
c312d2e
Update CoursePage to use courseData for fetching featured assets and …
tommygonzaleza May 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 256 additions & 3 deletions bun.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"js-md5": "^0.8.3",
"katex": "^0.16.8",
"markdown-to-jsx": "7.2.0",
"mermaid": "^11.6.0",
"next": "^13.4.1",
"next-redux-wrapper": "8.1.0",
"next-remove-imports": "^1.0.11",
Expand Down
6 changes: 3 additions & 3 deletions public/locales/en/course.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
},
"build-connector": {
"what-you-will": "Learn by building",
"description": "When learning to code at your own pace, having access to video tutorials, code examples, and continuous, real-time feedback is crucial to succeed. The tasks you'll tackle will closely resemble those encountered in a real-world tech position. Here's a glimpse of what you'll be working on:",
"description": "When learning at your own pace, having access to video tutorials, code examples, and continuous, real-time feedback is crucial to succeed. The tasks you'll tackle will closely resemble those encountered in a real-world tech position. Here's a glimpse of what you'll be working on:",
"build": "real-life projects"
},
"why-learn-4geeks-connector": {
Expand Down Expand Up @@ -394,9 +394,9 @@
},
"job-section": {
"image": "/static/images/happy-male-with-laptop.png",
"title": "Get prepared for a new career",
"title": "Get an exclusive 30% discount on Beyond the Resume",
"subtitle": "Improve your CV, portfolio, GitHub profile, and social media profiles.",
"description": "Get access to exercises that simulate real-life interviews curated from the companies actively hiring. At no additional cost to you and without deducting anything from the salary of the job offer you get.",
"description": "Earn an exclusive discount on Beyond the Resume after completing the course, a program designed to help you with career topics, from optimizing your professional profile to excelling in interviews.",
"button": "Join now!",
"button-link": "#pricing"
}
Expand Down
15 changes: 6 additions & 9 deletions public/locales/es/course.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"course-content-text": "Prepárate para obtener un trabajo con nuestro syllabus",
"course-content-text": "Prepárate para obtener un trabajo con nuestro curriculum",
"course-content-description": "Hemos ajustado nuestro contenido del curso con cientos de posiciones laborales para prepararte para una entrada exitosa en el mundo de la tecnología.",
"students-enrolled-count": "+{{count}} estudiantes inscritos",
"students-enrolled": "cientos de estudiantes inscritos",
Expand Down Expand Up @@ -110,7 +110,7 @@
},
"build-connector": {
"what-you-will": "Aprende construyendo",
"description": "Cuando aprendes a programar a tu propio ritmo, tener acceso a tutoriales en video, ejemplos de código y retroalimentación continua en tiempo real es crucial para tener éxito. Las tareas que abordarás se asemejarán estrechamente a las encontradas en una posición tecnológica del mundo real. Aquí tienes un vistazo de en qué estarás trabajando:",
"description": "Cuando aprendes a tu propio ritmo, tener acceso a tutoriales en video, ejemplos de código y retroalimentación continua en tiempo real es crucial para tener éxito. Las tareas que abordarás se asemejarán estrechamente a las encontradas en una posición tecnológica del mundo real. Aquí tienes un vistazo de en qué estarás trabajando:",
"build": "proyectos de la vida real",
"link": "#pricing"
},
Expand Down Expand Up @@ -329,19 +329,16 @@
{
"type": "lesson",
"title": "Lecturas",
"qty": 45,
"icon": "book"
},
{
"type": "exercise",
"title": "Ejercicios interactivos",
"qty": 584,
"icon": "strength"
},
{
"type": "project",
"title": "Proyectos interactivos",
"qty": 28,
"icon": "laptop-code"
},
{
Expand Down Expand Up @@ -385,7 +382,7 @@
"image": "/static/images/certificate-preview.webp",
"title": "Certifícate en tu tecnología favorita",
"description": "Tu certificado de 4Geeks abrirá puertas a oportunidades profesionales con nuestra extensa red de empresas en todo el mundo. Respaldado por instituciones internacionales, representa un estándar reconocido a nivel global, desbloqueando diversos caminos profesionales.",
"button": "Ver precios",
"button": "Empieza ahora",
"button-link": "#pricing"
},
"havent-decided": {
Expand All @@ -398,10 +395,10 @@
},
"job-section": {
"image": "/static/images/happy-male-with-laptop.png",
"title": "Prepárate para una carrera nueva",
"title": "Obtén un descuento exclusivo de 30% en Beyond the Resume",
"subtitle": "Mejora tu CV, portafolio, perfil de GitHub, y el perfil de tus redes sociales.",
"description": "Obtén acceso a ejercicios que simulan entrevistas de la vida real, obtenidas de empresas que están contratando activamente. Por ningún costo adicional para ti y sin quitarte nada del salario de la oferta de trabajo que obtengas.",
"button": "Únete ahora",
"description": "Obtén un descuento exclusivo en Beyond the Resume al completar el curso, un programa diseñado para ayudarte con temas de carrera, desde optimizar tu perfil profesional hasta destacar en entrevistas.",
"button": "Empieza ahora",
"button-link": "#pricing"
}
}
5 changes: 4 additions & 1 deletion src/components/Checkout/PaymentInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,10 @@ function PaymentInfo({ setShowPaymentDetails }) {
};

const handleSubmit = async (actions, values) => {
const resp = await bc.payment().addCard(values);
const resp = await bc.payment().addCard({
...values,
academy: selectedPlanCheckoutData?.owner?.id,
});
const data = await resp.json();
setIsSubmittingCard(false);

Expand Down
2 changes: 1 addition & 1 deletion src/components/Checkout/ServiceSummary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ function ServiceSummary({ service }) {
},
});
}
const resp = await bc.payment().addCard(values);
const resp = await bc.payment().addCard({ ...values, academy: service.academy.id });
const data = await resp.json();
setIsSubmittingCard(false);
if (resp.ok) {
Expand Down
12 changes: 7 additions & 5 deletions src/components/CourseContent.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import PropTypes from 'prop-types';
import { Flex } from '@chakra-ui/react';
import useTranslation from 'next-translate/useTranslation';
import Heading from './Heading';
import useStyle from '../hooks/useStyle';
import Text from './Text';
import AcordionList from './AcordionList';

function CourseContent({ data, ...rest }) {
function CourseContent({ data, courseContentText, courseContentDescription, ...rest }) {
const { hexColor } = useStyle();
const { t } = useTranslation('course');

return (
<Flex gridGap="12px" flexDirection="column">
<Heading as="h2" size={{ base: '20px', md: '34px' }}>
{t('course-content-text')}
{courseContentText}
</Heading>
<Text size={{ base: '16px', md: '18px' }} color={hexColor.fontColor2}>
{t('course-content-description')}
{courseContentDescription}
</Text>

<AcordionList defaultIndex={0} list={data} {...rest} />
Expand All @@ -25,8 +23,12 @@ function CourseContent({ data, ...rest }) {
}
CourseContent.propTypes = {
data: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object])),
courseContentText: PropTypes.string,
courseContentDescription: PropTypes.string,
};
CourseContent.defaultProps = {
data: {},
courseContentText: '',
courseContentDescription: '',
};
export default CourseContent;
8 changes: 8 additions & 0 deletions src/components/MarkDownParser/MDComponents/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import tomorrow from '../syntaxHighlighter/tomorrow';
import { slugify } from '../../../utils';
import Text from '../../Text';
import Image from '../../Image';
import MermaidRenderer from '../MermaidRenderer';

export function generateId(children) {
const text = children ? children
Expand Down Expand Up @@ -87,6 +88,13 @@ export function MDLink({ children, href }) {
export function Code({ inline, showLineNumbers, showInlineLineNumbers, className, children }) {
const match = /language-(\w+)/.exec(className || '');

console.log('LENGUAGE ', match);

if (match && match.includes('mermaid')) {
console.log(children, 'Children');
return <MermaidRenderer code={children[0]} />;
}

return !inline && match ? (
<SyntaxHighlighter
showLineNumbers={showLineNumbers}
Expand Down
17 changes: 17 additions & 0 deletions src/components/MarkDownParser/MermaidRenderer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect } from 'react';
import mermaid from 'mermaid';
import PropTypes from 'prop-types';

function MermaidRenderer({ code }) {
useEffect(() => {
mermaid.initialize({ startOnLoad: true });
mermaid.contentLoaded();
}, []);

return <div className="mermaid">{code}</div>;
}

MermaidRenderer.propTypes = {
code: PropTypes.string.isRequired,
};
export default MermaidRenderer;
8 changes: 5 additions & 3 deletions src/components/Navbar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { getAllMySubscriptions } from '../../handlers/subscriptions';

function Navbar({ translations, pageProps }) {
const [uniqueLanguages, setUniqueLanguages] = useState([]);
const { location } = useSession();
const { location, isLoadingLocation } = useSession();
const { isAuthenticated, isLoading, user, logout, cohorts } = useAuth();
const [navbarItems, setNavbarItems] = useState([]);
const [mktCourses, setMktCourses] = useState([]);
Expand Down Expand Up @@ -135,8 +135,10 @@ function Navbar({ translations, pageProps }) {
};

useEffect(() => {
fetchMktCourses();
}, [locale]);
if (!isLoadingLocation) {
fetchMktCourses();
}
}, [locale, isLoadingLocation]);

useEffect(() => {
if (pageProps?.existsWhiteLabel) {
Expand Down
41 changes: 29 additions & 12 deletions src/components/PhoneInput/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,15 @@ function PhoneInput({
const dropdownMenuRef = useRef();
const [validStatus, setValidStatus] = useState({ valid: true });
const regex = {
phone: /^(?!(\d{2,})\1+)(?!(\d+)\2{3,})(\+\d{1,3})?(\d{8,10})$/,
phone: /^(?!(\d{2,})\1+)(?!(\d+)\2{3,})(\+\d{1,3})?(\d{8,15})$/,
};
const highlightCountryIndex = 0;

const getCountryPhoneMask = () => {
if (selectedCountry.dialCode === '54') {
// Argentina needs a longer mask with parentheses around area code
return `+${selectedCountry.dialCode} (99) 9999 9999 9999`;
}
const getMask = countriesList.find(
(mask) => mask.iso === selectedCountry?.iso2?.toUpperCase(),
);
Expand Down Expand Up @@ -169,7 +173,7 @@ function PhoneInput({
const prefixCode = prefix + selectedCountry.dialCode;

// Gets the character of the formatted phone number
const formatOfCharacters = e.target.value.match(/[^A-Za-z0-9 ]/g);
const formatOfCharacters = e.target.value.match(/[^A-Za-z0-9 ]/g) || [];
const prefixLength = selectedCountry.isAreaCode
? prefixCode.length + formatOfCharacters.length
: prefixCode.length;
Expand All @@ -178,18 +182,37 @@ function PhoneInput({
const input = e.target.value.substr(prefixLength);
setPhoneNumber(prefixCode + input);

const cleanedPhoneInput = `+${(prefixCode + input).match(/\d+/g).join('')}`;
const cleanedPhoneInput = `+${(prefixCode + input).match(/\d+/g)?.join('') || ''}`;

isValid = required === false ? true : regex.phone.test(cleanedPhoneInput);
if (isValid !== validStatus) {
// Special handling for Argentina (country code 54)
if (selectedCountry.dialCode === '54') {
// Argentina phone numbers: +54 (area code) local number
const digitsOnly = cleanedPhoneInput.replace(/\D/g, '');
// Length limits: country code + area code + number
const MIN_LENGTH = 3; // At least country code + 1 digit
const MAX_LENGTH = 15; // Reasonable maximum for Argentine numbers
if (required === false) {
isValid = true;
} else if (digitsOnly.length <= 2) {
// Too short (just country code or less)
isValid = false;
} else if (digitsOnly.length > MAX_LENGTH) {
// Too long (unreasonable number)
isValid = false;
} else {
isValid = true;
}
} else {
isValid = required === false ? true : regex.phone.test(cleanedPhoneInput);
}
if (isValid !== validStatus.valid) {
setValidStatus({
valid: isValid,
msg: isValid ? 'Ok' : errorMsg,
});
}
setVal({
...formData,
// phone: { ...phoneFormValues, value: cleanedPhoneInput, valid: true },
phone: cleanedPhoneInput,
});
};
Expand Down Expand Up @@ -241,14 +264,8 @@ function PhoneInput({
value={phoneNumber}
type="phone"
id={id || 'phone'}
// mask="+1\(999) 999-9999"/
mask={getCountryPhoneMask()}
maskChar=""
// formatChars={{
// "9": "[0-9]",
// "a": "[A-Za-z]",
// "*": "[A-Za-z0-9]"
// }}
/>
<FormErrorMessage>{form.errors.phone}</FormErrorMessage>
</FormControl>
Expand Down
17 changes: 3 additions & 14 deletions src/components/ShowPrices.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import {
Box, Button, Flex, Grid,
} from '@chakra-ui/react';
import { useEffect, useState, useContext } from 'react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import useTranslation from 'next-translate/useTranslation';
import Heading from './Heading';
Expand All @@ -13,7 +13,6 @@ import useStyle from '../hooks/useStyle';
import Icon from './Icon';
import MktTechnologies from './MktTechnologies';
import { currenciesSymbols } from '../utils/variables';
import { SessionContext } from '../context/SessionContext';

function PlanButton({
plan,
Expand Down Expand Up @@ -62,8 +61,6 @@ function ShowPrices({
const router = useRouter();
const { applyDiscountCouponsToPlans, state } = useSignup();
const { selfAppliedCoupon } = state;
const { location } = useContext(SessionContext);
const isSpain = location?.country?.toLowerCase() === 'spain' || location?.country?.toLowerCase() === 'españa';

const tiersTypes = {
subscriptions: applyDiscountCouponsToPlans(list, selfAppliedCoupon) || data?.pricing.list || [],
Expand Down Expand Up @@ -93,14 +90,6 @@ function ShowPrices({
return '';
};

const getSpanishPrice = (plan, priceType) => {
if (plan.period === 'MONTH' && priceType === 'price') return '99.99€';
if (plan.period === 'MONTH' && priceType === 'lastPrice') return '199.99€';
if (plan.period === 'YEAR' && priceType === 'price') return '749.99€';
if (plan.period === 'YEAR' && priceType === 'lastPrice') return '1499.99€';
return '749.99€';
};

const getPlanLabel = (plan) => {
switch (plan.period) {
case 'YEAR':
Expand Down Expand Up @@ -310,7 +299,7 @@ function ShowPrices({
fontWeight="bold"
fontFamily="Space Grotesk Variable"
>
{isSpain ? getSpanishPrice(selectedPlan, 'price') : selectedPlan.priceText}
{selectedPlan.priceText}

{selectedPlan.period !== 'FINANCING' && selectedPlan.period !== 'ONE_TIME' && (
<Text as="span" style={{ fontSize: '12px', fontWeight: 'normal' }}>
Expand All @@ -321,7 +310,7 @@ function ShowPrices({
</Text>
<Flex gap="10px" alignItems="center" direction="column">
<Text as="span" fontSize="md" color="#01455E" textDecoration="line-through">
{isSpain ? getSpanishPrice(selectedPlan, 'lastPrice') : selectedPlan.lastPrice}
{selectedPlan.lastPrice}
</Text>
{selfAppliedCoupon && (
<Text as="span" fontSize="xs" color="#01455E">
Expand Down
Loading