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 };
}
225 changes: 200 additions & 25 deletions packages/core/admin/admin/src/components/NpsSurvey/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,91 @@
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 from 'styled-components';
import * as yup from 'yup';

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

const BannerWrapper = styled(Flex)`
border: 1px solid ${({ theme }) => theme.colors.primary200};
background: ${({ theme }) => theme.colors.neutral0};
box-shadow: ${({ theme }) => theme.shadows.popupShadow};
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
z-index: ${({ theme }) => theme.zIndices[2]};
width: 50%;
`;
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved

const Header = styled(Box)`
margin: 0 auto;
`;
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved

const FieldWrapper = styled(Field)`
height: ${32 / 16}rem;
width: ${32 / 16}rem;
border-radius: ${({ theme }) => theme.spaces[1]};
background-color: ${({ theme }) => theme.colors.primary100};
border: 1px solid ${({ theme }) => theme.colors.primary200};
color: ${({ theme }) => theme.colors.primary600};
position: relative;
cursor: pointer;
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved

> 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
postFirstDismissal: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
postSubsequentDismissal: 90 * 24 * 60 * 60 * 1000, // 90 days in ms
};

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

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

Expand Down Expand Up @@ -63,24 +140,10 @@ 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 { 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(
Expand All @@ -99,7 +162,11 @@ const NpsSurvey = () => {
lastDismissalDate: null,
}));
// TODO: send response to the backend
setSurveyIsShown(false);
setIsFeedbackResponse(true);

setTimeout(() => {
setSurveyIsShown(false);
}, 3000);
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
};

const handleDismiss = () => {
Expand All @@ -123,13 +190,121 @@ 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(),
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
})}
>
{({ handleSubmit, values, handleChange, setFieldValue }) => (
<Form name="npsSurveyForm" noValidate onSubmit={handleSubmit}>
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
<BannerWrapper hasRadius direction="column" padding={4}>
{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%">
<Header>
<Typography fontWeight="semiBold">
{formatMessage({
id: 'app.components.NpsSurvey.banner-title',
defaultMessage:
'How likely are you to recommend Strapi to a friend or colleague?',
})}
</Typography>
</Header>
<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
>
<FieldLabel htmlFor={number} id={`${number}-rating`}>
<VisuallyHidden>
<FieldInput
type="radio"
id={number}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
name="selectedRating"
checked={values.selectedRating === number}
onChange={() => setFieldValue('selectedRating', number)}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
value={number}
aria-checked={values.selectedRating === number}
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
aria-labelledby={`${number}-rating`}
/>
</VisuallyHidden>
{number}
</FieldLabel>
</FieldWrapper>
);
})}
<Typography variant="pi" textColor="neutral600">
{formatMessage({
id: 'app.components.NpsSurvey.happy-to-recommend',
defaultMessage: 'Extremely likely',
})}
</Typography>
</Flex>
{(values.selectedRating || values.selectedRating === 0) && (
madhurisandbhor marked this conversation as resolved.
Show resolved Hide resolved
<>
<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>
</>
)}
</>
)}
</BannerWrapper>
</Form>
)}
</Formik>
</Portal>
);
};

Expand Down
Loading