Skip to content

Commit

Permalink
feat: add settings tab and toggle for multi lang feature
Browse files Browse the repository at this point in the history
  • Loading branch information
siddarth2824 committed Aug 1, 2024
1 parent 0ab3e87 commit 67792b0
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 3 deletions.
16 changes: 16 additions & 0 deletions frontend/src/assets/icons/LanguageTranslation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const LanguageTranslation = (
props: React.SVGProps<SVGSVGElement>,
): JSX.Element => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
fill="currentColor"
className="bi bi-translate"
viewBox="0 0 16 16"
{...props}
>
<path d="M4.545 6.714 4.11 8H3l1.862-5h1.284L8 8H6.833l-.435-1.286zm1.634-.736L5.5 3.956h-.049l-.679 2.022z" />
<path d="M0 2a2 2 0 0 1 2-2h7a2 2 0 0 1 2 2v3h3a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-3H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zm7.138 9.995q.289.451.63.846c-.748.575-1.673 1.001-2.768 1.292.178.217.451.635.555.867 1.125-.359 2.08-.844 2.886-1.494.777.665 1.739 1.165 2.93 1.472.133-.254.414-.673.629-.89-1.125-.253-2.057-.694-2.82-1.284.681-.747 1.222-1.651 1.621-2.757H14V8h-3v1.047h.765c-.318.844-.74 1.546-1.272 2.13a6 6 0 0 1-.415-.492 2 2 0 0 1-.94.31" />
</svg>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { MultiLanguageSection } from './components/MultiLanguageSection/MultiLanguageSection'

export const SettingsMultiLangPage = (): JSX.Element => {
return (
<>
<MultiLanguageSection />
</>
)
}
8 changes: 8 additions & 0 deletions frontend/src/features/admin-form/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
import { FormResponseMode } from '~shared/types'
import { isNonEmpty } from '~shared/utils/isNonEmpty'

import { LanguageTranslation } from '~assets/icons/LanguageTranslation'
import { ADMINFORM_RESULTS_SUBROUTE, ADMINFORM_ROUTE } from '~constants/routes'
import { useDraggable } from '~hooks/useDraggable'

Expand All @@ -32,6 +33,7 @@ import { useAdminFormSettings } from './queries'
import { SettingsAuthPage } from './SettingsAuthPage'
import { SettingsEmailsPage } from './SettingsEmailsPage'
import { SettingsGeneralPage } from './SettingsGeneralPage'
import { SettingsMultiLangPage } from './SettingsMultiLangPage'
import { SettingsPaymentsPage } from './SettingsPaymentsPage'
import { SettingsTwilioPage } from './SettingsTwilioPage'
import { SettingsWebhooksPage } from './SettingsWebhooksPage'
Expand Down Expand Up @@ -103,6 +105,12 @@ export const SettingsPage = (): JSX.Element => {
component: SettingsPaymentsPage,
path: 'payments',
},
{
label: 'Multi-language',
icon: LanguageTranslation,
component: SettingsMultiLangPage,
path: 'multi-language',
},
]

return baseConfig.filter(isNonEmpty)
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/features/admin-form/settings/SettingsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ export const updateFormLimit: UpdateFormFn<'submissionLimit'> = async (
return updateFormSettings(formId, { submissionLimit: newLimit })
}

export const updateFormHasMultiLang: UpdateFormFn<'hasMultiLang'> = async (
formId,
newHasMultiLang,
) => {
return updateFormSettings(formId, {
hasMultiLang: newHasMultiLang,
})
}

export const updateFormSupportedLanguages: UpdateFormFn<
'supportedLanguages'
> = async (formId, newSupportedLanguages) => {
return updateFormSettings(formId, {
supportedLanguages: newSupportedLanguages,
})
}

export const updateFormCaptcha: UpdateFormFn<'hasCaptcha'> = async (
formId,
newHasCaptcha,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { useCallback, useMemo, useState } from 'react'
import { BiEditAlt } from 'react-icons/bi'
import { GoEye, GoEyeClosed } from 'react-icons/go'
import {
Box,
Divider,
Flex,
HStack,
IconButton,
Skeleton,
Text,
} from '@chakra-ui/react'
import _ from 'lodash'

import { Language } from '~shared/types'

import { convertUnicodeLocaleToLanguage } from '~utils/multiLanguage'
import Badge from '~components/Badge'
import Toggle from '~components/Toggle'

import { useMutateFormSettings } from '../../mutations'
import { useAdminFormSettings } from '../../queries'

interface LanguageTranslationRowProps {
unicodeLocale: Language
isDefaultLanguage: boolean
isLast?: boolean
}

interface LanguageTranslationSectionProps {
defaultLanguage: Language
}

const LanguageTranslationRow = ({
unicodeLocale,
isDefaultLanguage,
isLast,
}: LanguageTranslationRowProps): JSX.Element => {
const { data: settings } = useAdminFormSettings()

const supportedLanguages = settings?.supportedLanguages ?? null

const [isLanguageSupported, setIsLanguageSupported] = useState(
supportedLanguages?.indexOf(unicodeLocale) !== -1,
)

const languageToDisplay = convertUnicodeLocaleToLanguage(unicodeLocale)

const { mutateFormSupportedLanguages } = useMutateFormSettings()

const handleToggleSupportedLanague = useCallback(
(language: Language) => {
if (supportedLanguages == null) return

// Remove support for this language if it exists in supportedLanguages
const existingSupportedLanguageIdx = supportedLanguages.indexOf(language)
if (existingSupportedLanguageIdx !== -1) {
supportedLanguages.splice(existingSupportedLanguageIdx, 1)
setIsLanguageSupported(false)
return mutateFormSupportedLanguages.mutate({
nextSupportedLanguages: supportedLanguages,
selectedLanguage: language,
})
}

// If selected language is not in supportedLanguages then add this language
// to supportedLanguages
else {
const updatedSupportedLanguages = supportedLanguages.concat(language)
setIsLanguageSupported(true)
return mutateFormSupportedLanguages.mutate({
nextSupportedLanguages: updatedSupportedLanguages,
selectedLanguage: language,
})
}
},
[mutateFormSupportedLanguages, supportedLanguages],
)

return (
<>
<Flex alignItems="center" w="100%" py={2}>
<Flex marginRight="auto">
<Text mr="0.75rem">{languageToDisplay}</Text>
{isDefaultLanguage && (
<Badge variant="solid" colorScheme="success" color="secondary.700">
Default
</Badge>
)}
</Flex>
{!isDefaultLanguage && (
<HStack spacing="0.75rem">
<IconButton
variant="clear"
icon={
isLanguageSupported ? (
<GoEye width="44px" />
) : (
<GoEyeClosed width="44px" />
)
}
colorScheme="secondary"
aria-label={`Select ${unicodeLocale} as the form's default language`}
onClick={() => handleToggleSupportedLanague(unicodeLocale)}
/>
<IconButton
variant="clear"
icon={<BiEditAlt width="44px" />}
colorScheme="secondary"
aria-label={`Add ${unicodeLocale} translations`}
// TODO: Will add redirection to translation section in next PR
onClick={() => console.log('Edit translations')}
/>
</HStack>
)}
</Flex>
{!isLast && <Divider />}
</>
)
}

const LanguageTranslationSection = ({
defaultLanguage,
}: LanguageTranslationSectionProps): JSX.Element => {
let unicodeLocales = Object.values(Language)

const idxToRemove = unicodeLocales.indexOf(defaultLanguage)
unicodeLocales.splice(idxToRemove, 1)

unicodeLocales = [defaultLanguage, ...unicodeLocales]

return (
<Box pt={8}>
{unicodeLocales.map((unicodeLocale, id, arr) => {
return (
<LanguageTranslationRow
unicodeLocale={unicodeLocale}
isDefaultLanguage={unicodeLocale === defaultLanguage}
isLast={id === arr.length - 1}
key={unicodeLocale}
/>
)
})}
</Box>
)
}

export const FormMultiLanguageToggle = (): JSX.Element => {
const { data: settings, isLoading: isLoadingSettings } =
useAdminFormSettings()

const { mutateFormSupportedLanguages, mutateFormHasMultiLang } =
useMutateFormSettings()

const hasMultiLang = useMemo(
() => settings && settings?.hasMultiLang,
[settings],
)

const handleToggleMultiLang = useCallback(() => {
if (!settings || isLoadingSettings || mutateFormHasMultiLang.isLoading)
return

const nextHasMultiLang = !settings.hasMultiLang

const currentSupportedLanguages = settings?.supportedLanguages

// Restore back previously supported languages.
let nextSupportedLanguages = currentSupportedLanguages

// Add all languages as this is the first instance user turns on
// toggle
if (_.isEmpty(currentSupportedLanguages) && nextHasMultiLang) {
nextSupportedLanguages = Object.values(Language)
}

mutateFormSupportedLanguages.mutate({ nextSupportedLanguages })

return mutateFormHasMultiLang.mutate(nextHasMultiLang)
}, [
isLoadingSettings,
mutateFormHasMultiLang,
mutateFormSupportedLanguages,
settings,
])

return (
<Skeleton isLoaded={!isLoadingSettings && !!settings} mt="2rem">
<Toggle
onChange={handleToggleMultiLang}
isChecked={hasMultiLang}
label="Enable multi-language"
description="This will allow respondents to select a language they prefer to view your form in. Translations are not automated."
/>
{settings && hasMultiLang && (
<Skeleton isLoaded={true}>
<LanguageTranslationSection defaultLanguage={Language.ENGLISH} />
</Skeleton>
)}
</Skeleton>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CategoryHeader } from '../CategoryHeader'

import { FormMultiLanguageToggle } from './FormMultiLanguageToggle'

export const MultiLanguageSection = (): JSX.Element => {
return (
<>
<CategoryHeader>Multi-language</CategoryHeader>
<FormMultiLanguageToggle />
</>
)
}
54 changes: 54 additions & 0 deletions frontend/src/features/admin-form/settings/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FormResponseMode,
FormSettings,
FormStatus,
FormSupportedLanguages,
StorageFormSettings,
} from '~shared/types/form/form'
import { TwilioCredentials } from '~shared/types/twilio'
Expand All @@ -18,6 +19,7 @@ import { ApiError } from '~typings/core'

import { GUIDE_PREVENT_EMAIL_BOUNCE } from '~constants/links'
import { useToast } from '~hooks/useToast'
import { convertUnicodeLocaleToLanguage } from '~utils/multiLanguage'
import { formatOrdinal } from '~utils/stringFormat'

import { updateFormPayments } from '../common/AdminFormPageService'
Expand All @@ -33,11 +35,13 @@ import {
updateFormCaptcha,
updateFormEmails,
updateFormEsrvcId,
updateFormHasMultiLang,
updateFormInactiveMessage,
updateFormIssueNotification,
updateFormLimit,
updateFormNricMask,
updateFormStatus,
updateFormSupportedLanguages,
updateFormTitle,
updateFormWebhookRetries,
updateFormWebhookUrl,
Expand Down Expand Up @@ -137,6 +141,54 @@ export const useMutateFormSettings = () => {
},
)

const mutateFormHasMultiLang = useMutation(
(nextHasMultiLang: boolean) =>
updateFormHasMultiLang(formId, nextHasMultiLang),
{
onSuccess: (newData) => {
handleSuccess({
newData,
toastDescription: newData.hasMultiLang
? 'Multi-language enabled. Respondents can now select other languages to view your form in.'
: 'Multi-language disabled.',
})
},
},
)

const mutateFormSupportedLanguages = useMutation(
(nextSupportedLanguages?: FormSupportedLanguages) =>
updateFormSupportedLanguages(
formId,
nextSupportedLanguages?.nextSupportedLanguages,
),
{
onSuccess: (newData, newSupportedLanguages) => {
if (newSupportedLanguages && newSupportedLanguages.selectedLanguage) {
const supportedLanguages =
newSupportedLanguages.nextSupportedLanguages ?? []
const languageToDisplay = convertUnicodeLocaleToLanguage(
newSupportedLanguages.selectedLanguage,
)

let successMessage: string
if (
supportedLanguages.includes(newSupportedLanguages.selectedLanguage)
) {
successMessage = `Respondents will now be able to select and view your form in ${languageToDisplay}.`
} else {
successMessage = `${languageToDisplay} is now hidden. Respondents will not be able to see it.`
}
handleSuccess({
newData,
toastDescription: successMessage,
})
}
},
onError: handleError,
},
)

const mutateFormCaptcha = useMutation(
(nextHasCaptcha: boolean) => updateFormCaptcha(formId, nextHasCaptcha),
{
Expand Down Expand Up @@ -384,6 +436,8 @@ export const useMutateFormSettings = () => {
mutateFormWebhookUrl,
mutateFormStatus,
mutateFormLimit,
mutateFormHasMultiLang,
mutateFormSupportedLanguages,
mutateFormInactiveMessage,
mutateFormCaptcha,
mutateFormIssueNotification,
Expand Down
Loading

0 comments on commit 67792b0

Please sign in to comment.