Skip to content
Open
13 changes: 10 additions & 3 deletions shared/routes/query.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Rating, RATING_NEGATIVE, RATING_POSITIVE } from '../constants/index.js'
import { safeParseInt } from '../utils/index.js'
import { NonNullableRouteInformationType } from './RouteInformationTypes.js'
import { PLACES_ROUTE, SEARCH_ROUTE } from './index.js'

export const MULTI_PLACE_QUERY_KEY = 'multiplace'
export const SEARCH_QUERY_KEY = 'query'
export const CHAT_QUERY_KEY = 'chat'
export const FEEDBACK_QUERY_KEY = 'feedback'
export const PLACE_CATEGORY_QUERY_KEY = 'category'
export const ZOOM_QUERY_KEY = 'zoom'

Expand Down Expand Up @@ -37,7 +39,8 @@ export const queryStringFromRouteInformation = (
}

export type VisibilityQueryParams = {
chat?: boolean
chat?: true
feedback?: Rating
}

type QueryParams = VisibilityQueryParams & {
Expand All @@ -49,11 +52,13 @@ type QueryParams = VisibilityQueryParams & {

export const parseQueryParams = (queryParams: URLSearchParams): QueryParams => {
const searchText = queryParams.get(SEARCH_QUERY_KEY) ?? undefined
const chat = queryParams.get(CHAT_QUERY_KEY) ? queryParams.get(CHAT_QUERY_KEY) === 'true' : undefined
const chat = queryParams.get(CHAT_QUERY_KEY) === 'true' || undefined
const feedbackQuery = queryParams.get(FEEDBACK_QUERY_KEY) ?? undefined
const feedback = feedbackQuery === RATING_POSITIVE || feedbackQuery === RATING_NEGATIVE ? feedbackQuery : undefined
const multiPlace = safeParseInt(queryParams.get(MULTI_PLACE_QUERY_KEY))
const placeCategoryId = safeParseInt(queryParams.get(PLACE_CATEGORY_QUERY_KEY))
const zoom = safeParseInt(queryParams.get(ZOOM_QUERY_KEY))
return { searchText, multiPlace, placeCategoryId, zoom, chat }
return { searchText, multiPlace, placeCategoryId, zoom, chat, feedback }
}

export const toQueryParams = ({
Expand All @@ -62,10 +67,12 @@ export const toQueryParams = ({
zoom,
searchText,
chat,
feedback,
}: QueryParams): URLSearchParams => {
const queryParams: [string, string | undefined][] = [
[SEARCH_QUERY_KEY, searchText],
[CHAT_QUERY_KEY, chat?.toString()],
[FEEDBACK_QUERY_KEY, feedback],
[MULTI_PLACE_QUERY_KEY, multiPlace?.toString()],
[PLACE_CATEGORY_QUERY_KEY, placeCategoryId?.toString()],
[ZOOM_QUERY_KEY, zoom?.toString()],
Expand Down
1 change: 1 addition & 0 deletions web/src/RegionContentNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const RegionContentNavigator = ({ languageCode }: RegionContentNavigatorProps):
isLoading
region={region}
pageTitle={null}
slug={null}
/>
) : (
<Layout />
Expand Down
66 changes: 33 additions & 33 deletions web/src/components/FeedbackContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,38 @@ import CloseIcon from '@mui/icons-material/Close'
import IconButton from '@mui/material/IconButton'
import React, { ReactElement, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useSearchParams } from 'react-router'

import { Rating, SendingStatusType } from 'shared'
import { FEEDBACK_QUERY_KEY, parseQueryParams, Rating, RATING_POSITIVE, SEARCH_ROUTE, SendingStatusType } from 'shared'
import { createFeedbackEndpoint, FeedbackRouteType } from 'shared/api'

import { cmsApiBaseUrl } from '../constants/urls'
import useQueryParamVisibility from '../hooks/useQueryParamVisibility'
import useRegionContentParams from '../hooks/useRegionContentParams'
import { captureError } from '../utils/sentry'
import Feedback from './Feedback'
import Dialog from './base/Dialog'
import Snackbar from './base/Snackbar'

type FeedbackContainerProps = {
query?: string
noResults?: boolean
slug?: string
onSubmit?: () => void
onError?: () => void
initialRating: Rating | null
slug: string | null
}

export const FeedbackContainer = ({
query,
noResults,
slug,
onSubmit,
onError,
initialRating,
}: FeedbackContainerProps): ReactElement => {
const FeedbackContainer = ({ slug }: FeedbackContainerProps): ReactElement | null => {
const { visible, open, close, value: rating } = useQueryParamVisibility(FEEDBACK_QUERY_KEY)
const [queryParams] = useSearchParams()
const { t } = useTranslation('feedback')
const [rating, setRating] = useState<Rating | null>(initialRating)
const { route, regionCode, languageCode } = useRegionContentParams()
const { searchText } = parseQueryParams(queryParams)
const query = route === SEARCH_ROUTE ? searchText : undefined

const [comment, setComment] = useState<string>('')
const [contactMail, setContactMail] = useState<string>('')
const [sendingStatus, setSendingStatus] = useState<SendingStatusType>('idle')
const [snackbarOpen, setSnackbarOpen] = useState(false)
const [searchTerm, setSearchTerm] = useState<string | undefined>(query)
const { route, regionCode, languageCode } = useRegionContentParams()

const setRating = (newRating: Rating | null) => open(newRating ?? undefined)

useEffect(() => {
setSearchTerm(query)
Expand All @@ -54,38 +51,41 @@ export const FeedbackContainer = ({
comment,
contactMail,
query,
slug,
slug: slug ?? undefined,
searchTerm,
isPositiveRating: !noResults && rating === 'positive',
isPositiveRating: rating === RATING_POSITIVE,
})

setSendingStatus('successful')
setSnackbarOpen(true)
onSubmit?.()
close()
}

request().catch(err => {
captureError(err)
setSendingStatus('failed')
setSnackbarOpen(true)
onError?.()
})
}

return (
<>
<Feedback
language={languageCode}
onCommentChanged={setComment}
onContactMailChanged={setContactMail}
onSubmit={handleSubmit}
rating={rating}
comment={comment}
setRating={setRating}
contactMail={contactMail}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
{visible && (
<Dialog title={t('headline')} close={close}>
<Feedback
language={languageCode}
onCommentChanged={setComment}
onContactMailChanged={setContactMail}
onSubmit={handleSubmit}
rating={rating ?? null}
comment={comment}
setRating={setRating}
contactMail={contactMail}
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
</Dialog>
)}
<Snackbar
open={snackbarOpen}
onClose={() => setSnackbarOpen(false)}
Expand Down
70 changes: 10 additions & 60 deletions web/src/components/FeedbackToolbarItem.tsx
Comment thread
bahaaTuffaha marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,73 +1,23 @@
import CloseIcon from '@mui/icons-material/Close'
import SentimentDissatisfiedOutlinedIcon from '@mui/icons-material/SentimentDissatisfiedOutlined'
import SentimentSatisfiedOutlinedIcon from '@mui/icons-material/SentimentSatisfiedOutlined'
import IconButton from '@mui/material/IconButton'
import React, { ReactElement, useState } from 'react'
import React, { ReactElement } from 'react'
import { useTranslation } from 'react-i18next'

import { Rating, RATING_POSITIVE, SendingStatusType } from 'shared'
import { FEEDBACK_QUERY_KEY, RATING_POSITIVE, Rating } from 'shared'

import FeedbackContainer from './FeedbackContainer'
import useQueryParamVisibility from '../hooks/useQueryParamVisibility'
import ToolbarItem from './ToolbarItem'
import Dialog from './base/Dialog'
import Snackbar from './base/Snackbar'

type FeedbackToolbarItemProps = {
slug?: string
rating: Rating | null
}

const FeedbackToolbarItem = ({ slug, rating }: FeedbackToolbarItemProps): ReactElement => {
const [isFeedbackOpen, setIsFeedbackOpen] = useState(false)
const FeedbackToolbarItem = ({ rating }: { rating: Rating }): ReactElement => {
const { t } = useTranslation('feedback')
const [sendingStatus, setSendingStatus] = useState<SendingStatusType>('idle')
const [snackbarOpen, setSnackbarOpen] = useState(false)

const handleFeedbackSuccess = () => {
setIsFeedbackOpen(false)
setSendingStatus('successful')
setSnackbarOpen(true)
}

const handleFeedbackError = () => {
setIsFeedbackOpen(true)
setSendingStatus('failed')
setSnackbarOpen(true)
}
const { open } = useQueryParamVisibility(FEEDBACK_QUERY_KEY)

return (
<>
{isFeedbackOpen && (
<Dialog title={t('headline')} close={() => setIsFeedbackOpen(false)}>
<FeedbackContainer
onSubmit={handleFeedbackSuccess}
onError={handleFeedbackError}
slug={slug}
initialRating={rating}
/>
</Dialog>
)}
<ToolbarItem
icon={rating === RATING_POSITIVE ? <SentimentSatisfiedOutlinedIcon /> : <SentimentDissatisfiedOutlinedIcon />}
text={t(rating === RATING_POSITIVE ? 'useful' : 'notUseful')}
onClick={() => setIsFeedbackOpen(true)}
/>
<Snackbar
open={snackbarOpen}
severity={sendingStatus === 'successful' ? 'success' : 'error'}
onClose={() => setSnackbarOpen(false)}
message={sendingStatus === 'successful' ? t('thanksMessage') : t('failedSendingFeedback')}
action={
<IconButton
aria-label={t('common:close')}
color='inherit'
size='small'
onClick={() => setSnackbarOpen(false)}>
<CloseIcon />
</IconButton>
}
/>
</>
<ToolbarItem
icon={rating === RATING_POSITIVE ? <SentimentSatisfiedOutlinedIcon /> : <SentimentDissatisfiedOutlinedIcon />}
text={t(rating === RATING_POSITIVE ? 'useful' : 'notUseful')}
onClick={() => open(rating)}
/>
Comment thread
bahaaTuffaha marked this conversation as resolved.
)
}

Expand Down
4 changes: 4 additions & 0 deletions web/src/components/RegionContentLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import useDimensions from '../hooks/useDimensions'
import useRegionContentParams from '../hooks/useRegionContentParams'
import BottomNavigation from './BottomNavigation'
import ChatContainer from './ChatContainer'
import FeedbackContainer from './FeedbackContainer'
import Footer from './Footer'
import { LanguageChangePath } from './LanguageList'
import Layout from './Layout'
Expand All @@ -23,6 +24,7 @@ export type RegionContentLayoutProps = {
fitScreen?: boolean
category?: CategoryModel
pageTitle: string | null
slug: string | null
}

const RegionContentLayout = ({
Expand All @@ -35,6 +37,7 @@ const RegionContentLayout = ({
toolbar,
fitScreen = false,
pageTitle,
slug,
}: RegionContentLayoutProps): ReactElement => {
const { route } = useRegionContentParams()
const [layoutReady, setLayoutReady] = useState(!isLoading)
Expand Down Expand Up @@ -65,6 +68,7 @@ const RegionContentLayout = ({
{chatVisible && (
<ChatContainer region={region} languageCode={languageCode} languageChangePaths={languageChangePaths} />
)}
<FeedbackContainer slug={slug} />
{mobile && <BottomNavigation regionModel={region} languageCode={languageCode} />}
</>
}
Expand Down
30 changes: 9 additions & 21 deletions web/src/components/RegionContentMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
import CommentIcon from '@mui/icons-material/CommentOutlined'
import ContrastIcon from '@mui/icons-material/Contrast'
import { useTheme } from '@mui/material/styles'
import React, { ReactElement, useContext, useRef, useState } from 'react'
import React, { ReactElement, useContext, useRef } from 'react'
import { useTranslation } from 'react-i18next'

import { NEWS_ROUTE, CATEGORIES_ROUTE } from 'shared'
import { NEWS_ROUTE, CATEGORIES_ROUTE, FEEDBACK_QUERY_KEY } from 'shared'
import { CategoryModel } from 'shared/api'

import { ReadAloudIcon } from '../assets'
import { TtsContext } from '../contexts/TtsContext'
import useQueryParamVisibility from '../hooks/useQueryParamVisibility'
import useRegionContentParams from '../hooks/useRegionContentParams'
import FeedbackContainer from './FeedbackContainer'
import HeaderMenu, { MenuRef } from './HeaderMenu'
import MenuItem from './MenuItem'
import PdfMenuItem from './PdfMenuItem'
import Dialog from './base/Dialog'
import Svg from './base/Svg'

type RegionContentMenuProps = {
slug?: string
category?: CategoryModel
pageTitle: string | null
fitScreen?: boolean
}

const RegionContentMenu = ({ slug, category, pageTitle, fitScreen }: RegionContentMenuProps): ReactElement => {
const RegionContentMenu = ({ category, pageTitle, fitScreen }: RegionContentMenuProps): ReactElement => {
const { route, regionCode, languageCode } = useRegionContentParams()
const { showTtsPlayer, canRead } = useContext(TtsContext)
const { toggleTheme, dimensions } = useTheme()
const { t } = useTranslation('layout')
const ref = useRef<MenuRef>(null)
const { open } = useQueryParamVisibility(FEEDBACK_QUERY_KEY)

const [feedbackOpen, setFeedbackOpen] = useState(false)
const [feedbackSubmitted, setFeedbackSubmitted] = useState(false)
const showFeedback = fitScreen || (dimensions.mobile && route !== NEWS_ROUTE)
const closeMenu = ref.current?.closeMenu

Expand All @@ -51,7 +48,7 @@ const RegionContentMenu = ({ slug, category, pageTitle, fitScreen }: RegionConte
key='feedback'
text={t('feedback')}
icon={<CommentIcon fontSize='small' />}
onClick={() => setFeedbackOpen(true)}
onClick={open}
closeMenu={closeMenu}
/>
) : null,
Expand All @@ -68,18 +65,9 @@ const RegionContentMenu = ({ slug, category, pageTitle, fitScreen }: RegionConte
]

return (
<>
<HeaderMenu pageTitle={pageTitle} fitScreen={fitScreen} ref={ref}>
{items}
</HeaderMenu>
{feedbackOpen && (
<Dialog
title={feedbackSubmitted ? t('feedback:thanksHeadline') : t('feedback:headline')}
close={() => setFeedbackOpen(false)}>
<FeedbackContainer onSubmit={() => setFeedbackSubmitted(true)} slug={slug} initialRating={null} />
</Dialog>
)}
</>
<HeaderMenu pageTitle={pageTitle} fitScreen={fitScreen} ref={ref}>
{items}
</HeaderMenu>
Comment thread
bahaaTuffaha marked this conversation as resolved.
)
}

Expand Down
10 changes: 3 additions & 7 deletions web/src/components/RegionContentToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ import useRegionContentParams from '../hooks/useRegionContentParams'
import useUpdateDimensions from '../hooks/useUpdateDimensions'
import FeedbackToolbarItem from './FeedbackToolbarItem'

type RegionContentToolbarProps = {
slug?: string
}

const RegionContentToolbar = ({ slug }: RegionContentToolbarProps): ReactElement | null => {
const RegionContentToolbar = (): ReactElement | null => {
const { route } = useRegionContentParams()
useUpdateDimensions()

Expand All @@ -23,8 +19,8 @@ const RegionContentToolbar = ({ slug }: RegionContentToolbarProps): ReactElement

return (
<Stack id={TOOLBAR_ELEMENT_ID}>
<FeedbackToolbarItem key='positive' slug={slug} rating={RATING_POSITIVE} />
<FeedbackToolbarItem key='negative' slug={slug} rating={RATING_NEGATIVE} />
<FeedbackToolbarItem key='positive' rating={RATING_POSITIVE} />
<FeedbackToolbarItem key='negative' rating={RATING_NEGATIVE} />
</Stack>
)
}
Expand Down
Loading
Loading