From c1a90b0009fdb1fe466d82fa4c0c4e59b6783c29 Mon Sep 17 00:00:00 2001 From: Vahag Date: Tue, 10 Feb 2026 08:54:12 +0100 Subject: [PATCH] [IS-10713] draft implementation for language selector feature for SSP --- .../app/src/shared/ui/header/Header.tsx | 130 +++++++++++++++++- .../src/shared/ui/header/header.module.css | 59 +++++++- 2 files changed, 185 insertions(+), 4 deletions(-) diff --git a/src/self-service-portal/app/src/shared/ui/header/Header.tsx b/src/self-service-portal/app/src/shared/ui/header/Header.tsx index affdaab..dd7bc2d 100644 --- a/src/self-service-portal/app/src/shared/ui/header/Header.tsx +++ b/src/self-service-portal/app/src/shared/ui/header/Header.tsx @@ -12,11 +12,14 @@ import { useAuth } from '@/auth/data-access/AuthProvider'; import { usePageTitle } from '@/shared/utils/useRouteTitle'; import { Breadcrumbs, Button, toUiKitTranslation, UserMenu } from '@curity/ui-kit-component-library'; -import { IconGeneralKebabMenu, IconVciCredentialHome } from '@curity/ui-kit-icons'; -import { useEffect } from 'react'; +import { IconGeneralChevron, IconGeneralKebabMenu, IconGeneralLock, IconVciCredentialHome } from '@curity/ui-kit-icons'; +import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router'; import styles from './header.module.css'; +import { BOOTSTRAP_UI_CONFIG } from '@/BOOTSTRAP_UI_CONFIG.ts'; +import { UiConfigMetadataResponse } from '@/ui-config/typings.ts'; +import { setupI18nTranslations } from '@/i18n/setup-translations.ts'; interface HeaderProps { toggleSidebar: () => void; @@ -28,6 +31,72 @@ export const Header = ({ toggleSidebar, isSidebarOpen }: HeaderProps) => { const uiKitT = toUiKitTranslation(t); const pageTitle = usePageTitle(); const authContext = useAuth(); + const menuContainerRef = useRef(null); + const menuButtonRef = useRef(null); + + const [isLanguageSelectorOpen, setIsLanguageSelectorOpen] = useState(false); + const [language, setLanguage] = useState(null); + + const onSelectLanguage = (e: React.MouseEvent) => { + setLanguage(e.target.innerText.toLowerCase()); + }; + + async function setNewLanguage(requestURI: string): Promise<{ [key: string]: string }> { + const uiConfigMetadataResponse = await fetch(requestURI, { + credentials: 'include', + }); + + if (uiConfigMetadataResponse.status !== 200) { + throw new Error('Failed to fetch metadata'); + } + + const uiConfigMetadataResponseJSON: UiConfigMetadataResponse = await uiConfigMetadataResponse.json(); + const { messages } = uiConfigMetadataResponseJSON; + + return messages; + } + + useEffect(() => { + if (!language) { + return; + } + + const requestURI = `${BOOTSTRAP_UI_CONFIG.PATHS.BACKEND}${BOOTSTRAP_UI_CONFIG.PATHS.METADATA}?ui_locales=${language}`; + console.log('requestURI', requestURI); + setNewLanguage(requestURI).then(messages => { + setupI18nTranslations(messages); + }); + }, [language]); + + // TODO: this logic should be extracted to a custom hook, as it is repeated in the UserMenu component. + // The hook should receive as parameters whether the menu is open, (as well as the setter for it) + // a ref to the menu container and a ref to the button that opens the menu, and should return a function to toggle the menu open state + useEffect(() => { + if (!isLanguageSelectorOpen) return; + + const closeMenuOnOutsideClick = (event: MouseEvent) => { + const clickedOutsideMenu = menuContainerRef.current && !menuContainerRef.current.contains(event.target as Node); + + if (clickedOutsideMenu) { + setIsLanguageSelectorOpen(false); + } + }; + + const closeMenuOnEscape = (event: globalThis.KeyboardEvent) => { + if (event.key === 'Escape' && isLanguageSelectorOpen) { + setIsLanguageSelectorOpen(false); + menuButtonRef.current?.focus(); + } + }; + + document.addEventListener('mousedown', closeMenuOnOutsideClick); + document.addEventListener('keydown', closeMenuOnEscape); + + return () => { + document.removeEventListener('mousedown', closeMenuOnOutsideClick); + document.removeEventListener('keydown', closeMenuOnEscape); + }; + }, [isLanguageSelectorOpen]); useEffect(() => { document.title = pageTitle; @@ -48,7 +117,62 @@ export const Header = ({ toggleSidebar, isSidebarOpen }: HeaderProps) => {
{authContext?.session?.isLoggedIn && ( - + <> + {/* + TODO: the language selector should be extracted to its own component in @curity/ui-kit-component-library, + as it is a separate concern from the user menu and it will make the Header component cleaner and more readable. + */} +
+ + + {isLanguageSelectorOpen && ( + + )} +
+ + + )}