diff --git a/src/abstract/lib/constants.ts b/src/abstract/lib/constants.ts index ae04607f1..d394aabfd 100644 --- a/src/abstract/lib/constants.ts +++ b/src/abstract/lib/constants.ts @@ -32,4 +32,5 @@ export const phrasalTemplateCompatibleResponseTypes = [ 'multiSelectRows', 'singleSelectRows', 'sliderRows', + 'paragraphText', ]; diff --git a/src/entities/activity/ui/items/ActionPlan/ResponseSegment.tsx b/src/entities/activity/ui/items/ActionPlan/ResponseSegment.tsx index 38594c018..c0ed1e306 100644 --- a/src/entities/activity/ui/items/ActionPlan/ResponseSegment.tsx +++ b/src/entities/activity/ui/items/ActionPlan/ResponseSegment.tsx @@ -24,7 +24,7 @@ const isAnswersSkipped = (answers: string[]): boolean => { type FieldValueTransformer = (value: string) => string; const identity: FieldValueTransformer = (value) => value; -type FieldValuesJoiner = (values: string[]) => string; +type FieldValuesJoiner = (values: string[]) => string | JSX.Element[]; const joinWithComma: FieldValuesJoiner = (values) => values.join(', '); type ResponseSegmentProps = { @@ -58,6 +58,9 @@ export const ResponseSegment = ({ phrasalData, field, isAtStart }: ResponseSegme }; } else if (fieldPhrasalData.context.itemResponseType === 'timeRange') { joinSentenceWords = (values) => values.join(' - '); + } else if (fieldPhrasalData.context.itemResponseType === 'paragraphText') { + joinSentenceWords = (values) => + values.map((item, index) =>
{item || ' '}
); } let words: string[]; @@ -65,6 +68,12 @@ export const ResponseSegment = ({ phrasalData, field, isAtStart }: ResponseSegme words = isAnswersSkipped(fieldPhrasalData.values) ? [t('questionSkipped')] : fieldPhrasalData.values.map(transformValue); + } else if (fieldPhrasalDataType === 'paragraph') { + words = isAnswersSkipped(fieldPhrasalData.values) + ? [t('questionSkipped')] + : fieldPhrasalData.values + .flatMap((value) => value.split(/\r?\n/)) // Split each paragraph by newlines + .map(transformValue); } else if (fieldPhrasalDataType === 'indexed-array') { const indexedAnswers = fieldPhrasalData.values[field.itemIndex] || []; words = isAnswersSkipped(indexedAnswers) diff --git a/src/entities/activity/ui/items/ActionPlan/phrasalData.ts b/src/entities/activity/ui/items/ActionPlan/phrasalData.ts index 50ba98406..cb8cba5f2 100644 --- a/src/entities/activity/ui/items/ActionPlan/phrasalData.ts +++ b/src/entities/activity/ui/items/ActionPlan/phrasalData.ts @@ -1,3 +1,4 @@ +/* eslint-disable prettier/prettier */ import { phrasalTemplateCompatibleResponseTypes } from '~/abstract/lib/constants'; import { ActivityItemType } from '~/entities/activity/lib'; import { ItemRecord } from '~/entities/applet/model'; @@ -23,6 +24,8 @@ type ActivityPhrasalBaseData< type ActivityPhrasalArrayFieldData = ActivityPhrasalBaseData<'array', string[]>; +type ActivityPhrasalParagraphFieldData = ActivityPhrasalBaseData<'paragraph', string[]>; + type ActivityPhrasalItemizedArrayValue = Record; type ActivityPhrasalIndexedArrayFieldData = ActivityPhrasalBaseData< @@ -45,7 +48,8 @@ type ActivityPhrasalMatrixFieldData = ActivityPhrasalBaseData<'matrix', Activity type ActivityPhrasalData = | ActivityPhrasalArrayFieldData | ActivityPhrasalIndexedArrayFieldData - | ActivityPhrasalMatrixFieldData; + | ActivityPhrasalMatrixFieldData + | ActivityPhrasalParagraphFieldData; export type ActivitiesPhrasalData = Record; @@ -92,6 +96,13 @@ export const extractActivitiesPhrasalData = (items: ItemRecord[]): ActivitiesPhr context: fieldDataContext, }; fieldData = dateFieldData; + } else if (item.responseType === 'paragraphText') { + const dateFieldData: ActivityPhrasalParagraphFieldData = { + type: 'paragraph', + values: item.answer.map((value) => value || ''), + context: fieldDataContext, + }; + fieldData = dateFieldData; } else if (item.responseType === 'singleSelect' || item.responseType === 'multiSelect') { const dateFieldData: ActivityPhrasalArrayFieldData = { type: 'array', diff --git a/src/entities/user/model/hooks/useOnLogin.ts b/src/entities/user/model/hooks/useOnLogin.ts index 83facc1d7..f256332d9 100644 --- a/src/entities/user/model/hooks/useOnLogin.ts +++ b/src/entities/user/model/hooks/useOnLogin.ts @@ -47,7 +47,7 @@ export const useOnLogin = (params: Params) => { secureTokensStorage.setTokens(tokens); if (params.backRedirectPath !== undefined) { - navigate(params.backRedirectPath); + navigate(params.backRedirectPath, { replace: true }); } else { Mixpanel.track({ action: MixpanelEventType.LoginSuccessful }); Mixpanel.login(user.id); diff --git a/src/features/Logout/lib/useLogout.ts b/src/features/Logout/lib/useLogout.ts index 6337afc5d..b8b7d55c1 100644 --- a/src/features/Logout/lib/useLogout.ts +++ b/src/features/Logout/lib/useLogout.ts @@ -1,5 +1,7 @@ import { useCallback } from 'react'; +import { useLocation } from 'react-router-dom'; + import { appletModel } from '~/entities/applet'; import { useLogoutMutation, userModel } from '~/entities/user'; import { AutoCompletionModel } from '~/features/AutoCompletion'; @@ -19,6 +21,7 @@ type UseLogoutReturn = { export const useLogout = (): UseLogoutReturn => { const navigator = useCustomNavigation(); + const location = useLocation(); const { clearUser } = userModel.hooks.useUserState(); const { clearStore } = appletModel.hooks.useClearStore(); @@ -42,8 +45,18 @@ export const useLogout = (): UseLogoutReturn => { Mixpanel.track({ action: MixpanelEventType.Logout }); Mixpanel.logout(); FeatureFlags.logout(); - return navigator.navigate(ROUTES.login.path); - }, [clearUser, clearStore, clearAutoCompletionState, navigator, logoutMutation]); + + const backRedirectPath = `${location.pathname}${location.search}`; + return navigator.navigate(ROUTES.login.path, { state: { backRedirectPath } }); + }, [ + clearUser, + clearStore, + clearAutoCompletionState, + location.pathname, + location.search, + navigator, + logoutMutation, + ]); return { logout, diff --git a/src/features/PassSurvey/ui/EntityTimer.tsx b/src/features/PassSurvey/ui/EntityTimer.tsx index b3c426e44..6e7fc5714 100644 --- a/src/features/PassSurvey/ui/EntityTimer.tsx +++ b/src/features/PassSurvey/ui/EntityTimer.tsx @@ -12,6 +12,7 @@ import { getMsFromHours, getMsFromMinutes, useAppSelector, + useCustomTranslation, useTimer, } from '~/shared/utils'; @@ -20,6 +21,7 @@ type Props = { }; export const EntityTimer = ({ entityTimerSettings }: Props) => { + const { t } = useCustomTranslation(); const context = useContext(SurveyContext); const [varForDeps, forceUpdate] = useState({}); @@ -64,7 +66,7 @@ export const EntityTimer = ({ entityTimerSettings }: Props) => { return '00:00'; } - return `${formatTimerTime(timeToLeft)} remaining`; + return t('timeRemaining', { time: formatTimerTime(timeToLeft) }); }; const checkLessThan10Mins = (): boolean => { diff --git a/src/features/PassSurvey/ui/ItemTimerBar.tsx b/src/features/PassSurvey/ui/ItemTimerBar.tsx index ad56bd3ac..d07d3785d 100644 --- a/src/features/PassSurvey/ui/ItemTimerBar.tsx +++ b/src/features/PassSurvey/ui/ItemTimerBar.tsx @@ -1,7 +1,7 @@ import { Theme } from '~/shared/constants'; import Box from '~/shared/ui/Box'; import Text from '~/shared/ui/Text'; -import { convertMillisecondsToMinSec } from '~/shared/utils'; +import { convertMillisecondsToMinSec, useCustomTranslation } from '~/shared/utils'; type Props = { duration: number; // seconds @@ -10,6 +10,7 @@ type Props = { }; export const ItemTimerBar = ({ time, progress, duration }: Props) => { + const { t } = useCustomTranslation(); const getProgressBarShift = (progress: number) => { return progress - 100; }; @@ -70,7 +71,7 @@ export const ItemTimerBar = ({ time, progress, duration }: Props) => { }, }} > - {`${convertMillisecondsToMinSec(timeMS)} remaining `} + {t('timeRemaining', { time: convertMillisecondsToMinSec(timeMS) })} { transition: '0.6s', }} > - for this item + {` ${t('forItem')}`} diff --git a/src/features/Signup/ui/SignupForm.tsx b/src/features/Signup/ui/SignupForm.tsx index eaae94f6f..4db30fb00 100644 --- a/src/features/Signup/ui/SignupForm.tsx +++ b/src/features/Signup/ui/SignupForm.tsx @@ -129,9 +129,9 @@ export const SignupForm = ({ locationState }: SignupFormProps) => { setTerms((prev) => !prev)}> - I agree to the{' '} + {`${t('iAgreeTo')} `} - Terms of Service + {t('termsOfService')} diff --git a/src/features/TakeNow/ui/TakeNowSuccessModal.tsx b/src/features/TakeNow/ui/TakeNowSuccessModal.tsx index 575ef5682..847664314 100644 --- a/src/features/TakeNow/ui/TakeNowSuccessModal.tsx +++ b/src/features/TakeNow/ui/TakeNowSuccessModal.tsx @@ -2,7 +2,6 @@ import { useContext } from 'react'; import { TakeNowSuccessModalProps } from '../lib/types'; -import { SurveyContext } from '~/features/PassSurvey'; import { MuiModal } from '~/shared/ui'; import { addFeatureToEvent, @@ -13,6 +12,7 @@ import { ReturnToAdminAppEvent, useCustomTranslation, } from '~/shared/utils'; +import { AppletDetailsContext } from '~/widgets/ActivityGroups/lib'; export const TakeNowSuccessModal = ({ isOpen, @@ -23,7 +23,7 @@ export const TakeNowSuccessModal = ({ submitId, }: TakeNowSuccessModalProps) => { const { t } = useCustomTranslation(); - const { applet } = useContext(SurveyContext); + const { applet } = useContext(AppletDetailsContext); const handleReturnToAdminAppClick = () => { const event: ReturnToAdminAppEvent = { diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json index a07f7296d..c4530857d 100644 --- a/src/i18n/en/translation.json +++ b/src/i18n/en/translation.json @@ -173,8 +173,10 @@ "logIn": "Log In", "success": "Registration completed successfully", "or": "Or", - "pleaseAgreeTerms": "*Please agree to the Terms of Service." - }, + "pleaseAgreeTerms": "*Please agree to the Terms of Service.", + "iAgreeTo": "I agree to the", + "termsOfService": "Terms of Service" + }, "ForgotPassword": { "title": "Reset Password", "formTitle": "Enter the email associated with your account", @@ -329,6 +331,11 @@ "support": "Support", "privacy": "Privacy", "termsOfService": "Terms" - } + }, + "activity": "Activity", + "networkError": "Network Error", + "timeRemaining": "{{time}} remaining", + "forItem": "for this item", + "noApplets": "No applets" } } diff --git a/src/i18n/fr/translation.json b/src/i18n/fr/translation.json index a00e48695..2187d8f82 100644 --- a/src/i18n/fr/translation.json +++ b/src/i18n/fr/translation.json @@ -184,7 +184,9 @@ "account": "Vous avez déjà un compte?", "logIn": "Connexion", "success": "Inscription terminée avec succès", - "pleaseAgreeTerms": "*Please agree to the Terms of Service." + "pleaseAgreeTerms": "*Please agree to the Terms of Service.", + "iAgreeTo": "J'accepte les", + "termsOfService": "Conditions d'Utilisation" }, "ForgotPassword": { "title": "réinitialiser le mot de passe", @@ -348,6 +350,11 @@ "support": "Soutien", "privacy": "Confidentialité", "termsOfService": "Conditions" - } + }, + "activity": "Activité", + "networkError": "Erreur réseau", + "timeRemaining": "{{time}} restantes", + "forItem": "pour cet objet", + "noApplets": "Pas d'applets" } } diff --git a/src/pages/AuthorizedRoutes.tsx b/src/pages/AuthorizedRoutes.tsx index a89b01516..5eeb0ee17 100644 --- a/src/pages/AuthorizedRoutes.tsx +++ b/src/pages/AuthorizedRoutes.tsx @@ -5,6 +5,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import AppletDetailsPage from './AppletDetailsPage'; import AppletListPage from './AppletListPage'; import AutoCompletion from './AutoCompletion'; +import LoginPage from './Login'; import ProfilePage from './Profile'; import PublicAutoCompletion from './PublicAutoCompletion'; import SettingsPage from './Settings'; @@ -48,10 +49,10 @@ function AuthorizedRoutes({ refreshToken }: Props) { } /> } /> } /> - - } /> + } /> + } /> ); diff --git a/src/shared/utils/hooks/useSessionBanners/useSessionBanners.ts b/src/shared/utils/hooks/useSessionBanners/useSessionBanners.ts index 91cd43b2f..dd5d94fda 100644 --- a/src/shared/utils/hooks/useSessionBanners/useSessionBanners.ts +++ b/src/shared/utils/hooks/useSessionBanners/useSessionBanners.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import { useBanners } from '~/entities/banner/model'; import { userModel } from '~/entities/user'; @@ -9,11 +9,9 @@ export const useSessionBanners = () => { const prevIsAuthorized = useRef(isAuthorized); - useEffect(() => { - if (prevIsAuthorized.current !== isAuthorized && !isAuthorized) { - removeAllBanners(); - } + if (prevIsAuthorized.current !== isAuthorized && !isAuthorized) { + removeAllBanners(); + } - prevIsAuthorized.current = isAuthorized; - }, [isAuthorized, removeAllBanners]); + prevIsAuthorized.current = isAuthorized; }; diff --git a/src/widgets/AppletList/index.tsx b/src/widgets/AppletList/index.tsx index 8c95268de..573e1cc96 100644 --- a/src/widgets/AppletList/index.tsx +++ b/src/widgets/AppletList/index.tsx @@ -3,8 +3,10 @@ import { userModel } from '~/entities/user'; import { Box } from '~/shared/ui'; import { Text } from '~/shared/ui'; import Loader from '~/shared/ui/Loader'; +import { useCustomTranslation } from '~/shared/utils'; export const AppletListWidget = () => { + const { t } = useCustomTranslation(); const { user } = userModel.hooks.useUserState(); const { @@ -36,7 +38,7 @@ export const AppletListWidget = () => { if (isAppletsEmpty) { return ( - No applets + {t('noApplets')} ); } diff --git a/src/widgets/Survey/ui/ActivityMetaData.tsx b/src/widgets/Survey/ui/ActivityMetaData.tsx index 1a300c2c3..0eb86b0f7 100644 --- a/src/widgets/Survey/ui/ActivityMetaData.tsx +++ b/src/widgets/Survey/ui/ActivityMetaData.tsx @@ -23,7 +23,7 @@ export const ActivityMetaData = ({ activityLength, isFlow, activityOrderInFlow } variant="body1" component="span" testid="metadata-activity-serial-number" - >{`Activity ${activityOrderInFlow} `} + >{`${t('activity')} ${activityOrderInFlow} `} •