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 ({
Copy link
@@ -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 && (
+
+
+
+ {enterpriseLimit && !enterpriseRequestAccess
+ ? `Up to ${enterpriseLimit} recipients`
+ : `Multiple recipients`}
+ Enterprise
+ {enterpriseAlert && }
+
+
+ )}
+
- {isArticleSharingUxUpdates ? (
-
- Gift to anyone (uses 1 credit )
-
- ) : (
-
- with anyone (uses 1 gift credit)
-
- )}
+
+ {enterpriseEnabled ? `Single recipient` : `with anyone`}
+
@@ -44,15 +67,7 @@ export default ({ shareType, showGiftUrlSection, isArticleSharingUxUpdates, show
checked={shareType === ShareType.nonGift}
onChange={showNonGiftUrlSection}
/>
- {isArticleSharingUxUpdates ? (
-
- Share with other FT subscribers
-
- ) : (
-
- with other FT subscribers
-
- )}
+ FT subscribers only
)
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)
}