feat: add share in twitter button to the end of project. (#49395)

Co-authored-by: Muhammed Mustafa <muhammed@freecodecamp.org>
Co-authored-by: ahmad abdolsaheb <ahmad.abdolsaheb@gmail.com>
Co-authored-by: Muhammed Mustafa <MuhammedElruby@gmail.com>
This commit is contained in:
B.yashwanth
2023-07-21 13:54:52 +05:30
committed by GitHub
parent 96c022f749
commit 45c8d14b44
12 changed files with 915 additions and 8 deletions

View File

@@ -0,0 +1,12 @@
import React from 'react';
import { ShareTemplate } from './share-template';
import { ShareProps } from './types';
import { useShare } from './use-share';
export const Share = ({ superBlock, block }: ShareProps): JSX.Element => {
const redirectURL = useShare({
superBlock,
block
});
return <ShareTemplate redirectURL={redirectURL} />;
};

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ShareTemplate } from './share-template';
const redirectURL = 'string';
describe('Share Template Testing', () => {
render(<ShareTemplate redirectURL={redirectURL} />);
test('Testing share templete Click Redirect Event', () => {
const link = screen.getByRole('link', {
name: 'buttons.tweet aria.opens-new-window'
});
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', 'string');
});
});

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { faTwitter } from '@fortawesome/free-brands-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ShareRedirectProps } from './types';
export const ShareTemplate: React.ComponentType<ShareRedirectProps> = ({
redirectURL
}) => {
const { t } = useTranslation();
return (
<a
data-testid='ShareTemplateWrapperTestID'
className='btn fade-in'
href={redirectURL}
target='_blank'
rel='noreferrer'
>
<FontAwesomeIcon
icon={faTwitter}
size='1x'
aria-label='twitterIcon'
aria-hidden='true'
/>
{t('buttons.tweet')}
<span className='sr-only'>{t('aria.opens-new-window')}</span>
</a>
);
};

View File

@@ -0,0 +1,8 @@
export interface ShareProps {
superBlock: string;
block: string;
}
export interface ShareRedirectProps {
redirectURL: string;
}

View File

@@ -0,0 +1,29 @@
import { useTranslation } from 'react-i18next';
import {
action,
hastag,
nextLine,
space,
twitterDevelpoerDomainURL,
twitterDomain,
useShare
} from './use-share';
test('useShare testing', () => {
const superBlock = 'testSuperBlock';
const block = 'testBlock';
const { t } = useTranslation();
const redirectURL = useShare({
superBlock: superBlock,
block: block
});
const freecodecampLearnDomain = 'www.freecodecamp.org/learn';
const i18nSupportedBlock = t(`intro:${superBlock}.blocks.${block}.title`);
const tweetMessage = `I${space}have${space}completed${space}${i18nSupportedBlock}${space}%23freecodecamp`;
const redirectFreeCodeCampLearnURL = `https://${freecodecampLearnDomain}/${superBlock}/${hastag}${block}`;
expect(redirectURL).toBe(
`https://${twitterDomain}/${action}?original_referer=${twitterDevelpoerDomainURL}&text=${tweetMessage}${nextLine}&url=${redirectFreeCodeCampLearnURL}`
);
});

View File

@@ -0,0 +1,22 @@
import { useTranslation } from 'react-i18next';
import { ShareProps } from './types';
export const space = '%20';
export const hastag = '%23';
export const nextLine = '%0A';
export const action = 'intent/tweet';
export const twitterDomain = 'twitter.com';
export const freecodecampLearnDomainURL = 'www.freecodecamp.org/learn';
export const twitterDevelpoerDomainURL = 'https://developer.twitter.com';
export const useShare = ({ superBlock, block }: ShareProps): string => {
const { t } = useTranslation();
const redirectFreeCodeCampLearnURL = `https://${freecodecampLearnDomainURL}/${superBlock}/${hastag}${block}`;
const i18nSupportedBlock = t(`intro:${superBlock}.blocks.${block}.title`);
const tweetMessage = `I${space}have${space}completed${space}${i18nSupportedBlock}${space}${hastag}freecodecamp`;
const redirectURL = `https://${twitterDomain}/${action}?original_referer=${twitterDevelpoerDomainURL}&text=${tweetMessage}${nextLine}&url=${redirectFreeCodeCampLearnURL}`;
return redirectURL;
};

View File

@@ -168,11 +168,11 @@ textarea.inputarea {
gap: 0.75rem;
}
.utility-bar > button {
.utility-bar > * {
display: flex;
align-items: center;
justify-content: center;
gap: 0.25rem;
gap: 0.3rem;
padding: 6px 0;
}

View File

@@ -2,6 +2,8 @@ import React, { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@freecodecamp/react-bootstrap';
import { createSelector } from 'reselect';
import { connect } from 'react-redux';
import Fail from '../../../assets/icons/fail';
import LightBulb from '../../../assets/icons/lightbulb';
import GreenPass from '../../../assets/icons/green-pass';
@@ -10,17 +12,25 @@ import Help from '../../../assets/icons/help';
import Reset from '../../../assets/icons/reset';
import { MAX_MOBILE_WIDTH } from '../../../../../config/misc';
import { apiLocation } from '../../../../../config/env.json';
import { ChallengeMeta } from '../../../redux/prop-types';
import { Share } from '../../../components/share';
import { ShareProps } from '../../../components/share/types';
import ProgressBar from '../../../components/ProgressBar';
import Quote from '../../../assets/icons/quote';
import {
challengeMetaSelector,
completedPercentageSelector
} from '../redux/selectors';
const lowerJawButtonStyle = 'btn-block btn';
interface LowerJawPanelProps {
interface LowerJawPanelProps extends ShareProps {
resetButtonText: string;
helpButtonText: string;
resetButtonEvent: () => void;
helpButtonEvent: () => void;
hideHelpButton: boolean;
resetButtonText: string;
helpButtonText: string;
showShareButton: boolean;
}
interface LowerJawTipsProps {
@@ -37,7 +47,9 @@ interface LowerJawStatusProps {
testText: string;
}
interface LowerJawProps {
export interface LowerJawProps {
challengeMeta: ChallengeMeta;
completedPercent: number;
hint?: string;
challengeIsCompleted: boolean;
openHelpModal: () => void;
@@ -50,12 +62,24 @@ interface LowerJawProps {
updateContainer: () => void;
}
const mapStateToProps = createSelector(
challengeMetaSelector,
completedPercentageSelector,
(challengeMeta: ChallengeMeta, completedPercent: number) => ({
challengeMeta,
completedPercent
})
);
const LowerButtonsPanel = ({
resetButtonText,
helpButtonText,
resetButtonEvent,
hideHelpButton,
helpButtonEvent
helpButtonEvent,
showShareButton,
superBlock,
block
}: LowerJawPanelProps) => {
return (
<>
@@ -69,6 +93,7 @@ const LowerButtonsPanel = ({
<Reset />
{resetButtonText}
</button>
{showShareButton && <Share superBlock={superBlock} block={block} />}
{hideHelpButton && (
<button
@@ -137,7 +162,11 @@ const LowerJawStatus = ({
);
};
const isBlockCompleted = 100;
const LowerJaw = ({
challengeMeta: { superBlock, block },
completedPercent,
openHelpModal,
challengeIsCompleted,
hint,
@@ -157,6 +186,7 @@ const LowerJaw = ({
const [isFeedbackHidden, setIsFeedbackHidden] = useState(false);
const { t } = useTranslation();
const testFeedbackRef = React.createRef<HTMLDivElement>();
const checkYourCodeButtonRef = useRef<HTMLButtonElement>(null);
const submitButtonRef = useRef<HTMLButtonElement>(null);
const [focusManagementCompleted, setFocusManagementCompleted] =
@@ -171,6 +201,9 @@ const LowerJaw = ({
);
};
const showShareButton =
challengeIsCompleted && completedPercent === isBlockCompleted;
useEffect(() => {
// prevent unnecessary updates:
if (attempts === currentAttempts) return;
@@ -336,6 +369,9 @@ const LowerJaw = ({
isAttemptsLargerThanTest && !challengeIsCompleted
)}
helpButtonEvent={openHelpModal}
showShareButton={showShareButton}
superBlock={superBlock}
block={block}
/>
</div>
);
@@ -343,4 +379,4 @@ const LowerJaw = ({
LowerJaw.displayName = 'LowerJaw';
export default LowerJaw;
export default connect(mapStateToProps)(LowerJaw);