Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CS-115: NPS Banner UI component #17485

Merged
merged 10 commits into from
Aug 2, 2023
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { usePersistentState } from '@strapi/helper-plugin';

// Exported to make it available during admin user registration.
// Because we only enable the NPS for users who subscribe to the newsletter when signing up
export function useNpsSurveySettings() {
const [npsSurveySettings, setNpsSurveySettings] = usePersistentState(
'STRAPI_NPS_SURVEY_SETTINGS',
{
enabled: true,
lastResponseDate: null,
firstDismissalDate: null,
lastDismissalDate: null,
}
);

return { npsSurveySettings, setNpsSurveySettings };
}
237 changes: 209 additions & 28 deletions packages/core/admin/admin/src/components/NpsSurvey/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,60 @@
import * as React from 'react';

import { Flex, Button, Typography } from '@strapi/design-system';
import { usePersistentState } from '@strapi/helper-plugin';
import {
Box,
Flex,
IconButton,
Button,
Typography,
Textarea,
Portal,
Field,
FieldLabel,
FieldInput,
VisuallyHidden,
} from '@strapi/design-system';
import { Cross } from '@strapi/icons';
import { Formik, Form } from 'formik';
import { useIntl } from 'react-intl';
import styled, { useTheme } from 'styled-components';
import * as yup from 'yup';

import { useNpsSurveySettings } from './hooks/useNpsSurveySettings';

const FieldWrapper = styled(Field)`
height: ${32 / 16}rem;
width: ${32 / 16}rem;

> label,
~ input {
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

> label {
color: inherit;
cursor: pointer;
padding: ${({ theme }) => theme.spaces[2]};
text-align: center;
vertical-align: middle;
}

&:hover,
&:focus-within {
background-color: ${({ theme }) => theme.colors.neutral0};
}

&:active,
&.selected {
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
color: ${({ theme }) => theme.colors.primary700};
background-color: ${({ theme }) => theme.colors.neutral0};
border-color: ${({ theme }) => theme.colors.primary700};
}
`;

const delays = {
postResponse: 90 * 24 * 60 * 60 * 1000, // 90 days in ms
Expand All @@ -10,6 +63,8 @@ const delays = {
display: 5 * 60 * 1000, // 5 minutes in ms
};

const ratingArray = [...Array(11).keys()];

const checkIfShouldShowSurvey = (settings) => {
const { enabled, lastResponseDate, firstDismissalDate, lastDismissalDate } = settings;

Expand Down Expand Up @@ -64,44 +119,31 @@ const checkIfShouldShowSurvey = (settings) => {
return true;
};

// Exported to make it available during admin user registration.
// Because we only enable the NPS for users who subscribe to the newsletter when signing up
export function useNpsSurveySettings() {
const [npsSurveySettings, setNpsSurveySettings] = usePersistentState(
'STRAPI_NPS_SURVEY_SETTINGS',
{
enabled: true,
lastResponseDate: null,
firstDismissalDate: null,
lastDismissalDate: null,
}
);

return { npsSurveySettings, setNpsSurveySettings };
}

const NpsSurvey = () => {
const theme = useTheme();
const { formatMessage } = useIntl();
const { npsSurveySettings, setNpsSurveySettings } = useNpsSurveySettings();
const [isFeedbackResponse, setIsFeedbackResponse] = React.useState(false);
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved

// Only check on first render if the survey should be shown
const [surveyIsShown, setSurveyIsShown] = React.useState(
checkIfShouldShowSurvey(npsSurveySettings)
);

// Set a cooldown to show the survey when session begins
const [showSurvey, setShowSurvey] = React.useState(false);
const [displaySurvey, setDisplaySurvey] = React.useState(false);

React.useEffect(() => {
const displayTime = setTimeout(() => {
setShowSurvey(true);
setDisplaySurvey(true);
}, delays.display);

return () => {
clearTimeout(displayTime);
};
}, []);

if (!showSurvey) {
if (!displaySurvey) {
return null;
}

Expand All @@ -117,7 +159,14 @@ const NpsSurvey = () => {
lastDismissalDate: null,
}));
// TODO: send response to the backend
setSurveyIsShown(false);

// if success show thank you message
setIsFeedbackResponse(true);

// Thank you message displayed in the banner should disappear after few seconds.
setTimeout(() => {
setSurveyIsShown(false);
}, 3000);
};

const handleDismiss = () => {
Expand All @@ -141,13 +190,145 @@ const NpsSurvey = () => {
setSurveyIsShown(false);
};

// TODO: replace with the proper UI
return (
<Flex gap={2} padding={2}>
<Typography>NPS SURVEY</Typography>
<Button onClick={handleDismiss}>Dismiss</Button>
<Button onClick={handleSubmitResponse}>Submit</Button>
</Flex>
<Portal>
<Formik
initialValues={{ feedback: '', selectedRating: null }}
onSubmit={handleSubmitResponse}
validationSchema={yup.object({
feedback: yup.string(),
selectedRating: yup.number().required(),
})}
>
{({ values, handleChange, setFieldValue }) => (
<Form name="npsSurveyForm">
<Flex
hasRadius
direction="column"
padding={4}
borderColor="primary200"
background="neutral0"
shadow="popupShadow"
position="fixed"
bottom={0}
left="50%"
transform="translateX(-50%)"
zIndex={theme.zIndices[2]}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
width="50%"
>
{isFeedbackResponse ? (
<Typography fontWeight="semiBold">
{formatMessage({
id: 'app.components.NpsSurvey.feedback-response',
defaultMessage: 'Thank you very much for your feedback!',
})}
</Typography>
) : (
<>
<Flex justifyContent="space-between" width="100%">
<Box marginLeft="auto" marginRight="auto">
<Typography fontWeight="semiBold">
{formatMessage({
id: 'app.components.NpsSurvey.banner-title',
defaultMessage:
'How likely are you to recommend Strapi to a friend or colleague?',
})}
</Typography>
</Box>
<IconButton
onClick={handleDismiss}
aria-label={formatMessage({
id: 'app.components.NpsSurvey.dismiss-survey-label',
defaultMessage: 'Dismiss survey',
})}
icon={<Cross />}
/>
</Flex>
<Flex gap={2} marginLeft={8} marginRight={8} marginTop={2} marginBottom={2}>
<Typography variant="pi" textColor="neutral600">
{formatMessage({
id: 'app.components.NpsSurvey.no-recommendation',
defaultMessage: 'Not at all likely',
})}
</Typography>
{ratingArray.map((number) => {
return (
<FieldWrapper
key={number}
className={values.selectedRating === number ? 'selected' : null}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
borderRadius={theme.spaces[1]}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
background="primary100"
borderColor="primary200"
color="primary600"
position="relative"
cursor="pointer"
>
<FieldLabel
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
htmlFor={`nps-survey-rating-${number}-input`}
id={`nps-survey-rating-${number}`}
>
<VisuallyHidden>
<FieldInput
type="radio"
id={`nps-survey-rating-${number}-input`}
name="selectedRating"
checked={values.selectedRating === number}
onChange={(e) =>
setFieldValue('selectedRating', parseInt(e.target.value, 10))
}
value={number}
aria-checked={values.selectedRating === number}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
aria-labelledby={`nps-survey-rating-${number}`}
/>
</VisuallyHidden>
{number}
</FieldLabel>
</FieldWrapper>
);
})}
<Typography variant="pi" textColor="neutral600">
{formatMessage({
id: 'app.components.NpsSurvey.happy-to-recommend',
defaultMessage: 'Extremely likely',
})}
</Typography>
</Flex>
{values.selectedRating !== null && (
<>
<Box marginTop={2}>
<Typography fontWeight="semiBold">
{formatMessage({
id: 'app.components.NpsSurvey.feedback-question',
defaultMessage: 'Do you have any suggestion for improvements?',
})}
</Typography>
</Box>
<Box width="62%" marginTop={3} marginBottom={4}>
<Textarea
id="feedback"
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
name="feedback"
width="100%"
value={values.feedback}
onChange={handleChange}
>
{values.feedback}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
</Textarea>
</Box>
<Button marginBottom={2} type="submit">
{formatMessage({
id: 'app.components.NpsSurvey.submit-feedback',
defaultMessage: 'Submit Feedback',
})}
</Button>
</>
)}
</>
)}
</Flex>
</Form>
)}
</Formik>
</Portal>
);
};

Expand Down
Loading
Loading