-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
173 additions
and
0 deletions.
There are no files selected for viewing
35 changes: 35 additions & 0 deletions
35
client/reader/components/crowdsignal-poll/error-boundary.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters