mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: command bar feedback (#7485)
 
This commit is contained in:
		
							parent
							
								
									2706a09b8b
								
							
						
					
					
						commit
						c907199d23
					
				@ -1,4 +1,4 @@
 | 
			
		||||
import { useRef, useState } from 'react';
 | 
			
		||||
import { useEffect, useRef, useState } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
    Box,
 | 
			
		||||
    IconButton,
 | 
			
		||||
@ -13,7 +13,6 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
 | 
			
		||||
import { useKeyboardShortcut } from 'hooks/useKeyboardShortcut';
 | 
			
		||||
import { SEARCH_INPUT } from 'utils/testIds';
 | 
			
		||||
import { useOnClickOutside } from 'hooks/useOnClickOutside';
 | 
			
		||||
import { useOnBlur } from 'hooks/useOnBlur';
 | 
			
		||||
import {
 | 
			
		||||
    CommandResultGroup,
 | 
			
		||||
    type CommandResultGroupItem,
 | 
			
		||||
@ -26,6 +25,7 @@ import { CommandFeatures } from './CommandFeatures';
 | 
			
		||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
			
		||||
import { CommandRecent } from './CommandRecent';
 | 
			
		||||
import { CommandPages } from './CommandPages';
 | 
			
		||||
import { CommandBarFeedback } from './CommandBarFeedback';
 | 
			
		||||
import { RecentlyVisitedRecorder } from './RecentlyVisitedRecorder';
 | 
			
		||||
 | 
			
		||||
export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
 | 
			
		||||
@ -35,7 +35,7 @@ export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
 | 
			
		||||
    top: '39px',
 | 
			
		||||
    zIndex: 4,
 | 
			
		||||
    borderTop: theme.spacing(0),
 | 
			
		||||
    padding: theme.spacing(4, 0, 1.5),
 | 
			
		||||
    padding: theme.spacing(1.5, 0, 1.5),
 | 
			
		||||
    borderRadius: 0,
 | 
			
		||||
    borderBottomLeftRadius: theme.spacing(1),
 | 
			
		||||
    borderBottomRightRadius: theme.spacing(1),
 | 
			
		||||
@ -109,6 +109,7 @@ export const CommandBar = () => {
 | 
			
		||||
        CommandResultGroupItem[]
 | 
			
		||||
    >([]);
 | 
			
		||||
    const [searchedFlagCount, setSearchedFlagCount] = useState(0);
 | 
			
		||||
    const [hasNoResults, setHasNoResults] = useState(false);
 | 
			
		||||
    const [value, setValue] = useState<string>('');
 | 
			
		||||
    const { routes } = useRoutes();
 | 
			
		||||
    const allRoutes: Record<string, IPageRouteInfo> = {};
 | 
			
		||||
@ -166,8 +167,13 @@ export const CommandBar = () => {
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        setHasNoResults(noResultsFound);
 | 
			
		||||
    }, 200);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        debouncedSetSearchState(value);
 | 
			
		||||
    }, [searchedFlagCount]);
 | 
			
		||||
 | 
			
		||||
    const onSearchChange = (value: string) => {
 | 
			
		||||
        debouncedSetSearchState(value);
 | 
			
		||||
        setValue(value);
 | 
			
		||||
@ -195,8 +201,6 @@ export const CommandBar = () => {
 | 
			
		||||
    const placeholder = `Command bar (${hotkey})`;
 | 
			
		||||
 | 
			
		||||
    useOnClickOutside([searchContainerRef], hideSuggestions);
 | 
			
		||||
    useOnBlur(searchContainerRef, hideSuggestions);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledContainer ref={searchContainerRef} active={showSuggestions}>
 | 
			
		||||
            <RecentlyVisitedRecorder />
 | 
			
		||||
@ -261,6 +265,14 @@ export const CommandBar = () => {
 | 
			
		||||
                            items={searchedProjects}
 | 
			
		||||
                        />
 | 
			
		||||
                        <CommandPages items={searchedPages} />
 | 
			
		||||
                        <ConditionallyRender
 | 
			
		||||
                            condition={hasNoResults}
 | 
			
		||||
                            show={
 | 
			
		||||
                                <CommandBarFeedback
 | 
			
		||||
                                    onSubmit={hideSuggestions}
 | 
			
		||||
                                />
 | 
			
		||||
                            }
 | 
			
		||||
                        />
 | 
			
		||||
                    </CommandResultsPaper>
 | 
			
		||||
                }
 | 
			
		||||
                elseShow={
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										96
									
								
								frontend/src/component/commandBar/CommandBarFeedback.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								frontend/src/component/commandBar/CommandBarFeedback.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
			
		||||
import { Button, styled, TextField } from '@mui/material';
 | 
			
		||||
import type React from 'react';
 | 
			
		||||
import { useState } from 'react';
 | 
			
		||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
 | 
			
		||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
 | 
			
		||||
import useToast from 'hooks/useToast';
 | 
			
		||||
import useUserType from '../feedbackNew/useUserType';
 | 
			
		||||
 | 
			
		||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
    flexDirection: 'column',
 | 
			
		||||
    padding: theme.spacing(2),
 | 
			
		||||
    gap: theme.spacing(1),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledText = styled('span')(({ theme }) => ({
 | 
			
		||||
    fontSize: theme.spacing(1.5),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledButton = styled(Button)(({ theme }) => ({
 | 
			
		||||
    fontSize: theme.spacing(1.5),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
interface ICommandBarFeedbackProps {
 | 
			
		||||
    onSubmit: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CommandBarFeedback = ({ onSubmit }: ICommandBarFeedbackProps) => {
 | 
			
		||||
    const userType = useUserType();
 | 
			
		||||
    const { addFeedback } = useUserFeedbackApi();
 | 
			
		||||
    const { setToastData } = useToast();
 | 
			
		||||
    const [suggesting, setSuggesting] = useState(false);
 | 
			
		||||
    const [feedback, setFeedback] = useState<string | undefined>(undefined);
 | 
			
		||||
 | 
			
		||||
    const changeFeedback = (event: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
        setFeedback(event.target.value.trim());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const sendFeedback = async () => {
 | 
			
		||||
        await addFeedback({
 | 
			
		||||
            areasForImprovement: feedback,
 | 
			
		||||
            category: 'commandBar',
 | 
			
		||||
            userType: userType,
 | 
			
		||||
        });
 | 
			
		||||
        onSubmit();
 | 
			
		||||
        setToastData({
 | 
			
		||||
            title: 'Feedback sent',
 | 
			
		||||
            type: 'success',
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledContainer>
 | 
			
		||||
            <ConditionallyRender
 | 
			
		||||
                condition={suggesting}
 | 
			
		||||
                show={
 | 
			
		||||
                    <>
 | 
			
		||||
                        <StyledText>Describe the capability</StyledText>
 | 
			
		||||
                        <TextField
 | 
			
		||||
                            multiline={true}
 | 
			
		||||
                            minRows={2}
 | 
			
		||||
                            onChange={changeFeedback}
 | 
			
		||||
                        />
 | 
			
		||||
                        <StyledButton
 | 
			
		||||
                            type='submit'
 | 
			
		||||
                            variant='contained'
 | 
			
		||||
                            color='primary'
 | 
			
		||||
                            onClick={sendFeedback}
 | 
			
		||||
                        >
 | 
			
		||||
                            Send to Unleash
 | 
			
		||||
                        </StyledButton>
 | 
			
		||||
                    </>
 | 
			
		||||
                }
 | 
			
		||||
                elseShow={
 | 
			
		||||
                    <>
 | 
			
		||||
                        <StyledText>
 | 
			
		||||
                            We couldn’t find anything matching your search
 | 
			
		||||
                            criteria. If you think this is a missing capability,
 | 
			
		||||
                            feel free to make a suggestion.
 | 
			
		||||
                        </StyledText>
 | 
			
		||||
                        <StyledButton
 | 
			
		||||
                            type='submit'
 | 
			
		||||
                            variant='contained'
 | 
			
		||||
                            color='primary'
 | 
			
		||||
                            onClick={(e) => {
 | 
			
		||||
                                e.stopPropagation();
 | 
			
		||||
                                setSuggesting(true);
 | 
			
		||||
                            }}
 | 
			
		||||
                        >
 | 
			
		||||
                            Suggest capability
 | 
			
		||||
                        </StyledButton>
 | 
			
		||||
                    </>
 | 
			
		||||
                }
 | 
			
		||||
            />
 | 
			
		||||
        </StyledContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -17,6 +17,7 @@ import {
 | 
			
		||||
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
 | 
			
		||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
			
		||||
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
 | 
			
		||||
import { Children } from 'react';
 | 
			
		||||
 | 
			
		||||
export const listItemButtonStyle = (theme: Theme) => ({
 | 
			
		||||
    border: `1px solid transparent`,
 | 
			
		||||
@ -26,10 +27,6 @@ export const listItemButtonStyle = (theme: Theme) => ({
 | 
			
		||||
        borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    marginBottom: theme.spacing(3),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const StyledTypography = styled(Typography)(({ theme }) => ({
 | 
			
		||||
    fontSize: theme.fontSizes.bodySize,
 | 
			
		||||
    padding: theme.spacing(0, 2.5),
 | 
			
		||||
@ -193,7 +190,10 @@ export const CommandResultGroup = ({
 | 
			
		||||
    children,
 | 
			
		||||
}: CommandResultGroupProps) => {
 | 
			
		||||
    const { trackEvent } = usePlausibleTracker();
 | 
			
		||||
    if (!children && (!items || items.length === 0)) {
 | 
			
		||||
    if (
 | 
			
		||||
        (!children || Children.count(children) === 0) &&
 | 
			
		||||
        (!items || items.length === 0)
 | 
			
		||||
    ) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -211,7 +211,7 @@ export const CommandResultGroup = ({
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledContainer>
 | 
			
		||||
        <div>
 | 
			
		||||
            <StyledTypography color='textSecondary'>
 | 
			
		||||
                {groupName}
 | 
			
		||||
            </StyledTypography>
 | 
			
		||||
@ -249,6 +249,6 @@ export const CommandResultGroup = ({
 | 
			
		||||
                    </ListItemButton>
 | 
			
		||||
                ))}
 | 
			
		||||
            </List>
 | 
			
		||||
        </StyledContainer>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -16,12 +16,12 @@ import useToast from 'hooks/useToast';
 | 
			
		||||
import type { ProvideFeedbackSchema } from 'openapi';
 | 
			
		||||
import { useUserFeedbackApi } from 'hooks/api/actions/useUserFeedbackApi/useUserFeedbackApi';
 | 
			
		||||
import { useUserSubmittedFeedback } from 'hooks/useSubmittedFeedback';
 | 
			
		||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
import type { IToast } from 'interfaces/toast';
 | 
			
		||||
import { useTheme } from '@mui/material/styles';
 | 
			
		||||
import type { FeedbackData, FeedbackMode } from './FeedbackContext';
 | 
			
		||||
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
 | 
			
		||||
import { useUiFlag } from 'hooks/useUiFlag';
 | 
			
		||||
import useUserType from './useUserType';
 | 
			
		||||
 | 
			
		||||
export const ParentContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    position: 'relative',
 | 
			
		||||
@ -201,9 +201,10 @@ export const FeedbackComponent = ({
 | 
			
		||||
    feedbackMode,
 | 
			
		||||
}: IFeedbackComponent) => {
 | 
			
		||||
    const { setToastData } = useToast();
 | 
			
		||||
    const userType = useUserType();
 | 
			
		||||
    const { trackEvent } = usePlausibleTracker();
 | 
			
		||||
    const theme = useTheme();
 | 
			
		||||
    const { isPro, isOss, isEnterprise } = useUiConfig();
 | 
			
		||||
 | 
			
		||||
    const { addFeedback } = useUserFeedbackApi();
 | 
			
		||||
    const { setHasSubmittedFeedback } = useUserSubmittedFeedback(
 | 
			
		||||
        feedbackData.category,
 | 
			
		||||
@ -276,22 +277,6 @@ export const FeedbackComponent = ({
 | 
			
		||||
        setSelectedScore(event.target.value);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const getUserType = () => {
 | 
			
		||||
        if (isPro()) {
 | 
			
		||||
            return 'pro';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isOss()) {
 | 
			
		||||
            return 'oss';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isEnterprise()) {
 | 
			
		||||
            return 'enterprise';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return 'unknown';
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ConditionallyRender
 | 
			
		||||
            condition={showFeedback}
 | 
			
		||||
@ -320,7 +305,7 @@ export const FeedbackComponent = ({
 | 
			
		||||
                                    <input
 | 
			
		||||
                                        type='hidden'
 | 
			
		||||
                                        name='userType'
 | 
			
		||||
                                        value={getUserType()}
 | 
			
		||||
                                        value={userType}
 | 
			
		||||
                                    />
 | 
			
		||||
                                    <FormTitle>{feedbackData.title}</FormTitle>
 | 
			
		||||
                                    <StyledScoreContainer>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								frontend/src/component/feedbackNew/useUserType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								frontend/src/component/feedbackNew/useUserType.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
 | 
			
		||||
 | 
			
		||||
const useUserType = () => {
 | 
			
		||||
    const { isPro, isOss, isEnterprise } = useUiConfig();
 | 
			
		||||
 | 
			
		||||
    if (isPro()) {
 | 
			
		||||
        return 'pro';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isOss()) {
 | 
			
		||||
        return 'oss';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isEnterprise()) {
 | 
			
		||||
        return 'enterprise';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 'unknown';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useUserType;
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user