diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index f6cecb798a..52e277b419 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -62,6 +62,7 @@ jobs: runs-on: ubuntu-20.04 needs: build-client strategy: + fail-fast: false matrix: browsers: [chrome, firefox, electron] node-version: [16.x] diff --git a/.github/workflows/node.js-tests-upcoming.yml b/.github/workflows/node.js-tests-upcoming.yml index 8d45e5ef7d..ca3254ae99 100644 --- a/.github/workflows/node.js-tests-upcoming.yml +++ b/.github/workflows/node.js-tests-upcoming.yml @@ -15,6 +15,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -47,6 +48,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] diff --git a/.github/workflows/node.js-tests.yml b/.github/workflows/node.js-tests.yml index 0e6aae4e76..9d6f8a81b4 100644 --- a/.github/workflows/node.js-tests.yml +++ b/.github/workflows/node.js-tests.yml @@ -10,6 +10,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -39,6 +40,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -70,6 +72,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] @@ -103,6 +106,7 @@ jobs: runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: node-version: [16.x] locale: [chinese, espanol] diff --git a/api-server/src/common/models/user.js b/api-server/src/common/models/user.js index d34f95e9d8..a3c545f087 100644 --- a/api-server/src/common/models/user.js +++ b/api-server/src/common/models/user.js @@ -55,7 +55,7 @@ function destroyAll(id, Model) { return Observable.fromNodeCallback(Model.destroyAll, Model)({ userId: id }); } -function ensureLowerCaseString(maybeString) { +export function ensureLowerCaseString(maybeString) { return (maybeString && maybeString.toLowerCase()) || ''; } diff --git a/api-server/src/server/boot/certificate.js b/api-server/src/server/boot/certificate.js index 1d5637ad43..dd9240dcad 100644 --- a/api-server/src/server/boot/certificate.js +++ b/api-server/src/server/boot/certificate.js @@ -20,6 +20,7 @@ import { reportError } from '../middlewares/sentry-error-handler.js'; import { getChallenges } from '../utils/get-curriculum'; import { ifNoUser401 } from '../utils/middleware'; import { observeQuery } from '../utils/rx'; +import { ensureLowerCaseString } from '../../common/models/user'; const { legacyFrontEndChallengeId, @@ -523,7 +524,7 @@ function createVerifyCanClaim(certTypeIds, app) { let certType = superBlockCertTypeMap[superBlock]; log(certType); - return findUserByUsername$(username, { + return findUserByUsername$(ensureLowerCaseString(username), { isFrontEndCert: true, isBackEndCert: true, isFullStackCert: true, @@ -544,15 +545,36 @@ function createVerifyCanClaim(certTypeIds, app) { isHonest: true, completedChallenges: true }).subscribe(user => { + if (!user) { + return res.status(404).json({ + message: { + type: 'info', + message: 'flash.username-not-found', + variables: { username } + } + }); + } + + if (!certTypeIds[certType]) { + return res.status(404).json({ + message: { + type: 'info', + // TODO: create a specific 'flash.cert-not-found' message + message: 'flash.could-not-find' + } + }); + } + return Observable.of(certTypeIds[certType]) .flatMap(challenge => { const certName = certTypeTitleMap[certType]; const { tests = [] } = challenge; - const { isHonest, completedChallenges } = user; - const isProjectsCompleted = canClaim(tests, completedChallenges); let result = 'incomplete-requirements'; let status = false; + const { isHonest, completedChallenges } = user; + const isProjectsCompleted = canClaim(tests, completedChallenges); + if (isHonest && isProjectsCompleted) { status = true; result = 'requirements-met'; diff --git a/client/gatsby-node.js b/client/gatsby-node.js index b7d96010ef..2177a095b5 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -20,13 +20,6 @@ const createByIdentityMap = { exports.onCreateNode = function onCreateNode({ node, actions, getNode }) { const { createNodeField } = actions; - if (node.internal.type === 'ChallengeNode') { - const { tests = [], block, dashedName, superBlock } = node; - const slug = `/learn/${superBlock}/${block}/${dashedName}`; - createNodeField({ node, name: 'slug', value: slug }); - createNodeField({ node, name: 'blockName', value: blockNameify(block) }); - createNodeField({ node, name: 'tests', value: tests }); - } if (node.internal.type === 'MarkdownRemark') { const slug = createFilePath({ node, getNode }); @@ -71,37 +64,47 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { graphql(` { allChallengeNode( - sort: { fields: [superOrder, order, challengeOrder] } + sort: { + fields: [ + challenge___superOrder + challenge___order + challenge___challengeOrder + ] + } ) { edges { node { - block - challengeType - fields { - slug + challenge { + block + certification + challengeType + fields { + slug + } + hasEditableBoundaries + id + order + required { + link + src + } + challengeOrder + challengeFiles { + name + ext + contents + head + tail + } + solutions { + contents + ext + } + superBlock + superOrder + template + usesMultifileEditor } - id - order - required { - link - src - } - challengeOrder - challengeFiles { - name - ext - contents - head - tail - } - solutions { - contents - ext - } - superBlock - superOrder - template - usesMultifileEditor } } } @@ -114,6 +117,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { component } frontmatter { + certification block superBlock title @@ -137,12 +141,22 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { ); const blocks = uniq( - result.data.allChallengeNode.edges.map(({ node: { block } }) => block) + result.data.allChallengeNode.edges.map( + ({ + node: { + challenge: { block } + } + }) => block + ) ).map(block => blockNameify(block)); const superBlocks = uniq( result.data.allChallengeNode.edges.map( - ({ node: { superBlock } }) => superBlock + ({ + node: { + challenge: { superBlock } + } + }) => superBlock ) ); @@ -171,6 +185,7 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { const pageBuilder = createByIdentityMap[nodeIdentity](createPage); pageBuilder(edge); } catch (e) { + console.log(e); console.log(` ident: ${nodeIdentity} does not belong to a function @@ -256,6 +271,9 @@ exports.createSchemaCustomization = ({ actions }) => { const { createTypes } = actions; const typeDefs = ` type ChallengeNode implements Node { + challenge: Challenge + } + type Challenge { challengeFiles: [FileContents] notes: String url: String diff --git a/client/i18n/locales/chinese-traditional/translations.json b/client/i18n/locales/chinese-traditional/translations.json index 4f129bcf41..1df1fa9e76 100644 --- a/client/i18n/locales/chinese-traditional/translations.json +++ b/client/i18n/locales/chinese-traditional/translations.json @@ -32,7 +32,6 @@ "profile": "個人資料", "news": "專欄", "donate": "捐款", - "support-our-nonprofit": "Support our non-profit", "update-settings": "更新我的賬號設置", "sign-me-out": "退出登錄 freeCodeCamp", "flag-user": "標記該用戶的賬號爲濫用", @@ -217,8 +216,7 @@ "tax-exempt-status": "freeCodeCamp 是捐助者支持的 501(c)(3) 條款下具有免稅資格的非營利性組織(稅號:82-0779546)。", "mission-statement": "我們的使命:幫助人們免費學習編程。我們通過創建成千上萬的視頻、文章和交互式編程課程——所有內容向公衆免費開放——來實現這一目標。學員在世界各地自發成立數千個 freeCodeCamp 學習小組。", "donation-initiatives": "所有給 freeCodeCamp 的捐款都將用於我們的教育項目,購買服務器和其他服務,以及聘用員工。", - "donate-text": "你可以", - "donate-link": "點擊此處免稅捐款", + "donate-text": "You can <1>make a tax-deductible donation here.", "trending-guides": "精選文章", "our-nonprofit": "關於我們", "links": { @@ -287,11 +285,16 @@ "info": "信息", "code": "編程", "tests": "測試", + "restart": "Restart", + "restart-step": "Restart Step", + "console": "Console", + "notes": "Notes", "preview": "預覽" }, "help-translate": "我們仍然在翻譯以下證書。", "help-translate-link": "幫助我們翻譯。", "season-greetings": "Season's Greetings to you and your family.", + "season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉", "if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.", "project-preview-title": "下面是你將構建的項目的預覽" }, @@ -459,7 +462,7 @@ "username-not-found": "我們未找到用戶名爲 \"{{username}}\" 的用戶", "add-name": "這個用戶需要在賬號中添加名字,以便其他人查看該用戶的認證。", "not-eligible": "這個用戶目前不符合 freeCodeCamp.org 認證的條件。", - "profile-private": "{{username}} 已將其作品集設置爲僅自己可見。用戶需要將作品集設置爲公開,其他人才能查看該用戶的認證。", + "profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.", "certs-private": "{{username}} 已將其作品集設置爲僅自己可見。用戶需要將認證設置爲公開,其他人才能查看該用戶的認證。", "not-honest": "{{username}} 未接受我們的《學術誠信條例》。", "user-not-certified": "用戶 {{username}} 似乎未獲得 {{cert}} 認證", diff --git a/client/i18n/locales/chinese/translations.json b/client/i18n/locales/chinese/translations.json index cca9d43ad6..90d9f198f6 100644 --- a/client/i18n/locales/chinese/translations.json +++ b/client/i18n/locales/chinese/translations.json @@ -32,7 +32,6 @@ "profile": "个人资料", "news": "专栏", "donate": "捐款", - "support-our-nonprofit": "Support our non-profit", "update-settings": "更新我的账号设置", "sign-me-out": "退出登录 freeCodeCamp", "flag-user": "标记该用户的账号为滥用", @@ -217,8 +216,7 @@ "tax-exempt-status": "freeCodeCamp 是捐助者支持的 501(c)(3) 条款下具有免税资格的非营利性组织(税号:82-0779546)。", "mission-statement": "我们的使命:帮助人们免费学习编程。我们通过创建成千上万的视频、文章和交互式编程课程——所有内容向公众免费开放——来实现这一目标。学员在世界各地自发成立数千个 freeCodeCamp 学习小组。", "donation-initiatives": "所有给 freeCodeCamp 的捐款都将用于我们的教育项目,购买服务器和其他服务,以及聘用员工。", - "donate-text": "你可以", - "donate-link": "点击此处免税捐款", + "donate-text": "You can <1>make a tax-deductible donation here.", "trending-guides": "精选文章", "our-nonprofit": "关于我们", "links": { @@ -287,11 +285,16 @@ "info": "信息", "code": "编程", "tests": "测试", + "restart": "Restart", + "restart-step": "Restart Step", + "console": "Console", + "notes": "Notes", "preview": "预览" }, "help-translate": "我们仍然在翻译以下证书。", "help-translate-link": "帮助我们翻译。", "season-greetings": "Season's Greetings to you and your family.", + "season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉", "if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.", "project-preview-title": "下面是你将构建的项目的预览" }, @@ -459,7 +462,7 @@ "username-not-found": "我们未找到用户名为 \"{{username}}\" 的用户", "add-name": "这个用户需要在账号中添加名字,以便其他人查看该用户的认证。", "not-eligible": "这个用户目前不符合 freeCodeCamp.org 认证的条件。", - "profile-private": "{{username}} 已将其作品集设置为仅自己可见。用户需要将作品集设置为公开,其他人才能查看该用户的认证。", + "profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.", "certs-private": "{{username}} 已将其作品集设置为仅自己可见。用户需要将认证设置为公开,其他人才能查看该用户的认证。", "not-honest": "{{username}} 未接受我们的《学术诚信条例》。", "user-not-certified": "用户 {{username}} 似乎未获得 {{cert}} 认证", diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index cbd7537890..18fb0632a8 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -63,94 +63,127 @@ "Time to put your newly learnt skills to work. By working on these projects, you will get a chance to apply all of the skills, principles, and concepts you have learned so far: HTML, CSS, Visual Design, Accessibility, and more.", "Complete the five web programming projects below to earn your Responsive Web Design certification." ] + } + } + }, + "responsive-web-design-22": { + "title": "Responsive Web Design", + "intro": [ + "In this Responsive Web Design Certification, you'll learn the languages that developers use to build webpages: HTML (Hypertext Markup Language) for content, and CSS (Cascading Style Sheets) for design.", + "First, you'll build a cat photo app to learn the basics of HTML and CSS. Later, you'll learn modern techniques like CSS variables by building a penguin, and best practices for accessibility by building a web form.", + "Finally, you'll learn how to make webpages that respond to different screen sizes by building a Twitter card with Flexbox, and a complex blog layout with CSS Grid." + ], + "note": "Note: Some browser extensions, such as ad-blockers and dark mode extensions can interfere with the tests. If you face issues, we recommend disabling extensions that modify the content or layout of pages, while taking the course.", + "blocks": { + "build-a-tribute-page-project": { + "title": "Build a Tribute Page Project", + "intro": ["placeholder", "holder of place"] }, - "learn-html-by-building-a-cat-photo-app": { - "title": "Learn HTML by Building a Cat Photo App", - "intro": [ - "", - "" - ] + "build-a-personal-portfolio-webpage-project": { + "title": "Build a Personal Portfolio Webpage Project", + "intro": ["placeholder", "holder of place"] }, - "learn-basic-css-by-building-a-cafe-menu": { - "title": "Learn Basic CSS by Building a Cafe Menu", - "intro": [ - "", - "" - ] + "build-a-product-landing-page-project": { + "title": "Build a Product Landing Page Project", + "intro": ["placeholder", "holder of place"] }, - "learn-the-css-box-model-by-building-a-rothko-painting": { - "title": "Learn the CSS Box Model by Building a Rothko Painting", - "intro": [ - "Mark Rothko was known for his abstract style of painting. Rothko paintings commonly depicted rectangular regions of color in varying sizes.", - "In this lesson, you will be using CSS to create your own painting in Rothko's style. You will use many of the skills you have already been practicing, as well as new CSS tool such as blur and transform." - ] + "build-a-survey-form-project": { + "title": "Build a Survey Form Project", + "intro": ["placeholder", "holder of place"] }, - "learn-css-variables-by-building-a-city-skyline": { - "title": "Learn CSS Variables by Building a City Skyline", - "intro": [ - "", - "" - ] + "build-a-technical-documentation-page-project": { + "title": "Build a Technical Documentation Page Project", + "intro": ["placeholder", "holder of place"] }, - "learn-html-forms-by-building-a-registration-form": { - "title": "Learn HTML Forms by Building a Registration Form", - "intro": [ - "", - "" - ] - }, - "learn-accessibility-by-building-a-quiz": { - "title": "Learn Accessibility by Building a Quiz", - "intro": [ - "", - "" - ] - }, - "learn-intermediate-css-by-building-a-picasso-painting": { - "title": "Learn Intermediate CSS by Building a Picasso Painting", - "intro": [ - "Pablo Picasso was known for his Cubism style of painting, a style recognized by representations of objects broken down and reassembled from multiple perspectives. Picasso's paintings are often highly abstract and thought provoking.", - "In this course, you will use CSS to create your own painting in the style of Picasso. You will learn about FontAwesome SVG icons, CSS positioning, and reinforce the skills you have already been learning." - ] - }, - "learn-responsive-web-design-by-building-a-piano": { - "title": "Learn Responsive Web Design by Building a Piano", - "intro": [ - "", - "" - ] - }, - "learn-css-flexbox-by-building-a-photo-gallery": { - "title": "Learn CSS Flexbox by Building a Photo Gallery", - "intro": [ - "", - "" - ] - }, - "learn-css-grid-by-building-a-magazine": { - "title": "Learn CSS Grid by Building a Magazine", - "intro": [ - "", - "" - ] - }, - "learn-typography-by-building-a-nutrition-label": { - "title": "Learn Typography by Building a Nutrition Label", - "intro": [ - "", - "" - ] - }, - "learn-css-transforms-by-building-a-penguin": { "title": "Learn CSS Transforms by Building a Penguin", "intro": ["", ""] }, - "learn-css-animation-by-building-a-ferris-wheel": { "title": "Learn CSS Animation by Building a Ferris Wheel", "intro": ["", ""] }, - "learn-more-about-css-pseudo-selectors-by-building-a-balance-sheet": { "title": "Learn More About CSS Pseudo Selectors By Building A Balance Sheet", "intro": ["", ""] }, "learn-css-colors-by-building-a-color-markers-set": { "title": "Learn CSS Colors by Building a Color Markers Set", "intro": [ "", "" ] - } + }, + "learn-html-by-building-a-cat-photo-app": { + "title": "Learn HTML by Building a Cat Photo App", + "intro": [ + "HTML tags give a webpage its structure. You can use HTML tags to add photos, buttons, and other elements to your webpage.", + "In this course, you'll learn the most common HTML tags by building your own cat photo app." + ] + }, + "learn-basic-css-by-building-a-cafe-menu": { + "title": "Learn Basic CSS by Building a Cafe Menu", + "intro": [ + "CSS tells the browser how to display your webpage. You can use CSS to set the color, font, size, and other aspects of HTML elements.", + "In this course, you'll learn CSS by designing a menu page for a cafe webpage." + ] + }, + "learn-the-css-box-model-by-building-a-rothko-painting": { + "title": "Learn the CSS Box Model by Building a Rothko Painting", + "intro": [ + "Every HTML element is its own box – with its own spacing and a border. This is called the Box Model.", + "In this course, you'll use CSS and the Box Model to create your own Rothko-style rectangular art pieces." + ] + }, + "learn-css-variables-by-building-a-city-skyline": { + "title": "Learn CSS Variables by Building a City Skyline", + "intro": [ + "CSS variables help you organize your styles and reuse them.", + "In this course, you'll build a city skyline. You'll learn how to configure CSS variables so you can reuse them whenever you want." + ] + }, + "learn-html-forms-by-building-a-registration-form": { + "title": "Learn HTML Forms by Building a Registration Form", + "intro": [ + "You can use HTML forms to collect information from people who visit your webpage.", + "In this course, you'll learn HTML forms by building a signup page. You'll learn how to control what types of data people can type into your form, and some new CSS tools for styling your page." + ] + }, + "learn-accessibility-by-building-a-quiz": { + "title": "Learn Accessibility by Building a Quiz", + "intro": [ + "Accessibility is making your webpage easy for all people to use – even people with disabilities.", + "In this course, you'll build a quiz webpage. You'll learn accessibility tools such as keyboard shortcuts, ARIA attributes, and design best practices." + ] + }, + "learn-intermediate-css-by-building-a-picasso-painting": { + "title": "Learn Intermediate CSS by Building a Picasso Painting", + "intro": [ + "In this course, you'll use learn some intermediate CSS techniques by coding your own Picasso painting webpage. You'll learn about SVG icons, CSS positioning, and review other CSS skills you've learned." + ] + }, + "learn-responsive-web-design-by-building-a-piano": { + "title": "Learn Responsive Web Design by Building a Piano", + "intro": [ + "Responsive Design tells your webpage how it should look on different-sized screens.", + "In this course, you'll use CSS and Responsive Design to code a piano. You'll also learn more about media queries and pseudo selectors." + ] + }, + "learn-css-flexbox-by-building-a-photo-gallery": { + "title": "Learn CSS Flexbox by Building a Photo Gallery", + "intro": [ + "Flexbox helps you design your webpage so that it looks good on any screen size.", + "In this course, you'll use Flexbox to build a responsive photo gallery webpage." + ] + }, + "learn-css-grid-by-building-a-magazine": { + "title": "Learn CSS Grid by Building a Magazine", + "intro": [ + "CSS Grid gives you control over the rows and columns of your webpage design.", + "In this course, you'll build a magazine article. You'll learn how to use CSS Grid, including concepts like grid rows and grid columns." + ] + }, + "learn-typography-by-building-a-nutrition-label": { + "title": "Learn Typography by Building a Nutrition Label", + "intro": [ + "Typography is the art of styling your text to be easily readable and suit its purpose.", + "In this course, you'll use typography to build a nutrition label webpage. You'll learn how to style text, adjust line height, and position your text using CSS." + ] + }, + "learn-css-transforms-by-building-a-penguin": { "title": "Learn CSS Transforms by Building a Penguin", "intro": ["", ""] }, + "learn-css-animation-by-building-a-ferris-wheel": { "title": "Learn CSS Animation by Building a Ferris Wheel", "intro": [ + "You can use CSS animation to draw attention to specific sections of your webpage and make it more engaging.", + "In this course, you'll build a Ferris wheel. You'll learn how to use CSS to animate elements, transform them, and adjust their speed." + ] }, + "learn-more-about-css-pseudo-selectors-by-building-a-balance-sheet": { "title": "Learn More About CSS Pseudo Selectors By Building A Balance Sheet", "intro": ["", ""] } } }, "javascript-algorithms-and-data-structures": { @@ -622,6 +655,10 @@ "courses": "Courses", "steps": "Steps", "expand": "Expand", - "collapse": "Collapse" + "collapse": "Collapse", + "expand": "Expand courses", + "collapse": "Collapse courses", + "legacy-header": "Legacy Courses", + "legacy-desc": "These courses are no longer part of the certification path, but are still available for you to further your learning." } } \ No newline at end of file diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 2a4ac07f1f..aeeb29790e 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -32,7 +32,6 @@ "profile": "Profile", "news": "News", "donate": "Donate", - "support-our-nonprofit": "Support our non-profit", "update-settings": "Update my account settings", "sign-me-out": "Sign me out of freeCodeCamp", "flag-user": "Flag This User's Account for Abuse", @@ -217,8 +216,7 @@ "tax-exempt-status": "freeCodeCamp is a donor-supported tax-exempt 501(c)(3) nonprofit organization (United States Federal Tax Identification Number: 82-0779546)", "mission-statement": "Our mission: to help people learn to code for free. We accomplish this by creating thousands of videos, articles, and interactive coding lessons - all freely available to the public. We also have thousands of freeCodeCamp study groups around the world.", "donation-initiatives": "Donations to freeCodeCamp go toward our education initiatives, and help pay for servers, services, and staff.", - "donate-text": "You can", - "donate-link": "make a tax-deductible donation here", + "donate-text": "You can <1>make a tax-deductible donation here.", "trending-guides": "Trending Guides", "our-nonprofit": "Our Nonprofit", "links": { @@ -464,7 +462,7 @@ "username-not-found": "We could not find a user with the username \"{{username}}\"", "add-name": "This user needs to add their name to their account in order for others to be able to view their certification.", "not-eligible": "This user is not eligible for freeCodeCamp.org certifications at this time.", - "profile-private": "{{username}} has chosen to make their portfolio private. They will need to make their portfolio public in order for others to be able to view their certification.", + "profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.", "certs-private": "{{username}} has chosen to make their certifications private. They will need to make their certifications public in order for others to be able to view them.", "not-honest": "{{username}} has not yet agreed to our Academic Honesty Pledge.", "user-not-certified": "It looks like user {{username}} is not {{cert}} certified", diff --git a/client/i18n/locales/espanol/translations.json b/client/i18n/locales/espanol/translations.json index b7129dac53..ef7895c85c 100644 --- a/client/i18n/locales/espanol/translations.json +++ b/client/i18n/locales/espanol/translations.json @@ -32,7 +32,6 @@ "profile": "Perfil", "news": "Noticias", "donate": "Donar", - "support-our-nonprofit": "Apoya a nuestra organización sin fines de lucro", "update-settings": "Actualizar los ajustes de mi cuenta", "sign-me-out": "Cerrar sesión en freeCodeCamp", "flag-user": "Marcar la cuenta de este usuario por abuso", @@ -217,8 +216,7 @@ "tax-exempt-status": "freeCodeCamp es una organización sin fines de lucro exenta de impuestos 501(c)(3) respaldada por donantes (Número de identificación fiscal federal de los Estados Unidos: 82-0779546)", "mission-statement": "Nuestra misión: ayudar a las personas a aprender a programar de forma gratuita. Logramos esto mediante la creación de miles de videos, artículos y lecciones de programación interactivas, todos disponibles gratuitamente para el público. También tenemos miles de grupos de estudio de FreeCodeCamp en todo el mundo.", "donation-initiatives": "Las donaciones a freeCodeCamp se destinan a nuestras iniciativas educativas y ayudan a pagar los servidores, los servicios y el personal.", - "donate-text": "Tú puedes", - "donate-link": "hacer una donación deducible de impuestos aquí", + "donate-text": "You can <1>make a tax-deductible donation here.", "trending-guides": "Guías de tendencias", "our-nonprofit": "Nuestra organización sin fines de lucro", "links": { @@ -287,11 +285,16 @@ "info": "Info", "code": "Código", "tests": "Pruebas", + "restart": "Restart", + "restart-step": "Restart Step", + "console": "Console", + "notes": "Notes", "preview": "Vista" }, "help-translate": "Todavía estamos traduciendo las siguientes certificaciones.", "help-translate-link": "Ayúdanos a traducir.", "season-greetings": "Deseamos felices festividades a ti y a tu familia.", + "season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉", "if-getting-value": "Si estás obteniendo mucho de freeCodeCamp, ahora es un buen momento para donar para apoyar nuestra misión sin fines de lucro.", "project-preview-title": "Aquí hay una vista previa de lo que construirás" }, @@ -459,7 +462,7 @@ "username-not-found": "No pudimos encontrar un usuario con el nombre de usuario \"{{username}}\"", "add-name": "Este usuario debe agregar su nombre a su cuenta para que otros puedan ver su certificación.", "not-eligible": "Este usuario no es elegible para las certificaciones de freeCodeCamp.org en este momento.", - "profile-private": "{{username}} ha elegido que su portafolio sea privado. Deberá hacer público su portafolio para que otros puedan ver su certificación.", + "profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.", "certs-private": "{{username}} ha elegido que sus certificaciones sean privadas. Deberá hacer públicas sus certificaciones para que otros puedan verlas.", "not-honest": "{{username}} aún no ha aceptado nuestro Compromiso de Honestidad Académica.", "user-not-certified": "Parece que el usuario {{username}} no está certificado por {{cert}}", diff --git a/client/i18n/locales/italian/translations.json b/client/i18n/locales/italian/translations.json index e31524f5a1..5f669092e6 100644 --- a/client/i18n/locales/italian/translations.json +++ b/client/i18n/locales/italian/translations.json @@ -32,7 +32,6 @@ "profile": "Profilo", "news": "Notizie", "donate": "Dona", - "support-our-nonprofit": "Support our non-profit", "update-settings": "Aggiorna le impostazioni del mio account", "sign-me-out": "Esci da freeCodeCamp", "flag-user": "Segnala l'account di questo utente per abusi", @@ -217,8 +216,7 @@ "tax-exempt-status": "freeCodeCamp è un'organizzazione senza scopo di lucro (United States Federal Tax Identification Number: 82-0779546)", "mission-statement": "La nostra missione: aiutare le persone a imparare a programmare gratuitamente. Lo facciamo creando migliaia di video, articoli e lezioni di programmazione interattive - tutti liberamente disponibili al pubblico. Abbiamo anche migliaia di gruppi di studio freeCodeCamp in tutto il mondo.", "donation-initiatives": "Le donazioni a freeCodeCamp vanno alle nostre iniziative educative e aiutano a pagare i server, i servizi e il personale.", - "donate-text": "Puoi", - "donate-link": "fare una donazione deducibile dalle tasse qui", + "donate-text": "You can <1>make a tax-deductible donation here.", "trending-guides": "Guide Di Tendenza", "our-nonprofit": "Il nostro nonprofit", "links": { @@ -287,11 +285,16 @@ "info": "Informazioni", "code": "Codice", "tests": "Test", + "restart": "Restart", + "restart-step": "Restart Step", + "console": "Console", + "notes": "Notes", "preview": "Anteprima" }, "help-translate": "Stiamo ancora traducendo le seguenti certificazioni.", "help-translate-link": "Aiutaci con le traduzioni...", "season-greetings": "Season's Greetings to you and your family.", + "season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉", "if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.", "project-preview-title": "Here's a preview of what you will build" }, @@ -459,7 +462,7 @@ "username-not-found": "Non siamo riusciti a trovare un utente con il nome utente \"{{username}}\"", "add-name": "Questo utente deve aggiungere il proprio nome al proprio account affinché gli altri possano visualizzare la sua certificazione.", "not-eligible": "Questo utente non è idoneo per le certificazioni freeCodeCamp.org al momento.", - "profile-private": "{{username}} ha scelto di rendere privato il suo portfolio. Dovrà renderlo pubblico affinché altri possano vedere la sua certificazione.", + "profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.", "certs-private": "{{username}} ha scelto di rendere privato il suo portfolio. Dovrà renderlo pubblico affinché altri possano vedere la sua certificazione.", "not-honest": "{{username}} non ha ancora accettato il nostro Impegno di Onestà Accademica.", "user-not-certified": "Sembra che l'utente {{username}} non sia certificato {{cert}}", diff --git a/client/i18n/locales/portuguese/translations.json b/client/i18n/locales/portuguese/translations.json index 3f4f1802b0..b7e068c9f5 100644 --- a/client/i18n/locales/portuguese/translations.json +++ b/client/i18n/locales/portuguese/translations.json @@ -32,7 +32,6 @@ "profile": "Perfil", "news": "Notícias", "donate": "Doar", - "support-our-nonprofit": "Dê seu apoio à nossa organização sem fins lucrativos", "update-settings": "Atualizar as configurações da minha conta", "sign-me-out": "Sair do freeCodeCamp", "flag-user": "Denunciar a conta deste usuário por abuso", @@ -217,8 +216,7 @@ "tax-exempt-status": "O freeCodeCamp é uma organização sem fins lucrativos apoiada por doadores e isenta de impostos 501(c)(3) (Número de identificação fiscal federal dos Estados Unidos: 82-0779546)", "mission-statement": "Nossa missão: ajudar as pessoas a aprender a programar de graça. Nós conseguimos isso através da criação de milhares de vídeos, artigos e aulas interativas de programação – tudo disponível gratuitamente para o público. Também temos milhares de grupos de estudo do freeCodeCamp espalhados por todo o mundo.", "donation-initiatives": "Doações para o freeCodeCamp vão para as nossas iniciativas educacionais e ajudam a pagar por servidores, serviços e equipe.", - "donate-text": "Você pode", - "donate-link": "fazer uma doação dedutível de impostos aqui", + "donate-text": "Você pode <1>fazer uma doação dedutível de imposto aqui.", "trending-guides": "Guias em alta", "our-nonprofit": "Nossa organização sem fins lucrativos", "links": { @@ -287,11 +285,16 @@ "info": "Informações", "code": "Código", "tests": "Testes", + "restart": "Reiniciar", + "restart-step": "Reiniciar etapa", + "console": "Console", + "notes": "Observações", "preview": "Pré-visualizar" }, "help-translate": "Ainda estamos traduzindo as certificações a seguir.", "help-translate-link": "Ajude-nos a traduzir.", "season-greetings": "Boas festas de fim de ano para você e sua família.", + "season-greetings-fcc": "Boas festas de fim de ano, da comunidade do freeCodeCamp 🎉", "if-getting-value": "Se você está conseguindo tirar proveito do conteúdo do freeCodeCamp, agora é um ótimo momento para fazer sua doação e apoiar nossa missão sem fins lucrativos.", "project-preview-title": "Aqui está uma prévia do que você vai criar" }, @@ -459,7 +462,7 @@ "username-not-found": "Não foi possível encontrar um usuário com o nome de usuário \"{{username}}\"", "add-name": "Este usuário precisa adicionar seu nome à sua conta para que outros possam visualizar sua certificação.", "not-eligible": "Este usuário não é elegível para certificações do freeCodeCamp.org no momento.", - "profile-private": "{{username}} escolheu tornar seu portfólio privado. Ele/ela terá que tornar seu portfólio público para que outros possam ver a sua certificação.", + "profile-private": "{{username}} escolheu tornar seu perfil privado. Ele/ela terá que tornar seu perfil público para que outros possam ver a sua certificação.", "certs-private": "{{username}} escolheu deixar suas certificações privadas. Ele/ela terá que tornar suas certificações públicas para que outros possam vê-las.", "not-honest": "{{username}} ainda não concordou com nosso compromisso com a honestidade acadêmica.", "user-not-certified": "Parece que o usuário {{username}} não possui o certificado {{cert}}", diff --git a/client/i18n/locales/ukrainian/links.json b/client/i18n/locales/ukrainian/links.json new file mode 100644 index 0000000000..65f62b29c1 --- /dev/null +++ b/client/i18n/locales/ukrainian/links.json @@ -0,0 +1,28 @@ +{ + "help-translate-link-url": "https://contribute.freecodecamp.org/#/how-to-translate-files", + "top-contributors": "https://www.freecodecamp.org/news/freecodecamp-top-contributors/", + "footer": { + "about-url": "https://www.freecodecamp.org/news/about/", + "shop-url": "https://www.freecodecamp.org/shop/", + "support-url": "https://www.freecodecamp.org/news/support/", + "sponsors-url": "https://www.freecodecamp.org/news/sponsors/", + "honesty-url": "https://www.freecodecamp.org/news/academic-honesty-policy/", + "coc-url": "https://www.freecodecamp.org/news/code-of-conduct/", + "privacy-url": "https://www.freecodecamp.org/news/privacy-policy/", + "tos-url": "https://www.freecodecamp.org/news/terms-of-service/", + "copyright-url": "https://www.freecodecamp.org/news/copyright-policy/" + }, + "donate": { + "other-ways-url": "https://www.freecodecamp.org/news/how-to-donate-to-free-code-camp" + }, + "nav": { + "forum": "https://forum.freecodecamp.org/", + "news": "https://freecodecamp.org/news/" + }, + "help": { + "HTML-CSS": "HTML-CSS", + "JavaScript": "JavaScript", + "Python": "Python", + "Relational Databases": "Relational Databases" + } +} diff --git a/client/i18n/locales/ukrainian/meta-tags.json b/client/i18n/locales/ukrainian/meta-tags.json new file mode 100644 index 0000000000..cf2020dbfb --- /dev/null +++ b/client/i18n/locales/ukrainian/meta-tags.json @@ -0,0 +1,32 @@ +{ + "title": "Learn to Code — For Free — Coding Courses for Busy People", + "description": "Learn to Code — For Free", + "social-description": "Learn to Code — For Free", + "keywords": [ + "python", + "javascript", + "js", + "git", + "github", + "website", + "web", + "development", + "free", + "code", + "camp", + "course", + "courses", + "html", + "css", + "react", + "redux", + "api", + "front", + "back", + "end", + "learn", + "tutorial", + "programming" + ], + "youre-unsubscribed": "You have been unsubscribed" +} diff --git a/client/i18n/locales/ukrainian/motivation.json b/client/i18n/locales/ukrainian/motivation.json new file mode 100644 index 0000000000..d0cf5c66ae --- /dev/null +++ b/client/i18n/locales/ukrainian/motivation.json @@ -0,0 +1,819 @@ +{ + "compliments": [ + "Over the top!", + "Down the rabbit hole we go!", + "Bring that rain!", + "Target acquired.", + "Feel that need for speed!", + "You've got guts!", + "We have liftoff!", + "To infinity and beyond!", + "Encore!", + "Onward!", + "Challenge destroyed!", + "It's on like Donkey Kong!", + "Power level? It's over 9000!", + "Coding spree!", + "Code long and prosper.", + "The crowd goes wild!", + "One for the guinness book!", + "Flawless victory!", + "Most efficient!", + "You've got the touch!", + "You're on fire!", + "The town is now red!", + "To the nines!", + "To the Batmobile!", + "Pull out all the stops!", + "You're a wizard, Harry!", + "You're an all star!", + "Way to go!", + "Outta sight!", + "You're crushing it!", + "What sorcery is this?", + "The world rejoices!", + "That's the way it's done!", + "You rock!", + "Woo-hoo!", + "We knew you could do it!", + "Hyper Combo Finish!", + "Nothing but net!", + "Boom-shakalaka!", + "You're a shooting star!", + "You're unstoppable!", + "Way cool!", + "Walk on that sunshine!", + "Keep on trucking!", + "Off the charts!", + "There is no spoon!", + "Cranked it up to 11!", + "Escape velocity reached!", + "You make this look easy!", + "Passed with flying colors!", + "You've got this!", + "Happy, happy, joy, joy!", + "Tomorrow, the world!", + "Your powers combined!", + "It's alive. It's alive!", + "Sonic Boom!", + "Here's looking at you, Code!", + "Ride like the wind!", + "Legen - wait for it - dary!", + "Ludicrous Speed! Go!", + "Most triumphant!", + "One loop to rule them all!", + "By the power of Grayskull!", + "You did it!", + "Storm that castle!", + "Face-melting guitar solo!", + "Checkmate!", + "Bodacious!", + "Tubular!", + "You're outta sight!", + "Keep calm and code on!", + "Even sad panda smiles!", + "Even grumpy cat approves!", + "Kool Aid Man says oh yeah!", + "Bullseye!", + "Far out!", + "You're heating up!", + "Standing ovation!", + "Nice one!", + "All right!", + "Hasta la vista, challenge!", + "Terminated.", + "Off the hook!", + "Thundercats, Hooo!", + "Shiver me timbers!", + "Raise the roof!", + "Bingo!", + "Even honeybadger cares!", + "Helm, Warp Nine. Engage!", + "Gotta code 'em all!", + "Spool up the FTL drive!", + "Cool beans!", + "They're in another castle.", + "Power UP!", + "Pikachu chooses you!", + "I gotta have more cow bell.", + "Gotta go fast!", + "Yipee!", + "Cowabunga!", + "Moon Prism Power!", + "Plus Ultra!" + ], + "motivationalQuotes": [ + { + "quote": "Whatever you are, be a good one.", + "author": "Abraham Lincoln" + }, + { + "quote": "A change in perspective is worth 80 IQ points.", + "author": "Alan Kay" + }, + { + "quote": "The best way to predict the future is to invent it.", + "author": "Alan Kay" + }, + { + "quote": "The future is not laid out on a track. It is something that we can decide, and to the extent that we do not violate any known laws of the universe, we can probably make it work the way that we want to.", + "author": "Alan Kay" + }, + { + "quote": "We can only see a short distance ahead, but we can see plenty there that needs to be done.", + "author": "Alan Turing" + }, + { + "quote": "In the depth of winter, I finally learned that within me there lay an invincible summer.", + "author": "Albert Camus" + }, + { + "quote": "A person who never made a mistake never tried anything new.", + "author": "Albert Einstein" + }, + { + "quote": "Creativity is intelligence having fun.", + "author": "Albert Einstein" + }, + { + "quote": "I have no special talents. I am only passionately curious.", + "author": "Albert Einstein" + }, + { + "quote": "Life is like riding a bicycle. To keep your balance, you must keep moving.", + "author": "Albert Einstein" + }, + { + "quote": "Make everything as simple as possible, but not simpler.", + "author": "Albert Einstein" + }, + { + "quote": "Never memorize something that you can look up.", + "author": "Albert Einstein" + }, + { + "quote": "Once we accept our limits, we go beyond them.", + "author": "Albert Einstein" + }, + { + "quote": "Play is the highest form of research.", + "author": "Albert Einstein" + }, + { + "quote": "We cannot solve our problems with the same thinking we used when we created them.", + "author": "Albert Einstein" + }, + { + "quote": "Wisdom is not a product of schooling but of the lifelong attempt to acquire it.", + "author": "Albert Einstein" + }, + { + "quote": "Your imagination is your preview of life's coming attractions.", + "author": "Albert Einstein" + }, + { + "quote": "There is only one corner of the universe you can be certain of improving, and that's your own self.", + "author": "Aldous Huxley" + }, + { + "quote": "The most common way people give up their power is by thinking they don't have any.", + "author": "Alice Walker" + }, + { + "quote": "Follow your inner moonlight. Don't hide the madness.", + "author": "Allen Ginsberg" + }, + { + "quote": "The most difficult thing is the decision to act. The rest is merely tenacity.", + "author": "Amelia Earhart" + }, + { + "quote": "Life shrinks or expands in proportion with one's courage.", + "author": "Anaïs Nin" + }, + { + "quote": "Weeks of programming can save you hours of planning.", + "author": "Unknown" + }, + { + "quote": "Quality is not an act, it is a habit.", + "author": "Aristotle" + }, + { + "quote": "Start where you are. Use what you have. Do what you can.", + "author": "Arthur Ashe" + }, + { + "quote": "Nothing is impossible, the word itself says \"I'm possible\"!", + "author": "Audrey Hepburn" + }, + { + "quote": "Every strike brings me closer to the next home run.", + "author": "Babe Ruth" + }, + { + "quote": "By failing to prepare, you are preparing to fail.", + "author": "Benjamin Franklin" + }, + { + "quote": "Tell me and I forget. Teach me and I remember. Involve me and I learn.", + "author": "Benjamin Franklin" + }, + { + "quote": "Well done is better than well said.", + "author": "Benjamin Franklin" + }, + { + "quote": "There are no short cuts to any place worth going.", + "author": "Beverly Sills" + }, + { + "quote": "Controlling complexity is the essence of computer programming.", + "author": "Brian Kernighan" + }, + { + "quote": "I fear not the man who has practiced 10,000 kicks once, but I fear the man who has practiced one kick 10,000 times.", + "author": "Bruce Lee" + }, + { + "quote": "There are far, far better things ahead than any we leave behind.", + "author": "C.S. Lewis" + }, + { + "quote": "We are what we believe we are.", + "author": "C.S. Lewis" + }, + { + "quote": "With the possible exception of the equator, everything begins somewhere.", + "author": "C.S. Lewis" + }, + { + "quote": "You are never too old to set another goal, or to dream a new dream.", + "author": "C.S. Lewis" + }, + { + "quote": "Somewhere, something incredible is waiting to be known.", + "author": "Carl Sagan" + }, + { + "quote": "If you're not making mistakes, then you're not making decisions.", + "author": "Catherine Cook" + }, + { + "quote": "Find what you love and let it kill you.", + "author": "Charles Bukowski" + }, + { + "quote": "What matters most is how well you walk through the fire.", + "author": "Charles Bukowski" + }, + { + "quote": "It is not the strongest of the species that survive, nor the most intelligent, but the one most responsive to change.", + "author": "Charles Darwin" + }, + { + "quote": "Life is 10% what happens to you and 90% how you react to it.", + "author": "Charles R. Swindoll" + }, + { + "quote": "You will do foolish things, but do them with enthusiasm.", + "author": "Colette" + }, + { + "quote": "It does not matter how slowly you go as long as you do not stop.", + "author": "Confucius" + }, + { + "quote": "Real knowledge is to know the extent of one's ignorance.", + "author": "Confucius" + }, + { + "quote": "The past cannot be changed. The future is yet in your power.", + "author": "Confucius" + }, + { + "quote": "Looking at code you wrote more than two weeks ago is like looking at code you are seeing for the first time.", + "author": "Dan Hurvitz" + }, + { + "quote": "Someday is not a day of the week.", + "author": "Denise Brennan-Nelson" + }, + { + "quote": "UNIX is simple. It just takes a genius to understand its simplicity.", + "author": "Dennis Ritchie" + }, + { + "quote": "Computers are good at following instructions, but not at reading your mind.", + "author": "Donald Knuth" + }, + { + "quote": "A good programmer is someone who always looks both ways before crossing a one-way street.", + "author": "Doug Linder" + }, + { + "quote": "Tough times never last, but tough people do.", + "author": "Dr. Robert Schuller" + }, + { + "quote": "If things start happening, don't worry, don't stew, just go right along and you'll start happening too.", + "author": "Dr. Seuss" + }, + { + "quote": "Do not go gentle into that good night. Rage, rage against the dying of the light.", + "author": "Dylan Thomas" + }, + { + "quote": "The question of whether computers can think is like the question of whether submarines can swim.", + "author": "E.W. Dijkstra" + }, + { + "quote": "Any code of your own that you haven't looked at for six or more months might as well have been written by someone else.", + "author": "Eagleson's Law" + }, + { + "quote": "Do one thing every day that scares you.", + "author": "Eleanor Roosevelt" + }, + { + "quote": "With the new day comes new strength and new thoughts.", + "author": "Eleanor Roosevelt" + }, + { + "quote": "You must do the things you think you cannot do.", + "author": "Eleanor Roosevelt" + }, + { + "quote": "Light tomorrow with today.", + "author": "Elizabeth Barrett Browning" + }, + { + "quote": "Forever is composed of nows.", + "author": "Emily Dickinson" + }, + { + "quote": "Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter.", + "author": "Eric Raymond" + }, + { + "quote": "If you don't risk anything, you risk even more.", + "author": "Erica Jong" + }, + { + "quote": "The world breaks everyone, and afterward, many are strong at the broken places.", + "author": "Ernest Hemingway" + }, + { + "quote": "There is nothing noble in being superior to your fellow man; true nobility is being superior to your former self.", + "author": "Ernest Hemingway" + }, + { + "quote": "Never confuse a single defeat with a final defeat.", + "author": "F. Scott Fitzgerald" + }, + { + "quote": "I attribute my success to this - I never gave or took any excuse.", + "author": "Florence Nightingale" + }, + { + "quote": "The best revenge is massive success.", + "author": "Frank Sinatra" + }, + { + "quote": "The only limit to our realization of tomorrow, will be our doubts of today.", + "author": "Franklin D. Roosevelt" + }, + { + "quote": "Right or wrong, it's very pleasant to break something from time to time.", + "author": "Fyodor Dostoevsky" + }, + { + "quote": "The harder I work, the luckier I get.", + "author": "Gary Player" + }, + { + "quote": "Giving up is the only sure way to fail.", + "author": "Gena Showalter" + }, + { + "quote": "The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards.", + "author": "Gene Spafford" + }, + { + "quote": "A life spent making mistakes is not only more honorable, but more useful than a life spent doing nothing.", + "author": "George Bernard Shaw" + }, + { + "quote": "First learn computer science and all the theory. Next develop a programming style. Then forget all that and just hack.", + "author": "George Carrette" + }, + { + "quote": "Discovering the unexpected is more important than confirming the known.", + "author": "George Box" + }, + { + "quote": "We only see what we know.", + "author": "Goethe" + }, + { + "quote": "Without hard work, nothing grows but weeds.", + "author": "Gordon B. Hinckley" + }, + { + "quote": "The function of good software is to make the complex appear to be simple.", + "author": "Grady Booch" + }, + { + "quote": "When you know that you're capable of dealing with whatever comes, you have the only security the world has to offer.", + "author": "Harry Browne" + }, + { + "quote": "Pain is inevitable. Suffering is optional.", + "author": "Haruki Murakami" + }, + { + "quote": "Optimism is the faith that leads to achievement. Nothing can be done without hope and confidence.", + "author": "Helen Keller" + }, + { + "quote": "The price of anything is the amount of life you exchange for it.", + "author": "Henry David Thoreau" + }, + { + "quote": "Whether you think you can or think you can't, you're right.", + "author": "Henry Ford" + }, + { + "quote": "The most exciting phrase to hear in science, the one that heralds discoveries, is not 'Eureka!' but 'Now that's funny…'", + "author": "Isaac Asimov" + }, + { + "quote": "We are all failures. At least the best of us are.", + "author": "J.M. Barrie" + }, + { + "quote": "You can't wait for inspiration. You have to go after it with a club.", + "author": "Jack London" + }, + { + "quote": "Don't wish it were easier, wish you were better.", + "author": "Jim Rohn" + }, + { + "quote": "By seeking and blundering we learn.", + "author": "Johann Wolfgang von Goethe" + }, + { + "quote": "Knowing is not enough; we must apply. Wishing is not enough; we must do.", + "author": "Johann Wolfgang von Goethe" + }, + { + "quote": "We first make our habits, then our habits make us.", + "author": "John Dryden" + }, + { + "quote": "The power of imagination makes us infinite.", + "author": "John Muir" + }, + { + "quote": "May you live every day of your life.", + "author": "Jonathan Swift" + }, + { + "quote": "Perseverance is failing 19 times and succeeding the 20th.", + "author": "Julie Andrews" + }, + { + "quote": "The work of today is the history of tomorrow, and we are its makers.", + "author": "Juliette Gordon Low" + }, + { + "quote": "If you reveal your secrets to the wind, you should not blame the wind for revealing them to the trees.", + "author": "Kahlil Gibran" + }, + { + "quote": "Optimism is an occupational hazard of programming; feedback is the treatment.", + "author": "Kent Beck" + }, + { + "quote": "Opportunity does not knock, it presents itself when you beat down the door.", + "author": "Kyle Chandler" + }, + { + "quote": "To iterate is human, to recurse divine.", + "author": "Peter Deutsch" + }, + { + "quote": "A good traveler has no fixed plans and is not intent on arriving.", + "author": "Lao Tzu" + }, + { + "quote": "An ant on the move does more than a dozing ox.", + "author": "Lao Tzu" + }, + { + "quote": "Do the difficult things while they are easy and do the great things while they are small. A journey of a thousand miles must begin with a single step.", + "author": "Lao Tzu" + }, + { + "quote": "That's the thing about people who think they hate computers. What they really hate is lousy programmers.", + "author": "Larry Niven" + }, + { + "quote": "It had long since come to my attention that people of accomplishment rarely sat back and let things happen to them. They went out and happened to things.", + "author": "Leonardo da Vinci" + }, + { + "quote": "If you're any good at all, you know you can be better.", + "author": "Lindsay Buckingham" + }, + { + "quote": "If people never did silly things, nothing intelligent would ever get done.", + "author": "Ludwig Wittgenstein" + }, + { + "quote": "You only live once, but if you do it right, once is enough.", + "author": "Mae West" + }, + { + "quote": "Live as if you were to die tomorrow. Learn as if you were to live forever.", + "author": "Mahatma Gandhi" + }, + { + "quote": "Strength does not come from physical capacity. It comes from an indomitable will.", + "author": "Mahatma Gandhi" + }, + { + "quote": "One person's 'paranoia' is another person's 'engineering redundancy'.", + "author": "Marcus J. Ranum" + }, + { + "quote": "Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less.", + "author": "Marie Curie" + }, + { + "quote": "If you have everything under control, you're not moving fast enough.", + "author": "Mario Andretti" + }, + { + "quote": "Education: the path from cocky ignorance to miserable uncertainty.", + "author": "Mark Twain" + }, + { + "quote": "It ain't what you don't know that gets you into trouble. It's what you know for sure that just ain't so.", + "author": "Mark Twain" + }, + { + "quote": "The secret of getting ahead is getting started.", + "author": "Mark Twain" + }, + { + "quote": "The two most important days in your life are the day you are born and the day you find out why.", + "author": "Mark Twain" + }, + { + "quote": "Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails.", + "author": "Mark Twain" + }, + { + "quote": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", + "author": "Martin Fowler" + }, + { + "quote": "I know, somehow, that only when it is dark enough can you see the stars.", + "author": "Martin Luther King Jr." + }, + { + "quote": "It is never too late to be what you might have been.", + "author": "Mary Anne Evans" + }, + { + "quote": "Nothing will work unless you do.", + "author": "Maya Angelou" + }, + { + "quote": "We delight in the beauty of the butterfly, but rarely admit the changes it has gone through to achieve that beauty.", + "author": "Maya Angelou" + }, + { + "quote": "We may encounter many defeats, but we must not be defeated.", + "author": "Maya Angelou" + }, + { + "quote": "Everybody has talent, but ability takes hard work.", + "author": "Michael Jordan" + }, + { + "quote": "I've missed more than 9,000 shots during my career. I've lost almost 300 games. 26 times, I've been trusted to take the game winning shot and missed. I've failed over and over and over again in my life. And that is why I succeed.", + "author": "Michael Jordan" + }, + { + "quote": "Impossible is just a big word thrown around by small men who find it easier to live in the world they've been given than to explore the power they have to change it. Impossible is not a fact. It's an opinion. Impossible is not a declaration. It's a dare. Impossible is potential. Impossible is temporary. Impossible is nothing.", + "author": "Muhammad Ali" + }, + { + "quote": "A winner is a dreamer who never gives up.", + "author": "Nelson Mandela" + }, + { + "quote": "It always seems impossible until it's done.", + "author": "Nelson Mandela" + }, + { + "quote": "Failure will never overtake me if my determination to succeed is strong enough.", + "author": "Og Mandino" + }, + { + "quote": "I am not young enough to know everything.", + "author": "Oscar Wilde" + }, + { + "quote": "There is only one thing that makes a dream impossible to achieve: the fear of failure.", + "author": "Paulo Coelho" + }, + { + "quote": "Never go to bed mad. Stay up and fight.", + "author": "Phyllis Diller" + }, + { + "quote": "You can't cross the sea merely by standing and staring at the water.", + "author": "Rabindranath Tagore" + }, + { + "quote": "The only person you are destined to become is the person you decide to be.", + "author": "Ralph Waldo Emerson" + }, + { + "quote": "What you do speaks so loudly that I cannot hear what you say.", + "author": "Ralph Waldo Emerson" + }, + { + "quote": "People who are crazy enough to think they can change the world, are the ones who do.", + "author": "Rob Siltanen" + }, + { + "quote": "The best way out is always through.", + "author": "Robert Frost" + }, + { + "quote": "Today's accomplishments were yesterday's impossibilities.", + "author": "Robert H. Schuller" + }, + { + "quote": "Don't be satisfied with stories, how things have gone with others. Unfold your own myth.", + "author": "Rumi" + }, + { + "quote": "Forget safety. Live where you fear to live. Destroy your reputation. Be notorious.", + "author": "Rumi" + }, + { + "quote": "Sell your cleverness and buy bewilderment.", + "author": "Rumi" + }, + { + "quote": "The cure for pain is in the pain.", + "author": "Rumi" + }, + { + "quote": "Have no fear of perfection - you'll never reach it.", + "author": "Salvador Dalí" + }, + { + "quote": "Don't watch the clock. Do what it does. Keep going.", + "author": "Sam Levenson" + }, + { + "quote": "Ever Tried. Ever failed. No matter. Try again. Fail again. Fail better.", + "author": "Samuel Beckett" + }, + { + "quote": "The more you know, the more you realize you know nothing.", + "author": "Socrates" + }, + { + "quote": "The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.", + "author": "Stephen Hawking" + }, + { + "quote": "The universe doesn't allow perfection.", + "author": "Stephen Hawking" + }, + { + "quote": "Whether you want to uncover the secrets of the universe, or you want to pursue a career in the 21st century, basic computer programming is an essential skill to learn.", + "author": "Stephen Hawking" + }, + { + "quote": "The scariest moment is always just before you start.", + "author": "Stephen King" + }, + { + "quote": "You can, you should, and if you're brave enough to start, you will.", + "author": "Stephen King" + }, + { + "quote": "Arise, Awake and Stop not until the goal is reached.", + "author": "Swami Vivekananda" + }, + { + "quote": "It is said that your life flashes before your eyes just before you die. That is true, it's called Life.", + "author": "Terry Pratchett" + }, + { + "quote": "Believe you can and you're halfway there.", + "author": "Theodore Roosevelt" + }, + { + "quote": "I have not failed. I've just found 10,000 ways that won't work.", + "author": "Thomas A. Edison" + }, + { + "quote": "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.", + "author": "Thomas A. Edison" + }, + { + "quote": "The harder the conflict, the more glorious the triumph.", + "author": "Thomas Paine" + }, + { + "quote": "The Web as I envisaged it, we have not seen it yet. The future is still so much bigger than the past.", + "author": "Tim Berners-Lee" + }, + { + "quote": "Failure is the condiment that gives success its flavor.", + "author": "Truman Capote" + }, + { + "quote": "Those who says it cannot be done should not interrupt the person doing it.", + "author": "Unknown" + }, + { + "quote": "Even if you fall on your face, you're still moving forward.", + "author": "Victor Kiam" + }, + { + "quote": "It's not whether you get knocked down, it's whether you get up.", + "author": "Vince Lombardi" + }, + { + "quote": "I dream my painting and I paint my dream.", + "author": "Vincent van Gogh" + }, + { + "quote": "Let us cultivate our garden.", + "author": "Voltaire" + }, + { + "quote": "Aim for the moon. If you miss, you may hit a star.", + "author": "W. Clement Stone" + }, + { + "quote": "The way to get started is to quit talking and begin doing.", + "author": "Walt Disney" + }, + { + "quote": "You miss 100% of the shots you don't take.", + "author": "Wayne Gretzky" + }, + { + "quote": "Don't let yesterday take up too much of today.", + "author": "Will Rogers" + }, + { + "quote": "Even if you're on the right track, you'll get run over if you just sit there.", + "author": "Will Rogers" + }, + { + "quote": "Do not wait to strike till the iron is hot; but make it hot by striking.", + "author": "William Butler Yeats" + }, + { + "quote": "You cannot swim for new horizons until you have courage to lose sight of the shore.", + "author": "William Faulkner" + }, + { + "quote": "Be not afraid of greatness. Some are born great, some achieve greatness, and others have greatness thrust upon them.", + "author": "William Shakespeare" + }, + { + "quote": "We know what we are, but not what we may be.", + "author": "William Shakespeare" + }, + { + "quote": "In theory there is no difference between theory and practice. In practice there is.", + "author": "Yogi Berra" + }, + { + "quote": "You can see a lot by just looking.", + "author": "Yogi Berra" + }, + { + "quote": "There is no elevator to success, you have to take the stairs.", + "author": "Zig Ziglar" + }, + { + "quote": "You don't have to be great to start, but you have to start to be great.", + "author": "Zig Ziglar" + } + ] +} diff --git a/client/i18n/locales/ukrainian/translations.json b/client/i18n/locales/ukrainian/translations.json index 4e1886c368..dc99b41e4b 100644 --- a/client/i18n/locales/ukrainian/translations.json +++ b/client/i18n/locales/ukrainian/translations.json @@ -32,7 +32,6 @@ "profile": "Профіль", "news": "Новини", "donate": "Підтримати", - "support-our-nonprofit": "Support our non-profit", "update-settings": "Змінити налаштування облікового запису", "sign-me-out": "Вийти з freeCodeCamp", "flag-user": "Поскаржитись на обліковий запис цього користувача за порушення", @@ -217,8 +216,7 @@ "tax-exempt-status": "freeCodeCamp — це некомерційна організація, яка підтримується спонсорськими внесками та звільнена від сплати податків 501(c)(3) (Федеральний ідентифікаційний номер платника податків США: 82-0779546)", "mission-statement": "Ми хочемо допомогти людям безкоштовно навчитися програмувати. Ми досягаємо цього, створюючи тисячі відео, статей та інтерактивних уроків з програмування — вони усі доступні для широкого загалу. Також ми маємо тисячі безплатний навчальних груп freeCodeCamp у всьому світі.", "donation-initiatives": "Внески до freeCodeCamp йдуть на наші освітні програми та допомагають оплачувати сервери, послуги та персонал.", - "donate-text": "Ви можете", - "donate-link": "зробити внесок, що не підлягає оподаткуванню, тут", + "donate-text": "You can <1>make a tax-deductible donation here.", "trending-guides": "Популярні статті", "our-nonprofit": "Наша неприбуткова організація", "links": { @@ -287,11 +285,16 @@ "info": "Інформація", "code": "Код", "tests": "Тести", + "restart": "Restart", + "restart-step": "Restart Step", + "console": "Console", + "notes": "Notes", "preview": "Попередній перегляд" }, "help-translate": "Ми все ще перекладаємо такі сертифікати.", "help-translate-link": "Допоможіть нам з перекладом.", "season-greetings": "Season's Greetings to you and your family.", + "season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉", "if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our nonprofit's mission.", "project-preview-title": "Here's a preview of what you will build" }, @@ -459,7 +462,7 @@ "username-not-found": "Ми не змогли знайти користувача з ім'ям \"{{username}}\"", "add-name": "Цей користувач повинен додати своє ім'я до свого облікового запису, щоб інші могли переглянути його сертифікат.", "not-eligible": "Наразі цей користувач не має права отримати безкоштовний сертифікат freeCodeCamp.org.", - "profile-private": "{{username}} вирішив зробити своє портфоліо конфіденційним. Йому потрібно надати спільний доступ до свого портфоліо, щоб інші мали змогу переглянути його сертифікат.", + "profile-private": "{{username}} has chosen to make their profile private. They will need to make their profile public in order for others to be able to view their certification.", "certs-private": "{{username}} вирішив зробити свої сертифікати конфіденційними. Йому потрібно надати спільний доступ до своїх сертифікатів, щоб інші могли їх переглянути.", "not-honest": "{{username}} ще не прийняв нашу Обітницю академічної доброчесності.", "user-not-certified": "Схоже, що користувач {{username}} не є сертифікованим {{cert}}", diff --git a/client/i18n/locales/ukrainian/trending.json b/client/i18n/locales/ukrainian/trending.json new file mode 100644 index 0000000000..750086aece --- /dev/null +++ b/client/i18n/locales/ukrainian/trending.json @@ -0,0 +1,65 @@ +{ + "article0link": "https://www.freecodecamp.org/news/what-is-javascript-javascript-code-explained-in-plain-english/", + "article0title": "What is JavaScript?", + "article1link": "https://www.freecodecamp.org/news/linux-list-processes-how-to-check-running-processes/", + "article1title": "Linux List Processes", + "article2link": "https://www.freecodecamp.org/news/web-page-text-editor-how-to-open-html-code-in-mac-textedit/", + "article2title": "Web Page Text Editor", + "article3link": "https://www.freecodecamp.org/news/what-is-open-source-software-explained-in-plain-english/", + "article3title": "What is Open Source?", + "article4link": "https://www.freecodecamp.org/news/protect-yourself-against-sim-swapping-attacks/", + "article4title": "Sim Swapping Attacks", + "article5link": "https://www.freecodecamp.org/news/rng-meaning-what-does-rng-stand-for-in-gaming/", + "article5title": "RNG Meaning in Gaming", + "article6link": "https://www.freecodecamp.org/news/the-model-view-controller-pattern-mvc-architecture-and-frameworks-explained/", + "article6title": "Model View Controller", + "article7link": "https://www.freecodecamp.org/news/front-end-developer-what-is-front-end-development-explained-in-plain-english/", + "article7title": "Front End Development", + "article8link": "https://www.freecodecamp.org/news/what-is-a-full-stack-developer-back-end-front-end-full-stack-engineer/", + "article8title": "Full Stack Developer?", + "article9link": "https://www.freecodecamp.org/news/javascript-switch-case-js-switch-statement-example/", + "article9title": "JavaScript Switch Case", + "article10link": "https://www.freecodecamp.org/news/bash-sleep-how-to-make-a-shell-script-wait-n-seconds-example-command/", + "article10title": "Bash Sleep", + "article11link": "https://www.freecodecamp.org/news/bash-array-how-to-declare-an-array-of-strings-in-a-bash-script/", + "article11title": "Bash Array", + "article12link": "https://www.freecodecamp.org/news/what-is-a-cv-and-how-is-it-different-from-a-resume/", + "article12title": "What is a CV?", + "article13link": "https://www.freecodecamp.org/news/coding-programs-101-ways-to-learn-to-code-for-free/", + "article13title": "Coding Programs", + "article14link": "https://www.freecodecamp.org/news/how-to-exit-vim/", + "article14title": "How to Exit Vim", + "article15link": "https://www.freecodecamp.org/news/html-line-break-how-to-break-a-line-with-the-html-br-tag/", + "article15title": "HTML Line Break", + "article16link": "https://www.freecodecamp.org/news/how-to-convert-a-string-to-an-int-in-c-tutorial-with-example-code/", + "article16title": "C# String to Int", + "article17link": "https://www.freecodecamp.org/news/logical-fallacies-definition-fallacy-examples/", + "article17title": "Logical fallacies", + "article18link": "https://www.freecodecamp.org/news/javascript-online-html-css-js-code-editor-list-browser-ide-tools/", + "article18title": "JavaScript Online", + "article19link": "https://www.freecodecamp.org/news/sql-case-statement-tutorial-with-when-then-clause-example-queries/", + "article19title": "SQL Case Statement", + "article20link": "https://www.freecodecamp.org/news/javascript-tolowercase-how-to-convert-a-string-to-lowercase-and-uppercase-in-js/", + "article20title": "JavaScript toLowerCase", + "article21link": "https://www.freecodecamp.org/news/angular-ngclass-example/", + "article21title": "Angular NgClass Example", + "article22link": "https://www.freecodecamp.org/news/sql-aggregate-functions-with-example-data-queries-for-beginners/", + "article22title": "SQL Aggregate Functions", + "article23link": "https://www.freecodecamp.org/news/what-is-web-development-how-to-become-a-web-developer-career-path/", + "article23title": "What is Web Development?", + "article24link": "https://www.freecodecamp.org/news/the-best-way-to-learn-python-python-programming-tutorial-for-beginners/", + "article24title": "Best Way to Learn Python", + "article25link": "https://www.freecodecamp.org/news/word-count-in-google-docs-tutorial-counting-words-and-characters-in-a-google-doc-or-word-file/", + "article25title": "Word Count in Google Docs", + "article26link": "https://www.freecodecamp.org/news/how-to-use-node-environment-variables-with-a-dotenv-file-for-node-js-and-npm/", + "article26title": "Node Environment Variables", + "article27link": "https://www.freecodecamp.org/news/event-viewer-how-to-access-the-windows-10-activity-log/", + "article27title": "Event Viewer in Windows 10", + "article28link": "https://www.freecodecamp.org/news/combine-first-last-names-excel/", + "article28title": "Combine 1st/Last Name Excel", + "article29link": "https://www.freecodecamp.org/news/javascript-if-else-and-if-then-js-conditional-statements/", + "article29title": "JavaScript if-else & if-then" +} + + + diff --git a/client/package.json b/client/package.json index 86ad1481e0..3f35838660 100644 --- a/client/package.json +++ b/client/package.json @@ -24,7 +24,7 @@ "build:workers": "cross-env NODE_OPTIONS=\"--max-old-space-size=7168\" webpack --config ./webpack-workers.js", "clean": "gatsby clean", "predevelop": "tsc -p ../tools/ && node ../tools/scripts/build/ensure-env.js && npm run build:workers -- --env development", - "develop": "cross-env NODE_OPTIONS=\"--max-old-space-size=4000\" gatsby develop --inspect=9230", + "develop": "cross-env NODE_OPTIONS=\"--max-old-space-size=5000\" gatsby develop --inspect=9230", "lint": "node ./i18n/schema-validation.js", "serve": "gatsby serve -p 8000", "serve-ci": "serve -l 8000 -c ../serve.json public", @@ -33,12 +33,12 @@ "validate-keys": "tsc -p ../tools/ && node ../tools/scripts/lint/validate-keys.js" }, "dependencies": { - "@babel/plugin-proposal-export-default-from": "7.16.0", - "@babel/plugin-proposal-function-bind": "7.16.0", + "@babel/plugin-proposal-export-default-from": "7.16.5", + "@babel/plugin-proposal-function-bind": "7.16.5", "@babel/polyfill": "7.12.1", - "@babel/preset-env": "7.16.4", - "@babel/preset-react": "7.16.0", - "@babel/standalone": "7.16.4", + "@babel/preset-env": "7.16.5", + "@babel/preset-react": "7.16.5", + "@babel/standalone": "7.16.6", "@fortawesome/fontawesome": "1.1.8", "@fortawesome/fontawesome-svg-core": "1.2.36", "@fortawesome/free-brands-svg-icons": "5.15.4", @@ -48,10 +48,10 @@ "@freecodecamp/react-bootstrap": "0.32.3", "@freecodecamp/react-calendar-heatmap": "1.0.0", "@freecodecamp/strip-comments": "3.0.1", - "@loadable/component": "5.15.0", + "@loadable/component": "5.15.2", "@reach/router": "1.3.4", - "@stripe/react-stripe-js": "1.6.0", - "@stripe/stripe-js": "1.21.2", + "@stripe/react-stripe-js": "1.7.0", + "@stripe/stripe-js": "1.22.0", "@types/react-scrollable-anchor": "0.6.1", "algoliasearch": "4.11.0", "assert": "2.0.0", @@ -86,7 +86,7 @@ "nanoid": "3.1.30", "normalize-url": "4.5.1", "path-browserify": "1.0.1", - "postcss": "8.4.4", + "postcss": "8.4.5", "prismjs": "1.25.0", "process": "0.11.10", "prop-types": "15.7.2", @@ -98,8 +98,8 @@ "react-ga": "3.3.0", "react-helmet": "6.1.0", "react-hotkeys": "2.0.0", - "react-i18next": "11.14.3", - "react-instantsearch-dom": "6.17.0", + "react-i18next": "11.15.1", + "react-instantsearch-dom": "6.18.0", "react-lazy-load": "3.1.13", "react-monaco-editor": "0.40.0", "react-redux": "5.1.2", @@ -123,14 +123,14 @@ "store": "2.0.12", "stream-browserify": "3.0.0", "tone": "14.7.77", - "typescript": "4.5.3", + "typescript": "4.5.4", "uuid": "8.3.2", "validator": "13.7.0" }, "devDependencies": { "@babel/types": "7.16.0", - "@codesee/babel-plugin-instrument": "0.150.0", - "@codesee/tracker": "0.150.0", + "@codesee/babel-plugin-instrument": "0.153.0", + "@codesee/tracker": "0.153.0", "@testing-library/jest-dom": "5.16.1", "@testing-library/react": "12.1.2", "autoprefixer": "10.4.0", diff --git a/client/plugins/fcc-source-challenges/create-challenge-nodes.js b/client/plugins/fcc-source-challenges/create-challenge-nodes.js index 394364f033..7e5e80b27b 100644 --- a/client/plugins/fcc-source-challenges/create-challenge-nodes.js +++ b/client/plugins/fcc-source-challenges/create-challenge-nodes.js @@ -1,4 +1,5 @@ const crypto = require('crypto'); +const { blockNameify } = require('../../../utils/block-nameify'); function createChallengeNode(challenge, reporter) { // challengeType 11 is for video challenges (they only have instructions) @@ -31,6 +32,17 @@ function createChallengeNode(challenge, reporter) { type: challenge.challengeType === 7 ? 'CertificateNode' : 'ChallengeNode' }; + if (internal.type === 'ChallengeNode') { + const { tests = [], block, dashedName, superBlock } = challenge; + const slug = `/learn/${superBlock}/${block}/${dashedName}`; + + challenge.fields = { + slug, + blockName: blockNameify(block), + tests + }; + } + return JSON.parse( JSON.stringify( Object.assign( @@ -41,7 +53,8 @@ function createChallengeNode(challenge, reporter) { internal, sourceInstanceName: 'challenge' }, - challenge + { challenge }, + { id: crypto.randomUUID() } ) ) ); diff --git a/client/src/__mocks__/challenge-nodes.js b/client/src/__mocks__/challenge-nodes.js index 87f4ef28ed..6a4ce63b0e 100644 --- a/client/src/__mocks__/challenge-nodes.js +++ b/client/src/__mocks__/challenge-nodes.js @@ -1,136 +1,158 @@ const mockChallengeNodes = [ { - fields: { - slug: '/super-block-one/block-a/challenge-one', - blockName: 'Block A' - }, - id: 'a', - block: 'block-a', - title: 'Challenge One', - isPrivate: false, - superBlock: 'super-block-one', - dashedName: 'challenge-one' + challenge: { + fields: { + slug: '/super-block-one/block-a/challenge-one', + blockName: 'Block A' + }, + id: 'a', + block: 'block-a', + title: 'Challenge One', + isPrivate: false, + superBlock: 'super-block-one', + dashedName: 'challenge-one' + } }, { - fields: { - slug: '/super-block-one/block-a/challenge-two', - blockName: 'Block A' - }, - id: 'b', - block: 'block-a', - title: 'Challenge Two', - isPrivate: false, - superBlock: 'super-block-one', - dashedName: 'challenge-two' + challenge: { + fields: { + slug: '/super-block-one/block-a/challenge-two', + blockName: 'Block A' + }, + id: 'b', + block: 'block-a', + title: 'Challenge Two', + isPrivate: false, + superBlock: 'super-block-one', + dashedName: 'challenge-two' + } }, { - fields: { - slug: '/super-block-one/block-b/challenge-one', - blockName: 'Block B' - }, - id: 'c', - block: 'block-b', - title: 'Challenge One', - isPrivate: false, - superBlock: 'super-block-one', - dashedName: 'challenge-one' + challenge: { + fields: { + slug: '/super-block-one/block-b/challenge-one', + blockName: 'Block B' + }, + id: 'c', + block: 'block-b', + title: 'Challenge One', + isPrivate: false, + superBlock: 'super-block-one', + dashedName: 'challenge-one' + } }, { - fields: { - slug: '/super-block-one/block-b/challenge-two', - blockName: 'Block B' - }, + challenge: { + fields: { + slug: '/super-block-one/block-b/challenge-two', + blockName: 'Block B' + }, - id: 'd', - block: 'block-b', - title: 'Challenge Two', - isPrivate: false, - superBlock: 'super-block-one', - dashedName: 'challenge-two' + id: 'd', + block: 'block-b', + title: 'Challenge Two', + isPrivate: false, + superBlock: 'super-block-one', + dashedName: 'challenge-two' + } }, { - fields: { - slug: '/super-block-one/block-c/challenge-one', - blockName: 'Block C' - }, - id: 'e', - block: 'block-c', - title: 'Challenge One', - isPrivate: true, - superBlock: 'super-block-one', - dashedName: 'challenge-one' + challenge: { + fields: { + slug: '/super-block-one/block-c/challenge-one', + blockName: 'Block C' + }, + id: 'e', + block: 'block-c', + title: 'Challenge One', + isPrivate: true, + superBlock: 'super-block-one', + dashedName: 'challenge-one' + } }, { - fields: { - slug: '/super-block-two/block-a/challenge-one', - blockName: 'Block A' - }, - id: 'f', - block: 'block-a', - title: 'Challenge One', - isPrivate: false, - superBlock: 'super-block-two', - dashedName: 'challenge-one' + challenge: { + fields: { + slug: '/super-block-two/block-a/challenge-one', + blockName: 'Block A' + }, + id: 'f', + block: 'block-a', + title: 'Challenge One', + isPrivate: false, + superBlock: 'super-block-two', + dashedName: 'challenge-one' + } }, { - fields: { - slug: '/super-block-two/block-a/challenge-two', - blockName: 'Block A' - }, - id: 'g', - block: 'block-a', - title: 'Challenge Two', - isPrivate: false, - superBlock: 'super-block-two', - dashedName: 'challenge-two' + challenge: { + fields: { + slug: '/super-block-two/block-a/challenge-two', + blockName: 'Block A' + }, + id: 'g', + block: 'block-a', + title: 'Challenge Two', + isPrivate: false, + superBlock: 'super-block-two', + dashedName: 'challenge-two' + } }, { - fields: { - slug: '/super-block-two/block-b/challenge-one', - blockName: 'Block B' - }, - id: 'h', - block: 'block-b', - title: 'Challenge One', - isPrivate: false, - superBlock: 'super-block-two', - dashedName: 'challenge-one' + challenge: { + fields: { + slug: '/super-block-two/block-b/challenge-one', + blockName: 'Block B' + }, + id: 'h', + block: 'block-b', + title: 'Challenge One', + isPrivate: false, + superBlock: 'super-block-two', + dashedName: 'challenge-one' + } }, { - fields: { - slug: '/super-block-two/block-b/challenge-two', - blockName: 'Block B' - }, - id: 'i', - block: 'block-b', - title: 'Challenge Two', - isPrivate: false, - superBlock: 'super-block-two', - dashedName: 'challenge-two' + challenge: { + fields: { + slug: '/super-block-two/block-b/challenge-two', + blockName: 'Block B' + }, + id: 'i', + block: 'block-b', + title: 'Challenge Two', + isPrivate: false, + superBlock: 'super-block-two', + dashedName: 'challenge-two' + } }, { - fields: { - slug: '/super-block-three/block-a/challenge-one', - blockName: 'Block A' - }, - id: 'j', - block: 'block-a', - title: 'Challenge One', - isPrivate: false, - superBlock: 'super-block-three', - dashedName: 'challenge-one' + challenge: { + fields: { + slug: '/super-block-three/block-a/challenge-one', + blockName: 'Block A' + }, + id: 'j', + block: 'block-a', + title: 'Challenge One', + isPrivate: false, + superBlock: 'super-block-three', + dashedName: 'challenge-one' + } }, { - fields: { - slug: '/super-block-three/block-c/challenge-two', - blockName: 'Block C' - }, - id: 'k', - block: 'block-c', - title: 'Challenge Two', - isPrivate: false, - superBlock: 'super-block-three', - dashedName: 'challenge-two' + challenge: { + fields: { + slug: '/super-block-three/block-c/challenge-two', + blockName: 'Block C' + }, + id: 'k', + block: 'block-c', + title: 'Challenge Two', + isPrivate: false, + superBlock: 'super-block-three', + dashedName: 'challenge-two' + } } ]; diff --git a/client/src/assets/icons/green-pass.tsx b/client/src/assets/icons/green-pass.tsx index c4075c6b5e..ea641726fe 100644 --- a/client/src/assets/icons/green-pass.tsx +++ b/client/src/assets/icons/green-pass.tsx @@ -8,15 +8,15 @@ function GreenPass( return ( <> - {t('icons.passed')} - +
{t('donate.or-card')}
matches snapshot 1`] = `

- footer.donate-text - + You can - footer.donate-link + make a tax-deductible donation here .

diff --git a/client/src/components/Footer/index.tsx b/client/src/components/Footer/index.tsx index 7f7e8cc7e6..fed253ccbd 100644 --- a/client/src/components/Footer/index.tsx +++ b/client/src/components/Footer/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import Link from '../helpers/link'; import './footer.css'; @@ -15,11 +15,13 @@ function Footer(): JSX.Element {

{t('footer.mission-statement')}

{t('footer.donation-initiatives')}

- {t('footer.donate-text')}{' '} - - {t('footer.donate-link')} - - . + + You can + + make a tax-deductible donation here + + . +

diff --git a/client/src/components/Intro/index.tsx b/client/src/components/Intro/index.tsx index a80a0f150b..c3d801401e 100644 --- a/client/src/components/Intro/index.tsx +++ b/client/src/components/Intro/index.tsx @@ -92,7 +92,7 @@ const Intro = ({ sameTab={false} to='/donate' > - {t('buttons.support-our-nonprofit')} + {t('buttons.donate')}

diff --git a/client/src/components/Map/index.tsx b/client/src/components/Map/index.tsx index 024a739156..6988f52487 100644 --- a/client/src/components/Map/index.tsx +++ b/client/src/components/Map/index.tsx @@ -42,19 +42,19 @@ const linkSpacingStyle = { function renderLandingMap(nodes: ChallengeNode[]) { nodes = nodes.filter( - node => node.superBlock !== SuperBlocks.CodingInterviewPrep + ({ challenge }) => challenge.superBlock !== SuperBlocks.CodingInterviewPrep ); return (
{nodes - .filter(node => !isAuditedCert(curriculumLocale, node.superBlock)) - .map((node, i) => ( + .filter( + ({ challenge }) => + !isAuditedCert(curriculumLocale, challenge.superBlock) + ) + .map(({ challenge }, i) => (
  • - {generateIconComponent(node.superBlock, 'map-icon')} - {createSuperBlockTitle(node.superBlock)} + {generateIconComponent(challenge.superBlock, 'map-icon')} + {createSuperBlockTitle(challenge.superBlock)}
  • @@ -145,12 +152,14 @@ export function Map({ const data: MapData = useStaticQuery(graphql` query SuperBlockNodes { allChallengeNode( - sort: { fields: [superOrder] } - filter: { order: { eq: 0 }, challengeOrder: { eq: 0 } } + sort: { fields: [challenge___superOrder] } + filter: { challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } } } ) { nodes { - superBlock - dashedName + challenge { + superBlock + dashedName + } } } } diff --git a/client/src/components/profile/components/TimeLine.tsx b/client/src/components/profile/components/TimeLine.tsx index 0505e63b5a..0558289d16 100644 --- a/client/src/components/profile/components/TimeLine.tsx +++ b/client/src/components/profile/components/TimeLine.tsx @@ -269,11 +269,13 @@ function useIdToNameMap(): Map { allChallengeNode { edges { node { - fields { - slug + challenge { + fields { + slug + } + id + title } - id - title } } } @@ -289,12 +291,14 @@ function useIdToNameMap(): Map { edges.forEach( ({ node: { - // @ts-expect-error Graphql needs typing - id, - // @ts-expect-error Graphql needs typing - title, - // @ts-expect-error Graphql needs typing - fields: { slug } + challenge: { + // @ts-expect-error Graphql needs typing + id, + // @ts-expect-error Graphql needs typing + title, + // @ts-expect-error Graphql needs typing + fields: { slug } + } } }) => { idToNameMap.set(id, { challengeTitle: title, challengePath: slug }); diff --git a/client/src/components/profile/components/time-line.test.tsx b/client/src/components/profile/components/time-line.test.tsx index 833f5bc735..b5044188eb 100644 --- a/client/src/components/profile/components/time-line.test.tsx +++ b/client/src/components/profile/components/time-line.test.tsx @@ -12,29 +12,35 @@ beforeEach(() => { edges: [ { node: { - fields: { - slug: '' - }, - id: '5e46f802ac417301a38fb92b', - title: 'Page View Time Series Visualizer' + challenge: { + fields: { + slug: '' + }, + id: '5e46f802ac417301a38fb92b', + title: 'Page View Time Series Visualizer' + } } }, { node: { - fields: { - slug: '' - }, - id: '5e4f5c4b570f7e3a4949899f', - title: 'Sea Level Predictor' + challenge: { + fields: { + slug: '' + }, + id: '5e4f5c4b570f7e3a4949899f', + title: 'Sea Level Predictor' + } } }, { node: { - fields: { - slug: '' - }, - id: '5e46f7f8ac417301a38fb92a', - title: 'Medical Data Visualizer' + challenge: { + fields: { + slug: '' + }, + id: '5e46f7f8ac417301a38fb92a', + title: 'Medical Data Visualizer' + } } } ] diff --git a/client/src/pages/learn.tsx b/client/src/pages/learn.tsx index 13022530a7..1e3e39d58a 100644 --- a/client/src/pages/learn.tsx +++ b/client/src/pages/learn.tsx @@ -53,7 +53,9 @@ interface LearnPageProps { user: User; data: { challengeNode: { - fields: Slug; + challenge: { + fields: Slug; + }; }; }; executeGA: (payload: Record) => void; @@ -70,7 +72,9 @@ function LearnPage({ executeGA, data: { challengeNode: { - fields: { slug } + challenge: { + fields: { slug } + } } } }: LearnPageProps) { @@ -117,9 +121,11 @@ export default connect(mapStateToProps, mapDispatchToProps)(LearnPage); export const query = graphql` query FirstChallenge { - challengeNode(order: { eq: 0 }, challengeOrder: { eq: 0 }) { - fields { - slug + challengeNode(challenge: { order: { eq: 0 }, challengeOrder: { eq: 0 } }) { + challenge { + fields { + slug + } } } } diff --git a/client/src/pages/learn/back-end-development-and-apis/index.md b/client/src/pages/learn/back-end-development-and-apis/index.md index 7e54e1ef19..780563043f 100644 --- a/client/src/pages/learn/back-end-development-and-apis/index.md +++ b/client/src/pages/learn/back-end-development-and-apis/index.md @@ -1,6 +1,7 @@ --- title: Back End Development and APIs superBlock: back-end-development-and-apis +certification: back-end-development-and-apis --- ## Introduction to Back End Development and APIs diff --git a/client/src/pages/learn/coding-interview-prep/index.md b/client/src/pages/learn/coding-interview-prep/index.md index 977c233cf9..43e82b6bbf 100644 --- a/client/src/pages/learn/coding-interview-prep/index.md +++ b/client/src/pages/learn/coding-interview-prep/index.md @@ -1,6 +1,7 @@ --- title: Coding Interview Prep superBlock: coding-interview-prep +certification: coding-interview-prep --- ## Introduction to Coding Interview Prep diff --git a/client/src/pages/learn/data-analysis-with-python/index.md b/client/src/pages/learn/data-analysis-with-python/index.md index 82c1fffb2d..3a8f87eb1e 100644 --- a/client/src/pages/learn/data-analysis-with-python/index.md +++ b/client/src/pages/learn/data-analysis-with-python/index.md @@ -1,6 +1,7 @@ --- title: Data Analysis with Python superBlock: data-analysis-with-python +certification: data-analysis-with-python --- ## Introduction to Data Analysis with Python diff --git a/client/src/pages/learn/data-visualization/index.md b/client/src/pages/learn/data-visualization/index.md index 648d8171fd..c0435dfc0b 100644 --- a/client/src/pages/learn/data-visualization/index.md +++ b/client/src/pages/learn/data-visualization/index.md @@ -1,6 +1,7 @@ --- title: Data Visualization superBlock: data-visualization +certification: data-visualization --- ## Introduction to Data Visualization diff --git a/client/src/pages/learn/front-end-development-libraries/index.md b/client/src/pages/learn/front-end-development-libraries/index.md index 7932f57b90..ebc9011b4f 100644 --- a/client/src/pages/learn/front-end-development-libraries/index.md +++ b/client/src/pages/learn/front-end-development-libraries/index.md @@ -1,6 +1,7 @@ --- title: Front End Development Libraries superBlock: front-end-development-libraries +certification: front-end-development-libraries --- ## Introduction to Front End Development Libraries diff --git a/client/src/pages/learn/information-security/index.md b/client/src/pages/learn/information-security/index.md index bea921f99e..472eac0fea 100644 --- a/client/src/pages/learn/information-security/index.md +++ b/client/src/pages/learn/information-security/index.md @@ -1,6 +1,7 @@ --- title: Information Security superBlock: information-security +certification: information-security --- ## Introduction to Information Security diff --git a/client/src/pages/learn/javascript-algorithms-and-data-structures/index.md b/client/src/pages/learn/javascript-algorithms-and-data-structures/index.md index 249c8b2504..1874f71437 100644 --- a/client/src/pages/learn/javascript-algorithms-and-data-structures/index.md +++ b/client/src/pages/learn/javascript-algorithms-and-data-structures/index.md @@ -1,6 +1,7 @@ --- title: JavaScript Algorithms and Data Structures superBlock: javascript-algorithms-and-data-structures +certification: javascript-algorithms-and-data-structures --- ## Introduction to JavaScript Algorithms and Data Structures diff --git a/client/src/pages/learn/machine-learning-with-python/index.md b/client/src/pages/learn/machine-learning-with-python/index.md index 1796acaff8..c251181def 100644 --- a/client/src/pages/learn/machine-learning-with-python/index.md +++ b/client/src/pages/learn/machine-learning-with-python/index.md @@ -1,6 +1,7 @@ --- title: Machine Learning with Python superBlock: machine-learning-with-python +certification: machine-learning-with-python --- ## Introduction to Machine Learning with Python diff --git a/client/src/pages/learn/quality-assurance/index.md b/client/src/pages/learn/quality-assurance/index.md index e25b21d7eb..e3e5affd13 100644 --- a/client/src/pages/learn/quality-assurance/index.md +++ b/client/src/pages/learn/quality-assurance/index.md @@ -1,6 +1,7 @@ --- title: Quality Assurance superBlock: quality-assurance +certification: quality-assurance --- ## Introduction to Quality Assurance diff --git a/client/src/pages/learn/relational-databases/index.md b/client/src/pages/learn/relational-databases/index.md index 630d7ee662..afce9c161d 100644 --- a/client/src/pages/learn/relational-databases/index.md +++ b/client/src/pages/learn/relational-databases/index.md @@ -1,6 +1,7 @@ --- title: Relational Databases superBlock: relational-databases +certification: relational-databases --- ## Introduction to Relational Databases diff --git a/client/src/pages/learn/responsive-web-design-22/index.md b/client/src/pages/learn/responsive-web-design-22/index.md new file mode 100644 index 0000000000..c317eb6879 --- /dev/null +++ b/client/src/pages/learn/responsive-web-design-22/index.md @@ -0,0 +1,20 @@ +--- +title: Responsive Web Design +superBlock: responsive-web-design-22 +certification: responsive-web-design +--- + +## Introduction to Responsive Web Design + +Welcome to Responsive Web Design! + +Let's first take a look at what is in the scope of Responsive Web Design (and what is not). +The Web part of Responsive Web Design means that the content you are creating is designed to be delivered over the web to users' browsers. To do this, you will need to learn the language that browsers use to describe web pages: HTML (Hypertext Markup Language) for content, and CSS (Cascading Style Sheets) for design. + +That brings us to design. Web Design focuses on how websites should look and behave to the end user. There are plenty of additional aspects to websites, including how to host a website so people can access it, how to protect your websites and store your users' data, and the ethos behind what should go on a website in the first place. These are somewhat harder to teach, but do require a good understanding of the capabilities of the web in modern contexts. + +And what of the word Responsive? Well, that brings us to the idea of modern contexts. In today's world, websites are expected to be just as accessible on laptops, tablets and phones. If you compare your user experience on a document-editing program on a computer versus a document-editing program on a mobile, the features on mobile are likely to be far more constrained. This applies in some way to websites as well, but developers have found interesting ways to adapt to the mobile experience, not in the least by harnessing powerful features built for flexible (a.k.a responsive) web displays such as CSS `flexbox` and CSS `grid`. + +Ready to learn about good design practices for the web? Let's get started! + +[Begin Course](https://www.freecodecamp.org/learn/responsive-web-design/basic-html-and-html5/) diff --git a/client/src/pages/learn/responsive-web-design/index.md b/client/src/pages/learn/responsive-web-design/index.md index e365819b31..eef50f2e72 100644 --- a/client/src/pages/learn/responsive-web-design/index.md +++ b/client/src/pages/learn/responsive-web-design/index.md @@ -1,6 +1,7 @@ --- title: Responsive Web Design superBlock: responsive-web-design +certification: responsive-web-design --- ## Introduction to Responsive Web Design diff --git a/client/src/pages/learn/scientific-computing-with-python/index.md b/client/src/pages/learn/scientific-computing-with-python/index.md index a568c760bb..d44aeb1dd2 100644 --- a/client/src/pages/learn/scientific-computing-with-python/index.md +++ b/client/src/pages/learn/scientific-computing-with-python/index.md @@ -1,6 +1,7 @@ --- title: Scientific Computing with Python superBlock: scientific-computing-with-python +certification: scientific-computing-with-python --- ## Introduction to Scientific Computing with Python diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 65af9e912c..9f167b05bf 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -86,6 +86,8 @@ export type MarkdownRemark = { block: string; isBeta: boolean; superBlock: SuperBlocks; + // TODO: make enum like superBlock + certification: string; title: typeof certMap[number]['title']; }; headings: [ @@ -128,55 +130,59 @@ export interface VideoLocaleIds { } export type ChallengeNode = { - block: string; - challengeOrder: number; - challengeType: number; - dashedName: string; - description: string; - challengeFiles: ChallengeFiles; - fields: Fields; - forumTopicId: number; - guideUrl: string; - head: string[]; - helpCategory: string; - id: string; - instructions: string; - isComingSoon: boolean; - internal?: { - content: string; - contentDigest: string; + challenge: { + block: string; + certification: string; + challengeOrder: number; + challengeType: number; + dashedName: string; description: string; - fieldOwners: string[]; - ignoreType: boolean | null; - mediaType: string; - owner: string; - type: string; + challengeFiles: ChallengeFiles; + fields: Fields; + forumTopicId: number; + guideUrl: string; + head: string[]; + hasEditableBoundaries: boolean; + helpCategory: string; + id: string; + instructions: string; + isComingSoon: boolean; + internal?: { + content: string; + contentDigest: string; + description: string; + fieldOwners: string[]; + ignoreType: boolean | null; + mediaType: string; + owner: string; + type: string; + }; + notes: string; + removeComments: boolean; + isLocked: boolean; + isPrivate: boolean; + order: number; + question: Question; + required: Required[]; + solutions: { + [T in FileKey]: FileKeyChallenge; + }; + sourceInstanceName: string; + superOrder: number; + superBlock: SuperBlocks; + tail: string[]; + template: string; + tests: Test[]; + time: string; + title: string; + translationPending: boolean; + url: string; + usesMultifileEditor: boolean; + videoId: string; + videoLocaleIds?: VideoLocaleIds; + bilibiliIds?: BilibiliIds; + videoUrl: string; }; - notes: string; - removeComments: boolean; - isLocked: boolean; - isPrivate: boolean; - order: number; - question: Question; - required: Required[]; - solutions: { - [T in FileKey]: FileKeyChallenge; - }; - sourceInstanceName: string; - superOrder: number; - superBlock: SuperBlocks; - tail: string[]; - template: string; - tests: Test[]; - time: string; - title: string; - translationPending: boolean; - url: string; - usesMultifileEditor: boolean; - videoId: string; - videoLocaleIds?: VideoLocaleIds; - bilibiliIds?: BilibiliIds; - videoUrl: string; }; export type AllChallengeNode = { diff --git a/client/src/redux/settings/settings-sagas.js b/client/src/redux/settings/settings-sagas.js index 73b5ce5e44..00bf9dc895 100644 --- a/client/src/redux/settings/settings-sagas.js +++ b/client/src/redux/settings/settings-sagas.js @@ -1,3 +1,4 @@ +import { omit } from 'lodash-es'; import { call, delay, put, takeLatest, takeEvery } from 'redux-saga/effects'; import { createFlashMessage } from '../../components/Flash/redux'; @@ -85,7 +86,13 @@ function* verifyCertificationSaga({ payload }) { yield put( verifyCertComplete({ ...response, - payload: { ...isCertMap, completedChallenges } + payload: { + ...isCertMap, + completedChallenges: completedChallenges.map(x => ({ + ...omit(x, 'files'), + challengeFiles: x.files ?? null + })) + } }) ); yield put(createFlashMessage(response)); diff --git a/client/src/resources/cert-and-project-map.ts b/client/src/resources/cert-and-project-map.ts index 084ce891d7..c654e6bdbf 100644 --- a/client/src/resources/cert-and-project-map.ts +++ b/client/src/resources/cert-and-project-map.ts @@ -1,7 +1,11 @@ import { SuperBlocks } from '../../../config/certification-settings'; +import envData from '../../../config/env.json'; + +const { showNewCurriculum } = envData; const responsiveWebBase = '/learn/responsive-web-design/responsive-web-design-projects'; +const responsiveWeb22Base = '/learn/responsive-web-design-22'; const jsAlgoBase = '/learn/javascript-algorithms-and-data-structures/' + 'javascript-algorithms-and-data-structures-projects'; @@ -294,38 +298,50 @@ const certMap = [ certSlug: SuperBlocks.RespWebDesign, flag: 'isRespWebDesignCert', projects: [ - { - id: 'bd7158d8c442eddfaeb5bd18', - title: 'Build a Tribute Page', - link: `${responsiveWebBase}/build-a-tribute-page`, - certSlug: SuperBlocks.RespWebDesign - }, { id: '587d78af367417b2b2512b03', title: 'Build a Survey Form', - link: `${responsiveWebBase}/build-a-survey-form`, + link: getResponsiveWebDesignPath('build-a-survey-form', { + showNewCurriculum + }), certSlug: SuperBlocks.RespWebDesign }, { - id: '587d78af367417b2b2512b04', - title: 'Build a Product Landing Page', - link: `${responsiveWebBase}/build-a-product-landing-page`, + id: 'bd7158d8c442eddfaeb5bd18', + title: 'Build a Tribute Page', + link: getResponsiveWebDesignPath('build-a-tribute-page', { + showNewCurriculum + }), certSlug: SuperBlocks.RespWebDesign }, { id: '587d78b0367417b2b2512b05', title: 'Build a Technical Documentation Page', - link: `${responsiveWebBase}/build-a-technical-documentation-page`, + link: getResponsiveWebDesignPath( + 'build-a-technical-documentation-page', + { showNewCurriculum } + ), + certSlug: SuperBlocks.RespWebDesign + }, + { + id: '587d78af367417b2b2512b04', + title: 'Build a Product Landing Page', + link: getResponsiveWebDesignPath('build-a-product-landing-page', { + showNewCurriculum + }), certSlug: SuperBlocks.RespWebDesign }, { id: 'bd7158d8c242eddfaeb5bd13', title: 'Build a Personal Portfolio Webpage', - link: `${responsiveWebBase}/build-a-personal-portfolio-webpage`, + link: getResponsiveWebDesignPath('build-a-personal-portfolio-webpage', { + showNewCurriculum + }), certSlug: SuperBlocks.RespWebDesign } ] }, + { id: '561abd10cb81ac38a17513bc', title: 'JavaScript Algorithms and Data Structures', @@ -709,6 +725,15 @@ const certMap = [ } ] as const; +function getResponsiveWebDesignPath( + project: string, + { showNewCurriculum }: { showNewCurriculum: boolean } +) { + return showNewCurriculum + ? `${responsiveWeb22Base}/${project}-project/${project}` + : `${responsiveWebBase}/${project}/`; +} + const titles = certMap.map(({ title }) => title); type Title = typeof titles[number]; const legacyProjectMap: Partial> = {}; diff --git a/client/src/templates/Challenges/classic/MultifileEditor.js b/client/src/templates/Challenges/classic/MultifileEditor.js index 96f426d6f6..934058f2c8 100644 --- a/client/src/templates/Challenges/classic/MultifileEditor.js +++ b/client/src/templates/Challenges/classic/MultifileEditor.js @@ -7,11 +7,7 @@ import { isDonationModalOpenSelector, userSelector } from '../../../redux'; import { canFocusEditorSelector, consoleOutputSelector, - executeChallenge, - saveEditorContent, - setEditorFocusability, - visibleEditorsSelector, - updateFile + visibleEditorsSelector } from '../redux'; import { getTargetEditor } from '../utils/getTargetEditor'; import './editor.css'; @@ -26,7 +22,6 @@ const propTypes = { description: PropTypes.string, dimensions: PropTypes.object, editorRef: PropTypes.any.isRequired, - executeChallenge: PropTypes.func.isRequired, ext: PropTypes.string, fileKey: PropTypes.string, initialEditorContent: PropTypes.string, @@ -37,11 +32,9 @@ const propTypes = { onStopResize: PropTypes.func, onResize: PropTypes.func }), - saveEditorContent: PropTypes.func.isRequired, - setEditorFocusability: PropTypes.func, theme: PropTypes.string, title: PropTypes.string, - updateFile: PropTypes.func.isRequired, + showProjectPreview: PropTypes.bool, usesMultifileEditor: PropTypes.bool, visibleEditors: PropTypes.shape({ scriptjs: PropTypes.bool, @@ -65,13 +58,6 @@ const mapStateToProps = createSelector( }) ); -const mapDispatchToProps = { - executeChallenge, - saveEditorContent, - setEditorFocusability, - updateFile -}; - const MultifileEditor = props => { const { challengeFiles, @@ -83,7 +69,8 @@ const MultifileEditor = props => { resizeProps, title, visibleEditors: { stylescss, indexhtml, scriptjs, indexjsx }, - usesMultifileEditor + usesMultifileEditor, + showProjectPreview } = props; const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom'; // TODO: the tabs mess up the rendering (scroll doesn't work properly and @@ -145,6 +132,7 @@ const MultifileEditor = props => { theme={editorTheme} title={title} usesMultifileEditor={usesMultifileEditor} + showProjectPreview={showProjectPreview} /> ); @@ -159,4 +147,4 @@ const MultifileEditor = props => { MultifileEditor.displayName = 'MultifileEditor'; MultifileEditor.propTypes = propTypes; -export default connect(mapStateToProps, mapDispatchToProps)(MultifileEditor); +export default connect(mapStateToProps)(MultifileEditor); diff --git a/client/src/templates/Challenges/classic/desktop-layout.tsx b/client/src/templates/Challenges/classic/desktop-layout.tsx index d62e66189c..b289c683e0 100644 --- a/client/src/templates/Challenges/classic/desktop-layout.tsx +++ b/client/src/templates/Challenges/classic/desktop-layout.tsx @@ -2,15 +2,13 @@ import { first } from 'lodash-es'; import React, { useState, ReactElement } from 'react'; import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles'; -import envData from '../../../../../config/env.json'; import { ChallengeFile, ChallengeFiles, ResizeProps } from '../../../redux/prop-types'; import ActionRow from './action-row'; - -const { showUpcomingChanges } = envData; +import EditorTabs from './editor-tabs'; type Pane = { flex: number }; @@ -85,7 +83,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { } = props; const challengeFile = getChallengeFile(); - const projectBasedChallenge = showUpcomingChanges && hasEditableBoundaries; + const projectBasedChallenge = hasEditableBoundaries; const displayPreview = projectBasedChallenge ? showPreview && hasPreview : hasPreview; @@ -124,6 +122,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { )} + {challengeFile && !hasEditableBoundaries && } {challengeFile && ( ({ canFocus: open ? false : canFocus, + previewOpen, isResetting, output, theme, @@ -895,6 +901,18 @@ const Editor = (props: EditorProps): JSX.Element => { } // eslint-disable-next-line react-hooks/exhaustive-deps }, [props.challengeFiles, props.isResetting]); + + useEffect(() => { + const { showProjectPreview, previewOpen } = props; + if (!previewOpen && showProjectPreview) { + const description = document.getElementsByClassName( + 'description-container' + )[0]; + description.classList.add('description-highlighter'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.previewOpen]); + useEffect(() => { const { output } = props; const { model, insideEditDecId } = dataRef.current; diff --git a/client/src/templates/Challenges/classic/mobile-layout.tsx b/client/src/templates/Challenges/classic/mobile-layout.tsx index 7c1881e76d..a6d437a422 100644 --- a/client/src/templates/Challenges/classic/mobile-layout.tsx +++ b/client/src/templates/Challenges/classic/mobile-layout.tsx @@ -1,52 +1,52 @@ import { TabPane, Tabs } from '@freecodecamp/react-bootstrap'; import i18next from 'i18next'; import React, { Component, ReactElement } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; -import { createStructuredSelector } from 'reselect'; -import envData from '../../../../../config/env.json'; import ToolPanel from '../components/tool-panel'; -import { currentTabSelector, moveToTab } from '../redux'; import EditorTabs from './editor-tabs'; -const { showUpcomingChanges } = envData; - -const mapStateToProps = createStructuredSelector({ - currentTab: currentTabSelector as (state: unknown) => number -}); - -const mapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - moveToTab - }, - dispatch - ); interface MobileLayoutProps { - currentTab: number; editor: JSX.Element | null; guideUrl: string; hasEditableBoundaries: boolean; hasNotes: boolean; hasPreview: boolean; instructions: JSX.Element; - moveToTab: typeof moveToTab; notes: ReactElement; preview: JSX.Element; testOutput: JSX.Element; videoUrl: string; usesMultifileEditor: boolean; } -class MobileLayout extends Component { + +enum Tab { + Editor = 'editor', + Preview = 'preview', + Console = 'console', + Notes = 'notes', + Instructions = 'instructions' +} + +interface MobileLayoutState { + currentTab: Tab; +} + +class MobileLayout extends Component { static displayName: string; - componentDidMount() { - if (this.props.currentTab !== 1) this.props.moveToTab(1); - } + + state: MobileLayoutState = { + currentTab: this.props.hasEditableBoundaries ? Tab.Editor : Tab.Instructions + }; + + switchTab = (tab: Tab) => { + this.setState({ + currentTab: tab + }); + }; + render() { + const { currentTab } = this.state; const { - currentTab, - moveToTab, hasEditableBoundaries, instructions, editor, @@ -67,44 +67,41 @@ class MobileLayout extends Component { // Unlike the desktop layout the mobile version does not have an ActionRow, // but still needs a way to switch between the different tabs. - const projectBasedChallenge = showUpcomingChanges && usesMultifileEditor; - - const eventKeys = [1, 2, 3, 4, 5]; return ( <> {!hasEditableBoundaries && ( {instructions} )} - {projectBasedChallenge && } + {usesMultifileEditor && } {editor} {testOutput} - {hasNotes && projectBasedChallenge && ( + {hasNotes && usesMultifileEditor && ( {notes} @@ -112,7 +109,7 @@ class MobileLayout extends Component { )} {hasPreview && ( {preview} @@ -127,4 +124,4 @@ class MobileLayout extends Component { MobileLayout.displayName = 'MobileLayout'; -export default connect(mapStateToProps, mapDispatchToProps)(MobileLayout); +export default MobileLayout; diff --git a/client/src/templates/Challenges/classic/show.tsx b/client/src/templates/Challenges/classic/show.tsx index 1173f93848..3219da779f 100644 --- a/client/src/templates/Challenges/classic/show.tsx +++ b/client/src/templates/Challenges/classic/show.tsx @@ -212,7 +212,9 @@ class ShowClassic extends Component { componentDidMount() { const { data: { - challengeNode: { title } + challengeNode: { + challenge: { title } + } } } = this.props; this.initializeComponent(title); @@ -222,16 +224,20 @@ class ShowClassic extends Component { const { data: { challengeNode: { - title: prevTitle, - fields: { tests: prevTests } + challenge: { + title: prevTitle, + fields: { tests: prevTests } + } } } } = prevProps; const { data: { challengeNode: { - title: currentTitle, - fields: { tests: currTests } + challenge: { + title: currentTitle, + fields: { tests: currTests } + } } } } = this.props; @@ -250,11 +256,13 @@ class ShowClassic extends Component { openModal, data: { challengeNode: { - challengeFiles, - fields: { tests }, - challengeType, - removeComments, - helpCategory + challenge: { + challengeFiles, + fields: { tests }, + challengeType, + removeComments, + helpCategory + } } }, pageContext: { @@ -282,7 +290,7 @@ class ShowClassic extends Component { cancelTests(); } - getChallenge = () => this.props.data.challengeNode; + getChallenge = () => this.props.data.challengeNode.challenge; getBlockNameTitle(t: TFunction) { const { block, superBlock, title } = this.getChallenge(); @@ -334,11 +342,16 @@ class ShowClassic extends Component { renderEditor() { const { + pageContext: { + projectPreview: { showProjectPreview } + }, challengeFiles, data: { challengeNode: { - fields: { tests }, - usesMultifileEditor + challenge: { + fields: { tests }, + usesMultifileEditor + } } } } = this.props; @@ -355,6 +368,7 @@ class ShowClassic extends Component { resizeProps={this.resizeProps} title={title} usesMultifileEditor={usesMultifileEditor} + showProjectPreview={showProjectPreview} /> ) ); @@ -388,21 +402,14 @@ class ShowClassic extends Component { ); } - hasEditableBoundaries() { - const { challengeFiles } = this.props; - return ( - challengeFiles?.some( - challengeFile => challengeFile.editableRegionBoundaries?.length === 2 - ) ?? false - ); - } - render() { const { block, fields: { blockName }, forumTopicId, + hasEditableBoundaries, superBlock, + certification, title, usesMultifileEditor, notes @@ -433,7 +440,7 @@ class ShowClassic extends Component { { block={block} challengeFiles={challengeFiles} editor={this.renderEditor()} - hasEditableBoundaries={this.hasEditableBoundaries()} + hasEditableBoundaries={hasEditableBoundaries} hasNotes={!!notes} hasPreview={this.hasPreview()} instructions={this.renderInstructionsPanel({ @@ -468,6 +475,7 @@ class ShowClassic extends Component { @@ -489,40 +497,44 @@ export default connect( export const query = graphql` query ClassicChallenge($slug: String!) { - challengeNode(fields: { slug: { eq: $slug } }) { - block - title - description - instructions - notes - removeComments - challengeType - helpCategory - videoUrl - superBlock - translationPending - forumTopicId - fields { - blockName - slug - tests { - text - testString + challengeNode(challenge: { fields: { slug: { eq: $slug } } }) { + challenge { + block + title + description + hasEditableBoundaries + instructions + notes + removeComments + challengeType + helpCategory + videoUrl + superBlock + certification + translationPending + forumTopicId + fields { + blockName + slug + tests { + text + testString + } + } + required { + link + src + } + usesMultifileEditor + challengeFiles { + fileKey + ext + name + contents + head + tail + editableRegionBoundaries } - } - required { - link - src - } - usesMultifileEditor - challengeFiles { - fileKey - ext - name - contents - head - tail - editableRegionBoundaries } } } diff --git a/client/src/templates/Challenges/codeally/show.tsx b/client/src/templates/Challenges/codeally/show.tsx index d990c3f7ec..72defa8383 100644 --- a/client/src/templates/Challenges/codeally/show.tsx +++ b/client/src/templates/Challenges/codeally/show.tsx @@ -49,7 +49,9 @@ class ShowCodeAlly extends Component { const { updateChallengeMeta, data: { - challengeNode: { challengeType, title } + challengeNode: { + challenge: { challengeType, title } + } }, pageContext: { challengeMeta } } = this.props; @@ -60,9 +62,11 @@ class ShowCodeAlly extends Component { const { data: { challengeNode: { - title, - fields: { blockName }, - url + challenge: { + title, + fields: { blockName }, + url + } } }, webhookToken = null @@ -94,12 +98,14 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShowCodeAlly); // GraphQL export const query = graphql` query CodeAllyChallenge($slug: String!) { - challengeNode(fields: { slug: { eq: $slug } }) { - title - challengeType - url - fields { - blockName + challengeNode(challenge: { fields: { slug: { eq: $slug } } }) { + challenge { + title + challengeType + url + fields { + blockName + } } } } diff --git a/client/src/templates/Challenges/components/__snapshots__/challenge-title.test.tsx.snap b/client/src/templates/Challenges/components/__snapshots__/challenge-title.test.tsx.snap index 293a4fe488..1d274fc5d2 100644 --- a/client/src/templates/Challenges/components/__snapshots__/challenge-title.test.tsx.snap +++ b/client/src/templates/Challenges/components/__snapshots__/challenge-title.test.tsx.snap @@ -42,12 +42,8 @@ exports[` renders correctly 1`] = ` title text - - icons.passed - renders correctly 1`] = ` width="50" xmlns="http://www.w3.org/2000/svg" > - +