This commit is contained in:
Shaun Hamilton
2024-01-30 10:36:46 +01:00
committed by GitHub
parent b3bb5d245c
commit 3243a3852d
30 changed files with 186 additions and 114 deletions
@@ -1,3 +1,4 @@
import { Button } from '@freecodecamp/react-bootstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRightToBracket } from '@fortawesome/free-solid-svg-icons';
import React, { ReactNode } from 'react';
@@ -31,7 +32,8 @@ const Login = ({
const href = isSignedIn ? `${homeLocation}/learn` : `${apiLocation}/signin`;
return (
<a
<Button
bsStyle='default'
className={(block ? 'btn-cta-big btn-block' : '') + ' signup-btn btn-cta'}
data-test-label={dataTestLabel}
data-playwright-test-label='header-sign-in-button'
@@ -42,7 +44,7 @@ const Login = ({
<span className='sr-only'> {t('buttons.sign-in')}</span>
</span>
<span className='login-btn-text'>{children || t('buttons.sign-in')}</span>
</a>
</Button>
);
};
@@ -342,7 +342,6 @@ li > button.nav-link-signout:not([aria-disabled='true']):is(:hover, :focus) {
max-height: var(--header-element-size);
min-width: var(--header-element-size);
padding: 0 4px;
text-decoration: none;
}
@media (min-width: 601px) {
@@ -29,7 +29,7 @@ test('should render', () => {
const button = screen.getByText(/submit/i);
expect(button).toHaveAttribute('type', 'submit');
expect(button).toHaveAttribute('aria-disabled', 'true');
expect(button).toBeDisabled();
});
test('should render with default values', () => {
@@ -3,7 +3,7 @@
exports[`<BlockSaveButton /> snapshot 1`] = `
<div>
<button
class=" relative inline-block mt-[0.5px] border-solid border-3 active:before:w-full active:before:h-full active:before:absolute active:before:inset-0 active:before:border-3 active:before:border-transparent active:before:bg-gray-900 active:before:opacity-20 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 focus:outline-none focus-visible:ring focus-visible:ring-focus-outline-color text-center cursor-pointer no-underline block w-full border-foreground-secondary bg-background-quaternary text-foreground-secondary hover:bg-foreground-primary hover:text-background-primary hover:border-foreground-secondary dark:hover:bg-background-primary dark:hover:text-foreground-primary px-3 py-1.5 text-md"
class="btn btn-primary btn-block"
type="submit"
>
buttons.save
@@ -1,4 +1,4 @@
import { Button, type ButtonProps } from '@freecodecamp/ui';
import { Button } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -9,7 +9,7 @@ function BlockSaveButton({
}: {
children?: React.ReactNode;
disabled?: boolean;
bgSize?: ButtonProps['size'];
bgSize?: string;
}): JSX.Element {
const { t } = useTranslation();
@@ -18,7 +18,8 @@ function BlockSaveButton({
block={true}
// the button is used to submit solutions in projects that require external URL
// these buttons don't use bgSize, that's why the bgSize is optional.
size={bgSize}
bsSize={bgSize}
bsStyle='primary'
type='submit'
{...restProps}
>
@@ -0,0 +1,51 @@
.toggle-active.btn[disabled] {
background-color: var(--secondary-color);
color: var(--secondary-background);
opacity: 1;
}
:is(
.about-settings,
.privacy-settings,
.email-settings,
#usernameSettings,
#camper-identity,
#internet-presence,
#portfolio-items,
#honesty-policy
)
:is(
button[aria-disabled='true'],
button[aria-disabled='true']:is(:focus, :hover)
) {
background-color: var(--quaternary-background);
color: var(--secondary-color);
opacity: 0.65;
cursor: not-allowed;
}
.toggle-not-active {
background-color: var(--quaternary-background);
color: var(--secondary-color);
}
.toggle-not-active:hover {
color: var(--secondary-background);
}
.toggle-not-active:hover,
.toggle-not-active:focus {
background-color: var(--secondary-color);
}
.btn-group .btn.toggle-not-active,
.btn-group .btn.toggle-active {
border-color: var(--tertiary-color);
padding-inline: 30px;
display: flex;
}
.btn-group .btn-primary,
.btn-group .btn-primary:focus,
.btn-group .btn-primary:hover {
border-color: var(--secondary-color);
}
+4 -2
View File
@@ -292,14 +292,16 @@ fieldset[disabled] .btn-primary.focus {
.btn-cta {
background-color: #feac32;
background-image: linear-gradient(#fecc4c, #ffac33);
border: 3px solid #feac32;
border-width: 3px;
border-color: #feac32;
color: #0a0a23 !important;
}
.btn-cta:hover,
.btn-cta:focus,
.btn-cta:active:hover {
background-color: #fecc4c !important;
border: 3px solid #f1a02a;
border-width: 3px;
border-color: #f1a02a;
background-image: none;
color: #0a0a23 !important;
}
@@ -44,10 +44,13 @@ exports[`<Honesty /> <Honesty /> snapshot when isHonest is false: Honesty 1`] =
</p>
</y>
<Button
active={false}
aria-disabled={false}
block={true}
bsClass="btn"
bsStyle="primary"
disabled={false}
onClick={[Function]}
variant="primary"
>
buttons.agree-honesty
</Button>
@@ -99,10 +102,13 @@ exports[`<Honesty /> <Honesty /> snapshot when isHonest is true: HonestyAccepted
</p>
</y>
<Button
active={false}
aria-disabled={true}
block={true}
disabled={true}
bsClass="btn"
bsStyle="primary"
disabled={false}
onClick={[Function]}
variant="primary"
>
buttons.accepted-honesty
</Button>
+2 -2
View File
@@ -267,8 +267,8 @@ class AboutSettings extends Component<AboutProps, AboutState> {
</FormGroup>
</div>
<BlockSaveButton
disabled={this.isFormPristine()}
bgSize='large'
aria-disabled={this.isFormPristine()}
bgSize='lg'
{...(this.isFormPristine() && { tabIndex: -1 })}
>
{t('buttons.save')}{' '}
@@ -1,3 +1,4 @@
import { Button } from '@freecodecamp/react-bootstrap';
import { Link, navigate } from 'gatsby';
import { find } from 'lodash-es';
import React, { MouseEvent, useState } from 'react';
@@ -6,7 +7,7 @@ import type { TFunction } from 'i18next';
import { createSelector } from 'reselect';
import ScrollableAnchor, { configureAnchors } from 'react-scrollable-anchor';
import { connect } from 'react-redux';
import { Table, Button } from '@freecodecamp/ui';
import { Table } from '@freecodecamp/ui';
import { regeneratePathAndHistory } from '../../../../shared/utils/polyvinyl';
import ProjectPreviewModal from '../../templates/Challenges/components/project-preview-modal';
@@ -179,6 +180,12 @@ const LegacyFullStack = (props: CertificationSettingsProps) => {
const certSlug = Certification.LegacyFullStack;
const certLocation = `/certification/${username}/${certSlug}`;
const buttonStyle = {
marginBottom: '30px',
padding: '6px 12px',
fontSize: '18px'
};
const createClickHandler =
(certSlug: keyof typeof certSlugTypeMap) =>
(e: MouseEvent<HTMLButtonElement>) => {
@@ -218,27 +225,29 @@ const LegacyFullStack = (props: CertificationSettingsProps) => {
</ul>
</div>
<div>
<div className={'col-xs-12'}>
{fullStackClaimable ? (
<Button
size='small'
variant='primary'
block={true}
bsSize='sm'
bsStyle='primary'
className={'col-xs-12'}
href={certLocation}
id={'button-' + certSlug}
// This floating promise is acceptable
onClick={void createClickHandler(certSlug)}
onClick={createClickHandler(certSlug)}
style={buttonStyle}
target='_blank'
>
{isFullStackCert ? t('buttons.show-cert') : t('buttons.claim-cert')}
</Button>
) : (
<Button
size='small'
variant='primary'
block={true}
bsSize='sm'
bsStyle='primary'
className={'col-xs-12'}
disabled={true}
id={'button-' + certSlug}
style={buttonStyle}
target='_blank'
>
{t('buttons.claim-cert')}
</Button>
@@ -393,11 +402,11 @@ function CertificationSettings(props: CertificationSettingsProps) {
<td colSpan={2}>
<Button
block={true}
variant='primary'
bsStyle='primary'
className={'col-xs-12'}
href={certLocation}
data-cy={`btn-for-${certSlug}`}
// This floating promise is acceptable
onClick={void clickHandler}
onClick={clickHandler}
>
{isCert ? t('buttons.show-cert') : t('buttons.claim-cert')}{' '}
<span className='sr-only'>{certName}</span>
@@ -0,0 +1,15 @@
.btn-danger {
background-color: var(--danger-color);
color: var(--danger-background);
border-color: var(--danger-background);
}
.btn-danger:hover,
.btn-danger:focus {
color: var(--danger-color);
background-color: var(--danger-background);
border-color: var(--danger-background);
}
.danger-zone p {
color: var(--danger-color);
}
+10 -5
View File
@@ -1,16 +1,19 @@
import { Button } from '@freecodecamp/react-bootstrap';
import React, { useState } from 'react';
import type { TFunction } from 'i18next';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import type { Dispatch } from 'redux';
import { Panel, Button } from '@freecodecamp/ui';
import { Panel } from '@freecodecamp/ui';
import { deleteAccount, resetProgress } from '../../redux/settings/actions';
import { FullWidthRow, Spacer } from '../helpers';
import DeleteModal from './delete-modal';
import ResetModal from './reset-modal';
import './danger-zone.css';
interface DangerZoneProps {
deleteAccount: () => void;
resetProgress: () => void;
@@ -49,8 +52,9 @@ function DangerZone({ deleteAccount, resetProgress, t }: DangerZoneProps) {
<FullWidthRow>
<Button
block={true}
size='large'
variant='danger'
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={toggleResetModal}
type='button'
>
@@ -59,8 +63,9 @@ function DangerZone({ deleteAccount, resetProgress, t }: DangerZoneProps) {
<Spacer size='small' />
<Button
block={true}
size='large'
variant='danger'
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={toggleDeleteModal}
type='button'
>
@@ -1,10 +1,11 @@
import { Modal } from '@freecodecamp/react-bootstrap';
import { Button, Modal } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Button } from '@freecodecamp/ui';
import { Spacer } from '../helpers';
import './danger-zone.css';
type DeleteModalProps = {
delete: () => void;
onHide: () => void;
@@ -43,8 +44,9 @@ function DeleteModal(props: DeleteModalProps): JSX.Element {
<hr />
<Button
block={true}
size='large'
variant='primary'
bsSize='lg'
bsStyle='primary'
className='btn-invert'
onClick={props.onHide}
type='button'
>
@@ -53,8 +55,9 @@ function DeleteModal(props: DeleteModalProps): JSX.Element {
<Spacer size='small' />
<Button
block={true}
size='large'
variant='danger'
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={props.delete}
type='button'
>
+9 -12
View File
@@ -1,11 +1,11 @@
import { Button } from '@freecodecamp/react-bootstrap';
import {
HelpBlock,
Alert,
FormGroup,
FormGroupProps,
FormControl,
ControlLabel,
Button
ControlLabel
} from '@freecodecamp/ui';
import { Link } from 'gatsby';
import React, { useState } from 'react';
@@ -150,14 +150,11 @@ function EmailSettings({
<p className='large-p text-center'>{t('settings.email.missing')}</p>
</FullWidthRow>
<FullWidthRow>
<Button
block={true}
size='large'
variant='primary'
href='/update-email'
>
{t('buttons.edit')}
</Button>
<Link style={{ textDecoration: 'none' }} to='/update-email'>
<Button block={true} bsSize='lg' bsStyle='primary'>
{t('buttons.edit')}
</Button>
</Link>
</FullWidthRow>
</div>
);
@@ -247,8 +244,8 @@ function EmailSettings({
</div>
<BlockSaveButton
data-playwright-test-label='save-email-button'
disabled={isDisabled}
bgSize='large'
aria-disabled={isDisabled}
bgSize='lg'
{...(isDisabled && { tabIndex: -1 })}
>
{t('buttons.save')}{' '}
@@ -1,4 +1,4 @@
import { Button } from '@freecodecamp/ui';
import { Button } from '@freecodecamp/react-bootstrap';
import React from 'react';
import TestRenderer from 'react-test-renderer';
import ShallowRenderer from 'react-test-renderer/shallow';
+4 -3
View File
@@ -1,6 +1,7 @@
import { Button } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Panel, Button } from '@freecodecamp/ui';
import { Panel } from '@freecodecamp/ui';
import { FullWidthRow } from '../helpers';
import SectionHeader from './section-header';
@@ -38,8 +39,8 @@ const Honesty = ({ isHonest, updateIsHonest }: HonestyProps): JSX.Element => {
</Panel>
<Button
block={true}
variant='primary'
disabled={isHonest}
bsStyle='primary'
aria-disabled={isHonest}
onClick={() => !isHonest && updateIsHonest({ isHonest: true })}
>
{buttonText}
+2 -2
View File
@@ -298,8 +298,8 @@ class InternetSettings extends Component<InternetProps, InternetState> {
</div>
<BlockSaveButton
data-playwright-test-label='internet-save-button'
disabled={isDisabled}
bgSize='large'
aria-disabled={isDisabled}
bgSize='lg'
{...(isDisabled && { tabIndex: -1 })}
>
{t('buttons.save')}{' '}
+8 -8
View File
@@ -1,3 +1,4 @@
import { Button } from '@freecodecamp/react-bootstrap';
import { findIndex, find, isEqual } from 'lodash-es';
import { nanoid } from 'nanoid';
import React, { Component } from 'react';
@@ -7,8 +8,7 @@ import {
FormControl,
ControlLabel,
HelpBlock,
FormGroupProps,
Button
FormGroupProps
} from '@freecodecamp/ui';
import { withTranslation } from 'react-i18next';
import isURL from 'validator/lib/isURL';
@@ -340,8 +340,8 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
) : null}
</FormGroup>
<BlockSaveButton
disabled={isButtonDisabled}
bgSize='large'
aria-disabled={isButtonDisabled}
bgSize='lg'
{...(isButtonDisabled && { tabIndex: -1 })}
>
{t('buttons.save-portfolio')}
@@ -349,8 +349,8 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
<Spacer size='small' />
<Button
block={true}
size='large'
variant='danger'
bsSize='lg'
bsStyle='danger'
onClick={() => this.handleRemoveItem(id)}
type='button'
>
@@ -380,8 +380,8 @@ class PortfolioSettings extends Component<PortfolioProps, PortfolioState> {
<Button
data-cy='add-portfolio'
block={true}
size='large'
variant='primary'
bsSize='lg'
bsStyle='primary'
disabled={unsavedItemId !== null}
onClick={this.handleAdd}
type='button'
+6 -6
View File
@@ -1,10 +1,10 @@
import { Button } from '@freecodecamp/react-bootstrap';
import React, { useState } from 'react';
import { useTranslation, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import type { Dispatch } from 'redux';
import { createSelector } from 'reselect';
import { Button } from '@freecodecamp/ui';
import { userSelector } from '../../redux/selectors';
import type { ProfileUI } from '../../redux/prop-types';
@@ -145,11 +145,11 @@ function PrivacySettings({ submitProfileUI, user }: PrivacyProps): JSX.Element {
</div>
<Button
type='submit'
size='large'
variant='primary'
bsSize='lg'
bsStyle='primary'
data-cy='save-privacy-settings'
block={true}
disabled={!madeChanges}
aria-disabled={!madeChanges}
{...(!madeChanges && { tabIndex: -1 })}
>
{t('buttons.save')}{' '}
@@ -162,8 +162,8 @@ function PrivacySettings({ submitProfileUI, user }: PrivacyProps): JSX.Element {
<p>{t('settings.data')}</p>
<Button
block={true}
size='large'
variant='primary'
bsSize='lg'
bsStyle='primary'
download={`${user.username}.json`}
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(user)
@@ -1,7 +1,6 @@
import { Modal } from '@freecodecamp/react-bootstrap';
import { Button, Modal } from '@freecodecamp/react-bootstrap';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@freecodecamp/ui';
import { Spacer } from '../helpers';
@@ -36,8 +35,9 @@ function ResetModal(props: ResetModalProps): JSX.Element {
<hr />
<Button
block={true}
size='large'
variant='primary'
bsSize='lg'
bsStyle='primary'
className='btn-invert'
onClick={props.onHide}
type='button'
>
@@ -46,8 +46,9 @@ function ResetModal(props: ResetModalProps): JSX.Element {
<Spacer size='small' />
<Button
block={true}
size='large'
variant='danger'
bsSize='lg'
bsStyle='danger'
className='btn-danger'
onClick={props.reset}
type='button'
>
@@ -1,6 +1,7 @@
import React from 'react';
import ToggleCheck from '../../assets/icons/toggle-check';
import type { ToggleSettingProps } from './toggle-radio-setting';
import '../helpers/toggle-button.css';
import './toggle-setting.css';
export default function ToggleButtonSetting({
@@ -34,6 +35,7 @@ export default function ToggleButtonSetting({
aria-pressed={flag}
{...(!flag && { onClick: toggleFlag })}
value='1'
className='toggle-button-right'
>
<span>
{onLabel}
@@ -45,6 +47,7 @@ export default function ToggleButtonSetting({
aria-pressed={!flag}
{...(flag && { onClick: toggleFlag })}
value='2'
className='toggle-button-left'
>
<span>
{offLabel}
@@ -1,4 +1,5 @@
import React from 'react';
import '../helpers/toggle-button.css';
import './toggle-setting.css';
export type ToggleSettingProps = {
+2 -2
View File
@@ -221,8 +221,8 @@ class UsernameSettings extends Component<UsernameProps, UsernameState> {
this.renderAlerts(validating, error, isValidUsername)}
<FullWidthRow>
<BlockSaveButton
disabled={isDisabled}
bgSize='large'
aria-disabled={isDisabled}
bgSize='lg'
{...(isDisabled && { tabIndex: -1 })}
>
{t('buttons.save')}{' '}
+4 -4
View File
@@ -1,3 +1,4 @@
import { Button } from '@freecodecamp/react-bootstrap';
import { Link } from 'gatsby';
import { isString } from 'lodash-es';
import React, { useState, type FormEvent, type ChangeEvent } from 'react';
@@ -15,8 +16,7 @@ import {
FormControl,
ControlLabel,
Col,
Row,
Button
Row
} from '@freecodecamp/ui';
import { Spacer } from '../components/helpers';
@@ -102,8 +102,8 @@ function UpdateEmail({ isNewEmail, t, updateMyEmail }: UpdateEmailProps) {
</FormGroup>
<Button
block={true}
size='large'
variant='primary'
bsSize='lg'
bsStyle='primary'
disabled={getEmailValidationState() !== 'success'}
type='submit'
>
@@ -89,14 +89,7 @@ describe('project submission', () => {
// cy.url().should('not.have.string', url);
});
});
// This test is appears to be a false negative.
// It interacts with an element whose `data-cy` is `btn-for-javascript-algorithms-and-data-structures`,
// but there is no elements with this attribute in the implementation.
// We need to disable this test as it blocks the UI component migration.
// TODO: Write tests for the project submission workflow with Playwright and remove this file.
// Tracking issue: https://github.com/freeCodeCamp/freeCodeCamp/issues/52905
it.skip(
it(
'JavaScript projects can be submitted and then viewed in /settings and on the certifications',
{ browser: 'electron' },
() => {
@@ -39,12 +39,7 @@ describe('Front End Development Libraries Superblock', () => {
cy.url().should('match', /\/settings\/?#certification-settings/);
});
});
// This test suite is skipped because it is not reflecting the UI/UX correctly
// and blocks the UI component migration.
// TODO: Write tests for the claim cert workflow with Playwright and remove this file.
// Tracking issue: https://github.com/freeCodeCamp/freeCodeCamp/issues/52905
describe.skip('After submitting all 5 projects', () => {
describe('After submitting all 5 projects', () => {
before(() => {
cy.task('seed');
cy.login();
@@ -1,10 +1,6 @@
import '@testing-library/cypress/add-commands';
// This test suite is skipped because it is not reflecting the UI/UX correctly
// and blocks the UI component migration.
// TODO: Write tests for the claim cert workflow with Playwright and remove this file.
// Tracking issue: https://github.com/freeCodeCamp/freeCodeCamp/issues/52905
describe.skip('Settings certifications area', () => {
describe('Settings certifications area', () => {
describe('initially', () => {
before(() => {
cy.task('seed');
@@ -32,9 +32,6 @@ describe('Picture input field', () => {
}
);
cy.wait(500);
cy.get('#camper-identity')
.find('button')
.contains('Save')
.should('not.be.disabled');
cy.get('#camper-identity > .btn').should('not.be.disabled');
});
});
+2 -7
View File
@@ -12,9 +12,7 @@ describe('Add Portfolio Item', () => {
cy.get('[data-cy="validation-message"]').contains('A title is required');
cy.get('[data-cy="portfolio-title"]').type('This is a portfolio item');
cy.get('button')
.contains('Save this portfolio item')
.should('not.be.disabled');
cy.get('button').filter(':disabled').should('have.length.gt', 0);
cy.get('[data-cy="portfolio-url"]').type('This is a portfolio item');
cy.get('[data-cy="validation-message"]').contains(
@@ -45,10 +43,7 @@ describe('Add Portfolio Item', () => {
cy.get('[data-cy="validation-message"]').contains(
'There is a maximum limit of 288 characters, you have 0 left'
);
cy.get('button')
.contains('Save this portfolio item')
.should('not.be.disabled');
cy.get('button').filter(':disabled').should('have.length.gt', 0);
cy.get('[data-cy="portfolio-description"]').type('{backspace}');
cy.get('button[type=submit]').contains('Save this portfolio item').click();
+1 -1
View File
@@ -1,6 +1,6 @@
// Use this file as the entry point for component export
export { Alert, type AlertProps } from './alert';
export { Button, type ButtonProps } from './button';
export { Button } from './button';
export { CloseButton } from './close-button';
export { Image } from './image';
export { Table } from './table';