feat(client): add 3 disclosure cde for rdb (#62178)

Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
Shaun Hamilton
2025-10-07 23:27:57 +02:00
committed by GitHub
parent 940a38127b
commit 677ca70eed
8 changed files with 445 additions and 364 deletions
+26 -5
View File
@@ -5,6 +5,15 @@
"aa-test-in-component": { "aa-test-in-component": {
"defaultValue": false "defaultValue": false
}, },
"rdb-codespaces-instructions": {
"defaultValue": true
},
"rdb-local-instructions": {
"defaultValue": true
},
"rdb-ona-instructions": {
"defaultValue": true
},
"landing-top-skill-focused": { "landing-top-skill-focused": {
"defaultValue": false, "defaultValue": false,
"rules": [ "rules": [
@@ -13,8 +22,14 @@
"hashAttribute": "id", "hashAttribute": "id",
"seed": "landing-top-skill-focused", "seed": "landing-top-skill-focused",
"hashVersion": 2, "hashVersion": 2,
"variations": [false, true], "variations": [
"weights": [0.5, 0.5], false,
true
],
"weights": [
0.5,
0.5
],
"key": "landing-top-skill-focused", "key": "landing-top-skill-focused",
"meta": [ "meta": [
{ {
@@ -31,7 +46,7 @@
} }
] ]
}, },
"replace-20-with-25": { "replace-20-with-25": {
"defaultValue": false, "defaultValue": false,
"rules": [ "rules": [
{ {
@@ -39,8 +54,14 @@
"hashAttribute": "id", "hashAttribute": "id",
"seed": "replace-20-with-25", "seed": "replace-20-with-25",
"hashVersion": 2, "hashVersion": 2,
"variations": [false, true], "variations": [
"weights": [0.5, 0.5], false,
true
],
"weights": [
0.5,
0.5
],
"key": "replace-20-with-25", "key": "replace-20-with-25",
"meta": [ "meta": [
{ {
+24 -2
View File
@@ -533,6 +533,26 @@
"project-preview-title": "Here's a preview of what you will build", "project-preview-title": "Here's a preview of what you will build",
"demo-project-title": "Here's an example of a project that meets the requirements", "demo-project-title": "Here's an example of a project that meets the requirements",
"github-required": "<0>Create a GitHub</0> account if you don't have one. You'll need it when you create the virtual Linux server machine. This process may take a few minutes.", "github-required": "<0>Create a GitHub</0> account if you don't have one. You'll need it when you create the virtual Linux server machine. This process may take a few minutes.",
"codespaces": {
"intro": "This course runs in a virtual Linux machine using GitHub Codespaces. Follow these instructions to start the course:",
"step-1": "<0>Create an GitHub account</0> if you don't have one",
"step-2": "Click the start button below",
"step-3": "On that page, click the create button",
"step-4": "Once the virtual Linux machine is finished loading, start the CodeRoad extension by:",
"step-5": "Clicking the \"hamburger\" menu near the top left of the VSCode window,",
"step-6": "Going to the <0>View</0> menu,",
"step-7": "Clicking on the <0>Command Palette</0> option,",
"step-8": "and running the <0>CodeRoad: Start</0> command",
"step-9": "Follow the instructions in CodeRoad to complete the course",
"continue-project": "Clicking the button below will start a new project. If you have previously started the {{title}} course, go to the <0>repository page</0> to re-open a previous workspace.",
"learn-more": "Learn more about <0>Codespace workspaces</0>.",
"logout-warning": "If you log out of freeCodeCamp before you complete the entire {{course}} course, your progress will not be saved to your freeCodeCamp account.",
"sub-step-3": "Navigate to your <0>Codespaces secrets page</0>",
"sub-step-4": "Create a new secret named <0>CODEROAD_WEBHOOK_TOKEN</0>",
"sub-step-5": "In the <0>Value</0> field, paste your token",
"sub-step-6": "In the <0>Repository access</0> field, select the <1>freeCodeCamp/rdb-alpha</1> repository",
"summary": "Codespaces Setup"
},
"ona": { "ona": {
"intro": "This course runs in a virtual Linux machine using Ona. Follow these instructions to start the course:", "intro": "This course runs in a virtual Linux machine using Ona. Follow these instructions to start the course:",
"step-1": "<0>Create an Ona account</0> if you don't have one", "step-1": "<0>Create an Ona account</0> if you don't have one",
@@ -549,7 +569,8 @@
"logout-warning": "If you log out of freeCodeCamp before you complete the entire {{course}} course, your progress will not be saved to your freeCodeCamp account.", "logout-warning": "If you log out of freeCodeCamp before you complete the entire {{course}} course, your progress will not be saved to your freeCodeCamp account.",
"sub-step-3": "Navigate to your <0>Ona secrets page</0>", "sub-step-3": "Navigate to your <0>Ona secrets page</0>",
"sub-step-4": "Create a new secret named <0>CODEROAD_WEBHOOK_TOKEN</0>", "sub-step-4": "Create a new secret named <0>CODEROAD_WEBHOOK_TOKEN</0>",
"sub-step-5": "In the <0>Secret</0> field, paste your token" "sub-step-5": "In the <0>Secret</0> field, paste your token",
"summary": "Ona Setup"
}, },
"local": { "local": {
"intro": "This course runs in a virtual Linux machine on your computer. To run the course, you first need to download each of the following if you don't already have them:", "intro": "This course runs in a virtual Linux machine on your computer. To run the course, you first need to download each of the following if you don't already have them:",
@@ -571,7 +592,8 @@
"step-7": "Copy the course URL below, paste it in the URL input, and click \"Load\".", "step-7": "Copy the course URL below, paste it in the URL input, and click \"Load\".",
"copy-url": "Copy Course URL", "copy-url": "Copy Course URL",
"step-8": "Click \"Start\" to begin.", "step-8": "Click \"Start\" to begin.",
"step-9": "Follow the instructions in CodeRoad to complete the course. Note: You may need to restart the terminal once for terminal settings to take effect and the tests to pass." "step-9": "Follow the instructions in CodeRoad to complete the course. Note: You may need to restart the terminal once for terminal settings to take effect and the tests to pass.",
"summary": "Local Setup"
}, },
"step-1": "Step 1: Complete the project", "step-1": "Step 1: Complete the project",
"step-2": "Step 2: Submit your code", "step-2": "Step 2: Submit your code",
@@ -0,0 +1,190 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Spacer, Button, Callout } from '@freecodecamp/ui';
import { CodeAllyButton } from '../../../components/growth-book/codeally-button';
interface CodespacesInstructionsProps {
challengeType: number;
copyUrl: () => void;
copyUserToken: () => void;
generateUserToken: () => Promise<void>;
isSignedIn: boolean;
title: string;
userToken: string | null;
}
export function CodespacesInstructions({
challengeType,
copyUrl,
copyUserToken,
generateUserToken,
isSignedIn,
title,
userToken
}: CodespacesInstructionsProps) {
const { t } = useTranslation();
function openCodespaces() {
const codespacesUrl = `https://codespaces.new/freeCodeCamp/rdb-alpha`;
window.open(codespacesUrl, '_blank');
}
return (
<div className='ca-description'>
<p>{t('learn.codespaces.intro')}</p>
<ol>
<li>
<Trans i18nKey='learn.codespaces.step-1'>
<a
href='https://github.com/signup'
rel='noopener noreferrer'
target='_blank'
>
placeholder
</a>
</Trans>
</li>
{isSignedIn && (
<>
<Spacer size='s' />
<p>{t('learn.local.sub-step-heading')}</p>
<ol>
<li>{t('learn.local.sub-step-1')}</li>
<Spacer size='xxs' />
<Button
disabled={!!userToken}
block={true}
onClick={() => void generateUserToken()}
>
{t('learn.local.generate-token-btn')}
</Button>
<Spacer size='xs' />
<li>{t('learn.local.sub-step-2')}</li>
<Spacer size='xxs' />
<Button
disabled={!userToken}
block={true}
onClick={copyUserToken}
>
{t('learn.local.copy-token-btn')}
</Button>
<Spacer size='xs' />
<li>
<Trans i18nKey='learn.codespaces.sub-step-3'>
<a
href='https://github.com/settings/codespaces/secrets/new'
rel='noopener noreferrer'
target='_blank'
>
Codespaces secrets page
</a>
</Trans>
</li>
<li>
<Trans i18nKey='learn.codespaces.sub-step-4'>
<code>placeholder</code>
</Trans>
</li>
<li>
<Trans i18nKey='learn.codespaces.sub-step-5'>
<code>placeholder</code>
</Trans>
</li>
<li>
<Trans i18nKey='learn.codespaces.sub-step-6'>
<code>placeholder</code>
<code>placeholder</code>
</Trans>
</li>
</ol>
<Spacer size='s' />
</>
)}
<li>{t('learn.codespaces.step-2')}</li>
<li>{t('learn.codespaces.step-3')}</li>
<li>
{t('learn.codespaces.step-4')}
<ul>
<li>{t('learn.codespaces.step-5')}</li>
<li>
<Trans i18nKey='learn.codespaces.step-6'>
<code>placeholder</code>
</Trans>
</li>
<li>
<Trans i18nKey='learn.codespaces.step-7'>
<code>placeholder</code>
</Trans>
</li>
<li>
<Trans i18nKey='learn.codespaces.step-8'>
<code>placeholder</code>
</Trans>
</li>
<li>{t('learn.local.step-6')}</li>
<li>{t('learn.local.step-7')}</li>
<Spacer size='xxs' />
<Button block={true} onClick={copyUrl}>
{t('learn.local.copy-url')}
</Button>
<Spacer size='xs' />
<li>{t('learn.local.step-8')}</li>
</ul>
</li>
<li>{t('learn.codespaces.step-9')}</li>
</ol>
<Spacer size='m' />
<CodespacesContinueAlert title={title} />
{isSignedIn && <CodespacesLogoutAlert course={title} />}
<CodeAllyButton challengeType={challengeType} onClick={openCodespaces} />
</div>
);
}
interface CodespacesContinueAlertProps {
title: string;
}
function CodespacesContinueAlert({ title }: CodespacesContinueAlertProps) {
return (
<Callout variant='info'>
<Trans values={{ title }} i18nKey='learn.codespaces.continue-project'>
<a
href='https://github.com/freeCodeCamp/rdb-alpha'
rel='noopener noreferrer'
target='_blank'
>
placeholder
</a>
</Trans>
<Spacer size='m' />
<Trans i18nKey='learn.codespaces.learn-more'>
<a
href='https://forum.freecodecamp.org/t/relational-database-curriculum-in-codespaces/761449'
rel='noopener noreferrer'
target='_blank'
>
placeholder
</a>
</Trans>
</Callout>
);
}
interface CodespacesLogoutAlertProps {
course: string;
}
function CodespacesLogoutAlert({
course
}: CodespacesLogoutAlertProps): JSX.Element {
const { t } = useTranslation();
return (
<Callout variant='danger'>
{t('learn.codespaces.logout-warning', { course })}
</Callout>
);
}
@@ -1,103 +1,29 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Spacer, Button } from '@freecodecamp/ui'; import { Spacer, Button } from '@freecodecamp/ui';
import { postUserToken } from '../../../utils/ajax';
import { createFlashMessage } from '../../../components/Flash/redux';
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
import {
isSignedInSelector,
userTokenSelector
} from '../../../redux/selectors';
import { updateUserToken } from '../../../redux/actions';
import { Link } from '../../../components/helpers'; import { Link } from '../../../components/helpers';
import RdbLocalLogoutAlert from './rdb-local-logout-alert'; import RdbLocalLogoutAlert from './rdb-local-logout-alert';
const mapStateToProps = (state: unknown) => ({ interface LocalInstructionsProps {
isSignedIn: isSignedInSelector(state), copyUrl: () => void;
userToken: userTokenSelector(state) as string | null copyUserToken: () => void;
}); generateUserToken: () => Promise<void>;
const mapDispatchToProps = {
createFlashMessage,
updateUserToken
};
interface RdbLocalInstructionsProps {
course: string;
createFlashMessage: typeof createFlashMessage;
isSignedIn: boolean; isSignedIn: boolean;
updateUserToken: (arg0: string) => void; title: string;
url: string;
userToken: string | null; userToken: string | null;
} }
function RdbLocalInstructions({ export function LocalInstructions({
course, copyUrl,
createFlashMessage, copyUserToken,
generateUserToken,
isSignedIn, isSignedIn,
updateUserToken, title,
url,
userToken userToken
}: RdbLocalInstructionsProps): JSX.Element { }: LocalInstructionsProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const coderoadTutorial = `https://raw.githubusercontent.com/${url}/main/tutorial.json`;
const generateUserToken = async () => {
const createUserTokenResponse = await postUserToken();
const { data = { userToken: null } } = createUserTokenResponse;
if (data?.userToken) {
updateUserToken(data.userToken);
createFlashMessage({
type: 'success',
message: FlashMessages.UserTokenGenerated
});
} else {
createFlashMessage({
type: 'danger',
message: FlashMessages.UserTokenGenerateError
});
}
};
const copyUserToken = () => {
navigator.clipboard.writeText(userToken ?? '').then(
() => {
createFlashMessage({
type: 'success',
message: FlashMessages.UserTokenCopied
});
},
() => {
createFlashMessage({
type: 'danger',
message: FlashMessages.UserTokenCopyError
});
}
);
};
const copyUrl = () => {
navigator.clipboard.writeText(coderoadTutorial ?? '').then(
() => {
createFlashMessage({
type: 'success',
message: FlashMessages.CourseUrlCopied
});
},
() => {
createFlashMessage({
type: 'danger',
message: FlashMessages.CourseUrlCopyError
});
}
);
};
return ( return (
<div className='ca-description'> <div className='ca-description'>
<p>{t('learn.local.intro')}</p> <p>{t('learn.local.intro')}</p>
@@ -174,7 +100,7 @@ function RdbLocalInstructions({
</Trans> </Trans>
</li> </li>
<Spacer size='xs' /> <Spacer size='xs' />
<RdbLocalLogoutAlert course={course} /> <RdbLocalLogoutAlert title={title} />
</ol> </ol>
<Spacer size='s' /> <Spacer size='s' />
</> </>
@@ -203,10 +129,3 @@ function RdbLocalInstructions({
</div> </div>
); );
} }
RdbLocalInstructions.displayName = 'RdbLocalInstructions';
export default connect(
mapStateToProps,
mapDispatchToProps
)(RdbLocalInstructions);
@@ -1,101 +1,40 @@
import React from 'react'; import React from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { Spacer, Button } from '@freecodecamp/ui'; import { Spacer, Button } from '@freecodecamp/ui';
import { postUserToken } from '../../../utils/ajax';
import { createFlashMessage } from '../../../components/Flash/redux';
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
import { import { CodeAllyButton } from '../../../components/growth-book/codeally-button';
isSignedInSelector,
userTokenSelector
} from '../../../redux/selectors';
import { updateUserToken } from '../../../redux/actions';
import RdbLocalLogoutAlert from './rdb-local-logout-alert'; import RdbOnaContinueAlert from './rdb-ona-continue-alert';
import RdbOnaLogoutAlert from './rdb-ona-logout-alert';
const mapStateToProps = (state: unknown) => ({ interface OneInstructionsProps {
isSignedIn: isSignedInSelector(state), challengeType: number;
userToken: userTokenSelector(state) as string | null copyUrl: () => void;
}); copyUserToken: () => void;
generateUserToken: () => Promise<void>;
const mapDispatchToProps = {
createFlashMessage,
updateUserToken
};
interface RdbOnaInstructionsProps {
course: string;
createFlashMessage: typeof createFlashMessage;
isSignedIn: boolean; isSignedIn: boolean;
updateUserToken: (arg0: string) => void; title: string;
url: string;
userToken: string | null; userToken: string | null;
} }
function RdbOnaInstructions({ export function OnaInstructions({
course, challengeType,
createFlashMessage, copyUrl,
copyUserToken,
generateUserToken,
isSignedIn, isSignedIn,
updateUserToken, title,
url,
userToken userToken
}: RdbOnaInstructionsProps): JSX.Element { }: OneInstructionsProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const coderoadTutorial = `https://raw.githubusercontent.com/${url}/main/tutorial.json`; function openOna() {
const repoUrl = `https://github.com/freeCodeCamp/rdb-alpha`;
const onaDomain = `https://app.ona.com/`;
const onaUrl = `${onaDomain}#${repoUrl}`;
const generateUserToken = async () => { window.open(onaUrl, '_blank');
const createUserTokenResponse = await postUserToken(); }
const { data = { userToken: null } } = createUserTokenResponse;
if (data?.userToken) {
updateUserToken(data.userToken);
createFlashMessage({
type: 'success',
message: FlashMessages.UserTokenGenerated
});
} else {
createFlashMessage({
type: 'danger',
message: FlashMessages.UserTokenGenerateError
});
}
};
const copyUserToken = () => {
navigator.clipboard.writeText(userToken ?? '').then(
() => {
createFlashMessage({
type: 'success',
message: FlashMessages.UserTokenCopied
});
},
() => {
createFlashMessage({
type: 'danger',
message: FlashMessages.UserTokenCopyError
});
}
);
};
const copyUrl = () => {
navigator.clipboard.writeText(coderoadTutorial ?? '').then(
() => {
createFlashMessage({
type: 'success',
message: FlashMessages.CourseUrlCopied
});
},
() => {
createFlashMessage({
type: 'danger',
message: FlashMessages.CourseUrlCopyError
});
}
);
};
return ( return (
<div className='ca-description'> <div className='ca-description'>
@@ -158,8 +97,6 @@ function RdbOnaInstructions({
<code>placeholder</code> <code>placeholder</code>
</Trans> </Trans>
</li> </li>
<Spacer size='xs' />
<RdbLocalLogoutAlert course={course} />
</ol> </ol>
<Spacer size='s' /> <Spacer size='s' />
</> </>
@@ -185,6 +122,7 @@ function RdbOnaInstructions({
<code>placeholder</code> <code>placeholder</code>
</Trans> </Trans>
</li> </li>
<li>{t('learn.local.step-6')}</li>
<li>{t('learn.local.step-7')}</li> <li>{t('learn.local.step-7')}</li>
<Spacer size='xxs' /> <Spacer size='xxs' />
<Button block={true} onClick={copyUrl}> <Button block={true} onClick={copyUrl}>
@@ -196,10 +134,10 @@ function RdbOnaInstructions({
</li> </li>
<li>{t('learn.ona.step-9')}</li> <li>{t('learn.ona.step-9')}</li>
</ol> </ol>
<Spacer size='m' />
<RdbOnaContinueAlert course={title} />
{isSignedIn && <RdbOnaLogoutAlert course={title} />}
<CodeAllyButton challengeType={challengeType} onClick={openOna} />
</div> </div>
); );
} }
RdbOnaInstructions.displayName = 'RdbOnaInstructions';
export default connect(mapStateToProps, mapDispatchToProps)(RdbOnaInstructions);
@@ -3,17 +3,15 @@ import { useTranslation } from 'react-i18next';
import { Callout } from '@freecodecamp/ui'; import { Callout } from '@freecodecamp/ui';
interface RdbLocalLogoutAlertProps { interface RdbLocalLogoutAlertProps {
course: string; title: string;
} }
function RdbLocalLogoutAlert({ function RdbLocalLogoutAlert({ title }: RdbLocalLogoutAlertProps): JSX.Element {
course
}: RdbLocalLogoutAlertProps): JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Callout variant='danger'> <Callout variant='danger'>
{t('learn.local.logout-warning', { course })} {t('learn.local.logout-warning', { course: title })}
</Callout> </Callout>
); );
} }
+162 -169
View File
@@ -1,6 +1,6 @@
// Package Utilities // Package Utilities
import { graphql } from 'gatsby'; import { graphql } from 'gatsby';
import React, { useEffect, useRef } from 'react'; import React, { Fragment, useEffect, useRef } from 'react';
import Helmet from 'react-helmet'; import Helmet from 'react-helmet';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
@@ -48,16 +48,13 @@ import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
import { SuperBlocks } from '../../../../../shared-dist/config/curriculum'; import { SuperBlocks } from '../../../../../shared-dist/config/curriculum';
import { CodeAllyDown } from '../../../components/growth-book/codeally-down'; import { CodeAllyDown } from '../../../components/growth-book/codeally-down';
import { postUserToken } from '../../../utils/ajax'; import { postUserToken } from '../../../utils/ajax';
import { CodeAllyButton } from '../../../components/growth-book/codeally-button';
import RdbOnaContinueAlert from './rdb-ona-continue-alert';
import RdbOnaInstructions from './rdb-ona-instructions';
import RdbOnaLogoutAlert from './rdb-ona-logout-alert';
import RdbLocalInstructions from './rdb-local-instructions';
import RdbStep1Instructions from './rdb-step-1-instructions'; import RdbStep1Instructions from './rdb-step-1-instructions';
import RdbStep2Instructions from './rdb-step-2-instructions'; import RdbStep2Instructions from './rdb-step-2-instructions';
import { LocalInstructions } from './local-instructions';
import { OnaInstructions } from './ona-instructions';
import './codeally.css'; import './codeally.css';
import { CodespacesInstructions } from './codespaces-instructions';
// Redux // Redux
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
@@ -116,33 +113,43 @@ interface ShowCodeAllyProps {
userToken: string | null; userToken: string | null;
} }
function ShowCodeAlly(props: ShowCodeAllyProps) { function ShowCodeAlly({
completedChallenges,
data,
isChallengeCompleted,
isSignedIn,
partiallyCompletedChallenges,
t,
updateSolutionFormValues,
userToken,
updateUserToken,
createFlashMessage,
challengeMounted,
initTests,
pageContext: { challengeMeta },
updateChallengeMeta,
openCompletionModal
}: ShowCodeAllyProps) {
const container = useRef<HTMLElement>(null); const container = useRef<HTMLElement>(null);
const { const {
completedChallenges, challengeNode: {
data: { challenge: {
challengeNode: { block,
challenge: { challengeType,
block, fields: { tests },
challengeType, description,
description, helpCategory,
id: challengeId, id: challengeId,
instructions, instructions,
notes, notes,
superBlock, superBlock,
title, title,
translationPending, translationPending,
url url
}
} }
}, }
isChallengeCompleted, } = data;
isSignedIn,
partiallyCompletedChallenges,
t,
updateSolutionFormValues
} = props;
const blockNameTitle = `${t( const blockNameTitle = `${t(
`intro:${superBlock}.blocks.${block}.title` `intro:${superBlock}.blocks.${block}.title`
@@ -158,22 +165,6 @@ function ShowCodeAlly(props: ShowCodeAllyProps) {
); );
useEffect(() => { useEffect(() => {
const {
challengeMounted,
data: {
challengeNode: {
challenge: {
fields: { tests },
challengeType,
helpCategory,
title
}
}
},
pageContext: { challengeMeta },
initTests,
updateChallengeMeta
} = props;
initTests(tests); initTests(tests);
const challengePaths = getChallengePaths({ const challengePaths = getChallengePaths({
currentCurriculumPaths: challengeMeta currentCurriculumPaths: challengeMeta
@@ -191,54 +182,11 @@ function ShowCodeAlly(props: ShowCodeAllyProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
function openOna() {
const repoUrl = `https://github.com/freeCodeCamp/rdb-alpha`;
const onaDomain = `https://app.ona.com/`;
const onaUrl = `${onaDomain}#${repoUrl}`;
window.open(onaUrl, '_blank');
}
const startCourse = async () => {
const { isSignedIn, userToken, updateUserToken } = props;
if (!isSignedIn) {
openOna();
} else if (!userToken) {
const createUserTokenResponse = await postUserToken();
const { data = { userToken: null } } = createUserTokenResponse;
if (data?.userToken) {
updateUserToken(data.userToken);
openOna();
} else {
createFlashMessage({
type: 'danger',
message: FlashMessages.StartProjectErr
});
}
} else {
openOna();
}
};
const handleSubmit = ({ const handleSubmit = ({
showCompletionModal showCompletionModal
}: { }: {
showCompletionModal: boolean; showCompletionModal: boolean;
}) => { }) => {
const {
completedChallenges,
createFlashMessage,
data: {
challengeNode: {
challenge: { id: challengeId }
}
},
openCompletionModal,
partiallyCompletedChallenges
} = props;
const isPartiallyCompleted = partiallyCompletedChallenges.some( const isPartiallyCompleted = partiallyCompletedChallenges.some(
challenge => challenge.id === challengeId challenge => challenge.id === challengeId
); );
@@ -257,7 +205,85 @@ function ShowCodeAlly(props: ShowCodeAllyProps) {
} }
}; };
const onaDeprecated = useFeature('gitpod-deprecated').on; const rdbLocalInstructions = useFeature('rdb-local-instructions');
const rdbCodespacesInstructions = useFeature('rdb-codespaces-instructions');
const rdbOnaInstructions = useFeature('rdb-ona-instructions');
const coderoadTutorial = `https://raw.githubusercontent.com/${url}/main/tutorial.json`;
async function generateUserToken() {
const createUserTokenResponse = await postUserToken();
const { data = { userToken: null } } = createUserTokenResponse;
if (data?.userToken) {
updateUserToken(data.userToken);
createFlashMessage({
type: 'success',
message: FlashMessages.UserTokenGenerated
});
} else {
createFlashMessage({
type: 'danger',
message: FlashMessages.UserTokenGenerateError
});
}
}
function copyUserToken() {
navigator.clipboard.writeText(userToken ?? '').then(
() => {
createFlashMessage({
type: 'success',
message: FlashMessages.UserTokenCopied
});
},
() => {
createFlashMessage({
type: 'danger',
message: FlashMessages.UserTokenCopyError
});
}
);
}
function copyUrl() {
navigator.clipboard.writeText(coderoadTutorial ?? '').then(
() => {
createFlashMessage({
type: 'success',
message: FlashMessages.CourseUrlCopied
});
},
() => {
createFlashMessage({
type: 'danger',
message: FlashMessages.CourseUrlCopyError
});
}
);
}
const setups = [
{
name: t('learn.codespaces.summary'),
component: CodespacesInstructions,
on: rdbCodespacesInstructions.on
},
{
name: t('learn.local.summary'),
component: LocalInstructions,
on: rdbLocalInstructions.on
},
{
name: t('learn.ona.summary'),
component: OnaInstructions,
on: rdbOnaInstructions.on
}
];
const setupsToShow = setups.filter(setup => {
return setup.on;
});
return ( return (
<Hotkeys containerRef={container}> <Hotkeys containerRef={container}>
@@ -279,88 +305,55 @@ function ShowCodeAlly(props: ShowCodeAllyProps) {
<PrismFormatted text={description} /> <PrismFormatted text={description} />
<Spacer size='m' /> <Spacer size='m' />
{onaDeprecated ? ( {setupsToShow.map(({ name, component: SetupComponent }, i) => (
<Fragment key={name}>
<details
open={i === 0}
style={{ border: '1px solid #ccc', padding: '16px' }}
>
<summary>{name}</summary>
<Spacer size='s' />
<SetupComponent
{...{
challengeType,
copyUrl,
copyUserToken,
generateUserToken,
isSignedIn,
title,
userToken
}}
/>
</details>
<Spacer size='s' />
</Fragment>
))}
<Spacer size='m' />
{isSignedIn && challengeType === challengeTypes.codeAllyCert && (
<> <>
<RdbLocalInstructions course={title} url={url} /> <div className='ca-description'>
{t('learn.complete-both-steps')}
</div>
<hr />
<Spacer size='m' /> <Spacer size='m' />
{isSignedIn && <RdbStep1Instructions
challengeType === challengeTypes.codeAllyCert && ( instructions={instructions}
<> isCompleted={isPartiallyCompleted || isCompleted}
<div className='ca-description'> />
{t('learn.complete-both-steps')} <hr />
</div>
<hr />
<Spacer size='m' />
<RdbStep1Instructions
instructions={instructions}
isCompleted={isPartiallyCompleted || isCompleted}
/>
<hr />
<Spacer size='m' />
<RdbStep2Instructions
isCompleted={isCompleted}
notes={notes}
/>
<Spacer size='m' />
<SolutionForm
challengeType={challengeType}
description={description}
onSubmit={handleSubmit}
updateSolutionForm={updateSolutionFormValues}
/>
</>
)}
</>
) : (
<>
<RdbOnaInstructions course={title} url={url} />
<Spacer size='m' /> <Spacer size='m' />
{isSignedIn && <RdbStep2Instructions
challengeType === challengeTypes.codeAllyCert ? ( isCompleted={isCompleted}
<> notes={notes}
<div className='ca-description'> />
{t('learn.complete-both-steps')} <Spacer size='m' />
</div> <SolutionForm
<hr /> challengeType={challengeType}
<Spacer size='m' /> description={description}
<RdbStep1Instructions onSubmit={handleSubmit}
instructions={instructions} updateSolutionForm={updateSolutionFormValues}
isCompleted={isPartiallyCompleted || isCompleted} />
/>
<Spacer size='m' />
<RdbOnaContinueAlert course={title} />
{isSignedIn && <RdbOnaLogoutAlert course={title} />}
<CodeAllyButton
challengeType={challengeType}
//eslint-disable-next-line @typescript-eslint/no-misused-promises
onClick={startCourse}
/>
<hr />
<Spacer size='m' />
<RdbStep2Instructions
isCompleted={isCompleted}
notes={notes}
/>
<Spacer size='m' />
<SolutionForm
challengeType={challengeType}
description={description}
onSubmit={handleSubmit}
updateSolutionForm={updateSolutionFormValues}
/>
</>
) : (
<>
<RdbOnaContinueAlert course={title} />
{isSignedIn && <RdbOnaLogoutAlert course={title} />}
<CodeAllyButton
challengeType={challengeType}
//eslint-disable-next-line @typescript-eslint/no-misused-promises
onClick={startCourse}
/>
</>
)}
<Spacer size='xxs' />
</> </>
)} )}
+1 -1
View File
@@ -28,7 +28,7 @@ test.describe('After creating token', () => {
await page.goto( await page.goto(
'/learn/relational-database/learn-bash-by-building-a-boilerplate/build-a-boilerplate' '/learn/relational-database/learn-bash-by-building-a-boilerplate/build-a-boilerplate'
); );
await page.getByRole('button', { name: 'Start the course' }).click(); await page.getByText('Generate User Token').first().click();
await page.goto('/settings'); await page.goto('/settings');
// Set `exact` to `true` to only match the panel heading // Set `exact` to `true` to only match the panel heading