diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx index 2a9af14b33..8cafdf8879 100644 --- a/frontend/src/component/App.tsx +++ b/frontend/src/component/App.tsx @@ -1,5 +1,5 @@ import ConditionallyRender from 'component/common/ConditionallyRender'; -import Feedback from 'component/common/Feedback/Feedback'; +import { FeedbackNPS } from 'component/feedback/FeedbackNPS/FeedbackNPS'; import LayoutPicker from 'component/layout/LayoutPicker/LayoutPicker'; import Loader from 'component/common/Loader/Loader'; import NotFound from 'component/common/NotFound/NotFound'; @@ -72,7 +72,7 @@ export const App = () => { - + diff --git a/frontend/src/component/common/util.js b/frontend/src/component/common/util.js index a538005dd6..031b972d51 100644 --- a/frontend/src/component/common/util.js +++ b/frontend/src/component/common/util.js @@ -1,5 +1,4 @@ import { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums'; -import differenceInDays from 'date-fns/differenceInDays'; export const filterByFlags = flags => r => { if (r.flag && !flags[r.flag]) { @@ -80,30 +79,3 @@ export const modalStyles = { transform: 'translate(-50%, -50%)', }, }; - -export const showPnpsFeedback = feedbackList => { - if (!feedbackList) return; - if (feedbackList.length > 0) { - const feedback = feedbackList.find( - feedback => feedback.feedbackId === PNPS_FEEDBACK_ID - ); - - if (!feedback) return false; - - if (feedback.neverShow) { - return false; - } - - if (feedback.given) { - const SIX_MONTHS_IN_DAYS = 182; - const now = new Date(); - const difference = differenceInDays(now, new Date(feedback.given)); - - return difference > SIX_MONTHS_IN_DAYS; - } - return false; - } - return true; -}; - -export const PNPS_FEEDBACK_ID = 'pnps'; diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCES.styles.ts b/frontend/src/component/feedback/FeedbackCES/FeedbackCES.styles.ts new file mode 100644 index 0000000000..d8921675f3 --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCES.styles.ts @@ -0,0 +1,37 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + overlay: { + pointerEvents: 'none', + display: 'grid', + padding: '1rem', + overflowY: 'auto', + alignItems: 'center', + justifyContent: 'center', + height: '100vh', + width: '100vw', + }, + modal: { + pointerEvents: 'auto', + position: 'relative', + padding: '4rem', + background: 'white', + boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)', + borderRadius: '1rem', + [theme.breakpoints.down('sm')]: { + padding: '2rem', + }, + }, + close: { + all: 'unset', + position: 'absolute', + top: 0, + right: 0, + padding: '1rem', + cursor: 'pointer', + }, + closeIcon: { + fontSize: '1.5rem', + color: theme.palette.grey[600], + }, +})); diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCES.tsx b/frontend/src/component/feedback/FeedbackCES/FeedbackCES.tsx new file mode 100644 index 0000000000..b3a1eeb237 --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCES.tsx @@ -0,0 +1,43 @@ +import { Modal } from '@material-ui/core'; +import React, { useContext } from 'react'; +import { + feedbackCESContext, + IFeedbackCESState, +} from 'component/feedback/FeedbackCESContext/FeedbackCESContext'; +import { FeedbackCESForm } from 'component/feedback/FeedbackCES/FeedbackCESForm'; +import { useStyles } from 'component/feedback/FeedbackCES/FeedbackCES.styles'; +import { CloseOutlined } from '@material-ui/icons'; + +export interface IFeedbackCESProps { + state?: IFeedbackCESState; +} + +export const FeedbackCES = ({ state }: IFeedbackCESProps) => { + const { hideFeedbackCES } = useContext(feedbackCESContext); + const styles = useStyles(); + + const closeButton = ( + + ); + + const modalContent = state && ( + + ); + + return ( + +
+
+ {closeButton} + {modalContent} +
+
+
+ ); +}; diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.styles.ts b/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.styles.ts new file mode 100644 index 0000000000..0e10906fcf --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.styles.ts @@ -0,0 +1,36 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + container: { + fontWeight: theme.fontWeight.thin, + }, + form: { + display: 'grid', + gap: '3rem', + gridTemplateColumns: 'minmax(auto, 40rem)', + justifyContent: 'center', + }, + title: { + all: 'unset', + display: 'block', + textAlign: 'center', + color: theme.palette.grey[600], + }, + subtitle: { + all: 'unset', + display: 'block', + marginTop: '2.5rem', + fontSize: '1.5rem', + textAlign: 'center', + }, + textLabel: { + display: 'block', + marginBottom: '0.5rem', + }, + buttons: { + textAlign: 'center', + }, + button: { + minWidth: '15rem', + }, +})); diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.test.tsx b/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.test.tsx new file mode 100644 index 0000000000..551d359d89 --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.test.tsx @@ -0,0 +1,21 @@ +import { ThemeProvider } from '@material-ui/core'; +import renderer from 'react-test-renderer'; +import { FeedbackCESForm } from './FeedbackCESForm'; +import mainTheme from 'themes/mainTheme'; + +test('FeedbackCESForm', () => { + const onClose = () => { + throw new Error('Unexpected onClose call.'); + }; + + const tree = renderer.create( + + + + ); + + expect(tree).toMatchSnapshot(); +}); diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.tsx b/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.tsx new file mode 100644 index 0000000000..e647f90901 --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCESForm.tsx @@ -0,0 +1,96 @@ +import { useStyles } from 'component/feedback/FeedbackCES/FeedbackCESForm.styles'; +import { Button, TextField } from '@material-ui/core'; +import React, { useState } from 'react'; +import produce from 'immer'; +import useToast from 'hooks/useToast'; +import { IFeedbackCESState } from 'component/feedback/FeedbackCESContext/FeedbackCESContext'; +import { FeedbackCESScore } from 'component/feedback/FeedbackCES/FeedbackCESScore'; +import { sendFeedbackInput } from 'component/feedback/FeedbackCES/sendFeedbackInput'; + +export interface IFeedbackCESFormProps { + state: IFeedbackCESState; + onClose: () => void; +} + +export interface IFeedbackCESForm { + score: number; + comment: string; + path: string; +} + +export const FeedbackCESForm = ({ state, onClose }: IFeedbackCESFormProps) => { + const [loading, setLoading] = useState(false); + const { setToastData } = useToast(); + const styles = useStyles(); + + const [form, setForm] = useState>({ + path: state.path, + }); + + const onCommentChange = (event: React.ChangeEvent) => { + setForm( + produce(draft => { + draft.comment = event.target.value; + }) + ); + }; + + const onSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + if (loading) { + return; + } + + try { + setLoading(true); + await sendFeedbackInput(form); + setToastData({ + type: 'success', + title: 'Feedback sent. Thank you!', + confetti: true, + }); + onClose(); + } finally { + setLoading(false); + } + }; + + return ( +
+

Please help us improve

+
+

{state.title}

+ + + + +
+ ); +}; diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCESScore.styles.ts b/frontend/src/component/feedback/FeedbackCES/FeedbackCESScore.styles.ts new file mode 100644 index 0000000000..fb431e95b0 --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCESScore.styles.ts @@ -0,0 +1,55 @@ +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles(theme => ({ + scoreInput: { + display: 'flex', + gap: '1rem', + alignItems: 'center', + margin: '0 auto', + }, + scoreHelp: { + width: '8rem', + whiteSpace: 'nowrap', + color: theme.palette.grey[600], + '&:first-child': { + textAlign: 'right', + }, + [theme.breakpoints.down('xs')]: { + display: 'none', + }, + }, + scoreValue: { + '& input': { + clip: 'rect(0 0 0 0)', + clipPath: 'inset(50%)', + overflow: 'hidden', + position: 'absolute', + whiteSpace: 'nowrap', + width: 1, + height: 1, + }, + '& span': { + display: 'grid', + justifyContent: 'center', + alignItems: 'center', + background: theme.palette.grey[300], + width: '3rem', + height: '3rem', + borderRadius: '10rem', + fontSize: '1.25rem', + paddingBottom: 2, + userSelect: 'none', + cursor: 'pointer', + }, + '& input:checked + span': { + fontWeight: theme.fontWeight.bold, + background: theme.palette.primary.main, + color: 'white', + }, + '& input:focus-visible + span': { + outline: '2px solid', + outlineOffset: 2, + outlineColor: theme.palette.primary.main, + }, + }, +})); diff --git a/frontend/src/component/feedback/FeedbackCES/FeedbackCESScore.tsx b/frontend/src/component/feedback/FeedbackCES/FeedbackCESScore.tsx new file mode 100644 index 0000000000..57e6ac078d --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/FeedbackCESScore.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import produce from 'immer'; +import { useStyles } from 'component/feedback/FeedbackCES/FeedbackCESScore.styles'; +import { IFeedbackCESForm } from 'component/feedback/FeedbackCES/FeedbackCESForm'; + +interface IFeedbackCESScoreProps { + form: Partial; + setForm: React.Dispatch>>; +} + +export const FeedbackCESScore = ({ form, setForm }: IFeedbackCESScoreProps) => { + const styles = useStyles(); + + const onScoreChange = (event: React.ChangeEvent) => { + setForm( + produce(draft => { + draft.score = Number(event.target.value); + }) + ); + }; + + return ( +
+ Very difficult + {[1, 2, 3, 4, 5, 6, 7].map(score => ( + + ))} + Very easy +
+ ); +}; diff --git a/frontend/src/component/feedback/FeedbackCES/__snapshots__/FeedbackCESForm.test.tsx.snap b/frontend/src/component/feedback/FeedbackCES/__snapshots__/FeedbackCESForm.test.tsx.snap new file mode 100644 index 0000000000..8be1647ba3 --- /dev/null +++ b/frontend/src/component/feedback/FeedbackCES/__snapshots__/FeedbackCESForm.test.tsx.snap @@ -0,0 +1,222 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FeedbackCESForm 1`] = ` +
+

+ Please help us improve +

+
+

+ a +

+
+ + Very difficult + + + + + + + + + + Very easy + +
+