mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-20 12:03:11 -04:00
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:
12
client/src/components/share/index.tsx
Normal file
12
client/src/components/share/index.tsx
Normal 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} />;
|
||||
};
|
||||
16
client/src/components/share/share-template.test.tsx
Normal file
16
client/src/components/share/share-template.test.tsx
Normal 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');
|
||||
});
|
||||
});
|
||||
29
client/src/components/share/share-template.tsx
Normal file
29
client/src/components/share/share-template.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
8
client/src/components/share/types.ts
Normal file
8
client/src/components/share/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface ShareProps {
|
||||
superBlock: string;
|
||||
block: string;
|
||||
}
|
||||
|
||||
export interface ShareRedirectProps {
|
||||
redirectURL: string;
|
||||
}
|
||||
29
client/src/components/share/use-share.test.tsx
Normal file
29
client/src/components/share/use-share.test.tsx
Normal 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}`
|
||||
);
|
||||
});
|
||||
22
client/src/components/share/use-share.tsx
Normal file
22
client/src/components/share/use-share.tsx
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user