diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx
index 91b5e75029..de08ae46ad 100644
--- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx
+++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.test.tsx
@@ -2,6 +2,7 @@ import { screen } from '@testing-library/react';
import { render } from 'utils/testRenderer';
import { testServerRoute, testServerSetup } from 'utils/testServer';
import { FeatureToggleListTable } from './FeatureToggleListTable';
+import { FeedbackProvider } from '../../feedbackNew/FeedbackProvider';
type APIFeature = {
name: string;
@@ -123,7 +124,11 @@ test('Filter table by project', async () => {
},
],
);
- render();
+ render(
+
+
+ ,
+ );
await verifyTableFeature({
name: 'Operational Feature',
diff --git a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx
index 957c37544c..c9e38f93d7 100644
--- a/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx
+++ b/frontend/src/component/feature/FeatureToggleList/FeatureToggleListTable.tsx
@@ -1,5 +1,12 @@
import { useCallback, useEffect, useMemo, useState, VFC } from 'react';
-import { Box, Link, useMediaQuery, useTheme } from '@mui/material';
+import {
+ Box,
+ IconButton,
+ Link,
+ Tooltip,
+ useMediaQuery,
+ useTheme,
+} from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { createColumnHelper, useReactTable } from '@tanstack/react-table';
import { PaginatedTable, TablePlaceholder } from 'component/common/Table';
@@ -47,6 +54,8 @@ import { FeatureToggleListTable as LegacyFeatureToggleListTable } from './Legacy
import { FeatureToggleListActions } from './FeatureToggleListActions/FeatureToggleListActions';
import useLoading from 'hooks/useLoading';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
+import { useFeedback } from '../../feedbackNew/useFeedback';
+import { ReviewsOutlined } from '@mui/icons-material';
export const featuresPlaceholder = Array(15).fill({
name: 'Name of the feature',
@@ -60,6 +69,7 @@ const columnHelper = createColumnHelper();
const FeatureToggleListTableComponent: VFC = () => {
const theme = useTheme();
+ const { openFeedback } = useFeedback();
const { trackEvent } = usePlausibleTracker();
const { environments } = useEnvironments();
const enabledEnvironments = environments
@@ -70,7 +80,7 @@ const FeatureToggleListTableComponent: VFC = () => {
const [showExportDialog, setShowExportDialog] = useState(false);
const { setToastApiError } = useToast();
- const { uiConfig } = useUiConfig();
+ const { uiConfig, isPro, isOss, isEnterprise } = useUiConfig();
const stateConfig = {
offset: withDefault(NumberParam, 0),
@@ -259,6 +269,24 @@ const FeatureToggleListTableComponent: VFC = () => {
return null;
}
+ const createFeedbackContext = () => {
+ const userType = isPro()
+ ? 'pro'
+ : isOss()
+ ? 'oss'
+ : isEnterprise()
+ ? 'enterprise'
+ : 'unknown';
+ openFeedback({
+ category: 'search',
+ userType,
+ title: 'How easy was it to use search and filters?',
+ positiveLabel: 'What do you like most about search and filters?',
+ areasForImprovementsLabel:
+ 'What should be improved in search and filters page?',
+ });
+ };
+
return (
{
setShowExportDialog(true)}
/>
+
+
+
+
+
>
}
>
diff --git a/frontend/src/component/feedbackNew/FeedbackComponent.tsx b/frontend/src/component/feedbackNew/FeedbackComponent.tsx
index a5f3af0d36..c630da8f66 100644
--- a/frontend/src/component/feedbackNew/FeedbackComponent.tsx
+++ b/frontend/src/component/feedbackNew/FeedbackComponent.tsx
@@ -1,7 +1,18 @@
-import { Box, Button, styled, TextField } from '@mui/material';
+import {
+ Box,
+ Button,
+ IconButton,
+ styled,
+ TextField,
+ Tooltip,
+} from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useFeedback } from './useFeedback';
-import React from 'react';
+import React, { useState } from 'react';
+import CloseIcon from '@mui/icons-material/Close';
+import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
+import useToast from 'hooks/useToast';
+import { ProvideFeedbackSchema } from '../../openapi';
export const ParentContainer = styled('div')(({ theme }) => ({
position: 'relative',
@@ -48,7 +59,7 @@ export const StyledTitle = styled(Box)(({ theme }) => ({
lineHeight: theme.spacing(2.5),
}));
-export const StyledForm = styled(Box)(({ theme }) => ({
+export const StyledForm = styled('form')(({ theme }) => ({
display: 'flex',
width: '400px',
padding: theme.spacing(3),
@@ -60,6 +71,10 @@ export const StyledForm = styled(Box)(({ theme }) => ({
borderColor: 'rgba(0, 0, 0, 0.12)',
backgroundColor: '#fff',
boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.12)',
+
+ '& > *': {
+ width: '100%',
+ },
}));
export const FormTitle = styled(Box)(({ theme }) => ({
@@ -75,7 +90,7 @@ export const FormSubTitle = styled(Box)(({ theme }) => ({
lineHeight: theme.spacing(2.5),
}));
-export const StyledButton = styled(Button)(({ theme }) => ({
+export const StyledButton = styled(Button)(() => ({
width: '100%',
}));
@@ -86,9 +101,10 @@ const StyledScoreContainer = styled('div')(({ theme }) => ({
alignItems: 'flex-start',
}));
-const StyledScoreInput = styled('div')(({ theme }) => ({
+const StyledScoreInput = styled('div')(() => ({
display: 'flex',
- gap: theme.spacing(2),
+ width: '100%',
+ justifyContent: 'space-between',
}));
const StyledScoreHelp = styled('span')(({ theme }) => ({
@@ -96,7 +112,7 @@ const StyledScoreHelp = styled('span')(({ theme }) => ({
fontSize: theme.spacing(1.75),
}));
-const ScoreHelpContainer = styled('span')(({ theme }) => ({
+const ScoreHelpContainer = styled('span')(() => ({
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
@@ -131,33 +147,99 @@ const StyledScoreValue = styled('label')(({ theme }) => ({
},
}));
+const StyledCloseButton = styled(IconButton)(({ theme }) => ({
+ position: 'absolute',
+ right: theme.spacing(2),
+ top: theme.spacing(2),
+ color: theme.palette.background.paper,
+}));
+
export const FeedbackComponent = () => {
const { feedbackData, showFeedback, closeFeedback } = useFeedback();
if (!feedbackData) return null;
+ const { setToastData } = useToast();
+ const { addFeedback } = useUserFeedbackApi();
+
+ function isProvideFeedbackSchema(data: any): data is ProvideFeedbackSchema {
+ data.difficultyScore = data.difficultyScore
+ ? Number(data.difficultyScore)
+ : undefined;
+
+ return (
+ typeof data.category === 'string' &&
+ typeof data.userType === 'string' &&
+ (typeof data.difficultyScore === 'number' ||
+ data.difficultyScore === undefined)
+ );
+ }
+
+ const onSubmission = async (event: React.FormEvent) => {
+ event.preventDefault();
+ const formData = new FormData(event.currentTarget);
+ const data = Object.fromEntries(formData);
+
+ if (isProvideFeedbackSchema(data)) {
+ await addFeedback(data as ProvideFeedbackSchema);
+ setToastData({
+ title: 'Feedback sent',
+ type: 'success',
+ });
+ } else {
+ setToastData({
+ title: 'Feedback not sent',
+ type: 'error',
+ });
+ }
+ closeFeedback();
+ };
+
+ const [selectedScore, setSelectedScore] = useState(null);
+
+ const onScoreChange = (event: React.ChangeEvent) => {
+ setSelectedScore(event.target.value);
+ };
+
return (
+
+
+
+
+
Help us to improve Unleash
-
-
- How easy wasy it to configure the strategy?
-
+
+
+
+ {feedbackData.title}
{[1, 2, 3, 4, 5, 6, 7].map((score) => (
{score}
@@ -174,12 +256,12 @@ export const FeedbackComponent = () => {
- What do you like most about the strategy
- configuration?
+ {feedbackData.positiveLabel}
{
- What should be improved in the strategy
- configuration?
+ {feedbackData.areasForImprovementsLabel}
{
size='small'
/>
-
- Send Feedback
-
+
+ Send Feedback
+
+ }
+ />
diff --git a/frontend/src/component/feedbackNew/FeedbackContext.ts b/frontend/src/component/feedbackNew/FeedbackContext.ts
index 5b8e7c9dfe..6eebadfa60 100644
--- a/frontend/src/component/feedbackNew/FeedbackContext.ts
+++ b/frontend/src/component/feedbackNew/FeedbackContext.ts
@@ -2,33 +2,25 @@ import { createContext } from 'react';
import { ProvideFeedbackSchema } from '../../openapi';
interface IFeedbackContext {
- feedbackData: ProvideFeedbackSchema;
- openFeedback: (data: ProvideFeedbackSchema) => void;
+ feedbackData: IFeedbackData | undefined;
+ openFeedback: (data: IFeedbackData) => void;
closeFeedback: () => void;
showFeedback: boolean;
setShowFeedback: (visible: boolean) => void;
}
-export const DEFAULT_FEEDBACK_DATA = {
- category: 'general',
+type IFeedbackText = {
+ title: string;
+ positiveLabel: string;
+ areasForImprovementsLabel: string;
};
-const setShowFeedback = () => {
- throw new Error('setShowFeedback called outside FeedbackContext');
-};
+export type IFeedbackData = Pick<
+ ProvideFeedbackSchema,
+ 'category' | 'userType'
+> &
+ IFeedbackText;
-const openFeedback = () => {
- throw new Error('openFeedback called outside FeedbackContext');
-};
-
-const closeFeedback = () => {
- throw new Error('closeFeedback called outside FeedbackContext');
-};
-
-export const FeedbackContext = createContext({
- feedbackData: DEFAULT_FEEDBACK_DATA,
- showFeedback: true,
- setShowFeedback: setShowFeedback,
- openFeedback: openFeedback,
- closeFeedback: closeFeedback,
-});
+export const FeedbackContext = createContext(
+ undefined,
+);
diff --git a/frontend/src/component/feedbackNew/FeedbackProvider.tsx b/frontend/src/component/feedbackNew/FeedbackProvider.tsx
index d06a2a7037..4f3cadf593 100644
--- a/frontend/src/component/feedbackNew/FeedbackProvider.tsx
+++ b/frontend/src/component/feedbackNew/FeedbackProvider.tsx
@@ -1,21 +1,20 @@
import { FeedbackComponent } from './FeedbackComponent';
-import { DEFAULT_FEEDBACK_DATA, FeedbackContext } from './FeedbackContext';
+import { FeedbackContext, IFeedbackData } from './FeedbackContext';
import { FC, useState } from 'react';
-import { ProvideFeedbackSchema } from '../../openapi';
export const FeedbackProvider: FC = ({ children }) => {
- const [feedbackData, setFeedbackData] = useState(
- DEFAULT_FEEDBACK_DATA,
+ const [feedbackData, setFeedbackData] = useState(
+ undefined,
);
const [showFeedback, setShowFeedback] = useState(false);
- const openFeedback = (data: ProvideFeedbackSchema) => {
+ const openFeedback = (data: IFeedbackData) => {
setFeedbackData(data);
setShowFeedback(true);
};
const closeFeedback = () => {
- setFeedbackData(DEFAULT_FEEDBACK_DATA);
+ setFeedbackData(undefined);
setShowFeedback(false);
};
diff --git a/frontend/src/hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi.ts b/frontend/src/hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi.ts
new file mode 100644
index 0000000000..4287288fe1
--- /dev/null
+++ b/frontend/src/hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi.ts
@@ -0,0 +1,32 @@
+import { IInternalBanner } from 'interfaces/banner';
+import useAPI from '../useApi/useApi';
+import { ProvideFeedbackSchema } from '../../../../openapi';
+
+const ENDPOINT = 'api/admin/user-feedback';
+
+export const useUserFeedbackApi = () => {
+ const { loading, makeRequest, createRequest, errors } = useAPI({
+ propagateErrors: true,
+ });
+
+ const addFeedback = async (feedbackSchema: ProvideFeedbackSchema) => {
+ const requestId = 'addBanner';
+ const req = createRequest(
+ ENDPOINT,
+ {
+ method: 'POST',
+ body: JSON.stringify(feedbackSchema),
+ },
+ requestId,
+ );
+
+ const response = await makeRequest(req.caller, req.id);
+ return response.json();
+ };
+
+ return {
+ addFeedback,
+ errors,
+ loading,
+ };
+};