Skip to content

Commit

Permalink
Social: Add share status info (Automattic#39073)
Browse files Browse the repository at this point in the history
* Create REST endpoint

* Add share status logic to the store

* Update dependencies

* Show share status

* Add changelog

* Fix button size

* Fix up versions

* @wordpress/* packages are a nightmare to work with TypeScript

* Handle the case when all the connections are disabled and the post is not shared to any connection

* Add a message when there is timeout

* Fix typo

* Make use of usePostMeta
  • Loading branch information
manzoorwanijk authored Aug 27, 2024
1 parent 5281706 commit 9462203
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 8 deletions.
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Added share status info to Jetpack sidebar
3 changes: 2 additions & 1 deletion projects/js-packages/publicize-components/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-publicize-components",
"version": "0.63.0",
"version": "0.63.1-alpha",
"description": "A library of JS components required by the Publicize editor plugin",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/publicize-components/#readme",
"bugs": {
Expand Down Expand Up @@ -32,6 +32,7 @@
"@wordpress/components": "28.5.0",
"@wordpress/compose": "7.5.0",
"@wordpress/data": "10.5.0",
"@wordpress/date": "5.5.0",
"@wordpress/edit-post": "8.5.0",
"@wordpress/editor": "14.5.0",
"@wordpress/element": "6.5.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
import { useSelect } from '@wordpress/data';
import { PluginPostPublishPanel } from '@wordpress/edit-post';
import { store as editorStore } from '@wordpress/editor';
import { usePostMeta } from '../../hooks/use-post-meta';
import { store as socialStore } from '../../social-store';
import { ShareStatusModal } from '../share-status-modal';
import { ShareStatus } from './share-status';

/**
* Post publish share status component.
*
* @return {import('react').ReactNode} - Post publish share status component.
*/
export function PostPublishShareStatus() {
const { featureFlags } = useSelect( select => {
const { isPublicizeEnabled: willPostBeShared } = usePostMeta();
const { featureFlags, postId, isPostPublised } = useSelect( select => {
const store = select( socialStore );

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- `@wordpress/editor` is a nightmare to work with TypeScript
const _editorStore = select( editorStore ) as any;

return {
featureFlags: store.featureFlags(),
postId: _editorStore.getCurrentPostId(),
isPostPublised: _editorStore.isCurrentPostPublished(),
};
}, [] );

if ( ! featureFlags.useShareStatus ) {
if ( ! featureFlags.useShareStatus || ! willPostBeShared || ! isPostPublised ) {
return null;
}

return (
<PluginPostPublishPanel id="publicize-share-status">
Your post was successfully shared in 4 connections.
<ShareStatusModal />
<ShareStatus postId={ postId } />
</PluginPostPublishPanel>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { Button } from '@automattic/jetpack-components';
import { Spinner } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { getDate, isInTheFuture } from '@wordpress/date';
import { store as editorStore } from '@wordpress/editor';
import { useEffect } from '@wordpress/element';
import { __, _n, sprintf } from '@wordpress/i18n';
import { store as socialStore } from '../../social-store';
import Notice from '../notice';
import { ShareStatusModal } from '../share-status-modal';
import styles from './styles.module.scss';

export type ShareStatusProps = {
postId: number;
};

const ONE_MINUTE_IN_MS = 60 * 1000;

/**
* Share status component.
*
* @param {ShareStatusProps} props - Component props.
*
* @return {import('react').ReactNode} - Share status UI.
*/
export function ShareStatus( { postId }: ShareStatusProps ) {
const shareStatus = useSelect(
select => select( socialStore ).getPostShareStatus( postId ),
[ postId ]
);

// Whether the post has been published more than one minute ago.
const hasBeenMoreThanOneMinute = useSelect( select => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- `@wordpress/editor` is a nightmare to work with TypeScript
const date = ( select( editorStore ) as any ).getEditedPostAttribute( 'date' );

const oneMinuteAfterPostDate = new Date( Number( getDate( date ) ) + ONE_MINUTE_IN_MS );

// @ts-expect-error isInTheFuture is typed incorrectly as it should accept a Date object apart from a string.
return ! isInTheFuture( oneMinuteAfterPostDate );
}, [] );

// @ts-expect-error `invalidateResolution` exists in every store
const { invalidateResolution } = useDispatch( socialStore );

useEffect( () => {
if ( ! hasBeenMoreThanOneMinute && ! shareStatus.loading && ! shareStatus.done ) {
// Fire the next request as soon as the previous one is done but we are not done yet.
invalidateResolution( 'getPostShareStatus', [ postId ] );
}
}, [
hasBeenMoreThanOneMinute,
invalidateResolution,
postId,
shareStatus.loading,
shareStatus.done,
] );

if ( shareStatus.loading ) {
return (
<div className={ styles[ 'loading-block' ] }>
<Spinner />
<span className={ styles[ 'loading-text' ] }>
{ __( 'Sharing to your social media…', 'jetpack' ) }
</span>
</div>
);
}

const numberOfFailedShares = shareStatus.shares.filter(
share => share.status === 'failure'
).length;

if ( numberOfFailedShares > 0 ) {
return (
<Notice type="warning">
<p>
{ sprintf(
/* translators: %d: number of failed shares */
_n(
'Your post was unable to be shared to %d connection.',
'Your post was unable to be shared to %d connections.',
numberOfFailedShares,
'jetpack'
),
numberOfFailedShares
) }
</p>
<Button variant="link" size="small">
{ __( 'Review status and try again', 'jetpack' ) }
</Button>
</Notice>
);
}

if ( ! shareStatus.done ) {
return <span>{ __( 'The request to share your post is still in progress.', 'jetpack' ) }</span>;
}

if ( ! shareStatus.shares.length ) {
return <span>{ __( 'Your post was not shared.', 'jetpack' ) }</span>;
}

return (
<>
<b>{ __( 'Your post was shared.', 'jetpack' ) }</b>&nbsp;{ '🎉' }
<p>
{ sprintf(
/* translators: %d: number of connections to which a post was shared */
_n(
'You post was successfuly shared to %d connection.',
'You post was successfuly shared to %d connections.',
shareStatus.shares.length,
'jetpack'
),
shareStatus.shares.length
) }
</p>
<ShareStatusModal />
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.loading-block {
display: flex;
align-items: center;

:global(.components-spinner) {
margin-top: 0px;
}
}

.loading-text {
color: var(--jp-gray-40);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getShareMessageMaxLength } from '../../utils';
/**
* Returns the post meta values.
*
* @return {import('./types').UsePostMeta} The post meta values.
* @return {import('../../utils/types').UsePostMeta} The post meta values.
*/
export function usePostMeta() {
const { editPost } = useDispatch( editorStore );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ export const REMOVE_ABORT_CONTROLLERS = 'REMOVE_ABORT_CONTROLLERS';
export const REQUEST_TYPE_DEFAULT = 'DEFAULT';

export const REQUEST_TYPE_REFRESH_CONNECTIONS = 'REFRESH_CONNECTIONS';

export const FETCH_POST_SHARE_STATUS = 'FETCH_POST_SHARE_STATUS' as const;

export const RECEIVE_POST_SHARE_STATUS = 'RECEIVE_POST_SHARE_STATUS' as const;
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as connectionData from './connection-data';
import siteSettingActions from './jetpack-settings';
import jetpackSocialSettings from './jetpack-social-settings';
import * as shareStatus from './share-status';
import socialImageGeneratorSettingActions from './social-image-generator-settings';
import socialNotesSettings from './social-notes-settings';

const actions = {
...shareStatus,
...siteSettingActions,
...socialImageGeneratorSettingActions,
...jetpackSocialSettings,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { SocialStoreState } from '../types';
import { FETCH_POST_SHARE_STATUS, RECEIVE_POST_SHARE_STATUS } from './constants';

/**
* Returns an action object used in signalling that the post share status
* has been requested and is loading.
*
* @param {number} postId - Post ID.
* @param {boolean} [loading] - Loading status.
* @return {object} Action object.
*/
export function fetchPostShareStatus( postId: number, loading = true ) {
return {
type: FETCH_POST_SHARE_STATUS,
postId,
loading,
};
}

/**
* Returns an action object used in signalling that the post share status has been received.
*
* @param {SocialStoreState[ 'shareStaus' ][ number ]} shareStatus - Post share status.
* @param {number} postId - Post ID.
*
* @return {object} Action object.
*/
export function receivePostShareStaus(
shareStatus: SocialStoreState[ 'shareStatus' ][ number ],
postId: number
) {
return {
type: RECEIVE_POST_SHARE_STATUS,
shareStatus,
postId,
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { combineReducers } from '@wordpress/data';
import connectionData from './connection-data';
import jetpackSettings from './jetpack-settings';
import { shareStatus } from './share-status';
import siteData from './site-data';
import socialImageGeneratorSettings from './social-image-generator-settings';

Expand All @@ -9,6 +10,7 @@ const reducer = combineReducers( {
connectionData,
jetpackSettings,
socialImageGeneratorSettings,
shareStatus,
hasPaidPlan: ( state = false ) => state,
userConnectionUrl: ( state = '' ) => state,
useAdminUiV1: ( state = false ) => state,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { FETCH_POST_SHARE_STATUS, RECEIVE_POST_SHARE_STATUS } from '../actions/constants';
import { fetchPostShareStatus, receivePostShareStaus } from '../actions/share-status';
import { SocialStoreState } from '../types';

type Action =
| ReturnType< typeof fetchPostShareStatus | typeof receivePostShareStaus >
| { type: 'default' };

/**
* Connection data reducer
*
* @param {SocialStoreState['shareStaus']} state - State object.
* @param {Action} action - Action object.
*
* @return {SocialStoreState['shareStaus']} - The updated state.
*/
export function shareStatus(
state: SocialStoreState[ 'shareStatus' ] = {},
action: Action
): SocialStoreState[ 'shareStatus' ] {
switch ( action.type ) {
case FETCH_POST_SHARE_STATUS:
return {
...state,
[ action.postId ]: {
shares: [],
...state?.[ action.postId ],
loading: action.loading ?? true,
},
};
case RECEIVE_POST_SHARE_STATUS:
return {
...state,
[ action.postId ]: {
...action.shareStatus,
loading: false,
},
};
}

return state;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import apiFetch from '@wordpress/api-fetch';
import { store as editorStore } from '@wordpress/editor';
import { setConnections } from './actions/connection-data';
import { setJetpackSettings } from './actions/jetpack-settings';
import { fetchPostShareStatus, receivePostShareStaus } from './actions/share-status';
import { setSocialImageGeneratorSettings } from './actions/social-image-generator-settings';
import { fetchJetpackSettings, fetchSocialImageGeneratorSettings } from './controls';

Expand Down Expand Up @@ -58,8 +60,35 @@ export function getConnections() {
};
}

/**
* Resolves the post share status.
*
* @param {number} postId - The post ID.
*
* @return {Function} Resolver
*/
export function getPostShareStatus( postId ) {
return async ( { dispatch } ) => {
if ( ! postId ) {
return;
}

try {
dispatch( fetchPostShareStatus( postId ) );
const result = await apiFetch( {
path: `jetpack/v4/social/share-status/${ postId }`,
} );

dispatch( receivePostShareStaus( result, postId ) );
} catch ( error ) {
dispatch( fetchPostShareStatus( postId, false ) );
}
};
}

export default {
getJetpackSettings,
getSocialImageGeneratorSettings,
getConnections,
getPostShareStatus,
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as connectionDataSelectors from './connection-data';
import jetpackSettingSelectors from './jetpack-settings';
import * as shareStatusSelectors from './share-status';
import siteDataSelectors from './site-data';
import socialImageGeneratorSettingsSelectors from './social-image-generator-settings';

Expand All @@ -8,6 +9,7 @@ const selectors = {
...connectionDataSelectors,
...jetpackSettingSelectors,
...socialImageGeneratorSettingsSelectors,
...shareStatusSelectors,
userConnectionUrl: state => state.userConnectionUrl,
useAdminUiV1: state => state.useAdminUiV1,
featureFlags: state => state.featureFlags,
Expand Down
Loading

0 comments on commit 9462203

Please sign in to comment.