mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(client): add 3 disclosure cde for rdb (#62178)
Co-authored-by: Ahmad Abdolsaheb <ahmad.abdolsaheb@gmail.com>
This commit is contained in:
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+12
-93
@@ -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);
|
|
||||||
+27
-89
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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' />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user