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
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 };
}
230 changes: 202 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,138 @@ 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={{ npsSurveyFeedback: '', npsSurveyRating: null }}
onSubmit={handleSubmitResponse}
validationSchema={yup.object({
npsSurveyFeedback: yup.string(),
npsSurveyRating: 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>
) : (
<Box as="fieldset" width="100%">
<Flex justifyContent="space-between" width="100%">
<Box marginLeft="auto" marginRight="auto">
<Typography fontWeight="semiBold" as="legend">
{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} marginTop={2} marginBottom={2} justifyContent="center">
<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.npsSurveyRating === number ? 'selected' : null} // "selected" class added when child radio button is checked
hasRadius
background="primary100"
borderColor="primary200"
color="primary600"
position="relative"
cursor="pointer"
>
<FieldLabel htmlFor={`nps-survey-rating-${number}-input`}>
<VisuallyHidden>
<FieldInput
type="radio"
id={`nps-survey-rating-${number}-input`}
name="npsSurveyRating"
checked={values.npsSurveyRating === number}
onChange={(e) =>
setFieldValue('npsSurveyRating', parseInt(e.target.value, 10))
}
value={number}
/>
</VisuallyHidden>
{number}
</FieldLabel>
</FieldWrapper>
);
})}
<Typography variant="pi" textColor="neutral600">
{formatMessage({
id: 'app.components.NpsSurvey.happy-to-recommend',
defaultMessage: 'Extremely likely',
})}
</Typography>
</Flex>
{values.npsSurveyRating !== null && (
<>
<Box marginTop={2}>
<FieldLabel htmlFor="npsSurveyFeedback" fontWeight="semiBold" fontSize={2}>
{formatMessage({
id: 'app.components.NpsSurvey.feedback-question',
defaultMessage: 'Do you have any suggestion for improvements?',
})}
</FieldLabel>
</Box>
<Box width="62%" marginTop={3} marginBottom={4}>
<Textarea
id="npsSurveyFeedback" // formik element attribute "id" should be same as the values key to work
width="100%"
onChange={handleChange}
>
{values.npsSurveyFeedback}
</Textarea>
</Box>
<Button marginBottom={2} type="submit">
{formatMessage({
id: 'app.components.NpsSurvey.submit-feedback',
defaultMessage: 'Submit Feedback',
})}
</Button>
</>
)}
</Box>
)}
</Flex>
</Form>
)}
</Formik>
</Portal>
);
};

Expand Down
Loading
Loading