diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json
index 46fa09d6d6a..cea9f68c9f9 100644
--- a/client/i18n/locales/english/translations.json
+++ b/client/i18n/locales/english/translations.json
@@ -77,6 +77,7 @@
"change-language": "Change Language",
"resume-project": "Resume project",
"start-project": "Start project",
+ "tweet": "Tweet",
"previous-question": "Previous question",
"next-question": "Next question",
"finish-exam": "Finish the exam",
diff --git a/client/src/components/share/index.tsx b/client/src/components/share/index.tsx
new file mode 100644
index 00000000000..5e143e2a77c
--- /dev/null
+++ b/client/src/components/share/index.tsx
@@ -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 ;
+};
diff --git a/client/src/components/share/share-template.test.tsx b/client/src/components/share/share-template.test.tsx
new file mode 100644
index 00000000000..9ea28317482
--- /dev/null
+++ b/client/src/components/share/share-template.test.tsx
@@ -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();
+ 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');
+ });
+});
diff --git a/client/src/components/share/share-template.tsx b/client/src/components/share/share-template.tsx
new file mode 100644
index 00000000000..0426b8738eb
--- /dev/null
+++ b/client/src/components/share/share-template.tsx
@@ -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 = ({
+ redirectURL
+}) => {
+ const { t } = useTranslation();
+ return (
+
+
+ {t('buttons.tweet')}
+ {t('aria.opens-new-window')}
+
+ );
+};
diff --git a/client/src/components/share/types.ts b/client/src/components/share/types.ts
new file mode 100644
index 00000000000..1caca6957d4
--- /dev/null
+++ b/client/src/components/share/types.ts
@@ -0,0 +1,8 @@
+export interface ShareProps {
+ superBlock: string;
+ block: string;
+}
+
+export interface ShareRedirectProps {
+ redirectURL: string;
+}
diff --git a/client/src/components/share/use-share.test.tsx b/client/src/components/share/use-share.test.tsx
new file mode 100644
index 00000000000..1052eede19e
--- /dev/null
+++ b/client/src/components/share/use-share.test.tsx
@@ -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}`
+ );
+});
diff --git a/client/src/components/share/use-share.tsx b/client/src/components/share/use-share.tsx
new file mode 100644
index 00000000000..80a51ca0b80
--- /dev/null
+++ b/client/src/components/share/use-share.tsx
@@ -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;
+};
diff --git a/client/src/templates/Challenges/classic/editor.css b/client/src/templates/Challenges/classic/editor.css
index fab94859699..f8a1bdc0e20 100644
--- a/client/src/templates/Challenges/classic/editor.css
+++ b/client/src/templates/Challenges/classic/editor.css
@@ -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;
}
diff --git a/client/src/templates/Challenges/classic/lower-jaw.tsx b/client/src/templates/Challenges/classic/lower-jaw.tsx
index b4c37acbecc..69e3401f8e8 100644
--- a/client/src/templates/Challenges/classic/lower-jaw.tsx
+++ b/client/src/templates/Challenges/classic/lower-jaw.tsx
@@ -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 = ({
{resetButtonText}
+ {showShareButton && }
{hideHelpButton && (