Skip to content

Commit

Permalink
Jetpack Social | Warn the user that their scheduled posts won't get s…
Browse files Browse the repository at this point in the history
…hared if they will get over limits. (#34183)

* Enhance RecordBarMeter component with `className`

* Add changelog

* Add store types

* Update store selectors and actions

* Update consumer hook to use new selectors

* Create useShareLimits hook

* Move `ShareCounter` to publicize-components and name it `ShareLimitsBar`

* Create separate notice components

* Fix the post editor notices

* Update Jetpack Social admin page to use the new meter bar

* Remove unused code left after #34111

* Add changelog

* Fix share count

* Update index.js

* Include active connections count for notices

* Fix i18n build error

* Enhance RecordBarMeter component with `className`

* Add changelog

* Extract constants and improve types

* Extract messages to utility function

* Enhance RecordBarMeter component with `className`

* Add changelog

* Fix up versions

* Revert "Remove unused code left after #34111"

This reverts commit ca45999.

* Use @wordpress/element instead of react

* Fix messaging

* Let TS infer type

* Combine the share limits logic into a single useSelect call

* Fix the limits shown even after a paid plan
  • Loading branch information
manzoorwanijk authored Nov 22, 2023
1 parent d55655d commit d386252
Show file tree
Hide file tree
Showing 27 changed files with 617 additions and 205 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Fixed Jetpack Social scheduled post messaging
1 change: 1 addition & 0 deletions projects/js-packages/publicize-components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export { default as useDismissNotice } from './src/hooks/use-dismiss-notice';
export * from './src/social-store';
export * from './src/utils';
export * from './src/components/share-post';
export * from './src/components/share-limits-bar';
export * from './src/hooks/use-saving-post';
export * from './src/components/share-buttons';
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

import { getRedirectUrl } from '@automattic/jetpack-components';
import { getSiteFragment } from '@automattic/jetpack-shared-extension-utils';
import { Button, PanelRow, Disabled, ExternalLink } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { Fragment, createInterpolateElement, useMemo, useCallback } from '@wordpress/element';
import { _n, sprintf, __ } from '@wordpress/i18n';
import { Button, Disabled, ExternalLink, PanelRow } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { createInterpolateElement, Fragment, useCallback, useMemo } from '@wordpress/element';
import { __, _n } from '@wordpress/i18n';
import useAttachedMedia from '../../hooks/use-attached-media';
import useDismissNotice from '../../hooks/use-dismiss-notice';
import useFeaturedImage from '../../hooks/use-featured-image';
Expand All @@ -22,13 +22,15 @@ import useRefreshAutoConversionSettings from '../../hooks/use-refresh-auto-conve
import useRefreshConnections from '../../hooks/use-refresh-connections';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import useSocialMediaMessage from '../../hooks/use-social-media-message';
import { CONNECTION_SERVICE_INSTAGRAM_BUSINESS, SOCIAL_STORE_ID } from '../../social-store';
import { CONNECTION_SERVICE_INSTAGRAM_BUSINESS, store as socialStore } from '../../social-store';
import { getSupportedAdditionalConnections } from '../../utils';
import PublicizeConnection from '../connection';
import MediaSection from '../media-section';
import MessageBoxControl from '../message-box-control';
import Notice from '../notice';
import PublicizeSettingsButton from '../settings-button';
import { ShareCountInfo } from './share-count-info';
import { ShareCountNotice } from './share-count-notice';
import styles from './styles.module.scss';

const MONTH_IN_SECONDS = 30 * 24 * 60 * 60;
Expand All @@ -42,7 +44,6 @@ const checkConnectionCode = ( connection, code ) =>
* @param {object} props - The component props.
* @param {boolean} props.isPublicizeEnabled - Whether Publicize is enabled for this post.
* @param {boolean} props.isPublicizeDisabledBySitePlan - A combination of the republicize feature being enabled and/or the post not being published.
* @param {number} props.numberOfSharesRemaining - The number of shares remaining for the current period. Optional.
* @param {boolean} props.isEnhancedPublishingEnabled - Whether enhanced publishing options are available. Optional.
* @param {boolean} props.isSocialImageGeneratorAvailable - Whether the Social Image Generator feature is available. Optional.
* @param {string} props.connectionsAdminUrl - URL to the Admin connections page
Expand All @@ -54,7 +55,6 @@ const checkConnectionCode = ( connection, code ) =>
export default function PublicizeForm( {
isPublicizeEnabled,
isPublicizeDisabledBySitePlan,
numberOfSharesRemaining = null,
isEnhancedPublishingEnabled = false,
shouldShowAdvancedPlanNudge = false,
isSocialImageGeneratorAvailable = false,
Expand All @@ -72,7 +72,12 @@ export default function PublicizeForm( {
const hasInstagramConnection = connections.some(
connection => connection.service_name === 'instagram-business'
);

const { showShareLimits, numberOfSharesRemaining } = useSelect( select => {
return {
showShareLimits: select( socialStore ).showShareLimits(),
numberOfSharesRemaining: select( socialStore ).numberOfSharesRemaining(),
};
} );
const shouldShowInstagramNotice =
! hasInstagramConnection &&
getSupportedAdditionalConnections().includes( CONNECTION_SERVICE_INSTAGRAM_BUSINESS ) &&
Expand All @@ -92,26 +97,7 @@ export default function PublicizeForm( {
checkConnectionCode( connection, 'unsupported' )
);

const outOfConnections =
numberOfSharesRemaining !== null && numberOfSharesRemaining <= enabledConnections.length;

const { isEditedPostDirty } = useSelect( 'core/editor' );
const { autosave } = useDispatch( 'core/editor' );
const autosaveAndRedirect = useCallback(
async ev => {
const target = ev.target.getAttribute( 'target' );
if ( isEditedPostDirty() && ! target ) {
ev.preventDefault();
await autosave();
window.location.href = ev.target.href;
}
if ( target ) {
ev.preventDefault();
window.open( ev.target.href, target, 'noreferrer' );
}
},
[ autosave, isEditedPostDirty ]
);
const outOfConnections = showShareLimits && numberOfSharesRemaining <= enabledConnections.length;

const onAdvancedNudgeDismiss = useCallback(
() => dismissNotice( NOTICES.advancedUpgradeEditor, 3 * MONTH_IN_SECONDS ),
Expand All @@ -124,7 +110,7 @@ export default function PublicizeForm( {
);

const isAutoConversionEnabled = useSelect(
select => select( SOCIAL_STORE_ID ).isAutoConversionEnabled(),
select => select( socialStore ).isAutoConversionEnabled(),
[]
);

Expand Down Expand Up @@ -319,53 +305,12 @@ export default function PublicizeForm( {
</li>
</ul>
</PanelRow>
{ numberOfSharesRemaining !== null && (
{ showShareLimits && (
<PanelRow>
<Notice type={ numberOfSharesRemaining < connections.length ? 'warning' : 'default' }>
<Fragment>
{ createInterpolateElement(
sprintf(
/* translators: %d is the number of shares remaining, upgradeLink is the link to upgrade to a different plan */
_n(
'You have %d share remaining in the next 30 days. <upgradeLink>Upgrade now</upgradeLink> to share more.',
'You have %d shares remaining in the next 30 days. <upgradeLink>Upgrade now</upgradeLink> to share more.',
numberOfSharesRemaining,
'jetpack'
),
numberOfSharesRemaining
),
{
upgradeLink: (
<a
className={ styles[ 'upgrade-link' ] }
href={ getRedirectUrl( 'jetpack-social-basic-plan-block-editor', {
site: getSiteFragment(),
query: 'redirect_to=' + encodeURIComponent( window.location.href ),
} ) }
onClick={ autosaveAndRedirect }
/>
),
}
) }
<br />
<a
className={ styles[ 'more-link' ] }
href={ getRedirectUrl( 'jetpack-social-block-editor-more-info', {
site: getSiteFragment(),
...( adminUrl
? { query: 'redirect_to=' + encodeURIComponent( adminUrl ) }
: {} ),
} ) }
target="_blank"
rel="noopener noreferrer"
onClick={ autosaveAndRedirect }
>
{ __( 'More about Jetpack Social.', 'jetpack' ) }
</a>
</Fragment>
</Notice>
<ShareCountNotice />
</PanelRow>
) }
<ShareCountInfo />
{ renderNotices() }
{ showValidationNotice &&
( Object.values( validationErrors ).includes( NO_MEDIA_ERROR )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ThemeProvider } from '@automattic/jetpack-components';
import { useSelect } from '@wordpress/data';
import { store as socialStore } from '../../social-store';
import { ShareLimitsBar } from '../share-limits-bar';
import styles from './styles.module.scss';

export const ShareCountInfo: React.FC = () => {
const { showShareLimits, scheduledSharesCount, shareCount, shareLimit, activeConnections } =
useSelect( select => {
const store = select( socialStore );

return {
showShareLimits: store.showShareLimits(),
scheduledSharesCount: store.getScheduledSharesCount(),
shareCount: store.getSharesUsedCount(),
shareLimit: store.getShareLimit(),
activeConnections: store.getEnabledConnections(),
};
}, [] );

if ( ! showShareLimits ) {
return null;
}

return (
<ThemeProvider>
<ShareLimitsBar
maxCount={ shareLimit }
currentCount={ shareCount }
scheduledCount={ scheduledSharesCount }
className={ styles[ 'bar-wrapper' ] }
activeConnectionsCount={ activeConnections.length }
/>
</ThemeProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { getRedirectUrl } from '@automattic/jetpack-components';
import { getSiteFragment } from '@automattic/jetpack-shared-extension-utils';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { createInterpolateElement, useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { useShareLimits } from '../../hooks/use-share-limits';
import { store as socialStore } from '../../social-store';
import Notice from '../notice';
import styles from './styles.module.scss';

export const ShareCountNotice: React.FC = () => {
const { isEditedPostDirty } = useSelect( editorStore, [] );
const { autosave } = useDispatch( editorStore );

const showShareLimits = useSelect( select => select( socialStore ).showShareLimits(), [] );

const autosaveAndRedirect = useCallback(
async ev => {
const target = ev.target.getAttribute( 'target' );
if ( isEditedPostDirty() && ! target ) {
ev.preventDefault();
await autosave();
window.location.href = ev.target.href;
}
if ( target ) {
ev.preventDefault();
window.open( ev.target.href, target, 'noreferrer' );
}
},
[ autosave, isEditedPostDirty ]
);
const { noticeType, message } = useShareLimits();

if ( ! showShareLimits ) {
return null;
}

return (
<Notice type={ noticeType }>
{ message }
<br />
{ createInterpolateElement(
__( '<upgradeLink>Upgrade now</upgradeLink> to share more.', 'jetpack' ),
{
upgradeLink: (
<a
className={ styles[ 'upgrade-link' ] }
href={ getRedirectUrl( 'jetpack-social-basic-plan-block-editor', {
site: getSiteFragment(),
query: 'redirect_to=' + encodeURIComponent( window.location.href ),
} ) }
onClick={ autosaveAndRedirect }
/>
),
}
) }
</Notice>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@
height: auto;
margin-inline-start: 15px;
}

.bar-wrapper {
:global .record-meter-bar__legend--items{
flex-direction: column;
gap: 0.5rem
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ const PublicizePanel = ( { prePublish, children } ) => {
hidePublicizeFeature,
isPublicizeDisabledBySitePlan,
togglePublicizeFeature,
isShareLimitEnabled,
numberOfSharesRemaining,
hasPaidPlan,
connectionsAdminUrl,
adminUrl,
isEnhancedPublishingEnabled,
Expand Down Expand Up @@ -93,9 +90,6 @@ const PublicizePanel = ( { prePublish, children } ) => {
isPublicizeEnabled={ isPublicizeEnabled }
isPublicizeDisabledBySitePlan={ isPublicizeDisabledBySitePlan }
connectionsAdminUrl={ connectionsAdminUrl }
numberOfSharesRemaining={
isShareLimitEnabled && ! hasPaidPlan ? numberOfSharesRemaining : null
}
isEnhancedPublishingEnabled={ isEnhancedPublishingEnabled }
isSocialImageGeneratorAvailable={ isSocialImageGeneratorAvailable }
adminUrl={ adminUrl }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { RecordMeterBar, Text } from '@automattic/jetpack-components';
import { useMemo } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import styles from './styles.module.scss';

export type ShareLimitsBarProps = {
currentCount: number;
scheduledCount: number;
activeConnectionsCount?: number;
maxCount: number;
text?: string;
className?: string;
};

export const ShareLimitsBar = ( {
currentCount,
maxCount,
scheduledCount,
activeConnectionsCount,
text,
className,
}: ShareLimitsBarProps ) => {
const items = useMemo( () => {
return [
{
count: currentCount,
backgroundColor: 'var(--jp-gray-90)',
label: __( 'Shares used', 'jetpack' ),
},
activeConnectionsCount !== undefined && {
count: activeConnectionsCount,
backgroundColor: 'var(--jp-gray-40)',
label: __( 'Active connections', 'jetpack' ),
},
{
count: scheduledCount,
backgroundColor: 'var(--jp-gray-20)',
label: __( 'Shares scheduled', 'jetpack' ),
},
].filter( Boolean );
}, [ currentCount, scheduledCount, activeConnectionsCount ] );
return (
<div className={ className }>
{ text ? <Text className={ styles.text }>{ text }</Text> : null }
<RecordMeterBar
totalCount={ maxCount }
items={ items }
showLegendLabelBeforeCount
className={ styles[ 'bar-wrapper' ] }
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.bar-wrapper {
:global .record-meter-bar__items{
height: 1rem;
}
}
Loading

0 comments on commit d386252

Please sign in to comment.