From 45c8d14b449d568ec198f613ef73693c511f32bd Mon Sep 17 00:00:00 2001 From: "B.yashwanth" <36238382+BalanaguYashwanth@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:54:52 +0530 Subject: [PATCH] feat: add share in twitter button to the end of project. (#49395) Co-authored-by: Muhammed Mustafa Co-authored-by: ahmad abdolsaheb Co-authored-by: Muhammed Mustafa --- client/i18n/locales/english/translations.json | 1 + client/src/components/share/index.tsx | 12 + .../components/share/share-template.test.tsx | 16 + .../src/components/share/share-template.tsx | 29 + client/src/components/share/types.ts | 8 + .../src/components/share/use-share.test.tsx | 29 + client/src/components/share/use-share.tsx | 22 + .../templates/Challenges/classic/editor.css | 4 +- .../Challenges/classic/lower-jaw.tsx | 48 +- config/superblock-order.js | 694 ++++++++++++++++++ utils/block-nameify.js | 34 + utils/slugs.js | 26 + 12 files changed, 915 insertions(+), 8 deletions(-) create mode 100644 client/src/components/share/index.tsx create mode 100644 client/src/components/share/share-template.test.tsx create mode 100644 client/src/components/share/share-template.tsx create mode 100644 client/src/components/share/types.ts create mode 100644 client/src/components/share/use-share.test.tsx create mode 100644 client/src/components/share/use-share.tsx create mode 100644 config/superblock-order.js create mode 100644 utils/block-nameify.js create mode 100644 utils/slugs.js 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 ( + + + ); +}; 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 && (