Skip to content

Commit

Permalink
Reader: add feedback poll (#98768)
Browse files Browse the repository at this point in the history
  • Loading branch information
holdercp authored Jan 24, 2025
1 parent d50cf2e commit e59f11c
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 0 deletions.
35 changes: 35 additions & 0 deletions client/reader/components/crowdsignal-poll/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import config from '@automattic/calypso-config';
import { Component, ReactNode } from 'react';
import { logToLogstash } from 'calypso/lib/logstash';

interface CrowdsignalPollErrorBoundaryProps {
children: ReactNode;
}

class CrowdsignalPollErrorBoundary extends Component< CrowdsignalPollErrorBoundaryProps > {
state = { error: null };

componentDidCatch( error: Error ) {
this.setState( { error } );

logToLogstash( {
feature: 'calypso_client',
message: 'Reader Crowdsignal poll error',
severity: config( 'env_id' ) === 'production' ? 'error' : 'debug',
extra: {
env: config( 'env_id' ),
type: 'reader_crowdsignal_poll_error',
message: String( error ),
},
} );
}

render() {
if ( this.state.error ) {
return null;
}
return this.props.children;
}
}

export default CrowdsignalPollErrorBoundary;
48 changes: 48 additions & 0 deletions client/reader/components/crowdsignal-poll/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'calypso/state';
import { getCurrentUserDate } from 'calypso/state/current-user/selectors';
import { savePreference } from 'calypso/state/preferences/actions';
import { getPreference, hasReceivedRemotePreferences } from 'calypso/state/preferences/selectors';
import { isA8cTeamMember } from 'calypso/state/teams/selectors';
import ErrorBoundary from './error-boundary';
import CrowdsignalPollComponent from './main';

import './style.scss';

const READER_CROWDSIGNAL_POLL_VIEWED_PREFERENCE = 'reader-crowdsignal-poll-viewed';
const REGISTRATION_CUTOFF_DATE = new Date( '2025-01-01T00:00:00Z' );

const CrowdsignalPoll = () => {
const dispatch = useDispatch();

const remotePrefsLoaded = useSelector( hasReceivedRemotePreferences );
const isAutomattician = useSelector( isA8cTeamMember );
const userRegistrationDate = useSelector( getCurrentUserDate );
const hasViewedPollPref = useSelector( ( state ): boolean | undefined | null =>
getPreference( state, READER_CROWDSIGNAL_POLL_VIEWED_PREFERENCE )
);
const hasViewedPoll = useRef( hasViewedPollPref ); // Show the poll when the component first mounts, but not subsequently

const shouldNotRender =
( remotePrefsLoaded && hasViewedPoll.current ) ||
new Date( userRegistrationDate ) < REGISTRATION_CUTOFF_DATE ||
isAutomattician;

useEffect( () => {
if ( ! hasViewedPoll.current ) {
dispatch( savePreference( READER_CROWDSIGNAL_POLL_VIEWED_PREFERENCE, true ) );
}
}, [ dispatch ] );

if ( shouldNotRender ) {
return null;
}

return (
<ErrorBoundary>
<CrowdsignalPollComponent />
</ErrorBoundary>
);
};

export default CrowdsignalPoll;
82 changes: 82 additions & 0 deletions client/reader/components/crowdsignal-poll/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useEffect, useRef } from 'react';

type JQuery = () => object;
type Polldaddy = {
display: () => void;
};
type WindowWithPolldaddyAndJQuery = {
polldaddy: Polldaddy;
jQuery: JQuery;
} & Window &
typeof globalThis;

const POLL_ID = 14948860;

const createPollSettingsEl = ( pollId: number ): HTMLDivElement => {
const sliderMountEl = document.createElement( 'div' );
sliderMountEl.className = 'pd-embed';
sliderMountEl.dataset.settings = JSON.stringify( {
type: 'slider',
embed: 'poll',
delay: 100,
visit: 'multiple',
id: pollId,
} );

return sliderMountEl;
};

const CrowdsignalPollComponent = () => {
const mountRef = useRef< HTMLDivElement >( null );

const handleScriptLoad = () => {
if ( ! mountRef.current ) {
return;
}

const pollSettingsEl = createPollSettingsEl( POLL_ID );
mountRef.current.appendChild( pollSettingsEl );

// The polldaddy script depends on jQuery and will pull it itself and then call polldaddy.display() if it's not on the page.
// I know... it's gross.
if ( typeof ( window as WindowWithPolldaddyAndJQuery ).jQuery === 'undefined' ) {
return;
}

// If jQuery is already on the page, we need to call polldaddy.display() ourselves.
if ( typeof ( window as WindowWithPolldaddyAndJQuery ).polldaddy === 'undefined' ) {
return;
}

( window as WindowWithPolldaddyAndJQuery ).polldaddy.display();
};

useEffect( () => {
if ( ! mountRef.current ) {
return;
}

const scriptEl = document.createElement( 'script' );
scriptEl.setAttribute( 'src', 'https://app.crowdsignal.com/survey.js' );
scriptEl.setAttribute( 'async', 'true' );
scriptEl.setAttribute( 'charset', 'utf-8' );
scriptEl.onload = handleScriptLoad;

mountRef.current.appendChild( scriptEl );

return () => {
const sliderEl = document.getElementById( 'pd-embed-slider' ); // The polldaddy script adds a slider element to the end of the body, which won't get cleaned up by the component unmounting
const sliderStyleEl = sliderEl?.nextElementSibling; // The polldaddy script also adds style tag after the slider element, which we need to clean up
if ( sliderEl ) {
sliderEl.remove();
}
if ( sliderStyleEl && sliderStyleEl.tagName === 'STYLE' ) {
sliderStyleEl.remove();
}
};
}, [] );

return <div id="reader-crowdsignal-poll" ref={ mountRef }></div>;
};

export default CrowdsignalPollComponent;
6 changes: 6 additions & 0 deletions client/reader/components/crowdsignal-poll/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Undo some global styles specifically for the Crowdsignal poll
#pd-embed-slider {
.pds-radiobutton {
appearance: auto;
}
}
2 changes: 2 additions & 0 deletions client/reader/sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SidebarItem from 'calypso/layout/sidebar/item';
import SidebarMenu from 'calypso/layout/sidebar/menu';
import SidebarRegion from 'calypso/layout/sidebar/region';
import SidebarSeparator from 'calypso/layout/sidebar/separator';
import CrowdsignalPoll from 'calypso/reader/components/crowdsignal-poll';
import ReaderA8cConversationsIcon from 'calypso/reader/components/icons/a8c-conversations-icon';
import ReaderConversationsIcon from 'calypso/reader/components/icons/conversations-icon';
import ReaderDiscoverIcon from 'calypso/reader/components/icons/discover-icon';
Expand Down Expand Up @@ -297,6 +298,7 @@ export class ReaderSidebar extends Component {
<GlobalSidebar { ...props }>
<ReaderSidebarNudges />
{ this.renderSidebarMenu() }
<CrowdsignalPoll />
</GlobalSidebar>
);
}
Expand Down

0 comments on commit e59f11c

Please sign in to comment.