Skip to content

Commit

Permalink
Social | Extract connection toggle state into a hook. (#36776)
Browse files Browse the repository at this point in the history
* Create useConnectionState hook to extract the connection state logic

* Use the new useConnectionState hook in ConnectionsList component

* Fix the misleading connections count

* Update types

* Add changelog

* Rename index to .tsx
  • Loading branch information
manzoorwanijk authored Apr 8, 2024
1 parent 99ff1ec commit 08ccdf1
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Extracted the connection toggle state logic
Original file line number Diff line number Diff line change
@@ -1,83 +1,24 @@
import { useSelect } from '@wordpress/data';
import { useCallback } from '@wordpress/element';
import { usePublicizeConfig } from '../../..';
import useAttachedMedia from '../../hooks/use-attached-media';
import useFeaturedImage from '../../hooks/use-featured-image';
import useImageGeneratorConfig from '../../hooks/use-image-generator-config';
import useMediaDetails from '../../hooks/use-media-details';
import useMediaRestrictions, { NO_MEDIA_ERROR } from '../../hooks/use-media-restrictions';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import { store as socialStore } from '../../social-store';
import PublicizeConnection from '../connection';
import PublicizeSettingsButton from '../settings-button';
import styles from './styles.module.scss';
import { useConnectionState } from './use-connection-state';

export const ConnectionsList: React.FC = () => {
const { connections, toggleById, enabledConnections } = useSocialMediaConnections();
const { isPublicizeEnabled, isPublicizeDisabledBySitePlan } = usePublicizeConfig();
const { isEnabled: isSocialImageGeneratorEnabledForPost } = useImageGeneratorConfig();
const { showShareLimits, numberOfSharesRemaining } = useSelect( select => {
return {
showShareLimits: select( socialStore ).showShareLimits(),
numberOfSharesRemaining: select( socialStore ).numberOfSharesRemaining(),
};
}, [] );
const { connections, toggleById } = useSocialMediaConnections();

const outOfConnections = showShareLimits && numberOfSharesRemaining <= enabledConnections.length;

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

const { attachedMedia, shouldUploadAttachedMedia } = useAttachedMedia();
const featuredImageId = useFeaturedImage();
const mediaId = attachedMedia[ 0 ]?.id || featuredImageId;

const { validationErrors, isConvertible } = useMediaRestrictions(
connections,
useMediaDetails( mediaId )[ 0 ],
{
isSocialImageGeneratorEnabledForPost,
shouldUploadAttachedMedia,
}
);
const shouldAutoConvert = isAutoConversionEnabled && isConvertible;

const isConnectionEnabled = useCallback(
( { enabled, is_healthy = true, connection_id } ) =>
enabled &&
! isPublicizeDisabledBySitePlan &&
false !== is_healthy &&
( ! validationErrors[ connection_id ] || shouldAutoConvert ) &&
validationErrors[ connection_id ] !== NO_MEDIA_ERROR,
[ isPublicizeDisabledBySitePlan, validationErrors, shouldAutoConvert ]
);
const { canBeTurnedOn, shouldBeDisabled } = useConnectionState();

return (
<ul className={ styles[ 'connections-list' ] }>
{ connections.map( conn => {
const {
display_name,
enabled,
id,
service_name,
toggleable,
profile_picture,
is_healthy,
connection_id,
} = conn;
const { display_name, id, service_name, profile_picture, connection_id } = conn;
const currentId = connection_id ? connection_id : id;

return (
<PublicizeConnection
disabled={
! isPublicizeEnabled ||
( ! enabled && toggleable && outOfConnections ) ||
false === is_healthy ||
( validationErrors[ currentId ] !== undefined && ! shouldAutoConvert ) ||
validationErrors[ currentId ] === NO_MEDIA_ERROR
}
enabled={ isConnectionEnabled( conn ) }
disabled={ shouldBeDisabled( conn ) }
enabled={ canBeTurnedOn( conn ) && conn.enabled }
key={ currentId }
id={ currentId }
label={ display_name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { _n, sprintf } from '@wordpress/i18n';
import usePublicizeConfig from '../../hooks/use-publicize-config';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import styles from './styles.module.scss';
import { useConnectionState } from './use-connection-state';

/**
* Displays enabled connections text.
Expand All @@ -12,19 +13,24 @@ import styles from './styles.module.scss';
export function EnabledConnectionsNotice() {
const { enabledConnections } = useSocialMediaConnections();
const { isPublicizeEnabled } = usePublicizeConfig();
const { canBeTurnedOn, shouldBeDisabled } = useConnectionState();

return enabledConnections.length && isPublicizeEnabled ? (
const validConnections = enabledConnections.filter(
connection => canBeTurnedOn( connection ) && ! shouldBeDisabled( connection )
);

return validConnections.length && isPublicizeEnabled ? (
<PanelRow>
<p className={ styles[ 'enabled-connections-notice' ] }>
{ sprintf(
/* translators: %d: number of connections */
_n(
'This post will be shared to %d connection.',
'This post will be shared to %d connections.',
enabledConnections.length,
validConnections.length,
'jetpack'
),
enabledConnections.length
validConnections.length
) }
</p>
</PanelRow>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useSelect } from '@wordpress/data';
import { useCallback } from '@wordpress/element';
import { useMemo } from 'react';
import { usePublicizeConfig } from '../../..';
import useAttachedMedia from '../../hooks/use-attached-media';
import useFeaturedImage from '../../hooks/use-featured-image';
import useImageGeneratorConfig from '../../hooks/use-image-generator-config';
import useMediaDetails from '../../hooks/use-media-details';
import useMediaRestrictions, { NO_MEDIA_ERROR } from '../../hooks/use-media-restrictions';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import { store as socialStore } from '../../social-store';
import { Connection } from '../../social-store/types';

export const useConnectionState = () => {
const { connections, enabledConnections } = useSocialMediaConnections();
const { isPublicizeEnabled, isPublicizeDisabledBySitePlan } = usePublicizeConfig();
const { isEnabled: isSocialImageGeneratorEnabledForPost } = useImageGeneratorConfig();
const { showShareLimits, numberOfSharesRemaining } = useSelect( select => {
return {
showShareLimits: select( socialStore ).showShareLimits(),
numberOfSharesRemaining: select( socialStore ).numberOfSharesRemaining(),
};
}, [] );
const { attachedMedia, shouldUploadAttachedMedia } = useAttachedMedia();
const featuredImageId = useFeaturedImage();
const mediaId = attachedMedia[ 0 ]?.id || featuredImageId;

const { validationErrors, isConvertible } = useMediaRestrictions(
connections,
useMediaDetails( mediaId )[ 0 ],
{
isSocialImageGeneratorEnabledForPost,
shouldUploadAttachedMedia,
}
);

const isAutoConversionEnabled = useSelect(
select => select( socialStore ).isAutoConversionEnabled(),
[]
);
const shouldAutoConvert = isAutoConversionEnabled && isConvertible;

const outOfConnections = showShareLimits && numberOfSharesRemaining <= enabledConnections.length;

/**
* Returns whether a connection is in good shape.
*
* A connection is in good shape if:
* - It is healthy
* - It has no validation errors
* - It does not have a NO_MEDIA_ERROR when media is required
*/
const isInGoodShape = useCallback(
( connection: Connection ) => {
const { id, is_healthy, connection_id } = connection;
const currentId = connection_id ? connection_id : id;

// 1. Be healthy
const isHealthy = false !== is_healthy;

// 2. Have no validation errors
const hasValidationErrors =
validationErrors[ currentId ] !== undefined && ! shouldAutoConvert;

// 3. Not have a NO_MEDIA_ERROR when media is required
const hasNoMediaError = validationErrors[ currentId ] === NO_MEDIA_ERROR;

return isHealthy && ! hasValidationErrors && ! hasNoMediaError;
},
[ shouldAutoConvert, validationErrors ]
);

/**
* Returns whether a connection should be disabled.
* Disabled here means the disabled prop of the ToggleControl
*
* A connection can be disabled if:
* - Publicize is disabled
* - There are no more connections available
* - The connection is not in good shape
*/
const shouldBeDisabled = useCallback(
( connection: Connection ) => {
const { enabled, toggleable } = connection;

const isOutOfConnections = ! enabled && toggleable && outOfConnections;
// A connection toggle should be disabled if
return (
// Publicize is disabled
! isPublicizeEnabled ||
// or if there are no more connections available
isOutOfConnections ||
// or the connection is not in good shape
! isInGoodShape( connection )
);
},
[ isInGoodShape, isPublicizeEnabled, outOfConnections ]
);

/**
* Returns whether a connection can be enabled.
* Enabled here means the checked state of the ToggleControl
*
* A connection can be enabled if:
* - Publicize is not disabled due to the current site plan
* - The connection is in good shape
*/
const canBeTurnedOn = useCallback(
( connection: Connection ) => {
// A connection toggle can be turned ON if
return (
// Publicize is not disabled due to the current site plan
! isPublicizeDisabledBySitePlan &&
// and the connection is in good shape
isInGoodShape( connection )
);
},
[ isInGoodShape, isPublicizeDisabledBySitePlan ]
);

return useMemo(
() => ( {
shouldBeDisabled,
canBeTurnedOn,
} ),
[ shouldBeDisabled, canBeTurnedOn ]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function getMustReauthConnections( state ) {
*
* @param {import("../types").SocialStoreState} state - State object.
*
* @returns {Array} List of enabled connections.
* @returns {Array<import("../types").Connection>} List of enabled connections.
*/
export function getEnabledConnections( state ) {
return getConnections( state ).filter( connection => connection.enabled );
Expand Down

0 comments on commit 08ccdf1

Please sign in to comment.