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

2926: Redesign of search feedback in web #3035

Merged
9 changes: 1 addition & 8 deletions native/src/components/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ import useNavigate from '../hooks/useNavigate'
import Caption from './Caption'
import FeedbackButtons from './FeedbackButtons'
import { SendingStatusType } from './FeedbackContainer'
import HorizontalLine from './HorizontalLine'
import LoadingSpinner from './LoadingSpinner'
import Note from './Note'
import NothingFound from './NothingFound'
import InputSection from './base/InputSection'
import TextButton from './base/TextButton'

const Wrapper = styled.View`
padding: 20px;
gap: 8px;
`

Expand Down Expand Up @@ -79,11 +76,7 @@ const Feedback = ({
<KeyboardAwareScrollView>
<Wrapper>
{isSearchFeedback ? (
<>
<NothingFound />
<HorizontalLine />
<InputSection title={t('searchTermDescription')} value={searchTerm} onChange={setSearchTerm} />
</>
<InputSection title={t('searchTermDescription')} value={searchTerm} onChange={setSearchTerm} />
) : (
<>
<Caption title={t('headline')} />
Expand Down
59 changes: 47 additions & 12 deletions native/src/components/FeedbackContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components/native'

import { SEND_FEEDBACK_SIGNAL_NAME } from 'shared'
import { createFeedbackEndpoint, FeedbackRouteType } from 'shared/api'
import { config } from 'translations'

import buildConfig from '../constants/buildConfig'
import { determineApiUrl } from '../utils/helpers'
import sendTrackingSignal from '../utils/sendTrackingSignal'
import { reportError } from '../utils/sentry'
import Feedback from './Feedback'
import Text from './base/Text'
import TextButton from './base/TextButton'

const Container = styled.View`
flex: 1;
background-color: ${props => props.theme.colors.backgroundColor};
padding: 8px 20px;
gap: 8px;
`

const Title = styled(Text)`
font-weight: 600;
`

const Hint = styled(Title)`
margin-top: 8px;
text-align: center;
`

export type SendingStatusType = 'idle' | 'sending' | 'failed' | 'successful'
Expand All @@ -30,6 +46,8 @@ const FeedbackContainer = ({ query, language, routeType, cityCode, slug }: Feedb
const [isPositiveRating, setIsPositiveRating] = useState<boolean | null>(null)
const [sendingStatus, setSendingStatus] = useState<SendingStatusType>('idle')
const [searchTerm, setSearchTerm] = useState<string | undefined>(query)
const [showFeedback, setShowFeedback] = useState<boolean>(query === undefined)
const { t } = useTranslation('feedback')

useEffect(() => {
setSearchTerm(query)
Expand Down Expand Up @@ -70,20 +88,37 @@ const FeedbackContainer = ({ query, language, routeType, cityCode, slug }: Feedb
})
}

if (showFeedback) {
return (
<Container>
<Feedback
comment={comment}
contactMail={contactMail}
sendingStatus={sendingStatus}
onCommentChanged={setComment}
onFeedbackContactMailChanged={setContactMail}
isPositiveFeedback={isPositiveRating}
setIsPositiveFeedback={setIsPositiveRating}
onSubmit={handleSubmit}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
</Container>
)
}

const fallbackLanguage = config.sourceLanguage

return (
<Container>
<Feedback
comment={comment}
contactMail={contactMail}
sendingStatus={sendingStatus}
onCommentChanged={setComment}
onFeedbackContactMailChanged={setContactMail}
isPositiveFeedback={isPositiveRating}
setIsPositiveFeedback={setIsPositiveRating}
onSubmit={handleSubmit}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
<>
<Title>
{language === fallbackLanguage ? t('noResultsInUserLanguage') : t('noResultsInUserAndSourceLanguage')}
</Title>
<Text>{t('checkQuery', { appName: buildConfig().appName })}</Text>
<Hint>{t('informationMissing')}</Hint>
<TextButton text={t('giveFeedback')} onPress={() => setShowFeedback(true)} />
</>
</Container>
)
}
Expand Down
1 change: 0 additions & 1 deletion native/src/components/__tests__/Feedback.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ describe('Feedback', () => {
<Feedback {...buildProps(false, 'comment', 'query')} />
</NavigationContainer>,
)
expect(getByText('search:nothingFound')).toBeDefined()
expect(getByText('searchTermDescription')).toBeDefined()
})

Expand Down
8 changes: 7 additions & 1 deletion native/src/components/__tests__/FeedbackContainer.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ describe('FeedbackContainer', () => {
<FeedbackContainer routeType={SEARCH_ROUTE} language={language} cityCode={city} query={query} />
</NavigationContainer>,
)
const buttonToOpenFeedback = getByText('giveFeedback')
fireEvent.press(buttonToOpenFeedback)
const button = getByText('send')
fireEvent.press(button)
expect(await findByText('thanksMessage')).toBeDefined()
Expand All @@ -148,6 +150,8 @@ describe('FeedbackContainer', () => {
<FeedbackContainer routeType={SEARCH_ROUTE} language={language} cityCode={city} query={query} />
</NavigationContainer>,
)
const buttonToOpenFeedback = getByText('giveFeedback')
fireEvent.press(buttonToOpenFeedback)
const input = getByDisplayValue(query)
fireEvent.changeText(input, fullSearchTerm)
const button = getByText('send')
Expand All @@ -168,11 +172,13 @@ describe('FeedbackContainer', () => {
})

it('should disable send button if query term is removed', async () => {
const { findByText, getByDisplayValue } = render(
const { findByText, getByDisplayValue, getByText } = render(
<NavigationContainer>
<FeedbackContainer routeType={SEARCH_ROUTE} language={language} cityCode={city} query='query' />
</NavigationContainer>,
)
const buttonToOpenFeedback = getByText('giveFeedback')
fireEvent.press(buttonToOpenFeedback)
expect(await findByText('send')).not.toBeDisabled()
const input = getByDisplayValue('query')
fireEvent.changeText(input, '')
Expand Down
2 changes: 1 addition & 1 deletion native/src/routes/__tests__/SearchModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe('SearchModal', () => {

fireEvent.changeText(getByPlaceholderText('searchPlaceholder'), 'no results, please')

expect(getByText('search:nothingFound')).toBeTruthy()
expect(getByText('noResultsInUserLanguage')).toBeTruthy()
})

it('should open with an initial search text if one is supplied', () => {
Expand Down
14 changes: 12 additions & 2 deletions translations/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -4763,7 +4763,12 @@
"positiveRating": "Diese Seite ist hilfreich",
"searchTermDescription": "Für den folgenden Begriff konnte kein Ergebnis gefunden werden:",
"contactMailAddress": "E-Mail für Rückfragen",
"note": "Bitte wähle eine Reaktion oder schreibe einen Kommentar, um das Feedback abschicken zu können."
"note": "Bitte wähle eine Reaktion oder schreibe einen Kommentar, um das Feedback abschicken zu können.",
"giveFeedback": "Feedback geben",
"checkQuery": "Überprüfen Sie Ihren Suchbegriff oder ändern Sie die eingestellte Sprache der {{appName}}-App.",
"informationMissing": "Hier fehlen Informationen?",
"noResultsInUserLanguage": "Es wurden leider keine passenden Ergebnisse in Deiner Sprache gefunden.",
"noResultsInUserAndSourceLanguage": "Es wurden leider keine passenden Ergebnisse in Deiner Sprache oder auf Deutsch gefunden."
},
"am": {
"disclaimer": "የግንኙነት መረጃና ዕትም",
Expand Down Expand Up @@ -4947,7 +4952,12 @@
"positiveRating": "This site is useful",
"searchTermDescription": "No result could be found for the following term:",
"contactMailAddress": "E-Mail for further questions",
"note": "Please select a reaction or write a comment in order to send feedback."
"note": "Please select a reaction or write a comment in order to send feedback.",
"giveFeedback": "Give feedback",
"checkQuery": "Check your search term or select a different language in the {{appName}} app.",
"informationMissing": "Is information missing?",
"noResultsInUserLanguage": "Sorry, we could not find any matching results in your language.",
"noResultsInUserAndSourceLanguage": "Sorry, we could not find any matching results in your language or in German."
},
"es": {
"disclaimer": "Contacto y aviso legal",
Expand Down
12 changes: 3 additions & 9 deletions web/src/components/Feedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import buildConfig from '../constants/buildConfig'
import dimensions from '../constants/dimensions'
import Failure from './Failure'
import FeedbackButtons from './FeedbackButtons'
import { SendingStatusType } from './FeedbackContainer'
import Note from './Note'
Expand Down Expand Up @@ -49,7 +48,6 @@
onFeedbackChanged?: (isPositiveFeedback: boolean | null) => void
onSubmit: () => void
sendingStatus: SendingStatusType
noResults: boolean | undefined
searchTerm: string | undefined
setSearchTerm: (newTerm: string) => void
closeFeedback: (() => void) | undefined
Expand All @@ -63,36 +61,32 @@
onSubmit,
onCommentChanged,
onContactMailChanged,
onFeedbackChanged,
noResults,
searchTerm,
setSearchTerm,
closeFeedback,
}: FeedbackProps): ReactElement => {
const { t } = useTranslation('feedback')

const isSearchFeedback = searchTerm !== undefined
const commentTitle = isSearchFeedback ? 'wantedInformation' : 'commentHeadline'
const sendFeedbackDisabled = isPositiveFeedback === null && comment.trim().length === 0 && !searchTerm

if (sendingStatus === 'successful') {
return (
<Container>
<div>{t('thanksMessage')}</div>
{!!closeFeedback && !isSearchFeedback && <TextButton onClick={closeFeedback} text={t('common:close')} />}
</Container>
)
}

return (
<Container $fullWidth={isSearchFeedback}>
{isSearchFeedback ? (
<>
{noResults && <Failure errorMessage='search:nothingFound' />}
<InputSection id='searchTerm' title={t('searchTermDescription')}>
<Input id='searchTerm' value={searchTerm} onChange={setSearchTerm} />
</InputSection>
</>
<InputSection id='searchTerm' title={t('searchTermDescription')}>
<Input id='searchTerm' value={searchTerm} onChange={setSearchTerm} />
</InputSection>

Check notice on line 89 in web/src/components/Feedback.tsx

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (2926-fallback-language-in-search)

✅ Getting better: Complex Method

Feedback decreases in cyclomatic complexity from 13 to 12, threshold = 10. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.
) : (
onFeedbackChanged && <FeedbackButtons isPositive={isPositiveFeedback} onRatingChange={onFeedbackChanged} />
)}
Expand Down
3 changes: 0 additions & 3 deletions web/src/components/FeedbackContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ type FeedbackContainerProps = {
routeType: FeedbackRouteType
onClose?: () => void
query?: string
noResults?: boolean
slug?: string
onSubmit?: () => void
initialRating: boolean | null
Expand All @@ -22,7 +21,6 @@ export type SendingStatusType = 'idle' | 'sending' | 'failed' | 'successful'

export const FeedbackContainer = ({
query,
noResults,
language,
routeType,
cityCode,
Expand Down Expand Up @@ -84,7 +82,6 @@ export const FeedbackContainer = ({
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
closeFeedback={onClose}
noResults={noResults}
/>
)
}
Expand Down
36 changes: 34 additions & 2 deletions web/src/components/SearchFeedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'

import { SEARCH_ROUTE } from 'shared'
import { config } from 'translations'

import buildConfig from '../constants/buildConfig'
import FeedbackContainer from './FeedbackContainer'
import TextButton from './base/TextButton'

Expand All @@ -13,6 +15,22 @@ const Container = styled.div`
align-items: center;
`

const CenteredContainer = styled.div`
text-align: center;
`

const SmallTitle = styled.p`
font-weight: 600;
`

const Hint = styled.p`
padding-bottom: 16px;
`

const StyledButton = styled(TextButton)`
margin-top: 8px;
`

type SearchFeedbackProps = {
cityCode: string
languageCode: string
Expand All @@ -26,21 +44,35 @@ const SearchFeedback = ({ cityCode, languageCode, query, noResults }: SearchFeed

useEffect(() => setShowFeedback(false), [query])

if (noResults || showFeedback) {
if (showFeedback) {
return (
<Container>
<FeedbackContainer
cityCode={cityCode}
language={languageCode}
routeType={SEARCH_ROUTE}
query={query}
noResults={noResults}
initialRating={null}
/>
</Container>
)
}

if (noResults) {
const fallbackLanguage = config.sourceLanguage

return (
<CenteredContainer>
<SmallTitle>
{languageCode === fallbackLanguage ? t('noResultsInUserLanguage') : t('noResultsInUserAndSourceLanguage')}
</SmallTitle>
<Hint>{t('checkQuery', { appName: buildConfig().appName })}</Hint>
<SmallTitle>{t('informationMissing')}</SmallTitle>
<StyledButton type='button' text={t('giveFeedback')} onClick={() => setShowFeedback(true)} />
</CenteredContainer>
)
}

return (
<Container>
<TextButton onClick={() => setShowFeedback(true)} text={t('informationNotFound')} />
Expand Down
1 change: 0 additions & 1 deletion web/src/components/__tests__/Feedback.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ describe('Feedback', () => {
/>,
)
expect(getByText('feedback:wantedInformation')).toBeTruthy()
expect(getByText('error:search:nothingFound')).toBeTruthy()
})

it('should display error', () => {
Expand Down
6 changes: 4 additions & 2 deletions web/src/components/__tests__/SearchFeedback.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,18 @@ describe('SearchFeedback', () => {
expect(queryByText('feedback:wantedInformation')).toBeNull()
})

it('should show feedback if no results found', () => {
it('should show feedback button if no results found', () => {
const { getByText } = renderWithTheme(
<SearchFeedback cityCode={cityCode} languageCode={languageCode} query='ab' noResults />,
)
expect(getByText('feedback:send')).toBeTruthy()
expect(getByText('feedback:giveFeedback')).toBeTruthy()
})

it('should not allow sending search feedback if query term is removed', async () => {
const { getByText, rerender } = renderWithTheme(
<SearchFeedback cityCode={cityCode} languageCode={languageCode} query='ab' noResults />,
)
fireEvent.click(getByText('feedback:giveFeedback'))
expect(getByText('feedback:send')).toBeEnabled()

// the query is controlled in the parent of SearchFeedback, so we need to update the props
Expand All @@ -64,6 +65,7 @@ describe('SearchFeedback', () => {
<SearchFeedback cityCode={cityCode} languageCode={languageCode} query='' noResults />
</ThemeProvider>,
)
fireEvent.click(getByText('feedback:giveFeedback'))
await waitFor(() => expect(getByText('feedback:send')).toBeDisabled())
})
})
Loading