diff --git a/frontend/src/lib/components/InternalSurvey/InternalMultipleChoiceSurvey.tsx b/frontend/src/lib/components/InternalSurvey/InternalMultipleChoiceSurvey.tsx new file mode 100644 index 0000000000000..dee3b2f30cda8 --- /dev/null +++ b/frontend/src/lib/components/InternalSurvey/InternalMultipleChoiceSurvey.tsx @@ -0,0 +1,80 @@ +/** + * @fileoverview A component that displays an interactive survey within a session recording. It handles survey display, user responses, and submission + */ +import { LemonButton, LemonCheckbox, LemonTextArea } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' + +import { SurveyQuestion, SurveyQuestionType } from '~/types' + +import { internalMultipleChoiceSurveyLogic } from './internalMultipleChoiceSurveyLogic' + +interface InternalSurveyProps { + surveyId: string +} + +export function InternalMultipleChoiceSurvey({ surveyId }: InternalSurveyProps): JSX.Element { + const logic = internalMultipleChoiceSurveyLogic({ surveyId }) + const { survey, surveyResponse, showThankYouMessage, thankYouMessage, openChoice } = useValues(logic) + const { handleChoiceChange, handleSurveyResponse, setOpenChoice } = useActions(logic) + + if (!survey) { + return <> + } + + return ( +
+
+ {survey.questions.map((question: SurveyQuestion) => ( +
+ {showThankYouMessage && thankYouMessage} + {!showThankYouMessage && ( + <> + {question.question} + {question.type === SurveyQuestionType.MultipleChoice && ( +
    + {question.choices.map((choice, index) => { + // Add an open choice text area if the last choice is an open choice + if (index === question.choices.length - 1 && question.hasOpenChoice) { + return ( +
    + {choice} + +
    + ) + } + return ( +
  • + handleChoiceChange(choice, checked)} + label={choice} + className="font-normal" + /> +
  • + ) + })} +
+ )} + + {question.buttonText ?? 'Submit'} + + + )} +
+ ))} +
+
+ ) +} diff --git a/frontend/src/lib/components/InternalSurvey/internalMultipleChoiceSurveyLogic.ts b/frontend/src/lib/components/InternalSurvey/internalMultipleChoiceSurveyLogic.ts new file mode 100644 index 0000000000000..9c756539ebb78 --- /dev/null +++ b/frontend/src/lib/components/InternalSurvey/internalMultipleChoiceSurveyLogic.ts @@ -0,0 +1,95 @@ +import { actions, afterMount, kea, key, listeners, path, props, reducers } from 'kea' +import posthog from 'posthog-js' + +import { Survey } from '~/types' + +import type { internalMultipleChoiceSurveyLogicType } from './internalMultipleChoiceSurveyLogicType' + +export interface InternalSurveyLogicProps { + surveyId: string +} + +export const internalMultipleChoiceSurveyLogic = kea([ + path(['lib', 'components', 'InternalSurvey', 'internalMultipleChoiceSurveyLogicType']), + props({} as InternalSurveyLogicProps), + key((props) => props.surveyId), + actions({ + getSurveys: () => ({}), + setSurvey: (survey: Survey) => ({ survey }), + handleSurveys: (surveys: Survey[]) => ({ surveys }), + handleSurveyResponse: () => ({}), + handleChoiceChange: (choice: string, isAdded: boolean) => ({ choice, isAdded }), + setShowThankYouMessage: (showThankYouMessage: boolean) => ({ showThankYouMessage }), + setThankYouMessage: (thankYouMessage: string) => ({ thankYouMessage }), + setOpenChoice: (openChoice: string) => ({ openChoice }), + }), + reducers({ + survey: [ + null as Survey | null, + { + setSurvey: (_, { survey }) => survey, + }, + ], + thankYouMessage: [ + 'Thank you for your feedback!', + { + setThankYouMessage: (_, { thankYouMessage }) => thankYouMessage, + }, + ], + showThankYouMessage: [ + false as boolean, + { + setShowThankYouMessage: (_, { showThankYouMessage }) => showThankYouMessage, + }, + ], + openChoice: [ + null as string | null, + { + setOpenChoice: (_, { openChoice }) => openChoice, + }, + ], + surveyResponse: [ + [] as string[], + { + handleChoiceChange: (state, { choice, isAdded }) => + isAdded ? [...state, choice] : state.filter((c: string) => c !== choice), + }, + ], + }), + listeners(({ actions, values, props }) => ({ + /** When surveyId is set, get the list of surveys for the user */ + setSurveyId: () => {}, + /** Callback for the surveys response. Filter it to the surveyId and set the survey */ + handleSurveys: ({ surveys }) => { + const survey = surveys.find((s: Survey) => s.id === props.surveyId) + if (survey) { + posthog.capture('survey shown', { + $survey_id: props.surveyId, + }) + actions.setSurvey(survey) + + if (survey.appearance?.thankYouMessageHeader) { + actions.setThankYouMessage(survey.appearance?.thankYouMessageHeader) + } + } + }, + /** When the survey response is sent, capture the response and show the thank you message */ + handleSurveyResponse: () => { + const payload = { + $survey_id: props.surveyId, + $survey_response: values.surveyResponse, + } + if (values.openChoice) { + payload.$survey_response.push(values.openChoice) + } + posthog.capture('survey sent', payload) + + actions.setShowThankYouMessage(true) + setTimeout(() => actions.setSurvey(null as unknown as Survey), 5000) + }, + })), + afterMount(({ actions }) => { + /** When the logic is mounted, set the surveyId from the props */ + posthog.getSurveys((surveys) => actions.handleSurveys(surveys as unknown as Survey[])) + }), +]) diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index e2a84d413715f..8963d698a6bbe 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -317,6 +317,7 @@ export const SESSION_REPLAY_MINIMUM_DURATION_OPTIONS: LemonSelectOptions(false) + + /** + * Handle the opt in change + * @param checked + */ + const handleOptInChange = (checked: boolean): void => { + updateCurrentTeam({ + session_recording_opt_in: checked, + }) + + //If the user opts out, we show the survey + setShowSurvey(!checked) + } return (
@@ -521,16 +537,13 @@ export function ReplayGeneral(): JSX.Element { { - updateCurrentTeam({ - // when switching replay on or off, - // we set defaults for some of the other settings - session_recording_opt_in: checked, - }) + handleOptInChange(checked) }} label="Record user sessions" bordered checked={!!currentTeam?.session_recording_opt_in} /> + {showSurvey && }