Skip to content

Commit

Permalink
Merge pull request #757 from Financial-Times/ENTST-353-sharing-highli…
Browse files Browse the repository at this point in the history
…ghts-b-2-b-sharer

include highlights to non-gift & gift shares
  • Loading branch information
GlynnPhillips authored Jan 9, 2024
2 parents e9062b0 + 4ae721b commit c550209
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 101 deletions.
56 changes: 29 additions & 27 deletions components/x-gift-article/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,44 @@ To get correct styling, Your app should have:
Component provided by this module expects a map of [gift article properties](#properties). They can be used with vanilla JavaScript or JSX (if you are not familiar check out [WTF is JSX][jsx-wtf] first). For example if you were writing your application using React you could use the component like this:

```jsx
import React from 'react';
import { ShareArticleModal } from '@financial-times/x-gift-article';
import React from 'react'
import { ShareArticleModal } from '@financial-times/x-gift-article'

// A == B == C
const a = ShareArticleModal(props);
const b = <ShareArticleModal {...props} />;
const c = React.createElement(ShareArticleModal, props);
const a = ShareArticleModal(props)
const b = <ShareArticleModal {...props} />
const c = React.createElement(ShareArticleModal, props)
```

Your app should trigger the `activate` action to activate the gift article form when your app actually displays the form. For example, if your app is client-side rendered, you can use `actionsRef` to trigger this action:

```jsx
import { h, Component } from '@financial-times/x-engine';
import { ShareArticleModal } from '@financial-times/x-gift-article';
import { h, Component } from '@financial-times/x-engine'
import { ShareArticleModal } from '@financial-times/x-gift-article'

class Container extends Component {
showShareArticleModal() {
if(this.shareArticleModalActions) {
this.setState({ showShareArticleModal: true });
if (this.shareArticleModalActions) {
this.setState({ showShareArticleModal: true })

// trigger the action
this.shareArticleModalActions.activate();
this.shareArticleModalActions.activate()
}
}

render() {
return <div>
<button onClick={() => this.showShareArticleModal()}>
Share
</button>

<div style={{display: this.state.showShareArticleModal ? 'block' : 'none'}}>
<ShareArticleModal {...this.props} actionsRef={actions => this.shareArticleModalActions = actions} />
return (
<div>
<button onClick={() => this.showShareArticleModal()}>Share</button>

<div style={{ display: this.state.showShareArticleModal ? 'block' : 'none' }}>
<ShareArticleModal
{...this.props}
actionsRef={(actions) => (this.shareArticleModalActions = actions)}
/>
</div>
</div>
</div>
)
}
}
```
Expand All @@ -71,12 +74,11 @@ All `x-` components are designed to be compatible with a variety of runtimes, no

### Properties

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
`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.

| 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 |
| `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. |
33 changes: 1 addition & 32 deletions components/x-gift-article/src/AdvancedSharingOptions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,7 @@ import { NoCreditAlert } from './NoCreditAlert'
import { ReceivedHighlightsAlert } from './ReceivedHighlightsAlert'

export const AdvancedSharingOptions = (props) => {
const {
shareType,
actions,
showHighlightsCheckbox,
includeHighlights,
enterpriseHasCredits,
giftCredits,
showHighlightsRecipientMessage,
hasHighlights
} = props
const { shareType, actions, enterpriseHasCredits, giftCredits, showHighlightsRecipientMessage } = props
const onValueChange = (event) => {
if (event.target.value === ShareType.enterprise) {
actions.showEnterpriseUrlSection(event)
Expand All @@ -23,16 +14,11 @@ export const AdvancedSharingOptions = (props) => {
}
}

const includeHighlightsHandler = (event) => {
actions.setIncludeHighlights(event.target.checked)
}

return (
<div>
<div
className="o-forms-field o-forms-field--optional o-forms-field--professional share-article-dialog__advanced-sharing-options"
role="group"
aria-labelledby="radio-group-title"
>
<span className="o-forms-input o-forms-input--radio-round">
<span className="o-forms-input--radio-round__container">
Expand Down Expand Up @@ -70,23 +56,6 @@ export const AdvancedSharingOptions = (props) => {
</NoCreditAlert>
)}
{showHighlightsRecipientMessage && <ReceivedHighlightsAlert {...props} />}
{showHighlightsCheckbox && hasHighlights && (
<div className="o-forms-input o-forms-input--checkbox o-forms-field share-article-dialog__include-highlights">
<label htmlFor="includeHighlights">
<input
type="checkbox"
id="includeHighlights"
name="includeHighlights"
value={includeHighlights}
checked={includeHighlights}
onChange={includeHighlightsHandler}
disabled={shareType !== ShareType.enterprise}
data-trackable="make-highlights-visible"
/>
<span className="o-forms-input__label x-gift-article__checkbox-span">Include highlights</span>
</label>
</div>
)}
</div>
)
}
38 changes: 29 additions & 9 deletions components/x-gift-article/src/GiftArticle.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as updaters from './lib/updaters'
import { ShareType } from './lib/constants'
import ShareArticleDialog from './ShareArticleDialog'
import { parsedSavedAnnotationsFromLocalStorage } from './lib/highlightsHelpers'
import HighlightsApiClient from './lib/highlightsApi'

const isCopySupported =
typeof document !== 'undefined' && document.queryCommandSupported && document.queryCommandSupported('copy')
Expand All @@ -20,6 +21,8 @@ const withGiftFormActions = withActions(
})
const enterpriseApi = new EnterpriseApiClient(initialProps.enterpriseApiBaseUrl)

const highlightsApiClient = new HighlightsApiClient()

return {
showGiftUrlSection() {
return updaters.showGiftUrlSection
Expand All @@ -42,15 +45,22 @@ const withGiftFormActions = withActions(
},

async createGiftUrl() {
const { redemptionUrl, redemptionLimit } = await api.getGiftUrl(initialProps.article.id)

if (redemptionUrl) {
const { url, isShortened } = await api.getShorterUrl(redemptionUrl)
tracking.createGiftLink(url, redemptionUrl)
return async (state) => {
let response
const { highlightsAccessToken } = await highlightsApiClient.shareHighlights(
initialProps.article.id,
state.includeHighlights
)
response = await api.getGiftUrl(initialProps.article.id, highlightsAccessToken)
const { redemptionUrl, redemptionLimit } = response
if (redemptionUrl) {
const { url, isShortened } = await api.getShorterUrl(redemptionUrl)
tracking.createGiftLink(url, redemptionUrl)

return updaters.setGiftUrl(url, redemptionLimit, isShortened)
} else {
return updaters.setErrorState(true)
return updaters.setGiftUrl(url, redemptionLimit, isShortened)(state)
} else {
return updaters.setErrorState(true)
}
}
},

Expand All @@ -60,7 +70,17 @@ const withGiftFormActions = withActions(
state.showFreeArticleAlert = false
return state
}
const { url, isShortened } = await api.getShorterUrl(state.urls.nonGift)

const nonGiftUrl = new URL(state.urls.nonGift)
const { highlightsAccessToken } = await highlightsApiClient.shareHighlights(
initialProps.article.id,
state.includeHighlights
)
if (highlightsAccessToken) {
nonGiftUrl.searchParams.append('highlights', highlightsAccessToken)
}

const { url, isShortened } = await api.getShorterUrl(nonGiftUrl.toString())
tracking.createNonGiftLink(url, state.urls.nonGift)

if (isShortened) {
Expand Down
91 changes: 64 additions & 27 deletions components/x-gift-article/src/ShareArticleDialog.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@
@include oNormalise();
@include oTypography();

@include oButtons($opts: (
'sizes': ('big'),
'types': ('primary'),
'themes': ('professional', 'b2c'),
));
@include oButtons(
$opts: (
'sizes': (
'big'
),
'types': (
'primary'
),
'themes': (
'professional',
'b2c'
)
)
);

@include oForms(
$opts: (
Expand All @@ -34,27 +43,55 @@
)
);

@include oMessage($opts: (
'types': ('action', 'alert'),
'states': ('error', 'success', 'neutral'),
'layouts': ('inner')
));
@include oMessage(
$opts: (
'types': (
'action',
'alert'
),
'states': (
'error',
'success',
'neutral'
),
'layouts': (
'inner'
)
)
);

@include oMessageAddState(
$name: 'received-highlights',
$opts: (
'foreground-color': 'slate',
'background-color': whitesmoke,
'icon': 'info',
), $types: ('action', 'alert'));
'icon': 'info'
),
$types: (
'action',
'alert'
)
);

@include oShare($opts: (
'icons': ('x', 'facebook', 'linkedin', 'mail', 'whatsapp')
));
@include oShare(
$opts: (
'icons': (
'x',
'facebook',
'linkedin',
'mail',
'whatsapp'
)
)
);

@include oIcons($opts: (
'icons': ('mail')
));
@include oIcons(
$opts: (
'icons': (
'mail'
)
)
);

.share-article-dialog__wrapper {
@include oNormaliseBoxSizing();
Expand Down Expand Up @@ -213,13 +250,13 @@
font-size: 8px;
line-height: 1;
user-select: none;
// Increase hit zone of the button around it for better usability
&:after {
position: absolute;
content: '';
top: -(oSpacingByName('s3'));
right: -(oSpacingByName('s3'));
left: -(oSpacingByName('s3'));
bottom: -(oSpacingByName('s3'));
}
// Increase hit zone of the button around it for better usability
&:after {
position: absolute;
content: '';
top: -(oSpacingByName('s3'));
right: -(oSpacingByName('s3'));
left: -(oSpacingByName('s3'));
bottom: -(oSpacingByName('s3'));
}
}
24 changes: 23 additions & 1 deletion components/x-gift-article/src/SharedLinkTypeSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const SharedLinkTypeSelector = (props) => {
enterpriseRequestAccess,
showAdvancedSharingOptions,
enterpriseHasCredits,
giftCredits
giftCredits,
hasHighlights,
includeHighlights
} = props
const advancedSharingEnabled = enterpriseEnabled && !enterpriseRequestAccess
const canShareWithNonSubscribers = giftCredits > 0 || enterpriseHasCredits
Expand All @@ -37,6 +39,10 @@ export const SharedLinkTypeSelector = (props) => {
}
}

const includeHighlightsHandler = (event) => {
actions.setIncludeHighlights(event.target.checked)
}

return (
<div
id="share-with-non-subscribers-checkbox"
Expand Down Expand Up @@ -78,6 +84,22 @@ export const SharedLinkTypeSelector = (props) => {
</NoCreditAlert>
)}
{showAdvancedSharingOptions && <AdvancedSharingOptions {...props} />}
{hasHighlights && enterpriseEnabled && (
<div className="o-forms-input o-forms-input--checkbox o-forms-field share-article-dialog__include-highlights">
<label htmlFor="includeHighlights">
<input
type="checkbox"
id="includeHighlights"
name="includeHighlights"
value={includeHighlights}
checked={includeHighlights}
onChange={includeHighlightsHandler}
data-trackable="make-highlights-visible"
/>
<span className="o-forms-input__label x-gift-article__checkbox-span">Include highlights</span>
</label>
</div>
)}
</div>
)
}
11 changes: 9 additions & 2 deletions components/x-gift-article/src/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,16 @@ export default class ApiClient {
}
}

async getGiftUrl(articleId) {
async getGiftUrl(articleId, highlightsToken = undefined) {
try {
const json = await this.fetchJson('/article/gift-link/' + encodeURIComponent(articleId))
let json
if (highlightsToken) {
json = await this.fetchJson(
`/article/gift-link/${encodeURIComponent(articleId)}?highlightsToken=${highlightsToken}`
)
} else {
json = await this.fetchJson(`/article/gift-link/${encodeURIComponent(articleId)}`)
}

if (json.errors) {
throw new Error(`Failed to get gift article link: ${json.errors.join(', ')}`)
Expand Down
2 changes: 2 additions & 0 deletions components/x-gift-article/src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export const UrlType = {
gift: 'gift-link',
nonGift: 'non-gift-link'
}

export const HIGHLIGHTS_BASE_URL = 'https://enterprise-user-annotations-api.ft.com/v1'
Loading

0 comments on commit c550209

Please sign in to comment.