diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c0b28c4c..f11b17c6ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ The types of changes are: - Removed properties beta flag [#4710](https://github.com/ethyca/fides/pull/4710) - Add acknowledge button label to default Experience English form [#4714](https://github.com/ethyca/fides/pull/4714) - Update FidesJS to support localizing CMP UI with configurable, non-English default locales [#4720](https://github.com/ethyca/fides/pull/4720) +- Add loading of template translations for notices and experiences [#4718](https://github.com/ethyca/fides/pull/4718) ### Changed - Moved location-targeting from Notices to Experiences [#4576](https://github.com/ethyca/fides/pull/4576) diff --git a/clients/admin-ui/src/features/common/ScrollableList.tsx b/clients/admin-ui/src/features/common/ScrollableList.tsx index f91e4439ea..56ca78f538 100644 --- a/clients/admin-ui/src/features/common/ScrollableList.tsx +++ b/clients/admin-ui/src/features/common/ScrollableList.tsx @@ -39,6 +39,7 @@ const ScrollableListItem = ({ direction="row" gap={2} maxH={maxH} + w="full" px={2} align="center" role="group" @@ -66,12 +67,18 @@ const ScrollableListItem = ({ onRowClick(item); } }} + overflow="clip" > - + {label} - {onDeleteItem ? ( ({ borderColor: "gray.200", borderRadius: "md", w: "full", - overflowY: "hidden", + maxH: "8.5rem", + overflowY: "auto", } as ChakraProps; const innerList = draggable ? ( diff --git a/clients/admin-ui/src/features/common/api.slice.ts b/clients/admin-ui/src/features/common/api.slice.ts index 0e56908f90..0d25bbd185 100644 --- a/clients/admin-ui/src/features/common/api.slice.ts +++ b/clients/admin-ui/src/features/common/api.slice.ts @@ -44,7 +44,9 @@ export const baseApi = createApi({ "Organization", "Plus", "Privacy Experience Configs", + "Experience Config Translations", "Privacy Notices", + "Privacy Notice Translations", "Property", "Purpose", "System", diff --git a/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx b/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx index 66330225ef..d370144c66 100644 --- a/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx +++ b/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx @@ -42,6 +42,7 @@ import { ExperienceConfigCreate, ExperienceConfigResponse, ExperienceTranslation, + SupportedLanguage, } from "~/types/api"; import { isErrorResult } from "~/types/errors"; @@ -65,7 +66,6 @@ const translationSchema = (requirePreferencesLink: boolean) => const validationSchema = Yup.object().shape({ name: Yup.string().required().label("Experience name"), component: Yup.string().required().label("Experience type"), - // translations: Yup.array().of(translationSchema), translations: Yup.array().when("component", { is: (value: ComponentType) => value === ComponentType.BANNER_AND_MODAL || @@ -77,8 +77,10 @@ const validationSchema = Yup.object().shape({ const ConfigurePrivacyExperience = ({ passedInExperience, + passedInTranslations, }: { passedInExperience?: ExperienceConfigResponse; + passedInTranslations?: ExperienceTranslation[]; }) => { const [postExperienceConfigMutation] = usePostExperienceConfigMutation(); const [patchExperienceConfigMutation] = usePatchExperienceConfigMutation(); @@ -136,13 +138,35 @@ const ConfigurePrivacyExperience = ({ TranslationWithLanguageName | undefined >(undefined); - const handleNewTranslationSelected = (translation: ExperienceTranslation) => { + const [usingOOBValues, setUsingOOBValues] = useState(false); + + const handleTranslationSelected = (translation: ExperienceTranslation) => { setTranslationToEdit({ ...translation, name: findLanguageDisplayName(translation, allLanguages), }); }; + const handleCreateNewTranslation = (language: SupportedLanguage) => { + const availableTranslation = passedInTranslations?.find( + (t) => t.language === language + ); + if (availableTranslation) { + setUsingOOBValues(true); + } + return ( + availableTranslation ?? { + language, + is_default: false, + } + ); + }; + + const handleExitTranslationForm = () => { + setTranslationToEdit(undefined); + setUsingOOBValues(false); + }; + return ( setTranslationToEdit(undefined)} + isOOB={usingOOBValues} + onReturnToMainForm={handleExitTranslationForm} /> ) : ( )} diff --git a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx index 900a3bcbcc..91ad8ab5e2 100644 --- a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx +++ b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx @@ -70,7 +70,7 @@ export const PrivacyExperienceConfigColumnLayout = ({ children: React.ReactNode; }) => ( - + {children} @@ -82,9 +82,11 @@ export const PrivacyExperienceConfigColumnLayout = ({ export const PrivacyExperienceForm = ({ allPrivacyNotices, onSelectTranslation, + onCreateTranslation, }: { allPrivacyNotices: LimitedPrivacyNoticeResponseSchema[]; onSelectTranslation: (t: ExperienceTranslation) => void; + onCreateTranslation: (lang: SupportedLanguage) => ExperienceTranslation; }) => { const router = useRouter(); @@ -254,10 +256,9 @@ export const PrivacyExperienceForm = ({ is_default: false, }))} getItemLabel={getTranslationDisplayName} - createNewValue={(opt) => ({ - language: opt.value as SupportedLanguage, - is_default: false, - })} + createNewValue={(opt) => + onCreateTranslation(opt.value as SupportedLanguage) + } onRowClick={onSelectTranslation} selectOnAdd draggable diff --git a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx index d3d744ca3f..3efcf5e64a 100644 --- a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx +++ b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx @@ -14,6 +14,7 @@ import { CustomTextArea, CustomTextInput, } from "~/features/common/form/inputs"; +import InfoBox from "~/features/common/InfoBox"; import WarningModal from "~/features/common/modals/WarningModal"; import { BackButtonNonLink } from "~/features/common/nav/v2/BackButton"; import { @@ -27,11 +28,23 @@ import { ExperienceTranslation, } from "~/types/api"; +export const OOBTranslationNotice = ({ + languageName, +}: { + languageName: string; +}) => ( + +); + const PrivacyExperienceTranslationForm = ({ translation, + isOOB, onReturnToMainForm, }: { translation: TranslationWithLanguageName; + isOOB?: boolean; onReturnToMainForm: () => void; }) => { const { values, setFieldValue, errors, touched, setTouched } = @@ -42,7 +55,7 @@ const PrivacyExperienceTranslationForm = ({ const { name, ...rest } = translation; return rest as ExperienceTranslation; }, [translation]); - const isEditing = !!initialTranslation.title; + const isEditing = !!initialTranslation.title && !isOOB; const formConfig = getTranslationFormFields(values.component); @@ -90,7 +103,7 @@ const PrivacyExperienceTranslationForm = ({ }; const handleLeaveForm = () => { - if (translationIsTouched || !initialTranslation.title) { + if (translationIsTouched || isOOB) { onOpenUnsavedChanges(); } else { onReturnToMainForm(); @@ -126,7 +139,7 @@ const PrivacyExperienceTranslationForm = ({