Skip to content

Commit e46f3dd

Browse files
gmjuhaszmanzoorwanijk
authored andcommitted
Social: Add Social Share Status modal for published posts (#39051)
* Register meta with name of `jetpack_social_post_shares` * Add new meta to usePostMeta hook * Add bare components with fetching data * changelog * Add moment * Populate the modal * Remove moment and schema as we use store * Improve share retrieval and css * Only order the list * Rebase onto the global modal * Remove leftover file * revert bad change * Fix minification issue * remove external_id from types * Improve normalizeShareStatus --------- Co-authored-by: Manzoor Wani <manzoorwani.jk@gmail.com>
1 parent 52f166e commit e46f3dd

File tree

13 files changed

+283
-41
lines changed

13 files changed

+283
-41
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Add share status log modal to published posts

projects/js-packages/publicize-components/src/components/share-status-modal/index.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.

projects/js-packages/publicize-components/src/components/share-status-modal/styles.module.scss

Lines changed: 0 additions & 4 deletions
This file was deleted.

projects/js-packages/publicize-components/src/components/share-status/modal.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Modal } from '@wordpress/components';
33
import { useDispatch, useSelect } from '@wordpress/data';
44
import { __ } from '@wordpress/i18n';
55
import { store as socialStore } from '../../social-store';
6+
import { ShareList } from './share-list';
67
import styles from './styles.module.scss';
78

89
/**
@@ -14,13 +15,15 @@ export function ShareStatusModal() {
1415
const { closeShareStatusModal } = useDispatch( socialStore );
1516

1617
return (
17-
<Modal
18-
className={ styles.modal }
19-
onRequestClose={ closeShareStatusModal }
20-
title={ __( 'Share status', 'jetpack' ) }
21-
>
22-
Content goes here
23-
</Modal>
18+
<div className={ styles.wrapper }>
19+
<Modal
20+
onRequestClose={ closeShareStatusModal }
21+
title={ __( 'Sharing status', 'jetpack' ) }
22+
className={ styles.modal }
23+
>
24+
<ShareList />
25+
</Modal>
26+
</div>
2427
);
2528
}
2629

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { getDate, humanTimeDiff } from '@wordpress/date';
2+
import ConnectionIcon from '../connection-icon';
3+
import { ShareStatusAction } from './share-status-action';
4+
import { ShareStatusLabel } from './share-status-label';
5+
import styles from './styles.module.scss';
6+
7+
/**
8+
*
9+
* ShareInfo component
10+
*
11+
* @param {object} props - component props
12+
* @param {object} props.share - share object
13+
* @return {import('react').ReactNode} - React element
14+
*/
15+
export function ShareInfo( { share } ) {
16+
const { service, external_name, profile_picture, timestamp, status, message } = share;
17+
18+
return (
19+
<div className={ styles[ 'share-item' ] }>
20+
<ConnectionIcon
21+
serviceName={ service }
22+
label={ external_name }
23+
profilePicture={ profile_picture }
24+
/>
25+
<div className={ styles[ 'share-item-name-wrapper' ] }>
26+
<div className={ styles[ 'share-item-name' ] }>{ external_name }</div>
27+
</div>
28+
<div>
29+
{
30+
// @ts-expect-error - humanTimeDiff is incorrectly typed, first argument can be a timestamp
31+
humanTimeDiff( timestamp * 1000, getDate() )
32+
}
33+
</div>
34+
<ShareStatusLabel status={ status } message={ message } />
35+
<ShareStatusAction status={ status } shareLink={ 'success' === status ? message : '' } />
36+
</div>
37+
);
38+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Spinner } from '@wordpress/components';
2+
import { useSelect } from '@wordpress/data';
3+
import { store as editorStore } from '@wordpress/editor';
4+
import { __ } from '@wordpress/i18n';
5+
import { store as socialStore } from '../../social-store';
6+
import { ShareInfo } from './share-info';
7+
import styles from './styles.module.scss';
8+
9+
/**
10+
* ShareList component
11+
*
12+
* @return {import('react').ReactNode} - Share status modal component.
13+
*/
14+
export function ShareList() {
15+
const { shareStatus } = useSelect( select => {
16+
const store = select( socialStore );
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- `@wordpress/editor` is a nightmare to work with TypeScript
18+
const _editorStore = select( editorStore ) as any;
19+
20+
return {
21+
shareStatus: store.getPostShareStatus( _editorStore.getCurrentPostId() ),
22+
};
23+
}, [] );
24+
25+
return (
26+
<div className="connection-management">
27+
{ shareStatus.loading && (
28+
<div className={ styles.spinner }>
29+
<Spinner /> { __( 'Loading…', 'jetpack' ) }
30+
</div>
31+
) }
32+
{ shareStatus.shares.length > 0 && (
33+
<ul className={ styles[ 'share-log-list' ] }>
34+
{ shareStatus.shares.map( ( share, idx ) => (
35+
<li
36+
key={ `${ share.external_id || share.connection_id }${ idx }}` }
37+
className={ styles[ 'share-log-list-item' ] }
38+
>
39+
<ShareInfo share={ share } />
40+
</li>
41+
) ) }
42+
</ul>
43+
) }
44+
</div>
45+
);
46+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ExternalLink } from '@wordpress/components';
2+
import { __ } from '@wordpress/i18n';
3+
import styles from './styles.module.scss';
4+
5+
/**
6+
*
7+
* Share status action component.
8+
*
9+
* @param {object} props - component props
10+
* @param {boolean} props.status - status of the share
11+
* @param {string} props.shareLink - link to the share
12+
* @return {import('react').ReactNode} - React element
13+
*/
14+
export function ShareStatusAction( { status, shareLink } ) {
15+
return (
16+
<div className={ styles[ 'share-status-action-wrapper' ] }>
17+
{ 'success' !== status ? (
18+
<span>Retry</span>
19+
) : (
20+
<ExternalLink className={ styles[ 'profile-link' ] } href={ shareLink }>
21+
{ __( 'View', 'jetpack' ) }
22+
</ExternalLink>
23+
) }
24+
</div>
25+
);
26+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { IconTooltip, Text } from '@automattic/jetpack-components';
2+
import { __, _x } from '@wordpress/i18n';
3+
import { Icon, check } from '@wordpress/icons';
4+
import clsx from 'clsx';
5+
import React from 'react';
6+
import styles from './styles.module.scss';
7+
8+
/**
9+
*
10+
* Share status label component.
11+
*
12+
* @param {object} props - component props
13+
* @param {boolean} props.status - status of the share
14+
* @param {string} props.message - link to the share, or error message if failed
15+
* @return {import('react').ReactNode} - React element
16+
*/
17+
export function ShareStatusLabel( { status, message } ) {
18+
const isSuccessful = 'success' === status;
19+
20+
const icon = isSuccessful ? (
21+
<Icon className={ styles[ 'share-status-icon' ] } icon={ check } />
22+
) : (
23+
<IconTooltip
24+
title={ __( 'Sharing failed with the following message:', 'jetpack' ) }
25+
className={ styles[ 'share-status-icon-tooltip' ] }
26+
>
27+
<Text variant="body-small">{ message }</Text>
28+
</IconTooltip>
29+
);
30+
31+
return (
32+
<div
33+
className={ clsx( styles[ 'share-status-wrapper' ], {
34+
[ styles[ 'share-status-success' ] ]: isSuccessful,
35+
[ styles[ 'share-status-failure' ] ]: ! isSuccessful,
36+
} ) }
37+
>
38+
<div className={ styles[ 'share-status-icon' ] }>{ icon }</div>
39+
<div className={ styles[ 'share-status-label' ] }>
40+
{ isSuccessful
41+
? _x( 'Shared', 'The sharing is successful', 'jetpack' )
42+
: __( 'Failed', 'jetpack' ) }
43+
</div>
44+
</div>
45+
);
46+
}

projects/js-packages/publicize-components/src/components/share-status/styles.module.scss

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,88 @@
22
margin-top: 1rem;
33
padding-block: 1rem;
44
}
5+
6+
.wrapper {
7+
margin-top: 1rem;
8+
padding-block: 1rem;
9+
}
10+
11+
.spinner{
12+
margin: 0 1rem 1rem 1rem;
13+
}
14+
15+
.modal {
16+
width: 60rem;
17+
}
18+
19+
.share-log-list {
20+
outline: 1px solid var(--jp-gray-5);
21+
border-radius: 4px;
22+
margin: 0;
23+
width: 100%;
24+
25+
.share-log-list-item {
26+
margin-bottom: 0px;
27+
padding: 0.8rem 1rem;
28+
29+
&:not(:last-child) {
30+
border-bottom: 1px solid var(--jp-gray-5);
31+
}
32+
}
33+
34+
.share-item {
35+
display: flex;
36+
gap: 1rem;
37+
align-items: center;
38+
}
39+
}
40+
41+
.share-item-name-wrapper {
42+
display: flex;
43+
flex-direction: column;
44+
gap: 0.5rem;
45+
flex: 1;
46+
overflow: auto;
47+
}
48+
49+
.share-item-name {
50+
display: flex;
51+
align-items: center;
52+
}
53+
54+
.share-status-wrapper {
55+
display: flex;
56+
align-items: center;
57+
width: 5rem;
58+
59+
&.share-status-success {
60+
color: var(--jp-green-50);
61+
}
62+
63+
&.share-status-failure {
64+
color: var(--jp-red-50);
65+
height: 29px;
66+
}
67+
}
68+
69+
.share-status-label {
70+
flex: 1;
71+
}
72+
73+
.share-status-icon-tooltip {
74+
width: 24px;
75+
top: 2px;
76+
margin-inline-start: 2px;
77+
78+
> button {
79+
color: var(--jp-red-50) !important;
80+
}
81+
}
82+
83+
.share-status-icon {
84+
fill: var(--jp-green-50);
85+
}
86+
87+
.share-status-action-wrapper {
88+
width: 3rem;
89+
}

projects/js-packages/publicize-components/src/social-store/resolvers.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import apiFetch from '@wordpress/api-fetch';
22
import { store as editorStore } from '@wordpress/editor';
3+
import { normalizeShareStatus } from '../utils/share-status';
34
import { setConnections } from './actions/connection-data';
45
import { setJetpackSettings } from './actions/jetpack-settings';
56
import { fetchPostShareStatus, receivePostShareStaus } from './actions/share-status';
@@ -74,10 +75,12 @@ export function getPostShareStatus( _postId ) {
7475

7576
try {
7677
dispatch( fetchPostShareStatus( postId ) );
77-
const result = await apiFetch( {
78+
let result = await apiFetch( {
7879
path: `jetpack/v4/social/share-status/${ postId }`,
7980
} );
8081

82+
result = normalizeShareStatus( result );
83+
8184
dispatch( receivePostShareStaus( result, postId ) );
8285
} catch ( error ) {
8386
dispatch( fetchPostShareStatus( postId, false ) );

0 commit comments

Comments
 (0)