diff --git a/components/x-gift-article/__tests__/x-gift-article.test.jsx b/components/x-gift-article/__tests__/x-gift-article.test.jsx index da88a3ac7..57c491708 100644 --- a/components/x-gift-article/__tests__/x-gift-article.test.jsx +++ b/components/x-gift-article/__tests__/x-gift-article.test.jsx @@ -1,8 +1,9 @@ -const fetchMock = require('fetch-mock') -const { h } = require('@financial-times/x-engine') -const { mount } = require('@financial-times/x-test-utils/enzyme') +import fetchMock from 'fetch-mock' +import { h } from '@financial-times/x-engine' +import { mount } from '@financial-times/x-test-utils/enzyme' +import { ShareArticleModal } from '../dist/GiftArticle.cjs' -const { GiftArticle } = require('../dist/GiftArticle.cjs.js') +jest.mock('@financial-times/o-share', () => jest.fn()) const articleId = 'article id' const articleUrl = 'https://www.ft.com/content/blahblahblah' @@ -10,13 +11,14 @@ const articleUrlRedeemed = 'https://gift-url-redeemed' const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` const baseArgs = { - title: 'Title', + title: 'Share this article:', isFreeArticle: false, article: { - title: 'Article Title Blah Blah Blah', id: articleId, - url: articleUrl + url: articleUrl, + title: 'Equinor and Daimler Truck cut Russia ties as Volvo and JLR halt car deliveries' }, + id: 'base-gift-article-static-id', enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com` } @@ -27,70 +29,55 @@ describe('x-gift-article', () => { actions = {} fetchMock - .get('/article/gift-credits', { + .get('path:/article/gift-credits', { allowance: 20, consumedCredits: 5, remainingCredits: 15, renewalDate: '2018-08-01T00:00:00Z' }) - .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + .get(`path:/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { shortenedUrl: 'https://shortened-gift-url' }) - .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + .get(`path:/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { shortenedUrl: 'https://shortened-non-gift-url' }) - .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + .get(`path:/article/gift-link/${encodeURIComponent(articleId)}`, { redemptionUrl: articleUrlRedeemed, + redemptionLimit: 3, remainingAllowance: 1 }) - .get('https://enterprise-sharing-api.ft.com/v1/users/me/allowance', { + .get('path:/v1/users/me/allowance', { limit: 120, hasCredits: true, - firstTimeUser: true + firstTimeUser: false }) - .post('https://enterprise-sharing-api.ft.com/v1/shares', { + .post('path:/v1/shares', { url: articleUrlRedeemed, redeemLimit: 120 }) + .post('path:/v1/copy-annotations', { + annotationsCopyResult: [] + }) }) afterEach(() => { fetchMock.reset() }) - it('displays the correct title', async () => { + it('displays the article title', async () => { const args = { - ...baseArgs, - title: 'A given test title' + ...baseArgs } - const subject = mount() - - expect(subject.find('h2').text()).toEqual('A given test title') - }) + args.article.title = 'A given test article title' - it('displays the mobile share links when showMobileShareLinks is true', async () => { - const args = { - ...baseArgs, - showMobileShareLinks: true - } - const subject = mount() + const subject = mount() - expect(subject.find('div.x-gift-article-mobile-share-buttons').length).toEqual(1) - }) - - it('does not display the mobile share links when showMobileShareLinks is false', async () => { - const args = { - ...baseArgs, - showMobileShareLinks: false - } - const subject = mount() - - expect(subject.find('div.x-gift-article-mobile-share-buttons').length).toEqual(0) + expect(subject.find('h2').text()).toEqual('A given test article title') }) it('should call correct endpoints on activate', async () => { - mount( Object.assign(actions, a)} />) + mount( Object.assign(actions, a)} />) await actions.activate() @@ -100,126 +87,106 @@ describe('x-gift-article', () => { expect(fetchMock.called('/article/gift-credits')).toBe(true) }) - it('should call showGiftUrlSection and show correct html element', async () => { - const subject = mount( Object.assign(actions, a)} />) + it('should call shortenNonGiftUrl and display correct url', async () => { + const subject = mount( Object.assign(actions, a)} />) - await actions.activate() + await actions.showNonGiftUrlSection() + await actions.shortenNonGiftUrl() - expect(actions.showGiftUrlSection).toBeDefined() - await actions.showGiftUrlSection() subject.update() - expect(subject.find('div[data-section-id="giftLink"]')).toHaveLength(1) + const input = subject.find('input#share-link') + expect(input.prop('value')).toEqual('https://shortened-non-gift-url') }) - it('should call showEnterpriseUrlSection and show correct html element', async () => { - const subject = mount( Object.assign(actions, a)} />) + it('should call createGiftUrl and display correct url', async () => { + const subject = mount( Object.assign(actions, a)} />) - await actions.activate() + await actions.createGiftUrl() - expect(actions.showEnterpriseUrlSection).toBeDefined() - await actions.showEnterpriseUrlSection() subject.update() - expect(subject.find('div[data-section-id="enterpriseLink"]')).toHaveLength(1) - }) - - it('enterpriseLink radio button is not shown for non-enterprise users', async () => { - const args = { - ...baseArgs, - enterpriseApiBaseUrl: undefined - } - - const subject = mount( Object.assign(actions, a)} />) + const input = subject.find('input#share-link') - expect(subject.find('input#enterpriseLink')).toHaveLength(0) - expect(subject.find('input#giftLink')).toHaveLength(1) - expect(subject.find('input#nonGiftLink')).toHaveLength(1) + expect(input.prop('value')).toEqual('https://shortened-gift-url') }) - it('should call showNonGiftUrlSection and show correct html element', async () => { - const subject = mount( Object.assign(actions, a)} />) - await actions.activate() - - expect(actions.showNonGiftUrlSection).toBeDefined() - await actions.showNonGiftUrlSection() + it('should call createEnterpriseUrl and display correct url', async () => { + const subject = mount( Object.assign(actions, a)} />) + expect(actions.createEnterpriseUrl).toBeDefined() + await actions.createEnterpriseUrl() subject.update() - expect(subject.find('div[data-section-id="nonGiftLink"]')).toHaveLength(1) + const input = subject.find('input#share-link') + expect(input.prop('value')).toEqual('https://gift-url-redeemed') }) - it('should call createGiftUrl and display correct url', async () => { - const subject = mount( Object.assign(actions, a)} />) - expect(actions.createGiftUrl).toBeDefined() - await actions.createGiftUrl() + it('when credits are available, an alert is not shown', async () => { + const subject = mount( Object.assign(actions, a)} />) + + await actions.activate() subject.update() - const input = subject.find('input#share-link') - expect(input.prop('value')).toEqual('https://shortened-gift-url') + expect(subject.find('#no-credit-alert')).not.toExist() }) - it('should call createEnterpriseUrl and display correct url', async () => { - const subject = mount( Object.assign(actions, a)} />) - expect(actions.createEnterpriseUrl).toBeDefined() - await actions.createEnterpriseUrl() + it('when no credits are available, an alert is shown', async () => { + fetchMock + .get( + 'path:/article/gift-credits', + { + allowance: 20, + consumedCredits: 20, + remainingCredits: 0, + renewalDate: '2018-08-01T00:00:00Z' + }, + { overwriteRoutes: true } + ) + .get( + 'path:/v1/users/me/allowance', + { + limit: 120, + hasCredits: false, + firstTimeUser: false + }, + { overwriteRoutes: true } + ) + + const subject = mount( Object.assign(actions, a)} />) + + await actions.activate() subject.update() - const input = subject.find('input#share-link') - expect(input.prop('value')).toEqual('https://gift-url-redeemed') + expect(subject.find('#no-credit-alert')).toExist() }) - it('when no gift-credits are available, a message is shown', async () => { - fetchMock.get( - '/article/gift-credits', - { - allowance: 20, - consumedCredits: 20, - remainingCredits: 0, - renewalDate: '2018-08-01T00:00:00Z' - }, - { overwriteRoutes: true } - ) + it('displays the social share buttons', async () => { + const subject = mount( Object.assign(actions, a)} />) - const subject = mount( Object.assign(actions, a)} />) await actions.activate() - expect(actions.showGiftUrlSection).toBeDefined() - await actions.showGiftUrlSection() + await actions.shortenNonGiftUrl() + subject.update() - expect(subject.find('input#share-link')).toHaveLength(0) - expect( - subject.find('div.x-gift-article-message').text().includes('You’ve used all your gift article credits') - ).toBe(true) + expect(subject.find('#social-share-buttons')).toExist() }) - it('when no enterprise credits are available, a message is shown', async () => { - fetchMock.get( - `https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, - { - limit: 120, - hasCredits: false, - firstTimeUser: false - }, - { overwriteRoutes: true } - ) + it('should display the free article message when isFreeArticle is true', async () => { + const args = { + ...baseArgs, + isFreeArticle: true + } + const subject = mount( Object.assign(actions, a)} />) - const subject = mount( Object.assign(actions, a)} />) await actions.activate() - expect(actions.showEnterpriseUrlSection).toBeDefined() - await actions.showEnterpriseUrlSection() subject.update() - expect(subject.find('input#share-link')).toHaveLength(0) - expect( - subject - .find('div.x-gift-article-message') - .text() - .includes('Your organisation has run out of share credits') - ).toBe(true) + expect(subject.find('#free-article-alert')).toExist() + expect(subject.find('#share-with-non-subscribers-checkbox')).not.toExist() }) }) diff --git a/components/x-gift-article/package.json b/components/x-gift-article/package.json index c11a579d0..3200a5458 100644 --- a/components/x-gift-article/package.json +++ b/components/x-gift-article/package.json @@ -5,7 +5,7 @@ "main": "dist/GiftArticle.cjs.js", "browser": "dist/GiftArticle.es5.js", "module": "dist/GiftArticle.esm.js", - "style": "src/GiftArticle.scss", + "style": "src/main.scss", "scripts": { "build": "node rollup.js", "start": "node rollup.js --watch", @@ -23,10 +23,10 @@ }, "devDependencies": { "@financial-times/o-buttons": "^7.2.2", - "@financial-times/o-forms": "^9.2.3", + "@financial-times/o-forms": "^9.11.0", "@financial-times/o-labels": "^6.2.2", "@financial-times/o-loading": "^5.2.1", - "@financial-times/o-message": "^5.2.1", + "@financial-times/o-message": "^5.4.2", "@financial-times/o-normalise": "^3.2.2", "@financial-times/o-typography": "^7.4.1", "@financial-times/x-rollup": "file:../../packages/x-rollup", @@ -44,11 +44,12 @@ "@financial-times/o-banner": "^4.4.9", "@financial-times/o-buttons": "^7.2.2", "@financial-times/o-colors": "^6.6.0", - "@financial-times/o-forms": "^9.2.3", + "@financial-times/o-forms": "^9.11.0", "@financial-times/o-labels": "^6.2.2", "@financial-times/o-loading": "^5.2.1", - "@financial-times/o-message": "^5.2.1", + "@financial-times/o-message": "^5.4.2", "@financial-times/o-normalise": "^3.2.2", + "@financial-times/o-share": "^9.0.2", "@financial-times/o-typography": "^7.4.1" } } diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index 66d837a28..c304ba5c1 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -75,7 +75,6 @@ Property | Type | Required | Note --------------------------|---------|----------|---- `isFreeArticle` | Boolean | yes | Only non gift form is displayed when this value is `true`. `article` | Object | yes | Must contain `id`, `title` and `url` properties -`showMobileShareLinks` | Boolean | no | For ft.com on mobile sharing. `nativeShare` | Boolean | no | This is a property for App to display Native Sharing. `apiProtocol` | String | no | The protocol to use when making requests to the gift article and URL shortening services. Ignored if `apiDomain` is not set. `apiDomain` | String | no | The domain to use when making requests to the gift article and URL shortening services. diff --git a/components/x-gift-article/src/CopyConfirmation.jsx b/components/x-gift-article/src/CopyConfirmation.jsx index 39e5bd2cd..d759a57c6 100644 --- a/components/x-gift-article/src/CopyConfirmation.jsx +++ b/components/x-gift-article/src/CopyConfirmation.jsx @@ -1,14 +1,18 @@ import { h } from '@financial-times/x-engine' -export default ({ hideCopyConfirmation }) => ( +export default ({ hideCopyConfirmation, isArticleSharingUxUpdates }) => (

- The link has been copied to your clipboard + {isArticleSharingUxUpdates ? ( + Link copied to clipboard. + ) : ( + Link copied to clipboard. + )}

diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index 48f908b2d..c514dff40 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -29,7 +29,7 @@ export default (props) => ( shareType={props.shareType} hasHighlights={props.hasHighlights} includeHighlights={props.includeHighlights} - includeHighlightsHandler={props.actions.includeHighlightsHandler} + setIncludeHighlights={props.actions.setIncludeHighlights} isGiftUrlCreated={props.isGiftUrlCreated} saveHighlightsHandler={props.actions.saveHighlightsHandler} showHighlightsRecipientMessage={props.showHighlightsRecipientMessage} @@ -60,6 +60,6 @@ export default (props) => (
)} - {props.showMobileShareLinks && } +
) diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index afd065cd1..04ebe095f 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -2,7 +2,6 @@ import { h } from '@financial-times/x-engine' import { withActions } from '@financial-times/x-interaction' import Loading from './Loading' -import Form from './Form' import ApiClient from './lib/api' import EnterpriseApiClient from './lib/enterpriseApi' @@ -10,6 +9,8 @@ import { copyToClipboard, createMailtoUrl } from './lib/share-link-actions' import tracking from './lib/tracking' import * as updaters from './lib/updaters' import { ShareType } from './lib/constants' +import ShareArticleDialog from './v2/ShareArticleDialog' +import Form from './Form' const isCopySupported = typeof document !== 'undefined' && document.queryCommandSupported && document.queryCommandSupported('copy') @@ -32,19 +33,15 @@ const withGiftFormActions = withActions( }, showNonGiftUrlSection() { - return async (state) => { - const update = updaters.showNonGiftUrlSection(state) - - if (!state.isNonGiftUrlShortened) { - const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift) + return updaters.showNonGiftUrlSection + }, - if (isShortened) { - Object.assign(update, updaters.setShortenedNonGiftUrl(url)(state)) - } - } + showAdvancedSharingOptions() { + return updaters.showAdvancedSharingOptions + }, - return update - } + hideAdvancedSharingOptions() { + return updaters.hideAdvancedSharingOptions }, async createGiftUrl() { @@ -60,6 +57,23 @@ const withGiftFormActions = withActions( } }, + async shortenNonGiftUrl() { + return async (state) => { + if (state.isNonGiftUrlShortened) { + state.showFreeArticleAlert = false + return state + } + const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift) + tracking.createNonGiftLink(url, state.urls.nonGift) + + if (isShortened) { + return updaters.setShortenedNonGiftUrl(url)(state) + } else { + return updaters.setErrorState(true) + } + } + }, + async createEnterpriseUrl() { return async (state) => { const { redemptionUrl, redemptionLimit } = await enterpriseApi.getESUrl( @@ -161,15 +175,17 @@ const withGiftFormActions = withActions( if (initialProps.isFreeArticle) { const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift) - - if (isShortened) { - updaters.setShortenedNonGiftUrl(url)(state) - } - return { + const freeArticleState = { invalidResponseFromApi: true, enterpriseEnabled: enabled, ...enterpriseState } + + if (isShortened) { + Object.assign(freeArticleState, updaters.setShortenedNonGiftUrl(url)(state)) + freeArticleState.showFreeArticleAlert = true + } + return freeArticleState } else { const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance() @@ -177,7 +193,7 @@ const withGiftFormActions = withActions( if (giftCredits > 0 || giftCredits === 0) { return { ...updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate), - shareType: enabled && hasCredits ? ShareType.enterprise : ShareType.gift, + shareType: ShareType.nonGift, enterpriseEnabled: enabled, ...enterpriseState } @@ -191,9 +207,8 @@ const withGiftFormActions = withActions( } } }, - includeHighlightsHandler() { + setIncludeHighlights(includeHighlights) { return (state) => { - const includeHighlights = !state.includeHighlights state.includeHighlights = includeHighlights return { includeHighlights } } @@ -231,7 +246,7 @@ const withGiftFormActions = withActions( }, (props) => { const initialState = { - title: 'Share this article', + title: 'Share this article:', giftCredits: undefined, monthlyAllowance: undefined, showCopyButton: isCopySupported, @@ -239,6 +254,7 @@ const withGiftFormActions = withActions( isGiftUrlShortened: false, isNonGiftUrlShortened: false, includeHighlights: false, + showAdvancedSharingOptions: false, hasHighlights: false, showHighlightsRecipientMessage: new URL(location.href).searchParams.has('highlights'), showHighlightsSuccessMessage: false, @@ -257,22 +273,21 @@ const withGiftFormActions = withActions( nonGift: createMailtoUrl(props.article.title, `${props.article.url}?shareType=nongift`) }, - mobileShareLinks: props.showMobileShareLinks - ? { - facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent( - props.article.url - )}&t=${encodeURIComponent(props.article.title)}`, - twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent( - props.article.url - )}&text=${encodeURIComponent(props.article.title)}&via=financialtimes`, - linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent( - props.article.url - )}&title=${encodeURIComponent(props.article.title)}&source=Financial+Times`, - whatsapp: `whatsapp://send?text=${encodeURIComponent( - props.article.title - )}%20-%20${encodeURIComponent(props.article.url)}` - } - : undefined + mobileShareLinks: { + facebook: `http://www.facebook.com/sharer.php?u=${encodeURIComponent( + props.article.url + )}&t=${encodeURIComponent(props.article.title)}`, + twitter: `https://twitter.com/intent/tweet?url=${encodeURIComponent( + props.article.url + )}&text=${encodeURIComponent(props.article.title)}&via=financialtimes`, + linkedin: `http://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent( + props.article.url + )}&title=${encodeURIComponent(props.article.title)}&source=Financial+Times`, + whatsapp: `whatsapp://send?text=${encodeURIComponent(props.article.title)}%20-%20${encodeURIComponent( + props.article.url + )}` + }, + showFreeArticleAlert: false } const expandedProps = Object.assign({}, props, initialState) @@ -288,6 +303,12 @@ const BaseGiftArticle = (props) => { return props.isLoading ? :
} +const BaseShareArticleModal = (props) => { + return +} + const GiftArticle = withGiftFormActions(BaseGiftArticle) -export { GiftArticle } +const ShareArticleModal = withGiftFormActions(BaseShareArticleModal) + +export { GiftArticle, ShareArticleModal } diff --git a/components/x-gift-article/src/HighlightCheckbox.jsx b/components/x-gift-article/src/HighlightCheckbox.jsx index 7eb09563a..b25b1db5f 100644 --- a/components/x-gift-article/src/HighlightCheckbox.jsx +++ b/components/x-gift-article/src/HighlightCheckbox.jsx @@ -1,6 +1,6 @@ import { h } from '@financial-times/x-engine' -export default ({ includeHighlightsHandler, includeHighlights, isGiftUrlCreated }) => { +export default ({ setIncludeHighlights, includeHighlights, isGiftUrlCreated }) => { return (