From 2163fae7ae659e5bbd4ef2330b1d1cde75203498 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 10 Jan 2024 09:38:02 +0100 Subject: [PATCH] use content json also for own menu (#25744) * use content json also for own menu Signed-off-by: Erik Jan de Wit * change isHidden with isVisible Signed-off-by: Erik Jan de Wit --------- Signed-off-by: Erik Jan de Wit --- js/apps/account-ui/public/content.json | 26 +++++ .../src/content/ContentComponent.tsx | 18 ++-- .../src/content/ContentRenderer.tsx | 101 ------------------ js/apps/account-ui/src/content/content.ts | 38 ------- .../account-ui/src/content/fetchContent.ts | 14 +++ js/apps/account-ui/src/environment.ts | 28 ++--- js/apps/account-ui/src/root/PageNav.tsx | 97 +++++++---------- 7 files changed, 102 insertions(+), 220 deletions(-) create mode 100644 js/apps/account-ui/public/content.json delete mode 100644 js/apps/account-ui/src/content/ContentRenderer.tsx delete mode 100644 js/apps/account-ui/src/content/content.ts create mode 100644 js/apps/account-ui/src/content/fetchContent.ts diff --git a/js/apps/account-ui/public/content.json b/js/apps/account-ui/public/content.json new file mode 100644 index 0000000000..04b9379c55 --- /dev/null +++ b/js/apps/account-ui/public/content.json @@ -0,0 +1,26 @@ +[ + { "label": "personalInfo", "path": "/" }, + { + "label": "accountSecurity", + "children": [ + { "label": "signingIn", "path": "account-security/signing-in" }, + { "label": "deviceActivity", "path": "account-security/device-activity" }, + { + "label": "linkedAccounts", + "path": "account-security/linked-accounts", + "isVisible": "isLinkedAccountsEnabled" + } + ] + }, + { "label": "applications", "path": "applications" }, + { + "label": "groups", + "path": "groups", + "isVisible": "isViewGroupsEnabled" + }, + { + "label": "resources", + "path": "resources", + "isVisible": "isMyResourcesEnabled" + } +] diff --git a/js/apps/account-ui/src/content/ContentComponent.tsx b/js/apps/account-ui/src/content/ContentComponent.tsx index 15efefbb36..1c88853eaf 100644 --- a/js/apps/account-ui/src/content/ContentComponent.tsx +++ b/js/apps/account-ui/src/content/ContentComponent.tsx @@ -2,29 +2,29 @@ import { Spinner } from "@patternfly/react-core"; import { Suspense, lazy, useMemo, useState } from "react"; import { useParams } from "react-router-dom"; import { environment } from "../environment"; +import { ContentComponentParams } from "../routes"; import { joinPath } from "../utils/joinPath"; import { usePromise } from "../utils/usePromise"; -import { fetchContentJson } from "./ContentRenderer"; -import { ContentItem, ModulePageDef, isExpansion } from "./content"; -import { ContentComponentParams } from "../routes"; +import fetchContentJson from "./fetchContent"; +import { MenuItem } from "../root/PageNav"; function findComponent( - content: ContentItem[], + content: MenuItem[], componentId: string, ): string | undefined { for (const item of content) { - if ("path" in item && item.path === componentId) { - return (item as ModulePageDef).modulePath; + if ("path" in item && item.path === componentId && "modulePath" in item) { + return item.modulePath; } - if (isExpansion(item)) { - return findComponent(item.content, componentId); + if ("children" in item) { + return findComponent(item.children, componentId); } } return undefined; } const ContentComponent = () => { - const [content, setContent] = useState(); + const [content, setContent] = useState(); const { componentId } = useParams(); usePromise((signal) => fetchContentJson({ signal }), setContent); diff --git a/js/apps/account-ui/src/content/ContentRenderer.tsx b/js/apps/account-ui/src/content/ContentRenderer.tsx deleted file mode 100644 index 7a4bdbbbd6..0000000000 --- a/js/apps/account-ui/src/content/ContentRenderer.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { NavExpandable, Spinner } from "@patternfly/react-core"; -import { TFunction } from "i18next"; -import { Suspense, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useParams } from "react-router-dom"; -import { CallOptions } from "../api/methods"; -import { environment } from "../environment"; -import { TFuncKey } from "../i18n"; -import { NavLink } from "../root/PageNav"; -import { ContentComponentParams } from "../routes"; -import { joinPath } from "../utils/joinPath"; -import { usePromise } from "../utils/usePromise"; -import { - ContentItem, - Expansion, - PageDef, - isChildOf, - isExpansion, -} from "./content"; - -export async function fetchContentJson( - opts: CallOptions = {}, -): Promise { - const response = await fetch( - joinPath(environment.resourceUrl, "/content.json"), - opts, - ); - return await response.json(); -} - -function createNavItem(page: PageDef, activePage: string, t: TFunction) { - return ( - - {t(page.label as TFuncKey)} - - ); -} - -function createExpandableNav( - item: Expansion, - t: TFunction, - activePage: string, - groupNum: number, -) { - return ( - - {createNavItems(t, activePage, item.content, groupNum + 1)} - - ); -} - -function createNavItems( - t: TFunction, - activePage: string, - contentParam: ContentItem[], - groupNum: number, -) { - return contentParam.map((item: ContentItem) => { - if (isExpansion(item)) { - return createExpandableNav(item, t, activePage!, groupNum); - } else { - const page: PageDef = item as PageDef; - return createNavItem(page, activePage, t); - } - }); -} - -type MenuProps = { - content: ContentItem[]; -}; - -const Menu = ({ content }: MenuProps) => { - const { t } = useTranslation(); - const { componentId: activePage } = useParams(); - - const groupNum = 0; - return content.map((item) => { - if (isExpansion(item)) { - return createExpandableNav(item, t, activePage!, groupNum); - } else { - const page: PageDef = item as PageDef; - return createNavItem(page, activePage!, t); - } - }); -}; - -export const ContentMenu = () => { - const [content, setContent] = useState(); - - usePromise((signal) => fetchContentJson({ signal }), setContent); - - return ( - }> - {content && } - - ); -}; diff --git a/js/apps/account-ui/src/content/content.ts b/js/apps/account-ui/src/content/content.ts deleted file mode 100644 index 44b46fd7c4..0000000000 --- a/js/apps/account-ui/src/content/content.ts +++ /dev/null @@ -1,38 +0,0 @@ -export interface ContentItem { - id?: string; - label: string; - labelParams?: string[]; - hidden?: string; -} - -export interface Expansion extends ContentItem { - content: ContentItem[]; -} - -export interface PageDef extends ContentItem { - path: string; -} - -export interface ComponentPageDef extends PageDef { - component: React.ComponentType; -} - -export interface ModulePageDef extends PageDef { - modulePath: string; - componentName: string; -} - -export function isExpansion( - contentItem: ContentItem, -): contentItem is Expansion { - return "content" in contentItem; -} - -export function isChildOf(parent: Expansion, child: string): boolean { - for (const item of parent.content) { - if (isExpansion(item) && isChildOf(item, child)) return true; - if ("path" in item && item.path === child) return true; - } - - return false; -} diff --git a/js/apps/account-ui/src/content/fetchContent.ts b/js/apps/account-ui/src/content/fetchContent.ts new file mode 100644 index 0000000000..94c2d98447 --- /dev/null +++ b/js/apps/account-ui/src/content/fetchContent.ts @@ -0,0 +1,14 @@ +import { CallOptions } from "../api/methods"; +import { environment } from "../environment"; +import { MenuItem } from "../root/PageNav"; +import { joinPath } from "../utils/joinPath"; + +export default async function fetchContentJson( + opts: CallOptions = {}, +): Promise { + const response = await fetch( + joinPath(environment.resourceUrl, "/content.json"), + opts, + ); + return await response.json(); +} diff --git a/js/apps/account-ui/src/environment.ts b/js/apps/account-ui/src/environment.ts index 425e0c26d9..21ca0f7712 100644 --- a/js/apps/account-ui/src/environment.ts +++ b/js/apps/account-ui/src/environment.ts @@ -1,3 +1,17 @@ +export type Feature = { + isRegistrationEmailAsUsername: boolean; + isEditUserNameAllowed: boolean; + isInternationalizationEnabled: boolean; + isLinkedAccountsEnabled: boolean; + isEventsEnabled: boolean; + isMyResourcesEnabled: boolean; + isTotpConfigured: boolean; + deleteAccountAllowed: boolean; + updateEmailFeatureEnabled: boolean; + updateEmailActionEnabled: boolean; + isViewGroupsEnabled: boolean; +}; + export type Environment = { /** The URL to the root of the auth server. */ authUrl: string; @@ -14,19 +28,7 @@ export type Environment = { /** The locale of the user */ locale: string; /** Feature flags */ - features: { - isRegistrationEmailAsUsername: boolean; - isEditUserNameAllowed: boolean; - isInternationalizationEnabled: boolean; - isLinkedAccountsEnabled: boolean; - isEventsEnabled: boolean; - isMyResourcesEnabled: boolean; - isTotpConfigured: boolean; - deleteAccountAllowed: boolean; - updateEmailFeatureEnabled: boolean; - updateEmailActionEnabled: boolean; - isViewGroupsEnabled: boolean; - }; + features: Feature; }; // The default environment, used during development. diff --git a/js/apps/account-ui/src/root/PageNav.tsx b/js/apps/account-ui/src/root/PageNav.tsx index dcba03455d..e366e23a22 100644 --- a/js/apps/account-ui/src/root/PageNav.tsx +++ b/js/apps/account-ui/src/root/PageNav.tsx @@ -4,11 +4,14 @@ import { NavItem, NavList, PageSidebar, + Spinner, } from "@patternfly/react-core"; import { PropsWithChildren, MouseEvent as ReactMouseEvent, + Suspense, useMemo, + useState, } from "react"; import { useTranslation } from "react-i18next"; import { @@ -18,79 +21,55 @@ import { useLinkClickHandler, useLocation, } from "react-router-dom"; -import { ContentMenu } from "../content/ContentRenderer"; -import { environment } from "../environment"; +import fetchContentJson from "../content/fetchContent"; import { TFuncKey } from "../i18n"; +import { usePromise } from "../utils/usePromise"; +import { Feature, environment } from "../environment"; type RootMenuItem = { label: TFuncKey; path: string; - isHidden?: boolean; + isHidden?: keyof Feature; + modulePath?: string; }; type MenuItemWithChildren = { label: TFuncKey; children: MenuItem[]; - isHidden?: boolean; + isHidden?: keyof Feature; }; -type MenuItem = RootMenuItem | MenuItemWithChildren; +export type MenuItem = RootMenuItem | MenuItemWithChildren; -const menuItems: MenuItem[] = [ - { - label: "personalInfo", - path: "/", - }, - { - label: "accountSecurity", - children: [ - { - label: "signingIn", - path: "account-security/signing-in", - }, - { - label: "deviceActivity", - path: "account-security/device-activity", - }, - { - label: "linkedAccounts", - path: "account-security/linked-accounts", - isHidden: !environment.features.isLinkedAccountsEnabled, - }, - ], - }, - { - label: "applications", - path: "applications", - }, - { - label: "groups", - path: "groups", - isHidden: !environment.features.isViewGroupsEnabled, - }, - { - label: "resources", - path: "resources", - isHidden: !environment.features.isMyResourcesEnabled, - }, -]; +export const PageNav = () => { + const [menuItems, setMenuItems] = useState(); -export const PageNav = () => ( - - - - {menuItems - .filter((menuItem) => !menuItem.isHidden) - .map((menuItem) => ( - - ))} - - - } - /> -); + usePromise((signal) => fetchContentJson({ signal }), setMenuItems); + return ( + + + }> + {menuItems + ?.filter((menuItem) => + menuItem.isHidden + ? environment.features[menuItem.isHidden] + : true, + ) + .map((menuItem) => ( + + ))} + + + + } + /> + ); +}; type NavMenuItemProps = { menuItem: MenuItem;