Skip to content

Commit

Permalink
feat(surveys): UX for team level survey config (#25543)
Browse files Browse the repository at this point in the history
This PR adds and updates views in the application to support setting team level survey configuration options.
These team level survey configurations are then inherited by any new survey created in the organization.

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Phanatic and github-actions[bot] authored Oct 14, 2024
1 parent 4e7d876 commit b134da4
Show file tree
Hide file tree
Showing 27 changed files with 273 additions and 95 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-surveys--new-survey--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-surveys--new-survey--light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-surveys--surveys-list--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-surveys--surveys-list--light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
179 changes: 98 additions & 81 deletions frontend/src/scenes/surveys/SurveyCustomization.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
import { LemonButton, LemonCheckbox, LemonDialog, LemonInput, LemonSelect } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { useValues } from 'kea'
import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'
import { upgradeModalLogic } from 'lib/components/UpgradeModal/upgradeModalLogic'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { surveyLogic } from 'scenes/surveys/surveyLogic'

import {
AvailableFeature,
MultipleSurveyQuestion,
RatingSurveyQuestion,
SurveyAppearance as SurveyAppearanceType,
SurveyQuestion,
SurveyQuestionType,
} from '~/types'
import { AvailableFeature, SurveyAppearance as SurveyAppearanceType } from '~/types'

import { defaultSurveyAppearance } from './constants'
import { surveysLogic } from './surveysLogic'

interface CustomizationProps {
appearance: SurveyAppearanceType
surveyQuestionItem: RatingSurveyQuestion | SurveyQuestion | MultipleSurveyQuestion
customizeRatingButtons: boolean
customizePlaceholderText: boolean
hasBranchingLogic: boolean
deleteBranchingLogic?: () => void
onAppearanceChange: (appearance: SurveyAppearanceType) => void
}

interface WidgetCustomizationProps extends Omit<CustomizationProps, 'surveyQuestionItem'> {}

export function Customization({ appearance, surveyQuestionItem, onAppearanceChange }: CustomizationProps): JSX.Element {
export function Customization({
appearance,
customizeRatingButtons,
customizePlaceholderText,
hasBranchingLogic,
onAppearanceChange,
deleteBranchingLogic,
}: CustomizationProps): JSX.Element {
const { surveysStylingAvailable } = useValues(surveysLogic)
const { surveyShufflingQuestionsAvailable, hasBranchingLogic } = useValues(surveyLogic)
const { deleteBranchingLogic } = useActions(surveyLogic)
const surveyShufflingQuestionsAvailable = true
const surveyShufflingQuestionsDisabledReason = surveyShufflingQuestionsAvailable
? ''
: 'Please add more than one question to the survey to enable shuffling questions'
Expand All @@ -41,72 +42,85 @@ export function Customization({ appearance, surveyQuestionItem, onAppearanceChan
<></>
</PayGateMini>
)}
<div className="mt-2">Background color</div>
<LemonInput
value={appearance?.backgroundColor}
onChange={(backgroundColor) => onAppearanceChange({ ...appearance, backgroundColor })}
disabled={!surveysStylingAvailable}
/>
<div className="mt-2">Border color</div>
<LemonInput
value={appearance?.borderColor || defaultSurveyAppearance.borderColor}
onChange={(borderColor) => onAppearanceChange({ ...appearance, borderColor })}
disabled={!surveysStylingAvailable}
/>
<LemonField.Pure className="mt-2" label="Background color">
<LemonInput
value={appearance?.backgroundColor}
onChange={(backgroundColor) => onAppearanceChange({ ...appearance, backgroundColor })}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>
<LemonField.Pure className="mt-2" label="Border color">
<LemonInput
value={appearance?.borderColor || defaultSurveyAppearance.borderColor}
onChange={(borderColor) => onAppearanceChange({ ...appearance, borderColor })}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>
<>
<div className="mt-2">Position</div>
<div className="flex gap-1">
{['left', 'center', 'right'].map((position) => {
return (
<LemonButton
key={position}
type="tertiary"
onClick={() => onAppearanceChange({ ...appearance, position })}
active={appearance.position === position}
disabledReason={
surveysStylingAvailable
? null
: 'Upgrade your plan to customize survey position.'
}
>
{position}
</LemonButton>
)
})}
</div>
<LemonField.Pure className="mt-2" label="Position">
<div className="flex gap-1">
{['left', 'center', 'right'].map((position) => {
return (
<LemonButton
key={position}
type="tertiary"
onClick={() => onAppearanceChange({ ...appearance, position })}
active={appearance.position === position}
disabledReason={
surveysStylingAvailable
? null
: 'Upgrade your plan to customize survey position.'
}
>
{position}
</LemonButton>
)
})}
</div>
</LemonField.Pure>
</>
{surveyQuestionItem.type === SurveyQuestionType.Rating && (
{customizeRatingButtons && (
<>
<div className="mt-2">Rating button color</div>
<LemonInput
value={appearance?.ratingButtonColor}
onChange={(ratingButtonColor) => onAppearanceChange({ ...appearance, ratingButtonColor })}
disabled={!surveysStylingAvailable}
/>
<div className="mt-2">Rating button active color</div>
<LemonInput
value={appearance?.ratingButtonActiveColor}
onChange={(ratingButtonActiveColor) =>
onAppearanceChange({ ...appearance, ratingButtonActiveColor })
}
disabled={!surveysStylingAvailable}
/>
<LemonField.Pure className="mt-2" label="Rating button color">
<LemonInput
value={appearance?.ratingButtonColor}
onChange={(ratingButtonColor) =>
onAppearanceChange({ ...appearance, ratingButtonColor })
}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>
<LemonField.Pure className="mt-2" label="Rating button active color">
<LemonInput
value={appearance?.ratingButtonActiveColor}
onChange={(ratingButtonActiveColor) =>
onAppearanceChange({ ...appearance, ratingButtonActiveColor })
}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>
</>
)}
<div className="mt-2">Button color</div>
<LemonInput
value={appearance?.submitButtonColor}
onChange={(submitButtonColor) => onAppearanceChange({ ...appearance, submitButtonColor })}
disabled={!surveysStylingAvailable}
/>
<div className="mt-2">Button text color</div>
<LemonInput
value={appearance?.submitButtonTextColor}
onChange={(submitButtonTextColor) => onAppearanceChange({ ...appearance, submitButtonTextColor })}
disabled={!surveysStylingAvailable}
/>
<LemonField.Pure className="mt-2" label="Button color">
<LemonInput
value={appearance?.submitButtonColor}
onChange={(submitButtonColor) => onAppearanceChange({ ...appearance, submitButtonColor })}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>

<LemonField.Pure className="mt-2" label="Button text color">
<LemonInput
value={appearance?.submitButtonTextColor}
onChange={(submitButtonTextColor) =>
onAppearanceChange({ ...appearance, submitButtonTextColor })
}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>

<LemonField.Pure
className="mt-2"
label="Survey form zIndex"
info="If the survey popup is hidden behind another overlapping UI element, set this value higher than the overlapping element's zIndex."
>
Expand All @@ -119,14 +133,15 @@ export function Customization({ appearance, surveyQuestionItem, onAppearanceChan
defaultValue="99999"
/>
</LemonField.Pure>
{surveyQuestionItem.type === SurveyQuestionType.Open && (
{customizePlaceholderText && (
<>
<div className="mt-2">Placeholder text</div>
<LemonInput
value={appearance?.placeholder || defaultSurveyAppearance.placeholder}
onChange={(placeholder) => onAppearanceChange({ ...appearance, placeholder })}
disabled={!surveysStylingAvailable}
/>
<LemonField.Pure className="mt-2" label="Placeholder text">
<LemonInput
value={appearance?.placeholder || defaultSurveyAppearance.placeholder}
onChange={(placeholder) => onAppearanceChange({ ...appearance, placeholder })}
disabled={!surveysStylingAvailable}
/>
</LemonField.Pure>
</>
)}
<div className="mt-4">
Expand Down Expand Up @@ -168,7 +183,9 @@ export function Customization({ appearance, surveyQuestionItem, onAppearanceChan
children: 'Continue',
status: 'danger',
onClick: () => {
deleteBranchingLogic()
if (deleteBranchingLogic) {
deleteBranchingLogic()
}
onAppearanceChange({ ...appearance, shuffleQuestions: true })
},
},
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/scenes/surveys/SurveyEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
LinkSurveyQuestion,
RatingSurveyQuestion,
SurveyQuestion,
SurveyQuestionType,
SurveyType,
SurveyUrlMatchType,
} from '~/types'
Expand Down Expand Up @@ -468,6 +469,16 @@ export default function SurveyEdit(): JSX.Element {
Feedback button customization
</div>
<WidgetCustomization
hasBranchingLogic={hasBranchingLogic}
deleteBranchingLogic={deleteBranchingLogic}
customizeRatingButtons={
survey.questions[0].type ===
SurveyQuestionType.Rating
}
customizePlaceholderText={
survey.questions[0].type ===
SurveyQuestionType.Open
}
appearance={value || defaultSurveyAppearance}
onAppearanceChange={(appearance) => {
onChange(appearance)
Expand All @@ -479,7 +490,14 @@ export default function SurveyEdit(): JSX.Element {
)}
<Customization
appearance={value || defaultSurveyAppearance}
surveyQuestionItem={survey.questions[0]}
hasBranchingLogic={hasBranchingLogic}
deleteBranchingLogic={deleteBranchingLogic}
customizeRatingButtons={
survey.questions[0].type === SurveyQuestionType.Rating
}
customizePlaceholderText={
survey.questions[0].type === SurveyQuestionType.Open
}
onAppearanceChange={(appearance) => {
onChange(appearance)
}}
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/scenes/surveys/SurveyTemplates.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import './SurveyTemplates.scss'

import { LemonButton } from '@posthog/lemon-ui'
import { useActions } from 'kea'
import { useActions, useValues } from 'kea'
import { PageHeader } from 'lib/components/PageHeader'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { SceneExport } from 'scenes/sceneTypes'
import { teamLogic } from 'scenes/teamLogic'
import { urls } from 'scenes/urls'

import { Survey } from '~/types'
Expand All @@ -20,6 +21,10 @@ export const scene: SceneExport = {
export function SurveyTemplates(): JSX.Element {
const { setSurveyTemplateValues } = useActions(surveyLogic({ id: 'new' }))
const { reportSurveyTemplateClicked } = useActions(eventUsageLogic)
const { currentTeam } = useValues(teamLogic)
const surveyAppearance = {
...currentTeam?.survey_config?.appearance,
}

return (
<>
Expand Down Expand Up @@ -48,7 +53,11 @@ export function SurveyTemplates(): JSX.Element {
setSurveyTemplateValues({
name: template.templateType,
questions: template.questions,
appearance: { ...defaultSurveyAppearance, ...template.appearance },
appearance: {
...defaultSurveyAppearance,
...template.appearance,
...surveyAppearance,
},
})
reportSurveyTemplateClicked(template.templateType)
}}
Expand All @@ -69,6 +78,7 @@ export function SurveyTemplates(): JSX.Element {
...defaultSurveyAppearance,
whiteLabel: true,
...template.appearance,
...surveyAppearance,
},
} as Survey
}
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/scenes/surveys/Surveys.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Meta, StoryFn } from '@storybook/react'
import { router } from 'kea-router'
import { useEffect } from 'react'
import { App } from 'scenes/App'
import { SurveysTabs } from 'scenes/surveys/surveysLogic'
import { urls } from 'scenes/urls'

import { mswDecorator, useStorybookMocks } from '~/mocks/browser'
Expand Down Expand Up @@ -244,6 +245,13 @@ export const SurveysList: StoryFn = () => {
return <App />
}

export const SurveysGlobalSettings: StoryFn = () => {
useEffect(() => {
router.actions.push(urls.surveys(SurveysTabs.Settings))
}, [])
return <App />
}

export const NewSurvey: StoryFn = () => {
useEffect(() => {
router.actions.push(urls.survey('new'))
Expand Down
Loading

0 comments on commit b134da4

Please sign in to comment.