diff --git a/components/x-gift-article/bower.json b/components/x-gift-article/bower.json index 18dc603c2..90e74f8f4 100644 --- a/components/x-gift-article/bower.json +++ b/components/x-gift-article/bower.json @@ -5,7 +5,10 @@ "private": true, "dependencies": { "o-buttons": "^6.0.0", + "o-colors": "^5.0.0", "o-forms": "^8.0.0", + "o-icons": "^6.0.0", + "o-labels": "5.0.0", "o-loading": "^4.0.0", "o-message": "^4.0.0", "o-typography": "6.0.0", diff --git a/components/x-gift-article/readme.md b/components/x-gift-article/readme.md index df16886fc..06ee02a19 100644 --- a/components/x-gift-article/readme.md +++ b/components/x-gift-article/readme.md @@ -77,6 +77,7 @@ Property | Type | Required | Note `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. +`enterpriseApiBaseUrl` | String | no | The base URL to use when making requests to the enterprise sharing service. ### `isArticleSharingUxUpdates` boolean has been added as part of ACC-749 to enable AB testing of the impact of minor UX improvements to x-gift-article. Once AB testing is done, and decision to keep / remove has been made, the changes made in https://github.com/Financial-Times/x-dash/pull/579 need to be ditched or baked in as default. diff --git a/components/x-gift-article/src/Buttons.jsx b/components/x-gift-article/src/Buttons.jsx index 77d028e39..9a95956ed 100644 --- a/components/x-gift-article/src/Buttons.jsx +++ b/components/x-gift-article/src/Buttons.jsx @@ -34,7 +34,13 @@ export default ({ @@ -44,7 +50,13 @@ export default ({ href={mailtoUrl} target="_blank" rel="noopener noreferrer" - onClick={shareType === ShareType.gift ? actions.emailGiftUrl : actions.emailNonGiftUrl}> + onClick={ + shareType === ShareType.gift + ? actions.emailGiftUrl + : shareType === ShareType.enterprise + ? actions.emailEnterpriseUrl + : actions.emailNonGiftUrl + }> Email link to Share this article @@ -57,8 +69,8 @@ export default ({ className={ButtonClassNames} disabled={!giftCredits} type="button" - onClick={actions.createGiftUrl}> - Create gift link + onClick={shareType === ShareType.enterprise ? actions.createEnterpriseUrl : actions.createGiftUrl}> + Create {shareType === ShareType.enterprise ? 'enterprise' : 'gift'} link ) diff --git a/components/x-gift-article/src/Form.jsx b/components/x-gift-article/src/Form.jsx index a2a8b515f..1d24d322c 100644 --- a/components/x-gift-article/src/Form.jsx +++ b/components/x-gift-article/src/Form.jsx @@ -17,7 +17,13 @@ export default (props) => ( shareType={props.shareType} isArticleSharingUxUpdates={props.isArticleSharingUxUpdates} showGiftUrlSection={props.actions.showGiftUrlSection} + showEnterpriseUrlSection={props.actions.showEnterpriseUrlSection} showNonGiftUrlSection={props.actions.showNonGiftUrlSection} + enterpriseLimit={props.enterpriseLimit} + enterpriseHasCredits={props.enterpriseHasCredits} + enterpriseRequestAccess={props.enterpriseRequestAccess} + enterpriseAlert={!props.enterpriseHasCredits && !props.enterpriseRequestAccess} + enterpriseEnabled={props.enterpriseEnabled} /> )} diff --git a/components/x-gift-article/src/GiftArticle.jsx b/components/x-gift-article/src/GiftArticle.jsx index 38c4232de..08ded5b38 100644 --- a/components/x-gift-article/src/GiftArticle.jsx +++ b/components/x-gift-article/src/GiftArticle.jsx @@ -5,9 +5,11 @@ import Loading from './Loading' import Form from './Form' import ApiClient from './lib/api' +import EnterpriseApiClient from './lib/enterpriseApi' import { copyToClipboard, createMailtoUrl } from './lib/share-link-actions' import tracking from './lib/tracking' import * as updaters from './lib/updaters' +import { ShareType } from './lib/constants' const isCopySupported = typeof document !== 'undefined' && document.queryCommandSupported && document.queryCommandSupported('copy') @@ -21,12 +23,17 @@ const withGiftFormActions = withActions( protocol: initialProps.apiProtocol, domain: initialProps.apiDomain }) + const enterpriseApi = new EnterpriseApiClient(initialProps.enterpriseApiBaseUrl) return { showGiftUrlSection() { return updaters.showGiftUrlSection }, + showEnterpriseUrlSection() { + return updaters.showGiftEnterpriseSection + }, + showNonGiftUrlSection() { return async (state) => { const update = updaters.showNonGiftUrlSection(state) @@ -52,7 +59,18 @@ const withGiftFormActions = withActions( return updaters.setGiftUrl(url, redemptionLimit, isShortened) } else { - // TODO do something + return updaters.setErrorState(true) + } + }, + + async createEnterpriseUrl() { + const { redemptionUrl, redemptionLimit } = await enterpriseApi.getESUrl(initialProps.article.id) + + if (redemptionUrl) { + tracking.createESLink(redemptionUrl) + return updaters.setGiftUrl(redemptionUrl, redemptionLimit, false, true) + } else { + return updaters.setErrorState(true) } }, @@ -67,6 +85,17 @@ const withGiftFormActions = withActions( } }, + copyEnterpriseUrl(event) { + copyToClipboard(event) + + return (state) => { + const enterpriseUrl = state.urls.enterprise + tracking.copyLink('enterpriseLink', enterpriseUrl) + + return { showCopyConfirmation: true } + } + }, + copyNonGiftUrl(event) { copyToClipboard(event) @@ -84,6 +113,12 @@ const withGiftFormActions = withActions( } }, + emailEnterpriseUrl() { + return (state) => { + tracking.emailLink('enterpriseLink', state.urls.enterprise) + } + }, + emailNonGiftUrl() { return (state) => { tracking.emailLink('nonGiftLink', state.urls.nonGift) @@ -108,12 +143,28 @@ const withGiftFormActions = withActions( } } else { const { giftCredits, monthlyAllowance, nextRenewalDate } = await api.getGiftArticleAllowance() - + const { enabled, limit, hasCredits, firstTimeUser, requestAccess } = + await enterpriseApi.getEnterpriseArticleAllowance() // avoid to use giftCredits >= 0 because it returns true when null and "" if (giftCredits > 0 || giftCredits === 0) { - return updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate) + return { + ...updaters.setAllowance(giftCredits, monthlyAllowance, nextRenewalDate), + shareType: enabled ? ShareType.enterprise : ShareType.gift, + enterpriseEnabled: enabled, + enterpriseLimit: limit, + enterpriseHasCredits: hasCredits, + enterpriseFirstTimeUser: firstTimeUser, + enterpriseRequestAccess: requestAccess + } } else { - return { invalidResponseFromApi: true } + return { + invalidResponseFromApi: true, + enterpriseEnabled: enabled, + enterpriseLimit: limit, + enterpriseHasCredits: hasCredits, + enterpriseFirstTimeUser: firstTimeUser, + enterpriseRequestAccess: requestAccess + } } } } @@ -135,11 +186,13 @@ const withGiftFormActions = withActions( urls: { dummy: 'https://on.ft.com/gift_link', gift: undefined, + enterprise: undefined, nonGift: `${props.article.url}?shareType=nongift` }, mailtoUrls: { gift: undefined, + enterprise: undefined, nonGift: createMailtoUrl(props.article.title, `${props.article.url}?shareType=nongift`) }, diff --git a/components/x-gift-article/src/GiftArticle.scss b/components/x-gift-article/src/GiftArticle.scss index e3a0f2d36..8fb77bb0f 100644 --- a/components/x-gift-article/src/GiftArticle.scss +++ b/components/x-gift-article/src/GiftArticle.scss @@ -16,6 +16,16 @@ $o-message-is-silent: true; $o-typography-is-silent: true; @import 'o-typography/main'; +$o-labels-is-silent: true; +@import 'o-labels/main'; + +$o-colors-is-silent: true; +@import 'o-colors/main'; + +$o-icons-is-silent: true; +@import 'o-icons/main'; + + .container { @include oTypographySans; strong { @@ -23,6 +33,17 @@ $o-typography-is-silent: true; } } +.o-icons__enterprise-no-credits { + @include oIconsContent( + $icon-name: 'warning', + $color: oColorsByName('crimson'), + $size: 16 + ); + border-radius: 50%; + border: 2px solid oColorsByName('crimson'); + margin-bottom: -5px; +} + .share-form { max-width: none; @@ -36,6 +57,17 @@ $o-typography-is-silent: true; .radio-button-section { margin-bottom: 12px; + @include oLabels($opts: ( + 'states': ( + 'content-premium' + ), + )); + + .enterprise-label { + background-color: oColorsByName('black-10'); + color: oColorsByName('black'); + margin: 0px 6px; + } } .share-option-title { @@ -53,14 +85,6 @@ $o-typography-is-silent: true; .share-option-title { @include oNormaliseVisuallyHidden(); } - - .o-forms-field{ - @media only screen and (min-width: 600px) { - label[for$="Link"]{ - margin-bottom: 0; - } - } - } } @media only screen and (min-width: 600px) { @@ -99,13 +123,31 @@ $o-typography-is-silent: true; margin-top: 12px; } +.enterprise-message { + grid-area: message; + background: oColorsByName('white-40'); + border: 1px solid oColorsByName('black-5'); + margin-top: oSpacingByName('s4'); + padding: oSpacingByName('s4'); + + h4 { + @include oTypographySans($scale: 1, $weight: 'semibold'); + color: oColorsByName('black-90'); + font-size: 16px; + margin: 0; + } + p { + margin: oSpacingByName('s2') 0px; + } +} + .buttonBaseStyle { @include oButtonsContent($opts: ('type': 'primary', 'size':'big')); } .buttons { grid-area: buttons; - text-align: right; + text-align: left; white-space: nowrap; margin-top: 12px; } diff --git a/components/x-gift-article/src/Message.jsx b/components/x-gift-article/src/Message.jsx index 156c75c1e..e4ee1d5bb 100644 --- a/components/x-gift-article/src/Message.jsx +++ b/components/x-gift-article/src/Message.jsx @@ -13,7 +13,11 @@ export default ({ nextRenewalDateText, redemptionLimit, invalidResponseFromApi, - isArticleSharingUxUpdates + isArticleSharingUxUpdates, + enterpriseLimit, + enterpriseHasCredits, + enterpriseRequestAccess, + enterpriseFirstTimeUser }) => { if (isArticleSharingUxUpdates) { if (isFreeArticle) { @@ -80,7 +84,7 @@ export default ({ return (
- You have{' '} + Uses 1 gift credit. You have{' '} {giftCredits} gift article {giftCredits === 1 ? 'credit' : 'credits'} {' '} @@ -89,6 +93,75 @@ export default ({ ) } + if (shareType === ShareType.enterprise) { + if (invalidResponseFromApi) { + return
Unable to create enterprise link. Please try again later
+ } + + if (isGiftUrlCreated === true) { + return ( +
+ This link can be opened by up to {enterpriseLimit} people and is valid for 90 days +
+ ) + } + if (enterpriseHasCredits === true) { + if (enterpriseFirstTimeUser) { + return ( +
+

Engage more effectively with clients and colleagues.

+

+ Enterprise Sharing lets members of your organisation share FT article links with up to{' '} + {enterpriseLimit} people, even if they don’t have a FT subscription. +

+
+ ) + } + return ( +
+ Your organisation has Enterprise Sharing credits available for you to use +
+ ) + } else { + if (enterpriseRequestAccess) { + //Activation Message + return ( +
+

Enterprise Sharing is not enabled for your team

+

+ Enterprise Sharing lets members of your organisation share FT article links with potentially + thousands people, even if they don’t have a FT subscription +

+ + Learn more + +
+ ) + } + return ( +
+

Your organisation has run out of share credits.

+

+ Request more credits and our Enterprise team will get in touch with the admin of your FT + subscription to arrange a top-up of sharing credits. +

+ + Request more credits + +
+ ) + } + } + if (shareType === ShareType.nonGift) { return
This link can only be read by existing subscribers
} diff --git a/components/x-gift-article/src/RadioButtonsSection.jsx b/components/x-gift-article/src/RadioButtonsSection.jsx index c94181e4b..4c2041f53 100644 --- a/components/x-gift-article/src/RadioButtonsSection.jsx +++ b/components/x-gift-article/src/RadioButtonsSection.jsx @@ -5,16 +5,45 @@ import styles from './GiftArticle.scss' const radioSectionClassNames = [ styles['o-forms-input'], styles['o-forms-input--radio-round'], - styles['o-forms-input--inline'], styles['o-forms-field'], styles['radio-button-section'] ].join(' ') -export default ({ shareType, showGiftUrlSection, isArticleSharingUxUpdates, showNonGiftUrlSection }) => ( +export default ({ + shareType, + showGiftUrlSection, + showEnterpriseUrlSection, + showNonGiftUrlSection, + enterpriseEnabled = false, + enterpriseLimit = 100, + enterpriseRequestAccess = false, + enterpriseAlert = false +}) => (
Article share options + + {enterpriseEnabled === true && ( + + )} +
) diff --git a/components/x-gift-article/src/Url.jsx b/components/x-gift-article/src/Url.jsx index 9b83e5284..46940a0f6 100644 --- a/components/x-gift-article/src/Url.jsx +++ b/components/x-gift-article/src/Url.jsx @@ -14,7 +14,7 @@ export default ({ shareType, isGiftUrlCreated, url, urlType }) => { name={urlType} value={url} className={urlClassNames} - disabled={shareType === ShareType.gift && !isGiftUrlCreated} + disabled={(shareType === ShareType.gift || shareType === ShareType.enterprise) && !isGiftUrlCreated} readOnly aria-label="Gift article shareable link" /> diff --git a/components/x-gift-article/src/UrlSection.jsx b/components/x-gift-article/src/UrlSection.jsx index b1d6e8547..b86b3d9b1 100644 --- a/components/x-gift-article/src/UrlSection.jsx +++ b/components/x-gift-article/src/UrlSection.jsx @@ -22,9 +22,15 @@ export default ({ nativeShare, invalidResponseFromApi, isArticleSharingUxUpdates, - actions + actions, + enterpriseLimit, + enterpriseHasCredits, + enterpriseRequestAccess, + enterpriseFirstTimeUser }) => { - const hideUrlShareElements = giftCredits === 0 && shareType === ShareType.gift + const hideUrlShareElements = + (giftCredits === 0 && shareType === ShareType.gift) || + ((enterpriseRequestAccess || !enterpriseHasCredits) && shareType === ShareType.enterprise) const showUrlShareElements = !hideUrlShareElements return ( @@ -53,7 +59,11 @@ export default ({ nextRenewalDateText, redemptionLimit, invalidResponseFromApi, - isArticleSharingUxUpdates + isArticleSharingUxUpdates, + enterpriseHasCredits, + enterpriseLimit, + enterpriseRequestAccess, + enterpriseFirstTimeUser }} /> diff --git a/components/x-gift-article/src/lib/constants.js b/components/x-gift-article/src/lib/constants.js index 792b42a11..c1a149436 100644 --- a/components/x-gift-article/src/lib/constants.js +++ b/components/x-gift-article/src/lib/constants.js @@ -1,5 +1,6 @@ export const ShareType = { gift: 'gift', + enterprise: 'enterprise', nonGift: 'nonGift' } diff --git a/components/x-gift-article/src/lib/enterpriseApi.js b/components/x-gift-article/src/lib/enterpriseApi.js new file mode 100644 index 000000000..a6fca28ba --- /dev/null +++ b/components/x-gift-article/src/lib/enterpriseApi.js @@ -0,0 +1,94 @@ +export default class EnterpriseApiClient { + constructor(baseUrl) { + this.baseUrl = baseUrl + } + + /** + * Concatenates protocol, domain and path URLs. + * @param {string} path URL Path + * @returns {string} Fetch URL + * @throws {Error} if baseURL is empty + */ + getFetchUrl(path) { + if (!this.baseUrl) { + throw new Error('Enterprise Sharing API base url missing') + } + + return `${this.baseUrl}${path}` + } + + /** + * Makes a fetch request to the path with additional options + * @param {string} path URL path + * @param {RequestInit} additionalOptions fetch additional options + * @returns {Promise} A promise that resolves to the requested URL response parsed from json + */ + async fetchJson(path, additionalOptions) { + const url = this.getFetchUrl(path) + const options = Object.assign( + { + credentials: 'include' + }, + additionalOptions + ) + + const response = await fetch(url, options) + if (response.status === 403) { + // If ES API response code is 403 - User is B2B without access and should see the request access page + throw new Error('ShowRequestAccess') + } + if (response.status === 404) { + // If ES API response code is 404 - User is not B2B and should not see anything about ES + throw new Error('UserIsNotB2b') + } + return await response.json() + } + + /** + * @typedef EnterpriseSharingAllowance + * @type {object} + * @property {number} limit - number of views per share for the user's licence, null if licence doesn't have a ES package + * @property {boolean} hasCredits - true if user's licence has ES credits + * @property {boolean} firstTimeUser - true if user hasn't created an ES link before + * @property {boolean} enabled - true if enterprise sharing is enabled for this user + * @property {boolean} requestAccess - true if user should see the request access journey + */ + + /** + * Retrieves the Enterprise Sharing allowance for an user + * @returns {EnterpriseSharingAllowance} the Enterprise Sharing allowance for an user + */ + async getEnterpriseArticleAllowance() { + try { + const json = await this.fetchJson('/v1/users/me/allowance') + + return { + limit: json.limit, + hasCredits: json.hasCredits, + firstTimeUser: json.firstTimeUser, + enabled: true, + requestAccess: false + } + } catch (e) { + if (e?.message === 'ShowRequestAccess') { + // limit = 100 is the default value used for marketing ES ("share with up to 100 people") + return { enabled: true, limit: 100, hasCredits: false, firstTimeUser: false, requestAccess: true } + } + return { enabled: false, limit: 0, hasCredits: false, firstTimeUser: false, requestAccess: false } + } + } + + /** + * Generates an enterprise sharing redeem link for the contentId + * @param {string} contentId Article ID + * @returns {string} enterprise sharing redeem link URL + */ + async getESUrl(contentId) { + const json = await this.fetchJson('/v1/shares', { method: 'POST', body: JSON.stringify({ contentId }) }) + + return { + redemptionUrl: json.url, + redemptionLimit: json.redeemLimit + } + } +} diff --git a/components/x-gift-article/src/lib/tracking.js b/components/x-gift-article/src/lib/tracking.js index 302fbad00..7e72df219 100644 --- a/components/x-gift-article/src/lib/tracking.js +++ b/components/x-gift-article/src/lib/tracking.js @@ -17,6 +17,14 @@ module.exports = { longUrl }), + createESLink: (link) => + dispatchEvent({ + category: 'gift-link', + action: 'create', + linkType: 'enterpriseSharingLink', + link + }), + copyLink: (linkType, link) => dispatchEvent({ category: 'gift-link', diff --git a/components/x-gift-article/src/lib/updaters.js b/components/x-gift-article/src/lib/updaters.js index ea471c401..44a64e21b 100644 --- a/components/x-gift-article/src/lib/updaters.js +++ b/components/x-gift-article/src/lib/updaters.js @@ -21,7 +21,19 @@ export const showGiftUrlSection = (props) => ({ url: props.urls.gift || props.urls.dummy, urlType: props.urls.gift ? UrlType.gift : UrlType.dummy, mailtoUrl: props.mailtoUrls.gift, - showCopyConfirmation: false + isGiftUrlCreated: !!props.urls.gift, + showCopyConfirmation: false, + invalidResponseFromApi: false +}) + +export const showGiftEnterpriseSection = (props) => ({ + shareType: ShareType.enterprise, + url: props.urls.enterprise || props.urls.dummy, + urlType: props.urls.enterprise ? UrlType.gift : UrlType.dummy, + mailtoUrl: props.mailtoUrls.enterprise, + isGiftUrlCreated: !!props.urls.enterprise, + showCopyConfirmation: false, + invalidResponseFromApi: false }) export const showNonGiftUrlSection = (props) => ({ @@ -29,29 +41,34 @@ export const showNonGiftUrlSection = (props) => ({ url: props.urls.nonGift, urlType: UrlType.nonGift, mailtoUrl: props.mailtoUrls.nonGift, - showCopyConfirmation: false + isGiftUrlCreated: false, + showCopyConfirmation: false, + invalidResponseFromApi: false }) -export const setGiftUrl = (url, redemptionLimit, isShortened) => (props) => { - const mailtoUrl = createMailtoUrl(props.article.title, url) +export const setGiftUrl = + (url, redemptionLimit, isShortened, isEnterprise = false) => + (props) => { + const mailtoUrl = createMailtoUrl(props.article.title, url) - return { - url, - mailtoUrl, - redemptionLimit, - isGiftUrlCreated: true, - isGiftUrlShortened: isShortened, - urlType: UrlType.gift, + return { + url, + mailtoUrl, + redemptionLimit: isEnterprise ? props.redemptionLimit : redemptionLimit, //note: when creating an enterprise link we do not change the redemption limit (this value is only used in the gift message) + isGiftUrlCreated: true, + isGiftUrlShortened: isShortened, + urlType: isEnterprise ? UrlType.enterprise : UrlType.gift, - urls: Object.assign(props.urls, { - gift: url - }), + urls: Object.assign(props.urls, { + [isEnterprise ? 'enterprise' : 'gift']: url + }), - mailtoUrls: Object.assign(props.mailtoUrls, { - gift: mailtoUrl - }) + mailtoUrls: Object.assign(props.mailtoUrls, { + [isEnterprise ? 'enterprise' : 'gift']: mailtoUrl + }), + invalidResponseFromApi: false + } } -} export const setAllowance = (giftCredits, monthlyAllowance, nextRenewalDate) => { const date = new Date(nextRenewalDate) @@ -74,11 +91,15 @@ export const setShortenedNonGiftUrl = (shortenedUrl) => (props) => { isNonGiftUrlShortened: true, urls: Object.assign(props.urls, { - gift: shortenedUrl + nonGift: shortenedUrl }), mailtoUrls: Object.assign(props.mailtoUrls, { - gift: mailtoUrl + nonGift: mailtoUrl }) } } + +export const setErrorState = (errorState) => ({ + invalidResponseFromApi: errorState +}) diff --git a/components/x-gift-article/storybook/index.jsx b/components/x-gift-article/storybook/index.jsx index c9abcdc2d..ef919ffbf 100644 --- a/components/x-gift-article/storybook/index.jsx +++ b/components/x-gift-article/storybook/index.jsx @@ -72,6 +72,81 @@ export const FreeArticle = (args) => { ) } +export const WithEnterpriseSharing = (args) => { + require('./with-enterprise').fetchMock(fetchMock) + return ( +
+ {dependencies && } + + + + actions?.activate()} /> +
+ ) +} +WithEnterpriseSharing.storyName = 'With enterprise sharing' +WithEnterpriseSharing.args = require('./with-enterprise').args + +export const WithEnterpriseSharingWithoutCredits = (args) => { + require('./with-enterprise-no-credits').fetchMock(fetchMock) + return ( +
+ {dependencies && } + + + + actions?.activate()} /> +
+ ) +} +WithEnterpriseSharingWithoutCredits.storyName = 'With enterprise sharing (no credits)' +WithEnterpriseSharingWithoutCredits.args = require('./with-enterprise-no-credits').args + +export const WithEnterpriseSharingFirstTimeUser = (args) => { + require('./with-enterprise-first-time-user').fetchMock(fetchMock) + return ( +
+ {dependencies && } + + + + actions?.activate()} /> +
+ ) +} +WithEnterpriseSharingFirstTimeUser.storyName = 'With enterprise sharing (first time user)' +WithEnterpriseSharingFirstTimeUser.args = require('./with-enterprise-first-time-user').args + +export const WithEnterpriseSharingRequestAccess = (args) => { + require('./with-enterprise-request-access').fetchMock(fetchMock) + return ( +
+ {dependencies && } + + + + actions?.activate()} /> +
+ ) +} +WithEnterpriseSharingRequestAccess.storyName = 'With enterprise sharing (request access)' +WithEnterpriseSharingRequestAccess.args = require('./with-enterprise-request-access').args + +export const WithEnterpriseSharingLink = (args) => { + require('./with-enterprise-sharing-link').fetchMock(fetchMock) + return ( +
+ {dependencies && } + + + + actions?.activate()} /> +
+ ) +} +WithEnterpriseSharingLink.storyName = 'With enterprise sharing (link generated)' +WithEnterpriseSharingLink.args = require('./with-enterprise-sharing-link').args + FreeArticle.storyName = 'Free article' FreeArticle.args = require('./free-article').args diff --git a/components/x-gift-article/storybook/with-enterprise-first-time-user.js b/components/x-gift-article/storybook/with-enterprise-first-time-user.js new file mode 100644 index 000000000..5c5d5eff4 --- /dev/null +++ b/components/x-gift-article/storybook/with-enterprise-first-time-user.js @@ -0,0 +1,47 @@ +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` + +exports.args = { + title: 'Share this article (with enterprise sharing - first time user journey)', + isFreeArticle: false, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title' + }, + showMobileShareLinks: true, + id: 'base-gift-article-static-id', + enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com` +} + +// This reference is only required for hot module loading in development +// +exports.m = module + +exports.fetchMock = (fetchMock) => { + fetchMock + .restore() + .get('/article/gift-credits', { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) + .get(`https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, { + limit: 120, + hasCredits: true, + firstTimeUser: true + }) +} diff --git a/components/x-gift-article/storybook/with-enterprise-no-credits.js b/components/x-gift-article/storybook/with-enterprise-no-credits.js new file mode 100644 index 000000000..aee6190c3 --- /dev/null +++ b/components/x-gift-article/storybook/with-enterprise-no-credits.js @@ -0,0 +1,47 @@ +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` + +exports.args = { + title: 'Share this article (Enterprise Sharing without credits)', + isFreeArticle: false, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title' + }, + showMobileShareLinks: true, + id: 'base-gift-article-static-id', + enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com` +} + +// This reference is only required for hot module loading in development +// +exports.m = module + +exports.fetchMock = (fetchMock) => { + fetchMock + .restore() + .get('/article/gift-credits', { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) + .get(`https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, { + limit: 120, + hasCredits: false, + firstTimeUser: false + }) +} diff --git a/components/x-gift-article/storybook/with-enterprise-request-access.js b/components/x-gift-article/storybook/with-enterprise-request-access.js new file mode 100644 index 000000000..3c7deb3f8 --- /dev/null +++ b/components/x-gift-article/storybook/with-enterprise-request-access.js @@ -0,0 +1,43 @@ +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` + +exports.args = { + title: 'Share this article (with enterprise sharing - request access journey)', + isFreeArticle: false, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title' + }, + showMobileShareLinks: true, + id: 'base-gift-article-static-id', + enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com` +} + +// This reference is only required for hot module loading in development +// +exports.m = module + +exports.fetchMock = (fetchMock) => { + fetchMock + .restore() + .get('/article/gift-credits', { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) + .get(`https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, 403) +} diff --git a/components/x-gift-article/storybook/with-enterprise-sharing-link.js b/components/x-gift-article/storybook/with-enterprise-sharing-link.js new file mode 100644 index 000000000..1c5caa4fe --- /dev/null +++ b/components/x-gift-article/storybook/with-enterprise-sharing-link.js @@ -0,0 +1,49 @@ +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` + +exports.args = { + title: 'Share this article (with enterprise sharing link)', + isFreeArticle: false, + isGiftUrlCreated: true, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title' + }, + showMobileShareLinks: true, + id: 'base-gift-article-static-id', + enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com`, + shareType: 'enterprise' +} + +// This reference is only required for hot module loading in development +// +exports.m = module + +exports.fetchMock = (fetchMock) => { + fetchMock + .restore() + .get('/article/gift-credits', { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + remainingAllowance: 1 + }) + .get(`https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, { + limit: 120, + hasCredits: true, + firstTimeUser: false + }) +} diff --git a/components/x-gift-article/storybook/with-enterprise.js b/components/x-gift-article/storybook/with-enterprise.js new file mode 100644 index 000000000..6a1779953 --- /dev/null +++ b/components/x-gift-article/storybook/with-enterprise.js @@ -0,0 +1,52 @@ +const articleId = 'article id' +const articleUrl = 'https://www.ft.com/content/blahblahblah' +const articleUrlRedeemed = 'https://enterprise-sharing.ft.com/gift-url-redeemed' +const nonGiftArticleUrl = `${articleUrl}?shareType=nongift` + +exports.args = { + title: 'Share this article (with enterprise sharing)', + isFreeArticle: false, + article: { + id: articleId, + url: articleUrl, + title: 'Title Title Title Title' + }, + showMobileShareLinks: true, + id: 'base-gift-article-static-id', + enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com` +} + +// This reference is only required for hot module loading in development +// +exports.m = module + +exports.fetchMock = (fetchMock) => { + fetchMock + .restore() + .get('/article/gift-credits', { + allowance: 20, + consumedCredits: 5, + remainingCredits: 15, + renewalDate: '2018-08-01T00:00:00Z' + }) + .get(`/article/shorten-url/${encodeURIComponent(articleUrlRedeemed)}`, { + shortenedUrl: 'https://shortened-gift-url' + }) + .get(`/article/shorten-url/${encodeURIComponent(nonGiftArticleUrl)}`, { + shortenedUrl: 'https://shortened-non-gift-url' + }) + .get(`/article/gift-link/${encodeURIComponent(articleId)}`, { + redemptionUrl: articleUrlRedeemed, + redemptionLimit: 3, + remainingAllowance: 1 + }) + .get(`https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, { + limit: 120, + hasCredits: true, + firstTimeUser: false + }) + .post(`https://enterprise-sharing-api.ft.com/v1/shares`, { + url: articleUrlRedeemed, + redeemLimit: 120 + }) +} diff --git a/components/x-gift-article/storybook/with-gift-credits.js b/components/x-gift-article/storybook/with-gift-credits.js index 3f1ed3b65..c611eb591 100644 --- a/components/x-gift-article/storybook/with-gift-credits.js +++ b/components/x-gift-article/storybook/with-gift-credits.js @@ -12,7 +12,8 @@ exports.args = { title: 'Title Title Title Title' }, showMobileShareLinks: true, - id: 'base-gift-article-static-id' + id: 'base-gift-article-static-id', + enterpriseApiBaseUrl: `https://enterprise-sharing-api.ft.com` } // This reference is only required for hot module loading in development @@ -38,4 +39,5 @@ exports.fetchMock = (fetchMock) => { redemptionUrl: articleUrlRedeemed, remainingAllowance: 1 }) + .get(`https://enterprise-sharing-api.ft.com/v1/users/me/allowance`, 404) }