diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 1858dc30..d47fe664 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -5,11 +5,21 @@ import { ShellBarComponent } from './components/Core/ShellBar.tsx'; import { SentryRoutes } from './mount.ts'; import ProjectPage from './spaces/onboarding/pages/ProjectPage.tsx'; import McpPage from './spaces/mcp/pages/McpPage.tsx'; +import { SearchParamToggleVisibility } from './components/Helper/FeatureToggleExistance.tsx'; function AppRouter() { return ( <> - + { + if (params === undefined) return true; + if (params.get('showHeaderBar') === null) return true; + return params?.get('showHeaderBar') === 'true'; + }} + > + + + }> diff --git a/src/components/Core/BetaButton.tsx b/src/components/Core/BetaButton.tsx new file mode 100644 index 00000000..3f76eba7 --- /dev/null +++ b/src/components/Core/BetaButton.tsx @@ -0,0 +1,58 @@ +import { ButtonDomRef, Button, Icon, PopoverDomRef, Popover, Text } from '@ui5/webcomponents-react'; +import { useState, useRef, RefObject } from 'react'; +import styles from './ShellBar.module.css'; +import { useTranslation } from 'react-i18next'; +import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js'; +import { ThemingParameters } from '@ui5/webcomponents-react-base'; + +export function BetaButton() { + const [betaPopoverOpen, setBetaPopoverOpen] = useState(false); + const betaButtonRef = useRef(null); + const betaPopoverRef = useRef(null); + const { t } = useTranslation(); + + const onBetaClick = () => { + if (betaButtonRef.current) { + betaPopoverRef.current!.opener = betaButtonRef.current; + setBetaPopoverOpen(!betaPopoverOpen); + } + }; + + return ( + <> + + + + {t('ShellBar.betaButton')} + + + + > + ); +} + +const BetaPopover = ({ + open, + setOpen, + popoverRef, +}: { + open: boolean; + setOpen: (arg0: boolean) => void; + popoverRef: RefObject; +}) => { + const { t } = useTranslation(); + + return ( + setOpen(false)}> + + {t('ShellBar.betaButtonDescription')} + + + ); +}; diff --git a/src/components/Core/FeedbackButton.tsx b/src/components/Core/FeedbackButton.tsx new file mode 100644 index 00000000..1fc3d8aa --- /dev/null +++ b/src/components/Core/FeedbackButton.tsx @@ -0,0 +1,170 @@ +import { + PopoverDomRef, + Ui5CustomEvent, + TextAreaDomRef, + Button, + ButtonDomRef, + Popover, + Form, + FormGroup, + FormItem, + Label, + Link, + RatingIndicator, + TextArea, +} from '@ui5/webcomponents-react'; +import { Dispatch, RefObject, SetStateAction, useRef, useState } from 'react'; +import { useAuthOnboarding } from '../../spaces/onboarding/auth/AuthContextOnboarding'; +import { useTranslation } from 'react-i18next'; +import { ButtonClickEventDetail } from '@ui5/webcomponents/dist/Button.js'; +import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js'; +import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js'; + +type UI5RatingIndicatorElement = HTMLElement & { value: number }; + +export function FeedbackButton() { + const feedbackPopoverRef = useRef(null); + const [feedbackMessage, setFeedbackMessage] = useState(''); + const [feedbackSent, setFeedbackSent] = useState(false); + const [feedbackPopoverOpen, setFeedbackPopoverOpen] = useState(false); + const [rating, setRating] = useState(0); + const { user } = useAuthOnboarding(); + + const onFeedbackClick = (e: Ui5CustomEvent) => { + feedbackPopoverRef.current!.opener = e.target; + setFeedbackPopoverOpen(!feedbackPopoverOpen); + }; + + const onFeedbackMessageChange = (event: Ui5CustomEvent) => { + const newValue = event.target.value; + setFeedbackMessage(newValue); + }; + + async function onFeedbackSent() { + const payload = { + message: feedbackMessage, + rating: rating.toString(), + user: user?.email, + environment: window.location.hostname, + }; + try { + await fetch('/api/feedback', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + } catch (err) { + console.log(err); + } finally { + setFeedbackSent(true); + } + } + + return ( + <> + + + > + ); +} + +const FeedbackPopover = ({ + open, + setOpen, + popoverRef, + setRating, + rating, + onFeedbackSent, + feedbackMessage, + onFeedbackMessageChange, + feedbackSent, +}: { + open: boolean; + setOpen: (arg0: boolean) => void; + popoverRef: RefObject; + setRating: Dispatch>; + rating: number; + onFeedbackSent: () => void; + feedbackMessage: string; + onFeedbackMessageChange: ( + event: Ui5CustomEvent< + TextAreaDomRef, + { + value: string; + previousValue: string; + } + >, + ) => void; + feedbackSent: boolean; +}) => { + const { t } = useTranslation(); + + const onRatingChange = (event: Event & { target: UI5RatingIndicatorElement }) => { + setRating(event.target.value); + }; + + return ( + <> + setOpen(false)}> + + {!feedbackSent ? ( + + + {t('ShellBar.feedbackRatingLabel')}}> + + + {t('ShellBar.feedbackMessageLabel')}} + > + + + + onFeedbackSent()}> + {t('ShellBar.feedbackButton')} + + + + + {t('ShellBar.feedbackNotificationText')} + + {t('ShellBar.feedbackNotificationAction')} + + + + + + ) : ( + {t('ShellBar.feedbackThanks')} + )} + + + > + ); +}; diff --git a/src/components/Core/IntelligentBreadcrumbs.tsx b/src/components/Core/IntelligentBreadcrumbs.tsx index 1c07923d..8e641ce2 100644 --- a/src/components/Core/IntelligentBreadcrumbs.tsx +++ b/src/components/Core/IntelligentBreadcrumbs.tsx @@ -1,9 +1,23 @@ -import { Breadcrumbs, BreadcrumbsDomRef, Ui5CustomEvent } from '@ui5/webcomponents-react'; +import { + Breadcrumbs, + BreadcrumbsDomRef, + Button, + FlexBox, + FlexBoxAlignItems, + Menu, + MenuItem, + Ui5CustomEvent, +} from '@ui5/webcomponents-react'; import { BreadcrumbsItem } from '@ui5/webcomponents-react/wrappers'; import { NavigateOptions, useParams } from 'react-router-dom'; import useLuigiNavigate from '../Shared/useLuigiNavigate.tsx'; import LandscapeLabel from './LandscapeLabel.tsx'; import { useTranslation } from 'react-i18next'; +import { FeedbackButton } from './FeedbackButton.tsx'; +import { BetaButton } from './BetaButton.tsx'; +import { useRef, useState } from 'react'; +import { useAuthOnboarding } from '../../spaces/onboarding/auth/AuthContextOnboarding.tsx'; +import { SearchParamToggleVisibility } from '../Helper/FeatureToggleExistance.tsx'; const PREFIX = '/mcp'; @@ -58,3 +72,57 @@ export default function IntelligentBreadcrumbs() { > ); } + +export function BreadCrumbFeedbackHeader() { + return ( + + + + + { + if (params === undefined) return false; + if (params.get('showHeaderBar') === null) return false; + return params?.get('showHeaderBar') === 'false'; + }} + > + + + + ); +} + +function LogoutMenu() { + const auth = useAuthOnboarding(); + const { t } = useTranslation(); + + const buttonRef = useRef(null); + const [menuIsOpen, setMenuIsOpen] = useState(false); + return ( + <> + { + setMenuIsOpen(true); + }} + /> + { + setMenuIsOpen(false); + }} + > + { + setMenuIsOpen(false); + await auth.logout(); + }} + /> + + > + ); +} diff --git a/src/components/Core/ShellBar.tsx b/src/components/Core/ShellBar.tsx index a5f3c75f..c08b8edf 100644 --- a/src/components/Core/ShellBar.tsx +++ b/src/components/Core/ShellBar.tsx @@ -1,98 +1,31 @@ import { Avatar, - Button, - ButtonDomRef, - Form, - FormGroup, - FormItem, - Icon, - Label, - Link, List, ListItemStandard, Popover, PopoverDomRef, - RatingIndicator, ShellBar, ShellBarDomRef, - ShellBarItem, - ShellBarItemDomRef, - TextArea, - TextAreaDomRef, Ui5CustomEvent, } from '@ui5/webcomponents-react'; import { useAuthOnboarding } from '../../spaces/onboarding/auth/AuthContextOnboarding.tsx'; -import { Dispatch, RefObject, SetStateAction, useEffect, useRef, useState } from 'react'; +import { RefObject, useEffect, useRef, useState } from 'react'; import { ShellBarProfileClickEventDetail } from '@ui5/webcomponents-fiori/dist/ShellBar.js'; import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js'; import { useTranslation } from 'react-i18next'; import { generateInitialsForEmail } from '../Helper/generateInitialsForEmail.ts'; import styles from './ShellBar.module.css'; -import { ThemingParameters } from '@ui5/webcomponents-react-base'; -import { ShellBarItemClickEventDetail } from '@ui5/webcomponents-fiori/dist/ShellBarItem.js'; - -type UI5RatingIndicatorElement = HTMLElement & { value: number }; export function ShellBarComponent() { const auth = useAuthOnboarding(); const profilePopoverRef = useRef(null); - const betaPopoverRef = useRef(null); - const feedbackPopoverRef = useRef(null); const [profilePopoverOpen, setProfilePopoverOpen] = useState(false); - const [betaPopoverOpen, setBetaPopoverOpen] = useState(false); - const [feedbackPopoverOpen, setFeedbackPopoverOpen] = useState(false); - const [rating, setRating] = useState(0); - const [feedbackMessage, setFeedbackMessage] = useState(''); - const [feedbackSent, setFeedbackSent] = useState(false); - const betaButtonRef = useRef(null); - - const { user } = useAuthOnboarding(); - const { t } = useTranslation(); const onProfileClick = (e: Ui5CustomEvent) => { profilePopoverRef.current!.opener = e.detail.targetRef; setProfilePopoverOpen(!profilePopoverOpen); }; - const onBetaClick = () => { - if (betaButtonRef.current) { - betaPopoverRef.current!.opener = betaButtonRef.current; - setBetaPopoverOpen(!betaPopoverOpen); - } - }; - - const onFeedbackClick = (e: Ui5CustomEvent) => { - feedbackPopoverRef.current!.opener = e.detail.targetRef; - setFeedbackPopoverOpen(!feedbackPopoverOpen); - }; - - const onFeedbackMessageChange = (event: Ui5CustomEvent) => { - const newValue = event.target.value; - setFeedbackMessage(newValue); - }; - - async function onFeedbackSent() { - const payload = { - message: feedbackMessage, - rating: rating.toString(), - user: user?.email, - environment: window.location.hostname, - }; - try { - await fetch('/api/feedback', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - } catch (err) { - console.log(err); - } finally { - setFeedbackSent(true); - } - } - useEffect(() => { const shellbar = document.querySelector('ui5-shellbar'); const el = shellbar?.shadowRoot?.querySelector('.ui5-shellbar-overflow-container-left'); @@ -106,6 +39,7 @@ export function ShellBarComponent() { <> } startButton={ @@ -113,32 +47,12 @@ export function ShellBarComponent() { MCP - - - - {t('ShellBar.betaButton')} - - } onProfileClick={onProfileClick} - > - - + /> - - > ); } @@ -177,118 +91,3 @@ const ProfilePopover = ({ ); }; - -const BetaPopover = ({ - open, - setOpen, - popoverRef, -}: { - open: boolean; - setOpen: (arg0: boolean) => void; - popoverRef: RefObject; -}) => { - const { t } = useTranslation(); - - return ( - setOpen(false)}> - - {t('ShellBar.betaButtonDescription')} - - - ); -}; - -const FeedbackPopover = ({ - open, - setOpen, - popoverRef, - setRating, - rating, - onFeedbackSent, - feedbackMessage, - onFeedbackMessageChange, - feedbackSent, -}: { - open: boolean; - setOpen: (arg0: boolean) => void; - popoverRef: RefObject; - setRating: Dispatch>; - rating: number; - onFeedbackSent: () => void; - feedbackMessage: string; - onFeedbackMessageChange: ( - event: Ui5CustomEvent< - TextAreaDomRef, - { - value: string; - previousValue: string; - } - >, - ) => void; - feedbackSent: boolean; -}) => { - const { t } = useTranslation(); - - const onRatingChange = (event: Event & { target: UI5RatingIndicatorElement }) => { - setRating(event.target.value); - }; - - return ( - <> - setOpen(false)}> - - {!feedbackSent ? ( - - - {t('ShellBar.feedbackRatingLabel')}}> - - - {t('ShellBar.feedbackMessageLabel')}} - > - - - - onFeedbackSent()}> - {t('ShellBar.feedbackButton')} - - - - - {t('ShellBar.feedbackNotificationText')} - - {t('ShellBar.feedbackNotificationAction')} - - - - - - ) : ( - {t('ShellBar.feedbackThanks')} - )} - - - > - ); -}; diff --git a/src/components/Helper/FeatureToggleExistance.tsx b/src/components/Helper/FeatureToggleExistance.tsx new file mode 100644 index 00000000..3bb1d8de --- /dev/null +++ b/src/components/Helper/FeatureToggleExistance.tsx @@ -0,0 +1,12 @@ +interface FeatureToggleExistanceProps { + shouldBeVisible: (searchParams?: URLSearchParams) => boolean; + children?: React.ReactNode; +} + +// A component that conditionally renders its children based on a flag in the URL search parameters. +// The flag and its handling logic must be set via the `shouldBeVisible` prop function. +export function SearchParamToggleVisibility({ shouldBeVisible, children }: FeatureToggleExistanceProps) { + const searchParams = URL.parse(window.location.href)?.searchParams; + + return <>{shouldBeVisible(searchParams) && children}>; +} diff --git a/src/spaces/mcp/pages/McpPage.tsx b/src/spaces/mcp/pages/McpPage.tsx index 64870281..de04cac0 100644 --- a/src/spaces/mcp/pages/McpPage.tsx +++ b/src/spaces/mcp/pages/McpPage.tsx @@ -7,7 +7,7 @@ import '@ui5/webcomponents-fiori/dist/illustrations/SimpleError'; // thorws error sometimes if not imported import '@ui5/webcomponents-fiori/dist/illustrations/BeforeSearch'; import IllustratedError from '../../../components/Shared/IllustratedError.tsx'; -import IntelligentBreadcrumbs from '../../../components/Core/IntelligentBreadcrumbs.tsx'; +import { BreadCrumbFeedbackHeader } from '../../../components/Core/IntelligentBreadcrumbs.tsx'; import FluxList from '../../../components/ControlPlane/FluxList.tsx'; import { ControlPlane as ControlPlaneResource } from '../../../lib/api/types/crate/controlPlanes.ts'; @@ -65,7 +65,7 @@ export default function McpPage() { titleArea={ } + breadcrumbs={} //TODO: actionBar should use Toolbar and ToolbarButton for consistent design actionsBar={ } - breadcrumbs={} + breadcrumbs={} actionsBar={} /> } diff --git a/src/views/ProjectList.tsx b/src/views/ProjectList.tsx index d9b85c7e..ebb9fdd0 100644 --- a/src/views/ProjectList.tsx +++ b/src/views/ProjectList.tsx @@ -1,6 +1,6 @@ import { ObjectPage, ObjectPageTitle } from '@ui5/webcomponents-react'; import ProjectsList from '../components/Projects/ProjectsList.tsx'; -import IntelligentBreadcrumbs from '../components/Core/IntelligentBreadcrumbs.tsx'; +import { BreadCrumbFeedbackHeader } from '../components/Core/IntelligentBreadcrumbs.tsx'; import { ProjectListToolbar } from '../components/Projects/ProjectListToolbar.tsx'; import { useTranslation } from 'react-i18next'; @@ -13,7 +13,7 @@ export default function ProjectsListView() { titleArea={ } + breadcrumbs={} actionsBar={} /> }