diff --git a/projects/packages/publicize/_inc/components/connection-icon/index.tsx b/projects/packages/publicize/_inc/components/connection-icon/index.tsx
index 451da6654ed6..f51d8bcbff62 100644
--- a/projects/packages/publicize/_inc/components/connection-icon/index.tsx
+++ b/projects/packages/publicize/_inc/components/connection-icon/index.tsx
@@ -27,6 +27,11 @@ export type ConnectionIconProps = {
profilePicture: string;
disabled?: boolean;
className?: string;
+ // Visual size of the avatar + overlapping service icon. `small` (default)
+ // is 28×28 avatar + 14×14 service icon for compact rows / dataviews;
+ // `medium` is 32×32 avatar + 16×16 service icon for roomier rows such as
+ // the chassis Overview "Connected accounts" list.
+ size?: 'small' | 'medium';
};
/**
@@ -40,6 +45,7 @@ export function ConnectionIcon( {
profilePicture,
disabled,
className,
+ size = 'small',
}: ConnectionIconProps ) {
const [ imageErrorFor, setImageErrorFor ] = useState( null );
@@ -55,6 +61,7 @@ export function ConnectionIcon( {
` slot) — still renders at
+ // a sensible avatar size.
+ &.medium {
+ block-size: 32px;
+ inline-size: 32px;
+
+ img,
+ .avatar {
+ block-size: 32px;
+ border: 0;
+ inline-size: 32px;
+ }
+
+ .social-icon {
+ block-size: 16px;
+ inline-size: 16px;
+ }
}
}
diff --git a/projects/packages/publicize/_inc/components/connection-management/connection-info-modern.tsx b/projects/packages/publicize/_inc/components/connection-management/connection-info-modern.tsx
new file mode 100644
index 000000000000..efac45880a98
--- /dev/null
+++ b/projects/packages/publicize/_inc/components/connection-management/connection-info-modern.tsx
@@ -0,0 +1,142 @@
+import { useSelect } from '@wordpress/data';
+import { useState } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { chevronDown, info } from '@wordpress/icons';
+import { Collapsible, Icon, IconButton, Stack, Text } from '@wordpress/ui';
+import { store as socialStore } from '../../social-store';
+import ConnectionIcon from '../connection-icon';
+import { XNotice } from '../services/x-notice';
+import { ConnectionName } from './connection-name';
+import { ConnectionStatus, ConnectionStatusProps } from './connection-status';
+import { ConnectionTemplateEditor } from './connection-template';
+import { Disconnect } from './disconnect';
+import { MarkAsShared } from './mark-as-shared';
+import styles from './style-modern.module.scss';
+import type { SyntheticEvent } from 'react';
+
+type ConnectionInfoProps = ConnectionStatusProps & {
+ canMarkAsShared: boolean;
+};
+
+const stopPropagation = ( event: SyntheticEvent ) => event.stopPropagation();
+
+/**
+ * Connection info component
+ *
+ * @param {ConnectionInfoProps} props - component props
+ *
+ * @return React element
+ */
+export function ModernConnectionInfo( {
+ connection,
+ service,
+ canMarkAsShared,
+}: ConnectionInfoProps ) {
+ const [ isPanelOpen, setIsPanelOpen ] = useState( false );
+
+ const { canManageConnection, isUnsupported } = useSelect(
+ select => {
+ const { canUserManageConnection, getServicesBy } = select( socialStore );
+
+ return {
+ canManageConnection: canUserManageConnection( connection ),
+ isUnsupported: getServicesBy( 'status', 'unsupported' ).some(
+ ( { id } ) => id === connection.service_name
+ ),
+ };
+ },
+ [ connection ]
+ );
+
+ const hasStatus =
+ connection.status === 'broken' || connection.status === 'must_reauth' || isUnsupported;
+
+ const markAsSharedHelp = __(
+ 'If enabled, the connection will be available to all administrators, editors, and authors.',
+ 'jetpack-publicize-pkg'
+ );
+
+ return (
+
+ }
+ >
+
+
+ { /*
+ * The profile-name link lives inside the row, which doubles as
+ * the Collapsible.Trigger. Without stopping propagation a click
+ * on the link would also toggle the disclosure (and an anchor
+ * inside role="button" is invalid nesting). Mirror the
+ * connection-status-wrap below so the link opens the profile
+ * without toggling the panel.
+ */ }
+
+
+
+ { hasStatus ? (
+
+
+
+ ) : (
+
+ { service?.label }
+
+ ) }
+
+
+
+
+
+ { canMarkAsShared && (
+
+
+
+
+ ) }
+
+
+
+ { canManageConnection ? (
+
+ ) : (
+
+ { __( 'This connection is added by a site administrator.', 'jetpack-publicize-pkg' ) }
+
+ ) }
+ { service?.id === 'x' &&
}
+
+
+
+ );
+}
diff --git a/projects/packages/publicize/_inc/components/connection-management/connection-name.tsx b/projects/packages/publicize/_inc/components/connection-management/connection-name.tsx
index 4188ee85115f..abb6fc5268a3 100644
--- a/projects/packages/publicize/_inc/components/connection-management/connection-name.tsx
+++ b/projects/packages/publicize/_inc/components/connection-management/connection-name.tsx
@@ -8,16 +8,20 @@ import styles from './style.module.scss';
type ConnectionNameProps = {
connection: Connection;
+ /** Link tone. Defaults to the WPDS link default; the modernized chassis passes "neutral". */
+ tone?: 'neutral';
};
/**
* Connection name component
*
- * @param {ConnectionNameProps} props - component props
+ * @param {ConnectionNameProps} props - component props
+ * @param {Connection} props.connection - the connection to render
+ * @param {string} props.tone - optional WPDS link tone
*
* @return {import('react').ReactNode} - React element
*/
-export function ConnectionName( { connection }: ConnectionNameProps ) {
+export function ConnectionName( { connection, tone }: ConnectionNameProps ) {
const isUpdating = useSelect(
select => {
return select( socialStore ).getUpdatingConnections().includes( connection.connection_id );
@@ -30,7 +34,12 @@ export function ConnectionName( { connection }: ConnectionNameProps ) {
{ ! connection.profile_link ? (
{ connection.display_name }
) : (
-
+
{ connection.display_name }
) }
diff --git a/projects/packages/publicize/_inc/components/connection-management/connection-template/index.tsx b/projects/packages/publicize/_inc/components/connection-management/connection-template/index.tsx
index 563fa4ef617b..b6773e742fde 100644
--- a/projects/packages/publicize/_inc/components/connection-management/connection-template/index.tsx
+++ b/projects/packages/publicize/_inc/components/connection-management/connection-template/index.tsx
@@ -1,8 +1,18 @@
-import { siteHasFeature } from '@automattic/jetpack-script-data';
+import { getRedirectUrl } from '@automattic/jetpack-components';
+import { isSimpleSite, siteHasFeature } from '@automattic/jetpack-script-data';
+import { getSiteFragment, useAnalytics } from '@automattic/jetpack-shared-extension-utils';
import { useDebounce } from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
-import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
+import {
+ createInterpolateElement,
+ useCallback,
+ useEffect,
+ useRef,
+ useState,
+} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
+import { Link } from '@wordpress/ui';
+import { useIsModernized } from '../../../hooks/use-is-modernized';
import { store as socialStore } from '../../../social-store';
import { Connection } from '../../../social-store/types';
import { features } from '../../../utils/constants';
@@ -25,28 +35,46 @@ const HELP_TEXT = __(
'jetpack-publicize-pkg'
);
+const LABEL = __( 'Custom message for this connection', 'jetpack-publicize-pkg' );
+
+const NOOP = () => {};
+
/**
* Per-connection message template editor.
*
- * Renders only when the site has the `social-message-templates` feature
- * AND the `social-enhanced-publishing` paid plan, AND the user can manage
- * the connection. Auto-saves through `updateConnectionById` after the user
- * pauses typing.
+ * Renders the live editor when the site has the `social-message-templates`
+ * feature AND the `social-enhanced-publishing` paid plan, AND the user can
+ * manage the connection. On Jetpack sites that don't qualify, renders a
+ * disabled-textarea variant with an Upgrade link instead, so free-plan users
+ * still see what unlocks with an upgrade.
*
* @param {ConnectionTemplateEditorProps} props - The component's props.
- * @return The rendered editor, or `null` when gated out.
+ * @return The rendered editor or its locked upsell variant.
*/
export function ConnectionTemplateEditor( props: ConnectionTemplateEditorProps ) {
const { connection } = props;
- const featureEnabled = siteHasFeature( features.MESSAGE_TEMPLATES );
- const planEnabled = siteHasFeature( features.ENHANCED_PUBLISHING );
-
- const canManageConnection = useSelect(
- select => select( socialStore ).canUserManageConnection( connection ),
- [ connection ]
+ const isModernized = useIsModernized();
+
+ const { canManageConnection, globalTemplate } = useSelect(
+ select => ( {
+ canManageConnection: select( socialStore ).canUserManageConnection( connection ),
+ // Only the modernized chassis renders the gated upsell, which is the
+ // only consumer of the global default message. Keeping this read out
+ // of the legacy path preserves the trunk data dependencies exactly.
+ globalTemplate: isModernized
+ ? select( socialStore ).getSocialSettings().messageTemplate ?? ''
+ : '',
+ } ),
+ [ connection, isModernized ]
);
+ const { recordEvent } = useAnalytics();
+
+ const onUpgradeClick = useCallback( () => {
+ recordEvent( 'jetpack_social_per_network_customization_upgrade_click' );
+ }, [ recordEvent ] );
+
const savedTemplate = connection.template ?? '';
const [ draft, setDraft ] = useState( savedTemplate );
@@ -83,14 +111,60 @@ export function ConnectionTemplateEditor( props: ConnectionTemplateEditorProps )
[ debouncedSave ]
);
- if ( ! featureEnabled || ! planEnabled || ! canManageConnection ) {
+ if ( ! canManageConnection ) {
return null;
}
+ const featureEnabled = siteHasFeature( features.MESSAGE_TEMPLATES );
+ const planEnabled = siteHasFeature( features.ENHANCED_PUBLISHING );
+
+ if ( ! featureEnabled || ! planEnabled ) {
+ // The free-plan upsell ships only in the modernized chassis. The legacy
+ // admin page and block editor keep the trunk behavior (no editor when
+ // the plan/feature is missing).
+ if ( ! isModernized || isSimpleSite() ) {
+ return null;
+ }
+
+ const upgradeUrl = getRedirectUrl( 'jetpack-social-per-connection-template-upsell', {
+ site: getSiteFragment() || '',
+ query: 'redirect_to=' + encodeURIComponent( window.location.href ),
+ } );
+
+ const upsellHelp = createInterpolateElement(
+ __(
+ 'Showing your default share message. To customize it for this account,
upgrade your plan.',
+ 'jetpack-publicize-pkg'
+ ),
+ {
+ a: (
+
+ { null }
+
+ ),
+ }
+ );
+
+ return (
+
+
+
+ );
+ }
+
return (
! state, false );
const { deleteConnectionById } = useDispatch( socialStore );
@@ -94,7 +107,8 @@ export function Disconnect( { connection, variant = 'outline' }: DisconnectProps
) : (